diff --git a/.gitattributes b/.gitattributes
index 58ceb9b..dcc5902 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -5,3 +5,5 @@
*.py text eol=lf
*.toml text eol=lf
*.yml text eol=lf
+*.cose binary
+*.msgpack binary
diff --git a/.github/ISSUE_TEMPLATE/implementation-report.yml b/.github/ISSUE_TEMPLATE/implementation-report.yml
index 41701c8..82ecdd4 100644
--- a/.github/ISSUE_TEMPLATE/implementation-report.yml
+++ b/.github/ISSUE_TEMPLATE/implementation-report.yml
@@ -5,7 +5,7 @@ labels: ["implementation", "interoperability"]
body:
- type: markdown
attributes:
- value: An implementation report is evidence of an attempt, not endorsement, adoption, certification, or conformance unless the stated tests establish that bounded result.
+ value: An implementation report for the Verifier Standard (VSTD) is evidence of an attempt, not endorsement, adoption, certification, or conformance unless the stated tests establish that bounded result.
- type: input
id: implementation
attributes:
@@ -17,7 +17,7 @@ body:
id: coordinate
attributes:
label: VSTD coordinate
- description: Release, layer, wire identifier, and supported profile.
+ description: Release, numbered profile or closure coordinate, serialized receipt identifier, and supported receipt or application profile.
validations:
required: true
- type: textarea
diff --git a/.github/ISSUE_TEMPLATE/specification-ambiguity.yml b/.github/ISSUE_TEMPLATE/specification-ambiguity.yml
index 1a4f478..531a445 100644
--- a/.github/ISSUE_TEMPLATE/specification-ambiguity.yml
+++ b/.github/ISSUE_TEMPLATE/specification-ambiguity.yml
@@ -5,13 +5,13 @@ labels: ["specification", "needs-triage"]
body:
- type: markdown
attributes:
- value: Do not include secrets or vulnerability details. Use private vulnerability reporting for security-sensitive findings.
+ value: Report ambiguity in the Verifier Standard (VSTD) without including secrets or vulnerability details. Use private vulnerability reporting for security-sensitive findings.
- type: input
id: coordinate
attributes:
label: Exact coordinate
- description: File, section, schema field, layer, and release or commit.
- placeholder: standard/VSTD-4.md section 2.10 at v1.0.1
+ description: File, section, schema field, numbered profile or closure coordinate, and release or commit.
+ placeholder: standard/VSTD-4.md section X at release or commit Y
validations:
required: true
- type: textarea
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 60e9eb8..e48a981 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,6 +1,8 @@
## Coordinate
-- VSTD layer/profile:
+> **Acronyms:** Verifier Standard (VSTD).
+
+- VSTD numbered profile or closure coordinate:
- Repository release or target commit:
- Claim, schema, or implementation seam:
@@ -13,7 +15,7 @@
## Consequences
-- Compatibility and frozen-wire impact:
+- Serialized-format and compatibility impact:
- Trust roots, unknowns, residuals, and horizons:
- Downstream documents, schemas, examples, and receipts reviewed:
@@ -23,3 +25,4 @@
- [ ] I did not strengthen a claim without stronger evidence.
- [ ] I did not include secrets, private data, or proprietary operational material.
- [ ] Normative text, machine-readable surfaces, examples, and tests agree.
+- [ ] README maturity, claims guidance, generated reference, and Pages status still agree.
diff --git a/.github/external-links-allowlist.txt b/.github/external-links-allowlist.txt
new file mode 100644
index 0000000..ac8f0d8
--- /dev/null
+++ b/.github/external-links-allowlist.txt
@@ -0,0 +1,7 @@
+# One exact URL or trailing-* prefix and a tab-separated reason per line.
+# Entries are limited to publishers that reject this audit's automated request.
+https://doi.org/10.1145/263699.263712 Publisher returns HTTP 403 to automated probes.
+https://journals.ametsoc.org/view/journals/mwre/78/1/1520-0493_1950_078_0001_vofeit_2_0_co_2.xml Publisher returns HTTP 403 to automated probes.
+https://onlinelibrary.wiley.com/doi/abs/10.1111/j.1430-9134.2001.00173.x Publisher returns HTTP 403 to automated probes.
+https://rss.onlinelibrary.wiley.com/doi/10.1111/j.2517-6161.1952.tb00104.x Publisher returns HTTP 403 to automated probes.
+https://www.sciencedirect.com/science/article/pii/S1574013710000560 Publisher returns HTTP 400 to automated probes.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8cc961f..cb961e6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,7 +1,9 @@
-name: conformance
+name: repository-checks
on:
push:
+ branches: [main]
+ tags: ["v*"]
pull_request:
permissions:
@@ -22,6 +24,29 @@ jobs:
- run: python -m pip install ".[test]"
- run: python -m pytest -q
+ coverage:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
+ with:
+ python-version: "3.12"
+ - run: python -m pip install ".[test]"
+ - name: Record bounded branch-coverage evidence
+ run: |
+ python -m coverage run --branch --source=src/verifier -m pytest -q
+ python -m coverage report --show-missing --skip-covered | tee -a "$GITHUB_STEP_SUMMARY"
+ python -m coverage json --pretty-print -o coverage.json
+ python -m coverage xml -o coverage.xml
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: branch-coverage-python-3.12
+ path: |
+ coverage.json
+ coverage.xml
+ if-no-files-found: error
+ retention-days: 14
+
stdlib-smoke:
runs-on: ubuntu-latest
strategy:
@@ -35,6 +60,32 @@ jobs:
python-version: ${{ matrix.python-version }}
- run: PYTHONPATH=src python -S -c "import verifier; from verifier.core.run import load_manifest; print(verifier.__version__)"
+ scitt-crypto:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
+ with:
+ python-version: "3.12"
+ - run: python -m pip install ".[test,scitt]"
+ - name: Require the real SCITT/COSE statement and receipt path
+ run: |
+ python -c "import cbor2, cryptography, scitt_cose"
+ python -m pytest -q tests/test_scitt_crypto_example.py
+
+ artifact-seal:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
+ with:
+ python-version: "3.12"
+ - run: python -m pip install ".[test,seal]"
+ - name: Require exact-byte freeze and finite self-closing seal behavior
+ run: |
+ python -c "import cryptography"
+ python -m pytest -q tests/test_artifact_control.py
+
release-integrity:
runs-on: ${{ matrix.os }}
strategy:
@@ -58,7 +109,7 @@ jobs:
shell: bash
- run: python -m twine check dist/release-integrity/*.whl dist/release-integrity/*.tar.gz
shell: bash
- - run: python scripts/check_release_boundary.py dist/release-integrity/*.zip dist/release-integrity/*.whl dist/release-integrity/*.tar.gz
+ - run: python scripts/check_release_boundary.py dist/release-integrity/*.zip dist/release-integrity/*.whl dist/release-integrity/*.tar.gz dist/release-integrity/*.manifest.json dist/release-integrity/*.cdx.json
shell: bash
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
@@ -96,12 +147,21 @@ jobs:
- run: python -m pip wheel --no-cache-dir --no-deps --wheel-dir dist .
- run: python -m venv /tmp/vstd-wheel
- run: /tmp/vstd-wheel/bin/python -m pip install --no-deps dist/*.whl
- - run: /tmp/vstd-wheel/bin/vstd demo --json
- - run: /tmp/vstd-wheel/bin/vstd plan examples/generic_run/manifest.json --json
- - run: /tmp/vstd-wheel/bin/vstd run examples/generic_run/manifest.json --output /tmp/vstd-receipt
- - run: /tmp/vstd-wheel/bin/vstd validate /tmp/vstd-receipt
- - run: /tmp/vstd-wheel/bin/vstd reproduce /tmp/vstd-receipt --rerun
- - run: /tmp/vstd-wheel/bin/verifier demo --scenario honest-unknown --json
+ - name: Exercise the installed wheel outside the source checkout
+ run: |
+ cd /tmp
+ /tmp/vstd-wheel/bin/vstd demo --json
+ /tmp/vstd-wheel/bin/vstd plan "$GITHUB_WORKSPACE/examples/generic_run/manifest.json" --json
+ /tmp/vstd-wheel/bin/vstd run "$GITHUB_WORKSPACE/examples/generic_run/manifest.json" --output /tmp/vstd-receipt
+ /tmp/vstd-wheel/bin/vstd validate /tmp/vstd-receipt
+ /tmp/vstd-wheel/bin/vstd reproduce /tmp/vstd-receipt --rerun
+ /tmp/vstd-wheel/bin/verifier demo --scenario honest-unknown --json
+ /tmp/vstd-wheel/bin/python -c 'import json; from pathlib import Path; from verifier.core.checker import IndependentAuditor; receipt=json.loads(Path("/tmp/vstd-receipt/receipt.json").read_text()); hashes=(receipt["assessment_context"]["verifier"]["specification_hash"], IndependentAuditor.verifier_descriptor().specification_hash); assert all(value.startswith("sha256:") for value in hashes), hashes'
+ printf 'installed-wheel-artifact\n' > /tmp/vstd-wheel-artifact.bin
+ /tmp/vstd-wheel/bin/vstd artifact freeze /tmp/vstd-wheel-artifact.bin /tmp/vstd-wheel-artifact --json
+ /tmp/vstd-wheel/bin/vstd artifact verify /tmp/vstd-wheel-artifact --freeze-only --json
+ /tmp/vstd-wheel/bin/python -c 'import inspect; from verifier import thawed_artifact_status; assert "parent_bundle" in inspect.signature(thawed_artifact_status).parameters'
+ /tmp/vstd-wheel/bin/vstd artifact status --help | grep -- --parent-bundle
presentation:
runs-on: ubuntu-latest
@@ -111,25 +171,54 @@ jobs:
with:
python-version: "3.12"
- run: python scripts/check_presentation.py
- - run: python scripts/build_pages.py --output _site
+ - run: python scripts/build_pages.py --output _site --source-ref "$GITHUB_SHA"
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: pages-preview-${{ github.sha }}
+ path: _site
+ if-no-files-found: error
+ retention-days: 14
+
+ codeql:
+ name: CodeQL (Python)
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ security-events: write
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
+ with:
+ languages: python
+ queries: security-extended
+ - uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
+ # This identifier remains stable because main branch protection requires it.
conformance-gate:
if: always()
- needs: [base, stdlib-smoke, release-integrity, release-reproducibility, installed-wheel-smoke, presentation]
+ needs: [base, coverage, stdlib-smoke, scitt-crypto, artifact-seal, release-integrity, release-reproducibility, installed-wheel-smoke, presentation, codeql]
runs-on: ubuntu-latest
steps:
- name: Require every declared support and artifact check
env:
BASE: ${{ needs.base.result }}
+ COVERAGE: ${{ needs.coverage.result }}
STDLIB: ${{ needs.stdlib-smoke.result }}
+ SCITT: ${{ needs.scitt-crypto.result }}
+ ARTIFACT_SEAL: ${{ needs.artifact-seal.result }}
RELEASE: ${{ needs.release-integrity.result }}
REPRODUCIBLE: ${{ needs.release-reproducibility.result }}
WHEEL: ${{ needs.installed-wheel-smoke.result }}
PRESENTATION: ${{ needs.presentation.result }}
+ CODEQL: ${{ needs.codeql.result }}
run: |
test "$BASE" = success
+ test "$COVERAGE" = success
test "$STDLIB" = success
+ test "$SCITT" = success
+ test "$ARTIFACT_SEAL" = success
test "$RELEASE" = success
test "$REPRODUCIBLE" = success
test "$WHEEL" = success
test "$PRESENTATION" = success
+ test "$CODEQL" = success
diff --git a/.github/workflows/external-links.yml b/.github/workflows/external-links.yml
new file mode 100644
index 0000000..f0d8dfc
--- /dev/null
+++ b/.github/workflows/external-links.yml
@@ -0,0 +1,27 @@
+name: external-link-audit
+
+on:
+ schedule:
+ - cron: "23 11 * * 2"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ audit:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
+ with:
+ python-version: "3.12"
+ - name: Audit external documentation links with retry
+ run: python scripts/check_external_links.py --retries 2 --workers 8 --report external-links.json
+ - if: always()
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: external-link-audit-${{ github.run_id }}
+ path: external-links.json
+ if-no-files-found: error
+ retention-days: 14
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index 6345a20..52a00a4 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -24,7 +24,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0
- name: Assemble site and canonical schema routes
- run: python scripts/build_pages.py --output _site
+ run: python scripts/build_pages.py --output _site --source-ref "$GITHUB_SHA"
- uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
with:
path: _site
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1113b76..ab0a403 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -24,6 +24,9 @@ jobs:
with:
python-version: "3.12"
+ - name: Require TIME CLEAR in the exact tagged checkout
+ run: python scripts/check_time_status.py
+
- name: Require a protected-main commit and matching package version
env:
GH_TOKEN: ${{ github.token }}
@@ -36,6 +39,18 @@ jobs:
test "$VERSION" = "$PACKAGE_VERSION"
test "$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/check-runs" --jq '[.check_runs[] | select(.name == "conformance-gate" and .conclusion == "success")] | length')" -ge 1
+ - name: Require immutable GitHub releases before publication
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ test "$(gh api \
+ -H "X-GitHub-Api-Version: 2026-03-10" \
+ "repos/$GITHUB_REPOSITORY/immutable-releases" \
+ --jq '.enabled')" = true
+
+ - name: Require finalized release metadata in the exact tagged checkout
+ run: python scripts/check_release_metadata.py --version "${GITHUB_REF_NAME#v}"
+
- name: Re-run conformance on the tagged checkout
run: |
python -m pip install ".[test,release]"
@@ -52,15 +67,17 @@ jobs:
- name: Smoke-test the exact wheel
run: |
python -m twine check dist/*.whl dist/*.tar.gz
- python scripts/check_release_boundary.py dist/*.zip dist/*.whl dist/*.tar.gz
+ python scripts/check_release_boundary.py dist/*.zip dist/*.whl dist/*.tar.gz dist/*.manifest.json dist/*.cdx.json
python -m venv /tmp/vstd-release-wheel
/tmp/vstd-release-wheel/bin/python -m pip install --no-deps dist/*.whl
+ cd /tmp
/tmp/vstd-release-wheel/bin/vstd demo --json
- /tmp/vstd-release-wheel/bin/vstd plan examples/generic_run/manifest.json --json
- /tmp/vstd-release-wheel/bin/vstd run examples/generic_run/manifest.json --output /tmp/vstd-release-receipt
+ /tmp/vstd-release-wheel/bin/vstd plan "$GITHUB_WORKSPACE/examples/generic_run/manifest.json" --json
+ /tmp/vstd-release-wheel/bin/vstd run "$GITHUB_WORKSPACE/examples/generic_run/manifest.json" --output /tmp/vstd-release-receipt
/tmp/vstd-release-wheel/bin/vstd validate /tmp/vstd-release-receipt
/tmp/vstd-release-wheel/bin/vstd reproduce /tmp/vstd-release-receipt --rerun
/tmp/vstd-release-wheel/bin/vstd hardware list --json >/dev/null
+ /tmp/vstd-release-wheel/bin/python -c 'import json; from pathlib import Path; from verifier.core.checker import IndependentAuditor; receipt=json.loads(Path("/tmp/vstd-release-receipt/receipt.json").read_text()); hashes=(receipt["assessment_context"]["verifier"]["specification_hash"], IndependentAuditor.verifier_descriptor().specification_hash); assert all(value.startswith("sha256:") for value in hashes), hashes'
- name: Attest every published artifact
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
@@ -69,6 +86,7 @@ jobs:
dist/*.zip
dist/*.whl
dist/*.tar.gz
+ dist/*.cdx.json
dist/*.manifest.json
- name: Write bounded release notes
@@ -100,17 +118,23 @@ jobs:
' CHANGELOG.md
} > release-notes.md
- - name: Publish only the tested and attested artifacts
+ - name: Assemble a complete draft release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "$GITHUB_REF_NAME" \
- dist/*.zip dist/*.whl dist/*.tar.gz dist/*.manifest.json \
+ dist/*.zip dist/*.whl dist/*.tar.gz dist/*.cdx.json dist/*.manifest.json \
--repo "$GITHUB_REPOSITORY" \
--verify-tag \
+ --draft \
--title "VSTD ${GITHUB_REF_NAME}" \
--notes-file release-notes.md
+ - name: Publish the complete draft atomically
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: gh release edit "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --draft=false
+
- name: Stage only the Python distributions for PyPI
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
diff --git a/.zenodo.json b/.zenodo.json
index 19f2dd0..acb185a 100644
--- a/.zenodo.json
+++ b/.zenodo.json
@@ -4,7 +4,7 @@
"name": "Roost, Tyler"
}
],
- "description": "A two-axis verification ladder and reference implementation for bounded claims, provenance graphs, substrate accountability, grounded refutation certificates, and computed verification depth.",
+ "description": "Release-candidate metadata for a verification-domain language and Python reference implementation that packages bounded computational claims with explicit evidence, checking mechanisms, limits, refutation conditions, provenance, and reproducibility information. It does not replace native domain verifiers or strengthen their results. Publication metadata is assigned only after the release exists.",
"keywords": [
"verification",
"provenance",
@@ -13,10 +13,11 @@
"software supply chain",
"accelerator accountability",
"refutability",
- "proof certificates"
+ "proof certificates",
+ "bounded claims"
],
"license": "Apache-2.0",
- "title": "VSTD: A Two-Axis Ladder for Refutable Verification",
- "version": "1.1.3",
+ "title": "Verifier Standard (VSTD): Bounded, Refutable Evidence for Computational Claims",
+ "version": "1.2.0",
"upload_type": "software"
}
diff --git a/AGENTS.md b/AGENTS.md
index 01d445e..8f7111e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,24 +1,73 @@
# AGENTS.md
+> **Acronyms:** application programming interface (API); Concise Binary Object Representation (CBOR);
+> CBOR Object Signing and Encryption (COSE); continuous integration (CI); command-line interface (CLI);
+> carriage return and line feed (CRLF); GNU Privacy Guard (GPG); hash-based message authentication code (HMAC);
+> Hypertext Markup Language (HTML); Internet Engineering Task Force (IETF);
+> International Organization for Standardization (ISO); JavaScript Object Notation (JSON); line feed (LF);
+> Supply Chain Integrity, Transparency, and Trust (SCITT); Verifier Standard (VSTD);
+> World Wide Web Consortium (W3C).
+
Working rules for automated contributors to VSTD. Read this before editing anything.
## 1. What this repository is
-VSTD is a **specification** plus its **reference implementation** for portable, bounded,
-refutable evidence about computational claims. The distribution is `verifier-standard`,
-the import package is `verifier`, and `vstd` is the canonical command.
+VSTD is a **verification domain language** plus its **reference implementation** for
+portable, bounded, refutable evidence about computational claims. It standardizes claim
+boundaries and portable result semantics across domain verifiers without replacing their
+native work. The distribution is `verifier-standard`, the import package is `verifier`,
+and `vstd` is the canonical command.
Two independent axes: `VSTD-1..5` (object mechanics) and `VSTD-Graph-1..5` (collection
-dynamics). Layers 1-4 are implemented; **layer 5 is DRAFT**. An aggregate depth of `N`
-holds only when distinct evidence passes every layer from 1 through `N`. A higher-layer
-result never supplies, implies, upgrades, or repairs a lower-layer one.
-
-This is founder-maintained alpha project work. It is **not** an accredited, consensus,
+dynamics). These are cumulative **numbered profiles** over named **closure coordinates**,
+not interchangeable layers or scalar assurance levels. Implementation status is
+profile-specific: the compatibility VSTD-4 candidate-depth and Graph candidate-profile
+mechanisms compute over caller-supplied references or ratings with conformance
+`NOT_ESTABLISHED`. Separate evidence-bound paths rerun exact registered mechanisms and
+may establish VSTD-4, VSTD-5, or Graph conformance under their named evidence, trust
+roots, bounds, and exact collection or claim binding. Evidence-bound Graph profile zero
+remains `NOT_ESTABLISHED`. Object profile depth `N` holds only when
+distinct evidence passes every required coordinate in profiles 1 through `N`. A
+later-profile result never supplies, implies, upgrades, or repairs a prerequisite
+coordinate.
+
+Follow the terminology contract in [`standard/LADDER.md`](standard/LADDER.md#terminology-contract).
+Use **layer** only for a literal implementation, protocol, or physical stack; use **level**
+only for an explicitly named external taxonomy or retained compatibility identifier. In
+new prose, qualify **profile** as numbered, receipt, application, or geometry profile;
+qualify **depth** as object profile depth, VSTD-4 normative or candidate depth, or lineage topological
+depth; and qualify **closure** by the proposition it closes. VSTD-4 **rung** is reserved
+for obligations 4.1 through 4.14. Do not rename frozen serialized fields, supported API
+symbols, historical module paths, or published artifact bytes to satisfy an editorial
+preference; explain their compatibility meaning adjacent to them.
+
+This is maintainer-led alpha project work. It is **not** an accredited, consensus,
IETF, ISO, or W3C standard, and it has no demonstrated external adoption. Do not write
text implying otherwise. Orientation: [`README.md`](README.md),
[`standard/LADDER.md`](standard/LADDER.md),
[`docs/CLAIMS_AND_LIMITS.md`](docs/CLAIMS_AND_LIMITS.md), [`GOVERNANCE.md`](GOVERNANCE.md).
+### 1.1 Operating control surfaces
+
+- [`AGENTS.md`](AGENTS.md) contains automated-contributor rules.
+- [`HUMANS.md`](HUMANS.md) contains the human operating and reasoning guide.
+- [`TIME.md`](TIME.md) announces unresolved contradictions in the repository's current
+ authoritative state; it is not a standard, roadmap, or runtime receipt.
+
+Read `TIME.md` before substantive work. `Status: CLEAR` means only that no unresolved
+repository contradiction is currently recorded. If its status is not clear, preserve both
+claims and their exact coordinates, apply the authority order in
+[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md), and repair only what the evidence resolves.
+Do not continue work whose conclusion depends on the unresolved seam; if safe repair is not
+possible, leave a precise live entry instead of deleting or harmonizing either side.
+
+Use TIME for disagreements among normative documents, schemas, runtime behavior,
+conformance tests, or public implementation claims. Do not use it for an honestly represented
+`UNKNOWN`, a receipt or Graph `CONFLICTED` state, ordinary design work, or a roadmap item.
+`TIME == CLEAR` is a release invariant for version 1.2.0. Development branches and normal
+pull-request checks may retain exact unresolved entries, but the tag-triggered publication
+workflow must reject the exact tagged checkout unless its status is `CLEAR`.
+
## 2. Prime directive
> Changes that strengthen a claim without stronger evidence are non-conforming.
@@ -35,13 +84,93 @@ This inverts the usual agent instinct. An uncertain or negative result here is o
When a check cannot be discharged, the conforming output is the uncertain verdict with a
reason, not a pass. See [`CONTRIBUTING.md`](CONTRIBUTING.md).
+### Artifact-first state vocabulary
+
+`TRUST`, `RUST`, and `ROT` are formal semantic names, not acronyms, scalar scores,
+numbered-profile verdicts, or references to the Rust programming language. They serialize
+only as typed events in `VSTD-GRAPH-ASSURANCE-1`. `TRUST` is mechanism-earned
+forward artifact support; `RUST` is inverse-direction diagnostic traversal from a
+descendant deviation toward recorded ancestors; `ROT` is typed, time-indexed degradation
+of current admissibility without rewriting historical evidence. Define the terms at first
+use in every independently readable surface and preserve their normative meaning from
+[`standard/LADDER.md`](standard/LADDER.md).
+
+Assurance events are not self-authenticating status words. Portable reliance requires
+`recheck_assurance_log` to reconstruct the historical Graph, rehash embedded evidence,
+rerun every registered mechanism, reproduce the event chain, and compare the current view.
+When upstream admissibility changes, preserve historical events, exclude no-longer-current
+TRUST through `current_trust_events`, and use `impacted_descendants` only as a deduplicated
+reassessment surface—not as a verdict on descendants.
+
+Never turn identity, authorship, authorization, popularity, reputation, or actor separation
+into `TRUST` in computational validity. A mechanism may check an exact proposition about
+one of those coordinates, but that result remains adjacent to the artifact-bound process
+claim. VSTD verifies bounded propositions about processes represented by software and
+evidence-bearing artifacts; it does not rate whether an actor is good or bad.
+
+Architectural zero knowledge means zero unevidenced knowledge is presumed. When a witness
+must remain confidential, cryptographic zero knowledge may enclose that rule only through
+a named proof system binding the exact program, predicate, public commitments, output,
+parameters, and verifier. Never relabel a digest, omission, encryption, or hidden input as
+a zero-knowledge proof, and never attach the proof's TRUST to prover identity.
+
+Freeze, seal, encryption, and temporal continuity are distinct. Freeze must preserve the
+exact bytes needed for independent recomputation; hashes alone are not preservation. A
+seal closes a declared artifact state and may be readable or encrypted, but sealing is
+not encryption. The version 1 self-closing seal is readable. Thaw creates a mutable
+descendant and never edits the sealed parent. Never claim that a read-only guard prevents
+privileged writes, that a carried key prevents whole-bundle substitution, or that endpoint
+signatures prove uninterrupted closure. Realm, continuity, and transition claims require
+their own named verifiers.
+
+### 2.1 Minimum surface and blast radius
+
+This file governs the public VSTD repository. Verify the repository root and remote before
+editing; never copy private workspace coordinates or artifacts into the public tree.
+
+Use the **minimum lines of code and minimum lines of documentation** that carry the
+maximum necessary information for a reader to comprehend the complete expected
+verification standard. Minimum never permits omission of normative meaning, claim
+boundaries, wire behavior, failure/`UNKNOWN` semantics, conformance, or security.
+
+Default to the smallest change that restores truth. Reformat or restructure adjacent
+architecture only after the idea space is conditionally operationalized by a named target,
+dependency map, migration and compatibility analysis, tests, and explicit approval.
+
+Treat public onboarding examples as release surfaces. Feature one only when its declared
+subject is real, its critical artifacts are retrievable, and its claimed path is rerunnable;
+otherwise remove it from navigation and the live example/test surface instead of explaining
+away missing evidence. Preserve forensic material outside the public tree or in Git history.
+
+### 2.2 First-use acronym expansion
+
+Write every independently readable document and source file for a newcomer who starts at its
+first line. Expand each acronym at its first reader-facing use as **expanded term (ACRONYM)**;
+do not require a prior document, domain background, or the filename to supply the meaning.
+Source files place the expansion in the module documentation or the first explanatory comment.
+The canonical expansion key is [`docs/ACRONYMS.md`](docs/ACRONYMS.md), but a glossary link never
+substitutes for the local first-use expansion. Do not rename current serialized receipt identifiers, schema
+values, code symbols, filenames, commands, or third-party proper names; explain them in adjacent
+prose instead. `scripts/check_acronyms.py` enforces the registered terms on public prose and
+source-documentation surfaces.
+
+After a public credibility failure, audit adjacent first-impression claims, validation labels,
+evidence classifications, links, packaging, tests, and private/public boundary leaks. Never
+relabel digest integrity, same-process extraction, self-report, local rehearsal, or artifact
+retention as full validity, independent verification, attestation, public recomputation, or
+proof of correctness.
+
## 3. Environment and commands
```bash
python -m pip install ".[test]"
python -m pytest -q
+python -m coverage run --branch --source=src/verifier -m pytest -q
+python -m coverage report --show-missing
python scripts/check_presentation.py
+python scripts/build_reference.py --check
python -m compileall -q src scripts
+PYTHONPATH=src python scripts/build_experiment_index.py --check
```
Stdlib-purity smoke, mirroring the `stdlib-smoke` CI job:
@@ -74,13 +203,24 @@ If that path is not inside this repository, prefix commands with `PYTHONPATH=src
## 4. Layout
-- `standard/` — normative layer documents plus the frozen `WIRE_IDENTIFIERS.md`.
-- `src/verifier/core/` — receipt, checker, certificate, grounding, kernel, run.
-- `src/verifier/constraints/`, `hardware/`, `layer4/`, `data/` — layer surfaces.
+- `standard/` — normative numbered-profile documents plus `WIRE_IDENTIFIERS.md`.
+- `src/verifier/core/` — receipt, checker, certificate, grounding, kernel, evidence-bound
+ mechanism execution, witness corroboration, and the
+ generic-run capture/facade with planning, validation, inspection, reproduction, and
+ impact modules.
+- `src/verifier/constraints/`, `hardware/`, `layer4/`, `data/` — profile-specific runtime
+ surfaces, including additive Graph assurance propagation; `layer4/` is a retained module path.
- `src/verifier/runtime/` — `public_cli.py` (every CLI entry point) and `demo.py`.
- `src/verifier/specifications/` — byte-identical copies of normative spec files.
-- `receipts/schema/` — JSON Schemas. `examples/` — runnable specimens.
-- `scripts/` — `check_presentation.py`, `release_artifacts.py`, `build_pages.py`.
+- `receipts/schema/` — receipt JSON Schemas. `standard/schemas/` — strict non-receipt
+ mechanism schemas. `examples/` — runnable specimens.
+- `experiments/` — non-normative studies with profile manifests, explicit horizons,
+ and blockers.
+- `src/verifier/experimental_workflow/` — optional workflow/profile interchange; it
+ records allocation but never grants a VSTD verdict from repository state.
+- `scripts/` — presentation, release-state, artifact, Pages, reference, and experiment-index
+ gates, including `check_presentation.py`, `check_time_status.py`,
+ `check_terminology.py`, `check_release_metadata.py`, and `release_artifacts.py`.
- `tests/` — flat `tests/test_*.py`, no `conftest.py`.
## 5. Invariants that must not be refactored away
@@ -98,25 +238,30 @@ smoke job. Anything new belongs in an optional extra in `pyproject.toml`, import
behind that extra. A new third-party import on the base path breaks the build.
**Lazy exports.** `_LAZY_EXPORTS` plus module `__getattr__` in `src/verifier/__init__.py`
-keeps import cost near zero. Do not convert these into eager imports.
+keeps import cost near zero. Do not convert these into eager imports. Names in
+`verifier.__all__` are the supported Python application programming interface (API);
+follow [`docs/API_STABILITY.md`](docs/API_STABILITY.md) for additions and deprecations.
**Console scripts.** `vstd`, `verifier`, and `verifiable` all map to
`verifier.runtime.public_cli:main`. `vstd` is canonical because an unqualified `verifier`
on Windows commonly resolves to Windows Driver Verifier. `verifiable` is a **permanent**
-alias: published receipts bind it in falsification instructions, so removing it would
-render already-published refutation steps unrunnable.
-
-**Frozen wire identifiers.** `VSTD-0.1`, `VSTD-0.2`, `VSTD-3.0`, and `VSTD-DATA-0.1` are
-frozen; readers dispatch on them, not on filenames. Released artifacts are immutable and
-corrections are additive only. See
-[`standard/WIRE_IDENTIFIERS.md`](standard/WIRE_IDENTIFIERS.md).
-
-**Packaged specification bytes.** Editing `LADDER.md`, `VSTD-3.md`, `VSTD-4.md`, or
-`WIRE_IDENTIFIERS.md` under `standard/` requires copying the exact bytes into
-`src/verifier/specifications/`. `tests/test_packaged_specifications.py` compares them
-byte-for-byte.
-
-**Schema `$id` is a live route.** Every `receipts/schema/*.json` must carry
+alias: receipts in the `v0.1.0` and `v0.2.0` release artifacts bind it in falsification
+instructions, so removing it would render already-published refutation steps unrunnable.
+The evidence is the published releases, not a file in the current checkout.
+
+**Wire identifiers.** Current readers dispatch on the exact identifiers and required
+profile discriminators in [`standard/WIRE_IDENTIFIERS.md`](standard/WIRE_IDENTIFIERS.md),
+not on filenames or field resemblance. VSTD-1 and VSTD-2 use their full numbered-profile identifiers;
+do not restore retired partial-profile object identifiers or compatibility reads. Published
+release bytes remain historical facts in their tags and Git history, not active current
+profiles.
+
+**Packaged specification bytes.** Every `standard/*.md` file has a byte-identical
+installed copy under `src/verifier/specifications/` so verifier descriptors do not depend
+on a source checkout. `tests/test_packaged_specifications.py` enforces the complete set.
+
+**Schema `$id` is a live route.** Every `receipts/schema/*.json` and
+`standard/schemas/*.json` file must carry
`"$id": "https://timelordraps.github.io/verifier/schemas/"`. `scripts/build_pages.py`
refuses to assemble the site otherwise, and `tests/test_presentation_surface.py` checks that
each schema deploys byte-identical under that route. Renaming a schema file means updating
@@ -136,20 +281,25 @@ CRLF/LF equivalence as byte identity. This matters when working on Windows.
`CITATION.cff`, `.zenodo.json`, and a dated `## X.Y.Z - YYYY-MM-DD` heading in
`CHANGELOG.md` — bump all five together or the gate fails;
- a missing required boundary phrase in `README.md`, `ROADMAP.md`, or
- `standard/WIRE_IDENTIFIERS.md` (alpha status, non-substitution of layers, canonical CLI
+ `standard/WIRE_IDENTIFIERS.md` (alpha status, non-substitution of closure coordinates, canonical CLI
disclosure, explicit non-goals). Do not reword those sentences casually;
- a local Windows or home-directory path leaked into committed content;
- a change to the overview asset dimensions or its accessibility role.
+- a stale generated CLI/API reference or experiment index.
-The `conformance-gate` job requires `base`, `stdlib-smoke`, `release-integrity`,
-`installed-wheel-smoke`, and `presentation` to all succeed.
+The protected repository-check aggregate (the `conformance-gate` job identifier) requires `base`,
+`coverage`, `stdlib-smoke`, `scitt-crypto`, `artifact-seal`, `release-integrity`, `release-reproducibility`,
+`installed-wheel-smoke`, and `presentation` to all succeed. The dedicated SCITT/COSE job
+installs `.[test,scitt]`; the normal test matrix may skip that optional cryptographic
+integration module. The artifact-seal job installs `.[test,seal]` and must execute the
+complete freeze/seal/thaw adversarial suite.
## 7. Conventions
Every substantive module opens with `from __future__ import annotations`; the only files
without it are empty package `__init__.py` markers. Annotate all parameters and return
types. Records are frozen dataclasses by default; verdicts and tiers are enums. Module
-docstrings are normative — they state which ladder rung the code discharges, so update the
+docstrings are normative — they state which VSTD-4 rung the code discharges, so update the
docstring whenever behavior changes.
`requires-python = ">=3.10"`, and CI runs the suite on 3.10 through 3.13. Everything under
@@ -166,9 +316,12 @@ assertion to make a suite green.
## 9. Change process
Work lands via pull request into `main`. `.github/PULL_REQUEST_TEMPLATE.md` requires a
-Coordinate (layer, release, seam), a falsification condition, and compatibility plus
-frozen-wire impact. Commit subjects are short and imperative. Do not run release or tag
-workflows; [`RELEASING.md`](RELEASING.md) is a maintainer procedure.
+Coordinate (numbered profile, release, seam), a falsification condition, and compatibility plus
+wire-format impact. Commit subjects are short and imperative. Every commit is GPG-signed;
+never bypass a signing failure with an unsigned commit. A signature binds commit bytes to
+a key but does not establish identity, correctness, independence, authorization, or
+safety. Do not run release or tag workflows; [`RELEASING.md`](RELEASING.md) is a
+maintainer procedure.
`.github/workflows/pages.yml` publishes the `scripts/build_pages.py` output to GitHub Pages
on every push to `main`. Documentation and schema edits become public the moment they merge,
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 47e766d..52a4d9e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,207 @@
# Changelog
+> **Acronyms:** artificial intelligence (AI); Advanced Micro Devices (AMD); application programming interface (API);
+> Amazon Web Services (AWS); Concise Binary Object Representation (CBOR); continuous integration (CI);
+> command-line interface (CLI); conjunctive normal form (CNF); CBOR Object Signing and Encryption (COSE);
+> grounded decision certificate (GDC); Hypertext Transfer Protocol Secure (HTTPS);
+> Internet Engineering Task Force (IETF); JavaScript Object Notation (JSON); nondeterministic polynomial time (NP);
+> reduced instruction set computer (RISC); Boolean satisfiability problem (SAT);
+> Secure Hash Algorithm 256-bit (SHA-256); Secure Hash Algorithm 3 256-bit (SHA3-256);
+> Supply Chain Integrity, Transparency, and Trust (SCITT); Secure Shell (SSH);
+> Coordinated Universal Time (UTC); Verifier Standard (VSTD); ZIP archive format (ZIP);
+> zero-identity/zero-knowledge (ZIZK).
+
+## 1.2.0 - UNRELEASED
+
+### Public surface and integrations
+
+- Restructure the public first-view path around one bounded project description, one
+ deterministic demonstration, one canonical maturity table, skeptical claim limits,
+ contributor routes, and release/citation boundaries; align Pages and package metadata
+ without changing normative or serialized-receipt semantics.
+- Normalize the public architecture as a verification complex of named closure
+ coordinates and cumulative numbered profiles; reserve VSTD-4 rung, candidate depth,
+ verification order, compatibility level, and checker-cost tier for their distinct uses.
+- Add experimental workflow profile 0.1 with deterministic canonicalization, strict
+ validation, bounded work-allocation records, additive amendments and challenges,
+ explicit unresolved horizons, and verdict-neutral platform events.
+- Add a normalized GitHub adapter for issues, commits, workflow runs, artifacts, and
+ pull requests. Successful workflows and merges retain `verification_effect = NONE`
+ unless a separate native result is explicitly mapped through a bound VSTD receipt.
+- Add `vstd experiment validate` and `vstd experiment github-events` as offline,
+ verdict-neutral entry points. Repository artifacts are explicitly `NOT_CHECKED` with
+ exit code 2 unless their root is supplied.
+- Add a machine-readable schema, checked-in verdict-neutral specimen, generated
+ experiment index, adversarial tests, and a runnable offline example.
+- Add a generated CLI/API reference page and presentation gates that reject stale
+ reference or experiment-index content.
+- Clarify VSTD's role as a verification-domain language and interchange layer that
+ preserves, rather than replaces or strengthens, native verifier results.
+- Add the experimental SCITT adapter, rerunnable real-COSE specimen with ephemeral keys, explicit semantic
+ boundary, and adversarial composition tests without claiming IETF review or payload
+ truth from registration.
+- Surface zero-identity/zero-knowledge (ZIZK) artifact-first TRUST as governing
+ architecture, publish the bounded RISC Zero reference mechanism and exact recorded
+ public proof artifacts, and keep only unfinished mechanisms experimental while
+ preserving unresolved horizons and native-system authority.
+- Bind the recorded RISC Zero proof to the image produced from the tracked guest and
+ locked toolchain in the governed offline verifier, rather than accepting source/proof
+ correspondence from a neighboring historical image identifier.
+- Formally distinguish TRUST as mechanism-earned forward artifact support, ROT as typed
+ time-indexed degradation of current admissibility, and RUST as an inverse-TRUST memetic
+ causal backtrace toward recorded ancestor states. None is actor-tied trust or a scalar;
+ reachability alone never infers guilt, responsibility, or causal localization.
+- Present current reports, schemas, module descriptions, and examples under the full
+ VSTD-1 and VSTD-2 numbered-profile identifiers; remove retired partial-profile object identifiers from
+ active readers and add a regression preventing their return.
+- Add normative artifact-control mechanism version 1 with exact-byte file/directory
+ freezing, SHA-256 plus SHA3-256 artifact-derived identities, observable read-only
+ guards, readable finite self-closing Ed25519 seals, external anchor checks, and
+ copy-on-write thaw descendants. Sealing is not encryption and supplies no actor trust,
+ semantic correctness, trusted time, or numbered VSTD profile result.
+- Document multi-temporal realms, discrete and continuous coexistence, causal and
+ problem-space partial orders, atemporal versus temporal capsules, explicit cross-realm
+ mappings, and future constrained language-model transition verification without
+ claiming continuous mediation, inference-law implementation, or textual truth.
+
+### Claim boundaries and validation
+
+- Require `thawed_artifact_status` and `vstd artifact status` to verify an actual supplied,
+ cleanly sealed parent and every recorded parent coordinate before returning
+ `THAWED_CLEAN` or `THAWED_DIRTY`. Sidecar-only agreement is now `NOT_ESTABLISHED`; even a
+ verified current match does not authenticate the historical copy operation or external
+ parent continuity.
+- Preserve final filesystem-entry identity during artifact creation: freeze refuses
+ symbolic-link sources, and bundle, thaw-descendant, and sidecar outputs refuse every
+ preexisting lexical entry, including dangling symbolic links, without claiming universal
+ race-free filesystem security.
+- Require authoritative freeze-manifest, payload, seals-container, and seal-envelope
+ members to have ordinary lexical types; linked external or in-bundle targets cannot lend
+ bytes to bundle closure, while verified outer read aliases and ordinary hard-link
+ byte-and-path semantics remain explicitly distinct.
+- Remove the live SimulacraBench rehearsal and its front-door promotion; the repository
+ never contained or reproduced the submission, hosted image, hardware, or protected
+ evaluation identified by that name.
+- Correct generic-run wording: digest validation is an integrity check, external
+ references remain unattested until dereferenced and verified, same-path output
+ extraction is not independent verification, and unverified determinism is `UNKNOWN`.
+- Publish a Pages guide index and enforce language, title, viewport, main-region, skip-link,
+ image-alt, labelled-navigation, generated-reference, and local-link checks in CI.
+- Preserve explicit ordered-list starting numbers in generated Pages so procedures split
+ by code blocks retain their source step numbers instead of restarting at one.
+- Require CodeQL security-extended Python analysis in the protected repository-check
+ aggregate with only read access to content and write access to security results.
+- Fail closed on malformed generic-run receipts, publish their exact schema, and dispatch
+ `VSTD-1` by its required receipt profile.
+- Package every normative specification, verify byte identity, and smoke-test the built
+ wheel outside the source checkout so installed specification bindings cannot silently
+ become unavailable.
+- Bind the bundled checker to VSTD-1, record actor and execution separation explicitly,
+ and never infer independent actors from a historical field name, repeated runs, or
+ matching results.
+- Reject self-promoted independence even when every supplied status and digest agrees;
+ the generic-run compatibility path never derives `EVIDENCED` from serialized references.
+ The distinct VSTD-5 path reruns all seven separation propositions and does not upgrade
+ the legacy generic-run fields.
+- Require the real optional SCITT/COSE cryptographic example in the protected
+ repository-check aggregate
+ rather than allowing its dependency-gated tests to disappear from the base matrix.
+- Close generic-run control structures while retaining the released refutation-extension
+ map, make common receipt commands honor `--json`, and lock `validate` as an
+ integrity/profile check rather than a claim verifier.
+
+### Graph and conformance semantics
+
+- Add a zero-dependency evidence execution core that resolves and rehashes exact evidence
+ bytes, pins a registered mechanism implementation digest, enforces byte/item bounds,
+ reruns the mechanism, and preserves `PASS`, `FAIL`, or `UNKNOWN` under explicit trust roots.
+- Add an evidence-bound VSTD-4 path and replayable receipt form. Compatibility
+ `vstd4_depth` remains a `NOT_ESTABLISHED` candidate; only exact passing VSTD-1/2/3 and
+ fourteen-rung mechanisms plus an accepted kernel witness admit VSTD-5.
+- Implement the VSTD-5 reference mechanism and receipt: seven evidence-bound separation
+ dimensions, duplicate-witness/evidence refusal, exact admitted-certificate and
+ corroboration binding, typed binding/identity/separation/corroboration errors,
+ disagreement preservation, embedded evidence, and offline result recheck. Independence
+ fails closed on any identity or separation defect without parsing error-message text.
+ Witness identities and assertions serialize separately and in order, so duplicate,
+ orphan, missing, and reused-identity error inputs remain replayable instead of collapsing
+ during receipt construction. Keep permissive malformed-input assessment distinct from
+ portable receipt admission: the builder now raises unless the strict schema and complete
+ verdict-material evidence coverage hold, and the rechecker applies the same zero-dependency
+ gate before replay. The rechecker also compares the complete carried VSTD-4 entry, and
+ `corroboration_class` is mechanism-bound rather than relabelable metadata. This does not
+ claim a real external witness or independent implementation; a positive observation with
+ unresolved independence is overall `UNKNOWN`.
+- Add evidence-bound Graph profile computation and replay. The compatibility `graph_level`
+ path remains caller-supplied; the new path reruns every member, ancestor, and reached-edge
+ rating mechanism bound to the exact Graph, members, collection, and claim before profile
+ 1–5 can report `ESTABLISHED`. Profile zero remains `NOT_ESTABLISHED`.
+- Add `VSTD-GRAPH-ASSURANCE-1` and `AssuranceLedger` for hash-chained edge-local TRUST, ROT, RUST,
+ challenge-ledger projection, additive conflict declaration/resolution, structural RUST concentration,
+ explicit causal localization, and bounded artifact-relative BLAME/GUILT propositions.
+ Each TRUST event binds one exact transformation, its inputs/output, the historical Graph,
+ and prerequisite TRUST events; current eligibility recursively fails closed when any bound
+ dependency degrades or conflicts. Duplicate paths remain set-valued, historical graph bytes
+ remain immutable, and topology alone earns no causal or moral conclusion. BLAME establishes
+ bounded responsibility or material contribution. GUILT is not BLAME in the opposite
+ direction: it composes separately bound responsibility, exact scoped-obligation
+ applicability, and same-obligation violation components, then binds their exact event
+ digests. One compound mechanism may emit all three component evaluations in one invocation;
+ an opaque combined pass or decorative obligation string remains `NOT_ESTABLISHED`.
+ Localization binds one exact passing RUST event and descendant-deviation proposition.
+ Neither result establishes actor morality, reputation, automatic legal liability,
+ innocence, exoneration, obligation satisfaction, or absence of hidden contributors.
+ Status-conflict resolution projects the
+ selected state into current admissibility; arbitrary resolved predicates remain blocked.
+ The current runtime has no general non-status admissibility-effect mechanism. RUST follows
+ historically recorded contributing ancestry even when current lifecycle state excludes a
+ route from TRUST. New construction, evidence-bound Graph establishment, and assurance
+ propagation require globally disjoint artifact/transformation identifiers so an untyped
+ `subject_id` cannot ambiguously name both; the frozen `VSTD-DATA-0.1` reader retains its
+ original two namespaces.
+ Add complete offline event replay,
+ current TRUST filtering, and deduplicated descendant reassessment discovery.
+
+- Preserve incompatible Graph assertions as evidence-linked conflict records and label
+ rating-derived Graph profile numbers as `CALLER_SUPPLIED` candidates with conformance `NOT_ESTABLISHED`.
+- Classify the current VSTD-4 candidate-depth calculation as a structural result over
+ caller-supplied rung references with conformance `NOT_ESTABLISHED`; reject that
+ candidate at the VSTD-5 entry gate even when its candidate depth is 14.
+- Label compatibility Graph 2–5 candidates consistently while separately presenting the
+ implemented evidence-bound reference paths. Bind complete challenge-ledger state into an
+ additive current Graph view without mutating history.
+
+### Release and maintainer controls
+
+- Mark 1.2.0 metadata as an unreleased release candidate, omit any fabricated release
+ date, and require the exact tagged checkout to have `TIME.md` set to `Status: CLEAR`.
+- Move the immutable-release setting check before tag creation in the documented release
+ sequence and enforce it again in the tag workflow, so a disabled setting stops
+ publication rather than producing a mutable release.
+- Make package/reference status identify VSTD-5 as the highest exposed project
+ specification with an evidence-bound reference mechanism, without claiming a real
+ independent witness, and require finalized release metadata in the tag workflow.
+- Publish the architecture ownership map linking normative documents, runtime validators,
+ schemas, and conformance tests.
+- Document the five-As human traversal over existing receipt, Graph, hardware, certificate,
+ reproduction, and SCITT machinery without adding a serialized receipt format; reject duplicate Graph
+ identifiers and reproduction-fidelity states inferred from declarations, matching verdicts, or
+ mismatching runs.
+- Restore the three non-overlapping operating controls: `AGENTS.md` for automated work,
+ `HUMANS.md` for human five-As reasoning, and `TIME.md` for current repository
+ contradictions. Development may record `OPEN`; the exact tagged checkout must be
+ `CLEAR` before publication.
+- Replace the developmental profile-numbered generic-run container with required neutral
+ `assessment_context`; preserve its mechanism, bound, commitment, and refutation
+ coordinates without carrying a VSTD-4 conformance field.
+
## 1.1.3 - 2026-08-22
- Canonicalize source ZIP timestamps in UTC and remove host ZIP metadata, so the
same Git coordinate produces byte-identical source archives on Windows and Linux.
- Canonicalize generated wheel and source-distribution newlines, archive member
order, modes, timestamps, and ownership. Rebuild wheel `RECORD` after normalization
- and use compression-independent ZIP members plus a stable USTAR/gzip container.
+ and use compression-independent ZIP members plus a stable `ustar`/gzip container.
- Normalize common HTTPS and SSH spellings of the Git origin before recording the
public repository coordinate in a release manifest.
- Require CI to build the complete release artifact set independently on Windows and
@@ -26,7 +221,7 @@
manifests published through `v1.1.1` that bind `verifiable-standard-.zip`
remain verifiable without republishing.
- Record the import-package, distribution, and archive renames in
- `WIRE_IDENTIFIERS.md`. No receipt wire identifier, schema `$id`, or canonical digest
+ `WIRE_IDENTIFIERS.md`. No receipt serialized receipt identifier, schema `$id`, or canonical digest
changes.
- Attribute the specifications, distribution metadata, and governance decision rights to
`TimeLordRaps`. The legal name remains the copyright holder in `NOTICE`.
@@ -42,7 +237,7 @@
- Rename the VSTD-2 section 7 lifecycle term `VERIFIABLE` to `GEOMETRY_INSPECTABLE`
and record in `WIRE_IDENTIFIERS.md` that the section 7 vocabulary is prose-only, so
- no status token reuses the maintainer's name and no wire value changes.
+ no status token reuses the maintainer's name and no serialized receipt value changes.
- Label the reference emulator's synthetic accelerator descriptor `vendor` as
`EMULATED` instead of the maintainer's name, so fabricated hardware evidence cannot
read as maintainer attestation.
@@ -52,7 +247,7 @@
- Correct the SimulacraBench synthetic specimen additively: unobserved private
artifacts now remain `IDENTIFIED`, and the public challenge stops at
- `CHALLENGED` without a founder-authored adjudication.
+ `CHALLENGED` without a maintainer-authored adjudication.
- Require content-bound observed bytes before deriving `AVAILABLE` or `PORTABLE`;
locator and retention declarations alone no longer elevate availability.
- Expand the public presentation gate to reject drive-qualified paths, private
@@ -62,7 +257,7 @@
## 1.1.1 - 2026-08-22
- Replace the overview's generic maturity badges with the exact status of every
- object and graph layer, so the presentation cannot imply evidence or
+ object and Graph numbered profile, so the presentation cannot imply evidence or
implementation maturity that the specifications do not establish.
- Enforce those visual labels in the presentation gate and publish canonical
receipt schemas at their declared GitHub Pages `$id` routes.
@@ -88,13 +283,13 @@
## 1.0.1 - 2026-08-22
-- State explicitly that each VSTD layer requires its own evidence: layer 4 does not
- supply, entail, upgrade, or repair layers 3, 2, or 1.
+- State explicitly that each VSTD closure coordinate requires its own evidence:
+ Refutability does not supply, entail, upgrade, or repair prerequisite coordinates.
- Replace unsupported Tarski, generic NP-certificate, CNF-equals-3-SAT, and
physical-world co-NP claims with bounded statements tied to implemented formal
languages and declared observation surfaces.
-- Replace adopter-migration framing with a frozen wire-identifier and historical
- project-filename registry; no external adoption is claimed.
+- Replace adopter-migration framing with an exact current wire-dispatch registry; no
+ external adoption is claimed.
- Generate source releases from exact public Git objects and publish a separate
manifest binding the resolvable ref, commit, archive digest, file set, and member
bytes. Line-ending equivalence is not accepted as byte identity.
@@ -111,27 +306,29 @@
## 1.0.0 - 2026-08-22
-- Redesign specification numbers as verification-depth layers: VSTD-1 through
+- Redesign specification numbers as cumulative numbered profiles: VSTD-1 through
VSTD-5 on the object axis and VSTD-Graph-1 through VSTD-Graph-5 on the
collection axis.
-- Hard-rename the historical specification paths while preserving issued receipt
- wire identifiers and the `v0.1.0` and `v0.2.0` release history.
-- Implement the fourteen-rung VSTD-4 refutability ladder and compute depth by
- iterated satisfiability rather than accepting a declared level.
+- Establish integer numbered-profile specification paths while release history remains available in
+ the corresponding Git tags.
+- Add the fourteen-rung VSTD-4 structural calculation and compute its candidate depth by
+ iterated satisfiability rather than copying a declared depth. Version 1.2.0 clarifies
+ that its caller-supplied references do not establish VSTD-4 conformance.
- Add the `VSTD4-GDC-1` grounded decision-certificate format, independent bounded
checker, Horn/unit-propagation tier, width-bounded and general-resolution tiers,
and evidence-bearing `UNKNOWN` results on exhaustion.
- Add machine-readable refutation surfaces, precommitment envelopes, availability
assessment, append-only challenge adjudication, monotonic degradation, and
refutability closure.
-- Compute VSTD-Graph level from membership, provenance closure, status, and edge
- evidence, with a certificate explaining the next unreachable level.
+- Preserve the historical `graph_level` compatibility calculation from membership, provenance closure,
+ status, and caller-supplied edge ratings, with a certificate explaining the next
+ unreachable candidate Graph profile. Version 1.2.0 labels conformance `NOT_ESTABLISHED`.
- Replace fabricated conflict evidence, literal trust-boundary claims, and
decorative policy certificates with checked evidence and fail-closed divergence.
- Publish a draft VSTD-5 witness-corroboration interface. No independent witness
implementation or interoperability claim is included.
-- Move layer-specific and profile documentation under `docs/` and publish schemas
- with stable layer-oriented filenames.
+- Move profile-specific documentation under `docs/` and publish schemas with stable
+ compatibility filenames and paths.
## 0.2.0 - 2026-08-21
@@ -157,7 +354,8 @@
## 0.1.0 - 2026-08-21
-- Publish VSTD-0.1, VSTD-DATA-0.1, and experimental VSTD-0.2.
+- Publish the initial claim-mechanics, provenance-graph, and experimental
+ verification-geometry surfaces.
- Publish zero-required-dependency receipt, provenance, geometry, and policy primitives.
- Publish an optional logits-level constraint kernel with atomic dependency profiles.
- Add a target-neutral public CLI for generic-run and stored VSTD-DATA receipts.
diff --git a/CITATION.cff b/CITATION.cff
index 1283e42..5e9e71e 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -1,11 +1,10 @@
cff-version: 1.2.0
-message: "If you use VSTD or its reference implementation, cite this release."
-title: "VSTD: A Two-Axis Ladder for Refutable Verification"
+message: "This describes the Verifier Standard (VSTD) 1.2.0 release candidate; cite the published release after it exists."
+title: "Verifier Standard (VSTD): Bounded, Refutable Evidence for Computational Claims"
type: software
authors:
- name: "TimeLordRaps"
-version: 1.1.3
-date-released: 2026-08-22
+version: 1.2.0
license: Apache-2.0
repository-code: "https://github.com/TimeLordRaps/verifier"
keywords:
@@ -17,3 +16,4 @@ keywords:
- accelerator accountability
- refutability
- proof certificates
+ - bounded claims
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..c115e62
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,4 @@
+# CLAUDE.md
+
+See [AGENTS.md](AGENTS.md). It is the single source of working rules for this repository,
+shared by every automated contributor regardless of harness. Read it before editing.
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index da00c28..6d7c117 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,4 +1,4 @@
-# VSTD community conduct
+# Verifier Standard (VSTD) community conduct
## Expected conduct
@@ -25,5 +25,5 @@ GitHub channel. For security vulnerabilities, use the private reporting route in
`SECURITY.md`. This project does not promise confidentiality beyond the controls of the
channel used.
-Because governance is currently founder-maintained, enforcement is not independent.
+Because governance is currently centralized under one maintainer, enforcement is not independent.
That centralization boundary is disclosed in `GOVERNANCE.md`.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 370fb35..f918290 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,31 +1,133 @@
-# Contributing to VSTD
+# Contributing to Verifier Standard (VSTD)
+
+> **Acronyms:** GNU Privacy Guard (GPG); uniform resource locator (URL).
Contributions are welcome when they make a declared verification surface more precise,
-more independently checkable, or easier to implement without strengthening unsupported
-claims.
+more checkable outside its producer, or easier to implement without strengthening unsupported
+claims. Counterexamples, incompatible parser results, and failed interoperability attempts
+are useful contributions.
+
+## Choose the right surface
+
+| Change | Primary location | Required companion work |
+|---|---|---|
+| Normative requirement or numbered-profile meaning | `standard/` | Matching installed copy under `src/verifier/specifications/`, compatibility analysis, schema/model/runtime review, and falsification test |
+| Frozen identifier or profile dispatch | `standard/WIRE_IDENTIFIERS.md` | Historical-receipt audit; never silently redefine a released value |
+| Published receipt shape | `receipts/schema/` | Typed model, validator, examples, Pages schema route, and adversarial schema tests |
+| Reference implementation | `src/verifier/` | Tests for the exact implemented proposition and failure boundary |
+| Command-line behavior | `src/verifier/runtime/public_cli.py` | Generated reference, installed-wheel smoke, and machine-readable output tests |
+| Ecosystem adapter or application profile | `src/verifier/interoperability/` or an explicitly experimental profile | Accepted upstream versions, native-verifier boundary, information-loss declaration, trust roots, and substitution/replay/scope-widening tests |
+| Non-normative research | `experiments/` | Experiment manifest, fixtures, unresolved horizons, and generated index |
+| Explanatory documentation | `docs/` | Local-link, acronym, presentation, and semantic-drift review |
+
+The authority order is:
+
+1. normative numbered-profile document;
+2. serialized receipt identifier (`schema_version`) and profile discriminator;
+3. published schema;
+4. typed model and validator;
+5. conformance tests;
+6. generated reference and examples.
+
+See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the concrete ownership map.
+A lower surface cannot silently redefine a higher one.
## Required for a normative change
-- identify the affected VSTD layer, repository release, and coordinate or seam;
-- state compatibility effects, including any frozen wire identifiers or historical
- receipts affected;
-- include a falsification condition;
-- update machine-readable schemas or typed models where applicable;
-- add tests that fail before the change and pass after it;
-- document new trust roots, unknowns, residuals, and horizons.
-
-Do not replace `UNKNOWN` with false, erase `CONFLICTED`, infer missing provenance, or
-call self-observation independent verification.
-
-Unless explicitly stated otherwise, a contribution intentionally submitted for
-inclusion in this repository is provided under the Apache License 2.0, including its
-Section 3 patent terms and Section 5 contribution terms. The project does not yet have
-a separate contributor license agreement or standards-venue patent policy; this is a
-known boundary for future standards-venue work.
-
-## Feedback that does not require a proposed patch
-
-Use the structured issue forms for specification ambiguities, counterexamples or
-unsound claims, and independent implementation reports. A failed implementation or
-interoperability attempt is useful evidence and is not treated as endorsement or
-adoption. Send vulnerability details only through the private route in `SECURITY.md`.
+- identify the affected VSTD profile, repository release, and exact coordinate or seam;
+- state compatibility effects, including serialized receipt identifiers and historical receipts;
+- state a falsification condition;
+- update schemas, typed models, runtime behavior, and installed specification copies where applicable;
+- add a test that fails before the change and passes after it;
+- document trust roots, unknowns, residuals, information loss, and unresolved horizons;
+- check every public route that exposes the meaning: command-line output, examples,
+ generated reference, diagrams, claims guidance, and release metadata.
+
+Do not replace `UNKNOWN` with false, erase `CONFLICTED`, infer missing provenance, turn
+a candidate calculation into conformance, or call self-observation independent
+verification. Storage location, repetition, matching outputs, and actor reputation do not
+increase assurance.
+
+## Add a profile or adapter
+
+Before proposing an adapter, document and test:
+
+1. exact accepted upstream versions and identifiers;
+2. preserved source bytes and canonicalization rules;
+3. the native verifier and its trust roots;
+4. field-by-field mapping and declared information loss;
+5. freshness, availability, invalid, unsupported, and unknown behavior;
+6. substitution, omission, replay, conflict, and scope-widening fixtures;
+7. the VSTD proposition that consumes the native result;
+8. an explicit non-endorsement and non-adoption statement.
+
+The current Supply Chain Integrity, Transparency, and Trust (SCITT) work is an
+interoperability experiment, not evidence that every adjacent system needs an adapter.
+Each adapter increases the maintained and trusted surface.
+
+## Tests and local gates
+
+Run the repository-prescribed paths:
+
+```bash
+python -m pytest -q
+python -m coverage run --branch --source=src/verifier -m pytest -q
+python -m coverage report --show-missing
+python -m coverage json --pretty-print -o coverage.json
+python scripts/check_presentation.py
+python scripts/check_acronyms.py
+python scripts/check_terminology.py
+python scripts/build_reference.py --check
+python scripts/build_experiment_index.py --check
+python scripts/build_pages.py --output PATH_TO_EMPTY_DIRECTORY
+python scripts/check_time_status.py
+python -m compileall -q src scripts
+```
+
+The coverage report is bounded test evidence, not proof of correctness, completeness, or
+conformance. Review per-file and branch results in `coverage.json`; the aggregate cannot
+justify weakening a critical component's tests. No repository-wide pass threshold is
+defined until repeatable component baselines justify one.
+
+Pull requests retain the assembled Pages site as a commit-addressed review artifact.
+`documentation-coordinate.json` states its version, release state, source ref, canonical
+base URL, and normative owner. `standard/` remains authoritative; generated Pages output
+is navigation and rendering, not another specification. Published tags and their release
+artifacts are the historical documentation coordinates.
+
+Changes to optional cryptographic paths must also install their declared extra and run the
+non-skippable focused test. Release or packaging changes must run the exact-Git-object
+artifact builder, manifest verifier, package metadata check, release-boundary scanner,
+and installed-wheel smoke described in [`RELEASING.md`](RELEASING.md).
+
+## Commits and pull requests
+
+Commits are GPG-signed (`git commit -S`). A signature binds commit bytes to a key; it
+does not establish identity, correctness, authorization, independence, or safety.
+
+Use the pull-request template to record:
+
+- the exact coordinate;
+- what changes and what remains unchanged;
+- the falsification condition and tests;
+- serialized-format and compatibility impact;
+- trust roots, unknowns, residuals, and horizons; and
+- every downstream surface reviewed.
+
+## Report without a patch
+
+- [Specification ambiguity](https://github.com/TimeLordRaps/verifier/issues/new?template=specification-ambiguity.yml)
+- [Counterexample or unsound claim](https://github.com/TimeLordRaps/verifier/issues/new?template=counterexample.yml)
+- [Independent implementation or interoperability report](https://github.com/TimeLordRaps/verifier/issues/new?template=implementation-report.yml)
+- [Private vulnerability report](https://github.com/TimeLordRaps/verifier/security/advisories/new)
+
+Do not place sensitive vulnerability details in a public issue. If the private route is
+unavailable, report only that non-sensitive fact publicly.
+
+## License and governance
+
+Unless explicitly stated otherwise, a contribution intentionally submitted for inclusion
+is provided under the Apache License 2.0, including its Section 3 patent and Section 5
+contribution terms. The project has no separate contributor license agreement or
+standards-venue patent policy. Governance and current decision rights are documented in
+[`GOVERNANCE.md`](GOVERNANCE.md).
diff --git a/GOVERNANCE.md b/GOVERNANCE.md
index a24c048..03819b6 100644
--- a/GOVERNANCE.md
+++ b/GOVERNANCE.md
@@ -1,8 +1,10 @@
-# VSTD governance
+# Verifier Standard (VSTD) governance
+
+> **Acronym:** grounded decision certificate (GDC).
## Current phase
-VSTD is founder-maintained project specification work. Publication makes the text and
+VSTD is maintainer-led project specification work. Publication makes the text and
reference implementation inspectable; it does not manufacture multi-stakeholder
consensus or standards-body recognition.
@@ -11,24 +13,29 @@ emulator, offline adapters, provenance composition, and conformance suite. It is
claim that accelerator vendors implemented the firmware contract or accepted the
specification.
-VSTD-4 is implemented for the declared grounded-certificate, computed-depth,
-availability, precommitment, challenge, degradation, and composition surfaces.
-`VSTD4-GDC-1` has no demonstrated independent implementation or external
-interoperability. VSTD-5 remains draft and has no shipped witness procedure.
+VSTD-4 ships grounded-certificate/kernel checks and separate availability,
+precommitment, challenge, degradation, and composition mechanisms. Its candidate-depth runtime
+computes only a structural candidate over caller-supplied references with conformance
+`NOT_ESTABLISHED`. The separate evidence-bound runtime rehashes and reruns exact VSTD-1/2/3
+and fourteen-rung propositions before it may establish VSTD-4 conformance; its receipt
+rechecker reproduces that result offline with the supplied mechanism implementations.
+`VSTD4-GDC-1` has no demonstrated independent implementation or external interoperability.
+VSTD-5 has a shipped evidence-bound reference procedure, but the repository claims no real
+external witness or second implementation.
-## Layer and release states
+## Numbered-profile and release states
-- **Implemented base:** a layer has a published specification, schema or typed model,
+- **Implemented base:** a numbered profile has a published specification, schema or typed model,
executable reference path, and passing conformance tests for its declared surface.
- **Experimental:** the vocabulary and a bounded vertical slice exist, but independent
implementations or broader interoperability evidence are still missing.
- **Challenged:** current evidence no longer supports a previously published claim.
- **Superseded:** an additive correction replaces a bounded document while historical
- release bytes and wire identifiers remain unchanged.
+ release bytes and serialized receipt identifiers remain unchanged.
-Specification layers use integer names; repository releases use semantic versions.
+Numbered profiles use integer names; repository releases use semantic versions.
Released artifacts are frozen. Corrections are additive and identify the affected
-layer, release, claim, evidence, and downstream impact.
+numbered profile, release, claim, evidence, and downstream impact.
## Change process
@@ -50,12 +57,12 @@ intended next governance step is independent implementation feedback followed by
venue with explicit copyright and patent terms.
The repository's Apache License 2.0 governs the specification, documentation, and
-reference implementation in this release. Its contributor patent grant is a project
+reference implementation at this source coordinate. Its contributor patent grant is a project
license term, not a substitute for a neutral standards venue's intellectual-property
policy or a separate contributor agreement.
## Conformance and marks
No organization is currently an accredited VSTD certifier. Implementers may state the
-exact VSTD layer, repository release, receipt type, tests, and evidence they support. They must not imply
+exact VSTD profile, repository release, receipt type, tests, and evidence they support. They must not imply
endorsement, comprehensive safety, or verification beyond that surface.
diff --git a/HUMANS.md b/HUMANS.md
new file mode 100644
index 0000000..394ac8a
--- /dev/null
+++ b/HUMANS.md
@@ -0,0 +1,145 @@
+# Human operating guide for Verifier Standard (VSTD)
+
+**Role:** practical reasoning guide for human maintainers and reviewers. Normative meaning
+remains in [`standard/`](standard/); this file defines no receipt, status, or serialized receipt format.
+
+## Three repository controls
+
+| Surface | Use it for | Do not use it for |
+|---|---|---|
+| [`AGENTS.md`](AGENTS.md) | Rules for automated and coding-agent work | Human interpretation or normative semantics |
+| `HUMANS.md` | The questions a human asks before relying on a result | A second specification |
+| [`TIME.md`](TIME.md) | The live annunciator for contradictions in this repository's authoritative state | Runtime evidence conflicts, roadmaps, or ordinary limitations |
+
+## Traverse a claim with the five As
+
+The five As are a human traversal over existing VSTD records, not a new ontology or an
+assurance score.
+
+1. **ASSURE — establish the input state.** Identify the evidence or previously assessed
+ claim. Preserve its provenance, evidence basis, bounds, trust roots, limitations,
+ freshness, current state, conflicts, and unknowns.
+2. **ATTRIBUTE — name the supported proposition.** State the exact subject and predicate,
+ the mapping, extraction, or transformation that connects the evidence to them, its scope
+ and bounds, and any information loss. A reference without a checked mapping is not
+ attributed support.
+3. **ASSIGN — locate the evidenced execution.** Record only the coordinates established for
+ the computation, execution instance, software/runtime, machine/substrate, and optional
+ actor or operator. Partial assignment is valid. Assignment does not imply trust,
+ authorization, independence, or responsibility.
+4. **ASSESS — run the named mechanism.** Ask which bounded proposition this verifier,
+ specification, profile, trust-root set, and resource bound actually checks. The result
+ earns no predicate outside that mechanism.
+5. **ASSURE — preserve the output as new evidence.** Record the assessed claim with lineage
+ to every input, mechanism, bound, limitation, conflict, and unknown. A later assessment
+ may consume it, but propagation alone cannot strengthen it or rewrite its ancestors.
+
+> Storage location, field name, repetition, graph multiplicity, actor reputation, and
+> propagation add no semantic strength. Every increase in assurance names the mechanism
+> that earned it.
+
+## Read artifact state as TRUST, ROT, and RUST
+
+These capitalized terms are formal semantic names, not acronyms, actor ratings, scalar
+scores, serialized receipt values, or references to the Rust programming language.
+
+| Term | Human reading |
+|---|---|
+| **TRUST** | A named mechanism earned bounded forward support for an exact artifact-bound process claim. It says nothing about whether an actor is good, bad, reputable, or trustworthy. |
+| **ROT** | Typed lifecycle or dependency evidence degraded the support's current admissibility. Reassess affected dependents, but preserve the immutable historical receipt and its original result. |
+| **RUST** | An observed descendant deviation can be traced backward through recorded contributing ancestry. The trace identifies candidates for examination; it does not prove ancestor falsehood, guilt, responsibility, or causal localization. |
+
+The reference `AssuranceLedger` records these as additive Graph events. Treat structural
+RUST concentration as a triage count of unique deviating descendants, never causal
+strength. A bounded artifact-relative `BLAME` or `GUILT` result exists only after separate
+localization and attribution mechanisms pass. BLAME establishes responsibility or material
+contribution for the exact deviation. GUILT is not BLAME flowing in the opposite direction:
+it requires separately bound passing responsibility, exact obligation-applicability, and
+same-obligation violation components, then a final evaluation binding all three component
+digests. One compound mechanism may perform the three checks in one invocation only when it
+emits three separately bound evaluations. A label such as `violated_obligation`, even repeated
+consistently, earns nothing by itself. The localization must name the exact passing RUST event
+and descendant-deviation binding; sharing a descendant identifier is insufficient. Neither
+result evaluates an actor's character, reputation, social standing, or automatic legal
+liability. Missing GUILT is not innocence or exoneration, and does not prove obligation
+satisfaction or absence of hidden contributors. A resolved conflict restores current TRUST only when
+its selected value has a checked admissible status consequence; selecting an arbitrary value
+does not establish admissibility.
+For reliance, replay the portable log with `recheck_assurance_log`: a stored event word or
+hash chain without successful evidence rehash and mechanism execution is not current
+assurance. When upstream status changes, inspect `current_trust_events` and the deduplicated
+`impacted_descendants` reassessment surface rather than deleting historical results.
+
+Zero identity means identity contributes no verdict weight by itself. Zero knowledge means
+no unevidenced proposition is presumed: absent a mechanism-earned result, keep `UNKNOWN`.
+When a witness must remain confidential, a cryptographic zero-knowledge proof can enclose
+that architectural rule by binding the exact program, predicate, commitments, output,
+parameters, and verifier. Check the proof system rather than the prover's identity. A digest
+or undisclosed input alone is not a zero-knowledge proof. Identity, authorization,
+attribution, or actor separation may still be checked as their own bounded propositions;
+they never become TRUST in the validity of the represented computational process.
+
+## Read freeze, seal, and thaw separately
+
+- A **freeze** means the current preserved bytes, paths, manifest, and read-only tripwire
+ recomputed. It does not mean an external archive retained them or privileged mutation
+ is impossible.
+- A **seal** means the finite signature-and-identifier closure verified. It is not
+ encryption and does not establish correctness, ownership, authorization, trusted time,
+ or actor trust. Use an expected artifact/key coordinate to detect complete substitution.
+- A **thaw** creates a mutable descendant. `THAWED_CLEAN` records present equality to the
+ parent identity; `THAWED_DIRTY` records divergence. Neither state changes the parent.
+
+A time capsule adds a realm-specific temporal proposition and evidence. A structural seal
+alone does not establish continuous custody between endpoints, a realm's physical laws,
+cross-realm mappings, or the truth of generated text.
+
+## What a human may conclude
+
+These terms describe different evidence states; none substitutes for another.
+
+| Evidence state | Safe conclusion |
+|---|---|
+| **Recorded** | The identified statement or bytes are present at the named coordinate. Their presence does not establish truth or validation. |
+| **Checked** | The named mechanism ran its declared checks. Read its result and limits; execution alone is not a pass. |
+| **Bound** | The named digest, commitment, or coordinate ties the result to the declared subject inside its scope. Binding does not establish the subject's external truth. |
+| **Reproduced** | A declared rerun or comparison met the recorded equivalence rule. It does not by itself establish correctness, provenance completeness, or independent actors. |
+| **Independently corroborated** | Distinct actors and every independence seam required by the applicable profile are evidence-bound and checked. Matching runs, processes, machines, or self-declared references are insufficient. The version 1.2.0 bundled runtime has no actor/execution evidence-binding adapter and cannot derive `EVIDENCED`. |
+
+Status words are profile-scoped. Use their controlling specification; the safe minimum
+reading is:
+
+| Result | Safe conclusion |
+|---|---|
+| `PASS` | The named mechanism established its bounded proposition under the recorded preconditions. |
+| `FAIL` | The mechanism established the specified violation, counterexample, or failed condition. Do not dilute an evidenced failure into uncertainty. |
+| `UNKNOWN` | Available evidence, implemented fragment, or declared resources did not decide the proposition. This proves neither truth nor falsehood. |
+| `CONFLICTED` | Incompatible evidence is retained without collapse into a clean state. This is an evidence/runtime condition, not a TIME repository contradiction. |
+| `UNSUPPORTED` | The named mechanism lacks the capability or observation surface required for the proposition. This is not a `FAIL` and not a promise of future support. |
+
+First-hand and second-hand identify **provenance, not strength**. A first-hand
+self-observation can be weak; a second-hand certificate can be strongly bound to a narrow
+proposition. Judge the mechanism and binding, not the label, actor identity, or reputation.
+
+VSTD allows a human to select trust roots, compare bounded evidence, and make a separate
+risk or action decision. It does not make that judgment for the human. Record any judgment
+as a distinct decision with its own basis; do not rewrite a verifier result to match it.
+
+## Read and escalate
+
+When surfaces appear to disagree, use the complete authority order in
+[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md): normative numbered-profile document, serialized receipt identifier/profile,
+published schema, typed runtime and validator, conformance tests, then generated references
+and examples. A lower surface cannot silently redefine a higher one.
+
+Escalate to [`TIME.md`](TIME.md) when current authoritative repository surfaces remain
+incompatible—for example normative text versus schema, schema versus runtime, runtime versus
+conformance tests, a public claim beyond implementation, incompatible frozen semantics, or a
+five-As transition that gains assurance without a mechanism. Preserve both sides and exact
+coordinates; resolve only from evidence.
+
+Do **not** escalate a receipt's `CONFLICTED` evidence, an honest `UNKNOWN`, a roadmap item,
+or speculative research to TIME. Development branches may keep precise open contradictions.
+For publication, the tag-triggered workflow checks the exact tagged `TIME.md` and fails
+unless it contains exactly one `Status: CLEAR` line; maintainer judgment cannot override
+that release invariant.
diff --git a/README.md b/README.md
index ed461e0..e732ba8 100644
--- a/README.md
+++ b/README.md
@@ -1,29 +1,51 @@
-
-
-# VSTD
+# Verifier Standard (VSTD)
**Portable, bounded, refutable evidence for computational claims.**
-[](https://github.com/TimeLordRaps/verifier/actions/workflows/ci.yml)
+[](https://github.com/TimeLordRaps/verifier/actions/workflows/ci.yml)
[](https://github.com/TimeLordRaps/verifier/releases/latest)
[](https://www.python.org/)
[](LICENSE)
-[](#project-status)
-
-*A PASS is not enough. Show what passed, under which meaning, against which
-evidence, inside which bounds, and how somebody else can prove it wrong.*
-
-[Run the demo](#see-it-fail-correctly) ·
-[Read the quickstart](docs/QUICKSTART.md) ·
-[Inspect the standard](standard/LADDER.md) ·
-[Challenge a claim](https://github.com/TimeLordRaps/verifier/discussions/8) ·
-[See the roadmap](ROADMAP.md)
+[](#current-maturity)
-## See it fail correctly
+> **Acronyms used below:** identifier (ID); reduced instruction set computer (RISC).
+
+VSTD is a verification-domain language and Python reference implementation for packaging
+bounded computational claims with their evidence, checking mechanisms, limits,
+refutation conditions, provenance, and reproducibility information. It does **not**
+replace native domain verifiers, proof systems, signatures, identity systems,
+transparency logs, or provenance formats, and it never strengthens their results merely
+by translating or storing them.
+
+VSTD evaluates bounded validity propositions about computational processes represented by
+software and evidence-bearing artifacts. It does not decide whether an actor is good, bad,
+reputable, or trustworthy; identity and reputation alone contribute no verdict weight.
+
+It addresses a practical review problem: a final answer or green check rarely says
+exactly what was checked, which evidence was used, where the conclusion stops, or what
+would overturn it. VSTD carries those boundaries with the result.
+
+**Current boundary:** implemented reference paths cover receipts, generic computation
+capture, provenance graphs, verification geometry, accelerator evidence, grounded
+certificate checking, evidence-bound VSTD-4/VSTD-5 assessment, evidence-bound Graph
+ratings, replayable additive Graph lifecycle/assurance propagation, reproduction, exact-byte artifact
+freezing, finite self-closing seals, copy-on-write thawing, and a flagship adversarial demo.
+Compatibility candidate paths remain `NOT_ESTABLISHED`; evidence-bound paths rerun exact
+registered mechanisms and preserve their evidence, trust roots, bounds, and limitations.
+No real external witness or independent implementation is claimed. See [current maturity](#current-maturity) and
+[claims and limits](docs/CLAIMS_AND_LIMITS.md).
+
+[Normative specifications](standard/LADDER.md) ·
+[60-second quickstart](docs/QUICKSTART.md) ·
+[Implementation reference](https://timelordraps.github.io/verifier/reference.html) ·
+[Report an ambiguity or counterexample](https://github.com/TimeLordRaps/verifier/issues/new/choose) ·
+[Report a vulnerability privately](SECURITY.md)
+
+## 30–60 second demonstration
```bash
git clone https://github.com/TimeLordRaps/verifier.git
@@ -32,7 +54,7 @@ python -m pip install .
vstd demo
```
-The side-effect-free flagship demo runs four adversarial specimens. Abridged output:
+The side-effect-free demo runs four public adversarial specimens:
```text
VSTD flagship adversarial demo
@@ -40,35 +62,88 @@ VSTD flagship adversarial demo
[DEMO OK] Valid-looking proof, wrong artifact → REJECTED
[DEMO OK] Bound exhausted without a false answer → ACCEPTED/UNKNOWN
[DEMO OK] Inflated verification-cost claim → REJECTED
-[DEMO OK] Revoked ancestor behind valid descendants → GRAPH-LEVEL-0
+[DEMO OK] Revoked ancestor behind valid descendants → GRAPH-CANDIDATE-0
```
-These are bounded checks over included specimens—not evidence of empirical truth,
-complete provenance, external adoption, or general AI safety. Run `vstd demo --json`
-for the complete machine-readable results or `vstd demo --emit-specimens PATH` to
-emit each specimen.
-
-## What VSTD adds
+`[DEMO OK]` means the expected defensive outcome occurred; it is not a VSTD `PASS`.
+The scenarios establish bounded behavior of this reference implementation over the
+included specimens. They do not establish empirical truth, complete provenance,
+external adoption, independent implementation, or general artificial intelligence (AI)
+safety. Use `vstd demo --json` for JavaScript Object Notation (JSON) output or
+`vstd demo --emit-specimens PATH` to inspect the generated files.
+
+## What a result means
+
+VSTD result terms remain tied to one exact proposition, mechanism, evidence set, and
+bound:
+
+| Result | Bounded meaning | It does not mean |
+|---|---|---|
+| `PASS` | The named mechanism established its declared proposition inside the stated coordinate and bounds. | The proposition is universally or permanently true. |
+| `FAIL` | The mechanism found a checked violation, rejected certificate, or counterexample at the named surface. | Every broader interpretation is false. |
+| `UNKNOWN` | Available evidence, capability, or resources did not establish `PASS` or `FAIL`. | False, safe, unsupported forever, or “probably PASS.” |
+| `CONFLICTED` | Incompatible evidence or assertions remain explicit. | The conflict was resolved by choosing one side. |
+| `NOT_ESTABLISHED` | The evaluated path did not establish conformance: it may be a compatibility candidate, or required evidence, exact binding, mechanism availability, mechanism result, prerequisite, or profile floor was missing or non-passing. | Conformance, readiness, or a weak form of `PASS`. |
+
+A VSTD `PASS` never means “true in the real world” without the exact real-world
+proposition and observation boundary being part of the checked claim.
+
+## Current maturity
+
+This is the canonical repository status table. “Implemented” applies only to the named
+reference surface; it does not imply adoption, external interoperability, certification,
+or a second implementation.
+
+| Surface | Normative status | Reference implementation | Evidence binding | Conformance status | Missing mechanism or evidence |
+|---|---|---|---|---|---|
+| VSTD-1 | Project specification with implemented reference subset | Claim receipts, checker reports, strict generic-run profile, inspection, and current-profile reads | Claim coordinates, stable digests, mechanism descriptors, and declared provenance; actor separation is not inferred | Implemented reference subset | External implementation and a validator binding distinct producer/checker actors and execution seams |
+| VSTD-2 | Additive experimental project specification | Typed verification geometry, residuals, closure checks, schema, and tests | Geometry and declared reconstruction evidence inside the receipt | Implemented vertical slice | Independent implementation and broader geometry interoperability |
+| VSTD-3 | Implemented project specification | Typed accelerator model, strict validator, emulator, offline adapters, continuity, fleet, and claim evaluation | Conditional on source-specific signatures, nonces, reference values, topology, events, and trust roots; host inventory remains weak evidence | Implemented reference surface | Vendor firmware integration, production trust roots, and complete-mediation evidence outside the emulator boundary |
+| VSTD-4 | Project specification with implemented reference paths | grounded decision certificate (GDC) parser/kernel, compatibility candidate depth, and evidence-bound establishment/recheck | Exact VSTD-1/2/3 and fourteen-rung propositions, content-addressed evidence bytes, mechanism implementation digests, trust roots, and bounds | Candidate path `NOT_ESTABLISHED`; evidence-bound path can establish conformance | Independent implementation, external interoperability, and deployment-specific rung mechanisms/evidence |
+| VSTD-5 | Project specification with implemented reference mechanism | Evidence-bound entry gate, seven separation dimensions, exact admitted-certificate binding, corroboration checks, duplicate refusal, disagreement preservation, receipt build/recheck | Witness coordinate, exact negative separation propositions, VSTD-4 commitment/certificate, checker, observations, mechanisms, trust roots, bounds, and embedded evidence | Mechanism can establish a bounded result; a positive observation with unresolved independence remains overall `UNKNOWN`; no repository claim of a real independent witness | Real independent witnesses, second implementation, external attack, and operational interoperability |
+| VSTD-Graph-1 | Project specification with implemented reference subset | Content-addressed artifacts, transformations, conflicts, policy queries, receipts, and recorded reachability | Binds recorded objects and edges; it does not establish real-world completeness or causality | Implemented reference subset | Independent implementation and external provenance-profile interoperability |
+| VSTD-Graph-2 | Project specification with implemented reference paths | Compatibility candidate plus evidence-bound Bounded Collection Surface computation/recheck | Registered mechanisms rerun exact member, ancestor, and edge ratings bound to the Graph bytes, deduplicated members, collection, and claim | Candidate `NOT_ESTABLISHED`; evidence-bound profile 1–5 path can establish; profile zero cannot | External rating mechanisms, independent implementation, and interoperability |
+| VSTD-Graph-3 | Project specification with implemented reference paths | Compatibility candidate plus evidence-bound Accountable Provenance Closure computation/recheck | Same complete closure binding, including VSTD-3 rating propositions | Candidate `NOT_ESTABLISHED`; evidence-bound path can establish | Production VSTD-3 rating evidence across a real collection |
+| VSTD-Graph-4 | Project specification with implemented reference paths | Compatibility candidate plus evidence-bound Refutable Transformation Closure computation/recheck | Same complete closure binding; an edge mechanism must actually check its refutability closure | Candidate `NOT_ESTABLISHED`; evidence-bound path can establish | External closure mechanisms and independent replay |
+| VSTD-Graph-5 | Project specification with implemented reference paths | Compatibility candidate plus evidence-bound Corroborated Verification Network computation/recheck | Exact VSTD-5 object and transformation rating mechanisms across the complete closure | Candidate `NOT_ESTABLISHED`; evidence-bound path can establish | Real independently corroborated collection, second implementation, and interoperability |
+| Generic run | VSTD-1 generic-computation profile | Plan, execute, capture, inspect, strict shape/digest validation, and declared-output rerun | Captures command, source state, outputs, environment, and manifest declarations; generic validation is not native claim verification or VSTD-4 conformance | Implemented VSTD-1 profile | Sandbox, generic external-evidence resolver, and actor/execution binder |
+| Artifact freeze, seal, and thaw | Normative artifact-control mechanism; not a numbered VSTD or receipt profile | Exact regular-file byte preservation, dual-digest artifact identity, read-only guards, finite self-closing Ed25519 seals, external anchor checks, and copy-on-write thaw status | Binds artifact bytes, paths, media type, freeze manifest, carried key, signature, and optional expected artifact/key coordinates | Implemented mechanism version 1 | Durable external archive, privileged-write prevention, trusted time, encryption, semantic correctness, and realm/continuity verification |
+| Experimental workflow | Non-normative experimental profile 0.1 | Strict validator, verdict-neutral GitHub event projector, allocation records, and command-line interface (CLI) | Preserves native platform results and explicit horizons with `verification_effect = NONE` | No VSTD conformance claim | Independent consumer, additional platform adapter, and evidence for allocation optimality |
+| Supply Chain Integrity, Transparency, and Trust (SCITT) interoperability | Experimental, non-normative application profile and crosswalk | Real local Concise Binary Object Representation (CBOR) plus CBOR Object Signing and Encryption (COSE) signatures/receipt, loss-declared adapter, and adjacent native-result composition | Binds the exact payload under emitted test keys and local policy; registration never establishes payload truth | VSTD-4 remains `NOT_ESTABLISHED` | Public Transparency Service, external implementation/interoperability result, and Internet Engineering Task Force (IETF) review |
+| zero-identity/zero-knowledge (ZIZK) artifact-first TRUST | Governing VSTD architecture in `standard/LADDER.md` section 1.1; not a separate numbered profile | Hash-chained event serialization and offline replay, evidence-bound forward TRUST, typed ROT, challenge projection, reverse RUST, structural concentration, conflict resolution, explicit localization, and bounded diagnostic attribution | Exact Graph topology, proposition bindings, embedded evidence bytes, mechanisms, trust roots, bounds, and immutable history | Implemented reference mechanism; no universal support score or actor trust | Domain-specific transfer/localization mechanisms, independent cross-implementation replay, complete trichotomy derivation, and maturation of optional proof backends |
+| RISC Zero proof-carrying reference mechanism | Bounded non-normative mechanism example under the governing ZIZK architecture | Pinned prover/verifier source plus a tracked real receipt, public envelope, self-test result, and network-offline command that requires the tracked guest build and recorded proof to share one image identifier | Authenticates one fixed hidden-witness predicate and expected image identifier; it does not establish the witness's external truth | Native proof verified; no VSTD receipt mapping | Independent build host, external audit, complete VSTD trichotomy predicate, and additional proof backends |
+
+The authoritative implementation-to-specification map is
+[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). Normative meaning remains under
+[standard/](standard/).
+
+## Why VSTD exists
Ordinary computational results often omit machine-readable answers to four questions:
-1. **What exactly was claimed?** The subject, predicate, parameters, and limits.
-2. **Which exact evidence supports it?** Digests, mechanisms, provenance, and trust roots.
-3. **Where does the verdict stop?** Explicit coordinates and resource bounds.
-4. **How can it change?** Reproduction, counterexample, challenge, and degradation rules.
+1. **What exactly was claimed?** Subject, predicate, parameters, scope, and limits.
+2. **Which evidence supports it?** Exact bytes, digests, mechanisms, provenance, and trust roots.
+3. **Where does the verdict stop?** Explicit coordinates, exclusions, and resource bounds.
+4. **How can it change?** Reproduction, counterexample, challenge, invalidation, and degradation rules.
+
+VSTD packages that review boundary in receipts and provenance hypergraphs. The core
+design rule is:
-VSTD stores those answers in receipts and provenance hypergraphs. The reference
-implementation can validate stable receipt content, reproduce declared mechanisms,
-check grounded decision certificates, and compute collection-level ceilings from
-recorded ancestry and caller-supplied object and edge ratings.
+> No assurance is gained from storage location, field name, repetition, graph
+> multiplicity, actor reputation, or propagation. Every increase must identify the
+> verification mechanism that earned it.
-## Two axes; evidence never substitutes
+## Architecture
-Specification numbers identify verification depth, not revisions. Every row is a
-different question with its own evidence. A higher-layer result does **not** supply,
-imply, upgrade, or repair a lower-layer result.
+
-| Depth | VSTD object mechanics | VSTD-Graph collection dynamics |
+VSTD is a verification complex of named closure coordinates and evidence-bearing
+relations. Specification numbers select cumulative profiles, not software revisions,
+interchangeable layers, or scalar assurance levels. The object axis evaluates one
+computational claim; the Graph axis evaluates a bounded collection and its recorded
+transformations.
+
+| Profile number | Object closure coordinate | Graph closure coordinate |
|---:|---|---|
| 1 | Claim mechanics | Recorded lineage |
| 2 | Verification surface | Bounded collection surface |
@@ -76,136 +151,265 @@ imply, upgrade, or repair a lower-layer result.
| 4 | Refutability | Refutable transformation closure |
| 5 | Witness corroboration | Corroborated verification network |
-An aggregate depth of `N` is valid only when distinct evidence passes every layer from
-1 through `N`. Layers 1–4 are self-discernable; layer 5 requires another party to
-exist, act, and be independent. VSTD-5 and its witness protocol remain **DRAFT**.
+A later-profile result does **not** supply, imply, upgrade, or repair a prerequisite
+coordinate. Object profile depth requires separate passing evidence for every required
+coordinate.
+
+As an operational traversal, an implementation may capture a run through VSTD-1, map
+profiler or domain observations through adjacent adapters into a VSTD-2 surface, bind the
+execution substrate through VSTD-3, make the result portably refutable through VSTD-4, and
+record independently evidenced witness corroboration through VSTD-5. This traversal does
+not collapse the named coordinates. VSTD-Graph is the orthogonal collection axis: a bounded Graph result
+may be materialized as a content-addressed artifact and enter a later verification loop only
+with its source graph, selected surface, mechanism, lineage, losses, limitations, conflicts,
+and current admissibility preserved. The compatibility `graph_level` result remains a
+`NOT_ESTABLISHED` candidate. `establish_graph_level` can establish only profile 1–5 after
+every required rating mechanism is rerun from exact evidence bound to the exact Graph,
+member set, collection, and claim. Profile zero remains `NOT_ESTABLISHED`.
+
+The formal names **TRUST**, **ROT**, and **RUST** are semantic terms, not acronyms, actor
+ratings, scalar scores, numbered-profile verdicts, or references to the Rust programming language.
+They serialize only as typed events in `VSTD-GRAPH-ASSURANCE-1`.
+TRUST is mechanism-earned artifact support moving forward edge by edge through checked
+development, with each event binding one exact transformation, its inputs and output, the
+historical Graph digest, and any prerequisite TRUST events;
+ROT is typed, time-indexed degradation of current admissibility without rewriting
+historical evidence; RUST is the inverse-TRUST diagnostic mechanic moving backward from a
+descendant deviation through historically recorded contributing ancestry. Current
+revocation or conflict can remove a route from current TRUST without erasing that diagnostic
+history. This memetic propagation does not by itself prove guilt, responsibility, falsehood,
+causal localization, or automatic ancestor falsification. The reference runtime requires a separate passing localization
+mechanism bound to one exact passing RUST event and descendant-deviation proposition before
+it can emit a bounded
+`BLAME` or `GUILT`. BLAME establishes bounded responsibility or material contribution;
+GUILT is not its opposite, but the stronger combined result that additionally establishes
+an exact violated obligation. Neither result concerns actor character. See [the governing
+architecture](standard/LADDER.md#11-artifact-first-causal-provenance-orientation).
+
+`VSTD-GRAPH-ASSURANCE-1` carries the immutable historical Graph, exact event bindings,
+embedded evidence bytes, event hash chain, and derived current-view digest.
+`recheck_assurance_log` rehashes the evidence, reruns each exact registered mechanism, and
+rejects any event or current view that does not reproduce. Conflict adjudication and current
+admissibility are separate: a selected status affects the current artifact or transformation
+state, while resolving an arbitrary predicate cannot silently restore TRUST. No general
+non-status admissibility-effect mechanism is currently implemented, so that route remains
+blocked.
+
+This is VSTD's **ZIZK artifact-first TRUST architecture**, not an optional research
+profile. Zero identity means zero identity-derived verdict weight, not anonymity or the
+absence of identifiers. Zero knowledge means zero unevidenced knowledge is presumed: a
+proposition remains `UNKNOWN` until a named mechanism earns a bounded result. When a
+witness must remain confidential, cryptographic zero knowledge can enclose that
+architectural rule by binding the exact program, predicate, public commitments, output,
+proof parameters, and verifier without attaching TRUST to the prover's identity. Only a
+named proof system can earn that privacy property; a digest or hidden input cannot. The
+runnable
+[RISC Zero reference mechanism](examples/zizk_artifact_first/) is one bounded backend,
+while its proof system and unfinished transfer mechanics remain mechanism-specific.
+
+## Install and use
+
+The distribution name is `verifier-standard`. The published base package has no
+required third-party runtime dependencies.
-Start with [`standard/LADDER.md`](standard/LADDER.md). Wire identifiers are frozen
-separately in [`standard/WIRE_IDENTIFIERS.md`](standard/WIRE_IDENTIFIERS.md).
+```bash
+python -m pip install verifier-standard # latest published release
+python -m pip install . # current release-candidate checkout
+python -m pip install ".[yaml]" # YAML Ain't Markup Language (YAML) manifests
+python -m pip install ".[jsonschema]" # JSON Schema validation
+python -m pip install ".[seal]" # optional Ed25519 artifact sealing
+python -m pip install ".[scitt]" # optional SCITT/COSE experiment
+```
-## Choose a path
+`vstd` is the canonical cross-platform CLI name. `verifier` remains a compatibility
+alias but can resolve to Windows Driver Verifier. `verifiable` is a permanent legacy
+alias because historical receipts may bind it in falsification instructions.
-| If you want to… | Start here |
-|---|---|
-| Understand the claim model in ten minutes | [`docs/QUICKSTART.md`](docs/QUICKSTART.md) |
-| Try to break the core claim | [`examples/flagship_demo`](examples/flagship_demo) |
-| Inspect a disclosure-bounded closed evaluation | [`examples/simulacrabench_synthetic`](examples/simulacrabench_synthetic) |
-| Implement an independent checker | [`standard/VSTD-4.md`](standard/VSTD-4.md) and [`VSTD4-GDC-1` schema](receipts/schema/vstd4_certificate.json) |
-| Model a provenance collection | [`standard/VSTD-Graph-1.md`](standard/VSTD-Graph-1.md) |
-| Integrate accelerator evidence | [`docs/layers/vstd-3/vendor-integration.md`](docs/layers/vstd-3/vendor-integration.md) |
-| Use VSTD beside existing supply-chain/provenance systems | [`docs/ECOSYSTEM.md`](docs/ECOSYSTEM.md) |
-| Review exact public claim limits | [`docs/CLAIMS_AND_LIMITS.md`](docs/CLAIMS_AND_LIMITS.md) |
-
-## Capture a generic computation
-
-**Security boundary:** a manifest contains an executable command. `vstd run` does not
-sandbox it. Inspect the plan first; run only a trusted manifest inside an operating
-system or container boundary appropriate to that command. Declared-path checks expose
-capture scope, not everything the subprocess can access.
+An unrelated PyPI distribution named `verifier` exports the same top-level Python
+import. Do not co-install it with `verifier-standard`.
+
+### Freeze, seal, verify, and thaw an artifact
+
+Freezing preserves exact regular-file bytes and portable paths. Sealing is a separate,
+readable authentication and closure action; it is **not encryption**. Generate an
+Ed25519 key with a suitable local key tool, then run:
+
+```bash
+openssl genpkey -algorithm Ed25519 -out ed25519-private.pem
+vstd artifact freeze PATH ARTIFACT.vstd --media-type application/octet-stream
+vstd artifact verify ARTIFACT.vstd --freeze-only
+vstd artifact seal ARTIFACT.vstd --private-key ed25519-private.pem
+vstd artifact verify ARTIFACT.vstd --expected-artifact-id EXPECTED_ID
+vstd artifact thaw ARTIFACT.vstd MUTABLE_COPY
+vstd artifact status MUTABLE_COPY --parent-bundle ARTIFACT.vstd
+```
+
+The finite seal signs the complete envelope with its signature and identifier fields
+explicitly empty, then derives the seal identifier over the signature-bearing envelope
+with only its identifier empty. Verification recomputes both projections, avoiding an
+infinite seal-of-seal regress. The carried public key establishes internal consistency;
+an expected artifact identifier, expected key identifier, or separately verified
+manifest/log coordinate is still required to detect whole-bundle substitution.
+
+A freeze or seal establishes bounded integrity and closure only—not correctness,
+freshness, ownership, authorization, trusted time, external preservation, or actor trust.
+Thaw is copy-on-write: it creates a mutable descendant and leaves the sealed parent
+unchanged. Later `THAWED_CLEAN` or `THAWED_DIRTY` status requires that actual parent bundle,
+clean seal verification, and exact agreement with every sidecar parent coordinate. Without
+the parent, sidecar agreement remains `NOT_ESTABLISHED`. The sidecar's unkeyed hash does not
+prove that the historical copy occurred. A supplied parent establishes only internal
+consistency unless an expected artifact/key identifier or separately verified external log
+also supplies continuity. See the normative
+[artifact-control mechanism](standard/ARTIFACT_CONTROL.md) and the architectural
+[realm/time-capsule model](docs/REALMS_AND_TIME_CAPSULES.md).
+
+### Capture a generic computation
+
+A manifest contains an executable command. `vstd run` does not sandbox it. Inspect the
+plan first and execute only trusted manifests inside an appropriate operating-system or
+container boundary.
```bash
vstd plan examples/generic_run/manifest.json --json
vstd run examples/generic_run/manifest.json --output /tmp/vstd-receipt
-vstd inspect /tmp/vstd-receipt
vstd validate /tmp/vstd-receipt
+vstd inspect /tmp/vstd-receipt
vstd reproduce /tmp/vstd-receipt --rerun
```
-`validate` checks stable receipt content. `reproduce --rerun` executes the recorded
-command again when permitted and compares the declared outputs. Neither operation
-widens the receipt into a claim about the unobserved world.
+Generic `validate` checks the strict profile shape and stable-payload digest. It does
+not rehash external artifacts, resolve evidence references, rerun the command, or verify
+the recorded declaration as a native domain claim. `reproduce --rerun` separately
+executes the recorded command and compares declared output paths, digests, and execution
+outcome. Matching outputs do not establish actor independence, environment equivalence,
+semantic equivalence, or truth outside that scope.
-## The grounded certificate
+### Use the Python application programming interface (API)
-`VSTD4-GDC-1` binds a decision to the claim and evidence it is supposed to describe:
+```python
+from pathlib import Path
-```text
-DecisionCertificate
-├── header verdict, tightest cost tier, counts, binding digest
-├── formula normalized finite clauses
-├── grounding variables → facts; clauses → named encoding rules
-├── decision model, proof, witness, or bounded UNKNOWN transcript
-└── hints untrusted, optional, and strippable
+from verifier.core.run import describe_run_plan, load_manifest
+
+manifest_path = Path("examples/generic_run/manifest.json")
+manifest = load_manifest(manifest_path)
+plan = describe_run_plan(manifest, manifest_path.parent)
+print(plan["command"], plan["executes_without_sandbox"])
```
-The checker rejects over-budget headers before proof work, rejects cost-tier inflation,
-checks grounding before the decision block, and preserves `UNKNOWN` when a declared
-bound is exhausted. `VSTD4-GDC-1` is a VSTD project format; reference-kernel acceptance
-is not external validation.
+The installed wheel contains byte-identical copies of every normative specification, so
+a verifier descriptor can retain its exact specification binding outside a source
+checkout. See the generated [CLI and API
+reference](https://timelordraps.github.io/verifier/reference.html).
-## Install and command names
+## Receipts, Graphs, and grounded certificates
-The distribution name is `verifier-standard`; the base install has no required
-third-party runtime dependencies.
+- [VSTD-1 receipts](standard/VSTD-1.md) carry claim coordinates, evidence,
+ checker results, trust boundaries, and reproducibility information.
+- [VSTD-Graph-1](standard/VSTD-Graph-1.md) records content-addressed artifacts,
+ many-to-many transformations, conflicts, and bounded downstream reachability. Its frozen
+ reader preserves separate historical artifact/transformation namespaces; new construction
+ plus evidence-bound establishment and assurance propagation require global cross-kind
+ disjointness.
+- [`VSTD4-GDC-1`](standard/VSTD-4.md) binds a decision certificate to a formula,
+ grounding, claim coordinate, verifier descriptor, roots, and resource bounds.
-```bash
-python -m pip install "verifier-standard==1.1.3"
-python -m pip install .
-python -m pip install ".[yaml]" # YAML manifests
-python -m pip install ".[jsonschema]" # schema validation
-python -m pip install ".[llguidance]" # optional constraint adapter
-python -m pip install ".[torch]" # optional tensor adapter
+The grounded-certificate checker rejects over-budget headers before proof work, rejects
+cost-tier inflation, validates grounding before the decision block, and preserves
+`UNKNOWN` when a bound is exhausted. Kernel acceptance establishes only the bounded
+certificate result; it is not VSTD-4 conformance, evidence authenticity, external
+validation, or proof of the unobserved world.
+
+## Interoperability
+
+VSTD composes beside native systems rather than replacing them:
+
+```text
+native object ──native verifier──> native result
+ └──── exact bytes + identity ──> loss-declared adapter
+ └──> VSTD claim boundary
```
-`vstd` is the canonical cross-platform command. `verifier` remains an alias, but an
-unqualified `verifier` command on Windows commonly resolves to Windows Driver Verifier.
-`verifiable` remains a permanent compatibility alias because published project receipts
-may bind it in falsification instructions.
+The experimental SCITT profile uses
+real Concise Binary Object Representation (CBOR) and COSE
+signatures and a local inclusion receipt. It demonstrates exact payload carriage and
+adjacent verification under test keys. SCITT registration proves neither payload
+correctness nor VSTD conformance. See the [crosswalk](docs/standards/VSTD_SCITT_CROSSWALK.md),
+[semantic boundary](docs/standards/SCITT_SEMANTIC_BOUNDARY.md), and
+[runnable example](examples/scitt_interop/).
-An unrelated PyPI distribution named `verifier` exports the same top-level Python
-import. Do not co-install it with `verifier-standard`: Python packaging does not prevent
-two distributions from overwriting one import package. Install this project by its full
-distribution name and use `vstd` as the command.
+The [ecosystem map](docs/ECOSYSTEM.md) separately covers adjacent provenance,
+software-supply-chain, signing, and transparency systems without implying endorsement or
+adoption.
+
+## Specifications and navigation
-## Verify a release
+Read authoritative material in this order:
-Release assets include an external manifest binding the exact public source ref,
-commit, archive digest, file set, and member bytes. The release builder produces a
-platform-independent canonical source ZIP, wheel, and source distribution from that
-source coordinate. CI independently builds the full set on Windows and Linux and fails
-unless every artifact is byte-identical.
-GitHub/Sigstore artifact attestations bind the ZIP, wheel, source distribution, and
-manifest to the release workflow:
+1. [Verification complex, terminology, and profile composition](standard/LADDER.md)
+2. [Object and Graph numbered-profile documents](standard/)
+3. [Serialized receipt identifiers](standard/WIRE_IDENTIFIERS.md)
+4. [Published schemas](receipts/schema/)
+5. [Implementation ownership](docs/ARCHITECTURE.md)
+6. [Claims and limits](docs/CLAIMS_AND_LIMITS.md)
+
+Additional entry points:
+
+| Goal | Document |
+|---|---|
+| Install and exercise the first-run path | [Quickstart](docs/QUICKSTART.md) |
+| Understand terminology and precedents | [Concepts and precedents](docs/CONCEPTS_AND_PRECEDENTS.md) |
+| Inspect abbreviated terms | [Acronyms](docs/ACRONYMS.md) |
+| Review experimental profiles | [Experiment index](experiments/INDEX.md) |
+| Understand human claim traversal | [Human operating guide](HUMANS.md) |
+| Inspect project direction and non-goals | [Roadmap](ROADMAP.md) |
+
+## Reproducibility and releases
+
+A release contains a canonical artifact set: ZIP archive format (ZIP), wheel, source
+distribution, and external manifest bound to the exact public Git commit and file
+members. The continuous integration (CI) workflow builds on Windows and Linux and rejects
+cross-platform byte differences. GitHub
+artifact attestations bind uploaded bytes to the workflow; they do not establish source
+correctness, tag identity, or adoption.
```bash
gh attestation verify PATH_TO_DOWNLOADED_ASSET --repo TimeLordRaps/verifier
```
-Release notes report the tag-signature status separately. An artifact attestation is
-not a tag signature. The signed `v1.1.2` GitHub release was not uploaded to PyPI because
-its Windows and Linux builds differed. PyPI publication now requires the cross-platform
-equality gate plus approval in the protected `pypi` environment. See
-[`RELEASING.md`](RELEASING.md) for the complete gate.
-
-## Project status
-
-VSTD is a founder-maintained **alpha project specification**. There is no demonstrated
-external adoption, independent implementation, interoperability deployment, or
-third-party security review. It is not an accredited, consensus, IETF, ISO, or W3C
-standard. A `VERIFIED` result is always relative to declared coordinates, evidence,
-mechanisms, bounds, and trust roots.
-
-Current public-review priorities are counterexamples to normative statements,
-ambiguous wire rules, independent parser results, interoperability failures, and
-receipts that pass when they should fail. Use the
-[issue forms](https://github.com/TimeLordRaps/verifier/issues/new/choose). Send sensitive
-findings through [`SECURITY.md`](SECURITY.md), not a public issue.
-
-VSTD may improve auditability, reproducibility, incident analysis, and challenge
-propagation over observable records. It cannot prove general AI safety, reveal hidden
-model internals, establish physical-world completeness, or compensate for missing
-instrumentation.
-
-## Project process
-
-- Specification order: [`LADDER`](standard/LADDER.md) → layer documents → schemas →
- independent checker → conformance tests.
-- Public technical direction: [`ROADMAP.md`](ROADMAP.md).
-- Contribution rules: [`CONTRIBUTING.md`](CONTRIBUTING.md).
-- Automated-contributor rules: [`AGENTS.md`](AGENTS.md).
-- Governance and release authority: [`GOVERNANCE.md`](GOVERNANCE.md).
-- Security and disclosure: [`SECURITY.md`](SECURITY.md).
-- Release construction and attestations: [`RELEASING.md`](RELEASING.md).
-
-Apache License 2.0. See [`LICENSE`](LICENSE) and [`NOTICE`](NOTICE). VSTD is not
+Use [RELEASING.md](RELEASING.md) to verify the manifest, tag, artifact attestations,
+package name, and historical compatibility. The current checkout is an unreleased
+1.2.0 candidate; use the [latest release page](https://github.com/TimeLordRaps/verifier/releases/latest)
+for published citation and artifact coordinates.
+
+## Claims, security, and contribution
+
+Review [claims and limits](docs/CLAIMS_AND_LIMITS.md) before publishing a VSTD result.
+The reference implementation may improve auditability, reproducibility, incident
+analysis, and challenge routing over observable records. It cannot prove general AI
+safety, reveal hidden model state, establish physical-world completeness, or compensate
+for missing instrumentation.
+
+`vstd run` executes manifest commands without sandboxing. See the
+[security policy](SECURITY.md) and use GitHub private vulnerability reporting for
+sensitive findings.
+
+Contributors should start with [CONTRIBUTING.md](CONTRIBUTING.md), which identifies
+normative, implementation, schema, adapter, test, compatibility, and release pathways.
+Use the issue forms for a
+[specification ambiguity](https://github.com/TimeLordRaps/verifier/issues/new?template=specification-ambiguity.yml),
+[counterexample](https://github.com/TimeLordRaps/verifier/issues/new?template=counterexample.yml),
+or [implementation/interoperability report](https://github.com/TimeLordRaps/verifier/issues/new?template=implementation-report.yml).
+
+Project authority and centralization are documented in [GOVERNANCE.md](GOVERNANCE.md).
+Automated-contributor rules live in [AGENTS.md](AGENTS.md); the human operating model in
+[HUMANS.md](HUMANS.md); and live repository contradictions only in [TIME.md](TIME.md).
+
+## Citation and license
+
+Cite a published release from its versioned GitHub release metadata or
+`CITATION.cff` at that tagged coordinate. Do not cite unreleased candidate metadata as
+a published release.
+
+Licensed under the [Apache License 2.0](LICENSE); see [NOTICE](NOTICE). VSTD is not
affiliated with or endorsed by the Apache Software Foundation.
diff --git a/RELEASING.md b/RELEASING.md
index 89df313..0fa1384 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -1,12 +1,31 @@
# Release procedure
+> **Acronyms:** application programming interface (API); carriage return and line feed (CRLF); digital object identifier (DOI);
+> hash-based message authentication code (HMAC); line feed (LF); Secure Hash Algorithm 256-bit (SHA-256);
+> Software Bill of Materials (SBOM); Coordinated Universal Time (UTC); ZIP archive format (ZIP).
+
Public releases are built only from a commit already present in the public repository.
The release manifest is published beside the source ZIP rather than tracked inside the
source tree. This avoids a self-referential commit field and lets the manifest bind an
exact, publicly resolvable commit.
+Development branches may record precise contradictions with [`TIME.md`](TIME.md) set to
+`Status: OPEN`; normal pull-request checks do not prohibit that state. Publication is
+different: the tag-triggered workflow runs `python scripts/check_time_status.py` against
+the exact tagged checkout and fails unless it contains exactly one `Status: CLEAR` line.
+There is no subjective override.
+
+The source version may be prepared as 1.2.0 while the release does not exist. During that
+period, `CHANGELOG.md` says `UNRELEASED`, `CITATION.cff` identifies a release candidate and
+has no `date-released`, and install instructions distinguish a source checkout from the
+latest published package. Before tagging, land an explicit release-finalization change that
+uses the actual publication date consistently in the changelog and citation metadata; do
+not fabricate or backdate it. The tag workflow enforces this with
+`python scripts/check_release_metadata.py --version ` and also refuses
+release-candidate Zenodo metadata.
+
1. Merge the versioned release change through the public pull-request workflow. Require
- every protected conformance check on the exact candidate commit.
+ the protected repository-check aggregate to pass on the exact candidate commit.
2. From a clean checkout of that commit, run:
```bash
@@ -17,7 +36,7 @@ exact, publicly resolvable commit.
3. Build a pre-tag candidate from the full commit SHA, not a working directory:
```bash
- VERSION=1.1.3
+ VERSION=1.2.0
python scripts/release_artifacts.py build \
--ref FULL_PUBLIC_COMMIT_SHA --release "$VERSION" --output-dir dist/candidate
```
@@ -29,11 +48,14 @@ exact, publicly resolvable commit.
Generated packaging text is normalized to LF; wheel `RECORD` is rebuilt after
normalization; ZIP metadata, tar metadata, gzip metadata, ownership, modes, and member
order are canonical. The build fails unless each pair is byte-identical and both
- distributions declare `verifier-standard`, version `1.1.3`, import package `verifier`,
- and the frozen three console scripts.
+ distributions declare `verifier-standard`, version `1.2.0`, import package `verifier`,
+ and the frozen three console scripts. It also emits a deterministic CycloneDX 1.6
+ SBOM whose components bind the source ZIP, wheel, and source distribution by SHA-256
+ and byte size. The external manifest binds the SBOM digest; the SBOM does not list
+ itself, avoiding self-reference.
- The protected conformance gate separately builds this complete artifact set on
- Windows and Linux and compares every byte. Do not prepare a tag unless that
+ The protected repository-check aggregate separately builds this complete artifact set
+ on Windows and Linux and compares every byte. Do not prepare a tag unless that
cross-platform comparison passed on the exact candidate commit.
4. Run `twine check` on the candidate wheel and source distribution. Install the
@@ -41,19 +63,41 @@ exact, publicly resolvable commit.
`vstd hardware list --json`, and the deterministic virtual probe/verification
lifecycle. Test each optional dependency profile independently; never place a test
HMAC key in a committed fixture.
-5. Require a zero-match boundary scan of the candidate source archive, wheel, and source
- distribution for
+5. Require a zero-match boundary scan of the candidate source archive, wheel, source
+ distribution, external manifest, and SBOM for
private project names, proprietary model identifiers, local or home-directory paths,
credentials, and personal email addresses.
-6. Create the release tag locally at the exact tested commit. Prefer a cryptographically
+6. Confirm `python scripts/check_time_status.py` passes, release-candidate metadata has
+ been finalized with the actual intended publication date, and then create the release
+ tag locally at the exact tested commit. Before creating the tag, require GitHub release
+ immutability to be enabled:
+
+ ```bash
+ gh api -H "X-GitHub-Api-Version: 2026-03-10" \
+ repos/TimeLordRaps/verifier/immutable-releases
+ ```
+
+ The response MUST contain `"enabled": true`. GitHub release immutability is a
+ repository setting that applies only to future releases; GitHub documents both the
+ [repository setting](https://docs.github.com/en/code-security/how-tos/secure-your-supply-chain/establish-provenance-and-integrity/prevent-release-changes)
+ and the
+ [versioned API](https://docs.github.com/en/rest/repos/repos?apiVersion=2026-03-10#check-if-immutable-releases-are-enabled-for-a-repository).
+ The tag workflow repeats this check and stops before publication when the setting is
+ disabled. Immutability locks the published tag and attached assets and generates a
+ GitHub release attestation; it does not correct false metadata. Corrections,
+ revocations, and superseding releases remain additive. Enable the setting only after
+ the draft-first workflow is present on the protected release commit.
+
+ Prefer a cryptographically
signed annotated tag when the maintainer's signing key is registered and available.
Rebuild using the tag coordinate. The source ZIP, wheel, and source distribution MUST
- be byte-identical to the commit-coordinate candidate. The external manifest MUST
- differ only where its `source.ref` changes from the full commit SHA to the tag ref,
+ be byte-identical to the commit-coordinate candidate. The SBOM also remains
+ byte-identical because it binds the resolved commit rather than the ref spelling. The
+ external manifest MUST differ only where its `source.ref` changes from the full commit SHA to the tag ref,
plus the manifest's own resulting digest:
```bash
- VERSION=1.1.3
+ VERSION=1.2.0
git tag -s "v$VERSION" FULL_PUBLIC_COMMIT_SHA
python scripts/release_artifacts.py build \
--ref "refs/tags/v$VERSION" --release "$VERSION" --output-dir dist/tagged
@@ -62,12 +106,12 @@ exact, publicly resolvable commit.
If tag signing is unavailable, an unsigned annotated tag is permitted only through
`.github/workflows/release.yml`. That workflow records the GitHub tag-object
verification result and reason in the release notes and MUST create GitHub/Sigstore
- artifact attestations for the source ZIP, wheel, source distribution, and external
- manifest. An artifact attestation is not described as a tag signature.
+ artifact attestations for the source ZIP, wheel, source distribution, SBOM, and
+ external manifest. An artifact attestation is not described as a tag signature.
7. Run the verifier independently before upload:
```bash
- VERSION=1.1.3
+ VERSION=1.2.0
python scripts/release_artifacts.py verify \
"dist/tagged/verifier-standard-$VERSION.manifest.json"
```
@@ -76,10 +120,13 @@ exact, publicly resolvable commit.
file set and every member byte MUST match that commit. CRLF/LF equivalence is not
accepted as byte identity.
8. Push the tag only after all preceding checks pass. The tag-triggered release workflow
- rechecks protected-main ancestry, package version, the successful `conformance-gate`,
- the full test suite, deterministic build, installed wheel, and artifact manifest.
- It then attests and publishes exactly the tested source ZIP, wheel, source
- distribution, and external release manifest to the GitHub release. A second job can
+ rechecks protected-main ancestry, package version, the successful protected
+ repository-check aggregate (the `conformance-gate` status context), the full test
+ suite, immutable-release setting, deterministic build, installed wheel, and artifact
+ manifest.
+ It then attests the tested source ZIP, wheel, source distribution, SBOM, and external
+ release manifest. The workflow creates a draft, attaches the complete set, and only
+ then publishes it. A second job can
access only the wheel and source distribution, requires approval in the protected
`pypi` environment, and publishes them through the configured PyPI Trusted Publisher.
Existing tags and release assets remain untouched; corrections are additive.
@@ -89,10 +136,11 @@ exact, publicly resolvable commit.
gh attestation verify PATH_TO_ASSET --repo TimeLordRaps/verifier
```
- An attestation complements but does not replace the release manifest, and it does not
- turn an unsigned tag into a signed tag.
+ An attestation and SBOM complement but do not replace the release manifest, and neither
+ turns an unsigned tag into a signed tag.
+
10. Let Zenodo archive the GitHub release, then record the issued DOI additively.
-11. Confirm that `https://pypi.org/project/verifier-standard/1.1.3/` lists the same wheel
+11. Confirm that `https://pypi.org/project/verifier-standard/1.2.0/` lists the same wheel
and source-distribution SHA-256 values as the GitHub release and external manifest.
PyPI ownership establishes control of the distribution coordinate only; it does not
establish adoption, consensus, certification, or exclusive control of the Python
diff --git a/ROADMAP.md b/ROADMAP.md
index 1f9300c..ea18d63 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,9 +1,21 @@
-# VSTD public technical roadmap
+# Verifier Standard (VSTD) public technical roadmap
+
+> **Acronyms:** Concise Binary Object Representation (CBOR); CBOR Object Signing and Encryption (COSE);
+> grounded decision certificate (GDC); Internet Engineering Task Force (IETF);
+> reduced instruction set computer (RISC); Boolean satisfiability problem (SAT);
+> Supply Chain Integrity, Transparency, and Trust (SCITT);
+> zero-identity/zero-knowledge (ZIZK).
+
+TRUST is mechanism-earned forward artifact support; ROT is typed, time-indexed
+degradation of current admissibility; and RUST is inverse-TRUST diagnostic traversal
+toward recorded ancestors. They are formal semantic names, not acronyms or actor ratings.
**Status:** direction, not a promise of delivery or adoption
**Scope:** the public specification, reference implementation, and interoperability
surface only
+**Reader context:** [`Concept guide and intellectual precedents`](docs/CONCEPTS_AND_PRECEDENTS.md)
+
## The near-term problem
“Speed superintelligence” is used here as an operational condition, not as a model
@@ -18,11 +30,59 @@ record of:
- the time, memory, disclosure, and availability bounds;
- the conditions that produce `FAIL`, `UNKNOWN`, challenge, or degradation.
-VSTD's intended role is to make that review object cheap to transfer, independently
-checkable within stated bounds, and capable of being overturned. It is evidence
+VSTD's intended role is to make that review object cheap to transfer, checkable outside
+its producer within stated bounds, and capable of being overturned. It is evidence
infrastructure around fast systems—not proof that a system is aligned, safe,
conscious, superintelligent, or fully observed.
+## The next question: what should we check first?
+
+Verification is never free. A project can usually identify more claims, artifacts, and
+dependencies worth checking than its available time, compute, evidence access, and human
+attention can cover. Hiding that constraint does not remove it; it only makes the choice
+of what went unchecked harder to inspect.
+
+The intended next direction is straightforward for a newcomer:
+
+1. record the available verification budget;
+2. choose which check to run next under a declared policy;
+3. record why that check was selected and what was deferred;
+4. preserve the native verifier's actual result and VSTD claim boundary; and
+5. observe whether the policy makes artifacts easier to check—or merely easier to game.
+
+This is **bounded verification allocation**. A priority is a scheduling result, not a
+truth result. “Check this first” does not mean “this is false,” “this is important in
+every context,” or “everything else is safe.” Budget exhaustion leaves the deferred
+surface explicit and unresolved.
+
+The longer-term objective is a portable, verifier-neutral way to:
+
+- allocate bounded verification work across different proof engines, domain verifiers,
+ tests, reproduction procedures, and challenge routes;
+- bind the policy, evidence, expected cost, downstream blast radius, and recorded reason
+ for each allocation decision;
+- measure **verification yield** without reducing it to solver time alone;
+- make certificate-friendly, modular, replayable, and cheaply refutable artifacts easier
+ to select and deploy; and
+- expose feedback loops in which artifacts or adaptive systems change their behavior
+ because they anticipate what will be checked.
+
+The allocation policy is itself a versioned software artifact. It can therefore be
+tested, challenged, meta-verified, and represented in VSTD-Graph alongside the artifacts
+and verifier actions it influences. A stable feedback loop is not automatically a true
+one: randomized challenges, counterevidence searches, dependency-aware updates, and
+explicit `UNKNOWN` outcomes remain necessary to resist self-confirming verification.
+
+This direction composes established work on
+[bounded optimality](https://www.cs.cmu.edu/afs/cs/project/jair/pub/volume2/russell95a.pdf),
+[active testing](https://proceedings.mlr.press/v139/kossen21a.html),
+[cost-sensitive testing trees](https://proceedings.mlr.press/v32/cicalese14.html),
+[proof-carrying code](https://people.eecs.berkeley.edu/~necula/papers.html), and
+[certifying algorithms](https://www.sciencedirect.com/science/article/pii/S1574013710000560).
+The roadmap does not claim those foundations as VSTD inventions. The research question
+is whether VSTD can provide interoperable claim boundaries and portable result semantics
+for their combined use across heterogeneous verification substrates.
+
## Vision board
```text
@@ -30,7 +90,7 @@ TODAY NEXT TARGET CONDITION
fast opaque result result + bounded receipt claims travel with challenges
green check only → PASS / FAIL / UNKNOWN → wrong claims degrade visibly
flat artifact list provenance hypergraph poisoned ancestry has blast radius
-producer's own word independent checker kit multiple implementations can disagree
+producer's own word separate checker kit multiple implementations can disagree
manual after-the-fact audit policy-bound event capture review scales with evidence, not rhetoric
```
@@ -42,8 +102,118 @@ claim → evidence → bounded check → publish → challenge → adjudicate
└────────────────────── new evidence / corrected claim ──────────────────┘
```
-No arrow in that loop upgrades one VSTD layer with another layer's evidence. Each
-layer still requires its own evidence; the loop only carries results and challenges.
+No arrow in that loop upgrades one VSTD closure coordinate with another coordinate's
+evidence. Each coordinate still requires its own evidence; the loop only carries results
+and challenges.
+
+## Implemented 1.2 artifact-control foundation
+
+[`standard/ARTIFACT_CONTROL.md`](standard/ARTIFACT_CONTROL.md) defines a mechanism beneath
+the numbered profiles: exact regular-file byte and path preservation, dual-algorithm
+artifact-derived identity, an observable read-only payload-tree guard, finite readable
+self-closing seals, external artifact/key anchor checks, and copy-on-write thaw descendants.
+The mechanism is implemented through `vstd artifact` and the supported Python interface.
+
+This is structural closure, not encryption, archival custody, semantic correctness,
+trusted time, actor trust, or a numbered VSTD profile result. The
+[realm/time-capsule architecture](docs/REALMS_AND_TIME_CAPSULES.md) permits continuous,
+discrete, causal, problem-space, branching, cyclic, and atemporal structures, but VSTD
+1.2 does not yet define a realm receipt, continuity-law verifier, cross-realm mapping
+verifier, or language-model transition verifier.
+
+## Classical interoperability vocabulary target
+
+The near-term interoperability scope is classical computation. **Deterministic** means
+that every semantically relevant source of choice is absent or bound as an explicit
+input. A seed, repeated output, or deterministic-mode flag alone does not establish that
+condition.
+
+These are roadmap-level interoperability **meta-classes**, not new receipt fields or
+frozen identifiers:
+
+| Term | Minimum meaning |
+|---|---|
+| Semantic frame | Exact language, logic, theory, type system, operation set, machine and numerical semantics, versions, and undefined or implementation-defined behavior. |
+| Problem frame | Bound instance, declarations, inputs, assumptions, options, objectives, constraints, and initial or session state. |
+| Proposition frame | Exact relation being checked, its quantifiers, subject, scope, bounds, horizon, and required counterexample or witness condition. |
+| Mechanism contract | Supported frames and claim kinds, checker and trust roots, soundness basis, completeness or incompleteness boundary, resource limits, and known exclusions. |
+| Native outcome | The tool's exact status and native meaning; it remains distinct from the VSTD assessment earned by checking it. |
+| Evidence payload | Typed model, witness, proof, certificate, core, trace, counterexample, diagnostic, reproducer, coverage record, or primal/dual bound. |
+| Transformation obligation | Source and target frames, mapping, claimed relation—such as equivalence, refinement, implication, or equisatisfiability—information loss, and the mechanism checking that relation. |
+| Exploration scope | Exhaustive, sampled, bounded, abstracted, under-approximated, or over-approximated search; explored states, paths, regions, and stopping reason. |
+| Choice schedule | Random-number-generator algorithm and state, sampler, tie-breaking, concurrency schedule, external responses, and every other choice that affects replay. |
+| Numerical contract | Data types, precision, rounding, accumulation order, tolerances, overflow, exceptional values, quantization, and comparison rule. |
+| Operational trace | Bound states, transitions, events, causal or topological order, external effects, checkpoints, and omitted observation surface. |
+| Composition obligation | Typed dependency relation, imported assumptions or axioms, discharged guarantees, conflicts, and the rule preventing repetition or topology from increasing assurance. |
+
+### Meta-class and native-object boundary
+
+A meta-class names a cross-domain semantic role. A **meta-object** is one bounded VSTD
+instance of that role. A **native object kind** is defined by the source verifier, and a
+**native object** is an exact instance governed by that verifier's semantics. An adapter
+maps the native object into one or more meta-objects while preserving its identity,
+native result, assumptions, bounds, and declared information loss.
+
+A classical verification episode should be expressible through these meta-classes, but
+an individual artifact need not instantiate all twelve, and one artifact may occupy
+several roles. Missing, inapplicable, and unobserved roles remain distinct. Meta-class
+membership is organization, not verification; it earns no assurance without the named
+mechanism that checks the object and its mapping.
+
+For example, Lean retains its own objects and semantics:
+
+| Interoperability meta-class | Lean native object kind | Example meta-object binding |
+|---|---|---|
+| Semantic frame | Type theory and declaration environment | Exact Lean version, imported environment, options, and module identities. |
+| Proposition frame | Theorem declaration and its type | Exact proposition, universe parameters, and declaration coordinate. |
+| Evidence payload | Elaborated proof term | Exact term checked for the bound proposition. |
+| Mechanism contract | Kernel and its accepted core language | Kernel implementation/version, configuration, trust roots, and exclusions. |
+| Transformation obligation | Elaboration from syntax or tactics to a core proof term | Bound source, produced term, mapping, dependencies, and information loss. |
+| Composition obligation | Imported definitions, theorems, and axioms | Exact dependency and axiom set retained as prerequisites rather than inherited truth. |
+| Native outcome | Kernel acceptance or rejection | Exact native result and diagnostics before any VSTD assessment. |
+
+The first machine-learning specialization is a classically executed autoregressive
+transition: bound model and weight bytes, tokenizer, operation graph, prefix, cache/state,
+numerical contract, logits transformations, choice schedule, and external tool inputs map
+to a selected token and next state. Passing establishes only conformance of that declared
+transition. It does not establish that the emitted text is true; that requires a separate
+proposition-specific verifier.
+
+The vocabulary is grounded in distinctions already exposed by primary interfaces such as
+[Lean proof terms and kernel checking](https://lean-lang.org/doc/reference/latest/),
+[the satisfiability modulo theories library language](https://smt-lib.org/language.shtml),
+[TLA+ behaviors and model checking](https://lamport.azurewebsites.net/tla/high-level-view.html),
+[the Static Analysis Results Interchange Format](https://docs.oasis-open.org/sarif/sarif/v2.1.0/os/sarif-v2.1.0-os.html),
+[in-toto attestations](https://github.com/in-toto/attestation/tree/main/spec/v1),
+[PyTorch reproducibility limits](https://docs.pytorch.org/docs/stable/notes/randomness.html),
+[StableHLO program semantics](https://openxla.org/stablehlo/spec), and
+[Transformers generation controls](https://huggingface.co/docs/transformers/main_classes/text_generation).
+
+A public “99%+ coverage” claim is prohibited until a versioned taxonomy names the
+included classical fields and subfields, representative native specimens exist, and
+round-trip plus adversarial loss tests show which mandatory distinctions survive each
+adapter. Coverage means expressibility over that declared denominator; it is not market
+share, adoption, correctness, or evidence that every tool has been tested.
+
+Quantum, thermodynamic, deoxyribonucleic acid (DNA), chemical, and chemputer verification
+are outside the first denominator. A later operational-landscape extension must map this
+shared interoperability vocabulary without redefining VSTD outcomes. Whether an extension
+belongs in an optional module or a separately governed repository remains a future coupling
+and ownership decision.
+
+## Current experimental development tracks
+
+This dated register records substantive work as of **2026-08-29**. A committed experiment,
+passing test, or generated index is not normative, released, reproduced by a distinct actor,
+or evidence of adoption merely because it exists. Profile manifests and the generated
+[`experiments/INDEX.md`](experiments/INDEX.md) are the portable experiment register when
+intentional experiment artifacts are present.
+
+| Track | Public artifact | Current boundary | Next gate |
+|---|---|---|---|
+| SCITT interoperability | [`docs/standards/VSTD_SCITT_CROSSWALK.md`](docs/standards/VSTD_SCITT_CROSSWALK.md) | Experimental adapter, rerunnable real-COSE specimen with ephemeral keys, and adversarial tests; no IETF review or external interoperability result. | Independent implementation and interoperability result. |
+| Artifact-first mechanism completion | [`standard/schemas/vstd-graph-assurance-1.schema.json`](standard/schemas/vstd-graph-assurance-1.schema.json) | Event serialization, evidence-bound TRUST/ROT/RUST dispatch, challenge projection, conflict resolution, structural concentration, explicit localization, and bounded diagnostic attribution are implemented and adversarially tested. The complete domain-independent transfer algebra, complete trichotomy derivation, cross-implementation replay, and specific optional proof backends remain open. | Supply and falsify real domain mechanisms without creating actor-tied trust or topology-derived assurance. |
+| Workflow and allocation | [`docs/profiles/experimental-workflow.md`](docs/profiles/experimental-workflow.md) | Strict validator, verdict-neutral GitHub adapter, generated index, and allocation records; no optimality claim or independent consumer. | A second observable adapter and independent consumer. |
## Milestone 1 — make refutation the front door
@@ -57,11 +227,11 @@ layer still requires its own evidence; the loop only carries results and challen
- Public counterexample, ambiguity, implementation, and private-security routes are
distinct and usable.
-## Milestone 2 — independent checker kit
+## Milestone 2 — separate checker kit
**Build**
-- a language-neutral `VSTD4-GDC-1` byte-level test vector bundle;
+- a language-neutral `VSTD4-GDC-1` exact-byte test-vector bundle;
- positive, negative, malformed, over-budget, and semantic-misbinding corpora;
- a checker implementer's guide that does not require importing this Python package;
- differential test instructions and a machine-readable conformance report.
@@ -73,11 +243,24 @@ layer still requires its own evidence; the loop only carries results and challen
- disagreements are preserved as public interoperability failures until resolved;
- no “independent” label is used merely because two entry points call shared logic.
-## Milestone 3 — agent-work profile
+## Milestone 3 — experimental-workflow and agent-work profiles
-**Build**
+**Implemented in experimental profile 0.1**
+
+- a platform-independent, non-normative experimental-workflow profile for questions,
+ hypotheses, preregistration, interventions, observations, native-verifier results,
+ budgets, amendments, challenges, and publication state;
+- a GitHub adapter that maps issues, commits, workflow runs, artifacts, pull requests,
+ and merges without treating repository state as a verification verdict;
+- bounded verification-allocation records that preserve the policy, reason, budget,
+ deferred surface, and native outcome without assigning truth by priority;
+- deterministic canonicalization, repository-artifact binding, a generated experiment
+ index, adversarial tests, a verdict-neutral checked-in specimen, and an
+ artifact-first-mechanism dogfood manifest.
-- a non-normative profile for observable user, agent, and tool messages;
+**Still build**
+
+- an agent-harness specialization for observable user, agent, and tool messages;
- bindings for repository state, patches, file reads, commands, outputs, tests,
failures, retries, and final claims;
- explicit serialization gaps for hidden prompts, inaccessible reasoning, and
@@ -86,19 +269,34 @@ layer still requires its own evidence; the loop only carries results and challen
**Exit evidence**
-- the same trace can be checked by two independent consumers;
+- the SCITT, artifact-first-mechanism, and SAT tracks can be indexed through the same experimental-workflow
+ vocabulary without changing their native verifiers or erasing their blockers;
+- a GitHub merge remains an integration event rather than becoming a VSTD pass;
+- the same trace can be checked by two separately maintained consumers;
- deleting or substituting a bound tool output changes the receipt digest or fails a
declared rule;
- missing observability yields a named gap or `UNKNOWN`, never reconstructed fiction.
## Milestone 4 — challenge and degradation network
-**Build**
+**Implemented reference mechanism**
- append-only challenge envelopes and adjudication records;
-- transitive blast-radius computation over object and transformation nodes;
-- freshness and availability policies for evidence that disappears;
-- portable bundles for disconnected verification.
+- challenge-ledger projection into an additive current Graph view;
+- transitive, deduplicated descendant impact discovery;
+- strictly degrading ROT status propositions without historical mutation;
+- current TRUST invalidation when a required ancestor becomes inadmissible;
+- portable `VSTD-GRAPH-ASSURANCE-1` logs with embedded evidence and exact offline
+ mechanism replay; and
+- additive conflict resolution, RUST reachability/concentration, explicit localization,
+ and bounded artifact-relative diagnostic attribution.
+
+**Still supply per deployment**
+
+- freshness and availability mechanisms for the deployment's clocks, dependencies, and
+ retention boundary;
+- domain mechanisms that decide whether an affected descendant actually changes status;
+- external independent replay evidence and interoperable implementations.
**Exit evidence**
@@ -109,7 +307,9 @@ layer still requires its own evidence; the loop only carries results and challen
## Milestone 5 — corroboration without pseudo-independence
-VSTD-5 remains draft until operating experience and actual outside participants exist.
+The VSTD-5 meta-verification path and replayable receipt are implemented. A real
+independent-witness claim remains unavailable until outside participants supply evidence
+that passes every required separation and corroboration mechanism.
**Exit evidence**
@@ -142,4 +342,4 @@ This roadmap does not promise to:
- prove all physical execution has been recorded;
- replace sandboxing, signatures, identity systems, transparency logs, or domain
truth tests;
-- treat one layer's evidence as proof of another layer.
+- treat one closure coordinate's evidence as proof of another coordinate.
diff --git a/SECURITY.md b/SECURITY.md
index cf97653..01fbdad 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -1,5 +1,9 @@
# Security policy
+> **Acronyms:** application programming interface (API); hash-based message authentication code (HMAC);
+> Secure Hash Algorithm 256-bit (SHA-256); Secure Hash Algorithm 3 256-bit (SHA3-256);
+> Verifier Standard (VSTD).
+
## Supported release
Only the latest tagged public release is supported. Historical receipts and standards
@@ -13,10 +17,9 @@ GitHub repository to open a private vulnerability report with the maintainer:
`https://github.com/TimeLordRaps/verifier/security/advisories/new`
-GitHub private vulnerability reporting was enabled and verified through the repository
-API on 2026-08-21. If GitHub does not show the private-reporting form, do not disclose
-sensitive details in a public issue; report only the non-sensitive fact that the private
-route is unavailable.
+GitHub private vulnerability reporting is the intended sensitive-reporting route. If
+GitHub does not show the private-reporting form, do not disclose sensitive details in a
+public issue; report only the non-sensitive fact that the private route is unavailable.
## Scope
@@ -37,3 +40,18 @@ failure, or accidental credential/raw-evidence disclosure. The HMAC emulator and
anchor keys are explicitly test-only and are not production cryptography. Vendor or
cloud product vulnerabilities should also be reported to the affected vendor through
its own process; VSTD does not authorize testing third-party infrastructure.
+
+## Artifact-control boundary
+
+`vstd artifact freeze` rejects symbolic links and special filesystem objects, preserves
+regular-file bytes, and makes the payload tree read-only. That guard is observable state,
+not a sandbox or a defense against privileged writes. Keep an independently controlled
+copy when durable preservation matters.
+
+Version 1 seals are readable Ed25519 signatures, not encryption. Protect private keys
+outside artifact bundles and never commit a production private key. Because each bundle
+carries its own public key, self-verification alone cannot detect substitution of the
+entire bundle and key; relying parties should bind an expected artifact identifier,
+expected key identifier, or independently verified external manifest/log coordinate.
+SHA-256 and SHA3-256 are both recomputed over preserved bytes so later algorithm concerns
+can be addressed by additive re-anchoring rather than rewriting historical artifacts.
diff --git a/TIME.md b/TIME.md
new file mode 100644
index 0000000..297804c
--- /dev/null
+++ b/TIME.md
@@ -0,0 +1,23 @@
+# TIME
+
+Status: CLEAR
+
+TIME is the live repository-contradiction annunciator. Its status is repository process
+metadata, not Verifier Standard (VSTD) receipt vocabulary. A live entry belongs here only
+when current authoritative surfaces make incompatible claims about current semantics or
+implementation. Runtime `CONFLICTED`, an honest `UNKNOWN`, roadmaps, ordinary work items,
+limitations, and speculative research do not belong here.
+
+For agent response rules, see [`AGENTS.md`](AGENTS.md). For human interpretation and
+escalation, see [`HUMANS.md`](HUMANS.md).
+
+## Live contradictions
+
+None.
+
+When a contradiction is open, change the status to `Status: OPEN` and record the exact
+coordinates, both incompatible claims, evidence for each side, and affected behavior. An
+evidence-backed repair removes the resolved live entry and returns this file to
+`Status: CLEAR`; Git history preserves the prior state. Development branches may remain
+open. The tag-triggered publication workflow checks the exact tagged checkout and fails
+unless this file contains exactly one `Status: CLEAR` line.
diff --git a/docs/ACRONYMS.md b/docs/ACRONYMS.md
new file mode 100644
index 0000000..1e355ae
--- /dev/null
+++ b/docs/ACRONYMS.md
@@ -0,0 +1,129 @@
+# Acronyms and abbreviated terms
+
+This is the canonical expansion key for the Verifier Standard (VSTD) repository. Every
+independently readable document and source file must still expand each term at its first
+reader-facing use; this page is a reference, not a substitute for local clarity. Frozen wire
+identifiers, code symbols, filenames, and third-party names remain byte-for-byte unchanged.
+
+`TRUST`, `ROT`, and `RUST` are deliberately absent from the expansion table because they
+are formal semantic names, not acronyms. They mean mechanism-earned forward artifact
+support, typed time-indexed degradation of current admissibility, and inverse-TRUST
+diagnostic traversal, respectively. `RUST` is not the Rust programming language. Their
+normative definitions are in [`standard/LADDER.md`](../standard/LADDER.md).
+
+| Term | Expansion used in this repository | Scope note |
+|---|---|---|
+| `AI` | artificial intelligence | General field. |
+| `AMD` | Advanced Micro Devices | Vendor name. |
+| `API` | application programming interface | Software interface. |
+| `ASCII` | American Standard Code for Information Interchange | Text encoding. |
+| `ASIC` | application-specific integrated circuit | Purpose-built processor class. |
+| `AST` | abstract syntax tree | Parsed program structure. |
+| `AWS` | Amazon Web Services | Cloud provider. |
+| `CBOR` | Concise Binary Object Representation | Binary data format. |
+| `CCF` | Confidential Consortium Framework | Ledger framework used by one SCITT profile. |
+| `CD` | continuous delivery or deployment | The delivery/deployment half of CI/CD workflow shorthand. |
+| `CI` | continuous integration | Automated repository checks. |
+| `CLI` | command-line interface | Terminal-facing program surface. |
+| `CNF` | conjunctive normal form | Boolean-formula representation. |
+| `COSE` | CBOR Object Signing and Encryption | Signed-message and receipt envelope family. |
+| `CPU` | central processing unit | Processor class. |
+| `CRLF` | carriage return and line feed | Two-character line ending. |
+| `CT` | Certificate Transparency | Public certificate-log system. |
+| `CUDA` | Compute Unified Device Architecture | NVIDIA parallel-computing platform. |
+| `CVE` | Common Vulnerabilities and Exposures | Public vulnerability identifier system. |
+| `CWT` | CBOR Web Token | Claim set used in COSE messages. |
+| `DAG` | directed acyclic graph | Graph with directed edges and no directed cycle. |
+| `DICE` | Device Identifier Composition Engine | Device-attestation architecture. |
+| `DMTF` | DMTF standards organization | Current organizational name; do not invent a modern expansion. |
+| `DOE` | design of experiments | Experimental-design method. |
+| `DOI` | digital object identifier | Publication identifier. |
+| `DPE` | DICE Protection Environment | DICE execution and key-derivation component. |
+| `DPLL` | Davis-Putnam-Logemann-Loveland | Boolean satisfiability algorithm. |
+| `DRAT` | deletion resolution asymmetric tautology | Clausal refutation format. |
+| `EAT` | Entity Attestation Token | Attestation claim format. |
+| `ECN` | Engineering Change Notice | Standards-change document. |
+| `ELF` | Executable and Linkable Format | Binary executable format. |
+| `EU` | European Union | Political and regulatory body. |
+| `FLOP` | floating-point operation | Compute-work unit. |
+| `FRAT` | flexible SAT proof format | Solver-to-elaborator proof format; use the proper format name rather than inventing a letter-by-letter expansion. |
+| `FSM` | finite-state machine | State-transition model. |
+| `GB` | gigabyte | Storage or memory capacity unit. |
+| `GDC` | grounded decision certificate | VSTD-4 certificate family. |
+| `GPG` | GNU Privacy Guard | Signature tool. |
+| `GPU` | graphics processing unit | Accelerator class. |
+| `GRAT` | GRAT proof format | Proper name of a hinted SAT proof format; no documented letter-by-letter expansion is asserted here. |
+| `HMAC` | hash-based message authentication code | Keyed authentication construction. |
+| `HTML` | Hypertext Markup Language | Web-page format. |
+| `HTTP` | Hypertext Transfer Protocol | Web transfer protocol. |
+| `HTTPS` | Hypertext Transfer Protocol Secure | HTTP protected by transport security. |
+| `ID` | identifier | Stable name or coordinate. |
+| `IDE` | integrated development environment | Programming application. |
+| `IETF` | Internet Engineering Task Force | Internet standards organization. |
+| `IR` | intermediate representation | Program or proof representation. |
+| `ISO` | International Organization for Standardization | Standards organization. |
+| `JSON` | JavaScript Object Notation | Structured text format. |
+| `JSONL` | JSON Lines | One-JSON-value-per-line format. |
+| `LF` | line feed | Single-character line ending. |
+| `LRAT` | linear resolution asymmetric tautology | Hint-carrying clausal refutation format. |
+| `MIG` | multi-instance GPU | NVIDIA accelerator-partitioning feature. |
+| `ML` | machine learning | General field. |
+| `NIST` | National Institute of Standards and Technology | United States standards agency. |
+| `NP` | nondeterministic polynomial time | Computational-complexity class. |
+| `NPU` | neural processing unit | Machine-learning accelerator class. |
+| `NVML` | NVIDIA Management Library | NVIDIA device-management interface. |
+| `OS` | operating system | Host software environment. |
+| `PCC` | proof-carrying code | Producer-supplied proof checked by a consumer. |
+| `PCI` | Peripheral Component Interconnect | Hardware interconnect family. |
+| `PCI-SIG` | PCI Special Interest Group | PCI standards consortium. |
+| `POPL` | Principles of Programming Languages | Research conference. |
+| `PROV` | World Wide Web Consortium provenance vocabulary | W3C provenance standard family. |
+| `PROV-DM` | PROV data model | W3C provenance data model. |
+| `PS` | Protect the Software | NIST SSDF practice group. |
+| `RAM` | random-access memory | Working memory. |
+| `RAT` | resolution asymmetric tautology | Clausal redundancy property. |
+| `RATS` | Remote Attestation Procedures | IETF attestation architecture. |
+| `RFC` | Request for Comments | IETF publication series. |
+| `RIM` | Reference Integrity Manifest | Trusted reference-measurement set. |
+| `RISC` | reduced instruction set computer | Processor architecture family. |
+| `RISC0` | RISC Zero | Product-name prefix used by RISC Zero tooling. |
+| `RNG` | random number generator | Entropy or pseudorandomness source. |
+| `RUP` | reverse unit propagation | Clausal proof-checking rule. |
+| `SAT` | Boolean satisfiability problem | Decision problem and solver class. |
+| `SCITT` | Supply Chain Integrity, Transparency, and Trust | IETF architecture and working group. |
+| `SCRAPI` | SCITT Reference APIs | SCITT registration and receipt-resolution interface draft. |
+| `SDK` | software development kit | Developer-facing library and tools. |
+| `SHA-256` | Secure Hash Algorithm 256-bit | Cryptographic digest algorithm. |
+| `SLSA` | Supply-chain Levels for Software Artifacts | Software supply-chain framework. |
+| `SMI` | system management interface | Vendor device-management interface. |
+| `SMT` | satisfiability modulo theories | Decision-procedure family. |
+| `SMT-LIB` | SMT library standard | Common language and benchmark format for SMT solvers. |
+| `SPDM` | Security Protocol and Data Model | Device authentication and measurement protocol. |
+| `SPDX` | Software Package Data Exchange | Software-package metadata standard. |
+| `SR-IOV` | single-root input/output virtualization | Hardware virtualization interface. |
+| `SSDF` | Secure Software Development Framework | NIST software-development framework. |
+| `SSH` | Secure Shell | Remote command and transport protocol. |
+| `STARK` | scalable transparent argument of knowledge | Cryptographic proof-system family. |
+| `TCB` | trusted computing base | Components on which a result depends. |
+| `TDISP` | Trusted Device Interface Security Protocol | Device-interface isolation protocol. |
+| `TPU` | tensor processing unit | Machine-learning accelerator class. |
+| `TS` | Transparency Service | SCITT registration and receipt service. |
+| `TUF` | The Update Framework | Software-update security framework. |
+| `UNSAT` | unsatisfiable | Solver result meaning no satisfying assignment exists. |
+| `URI` | uniform resource identifier | Resource name or locator. |
+| `URL` | uniform resource locator | Network resource locator. |
+| `UTC` | Coordinated Universal Time | Time standard. |
+| `UTF-8` | Unicode Transformation Format, 8-bit | Text encoding. |
+| `VDP` | verifiable data structure proof | Proof format for a VDS. |
+| `VDS` | verifiable data structure | Append-only or otherwise provable data structure. |
+| `VM` | virtual machine | Software-defined machine environment. |
+| `VSTD` | Verifier Standard | Repository standard and reference implementation. |
+| `W3C` | World Wide Web Consortium | Web standards organization. |
+| `WG` | working group | Standards-development group. |
+| `WSL2` | Windows Subsystem for Linux 2 | Windows-hosted Linux environment. |
+| `YAML` | YAML Ain't Markup Language | Structured data format. |
+| `ZI` | zero-identity | Historical study coordinate; not a trust or conformance class. |
+| `ZIP` | ZIP archive format | Compressed archive format; treat ZIP as the format's proper name. |
+| `ZIZK` | zero-identity/zero-knowledge | Governing VSTD artifact-first architecture; particular privacy and propagation mechanisms have their own maturity. |
+| `ZK` | zero-knowledge | Cryptographic or semantic privacy property, only when explicitly supported. |
+| `zkVM` | zero-knowledge virtual machine | Virtual machine that emits a zero-knowledge proof. |
diff --git a/docs/API_STABILITY.md b/docs/API_STABILITY.md
new file mode 100644
index 0000000..58cdc34
--- /dev/null
+++ b/docs/API_STABILITY.md
@@ -0,0 +1,105 @@
+# Python application programming interface (API) stability
+
+> **Term:** Verifier Standard (VSTD).
+
+This policy applies beginning with the first release that contains it. It does not
+retroactively change frozen receipts or earlier release bytes.
+
+## Supported boundary
+
+The supported Python runtime API is the set of names exported by `verifier.__all__` and
+rendered under **Top-level Python exports** in the generated
+[reference](https://timelordraps.github.io/verifier/reference.html). The implementation
+tests that the two surfaces agree and that every exported name resolves.
+
+`verifier.__version__`, `verifier.__standard__`, and `verifier.__standard_status__` are
+stable read-only metadata names. The standard coordinate and status describe this project;
+they do not claim standards-body recognition, conformance, adoption, or endorsement.
+
+The supported artifact-control exports are `freeze_artifact`, `seal_artifact`,
+`verify_frozen_artifact`, `thaw_artifact`, `thawed_artifact_status`,
+`ArtifactVerification`, and `ArtifactControlError`. Seal creation and seal verification
+require the optional `seal` dependency extra; importing the base package and freeze-only
+operations retain the zero-third-party-dependency boundary.
+
+`freeze_artifact` classifies the supplied final source entry before dereferencing it and
+refuses symbolic links. New freeze bundles, thaw descendants, and generated thaw sidecars
+require absent lexical destination entries, including refusal of dangling symbolic links.
+This fail-closed creation contract does not promise universal race-free filesystem security
+against concurrent privileged replacement.
+
+Verification also requires authoritative internal bundle members to have their ordinary
+lexical file or directory type. The freeze manifest, payload, seals container, and seal
+envelopes cannot inherit authoritative bytes through symbolic links or supported
+reparse-point aliases. This does not change the accepted read-only alias behavior of an
+outer parent-bundle or explicit thaw-record argument. Ordinary hard links remain regular
+file byte-and-path semantics rather than an exclusive-inode claim.
+
+`thawed_artifact_status` treats a `VSTD-ARTIFACT-THAW-1` sidecar as unkeyed lineage
+metadata. Without `parent_bundle`, it returns `NOT_ESTABLISHED` even when descendant bytes
+agree with the sidecar's recorded identifier. `THAWED_CLEAN` or `THAWED_DIRTY` requires an
+actual supplied parent that verifies as cleanly sealed and matches every recorded parent
+coordinate. Optional expected artifact and key identifiers add external-anchor checks.
+The result still does not authenticate the historical copy operation.
+
+The supported evidence-bound construction exports are `BoundProposition`,
+`EvidenceBindingError`, `EvidenceBounds`, `EvidenceStore`, `MechanismDecision`,
+`MechanismOutcome`, `VerificationSession`, `WitnessBundle`, `ProvenanceHypergraph`,
+`claim_binding_from_dict`, `establish_vstd4`, `assess_witness_corroboration`,
+`establish_graph_level`, `graph_collection_binding_digest`, and `AssuranceLedger`.
+The matching supported portable-record exports are
+`build_evidence_bound_vstd4_receipt`, `recheck_evidence_bound_vstd4_receipt`,
+`build_vstd5_receipt`, `recheck_vstd5_receipt`,
+`build_evidence_bound_graph_level_record`,
+`recheck_evidence_bound_graph_level_record`, and `recheck_assurance_log`.
+Compatibility `vstd4_depth` and `graph_level`-style candidate results do not become
+conformance results merely because the evidence-bound APIs also exist.
+
+`assess_witness_corroboration` accepts incomplete inputs so it can return a typed diagnostic
+result. The supported `build_vstd5_receipt` boundary is stricter: it either raises or returns
+an object satisfying the published receipt shape with all verdict-material evidence bytes.
+`recheck_vstd5_receipt` applies the same zero-dependency structural gate before replay and
+does not accept a schema-invalid assessment object as a portable receipt. It also compares
+the complete carried VSTD-4 entry, requires the bundle `claim_id` to equal the admitted
+VSTD-4 claim identifier, and mechanism-checks `corroboration_class`; schema-valid field
+relabeling cannot retain an established replay result.
+
+`ProvenanceHypergraph.from_dict` retains the frozen `VSTD-DATA-0.1` two-namespace reader:
+one identifier may occur once as an artifact and once as a transformation. Direct `add_*`
+construction and default structural validation are stricter and globally disjoint. Such a
+historical overlap remains readable but cannot enter evidence-bound Graph establishment or
+assurance mechanisms. The compatibility candidate computation retains its historical scope
+and remains `NOT_ESTABLISHED`.
+
+Direct imports from `verifier.core`, `verifier.data`, `verifier.hardware`, other
+subpackages, or underscore-prefixed names are internal unless another published policy
+explicitly names them. They may change in a minor release. That freedom does not override
+frozen receipt identifiers, schemas, packaged specification bytes, command compatibility,
+or historical refutation obligations.
+
+## Version and deprecation rules
+
+- Patch releases preserve supported signatures and behavior while correcting defects.
+- Minor releases may add supported names and compatible parameters.
+- Removing or incompatibly changing a supported name requires the next major release.
+- Before removal, the name remains usable through the current major series, emits
+ `DeprecationWarning`, names a supported replacement, and appears in release notes.
+- `_API_DEPRECATIONS` in `verifier.__init__` is the testable warning registry. A warning
+ cannot change the returned object, verdict, or failure semantics.
+
+Semantic Versioning governs only this declared software compatibility surface. It does not
+increase assurance or establish an external standard.
+
+## Separate compatibility surfaces
+
+- `vstd` is the canonical command-line interface (CLI). `verifier` remains a compatibility
+ alias where unambiguous; `verifiable` is permanent because published refutation steps
+ bind it.
+- Serialized receipt identifiers and released receipt bytes follow
+ [`WIRE_IDENTIFIERS.md`](../standard/WIRE_IDENTIFIERS.md), not this Python policy.
+- Published JavaScript Object Notation (JSON) Schemas change only under their declared
+ profile and compatibility rules.
+- Artifact-control formats follow `standard/ARTIFACT_CONTROL.md`. They are not receipts;
+ an incompatible format change requires a new artifact-control mechanism identifier.
+- `verifier.experimental_workflow` is experimental and outside the supported Python API;
+ its outputs still cannot strengthen a VSTD verdict by naming or placement.
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
new file mode 100644
index 0000000..45ea4ba
--- /dev/null
+++ b/docs/ARCHITECTURE.md
@@ -0,0 +1,367 @@
+# Verifier Standard (VSTD) conformance architecture
+
+> **Acronyms:** application programming interface (API); Boolean satisfiability problem (SAT); command-line interface (CLI);
+> identifier (ID); JavaScript Object Notation (JSON); reduced instruction set computer (RISC);
+> Supply Chain Integrity, Transparency, and Trust (SCITT); Verifier Standard (VSTD);
+> zero-identity/zero-knowledge (ZIZK).
+
+**Status:** implementation and ownership map; normative meaning remains in `standard/`
+
+TRUST is mechanism-earned forward artifact support; ROT is typed, time-indexed
+degradation of current admissibility; and RUST is inverse-TRUST diagnostic traversal
+toward recorded ancestors. They are formal semantic names, not acronyms or actor ratings.
+
+Use this order when two surfaces appear to disagree:
+
+1. normative numbered-profile document;
+2. serialized receipt identifier (`schema_version`) and profile discriminator;
+3. published JSON Schema;
+4. typed model and validator;
+5. conformance tests;
+6. generated reference and examples.
+
+A lower item cannot silently redefine a higher item. A passing schema check establishes
+shape only; a passing validator establishes only its named implemented checks.
+
+## Numbered-profile ownership
+
+| Coordinate | Normative source | Runtime owner | Published shape | Primary tests |
+|---|---|---|---|---|
+| VSTD-1 claim receipt | `standard/VSTD-1.md` | `verifier.core.receipt`, `verifier.core.checker` | `vstd1_receipt.json` | `test_independent_checker.py`, `test_vstd_schemas.py` |
+| VSTD-1 generic run | `standard/VSTD-1.md` | `verifier.core.run` capture/facade plus `run_planning`, `run_validation`, `run_inspection`, `run_reproduction`, and `run_impact` | `vstd1_generic_run_receipt.json` | `test_generic_run.py` |
+| VSTD-2 | `standard/VSTD-2.md` | `verifier.core.geometry` | `vstd2_receipt.json` | `test_verification_geometry.py` |
+| VSTD-3 | `standard/VSTD-3.md` | `verifier.hardware` | `vstd3_receipt.json`, `vstd3_accelerator_profile.json` | `test_vstd3_schema.py`, hardware tests |
+| VSTD-4 | `standard/VSTD-4.md` | certificate/kernel checks plus candidate and evidence-bound paths in `verifier.core.depth` / `verifier.core.evidence` | `vstd4_certificate.json`, `vstd4_receipt.json` | `test_gdc_certificate.py`, `test_vstd4_depth.py`, `test_evidence_bound_assurance.py` |
+| VSTD-5 | `standard/VSTD-5.md` | `verifier.core.witness` evidence-bound entry, independence, corroboration, disagreement, build, and replay | `vstd5_receipt.json` | `test_evidence_bound_assurance.py`, `test_vstd_schemas.py` |
+| VSTD-Graph-1 | `standard/VSTD-Graph-1.md` | `verifier.data.models`, `verifier.data.receipt` | `vstd_graph_receipt.json` | `test_public_data.py` |
+| VSTD-Graph-2..5 | matching Graph documents | `verifier.data.graph_level` candidate/evidence-bound paths | `computed_graph_level` within `vstd_graph_receipt.json` | `test_graph_level.py`, `test_evidence_bound_assurance.py` |
+| ZIZK artifact-first TRUST/ROT/RUST | `standard/LADDER.md` section 1.1 | `verifier.data.assurance`; bounded RISC Zero example under `examples/zizk_artifact_first/` | `vstd-graph-assurance-1.schema.json`; not a numbered-profile receipt | assurance, presentation, experiment-manifest, and ZIZK mechanism tests |
+| Artifact freeze, seal, and thaw | `standard/ARTIFACT_CONTROL.md` | `verifier.artifact_control` and `vstd artifact` | `standard/schemas/artifact-control-1.schema.json`; these are mechanism objects, not receipts | `test_artifact_control.py`, public API/CLI tests |
+
+Compatibility VSTD-4 and Graph paths still compute candidates from caller-supplied
+references or ratings and return `conformance_status = NOT_ESTABLISHED`. Separate
+evidence-bound paths resolve exact bytes, pin and rerun registered mechanisms, enforce
+bounds, and recheck certificates before they can report `ESTABLISHED`. A mechanism result
+is limited to its proposition, evidence, trust roots, implementation digest, and bounds;
+the repository claims no external witness or independent implementation.
+
+Artifact control is an orthogonal mechanism beneath the axes. It can preserve and close
+an artifact used in any numbered profile, but its successful verification establishes only exact-byte
+integrity, structural closure, and any separately supplied external anchor. It cannot
+supply a numbered-profile result, semantic correctness, encryption, trusted time, actor trust, or
+continuous temporal mediation. A thaw sidecar is additive lineage metadata, not parent
+authentication: sidecar-only agreement remains `NOT_ESTABLISHED`. Current clean or dirty
+status requires an actual supplied, cleanly sealed parent whose artifact, content, freeze,
+seal, kind, and media-type coordinates match. Even that comparison does not authenticate
+the historical copy operation. Creation paths preserve the caller-supplied final entry
+through classification: freeze refuses symbolic-link sources, while bundle, descendant,
+and sidecar creation require an absent lexical destination. These checks do not claim
+universal race-free filesystem security against concurrent privileged replacement.
+Authoritative members inside a bundle are stricter: the freeze manifest, payload, seals
+container, and seal envelopes must have their required ordinary lexical types and cannot
+borrow bytes through symbolic links or supported reparse-point aliases. That internal
+closure rule does not reject an outer parent-bundle or explicit thaw-record read alias,
+whose resolved bytes and bindings are verified. Ordinary hard links remain byte-and-path
+objects rather than claims of exclusive inode ownership.
+
+## Verification complex and profile satisfaction
+
+VSTD is organized by named **closure coordinates**, not interchangeable layers or scalar
+assurance levels. A **numbered profile** is a cumulative requirement formula over those
+coordinates. The two axes use different coordinate sets even when their profile numbers
+match:
+
+| Profile number | Object closure coordinate | Graph closure coordinate |
+|---:|---|---|
+| 1 | Claim Mechanics | Recorded Lineage |
+| 2 | Verification Surface | Bounded Collection Surface |
+| 3 | Substrate Accountability | Accountable Provenance Closure |
+| 4 | Refutability | Refutable Transformation Closure |
+| 5 | Witness Corroboration | Corroborated Verification Network |
+
+Evidence enters through a named mechanism, establishes or fails to establish exact
+coordinate facts, and is then evaluated against the selected profile formula. A complete
+assessment preserves `ESTABLISHED`, `REFUTED`, `UNKNOWN`, `CONFLICTED`, and
+`NOT_ESTABLISHED` rather than collapsing absent evidence into false or a satisfiable
+encoding into conformance. A satisfying assignment over unvalidated caller assertions is
+only a candidate. Satisfaction suffices only when every satisfying fact is itself bound to
+evidence by the mechanism that earned it.
+
+“Closure” is always qualified. VSTD-2 surface closure, Graph provenance closure,
+refutability closure, and artifact-seal structural closure are distinct propositions.
+“Profile” is likewise qualified as a numbered, receipt, application, or geometry profile
+when context does not make the category unique. The exact compatibility names and
+exceptions are normative in [`standard/LADDER.md`](../standard/LADDER.md#terminology-contract).
+
+## Operational traversal and recursive Graph materialization
+
+This non-serialized implementation view does not redefine the numbered profiles:
+
+```text
+native computation
+ -> VSTD-1 execution and claim capture
+ -> profiler or domain adapter
+ -> VSTD-2 verification-surface normalization
+ -> VSTD-3 substrate accountability
+ -> VSTD-4 portable refutation
+ -> VSTD-5 independently evidenced witness corroboration
+ -> VSTD-Graph collection assessment
+ -> content-addressed result artifact
+ -> later bounded verification loop
+```
+
+VSTD-2 is the semantic target for adjacent adapters, not the adapter implementation
+itself. Geometry profiles constrain reusable selections of VSTD-2 geometry; they are connected only
+by explicit shared coordinates, seams, mappings, and evidence-bearing transformations.
+The current `VSTD-2` receipt has no geometry-profile or profile-composition field, so this
+relationship is conceptual rather than a new serialized contract. See
+[`VSTD-2` section 8.1](../standard/VSTD-2.md#81-profiles-and-profiler-adapters).
+
+The complete apparatus that constructs or assesses a Graph is a verifying process. In a
+later order it may become the subject of a new VSTD-2 surface, while an adjacent adapter
+maps its selected observable outputs into that geometry. Treating the whole apparatus as
+the adapter would erase the builder, mapping, verifier, and output seams that the next
+assessment must examine.
+
+A materialized Graph result must retain or bind the source Graph receipt, target collection
+or induced subgraph, selection query, object and edge ratings with their evidence, selected
+surface, lifecycle and conflict state, Graph profile certificate, materialization mechanism,
+and declared information loss. It earns no strength from size, path count, repetition,
+storage, or agreement. Its result is capped by every applicable member, transformation,
+mapping, substrate, refutation, witness, and materialization obligation. The compatibility
+path establishes only its candidate computation. The evidence-bound path can establish a
+Graph profile only after rerunning every required rating mechanism across the complete
+closure.
+
+## Governing ZIZK architecture and mechanism ownership
+
+ZIZK artifact-first TRUST is a governing VSTD architecture, not a side experiment,
+numbered profile, scalar trust system, or actor
+reputation system. VSTD evaluates bounded validity propositions about computational
+processes represented by software and evidence-bearing artifacts; it does not determine
+whether an actor is good or bad. Its normative source is `standard/LADDER.md` section 1.1.
+
+Zero identity means zero identity-derived verdict weight, not anonymity or absence of
+identifiers. Checked identity evidence may establish only its exact attribution,
+authorization, or separation proposition, adjacent to the process claim. Zero knowledge
+means zero unevidenced knowledge is presumed: absent a mechanism-earned result, the exact
+proposition remains `UNKNOWN`. When a witness must remain confidential, cryptographic zero
+knowledge may enclose this architectural rule by binding the exact program, predicate,
+public commitments, output, proof parameters, and verifier. The proof remains bearer- and
+artifact-bound; prover identity or reputation supplies no TRUST. This property applies
+only where a named proof system establishes it under explicit assumptions.
+
+TRUST, ROT, and RUST are formal semantic names, not acronyms, serialized receipt values, actor ratings,
+scalar scores, or references to the Rust programming language:
+
+- TRUST is mechanism-earned artifact support moving forward only across admissible bound
+ transformations while every child discharges its new obligations;
+- ROT is typed, time-indexed degradation of current admissibility without rewriting
+ historical evidence; and
+- RUST is the inverse-TRUST diagnostic mechanic moving backward from an observed descendant
+ deviation through recorded contributing ancestry.
+
+The three never cancel, reverse direction, or manufacture a clean signal from `UNKNOWN` or
+`CONFLICTED` inputs. Identity, popularity, repetition, age alone, topology, and propagation
+supply no assurance.
+
+Maturity attaches to mechanisms beneath that architecture:
+
+| Mechanism surface | Current status | Ownership boundary |
+|---|---|---|
+| RISC Zero hidden-witness predicate | Bounded reference mechanism with tracked public proof artifacts and a governed tracked-source-build/image-ID equality gate | `examples/zizk_artifact_first/risc0/`; native verification only, no VSTD receipt mapping or independent build host |
+| Bounded identity-disclosure evaluator | Bounded non-normative reference mechanism | `examples/zizk_artifact_first/zero_identity/`; no identity-derived trust |
+| Assurance event serialization and replay | Implemented bounded reference mechanism | `VSTD-GRAPH-ASSURANCE-1` embeds the historical Graph, exact bindings, evidence bytes, a hash chain, and a current-view digest; `recheck_assurance_log` reruns every event mechanism |
+| TRUST transfer | Implemented edge-local proposition-dispatch reference mechanism | `record_trust` binds one exact transformation, its complete inputs and output, the historical Graph digest, and the prerequisite TRUST event for every derived input. Recursive current-admissibility checking excludes the route if any required event, artifact, or transformation degrades or conflicts, without deleting history. No universal scalar support algebra exists. |
+| ROT derivation and cross-surface propagation | Implemented bounded reference mechanisms | Strictly degrading status propositions and complete challenge-ledger projections produce additive current-state overlays; the deduplicated descendant impact set is discovery, and a descendant status change still needs its own mechanism |
+| RUST concentration, localization, and diagnostic attribution | Implemented bounded reference mechanisms | A passing descendant-deviation proposition produces deduplicated reverse reachability; concentration counts unique descendants; localization and BLAME require separate passing propositions. GUILT additionally composes exact responsibility, obligation-applicability, and obligation-violation components; an opaque obligation label cannot establish it. |
+| Complete `PASS`/`FAIL`/`UNKNOWN`/`CONFLICTED` hidden-witness derivation | Experimental and unimplemented | A caller-supplied state tag is not an earned verdict |
+| Specific optional proof backends | Backend-specific maturity; the RISC Zero example has one recorded native proof | Optional proof machinery cannot make the governing architecture optional or establish broader VSTD conformance |
+
+## Serialized receipt dispatch
+
+Dispatch first by `schema_version`, then by the required profile discriminator. VSTD-1
+claim receipts require `receipt_kind = "claim_mechanics"`; generic-run receipts require
+`receipt_kind = "generic_computational_run"`. Unknown identifiers, absent discriminators,
+and mismatched shapes fail closed. The current reader does not infer a profile from
+retired pre-current-profile field arrangements.
+
+## Installed specification ownership
+
+Every `standard/*.md` file has a byte-identical installed resource under
+`src/verifier/specifications/`. Verifier descriptors use those resources when no source
+checkout is present. The installed-wheel gate runs outside the checkout and rejects an
+unavailable specification digest.
+
+The artifact-control schema has a byte-identical installed copy under
+`src/verifier/artifact_control/`. GitHub Pages publishes it at its declared `/schemas/`
+route alongside receipt schemas without reclassifying it as a receipt.
+
+## Generic-run validation contract
+
+`vstd validate` is an integrity/profile validator for the
+`generic_computational_run` profile. It enforces the strict receipt shape and recomputes
+the stable-payload digest. The dynamic path keys inside
+`source_state.source_file_hashes`, unconstrained recorded evaluator values, and
+additional declarations inside `assessment_context.refutation_surface` are explicit data or
+extension surfaces. The refutation surface remains open for caller-defined domain
+refutations, but every additional value remains a declaration until an applicable mechanism
+checks it. Unknown object properties outside those named surfaces fail closed.
+
+Validation does not rehash referenced artifacts, rerun the command, resolve evidence
+references, or establish that recorded declarations are true. Those are separate
+mechanisms. `validate`, `inspect`, and `reproduce` honor `--json` for generic-run and
+VSTD-Graph receipts; the envelope reports command completion without upgrading the
+receipt's claim semantics.
+
+### Generic-run assessment context
+
+`assessment_context` is a required VSTD-1 generic-run container, not a VSTD-4 object. It
+participates in the canonical digest and retains the manifest-declared mechanism,
+resource-bound, commitment, and refutation coordinates without using a numbered-profile
+identifier as a generic container name.
+
+| Member | Five-As role | Maximum current meaning |
+|---|---|---|
+| verifier identity | Assessment | Names the generic mechanism; identity alone earns no result. |
+| specification identity | Attribution and Assessment | Binds the mechanism to VSTD-1 bytes, not VSTD-4. |
+| implementation identity | Assignment | Identifies implementation bytes; it does not prove an independent implementation. |
+| parser identity | Assignment | Identifies parser bytes; equality with the implementation hash records shared bytes, not separation. |
+| format identity | Attribution | Names the generic capture/validate/reproduce fragment. |
+| resource bounds | Assurance input and Assessment bound | Records manifest declarations; the generic runtime does not establish their enforcement. |
+| prior commitment | Assurance input | Records a commitment string; receipt inclusion does not prove temporal priority. |
+| refutation surface | Attribution | Declares admissible refutations and exclusions; it is not the checked VSTD-4 `RefutationSurface`. |
+
+Closure coordinates identify assessment questions; they are not containers for generic
+verification context. The neutral container must not generate profile-numbered binding
+structures. Nothing in
+`assessment_context` supplies a VSTD-4 result.
+
+## Five-As human traversal
+
+**Status:** non-serialized architecture guide. The five As are roles in a human traversal
+of existing VSTD records, not five new object types, numbered profiles, statuses, or a scalar assurance
+score. No receipt or schema format is defined here.
+
+`ASSURANCE_0 -> ATTRIBUTION -> ASSIGNMENT -> ASSESSMENT -> ASSURANCE_1` reads as follows:
+
+| Stage | Operational meaning | Existing VSTD machinery | Current gap |
+|---|---|---|---|
+| `ASSURANCE_0` | Identified evidence or a previously assessed claim, with its evidence basis, provenance, bounds, trust roots, limitations, current state, and unresolved conflicts or unknowns. | Generic-run receipts and external-evaluation evidence; `EvidenceClassification`; Graph artifacts, statuses, and `ConflictRecord`; VSTD-3 evidence sources, gaps, and claim evaluations; VSTD-4 certificates and kernel results. | There is no universal Assurance record or cross-profile scalar ordering. |
+| `ATTRIBUTION` | The explicit relation from evidence to the exact subject/predicate it supports, including the mapping, extraction, or transformation, scope, bounds, provenance, and information loss. | Generic-run bound-output extraction and recorded external references; Graph transformation hyperedges; VSTD-4 `ClaimCoordinate`, `ClaimBinding`, and `Grounding`; loss-sensitive SCITT coordinates. | Mapping and loss declarations remain profile-specific; a reference alone is not a checked mapping. |
+| `ASSIGNMENT` | The most precise evidenced execution coordinate available: computation, execution instance, software/runtime, machine/substrate, then optional actor/operator bindings. Missing coordinates remain partial or `UNKNOWN`. | Generic-run execution and source-state records; VSTD-3 `WorkloadIdentity`, `ExecutionIdentity`, topology, device, runtime, and evidence-source records; VSTD-1 `independence_basis` for the separate independence question; generic `BoundProposition` mechanism dispatch for an exact assignment proposition. | The legacy generic-run declaration does not self-promote into an evidenced Assignment. A deployment supplies the mechanism and observations; Assignment alone establishes no trust, authorization, independence, or responsibility. |
+| `ASSESSMENT` | An identified verifier or mechanism evaluates one bounded proposition under the applicable input Assurance, Attribution, Assignment, specification/profile, trust roots, and bounds. It earns only the predicates it checks. | Generic validation, artifact rehash, and rerun mechanisms; VSTD-3 recomputed `ClaimEvaluation`; Graph validation and candidate-profile certificates; the VSTD-4 grounded certificate kernel; native VSTD plus native SCITT composition. | No one verifier covers every profile; mechanism results remain adjacent rather than silently merged. |
+| `ASSURANCE_1` | The assessment output recorded as new evidence with complete lineage to its inputs, mechanism, proposition, and limits. It may be `PASS`, `FAIL`, `UNKNOWN`, `CONFLICTED`, or a profile-specific equivalent. | Receipts, claim evaluations, kernel results, certificates, artifact digests, Graph artifacts/hyperedges, prior commitments, and the replayable `VSTD-GRAPH-ASSURANCE-1` event envelope preserve and reference the output. | The Graph envelope is not a universal scalar or an automatic cross-profile cast; each later loop still names and reruns its mechanism. |
+
+First-hand and second-hand describe **provenance**, not strength. A first-hand
+self-observation may be weak; a second-hand certificate may be strongly bound to a narrow
+proposition. `EvidenceClassification` records how evidence entered a profile, but its name,
+source, or placement never substitutes for the profile's verification mechanism.
+
+The smallest operational loop is:
+
+1. select one proposition and retain every applicable input state, limitation, conflict,
+ unknown, trust root, and freshness bound;
+2. bind each input to that proposition through an inspectable attribution, preserving
+ transformations and declared information loss;
+3. record Assignment only to the specificity evidenced, leaving absent coordinates `UNKNOWN`;
+4. run the named assessment mechanism under its specification and bounds; and
+5. record the output as a new evidence artifact and transformation, without changing any
+ input record. A later loop may consume that output only as lineage-preserving input to
+ another explicitly identified assessment.
+
+> No semantic strength is gained by storage location, field name, repetition, graph
+> multiplicity, actor reputation, or propagation. Every increase in assurance must
+> identify the verification mechanism that earned it.
+
+This is the human forward traversal of the same topology VSTD-Graph stores for machines.
+TRUST is bounded, mechanism-earned support across an admissible recorded transformation;
+the child still discharges its new obligations. ROT is typed, time-indexed degradation of
+current admissibility while historical evidence remains immutable. RUST is inverse-TRUST
+diagnostic reachability from a downstream deviation toward recorded ancestors. Together
+they describe memetic causal-provenance and lifecycle behavior over one development graph.
+They do not establish actor standing, moral character, responsibility, or automatic
+ancestor falsification. The reference runtime emits bounded TRUST, ROT, and RUST events
+only after their exact mechanisms run. Causal localization and artifact-relative `BLAME`
+or `GUILT` require additional exact propositions. BLAME establishes bounded responsibility
+or material contribution. GUILT is not directionally opposite: it composes three separately
+bound passing evaluations for responsibility, applicability of an exact scoped obligation,
+and violation of that same obligation relative to the same localized deviation. The final
+evaluation binds all three component digests. One compound mechanism may emit those three
+evaluations in one invocation, but one opaque combined result cannot substitute for them.
+Neither becomes actor reputation, moral character, automatic legal liability, or social
+scoring; missing GUILT does not establish innocence or exoneration.
+
+### Recursive-amplification falsification outcomes
+
+| Probe | Required outcome |
+|---|---|
+| Duplicate evidence or a duplicate identifier | No extra support; public receipts reject duplicates and in-memory graph construction rejects replacement. |
+| Duplicate graph paths | Reachability is set-valued; path count never raises assurance or the candidate Graph profile. |
+| Repeated identical reruns | At most the same bounded equivalence result; repetition does not prove independence or a stronger reproduction state. |
+| Assessment consumes its own output | Invalid within that assessment; an output can enter only a later, distinct assessment with preserved lineage. |
+| `A -> B -> A` or a self-loop | Invalid Graph topology; acyclicity checking rejects the loop. |
+| Second-hand evidence relabeled first-hand | Provenance conflict or unsupported declaration; no strength change. |
+| Attribution without a checked mapping | Declaration or `UNKNOWN`, never mapped support. |
+| Machine Assignment treated as responsibility | Prohibited inference; Assignment records execution coordinates only. |
+| Actor identity treated as trust | Prohibited inference; identity and reputation do not strengthen an artifact result. |
+| Conflicted upstream evidence collapsed | Conflict remains explicit and blocks a clean candidate Graph profile. |
+| Stale, revoked, challenged, or unknown evidence reused as clean current support | Inadmissible to a clean current Graph candidate; this is ROT in current admissibility, not revision of the historical record. |
+| Recursive propagation with no new mechanism | No transition from `ASSURANCE_0` to stronger `ASSURANCE_1`; lineage growth is not assurance growth. |
+
+## Separation and Graph boundaries
+
+The historical `independent_audit` field name does not prove independence. Its
+`independence_basis` records actor, implementation, and runtime separation. Repeated or
+matching results are artifact agreement, not evidence that separate actors performed the
+runs; absent separation evidence is `NOT_DEMONSTRATED`. Serialized status words and
+evidence-reference strings cannot self-promote that result. The generic-run compatibility
+path treats supplied assertions as no stronger than `DECLARED`, rejects receipts that label
+them `EVIDENCED`, and never derives `EVIDENCED`. The distinct VSTD-5 reference path can
+establish only its exact declarant/witness separation propositions after all seven seams are
+rerun by registered mechanisms. Typed binding, identity, separation, and corroboration
+errors keep `computed_independence` fail-closed; no error-message text is interpreted as a
+semantic category. Witness identities and independence assertions serialize as separate
+ordered arrays, so duplicates, orphan assertions, and missing cardinality survive receipt
+build and replay rather than disappearing inside a keyed map. Permissive assessment remains
+separate from portable-record admission: the builder raises unless the result inhabits the
+strict VSTD-5 schema and embeds every verdict-material evidence byte, and the rechecker
+enforces the same zero-dependency shape gate before replay. It also compares every redundant
+VSTD-4 entry coordinate and treats `corroboration_class` as part of the mechanism-checked
+expected proposition. The admitted evidence-bound VSTD-4 result retains its exact `claim_id`,
+and VSTD-5 requires the bundle to use that identifier; a shared binding or certificate digest
+cannot turn a neighboring identifier into an alias. Thus relabeling any of these coordinates
+cannot retain an established result. This path does not upgrade the legacy generic-run fields.
+
+Graph conflict records retain incompatible values and their evidence references without
+adding a scalar score or changing the frozen artifact-status vocabulary. A conflict makes
+the subject inadmissible to a clean candidate Graph profile. The frozen `VSTD-DATA-0.1`
+reader retains its historical separate artifact/transformation namespaces. Direct new
+construction, evidence-bound Graph establishment, and the assurance overlay require global
+cross-kind disjointness, so an untyped `subject_id` cannot ambiguously name both kinds of
+Graph object.
+
+### Recursive current-state audit
+
+Historical receipt bytes and their recorded `PASS` remain unchanged. A later current-state
+question is a new assessment over the retained graph and applicable lifecycle records:
+
+| Scenario | Implemented outcome |
+|---|---|
+| An ancestor is `CHALLENGED`, `REVOKED`, or `STALE` | Candidate/evidence-bound Graph recomputation follows the full ancestor closure and returns compatibility field `level = 0`. `AssuranceLedger` also records typed ROT or projects append-only challenge-ledger state into a derived current view; historical Graph bytes remain unchanged. |
+| An ancestor is `SUPERSEDED` | The historical Graph candidate remains admissible by design; the stricter all-ancestors-`VALID` policy rejects it for current-use admission. Supersession does not retroactively falsify its prior lineage role. |
+| Upstream evidence conflicts | A retained `ConflictRecord` or a mechanism-established `CONFLICT_DECLARATION` event blocks every dependent edge-local TRUST route. `resolve_conflict` accepts only a mechanism-passing proposition bound to the exact conflict and one retained competing value. A status resolution projects the selected artifact or transformation state: only `VALID` / `COMPLETED` can restore the route. An arbitrary resolved predicate remains admissibility-blocking because value adjudication does not establish support effect; no general non-status admissibility-effect mechanism is implemented. The original conflict and resolution evidence remain historical. |
+| Evidence arrives by multiple paths or one run receipt repeats a reference | Reachability and impact sets deduplicate identifiers. Multiplicity supplies no independence or strength. |
+| A descendant deviation points toward shared ancestors | A mechanism-passing deviation emits RUST over the deduplicated historically recorded contributing ancestor set. Current revocation or conflict does not erase diagnostic history. Structural concentration counts unique deviating descendants, not paths or causal strength. Localization selects and binds one exact passing RUST event, its descendant-deviation binding digest, and an ancestor contained in that event. BLAME and GUILT bind that localization event and require separate exact mechanisms. |
+| A challenge ledger changes a claim's current status | `project_challenges` binds its complete append-only records into a current Graph overlay and embeds those records for replay. Existing TRUST remains historical, while recursively dependent events disappear from `current_trust_events`; `impacted_descendants` reports the deduplicated reassessment surface. It never mutates the historical graph. |
+| Later evidence adjudicates a conflict | The additive resolution retains the original competing evidence and its mechanism evaluation. VSTD-Graph-1 receipts remain immutable; the separate assurance overlay owns the resolution and current-state projection. Removing or rewriting historical evidence remains invalid. |
+| Candidate calculation encounters cyclic ancestry | Rejected before candidate calculation; recursive topology cannot manufacture assurance. |
+
+The forward blast-radius query remains discovery only. `AssuranceLedger` is the distinct
+binding mechanism for explicit edge-local TRUST, ROT, RUST, challenge projection, and
+conflict declaration/resolution. `recheck_assurance_log` reconstructs the historical Graph, rehashes every
+embedded evidence item, reruns the exact registered mechanisms, reproduces the event chain,
+and compares the derived current view. The ledger never infers an unrecorded edge, converts
+topology into assurance, or treats RUST as causality. Domain-specific transfer and
+localization propositions still require their registered mechanisms and may return
+`UNKNOWN`.
diff --git a/docs/CLAIMS_AND_LIMITS.md b/docs/CLAIMS_AND_LIMITS.md
index 5533a9e..084de29 100644
--- a/docs/CLAIMS_AND_LIMITS.md
+++ b/docs/CLAIMS_AND_LIMITS.md
@@ -1,5 +1,17 @@
# Claims and limits in plain language
+> **Acronyms:** artificial intelligence (AI); Advanced Micro Devices (AMD); application programming interface (API);
+> Amazon Web Services (AWS); Concise Binary Object Representation (CBOR); CBOR Object Signing and Encryption (COSE);
+> conjunctive normal form (CNF); Device Identifier Composition Engine (DICE);
+> grounded decision certificate (GDC); identifier (ID); machine learning (ML); NVIDIA Management Library (NVML);
+> Secure Hash Algorithm 256-bit (SHA-256); Secure Hash Algorithm 3 256-bit (SHA3-256);
+> system management interface (SMI); Security Protocol and Data Model (SPDM);
+> Software Package Data Exchange (SPDX); Supply Chain Integrity, Transparency, and Trust (SCITT);
+> trusted computing base (TCB); Coordinated Universal Time (UTC);
+> Verifier Standard (VSTD).
+
+> Reader aid: [concept glossary and primary precedents](CONCEPTS_AND_PRECEDENTS.md).
+
**Status:** normative interpretation guide for the VSTD object and Graph ladders
This guide translates VSTD claim language into ordinary language. When a short claim
@@ -9,20 +21,42 @@ conflicts with the bounded wording here, the bounded wording controls.
A VSTD result always has this form:
-> For this identified subject snapshot, this declared verification surface passed this
-> identified mechanism using this bound evidence, subject to these limitations, trust
-> roots, and horizons.
+> For this **identified subject snapshot**, this **identified mechanism** returned this
+> **bounded result** over this **declared verification surface**, using this
+> **bound evidence**, subject to these **limitations**, **trust roots**, and **horizons**.
Omitting any bolded idea changes the claim. `VERIFIED` never means universally true,
safe, complete, permanent, legally authorized, or endorsed.
+## Skeptical review summary
+
+VSTD has no single scalar “strongest claim”; mechanisms establish different predicates.
+The strongest generally reusable implemented statement is therefore an exact, bounded
+checker result—not a claim of universal truth or whole-project conformance.
+
+| Reviewer question | Current answer | Mechanism and trust roots | Boundary or missing mechanism |
+|---|---|---|---|
+| What can the generic validator establish? | Stable receipt content and strict profile shape. | Canonicalization, recorded digest, profile discriminator, and bundled validator bytes. | It does not verify the recorded native claim, external evidence, actor identity, or independence. |
+| What can the grounded-certificate kernel establish? | The exact `VSTD4-GDC-1` decision was accepted, rejected, or left `UNKNOWN` under its claim binding and resource bound. | Certificate bytes, formula, grounding, policy/evidence roots, verifier descriptor, and kernel. | Kernel acceptance alone is not VSTD-4 conformance. The evidence-bound path separately reruns every prerequisite/rung mechanism before it may establish conformance. |
+| What can VSTD-Graph establish? | Stored topology plus a candidate over supplied ratings, or an evidence-bound Graph profile after every complete-closure rating mechanism is rerun. | Graph bytes, lifecycle/conflict view, exact rating bindings, embedded evidence, mechanisms, roots, bounds, and certificate. | Recorded topology is not complete real-world causality; the compatibility path remains `NOT_ESTABLISHED`, and domain mechanism correctness remains a declared trust boundary. |
+| What can VSTD-3 establish? | Conditional device, firmware, execution, accounting, continuity, or fleet predicates when each required evidence path validates. | Named roots, keys, nonces, measurements, topology, events, appraisal inputs, and profile-specific validators. | Host inventory is not attestation; production vendor integration and complete mediation outside the emulator remain separate requirements. |
+| What can artifact control establish? | Current exact file bytes and paths match a freeze manifest; an optional finite seal closes that freeze; with an actual supplied and cleanly verified parent whose recorded coordinates agree, a thawed descendant currently matches or differs from that parent. | Preserved bytes, SHA-256 and SHA3-256 commitments, read-only payload-tree guard, Ed25519 signature, artifact-derived identifiers, supplied parent bundle, any supplied external artifact/key anchor, fail-closed final-entry classification for supported creation paths, and ordinary lexical-type checks for authoritative internal bundle members. | Read-only is not privileged access control; a seal is not encryption, correctness, trusted time, ownership, durable external archiving, or a numbered-profile result. A thaw sidecar alone does not authenticate a parent or historical copy operation. Outer read aliases remain distinct from internal closure, ordinary hard links do not prove exclusive inode ownership, and path checks do not establish universal race-free, mount-independent, or network-filesystem security. |
+| What does SCITT add? | Signature and registration/inclusion evidence for exact payload bytes under a declared relying-party policy. | Native SCITT verifier, issuer/log keys, payload digest, registration policy, and Transparency Service evidence. | Registration cannot establish payload correctness, VSTD conformance, or issuer authority outside the policy. The current example uses a local test log. |
+| What remains outside current support? | General AI safety, hidden state, complete physical-world history, automatic real-world actor independence, unrecorded provenance, universal support algebra, and unqualified truth. | VSTD-5 and Graph assurance now dispatch exact evidence-bound mechanisms; they do not manufacture the missing domain observations or external witnesses. | Preserve `UNKNOWN`, `UNSUPPORTED`, `CONFLICTED`, or `NOT_ESTABLISHED`; do not infer a clean result. |
+
+Every claim below expands one of these boundaries into publishable wording and its
+required falsification surface.
+
## Claim translation table
| Claim | May it be made? | Why | Required evidence | What it does not mean |
|---|---|---|---|---|
| “This receipt's stable content has not changed.” | **Yes, after validation passes.** | The validator recomputes the canonical digest over the specified stable fields and compares it with the recorded digest. | Receipt bytes, canonicalization version, recorded digest, passing validator result. | The statements inside the receipt are true or authentic. |
| “These observed bytes match this SHA-256 digest.” | **Yes, conditionally.** | A named mechanism can hash accessible bytes at an observation time and compare them with the recorded digest. | The bytes, hashing mechanism, observation time, expected digest, comparison result. | The bytes came from the claimed source, existed before observation, are uncontaminated, or are legally usable. |
-| “VSTD-Graph records this lineage graph.” | **Yes.** | The receipt binds the stored artifact nodes, transformation edges, roles, statuses, and declarations. Historical Graph-1 receipts retain the `VSTD-DATA-0.1` wire identifier. | Valid receipt and structurally valid hypergraph. | The graph contains every real-world input or transformation. |
+| “This artifact is frozen.” | **Yes, after freeze verification passes.** | The supplied source was an accepted ordinary file or directory; the current regular-file bytes, portable paths, manifest identifiers, and read-only payload-tree guard recompute. | Complete bundle, passing `vstd artifact verify --freeze-only`, and the exact mechanism version. | A symbolic-link source was frozen as its target, privileged mutation is impossible, an external archive retained the artifact, the artifact is correct, or a signature exists. |
+| “This artifact is sealed.” | **Yes, after seal verification passes.** | The carried Ed25519 key verifies the finite signature closure and the seal identifier closes the signature-bearing envelope. | Passing seal verification plus an expected artifact/key coordinate when whole-bundle substitution is in scope. | Encryption, secrecy, ownership, authorization, trusted time, semantic correctness, continuous custody, actor trust, or a numbered VSTD profile result. |
+| “This thawed descendant currently matches this sealed parent.” | **Yes, only when that actual parent is supplied and verifies.** | The parent must be cleanly `SEALED`; every sidecar parent coordinate and recorded seal must agree; and the descendant identity is recomputed from authoritative parent kind and media type. | Descendant, strict thaw sidecar, supplied parent bundle, passing parent verification, exact coordinate comparison, and external artifact/key anchor when continuity is required. | The sidecar proves its own history, `thaw_artifact` was independently observed, or the supplied parent has external continuity when no external coordinate was checked. Sidecar-only agreement remains `NOT_ESTABLISHED`. |
+| “VSTD-Graph records this lineage graph.” | **Yes.** | The receipt binds the stored artifact nodes, transformation edges, roles, statuses, and declarations. Historical Graph-1 receipts retain the serialized receipt identifier `VSTD-DATA-0.1`. | Valid receipt and structurally valid hypergraph. | The graph contains every real-world input or transformation. |
| “This is the complete provenance of the model or dataset.” | **No, unless completeness is independently evidenced for the declared boundary.** | A graph cannot infer hidden inputs, pre-observation history, out-of-band processing, or missing instrumentation. | Independent coverage evidence for every declared boundary plus explicit horizons outside it. | That a high coverage summary proves complete real-world lineage. |
| “This transformation actually ran and produced this output.” | **Only with execution evidence.** | Software, parameters, and environment fields are declarations until a run trace, rerun, attestation, or equivalent evidence binds execution to the output. | Identified inputs and outputs, execution trace or rerun, software identity, parameters, environment, and evidence classification. | Recording a script name or commit proves execution. |
| “The recorded Boolean provenance policy passed.” | **Yes, when the policy result validates.** | The reference solver evaluates the recorded CNF formula. | Formula, variable map, graph snapshot, solver identity, passing result. | The prose-to-formula translation was complete, the external facts were true, or broader policy compliance was established. |
@@ -30,14 +64,41 @@ safe, complete, permanent, legally authorized, or endorsed.
| “All recorded target ancestors are explicitly `VALID`.” | **Yes, if the fail-closed valid-ancestor policy passes.** | That policy rejects every recorded target ancestor not explicitly marked `VALID`. | Target artifact, ancestor closure, status evidence, passing `POL-ALL-ANCESTORS-VALID`. | The status declarations are authentic or that unrecorded ancestors do not exist. |
| “The recorded SPDX metadata matches the allowlist.” | **Yes, if the exact metadata policy passes.** | The policy compares recorded license identifiers with the declared allowlist. | Rights records, roots, allowlist, passing policy result. | Copyright ownership, license authenticity, compatibility, fair use, or a legal ruling. |
| “This result reproduced bitwise.” | **Yes, for the declared outputs after a passing rerun.** | The rerun produced byte-identical declared output artifacts. | Original receipt, runnable command, captured inputs, environment boundary, rerun outputs, byte comparison. | All environments will reproduce it or the computation is empirically correct. |
-| “This was independently verified.” | **Only when the relevant independence seam is demonstrated.** | Independence requires separation from the producer's relevant state and logic plus a declared trusted computing base. | Producer/auditor boundary, TCB, source identities, isolation evidence, independent result. | Running the bundled verifier on its own output is automatically independent. |
+| “This was independently verified.” | **Only when distinct producer and checker actors plus the relevant execution seams are evidenced.** | Matching results establish artifact agreement, not who performed either run. Actor independence, implementation separation, runtime separation, and the trusted computing base must be recorded separately. | Evidence binding distinct actors to the producer and checker runs, implementation/runtime isolation, trusted computing base, and the checker result. | Two runs, two processes, two machines, or matching outputs automatically prove independent actors. |
| “This verification surface is self-closed.” | **Only if every VSTD-2 self-closure condition passes.** | Self-closure requires ordinary closure, resolved material residuals, discharged valences, post-verified mechanisms, no unresolved trust-root horizon, and contiguous verification orders. | Complete geometry document and passing closure assessment with no blockers. | Universal truth, infinite regress closure, permanent validity, or verification outside the surface. |
| “This competition submission and score are bound together.” | **Yes, conditionally.** | A receipt can bind identified submission bytes, evaluator version, raw metrics, and deterministic score derivation. | Submission digest, evaluator/scorer identity, environment, raw metrics, score rule, receipt. | Hidden-test integrity, no leakage, leaderboard ranking, prize eligibility, or organizer acceptance. |
-| “A challenge to this recorded ancestor affects these recorded descendants.” | **Yes.** | Blast radius is forward reachability over the stored graph. | Challenged artifact ID and bound hypergraph. | Historical receipts were automatically mutated or that unrecorded downstream systems were found. |
+| “This native verifier result was mapped into VSTD.” | **Yes, when the mapping preserves the native object, result, trust roots, bounds, and unsupported fields.** | VSTD can standardize the claim boundary and portable result semantics around a domain verifier without performing that verifier's native work. | Native object and version, native verifier implementation/version, native result, per-field mapping, information-loss declaration, VSTD coordinate, adapter tests. | VSTD replaced or reimplemented the native verifier, strengthened its result, inherited its authority, or established conformance to the source standard. |
+| “A challenge to this recorded ancestor affects these recorded descendants.” | **Yes, as a bounded reassessment surface.** | `project_challenges` reruns the built-in projection over complete challenge records; `impacted_descendants` deduplicates forward reachability; current TRUST records depending on the now-inadmissible ancestor are excluded. | Challenged artifact ID, bound hypergraph, complete challenge records, and replayed assurance log. | Historical receipts or TRUST events were mutated, every descendant is false, or unrecorded downstream systems were found. |
+| “This artifact has bounded technical GUILT for this deviation.” | **Only after component composition passes.** | The reference ledger requires separately bound passing responsibility, exact obligation-applicability, and same-obligation violation evaluations whose artifact, deviation, localization, and scope coordinates agree; the final mechanism binds their exact digests. | Exact artifact and descendant IDs, passing localization and RUST lineage, typed obligation coordinate and scope, all three component events and evidence, mechanisms and implementation digests, trust roots, bounds, final composition, and successful replay. | Moral character, actor reputation, social scoring, automatic legal liability, innocence or exoneration when absent, obligation satisfaction, or absence of hidden contributors. |
+| “The compatibility API returned Graph `level = N`.” | **Not yet as a conformance claim.** | The current implementation computes candidate Graph profile `N` from caller-supplied artifact and edge ratings; `level` is the retained field name for that profile number. It labels the result `CALLER_SUPPLIED` and `NOT_ESTABLISHED`. | A structurally valid graph and explicit supplied ratings. Conformance additionally requires implemented rating-to-evidence bindings for every required profile coordinate. | The supplied ratings were independently derived, every coordinate's evidence passed, or Graph conformance was established. |
+| “The evidence-bound Graph path established profile `N`.” | **Only for profile 1–5 at the exact collection and current view under the rerun mechanisms.** | Every member, ancestor, and reached edge rating binding passed; each rating binds the Graph bytes, deduplicated member set, collection, and claim; the Graph certificate checked; embedded evidence permits replay. | Exact Graph/event-log bytes, bindings, evidence, mechanism digests, trust roots, bounds, and recheck result. | Profile zero is conformance, real-world lineage is complete, topology proves causality, or another collection inherits the result. |
+
+## Competition and scored-evaluation claims
+
+For predictive-AI, scientific-ML, agent, and other scored evaluations, bind the exact
+rules, data, model, submission, evaluator, metrics, score, transformations, environment,
+and evidence classes. Mark hidden tests as a horizon—not evidence of integrity. This adds
+no verdict, affiliation, certification, endorsement, ranking, prize eligibility, or
+organizer acceptance.
+
+For later-resolved predictions, also bind emission and resolution times, the frozen
+prediction digest, update or abstention policy, resolution source and digest, scoring
+rule, and channel independence. Corrections are additive; never overwrite a frozen
+prediction. See the complete non-normative
+[`competition profile`](profiles/competition-evaluation.md).
+
+Use the coordinate-bounded wording:
+
+> The submission and score receipt binds the declared artifact, evaluator, and
+> provenance surface. Hidden-test integrity and organizer acceptance remain outside the
+> participant-observable surface.
+
+Do not shorten this to “the model,” “the competition result,” or “the prediction is
+verified.”
## VSTD-4 grounded-decision claim translations
-`VSTD4-GDC-1` makes a decision certificate independently checkable against an
+`VSTD4-GDC-1` makes a decision certificate checkable outside its producer against an
explicit claim coordinate, formula, grounding map, verifier identity, resource
bounds, and prior commitment. It does not make the certificate independent of
the evidence source or make the grounded claim true outside that coordinate.
@@ -46,13 +107,14 @@ the evidence source or make the grounded claim true outside that coordinate.
|---|---|---|---|
| “This VSTD4-GDC-1 certificate was accepted.” | The identified reference kernel accepted the exact canonical certificate under the declared claim binding, fragment, verifier, and resource bounds. | Name the certificate digest, implementation commit, claim coordinate, cost tier, bounds, and kernel result. | The underlying evidence is authentic, the policy captured every intended condition, or the claim is globally true. |
| “This decision is grounded.” | Every variable and clause in the accepted certificate maps to declared subjects, predicates, values, and encoding rules whose roots are bound by the certificate. | Preserve the evidence root, policy root, grounding map, and exclusions. | Unrecorded evidence does not exist, the grounding source is independent, or the physical world is completely represented. |
-| “`vstd4_depth = k`.” | Rungs `1..k` have accepted evidence in dependency order and, when `k < 14`, an accepted ceiling certificate refutes or blocks rung `k+1`. | Name the rung profile, witness certificates, ceiling certificate, budgets, and horizons. | Rungs above `k` are universally impossible or no proof can ever be found. |
+| “The reference implementation computed VSTD-4 candidate depth `k`.” | Caller-supplied nonempty references were structurally consistent through rungs `1..k` and, when `k < 14`, the candidate ceiling certificate blocks rung `k+1`. | State `CANDIDATE`, `conformance_status = NOT_ESTABLISHED`, the supplied references, certificates, budgets, and horizons. | The references establish their rung propositions, VSTD-1/2/3 passed, normative VSTD-4 conformance was established, or VSTD-5 entry is permitted. |
+| “The evidence-bound path established VSTD-4 normative depth 14.” | Exact VSTD-1/2/3 and fourteen-rung propositions passed after evidence rehash, mechanism selection/execution, bound enforcement, and kernel checking. | Name the receipt, evidence and mechanism digests, trust roots, bounds, implementation coordinate, and recheck result. | The mechanisms are universally correct, an outside witness participated, or the claim is true beyond its exact bindings. |
| “The result is refutable.” | The published result exposes a machine-checkable falsification surface and admissible counterevidence within the declared boundary. | Name that surface, the admissible counterevidence, exclusions, and decision rule. | A separate party actually attempted refutation or independently witnessed the evidence. |
| “The verifier returned `UNKNOWN`.” | The declared check could not establish `PASS` or `FAIL` within the implemented fragment, available evidence, or resource bound. | Preserve the indeterminacy reason and transcript. | The proposition is false, no proof exists, or a larger bound could not decide it. |
-| “The artifact is ready for VSTD-5 evaluation.” | The VSTD-4 result reached depth 14 with an accepted `PASS` witness and no ceiling refutation. | This is only the mechanical entry gate implemented by `require_vstd5_entry`. | VSTD-5 conformance, independent witnessing, or external certification has occurred. |
+| “The artifact is ready for VSTD-5 evaluation.” | **Only after the evidence-bound VSTD-4 path establishes VSTD-4 normative depth 14.** | `require_vstd5_entry` rejects the compatibility candidate and admits only the distinct established result type. | Candidate depth 14, a `PASS` over the candidate formula, or nonempty references satisfy the gate. |
-The public reference implementation and its tests are one implementation. This
-release does not claim an external implementation, interoperability result,
+The public reference implementation and its tests are one implementation. This source
+coordinate does not claim an external implementation, interoperability result,
security audit, independent witness, or third-party certification.
## VSTD-3 accelerator claim translations
@@ -104,6 +166,11 @@ Always cite the exact VSTD version, implementation commit, receipt type, mechani
and demonstrated test or receipt. Do not turn specification text into an implementation
claim.
+The bundled checker records a checker verdict. Its historical `independent_audit` field
+name is not evidence of independence. Claim independent verification only when the
+receipt's `independence_basis` demonstrates distinct actors plus the relevant
+implementation and runtime separation. Matching run results cannot supply that evidence.
+
## Safe claim template
> Using VSTD-Graph-1 at commit ``, receipt `` validated the stored
@@ -130,12 +197,20 @@ VSTD-4 safe template:
> establish evidence authenticity, complete policy coverage, independent
> witnessing, or truth outside the coordinate.
-VSTD-5 draft boundary template:
+VSTD-5 reference-result template:
+
+> At receipt ``, the VSTD-5 reference mechanism admitted evidence-bound
+> VSTD-4 result ``, rehashed the embedded witness evidence, and reran the
+> exact seven separation and corroboration mechanisms under ``.
+> It returned `` with conformance
+> ``. This does not imply actor trust, an external
+> witness not named by the evidence, a second implementation, or truth outside
+> the checked propositions.
-> The artifact passed the implemented VSTD-5 entry gate because its VSTD-4
-> result reached depth 14 with an accepted `PASS` witness. VSTD-5 remains a
-> draft specification and this release implements no VSTD-5 witness procedure.
-> Therefore no VSTD-5 conformance or independent-witness claim is made.
+A `CORROBORATED` overall result is valid only with `ESTABLISHED` conformance and
+`INDEPENDENT` computed separation. If a positive observation survives but any separation
+seam is unresolved, report overall `UNKNOWN`; preserve `REFUTED` and `CONFLICTED` results
+rather than softening demonstrated negative evidence.
## Prohibited shortcuts
@@ -158,3 +233,9 @@ Do not publish any of these without the missing qualification:
- “All compute was accounted for” without complete-mediation evidence for every path in
the named governed boundary.
- “No undeclared compute occurred” from a device, host, provider, or fleet receipt.
+- “GUILT” from a decorative obligation string, an opaque combined `PASS`, graph placement,
+ actor identity, role, ownership, reputation, or a violation that does not bind the same
+ artifact, scoped obligation, and localized deviation.
+- “No GUILT means innocence, exoneration, obligation satisfaction, or no hidden contributor.”
+- “Sealed means encrypted, immutable, correct, externally archived, or continuously
+ guarded.”
diff --git a/docs/CONCEPTS_AND_PRECEDENTS.md b/docs/CONCEPTS_AND_PRECEDENTS.md
new file mode 100644
index 0000000..c0c3704
--- /dev/null
+++ b/docs/CONCEPTS_AND_PRECEDENTS.md
@@ -0,0 +1,110 @@
+# Concept guide and intellectual precedents
+
+> **Acronyms:** conjunctive normal form (CNF); Certificate Transparency (CT);
+> deletion resolution asymmetric tautology (DRAT); Internet Engineering Task Force (IETF);
+> JavaScript Object Notation (JSON); National Institute of Standards and Technology (NIST); proof-carrying code (PCC);
+> Principles of Programming Languages (POPL); World Wide Web Consortium provenance vocabulary (PROV);
+> PROV data model (PROV-DM); Protect the Software (PS); Request for Comments (RFC); reverse unit propagation (RUP);
+> Boolean satisfiability problem (SAT); Supply-chain Levels for Software Artifacts (SLSA);
+> satisfiability modulo theories (SMT); SMT library standard (SMT-LIB); The Update Framework (TUF);
+> Verifier Standard (VSTD); World Wide Web Consortium (W3C).
+
+**Status:** non-normative reader aid
+
+VSTD did not arise in a vacuum, but it also does not inherit another system's guarantees
+merely by citing it. This guide separates two jobs:
+
+1. **Orientation links** answer only “what neighboring concept should I recognize?” so an
+ unfamiliar reader can stay in the flow of the guide. On the GitHub Pages site, hovering
+ over or focusing one displays a wiki-style card whose short definition is versioned in
+ this repository. The link itself opens optional Wikipedia background; repository
+ Markdown degrades to that ordinary link and its boundary title. The popup performs no
+ network request, and neither the popup nor the external page is VSTD evidence.
+2. **Primary references** support the stated historical or technical precedent by pointing
+ to a standard, specification, or original paper. They do not prove that VSTD is correct,
+ adopted, interoperable, accredited, or conformant to the referenced system.
+
+Implemented commands and supported Python interfaces are documented separately in the
+generated [command-line interface (CLI) and application programming interface (API)
+reference](https://timelordraps.github.io/verifier/reference.html). A concept is linked to
+that reference only when the implementation exposes an exact public coordinate; the guide
+does not invent an API mapping for a conceptual resemblance.
+
+When a repository orientation definition and a primary source differ, use the primary
+source for the external concept. When a primary source and a VSTD requirement differ, the
+VSTD document controls VSTD conformance and the difference must remain explicit.
+
+## Orientation glossary
+
+| Concept | Optional background | Repository-owned definition and VSTD boundary |
+|---|---|---|
+| Assurance | [Information assurance](https://en.wikipedia.org/wiki/Information_assurance "Wikipedia orientation; not a VSTD authority") | VSTD reports evidence-bounded results, not universal confidence or institutional accreditation. |
+| TRUST | [Proof-carrying code](https://en.wikipedia.org/wiki/Proof-carrying_code "Wikipedia orientation; not a VSTD authority") | Formal VSTD name for mechanism-earned, artifact-bound support that may move forward only through checked transformations. It is not actor trust, a scalar, or an acronym. |
+| ROT | [Software rot](https://en.wikipedia.org/wiki/Software_rot "Wikipedia orientation; not a VSTD authority") | Formal VSTD name for typed, time-indexed degradation of current admissibility. It preserves historical results and is not inferred from age alone. It is not an acronym. |
+| RUST | [Fault localization](https://en.wikipedia.org/wiki/Fault_localization "Wikipedia orientation; not a VSTD authority") | Formal VSTD name for inverse-TRUST diagnostic traversal from a descendant deviation toward recorded ancestor candidates. Reachability is not causal localization. It is not an acronym or the Rust programming language. |
+| Verification complex | [Constraint satisfaction problem](https://en.wikipedia.org/wiki/Constraint_satisfaction_problem "Wikipedia orientation; not a VSTD authority") | Named closure coordinates and evidence-bearing relations form the semantic space. Numbered profiles are cumulative requirement formulas over that space, not scalar assurance levels. |
+| Closure coordinate | [Security assurance component](https://en.wikipedia.org/wiki/Common_Criteria "Wikipedia orientation; not a VSTD authority") | One named verification question and failure class. Evidence for one coordinate never supplies another. Closure is always proposition-qualified. |
+| Numbered profile | [Conformance testing](https://en.wikipedia.org/wiki/Conformance_testing "Wikipedia orientation; not a VSTD authority") | `VSTD-N` and `VSTD-Graph-N` select cumulative coordinate requirements. Matching object and Graph numbers do not identify the same coordinate. |
+| Layer and level | [Abstraction layer](https://en.wikipedia.org/wiki/Abstraction_layer "Wikipedia orientation; not a VSTD authority") | VSTD reserves layer for actual implementation, protocol, or physical stacks and level for named external taxonomies or compatibility identifiers. Neither word is a synonym for a numbered VSTD profile. |
+| Defense in depth | [Defense in depth](https://en.wikipedia.org/wiki/Defense_in_depth_%28computing%29 "Wikipedia orientation; primary references are mapped below") | Multiple independent controls limit the effect of one control failing. VSTD applies the pattern to distinct closure coordinates without claiming that their profile numbers are assurance levels. |
+| Fail-closed decisions | [Fail-safe](https://en.wikipedia.org/wiki/Fail-safe "Wikipedia orientation; not a VSTD authority") | Missing or exhausted evidence stays `UNKNOWN`, `INDETERMINATE`, or `UNSUPPORTED`; it does not become a pass. |
+| Trusted computing base | [Trusted computing base](https://en.wikipedia.org/wiki/Trusted_computing_base "Wikipedia orientation; not a VSTD authority") | Every result must expose the mechanism and trust roots on which it depends. |
+| Zero trust | [Zero trust architecture](https://en.wikipedia.org/wiki/Zero_trust_architecture "Wikipedia orientation; not a VSTD authority") | VSTD borrows no product architecture wholesale; it uses explicit verification rather than identity or location as an automatic correctness signal. |
+| Canonicalization | [Canonicalization](https://en.wikipedia.org/wiki/Canonicalization "Wikipedia orientation; not a VSTD authority") | Stable fields need one declared byte representation before hashing. VSTD's formats are not thereby RFC 8785 implementations. Public API: `compute_canonical_digest` ([reference](reference.html#api-compute_canonical_digest)). |
+| Content addressing | [Content-addressable storage](https://en.wikipedia.org/wiki/Content-addressable_storage "Wikipedia orientation; not a VSTD authority") | Artifact and receipt coordinates bind declared bytes through digests; a digest alone does not establish origin or truth. |
+| Cryptographic digest | [Cryptographic hash function](https://en.wikipedia.org/wiki/Cryptographic_hash_function "Wikipedia orientation; not a VSTD authority") | Hash observations can establish byte identity within an algorithm and observation boundary, not semantic correctness. |
+| Provenance | [Data provenance](https://en.wikipedia.org/wiki/Data_provenance "Wikipedia orientation; not a VSTD authority") | VSTD-Graph records declared entities, transformations, and ancestry while preserving incomplete or unauthenticated history as such. |
+| Hypergraph | [Hypergraph](https://en.wikipedia.org/wiki/Hypergraph "Wikipedia orientation; not a VSTD authority") | N-ary transformation edges preserve many-input and many-output structure without flattening it into ambiguous binary links. |
+| Attestation | [Attestation](https://en.wikipedia.org/wiki/Attestation "Wikipedia orientation; not a VSTD authority") | VSTD-3 records who or what supplied evidence, the mechanism used, and the resulting evidence ceiling. |
+| Trust root | [Trust anchor](https://en.wikipedia.org/wiki/Trust_anchor "Wikipedia orientation; not a VSTD authority") | A declared root is an explicit dependency and stopping boundary, not evidence that the root is honest. |
+| Reproducibility | [Reproducibility](https://en.wikipedia.org/wiki/Reproducibility "Wikipedia orientation; not a VSTD authority") | VSTD binds the exact mechanism, inputs, environment, and equivalence relation required by the claim rather than treating the word as self-defining. Public API: `ReproducibilityLevel` ([reference](reference.html#api-ReproducibilityLevel)). |
+| Reproducible build | [Reproducible builds](https://en.wikipedia.org/wiki/Reproducible_builds "Wikipedia orientation; not a VSTD authority") | Recreating identical artifacts is an important special case of portable checking, not a proof of every property of the artifact or of distinct actors. |
+| Falsifiability | [Falsifiability](https://en.wikipedia.org/wiki/Falsifiability "Wikipedia orientation; not a VSTD authority") | VSTD-4 requires an explicit, bounded way for an outside checker to refute the exact claim. It does not turn Popper's philosophy into a software theorem. |
+| Proof-carrying artifact | [Proof-carrying code](https://en.wikipedia.org/wiki/Proof-carrying_code "Wikipedia orientation; not a VSTD authority") | The engineering precedent is that an untrusted producer can ship a result with a smaller consumer-checkable certificate under a declared policy. Public API: `DecisionCertificate` ([reference](reference.html#api-DecisionCertificate)). |
+| SAT | [Boolean satisfiability problem](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem "Wikipedia orientation; not a VSTD authority") | The reference subset encodes finite admission questions; SAT success establishes only the encoded formula. |
+| CNF | [Conjunctive normal form](https://en.wikipedia.org/wiki/Conjunctive_normal_form "Wikipedia orientation; not a VSTD authority") | VSTD's bounded policy encodings use finite CNF and do not equate arbitrary CNF with 3-SAT. |
+| Resolution | [Resolution](https://en.wikipedia.org/wiki/Resolution_%28logic%29 "Wikipedia orientation; not a VSTD authority") | Clausal refutations provide checkable evidence for an unsatisfiable result within the implemented proof format. |
+| Unit propagation | [Unit propagation](https://en.wikipedia.org/wiki/Unit_propagation "Wikipedia orientation; not a VSTD authority") | The minimal trusted checker validates the supported reverse-unit-propagation certificate path rather than trusting the producer's solver. |
+| Three-valued result | [Three-valued logic](https://en.wikipedia.org/wiki/Three-valued_logic "Wikipedia orientation; not a VSTD authority") | `UNKNOWN` is a first-class refusal to overstate, not a Boolean false and never a pass. VSTD's statuses are not claimed to implement one historical three-valued logic. |
+| Append-only transparency | [Certificate Transparency](https://en.wikipedia.org/wiki/Certificate_Transparency "Wikipedia orientation; not a VSTD authority") | Immutable receipts and additive corrections share an auditability goal with append-only logs; VSTD is not a Certificate Transparency implementation. |
+| Update freshness | [The Update Framework](https://en.wikipedia.org/wiki/The_Update_Framework "Wikipedia orientation; not a VSTD authority") | Staleness, rollback, revocation, and key compromise are separate from content integrity and require explicit current-state evidence. |
+| Semantic versioning | [Semantic Versioning](https://en.wikipedia.org/wiki/Software_versioning#Semantic_versioning "Wikipedia orientation; not a VSTD authority") | Repository releases use semantic versions independently of the VSTD object and Graph profile numbers. |
+| Object language and metalanguage | [Metalogic](https://en.wikipedia.org/wiki/Metalogic "Wikipedia orientation; not a VSTD authority") | VSTD uses this only as a design analogy for examining a verification surface; it does not claim that every adjacent profile is a formal metalanguage. |
+| Undefinability of truth | [Tarski's undefinability theorem](https://en.wikipedia.org/wiki/Tarski%27s_undefinability_theorem "Wikipedia orientation; not a VSTD authority") | The verification complex expressly does not derive its architecture or observational limits from Tarski's theorem. |
+
+## Primary reference map
+
+| VSTD design seam | Primary or official reference | Relevant precedent and explicit limit |
+|---|---|---|
+| Separate failure controls and fail-safe defaults | Saltzer and Schroeder, [*The Protection of Information in Computer Systems*](https://web.mit.edu/Saltzer/www/publications/pubs.html) (1975) | Classic security-design principles include fail-safe defaults, complete mediation, separation of privilege, least privilege, and least common mechanism. They motivate separating failure surfaces; they do not derive VSTD's five coordinates on either axis. |
+| Security-assurance components and packages | Common Criteria, [Part 3: Security assurance components](https://www.commoncriteriaportal.org/files/ccfiles/CC2022PART3R1.pdf) (CC:2022 Revision 1) | Established precedent for decomposing assurance into named components and packages. VSTD is not Common Criteria, accredited evaluation, or an Evaluation Assurance Level. |
+| Canonical JSON as cryptographic input | IETF Independent Stream, [RFC 8785: JSON Canonicalization Scheme](https://www.rfc-editor.org/rfc/rfc8785.html) | Shows why cryptographic operations over JSON require invariant representation. VSTD uses its own declared canonicalization rules and must not claim RFC 8785 conformance unless a format actually implements it. |
+| Provenance entities, activities, and agents | W3C, [PROV-DM: The PROV Data Model](https://www.w3.org/TR/prov-dm/) | Standardized vocabulary and constraints for interoperable provenance. VSTD-Graph's artifact and transformation model is adjacent, not a PROV implementation or complete history claim. |
+| Supply-chain step and artifact attestations | [in-toto specification v1.0](https://in-toto.io/docs/specs/) and [SLSA v1.2](https://slsa.dev/spec/v1.2/) | Established formats and levels for materials, products, builders, steps, and provenance. VSTD may bind their outputs as evidence but does not manufacture their authorization or assurance level. |
+| Release preservation and provenance integrity | NIST, [Special Publication (SP) 800-218: Secure Software Development Framework 1.1](https://doi.org/10.6028/NIST.SP.800-218) | Protect the Software practices PS.3.1 and PS.3.2 cover archiving releases, maintaining provenance, protecting its integrity, and enabling recipient verification. This is operational precedent, not VSTD certification. |
+| Independent recreation of artifacts | Reproducible Builds, [formal definition](https://reproducible-builds.org/docs/definition/) | Defines the source, environment, instruction, and artifact relationship needed for bit-for-bit recreation. VSTD permits other explicitly declared equivalence relations and does not infer truth from reproducibility alone. |
+| Producer-supplied, consumer-checked certificates | Necula, [*Proof-Carrying Code*](https://doi.org/10.1145/263699.263712) (POPL 1997) | Primary precedent for an untrusted producer supplying a proof checked under a defined policy by the consumer. VSTD generalizes the receipt pattern but does not inherit PCC's safety theorem. |
+| Checkable SAT refutations | Wetzler, Heule, and Hunt, [*DRAT-trim: Efficient Checking and Trimming Using Expressive Clausal Proofs*](https://www.cs.cmu.edu/~mheule/publications/drat-trim.pdf) (2014) | Demonstrates checking unsatisfiability proofs outside the solver rather than trusting its answer. VSTD's implemented certificate is a narrower declared RUP path, not arbitrary DRAT. |
+| Explicit indeterminate solver results | [SMT-LIB Standard 2.7](https://smt-lib.org/papers/smt-lib-reference-v2.7-r2025-04-09.pdf) | The standard response grammar includes `sat`, `unsat`, and `unknown`. VSTD's richer status vocabulary is independently defined, but the refusal to fabricate a Boolean answer has established solver precedent. |
+| Append-only evidence and independently detectable equivocation | IETF, [RFC 9162: Certificate Transparency Version 2.0](https://www.rfc-editor.org/rfc/rfc9162.html) | Merkle inclusion and consistency proofs support auditing an append-only log, while the RFC also names split-view limitations. VSTD's additive history is analogous but not a CT log. |
+| Freshness, rollback, freeze, and key-compromise boundaries | [The Update Framework specification](https://theupdateframework.github.io/specification/latest/) | Separates current-version metadata, expiration, delegated roles, and compromise recovery from artifact bytes. VSTD does not implement TUF, but shares the requirement that old authentic data is not automatically current data. |
+
+## How to cite these precedents
+
+Use language such as:
+
+- "VSTD's portable-certificate design is adjacent to proof-carrying code."
+- "VSTD-Graph overlaps W3C PROV, in-toto, and SLSA at the provenance boundary."
+- "The refusal to convert resource exhaustion into a false result has precedent in the
+ `unknown` response of SMT-LIB."
+
+Do not write:
+
+- "Saltzer and Schroeder prove the VSTD verification complex."
+- "VSTD implements PROV, SLSA, in-toto, TUF, Common Criteria, or Certificate
+ Transparency," unless separately demonstrated by a named conformance mechanism.
+- "These citations establish VSTD's security, completeness, adoption, or novelty."
+
+The point of the map is traceable intellectual context: which established problem a VSTD
+rule resembles, where the design deliberately differs, and what remains original project
+architecture rather than inherited authority.
diff --git a/docs/ECOSYSTEM.md b/docs/ECOSYSTEM.md
index 62ef450..861d7e9 100644
--- a/docs/ECOSYSTEM.md
+++ b/docs/ECOSYSTEM.md
@@ -1,14 +1,29 @@
# Ecosystem boundary map
+> **Acronyms:** Concise Binary Object Representation (CBOR); CBOR Object Signing and Encryption (COSE);
+> Internet Engineering Task Force (IETF); World Wide Web Consortium provenance vocabulary (PROV);
+> Request for Comments (RFC); Supply Chain Integrity, Transparency, and Trust (SCITT);
+> Supply-chain Levels for Software Artifacts (SLSA); verifiable data structure (VDS); Verifier Standard (VSTD);
+> World Wide Web Consortium (W3C).
+
+> Reader aid: [concept glossary and primary precedents](CONCEPTS_AND_PRECEDENTS.md).
+
**Status:** non-normative positioning note
-**Reviewed:** 2026-08-22
+**Reviewed:** 2026-08-23
VSTD is designed to compose with established provenance, software-supply-chain, and
artifact-authentication systems. It does not rename their guarantees as its own and
does not claim to replace them.
+VSTD supplies a common operator language for claim coordinates, evidence references,
+bounds, native outcomes, assumptions, and degradation rules. Native verifiers retain
+their own semantics and authority. A loss-declared adapter maps between those roles; it
+does not transfer authority to VSTD, strengthen a native result, or require consumers to
+adopt the producer's private orchestration logic.
+
| System | Its documented center of gravity | What VSTD may bind or add | What VSTD must not claim |
|---|---|---|---|
+| [IETF SCITT RFC 9943](https://datatracker.ietf.org/doc/html/rfc9943) and [COSE Receipts RFC 9942](https://datatracker.ietf.org/doc/html/rfc9942) | Signed Statements, registration policy, append-only/non-equivocating transparency services, and portable VDS receipts. | Carry a complete VSTD receipt as an application payload; consume native-verified registration/inclusion as narrowly typed transparency evidence. See the [experimental crosswalk](standards/VSTD_SCITT_CROSSWALK.md). | That registration establishes computational truth, distinct actors, or that VSTD replaces COSE, a Transparency Service, VDS proof profiles, or SCITT trust policy. |
| [SLSA v1.2](https://slsa.dev/spec/v1.2/) | Levels and tracks for incrementally improving software supply-chain security, including recommended provenance and verification-summary formats. | A SLSA statement or verification summary as evidence under an explicit VSTD claim coordinate; separate refutation and degradation conditions. | That a VSTD receipt establishes a SLSA level without satisfying and assessing the relevant SLSA requirements. |
| [in-toto](https://in-toto.io/docs/getting-started/) | Signed layouts and link metadata describing authorized supply-chain steps, functionaries, materials, and products. | in-toto layout/link bytes as named evidence; graph edges that point to checked step metadata. | That VSTD re-authorizes a functionary or repairs a missing/invalid in-toto chain. |
| [Sigstore](https://docs.sigstore.dev/) | Artifact signing associated with identity, short-lived certificates, and transparency-log evidence. | Sigstore bundle, certificate identity, trust root, and verification result as explicit evidence and trust-root fields. | That a digest alone authenticates a signer, or that VSTD reference-kernel acceptance substitutes for signature and transparency-log verification. |
@@ -23,20 +38,28 @@ that result and which information remains outside the mapping.
```text
native object ──native verifier──> native result
│ │
- └──── preserved bytes + identity ──┴──> VSTD evidence reference
+ └──── preserved bytes + identity ──┴──> loss-declared adapter
│
- └── bounded VSTD claim
+ ▼
+ VSTD claim boundary + portable result
+ │
+ ▼
+ another verifier, framework, or relying party
```
The VSTD claim does not flow backward and strengthen the native result. If the native
verifier returns an unknown, unsupported, expired, or invalid outcome, the adapter must
preserve it rather than translating it into a clean VSTD result.
+Mapping through VSTD is not automatic semantic equivalence. Every adapter must state
+what was preserved, what was omitted, what was transformed, and which native
+assumptions remain authoritative.
+
## Adapter acceptance checklist
An ecosystem adapter is not ready until it declares and tests:
-1. exact accepted upstream versions and wire identifiers;
+1. exact accepted upstream versions and serialized receipt identifiers;
2. canonical bytes and digest rules;
3. upstream verification mechanism and trust roots;
4. field-by-field mapping, including information loss;
@@ -47,3 +70,7 @@ An ecosystem adapter is not ready until it declares and tests:
No adapter is included merely to populate a compatibility list. Each adapter increases
the trusted and maintained surface and therefore needs its own evidence and tests.
+
+The current SCITT adapter is explicitly experimental and non-normative. Its exact
+claim boundary is documented in
+[`standards/SCITT_SEMANTIC_BOUNDARY.md`](standards/SCITT_SEMANTIC_BOUNDARY.md).
diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md
index a77c09a..b003dba 100644
--- a/docs/QUICKSTART.md
+++ b/docs/QUICKSTART.md
@@ -1,4 +1,6 @@
-# VSTD quickstart
+# Verifier Standard (VSTD) quickstart
+
+> Reader aid: [concept glossary and primary precedents](CONCEPTS_AND_PRECEDENTS.md).
## 1. Install the public source
@@ -14,6 +16,10 @@ python -m pip install .
Use `vstd` as the cross-platform command. The `verifier` compatibility alias can be
shadowed by Windows Driver Verifier.
+Before evaluating a broader claim, review the canonical
+[implementation-maturity table](../README.md#current-maturity). It separates implemented
+checks from candidate calculations and unimplemented mechanisms.
+
## 2. Run the adversarial demo
```bash
@@ -55,9 +61,11 @@ vstd validate /tmp/vstd-receipt
vstd inspect /tmp/vstd-receipt
```
-This establishes that the receipt is structurally valid and that its stable recorded
-content agrees with the declared artifacts. It does not establish that the claim is
-empirically true beyond that observation surface.
+`validate` applies the bundled profile's structural checks and recomputes the receipt's
+stable-payload digest. It does not invoke an external JavaScript Object Notation (JSON)
+Schema engine, rehash the
+declared artifacts, verify external evidence, or establish that the claim is true. Use
+`reproduce` for the separately bounded artifact comparison.
## 5. Exercise the falsification route
@@ -72,7 +80,7 @@ silently converted into success.
## 6. Read the normative path
-1. [`standard/LADDER.md`](../standard/LADDER.md) — numbering, independent evidence,
+1. [`standard/LADDER.md`](../standard/LADDER.md) — verification-complex terminology, numbered profiles, separate evidence per closure coordinate,
and composition.
2. [`standard/VSTD-4.md`](../standard/VSTD-4.md) — refutability and the grounded
decision certificate.
@@ -81,4 +89,7 @@ silently converted into success.
To evaluate the project rather than merely run it, start by trying to create a receipt
that passes outside its declared coordinate. A reproducible counterexample is more
-valuable than a general endorsement.
+valuable than a general endorsement. Report a
+[specification ambiguity](https://github.com/TimeLordRaps/verifier/issues/new?template=specification-ambiguity.yml),
+[counterexample](https://github.com/TimeLordRaps/verifier/issues/new?template=counterexample.yml),
+or [security issue](../SECURITY.md) through its designated route.
diff --git a/docs/REALMS_AND_TIME_CAPSULES.md b/docs/REALMS_AND_TIME_CAPSULES.md
new file mode 100644
index 0000000..d567200
--- /dev/null
+++ b/docs/REALMS_AND_TIME_CAPSULES.md
@@ -0,0 +1,139 @@
+# Realms, temporal structures, and time capsules
+
+> **Acronyms:** directed acyclic graph (DAG); Verifier Standard (VSTD).
+
+**Status:** architectural model for VSTD 1.2; no public realm or time-capsule receipt
+format and no complete inference-law verifier are defined here.
+
+This model separates structural artifact closure from propositions about time, execution,
+or physical law. Multiple temporal structures may coexist in one declared reality, called
+a **realm**. A seal can bind the realm description and its evidence; it does not make the
+description true.
+
+## 1. A realm carries temporal domains, not one overloaded time field
+
+A realm may declare any combination of:
+
+| Domain | Carrier and relation | Optional structure |
+|---|---|---|
+| Continuous time | instants or intervals ordered over a metric domain | duration, topology, continuity |
+| Discrete step time | states or steps with an order | successor, ticks, bounded gaps |
+| Event or causal order | events under a partial order | concurrency, branching |
+| Problem-space order | clauses, obligations, or solutions under dependency | valid linearizations, equivalence |
+| Branching possibilities | histories or states under reachability | forks, joins, alternatives |
+| Cyclic transition time | states under a transition relation | loops, backtracking, recurrence |
+| Atemporal structure | no internal temporal carrier | structural closure only |
+
+Each declared temporal domain identifies its carrier, ordering relation, optional successor,
+optional duration or metric, branching/cyclic/partial-order behavior, observation mechanism,
+bounds, and unresolved coordinates. Cross-domain mappings are explicit and may be partial,
+many-to-many, or information-losing.
+
+For example, one token step may map to a wall-clock interval, several hardware-kernel
+events, one decoder-state transition, and several proof dependencies. Discrete observations
+at the endpoints do not establish what occurred continuously between them. That proposition
+requires a continuity mechanism covering the gap.
+
+## 2. Trace order is not dependency order
+
+If two clauses independently support a third, more than one total execution sequence may
+respect the same dependency partial order. VSTD should distinguish:
+
+- **trace identity:** the exact same recorded sequence;
+- **topological equivalence:** different sequences respecting the same dependencies;
+- **solution equivalence:** different valid derivations reaching an equivalent solution;
+- **evidence equivalence:** different executions producing certificates equivalent under a
+ named checker.
+
+A solver trace is one linearization; it is not the governing dependency structure. A
+search with loops or backtracking is not globally a DAG. Its transition system retains
+the internal cycles. A verifier may collapse strongly connected regions and topologically
+order the resulting condensation graph without pretending the cycles disappeared.
+
+## 3. Structural seals and temporal capsules
+
+The artifact-control seal in [`standard/ARTIFACT_CONTROL.md`](../standard/ARTIFACT_CONTROL.md)
+establishes finite structural closure. It makes no internal time proposition. A
+**time capsule** is the composition:
+
+```text
+preserved artifact
+ + verified self-closing seal
+ + sealed realm descriptor
+ + temporal-closure policy
+ + transition, checkpoint, or continuity evidence
+```
+
+An atemporal capsule can establish that an artifact was structurally closed under the
+seal mechanism. Atemporal does not mean eternal: a verifier in another realm may later
+apply time-indexed ROT because a key was revoked, evidence became stale, or a dependency
+changed.
+
+A temporal capsule adds one exact proposition, for example:
+
+> Closure of artifact A was continuously mediated over interval I in temporal domain T by
+> mechanism M.
+
+A topological capsule can instead establish that every recorded transition respects a
+declared dependency relation independent of wall-clock order. Both propositions may coexist.
+Neither follows from a signature at two endpoints.
+
+Cross-realm interoperability is earned only when a named verifier checks the declared
+mapping between realm structures. Missing mapping evidence remains `UNKNOWN` or
+`UNSUPPORTED`; a seal cannot fill it.
+
+## 4. Autoregressive language-model generation
+
+One generation can occupy several domains simultaneously:
+
+| Surface | Temporal structure |
+|---|---|
+| Token emission | discrete total order within one accepted sequence |
+| Prefix dependency | each accepted next token depends on the accepted prefix |
+| Decoder state | discrete state transitions |
+| Attention and cache dependencies | directed dependency graph |
+| Batched hardware execution | partially ordered events |
+| Physical execution | continuous wall-clock intervals |
+| Tool calls and revisions | branching event history |
+| Reasoning or problem dependencies | partial order that may differ from emitted-token order |
+
+A future transition verifier could bind the prior state, model and weight identity,
+tokenizer, prefix commitment, attention/cache commitment, constraint state, logits
+commitment, sampler, random state, selected token, and next state:
+
+```text
+VerifyTransition(state_n, token_n+1, state_n+1)
+ -> PASS | FAIL | UNKNOWN
+```
+
+A complete generation would be a checked chain or graph of such transitions. The law
+families are distinct:
+
+- **model-realm law:** the transition follows the declared model, tokenizer, cache,
+ decoding, and constraints;
+- **problem-realm law:** the derivation respects declared proof rules, schemas, clause
+ dependencies, or domain invariants;
+- **substrate-realm law:** evidence binds the transition to the declared runtime and
+ machine substrate; and
+- **cross-realm law:** a checked mapping connects the logical transition, problem
+ derivation, and substrate execution.
+
+Passing any such law establishes only its bounded execution proposition. It does not
+establish that generated text is true. Textual truth still requires proposition-specific
+evidence and verifiers.
+
+## 5. Placement on the VSTD axes
+
+This is an architectural allocation, not a new serialized profile:
+
+- **VSTD-1** records individual operations, transitions, and executions.
+- **VSTD-2** describes the selected temporal/problem geometry and cross-domain mappings.
+- **VSTD-3** anchors observations to runtime and physical substrate.
+- **VSTD-4** exposes violations of transition, continuity, or mapping laws.
+- **VSTD-5** may corroborate those bounded results through evidenced independent witnesses.
+- **VSTD-Graph** represents the complete multi-temporal topology and retained conflicts.
+
+Current VSTD 1.2 artifact control can seal an independently serialized realm descriptor
+as a generic `bound_contexts` artifact. It does not define the descriptor's schema, check
+cross-domain mappings, establish continuous closure, or verify language-model transitions.
+Those remain explicit future mechanism work rather than inferred capability.
diff --git a/docs/assets/orientation-previews.js b/docs/assets/orientation-previews.js
new file mode 100644
index 0000000..49998a3
--- /dev/null
+++ b/docs/assets/orientation-previews.js
@@ -0,0 +1,107 @@
+(() => {
+ "use strict";
+
+ const links = [...document.querySelectorAll('a[data-orientation-preview="repository"]')];
+ if (!links.length) return;
+
+ const card = document.createElement("aside");
+ card.id = "orientation-preview";
+ card.className = "orientation-preview";
+ card.hidden = true;
+ card.setAttribute("role", "tooltip");
+ document.body.append(card);
+
+ let activeLink = null;
+ let timer = null;
+
+ const textElement = (tag, className, text) => {
+ const element = document.createElement(tag);
+ element.className = className;
+ element.textContent = text;
+ return element;
+ };
+
+ const position = (link) => {
+ if (card.hidden) return;
+ const rect = link.getBoundingClientRect();
+ const margin = 12;
+ const width = Math.min(420, window.innerWidth - margin * 2);
+ card.style.width = `${width}px`;
+ let left = Math.min(rect.left, window.innerWidth - width - margin);
+ left = Math.max(margin, left);
+ let top = rect.bottom + 10;
+ if (top + card.offsetHeight > window.innerHeight - margin) {
+ top = Math.max(margin, rect.top - card.offsetHeight - 10);
+ }
+ card.style.left = `${left}px`;
+ card.style.top = `${top}px`;
+ };
+
+ const render = (link) => {
+ card.replaceChildren();
+ card.append(
+ textElement(
+ "span",
+ "orientation-preview-eyebrow",
+ "Repository definition · versioned with VSTD",
+ ),
+ );
+ card.append(
+ textElement("strong", "orientation-preview-title", link.dataset.orientationConcept),
+ );
+ card.append(
+ textElement("p", "orientation-preview-body", link.dataset.orientationDefinition),
+ );
+ card.append(
+ textElement(
+ "small",
+ "orientation-preview-hint",
+ "The link opens optional external background; it is not VSTD authority.",
+ ),
+ );
+ requestAnimationFrame(() => position(link));
+ };
+
+ const show = (link) => {
+ clearTimeout(timer);
+ activeLink = link;
+ link.setAttribute("aria-describedby", card.id);
+ card.hidden = false;
+ render(link);
+ };
+
+ const hide = (link) => {
+ if (
+ activeLink !== link ||
+ link.matches(":hover") ||
+ document.activeElement === link
+ ) {
+ return;
+ }
+ link.removeAttribute("aria-describedby");
+ activeLink = null;
+ card.hidden = true;
+ };
+
+ for (const link of links) {
+ link.addEventListener("mouseenter", () => {
+ clearTimeout(timer);
+ timer = setTimeout(() => show(link), 250);
+ });
+ link.addEventListener("mouseleave", () => {
+ clearTimeout(timer);
+ timer = setTimeout(() => hide(link), 120);
+ });
+ link.addEventListener("focus", () => show(link));
+ link.addEventListener("blur", () => hide(link));
+ }
+
+ document.addEventListener("keydown", (event) => {
+ if (event.key === "Escape" && activeLink) {
+ const link = activeLink;
+ link.blur();
+ hide(link);
+ }
+ });
+ window.addEventListener("resize", () => activeLink && position(activeLink));
+})();
diff --git a/docs/assets/site.css b/docs/assets/site.css
index 68d667a..8aa8aff 100644
--- a/docs/assets/site.css
+++ b/docs/assets/site.css
@@ -27,6 +27,10 @@ body {
a { color: var(--teal); text-underline-offset: .2em; }
a:hover { color: #8be6d6; }
+a:focus-visible { outline: 3px solid var(--amber); outline-offset: 4px; border-radius: 3px; }
+
+.skip-link { position: fixed; left: 18px; top: -80px; z-index: 10; padding: 10px 14px; color: #06141f; background: var(--amber); font-weight: 800; }
+.skip-link:focus { top: 12px; }
.wrap { width: min(1120px, calc(100% - 36px)); margin: 0 auto; }
@@ -47,7 +51,13 @@ nav {
.eyebrow { color: var(--teal); font-size: .78rem; font-weight: 780; letter-spacing: .16em; text-transform: uppercase; }
h1 { max-width: 760px; margin: 12px 0 22px; font-size: clamp(2.8rem, 6vw, 5.6rem); line-height: .98; letter-spacing: -.055em; }
.lead { color: #c6d6da; font-size: clamp(1.12rem, 2vw, 1.34rem); max-width: 670px; }
+.lead-defs { margin: 18px 0 0; max-width: 670px; color: #94a9ae; font-size: 0.95rem; line-height: 1.55; border-left: 2px solid #24373c; padding-left: 16px; }
+.lead-defs dt { color: #c6d6da; font-weight: 600; letter-spacing: 0.01em; }
+.lead-defs dd { margin: 2px 0 12px; }
+.lead-defs dd:last-child { margin-bottom: 0; }
+.lead-close { margin-top: 18px; }
.actions { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 30px; }
+.hero-actions { margin: 26px 0 30px; }
.button { display: inline-flex; align-items: center; min-height: 46px; padding: 0 18px; border: 1px solid var(--line); border-radius: 9px; color: var(--ink); text-decoration: none; font-weight: 700; background: rgba(14, 39, 52, .72); }
.button.primary { color: #061a1b; background: var(--teal); border-color: var(--teal); }
.hero-card, .card, pre { border: 1px solid var(--line); border-radius: 15px; background: var(--panel); box-shadow: 0 22px 65px rgba(0, 0, 0, .22); }
@@ -71,20 +81,102 @@ pre { margin: 0; padding: 24px; color: #dcebed; font: 500 .92rem/1.75 ui-monospa
.boundary { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-top: 28px; }
.boundary ul { margin: 10px 0 0; padding-left: 20px; color: var(--muted); }
.status { border-left: 3px solid var(--amber); padding: 3px 0 3px 18px; color: #d6e2e5; max-width: 850px; }
+.release-coordinate { max-width: 850px; color: var(--muted); }
+
+.ref-hero { padding: 54px 0 10px; }
+.ref-hero h1 { font-size: clamp(2.4rem, 5vw, 4.2rem); margin-bottom: 18px; }
+.ref-hero .status { margin-top: 20px; }
+.ref-table, .ref-list { margin-top: 26px; }
+.ref-table { overflow-x: auto; }
+table { width: 100%; border-collapse: collapse; font-size: .93rem; }
+th, td { text-align: left; vertical-align: top; padding: 10px 14px; border-bottom: 1px solid var(--line); }
+th { color: var(--teal); font-size: .74rem; letter-spacing: .12em; text-transform: uppercase; }
+td { color: var(--muted); }
+td code, .ref-help code, .section-lead code, .lead code, li code, p code { color: #dcebed; background: rgba(9, 30, 41, .8); border: 1px solid var(--line); border-radius: 6px; padding: 1px 6px; font: 500 .86em/1.5 ui-monospace, SFMono-Regular, Consolas, monospace; }
+.ref-item { border: 1px solid var(--line); border-radius: 15px; background: var(--panel); padding: 20px 22px; margin-bottom: 14px; overflow-x: auto; }
+.ref-item h3 { margin: 0 0 8px; font-size: 1.05rem; }
+.ref-item h3 code { background: none; border: none; padding: 0; color: var(--ink); font-size: 1em; }
+.ref-tag { color: var(--amber); font-size: .7rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; }
+.ref-help, .ref-none, .ref-source { color: var(--muted); font-size: .93rem; margin: 0 0 12px; }
+.ref-source { font-size: .84rem; }
+.ref-signature { padding: 14px 16px; margin: 0 0 12px; font-size: .84rem; border-radius: 10px; overflow-x: auto; }
+.guide-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; margin-top: 28px; }
+.guide-card { border: 1px solid var(--line); border-radius: 15px; background: var(--panel); padding: 22px; }
+.guide-card h2 { font-size: 1.35rem; margin-bottom: 10px; }
+.guide-card ul { margin: 0; padding-left: 20px; }
+.guide-card li { margin: 8px 0; color: var(--muted); }
+.links a[aria-current="page"] { color: var(--ink); text-decoration: underline; text-decoration-color: var(--teal); text-underline-offset: .42em; }
+
+.doc-shell { display: grid; grid-template-columns: minmax(220px, 285px) minmax(0, 1fr); gap: clamp(34px, 6vw, 78px); align-items: start; padding-top: 44px; }
+.doc-sidebar { position: sticky; top: 18px; max-height: calc(100vh - 36px); overflow: auto; padding: 18px 18px 20px; border: 1px solid var(--line); border-radius: 14px; background: rgba(8, 28, 39, .92); scrollbar-color: var(--line) transparent; }
+.doc-sidebar section { padding: 0; }
+.doc-sidebar section + section { margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--line); }
+.doc-sidebar h2 { margin: 0 0 9px; color: var(--teal); font-size: .7rem; letter-spacing: .13em; text-transform: uppercase; }
+.doc-sidebar ul { margin: 0; padding: 0; list-style: none; }
+.doc-sidebar li { margin: 6px 0; line-height: 1.35; }
+.doc-sidebar a { display: block; padding: 4px 7px; border-radius: 6px; color: var(--muted); text-decoration: none; font-size: .81rem; }
+.doc-sidebar a:hover { color: var(--ink); background: rgba(85, 211, 189, .08); }
+.doc-sidebar a[aria-current="page"] { color: var(--ink); background: rgba(85, 211, 189, .13); box-shadow: inset 2px 0 0 var(--teal); }
+.doc-sidebar .toc-depth-3 a { padding-left: 17px; font-size: .76rem; }
+
+.doc-content { min-width: 0; max-width: 820px; }
+.doc-coordinate { display: flex; justify-content: space-between; gap: 20px; align-items: center; margin-bottom: 12px; color: var(--teal); font-size: .74rem; font-weight: 760; letter-spacing: .1em; text-transform: uppercase; }
+.doc-coordinate a { color: var(--muted); letter-spacing: normal; text-transform: none; }
+.doc-boundary { margin: 0 0 38px; padding: 13px 16px; border-left: 3px solid var(--amber); color: var(--muted); background: rgba(242, 191, 104, .055); font-size: .88rem; }
+.doc-content h1 { max-width: none; margin: 0 0 20px; font-size: clamp(2.35rem, 5vw, 4rem); line-height: 1.04; letter-spacing: -.045em; }
+.doc-content h2 { margin: 54px 0 14px; padding-top: 8px; font-size: clamp(1.65rem, 3vw, 2.25rem); }
+.doc-content h3 { margin: 36px 0 12px; font-size: 1.35rem; }
+.doc-content h4 { margin: 28px 0 10px; font-size: 1.08rem; }
+.doc-content h5, .doc-content h6 { margin: 24px 0 8px; color: var(--teal); font-size: .94rem; letter-spacing: .03em; }
+.doc-content p, .doc-content li { color: #bdcdd1; }
+.doc-content p { margin: 13px 0; }
+.doc-content ul, .doc-content ol { padding-left: 25px; }
+.doc-content li { margin: 7px 0; }
+.doc-content blockquote { margin: 22px 0; padding: 3px 20px; border-left: 3px solid var(--teal); background: rgba(85, 211, 189, .055); }
+.doc-content blockquote p { color: #d5e3e5; }
+.doc-content hr { height: 1px; margin: 44px 0; border: 0; background: var(--line); }
+.doc-content pre { margin: 20px 0; overflow: auto; box-shadow: none; }
+.doc-content .doc-table { margin: 22px 0; overflow-x: auto; border: 1px solid var(--line); border-radius: 11px; }
+.doc-content .doc-table table { min-width: 560px; }
+.doc-content .doc-table tr:last-child td { border-bottom: 0; }
+.doc-content .doc-image { padding: 18px; border: 1px solid var(--line); border-radius: 14px; background: var(--panel); }
+.doc-content .doc-image img, .doc-content p > img { display: block; max-width: 100%; height: auto; margin: 0 auto; }
+.math, .math-block { color: #dcebed; font-family: "Cambria Math", "STIX Two Math", ui-monospace, Consolas, monospace; }
+.math { white-space: nowrap; }
+.math-block { margin: 22px 0; padding: 16px 20px; overflow-x: auto; border: 1px solid var(--line); border-radius: 10px; background: rgba(9, 30, 41, .8); text-align: center; }
+.heading-anchor { margin-left: 9px; color: transparent; text-decoration: none; font-size: .65em; }
+.doc-content h1:hover .heading-anchor, .doc-content h2:hover .heading-anchor, .doc-content h3:hover .heading-anchor, .heading-anchor:focus { color: var(--muted); }
+.orientation-link { text-decoration-style: dotted; text-underline-offset: .18em; }
+.orientation-link::after { content: " ?"; color: var(--teal); font-size: .7em; font-weight: 800; vertical-align: super; }
+.orientation-preview { position: fixed; z-index: 100; width: min(420px, calc(100vw - 24px)); padding: 16px 18px; border: 1px solid var(--teal); border-radius: 13px; background: #081c27; box-shadow: 0 18px 50px rgba(0, 0, 0, .42); color: var(--ink); pointer-events: none; }
+.orientation-preview[hidden] { display: none; }
+.orientation-preview-eyebrow { display: block; margin-bottom: 7px; color: var(--teal); font-size: .66rem; font-weight: 800; letter-spacing: .11em; text-transform: uppercase; }
+.orientation-preview-title { display: block; margin-bottom: 8px; color: var(--ink); font-size: 1.02rem; }
+.orientation-preview-body { margin: 0; color: #c8d7da; font-size: .86rem; line-height: 1.5; }
+.orientation-preview-hint { display: block; margin-top: 9px; color: var(--muted); font-size: .72rem; }
footer { border-top: 1px solid var(--line); margin-top: 54px; padding: 28px 0 44px; color: var(--muted); font-size: .9rem; }
@media (max-width: 900px) {
.hero { grid-template-columns: 1fr; padding-top: 40px; }
.grid { grid-template-columns: 1fr 1fr; }
+ .doc-shell { grid-template-columns: 1fr; }
+ .doc-sidebar { position: static; max-height: 420px; }
}
@media (max-width: 620px) {
nav { align-items: flex-start; flex-wrap: wrap; }
.links { width: 100%; justify-content: flex-start; gap: 14px; }
- .grid, .boundary { grid-template-columns: 1fr; }
+ .grid, .boundary, .guide-grid { grid-template-columns: 1fr; }
h1 { font-size: 3.1rem; }
.eyebrow { font-size: .7rem; overflow-wrap: anywhere; }
.actions { display: grid; grid-template-columns: 1fr; }
.button { width: 100%; justify-content: center; }
+ .doc-sidebar { max-height: 280px; }
+ .doc-coordinate { align-items: flex-start; flex-direction: column; gap: 5px; }
+ .orientation-preview { right: 12px !important; bottom: 12px; left: 12px !important; top: auto !important; width: auto; }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ html { scroll-behavior: auto; }
}
diff --git a/docs/assets/vstd-overview.png b/docs/assets/vstd-overview.png
index bfe77e7..a2a817c 100644
Binary files a/docs/assets/vstd-overview.png and b/docs/assets/vstd-overview.png differ
diff --git a/docs/assets/vstd-overview.svg b/docs/assets/vstd-overview.svg
index d00dca2..3d35c84 100644
--- a/docs/assets/vstd-overview.svg
+++ b/docs/assets/vstd-overview.svg
@@ -1,6 +1,6 @@
- VSTD two-axis verification overview
- Five object-mechanics layers and five collection-dynamics layers. Every layer requires separate evidence; higher layers never substitute for lower layers.
+ Verifier Standard (VSTD) two-axis verification overview
+ Five cumulative object profiles and five cumulative Graph profiles over distinct closure coordinates. Every coordinate requires separate evidence; later profiles never substitute for prerequisites.
@@ -28,7 +28,7 @@
portable · bounded · refutable
- FOUNDER-MAINTAINED ALPHA
+ ALPHA PROJECT SPECIFICATION
OBJECT MECHANICS
COLLECTION DYNAMICS
@@ -67,14 +67,14 @@
Witness corroboration Corroborated verification network
- REF. SUBSET REF. SUBSET
- EXPERIMENTAL IMPLEMENTED
- IMPLEMENTED IMPLEMENTED
- IMPLEMENTED IMPLEMENTED
- DRAFT DRAFT
+ REF. SUBSET REF. SUBSET
+ EXPERIMENTAL REF. MECH.
+ IMPLEMENTED REF. MECH.
+ REF. MECH. REF. MECH.
+ REF. MECH. REF. MECH.
- Higher depth = more checked questions · never evidence substitution
+ Cumulative profiles = more checked coordinates · never evidence substitution
diff --git a/docs/guides.html b/docs/guides.html
new file mode 100644
index 0000000..f383672
--- /dev/null
+++ b/docs/guides.html
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+ VSTD guides and standards
+
+
+
+
+
+ Skip to content
+
+
+
+
+
Public documentation
+
Find the exact boundary.
+
This page links the maintained guides and specifications without
+ restating them. Normative requirements live under standard/; guides and
+ profiles must not silently strengthen those requirements.
+
Interoperability material below uses Supply Chain Integrity,
+ Transparency, and Trust (SCITT).
+
Documentation coordinate:
+ verifier-standard 1.2.0 unreleased candidate. The assembled site also
+ carries machine-readable build metadata ;
+ published tags and release
+ artifacts preserve historical coordinates. Normative ownership remains under
+ standard/.
+
+
+
+
+
+ Start here
+
+ Quickstart — install, inspect, capture, validate, and refute.
+ Current maturity — normative, implementation, evidence-binding, conformance, and missing-mechanism status.
+ Claims and limits — allowed wording and prohibited inference.
+ Concepts and precedents — terminology and intellectual lineage.
+ Acronyms and abbreviated terms — canonical repository expansion key.
+ Conformance architecture — normative, wire, schema, runtime, and test ownership.
+ Artifact freeze, seal, and thaw — exact-byte preservation, finite self-closing seals, external anchors, and copy-on-write descendants.
+ Realms and time capsules — discrete, continuous, causal, problem-space, branching, cyclic, and atemporal structures with explicit mappings.
+ Artifact-first reference mechanisms — governing zero-identity/zero-knowledge (ZIZK) boundaries; process-bound TRUST, ROT, and RUST semantics; bounded identity disclosure; and the recorded reduced instruction set computer (RISC) Zero proof.
+ command-line interface (CLI) and application programming interface (API) reference — generated from the importable package.
+ Python API stability — supported exports, versioning, and deprecation lifecycle.
+
+
+
+
+ Normative specifications
+
+
+
+
+ Profiles and interoperability
+
+
+
+
+ Project and contribution
+
+
+
+
+
+
+
+
diff --git a/docs/index.html b/docs/index.html
index 8c24c75..0151f66 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -3,22 +3,29 @@
-
+
+
VSTD — portable, bounded, refutable
+
+
+ Skip to content
VSTD
@@ -29,14 +36,27 @@
Verification language for computational work
Make the claim challengeable.
-
VSTD makes computational claims portable, bounded, and refutable—so a result travels with its meaning, evidence, limits, and failure route.
-
-
Run the four-scenario demo
-
Read the quickstart
+
VSTD packages bounded computational claims with their evidence,
+ checking mechanisms, limits, refutation conditions, provenance, and
+ reproducibility information. It does not replace native domain verifiers or
+ strengthen their results.
+
+
VSTD makes computational claims:
+
+ Portable:
+ checkable without post-verdict cooperation from the declarant because every verdict-critical byte is included or retrievable and digest-bound; a locator or retention promise alone does not qualify.
+ Bounded:
+ carrying their own claim coordinates and resource ceilings, so exhausted work remains UNKNOWN rather than being answered outside the checked boundary.
+ Refutable:
+ exposing falsification conditions, admissible counterevidence, exclusions, and decision rules so another party can challenge the exact result.
+
+
A result travels with its meaning, evidence, limits, and failure routes so downstream humans and agents can draw conclusions without silently widening it.
-
+
@@ -54,6 +74,19 @@ A green check is not a complete explanation.
+
+
+
Artifact-first semantics
+
Verify the process, not the actor.
+
VSTD evaluates bounded validity propositions about computational processes represented by software and evidence-bearing artifacts. Identity, popularity, and reputation alone add no verdict weight. TRUST, ROT, and RUST are formal semantic names, not acronyms or actor ratings; the reference Graph assurance log records them as evidence-bound events and can replay their embedded evidence and mechanisms offline. Architectural zero knowledge presumes no unevidenced proposition; cryptographic zero knowledge can enclose a confidential witness only through a named proof system bound to the exact program and predicate.
+
+
TRUST · FORWARD
Mechanism-earned support Exact artifact support moves edge by edge through a checked transformation bound to its inputs, output, Graph, and prerequisite support. It is never an actor rating.
+
ROT · CURRENT STATE
Admissibility degrades Typed lifecycle evidence can require reassessment without rewriting an immutable historical result. Age alone is insufficient.
+
RUST · BACKWARD
Diagnostic ancestry A checked descendant deviation traces over deduplicated recorded ancestors. BLAME requires localized responsibility; GUILT additionally requires an exact violated obligation. Neither follows from reachability alone.
+
+
+
+
Executable first impression
@@ -66,7 +99,7 @@
See it reject, preserve uncertainty, and degrade.
[DEMO OK] Valid-looking proof, wrong artifact → REJECTED
[DEMO OK] Bound exhausted without a false answer → ACCEPTED/UNKNOWN
[DEMO OK] Inflated verification-cost claim → REJECTED
-
[DEMO OK] Revoked ancestor behind valid descendants → GRAPH-LEVEL-0
+[DEMO OK] Revoked ancestor behind valid descendants → GRAPH-CANDIDATE-0
@@ -75,8 +108,8 @@ See it reject, preserve uncertainty, and degrade.
Boundaries
Useful without pretending to be total.
-
VSTD can help carry exact claims and evidence between systems; preserve PASS, FAIL, and UNKNOWN distinctly; trace poisoned ancestry and downstream impact; make challenge conditions machine-readable.
-
VSTD cannot establish general AI safety, alignment, or intent; hidden model state or unobserved tool context; complete physical-world execution history; truth outside the declared observation surface.
+
VSTD can help carry exact claims and evidence between systems; preserve PASS, FAIL, and UNKNOWN distinctly; query recorded ancestry and bounded downstream impact; make challenge conditions machine-readable.
+
VSTD cannot establish general artificial intelligence (AI) safety, alignment, or intent; hidden model state or unobserved tool context; complete physical-world execution history; truth outside the declared observation surface.
@@ -84,12 +117,17 @@ Useful without pretending to be total.
Current status
-
Built for adversarial review, not ceremonial adoption.
-
VSTD is a founder-maintained alpha project specification. It has no demonstrated external adoption, independent implementation, interoperability deployment, or third-party security review. VSTD-5 remains draft.
+
Current implementation status
+
VSTD is a maintainer-led alpha project specification. Compatibility VSTD-4 and Graph paths remain NOT_ESTABLISHED candidates. Separate evidence-bound VSTD-4, VSTD-5, Graph-profile, and assurance-event paths rerun exact registered mechanisms from embedded evidence; no real external witness is claimed by this repository. The project has no demonstrated external adoption, independent implementation, interoperability deployment, or third-party security review.
+
Artifact-control boundary: freeze preserves exact bytes and sealing is not encryption. A thaw sidecar alone is unkeyed metadata and remains NOT_ESTABLISHED; clean or dirty status requires the actual supplied parent to verify as sealed with every recorded parent coordinate matching. This current comparison does not authenticate the historical copy operation or external continuity.
+
Release coordinate: this branch
+ documents verifier-standard 1.2.0 as an unreleased candidate. Use
+ GitHub Releases for
+ the latest published artifact.
diff --git a/docs/profiles/competition-evaluation.md b/docs/profiles/competition-evaluation.md
index 44f63cd..960dd54 100644
--- a/docs/profiles/competition-evaluation.md
+++ b/docs/profiles/competition-evaluation.md
@@ -1,5 +1,9 @@
# Competition evaluation profile
+> **Acronyms:** artificial intelligence (AI); machine learning (ML); Verifier Standard (VSTD).
+
+> Reader aid: [concept glossary and primary precedents](../CONCEPTS_AND_PRECEDENTS.md).
+
**Status:** non-normative VSTD-1/VSTD-Graph integration profile
**Version:** 0.1
**Date:** 2026-08-21
@@ -9,6 +13,19 @@ scientific-ML, agent, and other scored evaluations. It does not add a new VSTD v
and does not claim adoption, affiliation, certification, or endorsement by any
conference, competition, benchmark, or organizer.
+The bounded public wording in
+[`docs/CLAIMS_AND_LIMITS.md`](../CLAIMS_AND_LIMITS.md#competition-and-scored-evaluation-claims)
+controls if a shorter phrase in this non-normative profile could be read more broadly.
+
+## VSTD-2 relationship
+
+Conceptually, this profile selects Verifier Standard (VSTD)-2 coordinates across the
+submission, evaluator, environment, score, and their seams. It does not emit a
+`VSTD-2` receipt or establish VSTD-2 conformance by itself. Each native scorer or
+benchmark adapter must attribute its output to the exact selected coordinates, preserve
+translation loss and horizons, and bind a separate assessment before any native result
+becomes a VSTD judgment.
+
## 1. Evaluation surface
An integration declares the exact surface before it reports a verified result:
@@ -43,7 +60,7 @@ rules + data snapshots + permitted externals
Each artifact receives a stable identifier and content digest. Each transformation
records its input and output roles, software identity, parameters, environment, and
evidence classification. A declaration is not relabeled as direct observation or
-independent reproduction.
+reproduction by a distinct actor.
## 3. Predictive-evaluation time boundary
@@ -67,7 +84,7 @@ artifact.
A participant normally cannot observe or serialize hidden tests. The participant
receipt therefore records an explicit horizon. An organizer can later close part of
that horizon by publishing a commitment, signed attestation, disclosed snapshot, or
-independently reproducible evaluator receipt.
+evaluator receipt reproducible by a distinct actor.
Absence of access is not evidence of hidden-test integrity. A participant-side
`VERIFIED` result MUST NOT imply that the organizer's hidden corpus was uncontaminated,
diff --git a/docs/profiles/experimental-workflow.md b/docs/profiles/experimental-workflow.md
new file mode 100644
index 0000000..9b8b4d4
--- /dev/null
+++ b/docs/profiles/experimental-workflow.md
@@ -0,0 +1,182 @@
+# Experimental workflow profile
+
+> **Acronyms:** application programming interface (API); American Standard Code for Information Interchange (ASCII);
+> command-line interface (CLI); JavaScript Object Notation (JSON); Boolean satisfiability problem (SAT);
+> Secure Hash Algorithm 256-bit (SHA-256); Unicode Transformation Format, 8-bit (UTF-8); Verifier Standard (VSTD).
+
+**Status:** experimental, non-normative VSTD-1/VSTD-Graph integration profile
+**Profile identifier:** `vstd.experimental-workflow`
+**Version:** `0.1`
+**Date:** 2026-08-24
+
+This profile gives experiments a portable record of **what question is being tested,
+what verification work was selected, why it was selected, how much work was allowed,
+what the native tools actually returned, and what remains unresolved**. It lets two
+workflow systems exchange the same experiment boundary without pretending that a GitHub
+merge, successful job, publication, or verifier exit code is automatically a VSTD
+`PASS`.
+
+The application profile is not a new numbered VSTD profile or verdict. It does not change
+any serialized receipt identifier, canonical digest, schema `$id`, conformance behavior, or normative VSTD
+semantics.
+
+## VSTD-2 relationship
+
+Conceptually, this profile is a reusable constraint over Verifier Standard (VSTD)-2
+verification geometry: the experiment is a subject; artifacts, actions, adapters, native
+tools, and verifier mechanisms can be loci; dependencies and mappings are seams; and
+horizons preserve uncovered coordinates. The profile does not emit a `VSTD-2` receipt,
+so it establishes no VSTD-2 conformance by itself. A mapped judgment requires a separate
+VSTD-2 surface and evidence-bearing assessment; profile identity or workflow completion
+does not transfer a verdict.
+
+## 1. The portable unit
+
+A profile manifest binds these surfaces:
+
+| Surface | Required meaning |
+|---|---|
+| `experiment` | Stable identifier, question, lifecycle state, and start boundary. |
+| `hypotheses` | Falsifiable statements. `SUPPORTED` remains evidence-bounded rather than universally true. |
+| `preregistration` | Whether a plan was absent, drafted, frozen, or later amended, plus the bound artifact when frozen. |
+| `artifacts` | Portable locators and lowercase SHA-256 digests. Local machine paths are prohibited. |
+| `budgets` | Integer resource limits and recorded consumption. Every selected action binds at least one budget. |
+| `actions` | The work selected, its priority, reason, alternatives, dependencies, trigger, substrate, and expected artifact effect. |
+| `observations` | What was observed, with evidence references and limitations. |
+| `interventions` | The declared change applied to bound artifacts and the artifacts it produced. |
+| `native_results` | The exact native verifier status and its artifact. A separate mapping field records whether VSTD evaluation occurred. |
+| `adaptations` | Which observations or challenges changed later actions or artifacts, and why. |
+| `amendments` | Additive corrections that name what they supersede; history is not overwritten. |
+| `challenges` | Open, resolved, or rejected attempts to refute a bound record. |
+| `horizons` | Explicit `UNKNOWN`, `CONFLICTED`, `BLOCKED`, or out-of-scope surfaces. |
+| `publication` | Distribution state only. Publication does not establish correctness or adoption. |
+| `workflow_events` | Platform observations whose `verification_effect` is always `NONE`. |
+| `manifest_digest` | SHA-256 over deterministic JSON for every other field. |
+
+The machine-readable shape is in
+[`experimental-workflow.schema.json`](experimental-workflow.schema.json). The
+standard-library validator is
+[`profile.py`](../../src/verifier/experimental_workflow/profile.py).
+
+## 2. Bounded verification allocation
+
+An action records:
+
+1. a target and verifier substrate;
+2. a positive integer priority;
+3. why this action was selected;
+4. evidence used for that selection;
+5. alternatives considered;
+6. an explicit resource budget and consumed amount;
+7. dependencies and observations that triggered it; and
+8. its expected effect on the artifact under construction.
+
+This makes allocation inspectable. It does **not** prove that the allocation was optimal,
+unbiased, safe, or the only reasonable allocation. Priority is a scheduling coordinate,
+not a truth coordinate. Exhausted work remains visible through the action state and
+horizons instead of being rewritten as success.
+
+The profile deliberately does not prescribe Bayesian inference, decision trees,
+boosted trees, control theory, embeddings, or any other selection engine. Those are
+orchestrated substrates. Their native outputs can be bound as selection evidence, while
+the portable fields above preserve the claim boundary between the allocation operator
+and the mechanism it orchestrates.
+
+## 3. Native result and VSTD mapping boundary
+
+Every native result has a `mapping` object:
+
+- `NOT_EVALUATED` requires the VSTD verdict, mapping profile, and receipt reference to
+ remain `null`.
+- `MAPPED` requires an explicit VSTD verdict, mapping profile, receipt artifact, and
+ reason.
+
+Recording `native_status = "PASS"`, `"SAT"`, `"proof verified"`, or any other tool
+vocabulary does not authorize `mapping.status = "MAPPED"`. The actual mapping and bound
+VSTD receipt are separate evidence. `UNKNOWN` and `CONFLICTED` remain distinct mapping
+outcomes and cannot be dropped because the surrounding workflow completed.
+
+## 4. GitHub adapter
+
+[`github.py`](../../src/verifier/experimental_workflow/github.py) consumes a strict,
+normalized snapshot rather than an unconstrained GitHub API response. It maps:
+
+| GitHub observation | Workflow event |
+|---|---|
+| issue state | `PLATFORM_ISSUE` |
+| commit identity | `PLATFORM_COMMIT` |
+| workflow run and conclusion | `PLATFORM_WORKFLOW_RUN` |
+| workflow artifact availability | `PLATFORM_ARTIFACT` |
+| pull-request and merge state | `PLATFORM_PULL_REQUEST` |
+
+Every emitted event sets `verification_effect = "NONE"`. In particular:
+
+- a successful Actions run is not a VSTD `PASS`;
+- a merge is an integration event, not verification;
+- an available artifact is not evidence that its bytes satisfy a claim; and
+- a closed issue is not evidence that the underlying defect was corrected.
+
+Unknown fields are rejected rather than guessed into the portable representation. A
+different workflow platform can implement the same event boundary without adopting
+GitHub identifiers.
+
+## 5. Canonicalization
+
+The manifest digest uses UTF-8 JSON with:
+
+- keys sorted recursively;
+- compact `,` and `:` separators;
+- ASCII escaping enabled;
+- no floating-point values; and
+- `manifest_digest` omitted from its own input.
+
+The stored value is `sha256:<64 lowercase hexadecimal characters>`. This digest binds
+the workflow record. It does not verify the bytes at an artifact locator; each artifact
+has its own digest for that check.
+
+## 6. Dogfooding and index
+
+Experimental manifests live below `experiments/` as `experiment.json`. The command
+
+```bash
+PYTHONPATH=src python scripts/build_experiment_index.py --check
+```
+
+validates every manifest and confirms that [`experiments/INDEX.md`](../../experiments/INDEX.md)
+is current. The first bound record is the deterministic GitHub verdict-neutrality
+specimen. A blocked experiment remains eligible for indexing once its intentional files
+are isolated and its manifest honestly records the blocker; indexing is not publication
+of a positive result.
+
+The runnable example under
+[`examples/experimental_workflow/`](../../examples/experimental_workflow/) demonstrates
+that a successful GitHub workflow and merged pull request remain verdict-neutral.
+
+The installed CLI exposes the same bounded surface:
+
+```bash
+vstd experiment validate experiments/github_verdict_neutrality/experiment.json --json
+vstd experiment github-events examples/experimental_workflow/github_snapshot.json --json
+```
+
+`validate` checks the strict profile shape and manifest digest. If a manifest contains
+`repo:` artifact locators, supply `--repo-root PATH`; otherwise the command returns exit
+code `2` and reports `VALID_WITH_UNCHECKED_REPOSITORY_ARTIFACTS` rather than silently
+claiming those bytes were checked.
+
+## 7. Claims licensed by profile conformance
+
+For a valid, digest-matching manifest an implementation may state:
+
+> The experiment record conforms to experimental workflow profile 0.1 for the declared
+> question, artifacts, budgets, actions, native results, adaptations, and horizons.
+
+This means the record is structurally valid and internally bound. It does not establish:
+
+- that the experiment was executed as recorded without supporting evidence;
+- that a hypothesis is true outside its declared evidence;
+- that a native verifier is correct;
+- that a VSTD mapping is valid without checking its bound receipt;
+- that a selected action was optimal;
+- that a publication, commit, workflow, pull request, or merge is correct;
+- external adoption, endorsement, independence, identity, authorization, or safety.
diff --git a/docs/profiles/experimental-workflow.schema.json b/docs/profiles/experimental-workflow.schema.json
new file mode 100644
index 0000000..f3d224a
--- /dev/null
+++ b/docs/profiles/experimental-workflow.schema.json
@@ -0,0 +1,898 @@
+{
+ "$comment": "Terminology: Verifier Standard (VSTD).",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://timelordraps.github.io/verifier/profiles/experimental-workflow.schema.json",
+ "title": "VSTD experimental workflow profile 0.1",
+ "description": "Non-normative, verdict-neutral interchange for bounded experimental work. Schema validity does not verify referenced evidence or native results.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "profile": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "const": "vstd.experimental-workflow"
+ },
+ "version": {
+ "const": "0.1"
+ },
+ "status": {
+ "const": "EXPERIMENTAL_NON_NORMATIVE"
+ }
+ },
+ "required": [
+ "id",
+ "version",
+ "status"
+ ]
+ },
+ "experiment": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "title": {
+ "type": "string",
+ "minLength": 1
+ },
+ "question": {
+ "type": "string",
+ "minLength": 1
+ },
+ "state": {
+ "enum": [
+ "DRAFT",
+ "PREREGISTERED",
+ "RUNNING",
+ "BLOCKED",
+ "COMPLETED",
+ "ABANDONED"
+ ]
+ },
+ "started_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "minLength": 1
+ }
+ },
+ "required": [
+ "id",
+ "title",
+ "question",
+ "state",
+ "started_at"
+ ]
+ },
+ "hypotheses": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "statement": {
+ "type": "string",
+ "minLength": 1
+ },
+ "falsification_condition": {
+ "type": "string",
+ "minLength": 1
+ },
+ "state": {
+ "enum": [
+ "OPEN",
+ "SUPPORTED",
+ "REFUTED",
+ "UNKNOWN",
+ "CONFLICTED"
+ ]
+ }
+ },
+ "required": [
+ "id",
+ "statement",
+ "falsification_condition",
+ "state"
+ ]
+ },
+ "minItems": 1
+ },
+ "preregistration": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "state": {
+ "enum": [
+ "NONE",
+ "DRAFT",
+ "FROZEN",
+ "AMENDED"
+ ]
+ },
+ "recorded_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "minLength": 1
+ },
+ "artifact_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "minLength": 1
+ },
+ "limitations": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ }
+ },
+ "required": [
+ "state",
+ "recorded_at",
+ "artifact_id",
+ "limitations"
+ ]
+ },
+ "artifacts": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "role": {
+ "type": "string",
+ "minLength": 1
+ },
+ "media_type": {
+ "type": "string",
+ "minLength": 1
+ },
+ "digest": {
+ "type": "string",
+ "pattern": "^sha256:[0-9a-f]{64}$"
+ },
+ "locator": {
+ "type": "string",
+ "pattern": "^(artifact:|git:|https://|repo:|urn:).+"
+ }
+ },
+ "required": [
+ "id",
+ "role",
+ "media_type",
+ "digest",
+ "locator"
+ ]
+ }
+ },
+ "budgets": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "resource": {
+ "type": "string",
+ "minLength": 1
+ },
+ "limit": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "consumed": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "unit": {
+ "type": "string",
+ "minLength": 1
+ },
+ "scope": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "required": [
+ "id",
+ "resource",
+ "limit",
+ "consumed",
+ "unit",
+ "scope"
+ ]
+ }
+ },
+ "actions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "kind": {
+ "type": "string",
+ "minLength": 1
+ },
+ "target": {
+ "type": "string",
+ "minLength": 1
+ },
+ "state": {
+ "enum": [
+ "PLANNED",
+ "RUNNING",
+ "BLOCKED",
+ "COMPLETED",
+ "ABANDONED"
+ ]
+ },
+ "priority": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "selected_because": {
+ "type": "string",
+ "minLength": 1
+ },
+ "selection_evidence_ids": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ }
+ },
+ "alternatives_considered": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "budget_ids": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "minItems": 1
+ },
+ "depends_on": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ }
+ },
+ "triggered_by": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ }
+ },
+ "expected_artifact_effect": {
+ "type": "string",
+ "minLength": 1
+ },
+ "substrate": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "kind": {
+ "type": "string",
+ "minLength": 1
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1
+ },
+ "version": {
+ "type": "string",
+ "minLength": 1
+ },
+ "coordinate": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "required": [
+ "kind",
+ "name",
+ "version",
+ "coordinate"
+ ]
+ },
+ "native_result_ids": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ }
+ },
+ "produced_artifact_ids": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "kind",
+ "target",
+ "state",
+ "priority",
+ "selected_because",
+ "selection_evidence_ids",
+ "alternatives_considered",
+ "budget_ids",
+ "depends_on",
+ "triggered_by",
+ "expected_artifact_effect",
+ "substrate",
+ "native_result_ids",
+ "produced_artifact_ids"
+ ]
+ }
+ },
+ "observations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "action_id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "recorded_at": {
+ "type": "string",
+ "minLength": 1
+ },
+ "statement": {
+ "type": "string",
+ "minLength": 1
+ },
+ "status": {
+ "enum": [
+ "OBSERVED",
+ "UNKNOWN",
+ "CONFLICTED"
+ ]
+ },
+ "evidence_artifact_ids": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ }
+ },
+ "limitations": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ }
+ },
+ "required": [
+ "id",
+ "action_id",
+ "recorded_at",
+ "statement",
+ "status",
+ "evidence_artifact_ids",
+ "limitations"
+ ]
+ }
+ },
+ "interventions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "action_id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "description": {
+ "type": "string",
+ "minLength": 1
+ },
+ "applied_at": {
+ "type": "string",
+ "minLength": 1
+ },
+ "target_artifact_ids": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ }
+ },
+ "produced_artifact_ids": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "action_id",
+ "description",
+ "applied_at",
+ "target_artifact_ids",
+ "produced_artifact_ids"
+ ]
+ }
+ },
+ "native_results": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "action_id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "verifier": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "kind": {
+ "type": "string",
+ "minLength": 1
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1
+ },
+ "version": {
+ "type": "string",
+ "minLength": 1
+ },
+ "coordinate": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "required": [
+ "kind",
+ "name",
+ "version",
+ "coordinate"
+ ]
+ },
+ "native_status": {
+ "type": "string",
+ "minLength": 1
+ },
+ "result_artifact_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "minLength": 1
+ },
+ "mapping": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "status": {
+ "enum": [
+ "NOT_EVALUATED",
+ "MAPPED"
+ ]
+ },
+ "vstd_verdict": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "enum": [
+ "PASS",
+ "FAIL",
+ "UNKNOWN",
+ "CONFLICTED",
+ "REJECTED",
+ null
+ ]
+ },
+ "mapping_profile": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "minLength": 1
+ },
+ "receipt_artifact_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "minLength": 1
+ },
+ "reason": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "required": [
+ "status",
+ "vstd_verdict",
+ "mapping_profile",
+ "receipt_artifact_id",
+ "reason"
+ ]
+ }
+ },
+ "required": [
+ "id",
+ "action_id",
+ "verifier",
+ "native_status",
+ "result_artifact_id",
+ "mapping"
+ ]
+ }
+ },
+ "adaptations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "trigger_ids": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ }
+ },
+ "decision": {
+ "type": "string",
+ "minLength": 1
+ },
+ "reason": {
+ "type": "string",
+ "minLength": 1
+ },
+ "action_ids": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ }
+ },
+ "artifact_ids": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "trigger_ids",
+ "decision",
+ "reason",
+ "action_ids",
+ "artifact_ids"
+ ]
+ }
+ },
+ "amendments": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "recorded_at": {
+ "type": "string",
+ "minLength": 1
+ },
+ "reason": {
+ "type": "string",
+ "minLength": 1
+ },
+ "supersedes": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ }
+ },
+ "artifact_id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ }
+ },
+ "required": [
+ "id",
+ "recorded_at",
+ "reason",
+ "supersedes",
+ "artifact_id"
+ ]
+ }
+ },
+ "challenges": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "target_id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "state": {
+ "enum": [
+ "OPEN",
+ "RESOLVED",
+ "REJECTED"
+ ]
+ },
+ "statement": {
+ "type": "string",
+ "minLength": 1
+ },
+ "evidence_artifact_ids": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "target_id",
+ "state",
+ "statement",
+ "evidence_artifact_ids"
+ ]
+ }
+ },
+ "horizons": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "status": {
+ "enum": [
+ "UNKNOWN",
+ "CONFLICTED",
+ "BLOCKED",
+ "OUT_OF_SCOPE"
+ ]
+ },
+ "description": {
+ "type": "string",
+ "minLength": 1
+ },
+ "reason": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "required": [
+ "id",
+ "status",
+ "description",
+ "reason"
+ ]
+ }
+ },
+ "publication": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "state": {
+ "enum": [
+ "PRIVATE",
+ "INTERNAL",
+ "CANDIDATE",
+ "PUBLISHED",
+ "RETRACTED"
+ ]
+ },
+ "artifact_ids": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ }
+ }
+ },
+ "required": [
+ "state",
+ "artifact_ids"
+ ]
+ },
+ "workflow_events": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
+ },
+ "kind": {
+ "enum": [
+ "PLATFORM_ISSUE",
+ "PLATFORM_COMMIT",
+ "PLATFORM_WORKFLOW_RUN",
+ "PLATFORM_ARTIFACT",
+ "PLATFORM_PULL_REQUEST"
+ ]
+ },
+ "recorded_at": {
+ "type": "string",
+ "minLength": 1
+ },
+ "source": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "platform": {
+ "type": "string",
+ "minLength": 1
+ },
+ "repository": {
+ "type": "string",
+ "minLength": 1
+ },
+ "coordinate": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "required": [
+ "platform",
+ "repository",
+ "coordinate"
+ ]
+ },
+ "native_state": {
+ "type": "string",
+ "minLength": 1
+ },
+ "verification_effect": {
+ "const": "NONE"
+ },
+ "details": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "id",
+ "kind",
+ "recorded_at",
+ "source",
+ "native_state",
+ "verification_effect",
+ "details"
+ ]
+ }
+ },
+ "manifest_digest": {
+ "type": "string",
+ "pattern": "^sha256:[0-9a-f]{64}$"
+ }
+ },
+ "required": [
+ "profile",
+ "experiment",
+ "hypotheses",
+ "preregistration",
+ "artifacts",
+ "budgets",
+ "actions",
+ "observations",
+ "interventions",
+ "native_results",
+ "adaptations",
+ "amendments",
+ "challenges",
+ "horizons",
+ "publication",
+ "workflow_events",
+ "manifest_digest"
+ ]
+}
diff --git a/docs/layers/vstd-3/compatibility.md b/docs/profiles/vstd-3/compatibility.md
similarity index 60%
rename from docs/layers/vstd-3/compatibility.md
rename to docs/profiles/vstd-3/compatibility.md
index 01f9ba8..caf904f 100644
--- a/docs/layers/vstd-3/compatibility.md
+++ b/docs/profiles/vstd-3/compatibility.md
@@ -1,14 +1,25 @@
-# VSTD-3 implementation compatibility
+# Verifier Standard (VSTD)-3 implementation compatibility
-VSTD-3 is additive. It does not reinterpret earlier receipt wire formats. For
-the historical filename and wire-identifier table, see
+> Reader aid: [concept glossary and primary precedents](../../CONCEPTS_AND_PRECEDENTS.md).
+
+VSTD-3 is additive. It does not reinterpret adjacent receipt surfaces. For the current
+wire-identifier table, see
`../../../standard/WIRE_IDENTIFIERS.md`.
+The currently shipped adapter boundary is centralized in
+[`docs/CLAIMS_AND_LIMITS.md`](../../CLAIMS_AND_LIMITS.md#what-the-current-adapters-can-say):
+host-visible metadata is not device attestation, and the virtual accelerator establishes
+only its emulator-scoped claims.
+
## Existing receipts
-- `VSTD-0.1` receipt validators keep their existing VSTD-1 wire semantics.
-- `VSTD-DATA-0.1` hypergraphs remain readable as historical VSTD-Graph-1 receipts.
-- `VSTD-0.2` geometry remains the frozen VSTD-2 wire surface.
+- `VSTD-1` claim-mechanics and generic-run receipts retain their separate required
+ `receipt_kind` values.
+- `VSTD-DATA-0.1` hypergraphs remain readable as historical VSTD-Graph-1 receipts, including
+ their original separate artifact and transformation identifier namespaces. Direct new
+ construction, evidence-bound Graph establishment, and current Graph assurance require
+ global cross-kind disjointness.
+- `VSTD-2` geometry remains a separate verification-surface receipt.
- The public `validate`, `inspect`, `reproduce`, `data`, and `impact` commands retain
their earlier behavior.
@@ -42,5 +53,5 @@ execution records are not converted into device attestation without new evidence
## Schema/version dispatch
-Dispatch by exact `schema_version`. VSTD-3 receipts use the frozen `VSTD-3.0` wire identifier. Unknown versions
+Dispatch by exact `schema_version`. VSTD-3 receipts use the serialized receipt identifier `VSTD-3.0`. Unknown versions
must fail closed. Do not guess a compatible decoder from field similarity.
diff --git a/docs/layers/vstd-3/references.md b/docs/profiles/vstd-3/references.md
similarity index 77%
rename from docs/layers/vstd-3/references.md
rename to docs/profiles/vstd-3/references.md
index f59b215..0ea44f9 100644
--- a/docs/layers/vstd-3/references.md
+++ b/docs/profiles/vstd-3/references.md
@@ -1,4 +1,15 @@
-# VSTD-3 official public references
+# Verifier Standard (VSTD)-3 official public references
+
+> **Acronyms:** Advanced Micro Devices (AMD); application programming interface (API); Amazon Web Services (AWS);
+> command-line interface (CLI); Device Identifier Composition Engine (DICE); DMTF standards organization (DMTF);
+> design of experiments (DOE); Engineering Change Notice (ECN); graphics processing unit (GPU);
+> integrated development environment (IDE); Internet Engineering Task Force (IETF); multi-instance GPU (MIG);
+> NVIDIA Management Library (NVML); Peripheral Component Interconnect (PCI); PCI Special Interest Group (PCI-SIG);
+> Remote Attestation Procedures (RATS); Request for Comments (RFC); Reference Integrity Manifest (RIM);
+> software development kit (SDK); system management interface (SMI); Security Protocol and Data Model (SPDM);
+> Trusted Device Interface Security Protocol (TDISP).
+
+> Reader aid: [cross-profile concept glossary and primary precedents](../../CONCEPTS_AND_PRECEDENTS.md).
**Retrieved:** 2026-08-21
diff --git a/docs/layers/vstd-3/threat-model.md b/docs/profiles/vstd-3/threat-model.md
similarity index 93%
rename from docs/layers/vstd-3/threat-model.md
rename to docs/profiles/vstd-3/threat-model.md
index b697e40..0a99d08 100644
--- a/docs/layers/vstd-3/threat-model.md
+++ b/docs/profiles/vstd-3/threat-model.md
@@ -1,6 +1,12 @@
-# VSTD-3 threat model
+# Verifier Standard (VSTD)-3 threat model
-**Layer:** VSTD-3; historical receipt wire identifier `VSTD-3.0`
+> **Acronyms:** Advanced Micro Devices (AMD); command-line interface (CLI);
+> hash-based message authentication code (HMAC); identifier (ID); trusted computing base (TCB);
+> Coordinated Universal Time (UTC); virtual machine (VM).
+
+> Reader aid: [concept glossary and primary precedents](../../CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-3; required closure coordinate: Substrate Accountability; serialized receipt identifier `VSTD-3.0`
**Purpose:** defensive verification and conformance; not offensive exploit guidance
## Boundary
diff --git a/docs/layers/vstd-3/vendor-integration.md b/docs/profiles/vstd-3/vendor-integration.md
similarity index 90%
rename from docs/layers/vstd-3/vendor-integration.md
rename to docs/profiles/vstd-3/vendor-integration.md
index 91d9671..dba3255 100644
--- a/docs/layers/vstd-3/vendor-integration.md
+++ b/docs/profiles/vstd-3/vendor-integration.md
@@ -1,8 +1,18 @@
-# VSTD-3 accelerator vendor integration kit
+# Verifier Standard (VSTD)-3 accelerator vendor integration kit
+
+> **Acronyms:** graphics processing unit (GPU); identifier (ID); multi-instance GPU (MIG);
+> single-root input/output virtualization (SR-IOV).
+
+> Reader aid: [concept glossary and primary precedents](../../CONCEPTS_AND_PRECEDENTS.md).
This is the minimum review surface for a firmware or silicon security team evaluating
VSTD-3. It does not require adopting VSTD product names in firmware.
+Before selecting a profile, read the centralized
+[`current-adapter claim boundary`](../../CLAIMS_AND_LIMITS.md#what-the-current-adapters-can-say).
+Host-visible metadata is not device attestation, and the virtual accelerator establishes
+only its emulator-scoped claims.
+
## 1. Select the honest profile
Implement only the profiles the device can demonstrate:
diff --git a/docs/reference.html b/docs/reference.html
new file mode 100644
index 0000000..a64118f
--- /dev/null
+++ b/docs/reference.html
@@ -0,0 +1,802 @@
+
+
+
+
+
+
+
+
+
+
+
+ VSTD docs — command-line interface (CLI) and application programming interface (API) reference
+
+
+
+
+
+ Skip to content
+
+
+
+
+
Reference · verifier-standard 1.2.0 · VSTD-5 PROJECT SPECIFICATION; EVIDENCE-BOUND REFERENCE MECHANISM
+
Inspect the whole pipeline.
+
Terms used below: hash-based message authentication
+ code (HMAC); International Organization for Standardization (ISO); JavaScript Object
+ Notation (JSON); Secure Hash Algorithm 256-bit (SHA-256); and YAML Ain't Markup Language
+ (YAML).
+
Every command, argument, top-level export, and listed dispatch edge below
+ is read out of the installed package when this page is built, by
+ scripts/build_reference.py, and the presentation tests fail closed when the
+ committed page drifts — so it cannot describe behaviour the implementation no
+ longer has.
+
This page states the declared public surface of one implementation. It
+ does not establish that any individual claim checked by these commands is true, nor that
+ an external implementation exists.
+
+
+
+
+
+
Pipeline
+
Command to implementation, without a gap.
+
Each entry point below is imported while this page is built. A
+ rename, move, or deletion fails the build instead of publishing a stale map.
+
Command What it does Implementation entry points
+vstd demoRuns the four adversarial specimens in-process and reports whether each defensive outcome matched its declared invariant. verifier.runtime.demo:run_demoverifier.runtime.demo:demo_report
+vstd planResolves a manifest's command and declared paths without executing anything. verifier.core.run_planning:load_manifestverifier.core.run_planning:describe_run_plan
+vstd runExecutes a trusted manifest without sandboxing, captures the observed execution, and writes a canonically digested receipt. verifier.core.run_planning:load_manifestverifier.core.run:capture_runverifier.core.receipt:compute_canonical_digest
+vstd validateDispatches on the receipt's serialized `schema_version` identifier and runs its implemented checks. Generic-run validation enforces its required structure and stable digest; other receipt kinds enforce their separately documented structure and evidence rules. verifier.core.run_validation:validate_run_receiptverifier.data.receipt:validate_data_receiptverifier.hardware.validation:validate_vstd3_receipt
+vstd inspectPrints the claim coordinate, digest, and verdict surface of a stored receipt. verifier.core.run_inspection:inspect_run_receiptverifier.hardware.receipt:load_vstd3_receipt
+vstd reproduceReplays only the mechanisms a stored receipt actually carries; physical hardware execution is refused rather than simulated. verifier.core.run_reproduction:reproduce_run_receiptverifier.data.receipt:reproduce_data_receipt
+vstd impactFinds stored run receipts whose recorded ancestry reaches a revoked provenance artifact. verifier.core.run_impact:find_run_receipts_impacted_by_revocation
+vstd dataTraces, renders, or exports the provenance hypergraph carried by a VSTD-Graph receipt. verifier.data.models:ProvenanceHypergraph
+vstd artifactFreezes exact regular-file bytes, adds or verifies finite self-closing seals, and creates observable copy-on-write thaw descendants. verifier.artifact_control:freeze_artifactverifier.artifact_control:seal_artifactverifier.artifact_control:verify_frozen_artifactverifier.artifact_control:thaw_artifact
+vstd experimentValidates experimental workflow manifests or maps normalized GitHub snapshots without granting a VSTD verdict. verifier.runtime.experimental_workflow_cli:handle_experiment_commandverifier.experimental_workflow.profile:load_manifestverifier.experimental_workflow.github:github_snapshot_to_events
+vstd hardware / continuity / fleet / evidence / claimsEvaluates VSTD-3 substrate-accountability receipts, their continuity and fleet evidence, and their declared claims. verifier.runtime.hardware_cli:handle_vstd3_commandverifier.hardware.validation:validate_vstd3_receipt
+
+
+
+
+
+
+
CLI
+
The vstd command reference.
+
Extracted from the live argument parser in
+ verifier.runtime.public_cli .
+ vstd is the canonical cross-platform command; verifier is
+ retained as an alias only on platforms where it is unambiguous.
+
+vstd
+Subcommand group.
+No arguments; this command only groups subcommands.
+
+
+vstd demo
+Run the side-effect-free VSTD adversarial flagship demonstration.
+Argument Kind Meaning
+--scenariooptional Run all scenarios or one named scenario. (one of: all, wrong-artifact, honest-unknown, inflated-tier, poisoned-ancestor) [default: all]
+--jsonoptional
+--emit-specimensoptional Write deterministic JSON specimens and observations to DIR.
+
+
+
+vstd run
+Execute a trusted manifest without sandboxing and capture a VSTD receipt.
+Argument Kind Meaning
+manifestpositional JSON or YAML run manifest.
+--outputoptional Receipt output directory.
+--receipt-idoptional Override the manifest claim id.
+
+
+
+vstd plan
+Show a manifest's declared command and paths without executing it.
+Argument Kind Meaning
+manifestpositional JSON or YAML run manifest.
+--jsonoptional
+
+
+
+vstd validate
+Run implemented receipt checks; Graph candidate validation is not conformance.
+Argument Kind Meaning
+receiptpositional Receipt directory or receipt.json.
+--jsonoptional
+--keyoptional
+
+
+
+vstd inspect
+Inspect a generic-run or VSTD-Graph receipt; validate and report VSTD-3.
+Argument Kind Meaning
+receiptpositional Receipt directory or receipt.json.
+--jsonoptional
+--keyoptional
+
+
+
+vstd reproduce
+Replay the mechanisms available in a stored receipt.
+Argument Kind Meaning
+receiptpositional Receipt directory or receipt.json.
+--jsonoptional
+--rerunoptional Generic-run receipts only: execute the recorded command again.
+
+
+
+vstd impact
+Find run receipts affected by a provenance-artifact revocation.
+Argument Kind Meaning
+dataset_receiptpositional
+artifact_idpositional
+--search-rootoptional [default: receipts]
+
+
+
+vstd data
+Inspect a stored VSTD-Graph hypergraph.
+No arguments; this command only groups subcommands.
+
+
+vstd data trace
+Subcommand group.
+Argument Kind Meaning
+artifact_idpositional
+--receiptoptional
+--directionoptional (one of: ancestors, descendants, blast_radius) [default: ancestors]
+
+
+
+vstd data graph
+Subcommand group.
+Argument Kind Meaning
+receiptpositional
+
+
+
+vstd data export
+Subcommand group.
+Argument Kind Meaning
+receiptpositional
+
+
+
+vstd artifact
+Freeze exact artifact bytes, add or verify a seal, or thaw a descendant.
+No arguments; this command only groups subcommands.
+
+
+vstd artifact freeze
+Copy exact ordinary file or directory bytes into a new guarded bundle; symbolic-link sources are refused.
+Argument Kind Meaning
+sourcepositional
+bundlepositional
+--media-typeoptional [default: application/octet-stream]
+--parentoptional
+--contextoptional
+--jsonoptional
+
+
+
+vstd artifact seal
+Add a readable finite self-closing Ed25519 seal.
+Argument Kind Meaning
+bundlepositional
+--private-keyoptional
+--jsonoptional
+
+
+
+vstd artifact verify
+Recompute exact bytes, guards, seals, and optional external anchors.
+Argument Kind Meaning
+bundlepositional
+--expected-artifact-idoptional
+--expected-key-idoptional
+--freeze-onlyoptional Accept a clean freeze without claiming seal-backed identity.
+--jsonoptional
+
+
+
+vstd artifact thaw
+Copy a clean sealed parent into a new, lexically absent mutable descendant.
+Argument Kind Meaning
+bundlepositional
+destinationpositional
+--expected-artifact-idoptional
+--expected-key-idoptional
+--jsonoptional
+
+
+
+vstd artifact status
+Compare a descendant with recorded sidecar metadata, or verify current equality against a supplied sealed parent.
+Argument Kind Meaning
+artifactpositional
+--recordoptional
+--parent-bundleoptional Actual frozen parent bundle required to establish THAWED_CLEAN or THAWED_DIRTY.
+--expected-artifact-idoptional
+--expected-key-idoptional
+--jsonoptional
+
+
+
+vstd experiment
+Validate or adapt experimental, non-normative workflow records.
+No arguments; this command only groups subcommands.
+
+
+vstd experiment validate
+Validate a profile manifest without granting a VSTD verdict.
+Argument Kind Meaning
+manifestpositional Experimental workflow manifest JSON.
+--repo-rootoptional Repository root used to verify every repo: artifact locator.
+--jsonoptional
+
+
+
+vstd experiment github-events
+Map a strict normalized GitHub snapshot to verdict-neutral events.
+Argument Kind Meaning
+snapshotpositional Normalized GitHub snapshot JSON.
+--jsonoptional
+
+
+
+vstd hardware
+Discover or emulate accelerator evidence.
+No arguments; this command only groups subcommands.
+
+
+vstd hardware list
+List accelerator profiles.
+Argument Kind Meaning
+--vendoroptional
+--jsonoptional Emit stable machine-readable JSON.
+
+
+
+vstd hardware inspect
+Inspect one accelerator profile.
+Argument Kind Meaning
+profile_idpositional
+--jsonoptional Emit stable machine-readable JSON.
+
+
+
+vstd hardware discover
+Run a vendor or generic adapter.
+Argument Kind Meaning
+--adapteroptional (one of: generic, nvidia, amd, intel)
+--fixtureoptional
+--outputoptional Write the normalized adapter result JSON.
+--jsonoptional Emit stable machine-readable JSON.
+
+
+
+vstd hardware emulate
+Run the deterministic virtual firmware contract probe.
+Argument Kind Meaning
+--outputoptional
+--created-atoptional Final ISO-8601 receipt timestamp.
+--device-idoptional [default: vstd3-virtual-0]
+--firmware-versionoptional [default: 1.0.0]
+--key-idoptional [default: vstd3-virtual-device-key]
+--key-hexoptional Test-only emulator HMAC key in hex.
+--jsonoptional Emit stable machine-readable JSON.
+
+
+
+vstd hardware attest
+Run an explicitly virtual attestation probe; no commodity claim is made.
+Argument Kind Meaning
+--virtualoptional
+--outputoptional
+--created-atoptional
+--device-idoptional [default: vstd3-virtual-0]
+--firmware-versionoptional [default: 1.0.0]
+--key-idoptional [default: vstd3-virtual-device-key]
+--key-hexoptional
+--jsonoptional Emit stable machine-readable JSON.
+
+
+
+vstd hardware capabilities
+Evaluate incremental VSTD 3 conformance profiles.
+Argument Kind Meaning
+receiptpositional
+--jsonoptional Emit stable machine-readable JSON.
+--keyoptional Test-only HMAC verification key; repeat for multiple key ids.
+
+
+
+vstd hardware verify
+Verify a VSTD 3 receipt and all recorded passing claims.
+Argument Kind Meaning
+receiptpositional
+--jsonoptional Emit stable machine-readable JSON.
+--keyoptional Test-only HMAC verification key; repeat for multiple key ids.
+
+
+
+vstd continuity
+Verify authenticated event continuity.
+No arguments; this command only groups subcommands.
+
+
+vstd continuity verify
+Subcommand group.
+Argument Kind Meaning
+receiptpositional
+--jsonoptional Emit stable machine-readable JSON.
+--keyoptional Test-only HMAC verification key; repeat for multiple key ids.
+
+
+
+vstd fleet
+Verify a declared enrolled fleet boundary.
+No arguments; this command only groups subcommands.
+
+
+vstd fleet verify
+Subcommand group.
+Argument Kind Meaning
+receiptpositional
+--jsonoptional Emit stable machine-readable JSON.
+
+
+
+vstd evidence
+Inspect VSTD 3 evidence strength.
+No arguments; this command only groups subcommands.
+
+
+vstd evidence inspect
+Subcommand group.
+Argument Kind Meaning
+receiptpositional
+--jsonoptional Emit stable machine-readable JSON.
+--keyoptional Test-only HMAC verification key; repeat for multiple key ids.
+
+
+
+vstd claims
+Evaluate or explain VSTD 3 claims.
+No arguments; this command only groups subcommands.
+
+
+vstd claims evaluate
+Subcommand group.
+Argument Kind Meaning
+receiptpositional
+--jsonoptional Emit stable machine-readable JSON.
+--keyoptional Test-only HMAC verification key; repeat for multiple key ids.
+
+
+
+vstd claims explain
+Subcommand group.
+Argument Kind Meaning
+kindpositional (one of: DEVICE_IDENTITY, FIRMWARE_INTEGRITY, EXECUTION_OBSERVED, EXECUTION_ATTESTATION, EXECUTION_ACCOUNTING, ACCOUNTING_CONTINUITY, COMPLETE_MEDIATION, FLEET_COMPLETENESS, PHYSICAL_WORLD_COMPLETENESS)
+--jsonoptional Emit stable machine-readable JSON.
+
+
+
+
+
+
+
+
API
+
Top-level Python exports.
+
The names in verifier.__all__, with their live
+ signatures and declared docstrings, are the supported runtime surface under the
+ Python API stability policy .
+ Subpackage imports are internal unless a published policy names them.
+
+ArtifactControlError class
+ArtifactControlError
+Raised when an artifact-control action cannot fail closed.
+Defined in verifier.artifact_control
+
+
+
+ArtifactVerification class
+ArtifactVerification(state: 'str', artifact_id: 'str | None', content_id: 'str | None', freeze_id: 'str | None', freeze_valid: 'bool', guard_valid: 'bool', valid_seal_ids: 'tuple[str, ...]', key_ids: 'tuple[str, ...]', external_anchor: 'str', errors: 'tuple[str, ...]', warnings: 'tuple[str, ...]') -> None
+Result of independently recomputing a frozen artifact and its seals.
+Defined in verifier.artifact_control
+Method Summary
+to_dict(self) -> 'dict[str, Any]'
+
+
+
+AssuranceLedger class
+AssuranceLedger(graph: 'ProvenanceHypergraph') -> 'None'
+Append-only current-state overlay for an immutable provenance graph.
+Defined in verifier.data.assurance
+Method Summary
+admissibility_blocking_conflicts(self) -> 'tuple[ConflictRecord, ...]'Return conflicts whose effect still blocks a clean TRUST route.
+compose_guilt(self, ancestor_id: 'str', descendant_id: 'str', obligation: 'ObligationCoordinate', proposition: 'BoundProposition', *, localization_event_digest: 'str', responsibility_component_digest: 'str', applicability_component_digest: 'str', violation_component_digest: 'str', session: 'VerificationSession', recorded_at: 'str') -> 'DiagnosticAttribution'Compose technical GUILT from three exact, separately earned components.
+current_status(self, artifact_id: 'str') -> 'ArtifactStatus'
+current_transformation_status(self, transformation_id: 'str') -> 'str'Return a transformation's additive current status projection.
+current_trust_events(self) -> 'tuple[AssuranceEvent, ...]'Return recursively current edge-local TRUST records.
+diagnose(self, kind: 'DiagnosticKind', ancestor_id: 'str', descendant_id: 'str', proposition: 'Optional[BoundProposition]', *, session: 'VerificationSession', recorded_at: 'str') -> 'DiagnosticAttribution'Compute BLAME, or fail closed for legacy opaque GUILT calls.
+establish_guilt_components(self, ancestor_id: 'str', descendant_id: 'str', obligation: 'ObligationCoordinate', responsibility_proposition: 'BoundProposition', applicability_proposition: 'BoundProposition', violation_proposition: 'BoundProposition', *, localization_event_digest: 'str', session: 'VerificationSession', recorded_at: 'str') -> 'tuple[AssuranceEvent, AssuranceEvent, AssuranceEvent]'Run one compound mechanism and retain three separately bound results.
+establish_obligation_applicability(self, artifact_id: 'str', obligation: 'ObligationCoordinate', proposition: 'BoundProposition', *, session: 'VerificationSession', recorded_at: 'str') -> 'AssuranceEvent'Evaluate whether one exact obligation applies to one exact artifact.
+establish_obligation_violation(self, ancestor_id: 'str', descendant_id: 'str', obligation: 'ObligationCoordinate', proposition: 'BoundProposition', *, localization_event_digest: 'str', applicability_component_digest: 'str', session: 'VerificationSession', recorded_at: 'str') -> 'AssuranceEvent'Evaluate violation after exact applicability and deviation localization.
+establish_responsibility(self, ancestor_id: 'str', descendant_id: 'str', proposition: 'BoundProposition', *, localization_event_digest: 'str', session: 'VerificationSession', recorded_at: 'str') -> 'AssuranceEvent'Evaluate one separately bound material-contribution component.
+events(self) -> 'tuple[AssuranceEvent, ...]'
+impacted_descendants(self, artifact_id: 'str') -> 'tuple[str, ...]'Return the deduplicated recorded forward impact set, not a verdict.
+localize_cause(self, ancestor_id: 'str', descendant_id: 'str', proposition: 'BoundProposition', *, rust_event_digest: 'str', session: 'VerificationSession', recorded_at: 'str') -> 'AssuranceEvent'Bind one ancestor to one exact passing descendant-deviation event.
+materialize_current_graph(self) -> 'ProvenanceHypergraph'Create a derived current view; never mutate the historical graph.
+project_challenges(self, challenges: 'ChallengeLedger', *, recorded_at: 'str') -> 'tuple[AssuranceEvent, ...]'Project challenge state into an additive current Graph overlay.
+record_conflict(self, conflict: 'ConflictRecord', proposition: 'BoundProposition', *, session: 'VerificationSession', recorded_at: 'str') -> 'AssuranceEvent'Add mechanism-established current conflict evidence without rewriting Graph.
+record_rot(self, artifact_id: 'str', resulting_status: 'ArtifactStatus', proposition: 'BoundProposition', *, session: 'VerificationSession', recorded_at: 'str') -> 'AssuranceEvent'
+record_rust(self, descendant_id: 'str', deviation: 'BoundProposition', *, session: 'VerificationSession', recorded_at: 'str') -> 'AssuranceEvent'
+record_status_projection(self, artifact_id: 'str', proposition: 'BoundProposition', *, session: 'VerificationSession', recorded_at: 'str') -> 'AssuranceEvent'Record a mechanism-checked current-status projection additively.
+record_trust(self, target_id: 'str', source_ids: 'Iterable[str]', proposition: 'BoundProposition', *, transformation_id: 'str', prerequisite_trust_event_digests: 'Iterable[str]' = (), session: 'VerificationSession', recorded_at: 'str') -> 'AssuranceEvent'
+resolutions(self) -> 'tuple[ConflictResolution, ...]'
+resolve_conflict(self, conflict_id: 'str', selected_value: 'str', proposition: 'BoundProposition', *, session: 'VerificationSession', recorded_at: 'str') -> 'ConflictResolution'Adjudicate one value without equating selection with admissibility.
+rust_concentration(self) -> 'tuple[StructuralConcentration, ...]'
+to_dict(self) -> 'dict[str, Any]'
+unresolved_conflicts(self) -> 'tuple[ConflictRecord, ...]'
+verify_hash_chain(self) -> 'bool'
+
+
+
+BoundProposition class
+BoundProposition(subject_id: 'str', predicate: 'str', expected: 'Any', mechanism_id: 'str', mechanism_digest: 'str', evidence_refs: 'tuple[str, ...]', trust_roots: 'tuple[str, ...]', bounds: 'EvidenceBounds', parameters: 'Mapping[str, str]' = <factory>) -> None
+Exact proposition, evidence, mechanism, trust-root, and bound binding.
+Defined in verifier.core.evidence
+Method Summary
+digest(self) -> 'str'
+to_dict(self) -> 'dict[str, Any]'
+
+
+
+DecisionCertificate class
+DecisionCertificate(header: 'CertificateHeader', formula: 'tuple[tuple[int, ...], ...]', grounding: 'Grounding', decision: 'DecisionBlock', hints: 'dict[str, Any]' = <factory>) -> None
+Canonical grounded decision certificate (GDC) blocks for the bounded checker.
+Defined in verifier.core.certificate
+Method Summary
+digest(self) -> 'str'
+to_dict(self) -> 'dict[str, Any]'
+without_hints(self) -> "'DecisionCertificate'"Hint-stripped form.
+
+
+
+EvidenceBindingError class
+EvidenceBindingError
+An evidence binding is malformed or cannot be resolved exactly.
+Defined in verifier.core.evidence
+
+
+
+EvidenceBounds class
+EvidenceBounds(max_evidence_items: 'int', max_evidence_bytes: 'int') -> None
+Resource ceilings enforced before invoking a domain mechanism.
+Defined in verifier.core.evidence
+Method Summary
+to_dict(self) -> 'dict[str, int]'
+
+
+
+EvidenceStore class
+EvidenceStore() -> 'None'
+In-memory content-addressed evidence store with collision/fork refusal.
+Defined in verifier.core.evidence
+Method Summary
+add(self, payload: 'bytes') -> 'str'
+export_base64(self, references: 'Sequence[str]') -> 'dict[str, str]'Export exact evidence bytes for portable, offline mechanism replay.
+import_base64(self, payloads: 'Mapping[str, str]') -> 'None'Import a portable bundle and refuse every reference/byte mismatch.
+resolve(self, reference: 'str') -> 'bytes'
+
+
+
+MechanismDecision class
+MechanismDecision(outcome: 'MechanismOutcome', details: 'str', observations: 'Mapping[str, Any]' = <factory>) -> None
+One bounded mechanism result plus its exact observations.
+Defined in verifier.core.evidence
+
+
+
+MechanismOutcome enum
+Enumeration of the exported result values.
+Defined in verifier.core.evidence
+Members: PASS, FAIL, UNKNOWN
+
+
+ObligationCoordinate class
+ObligationCoordinate(obligation_id: 'str' = '', content_digest: 'str' = '', scope: 'Mapping[str, str]' = <factory>, assumptions: 'tuple[str, ...]' = (), exclusions: 'tuple[str, ...]' = ()) -> None
+Exact technical obligation and the declared scope in which it applies.
+Defined in verifier.data.assurance
+Method Summary
+digest(self) -> 'str'
+to_dict(self) -> 'dict[str, Any]'
+
+
+
+ProvenanceHypergraph class
+ProvenanceHypergraph() -> 'None'
+N-ary Hypergraph structure for dataset, training, and artifact lineage.
+Defined in verifier.data.models
+Method Summary
+add_artifact(self, artifact: 'ArtifactNode') -> 'str'
+add_conflict(self, conflict: 'ConflictRecord') -> 'str'
+add_contributor(self, contributor: 'ContributorSpec') -> 'str'
+add_rights(self, rights: 'RightsSpec') -> 'str'
+add_transformation(self, transform: 'TransformationHyperedge') -> 'str'
+ancestors(self, artifact_ids: 'Iterable[str]') -> 'set[str]'Backward reachability closure across transformation hyperedges.
+blast_radius(self, revoked_artifact_id: 'str') -> 'list[str]'Compute the forward blast radius of affected downstream artifacts when one node is revoked.
+compute_completeness(self) -> 'CompletenessMetrics'
+descendants(self, artifact_ids: 'Iterable[str]') -> 'set[str]'Forward reachability closure across transformation hyperedges.
+has_conflict(self, subject_id: 'str') -> 'bool'
+incoming_hyperedges(self, artifact_id: 'str') -> 'list[TransformationHyperedge]'Hyperedges that produce artifact_id as an output.
+outgoing_hyperedges(self, artifact_id: 'str') -> 'list[TransformationHyperedge]'Hyperedges that consume artifact_id as an input.
+root_sources(self) -> 'set[str]'Artifacts with zero incoming hyperedges (genesis roots).
+to_dict(self) -> 'dict[str, Any]'
+validate_structure(self, *, allow_legacy_identifier_overlap: 'bool' = False) -> 'list[str]'Return deterministic errors for the implemented graph surface.
+verify_acyclicity(self, artifact_ids: 'Optional[Iterable[str]]' = None) -> 'bool'Check whether all or a selected artifact-induced subgraph contains cycles.
+
+
+
+ReproducibilityLevel enum
+Monotone reproduction-fidelity states; class name retained for compatibility.
+Defined in verifier.core.reproducibility
+Members: BITWISE_IDENTICAL, CONTENT_IDENTICAL, EVIDENCE_EQUIVALENT, RESULT_EQUIVALENT, SEMANTIC_REPRODUCTION
+
+
+VerificationGeometry class
+VerificationGeometry(geometry_id: 'str', primary_subject_id: 'str', subjects: 'list[Subject]', loci: 'list[Locus]', facets: 'list[Facet]', coordinates: 'list[Coordinate]', surface: 'VerificationSurface', seams: 'list[Seam]' = <factory>, mechanisms: 'list[VerificationMechanism]' = <factory>, judgments: 'list[CoordinateJudgment]' = <factory>, horizons: 'list[Horizon]' = <factory>, residuals: 'list[Residual]' = <factory>, valences: 'list[VerificationValence]' = <factory>, reconstructions: 'list[ReconstructionAttempt]' = <factory>, verification_layers: 'list[VerificationLayer]' = <factory>, novelties: 'list[Novelty]' = <factory>, secondary_subject_id: 'Optional[str]' = None, focus_coordinate_ids: 'tuple[str, ...]' = (), meta_focus_coordinate_ids: 'tuple[str, ...]' = (), schema_version: 'str' = 'VSTD-2') -> None
+A finite verification geometry and its higher-order audit surface.
+Defined in verifier.core.geometry
+Method Summary
+assess_closure(self) -> 'ClosureAssessment'Assess declared closure and higher-order self-closure separately.
+canonical_digest(self) -> 'str'
+to_dict(self) -> 'dict[str, Any]'
+validate(self) -> 'list[str]'Return structural and epistemic errors; an empty list means valid.
+
+
+
+VerificationSession class
+VerificationSession(evidence: 'EvidenceStore') -> 'None'
+Resolve evidence and rerun only explicitly registered mechanisms.
+Defined in verifier.core.evidence
+Method Summary
+evaluate(self, binding: 'BoundProposition') -> 'EvaluatedProposition'
+evaluate_compound(self, bindings: 'Sequence[BoundProposition]') -> 'tuple[EvaluatedProposition, ...]'Run one compound mechanism invocation over separately bound propositions.
+register(self, mechanism: 'VerificationMechanism') -> 'None'
+
+
+
+VerificationVerdict enum
+Outcome vocabulary returned by the VSTD-1 claim-mechanics checker.
+Defined in verifier.core.checker
+Members: VERIFIED, FALSIFIED, INDETERMINATE, UNSUPPORTED
+
+
+VstdReceipt class
+VstdReceipt(schema_version: 'str', receipt_kind: 'str', receipt_id: 'str', claim: 'ClaimSpec', evidence: 'EvidencePayload', target_result: 'dict[str, Any]', independent_audit: 'IndependentAuditReport', provenance: 'ProvenanceRecord', reproducibility: 'dict[str, Any]', canonical_digest: 'str' = '', execution_metadata: 'Optional[ExecutionMetadata]' = None) -> None
+Mutable in-memory model of a canonically digested VSTD-1 claim receipt.
+Defined in verifier.core.receipt
+Method Summary
+compute_and_set_digest(self) -> 'str'
+get_stable_payload(self) -> 'dict[str, Any]'Extract only deterministic, location-independent fields for canonical hashing.
+save_to_directory(self, out_dir: 'Path') -> 'Path'
+to_dict(self) -> 'dict[str, Any]'
+verify_digest_integrity(self) -> 'bool'
+
+
+
+WitnessBundle class
+WitnessBundle(claim_id: 'str', declarant_id: 'str', claim_binding_digest: 'str', witnesses: 'tuple[WitnessIdentity, ...]', independence: 'tuple[IndependenceAssertion, ...]', corroborations: 'tuple[CorroborationRecord, ...]') -> None
+Claim-bound identities, ordered separation assertions, and corroborations.
+Defined in verifier.core.witness
+Method Summary
+to_dict(self) -> 'dict[str, Any]'
+
+
+
+assess_witness_corroboration function
+assess_witness_corroboration(entry: 'EvidenceBoundDepthResult', bundle: 'WitnessBundle', *, session: 'VerificationSession') -> 'WitnessCorroborationResult'
+Recheck VSTD-5 entry, separation evidence, and corroboration evidence.
+Defined in verifier.core.witness
+
+
+
+build_evidence_bound_graph_level_record function
+build_evidence_bound_graph_level_record(result: 'EvidenceBoundGraphLevelResult', *, graph: 'ProvenanceHypergraph', members: 'Sequence[str]', binding: 'ClaimBinding', object_evidence: 'Mapping[str, BoundProposition]', edge_evidence: 'Mapping[str, BoundProposition]', session: 'VerificationSession') -> 'dict[str, Any]'
+Serialize exact Graph rating bindings and bytes for offline replay.
+Defined in verifier.data.graph_level
+
+
+
+build_evidence_bound_vstd4_receipt function
+build_evidence_bound_vstd4_receipt(result: 'EvidenceBoundDepthResult', *, receipt_id: 'str', claim_id: 'str', binding: 'ClaimBinding', prerequisite_evidence: 'Mapping[int, BoundProposition]', rung_evidence: 'Mapping[str, BoundProposition]', session: 'VerificationSession', status: 'str' = 'VALID') -> 'dict[str, object]'
+Serialize every input needed to rerun an evidence-bound VSTD-4 result.
+Defined in verifier.core.depth
+
+
+
+build_vstd5_receipt function
+build_vstd5_receipt(entry: 'EvidenceBoundDepthResult', bundle: 'WitnessBundle', result: 'WitnessCorroborationResult', *, receipt_id: 'str', session: 'VerificationSession') -> 'dict[str, Any]'
+Serialize a replayable VSTD-5 receipt without treating names as trust.
+Defined in verifier.core.witness
+
+
+
+capture_run function
+capture_run(manifest: 'Mapping[str, Any]', manifest_dir: 'Path', receipt_id: 'Optional[str]' = None) -> 'GenericRunReceipt'
+Execute the manifest-declared command and capture a computational run receipt.
+Defined in verifier.core.run
+
+
+
+certificate_from_canonical_bytes function
+certificate_from_canonical_bytes(data: 'bytes') -> 'DecisionCertificate'
+Decode only the canonical JSON representation used in commitment digests.
+Defined in verifier.core.certificate
+
+
+
+claim_binding_from_dict function
+claim_binding_from_dict(data: 'Mapping[str, object]') -> 'ClaimBinding'
+Reconstruct the exact VSTD-4 claim binding carried by a receipt.
+Defined in verifier.core.depth
+
+
+
+compute_canonical_digest function
+compute_canonical_digest(stable_payload: 'Mapping[str, Any]') -> 'str'
+Compute SHA-256 digest of canonicalized stable payload.
+Defined in verifier.core.receipt
+
+
+
+establish_graph_level function
+establish_graph_level(graph: 'ProvenanceHypergraph', *, collection_id: 'str', members: 'Sequence[str]', object_evidence: 'Mapping[str, BoundProposition]', edge_evidence: 'Mapping[str, BoundProposition]', session: 'VerificationSession', binding: 'ClaimBinding') -> 'EvidenceBoundGraphLevelResult'
+Rerun rating mechanisms before computing a conforming Graph profile.
+Defined in verifier.data.graph_level
+
+
+
+establish_vstd4 function
+establish_vstd4(rung_evidence: 'Mapping[str, BoundProposition]', *, prerequisite_evidence: 'Mapping[int, BoundProposition]', session: 'VerificationSession', claim_id: 'str', binding: 'ClaimBinding') -> 'EvidenceBoundDepthResult'
+Rerun evidence mechanisms and establish VSTD-4 only if all pass.
+Defined in verifier.core.depth
+
+
+
+freeze_artifact function
+freeze_artifact(source: 'str | Path', bundle: 'str | Path', *, media_type: 'str' = 'application/octet-stream', parent_bundles: 'Iterable[str | Path]' = (), context_bundles: 'Iterable[str | Path]' = ()) -> 'dict[str, Any]'
+Preserve exact bytes in a new guarded bundle without creating a seal.
+Defined in verifier.artifact_control
+
+
+
+graph_collection_binding_digest function
+graph_collection_binding_digest(graph: 'ProvenanceHypergraph', *, collection_id: 'str', members: 'Sequence[str]', binding: 'ClaimBinding') -> 'str'
+Bind ratings to one Graph, member set, collection, and claim coordinate.
+Defined in verifier.data.graph_level
+
+
+
+recheck_assurance_log function
+recheck_assurance_log(payload: 'Mapping[str, Any]', *, mechanisms: 'Iterable[VerificationMechanism]') -> 'AssuranceLedger'
+Rebuild and replay a portable assurance log from its embedded bytes.
+Defined in verifier.data.assurance
+
+
+
+recheck_evidence_bound_graph_level_record function
+recheck_evidence_bound_graph_level_record(graph: 'ProvenanceHypergraph', record: 'Mapping[str, Any]', *, mechanisms: 'Sequence[VerificationMechanism]') -> 'EvidenceBoundGraphLevelResult'
+Rebuild the evidence store, rerun rating mechanisms, and compare result.
+Defined in verifier.data.graph_level
+
+
+
+recheck_evidence_bound_vstd4_receipt function
+recheck_evidence_bound_vstd4_receipt(receipt: 'Mapping[str, object]', *, mechanisms: 'Sequence[VerificationMechanism]') -> 'EvidenceBoundDepthResult'
+Reconstruct evidence bytes and rerun an evidence-bound VSTD-4 receipt.
+Defined in verifier.core.depth
+
+
+
+recheck_vstd5_receipt function
+recheck_vstd5_receipt(entry: 'EvidenceBoundDepthResult', receipt: 'Mapping[str, Any]', *, mechanisms: 'tuple[VerificationMechanism, ...]') -> 'WitnessCorroborationResult'
+Import exact bytes, rerun all witness mechanisms, and compare the result.
+Defined in verifier.core.witness
+
+
+
+require_vstd5_entry function
+require_vstd5_entry(result: 'DepthResult | EvidenceBoundDepthResult') -> 'EvidenceBoundDepthResult'
+Reject the current unbound candidate result at the VSTD-5 boundary.
+Defined in verifier.core.depth
+
+
+
+seal_artifact function
+seal_artifact(bundle: 'str | Path', private_key: 'str | Path') -> 'dict[str, Any]'
+Add one deterministic, readable, self-closing Ed25519 seal.
+Defined in verifier.artifact_control
+
+
+
+thaw_artifact function
+thaw_artifact(bundle: 'str | Path', destination: 'str | Path', *, expected_artifact_id: 'str | None' = None, expected_key_id: 'str | None' = None) -> 'dict[str, Any]'
+Create a mutable descendant from a cleanly sealed frozen artifact.
+Defined in verifier.artifact_control
+
+
+
+thawed_artifact_status function
+thawed_artifact_status(artifact: 'str | Path', thaw_record: 'str | Path | None' = None, *, parent_bundle: 'str | Path | None' = None, expected_artifact_id: 'str | None' = None, expected_key_id: 'str | None' = None) -> 'dict[str, Any]'
+Assess current descendant equality without authenticating the historical copy.
+Defined in verifier.artifact_control
+
+
+
+validate_run_receipt function
+validate_run_receipt(receipt_path_or_dir: 'Path') -> 'int'
+Validate one generic-run receipt's required fields and stable canonical digest.
+Defined in verifier.core.run_validation
+
+
+
+verify_frozen_artifact function
+verify_frozen_artifact(bundle: 'str | Path', *, expected_artifact_id: 'str | None' = None, expected_key_id: 'str | None' = None, require_seal: 'bool' = True) -> 'ArtifactVerification'
+Recompute preserved bytes, write guards, closure, and optional external anchors.
+Defined in verifier.artifact_control
+
+
+
+vstd4_depth function
+vstd4_depth(evidence: 'Mapping[str, str]', *, claim_id: 'str', binding: 'ClaimBinding') -> 'DepthResult'
+Compute a structural candidate depth from caller-supplied references.
+Defined in verifier.core.depth
+
+
+
+
+
+
+
+
Wire
+
Canonical schemas and identifiers.
+
Receipt schemas are served from this site at their canonical
+ $id routes, and their serialized `schema_version` identifiers are listed in the
+ standard.
+
+
+
+
+
+
+
+
diff --git a/docs/standards/SCITT_SEMANTIC_BOUNDARY.md b/docs/standards/SCITT_SEMANTIC_BOUNDARY.md
new file mode 100644
index 0000000..8a69f8f
--- /dev/null
+++ b/docs/standards/SCITT_SEMANTIC_BOUNDARY.md
@@ -0,0 +1,211 @@
+# Supply Chain Integrity, Transparency, and Trust (SCITT) semantic boundary for Verifier Standard (VSTD) interoperability
+
+> **Acronyms:** Concise Binary Object Representation (CBOR); CBOR Object Signing and Encryption (COSE); grounded decision certificate (GDC);
+> JavaScript Object Notation (JSON); Request for Comments (RFC); Secure Hash Algorithm 256-bit (SHA-256);
+> Transparency Service (TS); verifiable data structure proof (VDP); verifiable data structure (VDS);
+> working group (WG).
+
+> **Status:** experimental, non-normative. This boundary follows [RFC 9943](https://datatracker.ietf.org/doc/html/rfc9943), [RFC 9942](https://datatracker.ietf.org/doc/html/rfc9942), and the current repository's implemented VSTD specifications. It does not claim SCITT WG review.
+
+## SCITT can establish
+
+Subject to the named trust anchors, keys, algorithms, VDS profile, registration
+policy, receipt validity period, and relying-party checks, SCITT can establish:
+
+- which exact Signed Statement bytes an issuer signed;
+- the authenticated `iss` and `sub` protected claims and payload media type;
+- that a TS applied its then-current registration policy before registration;
+- that the Signed Statement was included in the TS's VDS state represented by a
+ valid COSE Receipt;
+- the VDS proof properties implemented by the Receipt profile, such as inclusion
+ and, where supported, consistency;
+- append-only/non-equivocation evidence and auditable registration history;
+- enough registration collateral for authorized auditors to reproduce the
+ registration checks required by RFC 9943;
+- historical relationships such as later same-issuer/same-subject statements that
+ a relying-party policy may treat as superseding earlier statements.
+
+RFC 9943 is explicit that an issuer can make a false statement and that registration
+only proves the statement was produced by the issuer. A SCITT Receipt is therefore
+not a generic certificate of payload truth.
+
+## VSTD can establish
+
+VSTD is not the domain verifier or proof engine. It is the standard domain language
+and operator/result layer through which those orchestrated substrates expose
+portable claim boundaries and results. Only for its declared claim, coordinate,
+evidence, policy, native verifier fragment, and resource bounds, the implemented
+VSTD numbered profiles can establish:
+
+- claim-mechanics and declared falsification conditions;
+- an explicit verification surface and claim coordinate;
+- substrate/accountability evidence within VSTD-3's implemented capability model;
+- an accepted VSTD4-GDC-1 certificate result of PASS, FAIL, or UNKNOWN without
+ upgrading it to VSTD-4 profile conformance;
+- grounding between a bounded logical encoding and named artifact facts;
+- checker-side recomputation of the VSTD-4 certificate without sharing verdict-producing
+ code;
+- a bounded cost/memory/certificate-size ceiling and honest refusal when exceeded;
+- Graph lineage and blast-radius queries plus candidate degradation from statuses already
+ recorded in VSTD-Graph. This SCITT example does not invoke the evidence-bound Graph
+ rating or challenge-projection mechanisms, so those results remain `NOT_ESTABLISHED`
+ for this example.
+
+The native solver, proof engine, signature checker, identity service, transparency
+log, or provenance system retains its own semantics and result. A loss-declared
+adapter maps that result into VSTD's verification interlingua and records the
+boundary around its portable composition; VSTD does not absorb or reimplement the
+substrate.
+
+VSTD-5 and VSTD-Graph-5 have implemented evidence-bound reference paths, but this SCITT
+example supplies neither a qualifying witness nor profile-5 rating evidence. A later VSTD profile does not supply a missing
+prerequisite coordinate.
+
+## Identity, disclosure, trust, and reputation
+
+VSTD verification is claim-first. Deciding a bounded claim does not, merely by
+being a VSTD check, require a natural-person identity, creator identity, or
+persistent actor identity. Some VSTD numbered and application profiles name devices, verifier
+implementations, evidence sources, or witnesses where those coordinates are part
+of the claim. Such identifiers do not automatically establish authorship,
+authority, independence, reputation, or real-world identity.
+
+SCITT composition is therefore **optional**, not a prerequisite for VSTD. An RFC
+9943 Signed Statement introduces an authenticated issuer coordinate, and public
+registration may expose stable identifiers, subjects, payload bytes or digests,
+timing, and relationship metadata. A key or pseudonym need not identify a natural
+person, but it can still be linkable. Wrapping a VSTD receipt in SCITT adds an
+accountability/transparency proposition; it does not strengthen the native VSTD
+computational proposition and can weaken an identity-minimizing privacy posture.
+
+The implemented VSTD core is disclosure-neutral, not itself a zero-knowledge proof
+protocol. Under the governing architecture, zero identity means zero identity-derived
+verdict weight and zero knowledge means zero unevidenced knowledge is presumed. Neither
+alone is a privacy claim. When a witness must remain confidential, a cryptographic
+zero-knowledge proof can enclose that architectural rule by binding the exact program,
+predicate, public commitments, output, parameters, and verifier. Full-disclosure receipts
+remain valid, and no receipt may claim the cryptographic zero-knowledge property without a
+real proof-system guarantee. “Trustless”
+must mean trust-minimized and assumption-explicit: a relying party still depends on
+selected algorithms, checker code, canonicalization, policy, input availability, and,
+when used, proof-system parameters or trust roots.
+
+VSTD-Graph can preserve artifact history, challenges, lifecycle changes, and
+refutations, but the current standard does not define a scalar artifact-reputation
+score. The governing semantics instead distinguish TRUST, mechanism-earned artifact
+support; ROT, typed time-indexed degradation of current admissibility; and RUST,
+inverse-TRUST diagnostic traversal toward recorded ancestors. Their reference transfer
+mechanisms must never overwrite a native verdict or turn actor identity, repeated
+registrations, signatures, observations, age, or reputation into process validity.
+
+## Neither establishes automatically
+
+Neither a valid SCITT Receipt nor a valid VSTD receipt automatically establishes:
+
+- truth of arbitrary physical-world or historical propositions;
+- completeness of evidence that was never disclosed or discoverable;
+- causal correctness or causal influence merely from recorded lineage;
+- safety, harmlessness, fitness for purpose, or regulatory compliance;
+- authorization, rights, ownership, or permission merely from identity or
+ provenance;
+- provenance merely from integrity or a matching digest;
+- computational correctness merely from signature validity or registration;
+- issuer independence, uniqueness, Sybil resistance, or lack of collusion;
+- current validity merely from historical inclusion;
+- correct policy selection merely because a policy identifier is present;
+- privacy, anonymity, confidentiality, or unlinkability.
+
+## Two receipts, two propositions
+
+| Artifact | Native proposition |
+|---|---|
+| VSTD receipt | The declared bounded computational result and its evidence/refutation boundary. |
+| SCITT COSE Receipt | A VDS property, normally inclusion of the exact Signed Statement under a TS identity and proof profile. |
+
+The experimental profile places the first inside the payload of a SCITT Signed
+Statement and attaches the second to that statement. Implementations must name the
+receipt type whenever “receipt” would be ambiguous.
+
+The unwrapped VSTD receipt remains checkable outside its producer. Selecting the SCITT
+profile deliberately adds issuer and transparency coordinates; it is not the
+default wire path for an identity-independent or witness-private VSTD profile.
+
+## Trust coordinates that must remain visible
+
+### SCITT
+
+- issuer key/certificate and identity interpretation;
+- TS receipt-verification key and TS identity;
+- VDS/VDP profile and algorithm;
+- registration policy and policy version/state;
+- statement subject and content type;
+- registration/receipt time and freshness policy;
+- key-compromise, supersession, revocation, and discovery policy;
+- external native verifier implementation/version.
+
+### VSTD
+
+- claim, subject, predicate, and parameters;
+- policy root and evidence root;
+- artifact identities and content digests;
+- verifier specification, implementation, parser, and supported fragment;
+- resource bounds and prior commitment;
+- certificate format, verdict, reason, and native lifecycle status;
+- evidence availability, challenges, and graph ancestors when applicable.
+
+## Composition rule
+
+A composed PASS is permitted only when all of the following hold:
+
+1. the native VSTD checker accepts a VSTD PASS without sharing verdict-producing code;
+2. the full VSTD payload digest matches the payload signed in the SCITT statement;
+3. the SCITT statement signature is valid under an accepted issuer policy;
+4. the SCITT Receipt is valid for that exact statement under an accepted TS/VDS
+ policy;
+5. the SCITT subject equals the VSTD claim-coordinate subject;
+6. the observed artifact digests equal the VSTD-bound artifact digests;
+7. the required evidence is current and neither revoked, superseded, conflicted,
+ missing, nor unavailable under the declared relying-party policy.
+
+Any single failed condition prevents PASS. Registration never repairs a failed VSTD
+claim. A VSTD PASS never fabricates missing SCITT transparency.
+
+## UNKNOWN and lifecycle behavior
+
+SCITT core does not define one application-level UNKNOWN verdict. The individual
+[Composite Evidence Verification draft](https://datatracker.ietf.org/doc/draft-nobuo-scitt-composite-evidence-verification/)
+proposes `unknown`, `missing`, `stale`, `conflict`, and `warning`, but it is not an
+adopted WG standard and its result precedence remains draft work.
+
+VSTD UNKNOWN is bounded and reason-bearing. In VSTD-4, resource exhaustion,
+unavailable dependencies, unavailable verifiers, and unretrievable artifacts have
+distinct indeterminacy reasons. Therefore adapters must preserve both the native
+SCITT condition and native VSTD reason. Label equality alone is not semantic
+equivalence.
+
+Historical SCITT inclusion may remain valid while current VSTD usability falls. For
+example, a receipt can still prove that a statement was registered in the past even
+after a relying party considers its evidence stale or an ancestor revoked. The
+adapter records both facts rather than deleting history or treating inclusion as
+current computational validity.
+
+## Implementation boundary
+
+The module in `src/verifier/interoperability/scitt/`:
+
+- emits deterministic application payload bytes and a normalized registration
+ template;
+- does **not** claim that JSON is the SCITT serialized transport format;
+- requires a native RFC 9943/COSE producer to create a real Signed Statement;
+- requires a native RFC 9942 verifier to validate a COSE Receipt;
+- consumes the native verifier's output only under explicit issuer, subject, payload,
+ policy, TS, and VDS coordinates;
+- requires a separately bound native VSTD checker result for the exact embedded
+ receipt; a receipt's declared `PASS` is not evidence that it was checked;
+- returns `computational_verdict = NOT_EVALUATED` when adapting SCITT evidence alone;
+- rejects unknown mappings instead of guessing.
+
+The example uses pinned optional libraries to create and verify real COSE bytes and
+an RFC 9162 SHA-256 inclusion receipt in a local one-entry test log. That demonstrates
+the cryptographic boundary but does not represent a production TS, public witness,
+or public anchoring.
diff --git a/docs/standards/VSTD_SCITT_CROSSWALK.md b/docs/standards/VSTD_SCITT_CROSSWALK.md
new file mode 100644
index 0000000..9fad6c9
--- /dev/null
+++ b/docs/standards/VSTD_SCITT_CROSSWALK.md
@@ -0,0 +1,188 @@
+# Verifier Standard (VSTD) and Internet Engineering Task Force (IETF) Supply Chain Integrity, Transparency, and Trust (SCITT): experimental interoperability crosswalk
+
+> **Acronyms:** artificial intelligence (AI); application programming interface (API);
+> Concise Binary Object Representation (CBOR); Confidential Consortium Framework (CCF);
+> CBOR Object Signing and Encryption (COSE); CBOR Web Token (CWT); European Union (EU);
+> grounded decision certificate (GDC); Hypertext Transfer Protocol (HTTP); Request for Comments (RFC);
+> Supply Chain Integrity, Transparency, and Trust (SCITT); SCITT Reference APIs (SCRAPI); Transparency Service (TS);
+> verifiable data structure proof (VDP); verifiable data structure (VDS); working group (WG); zero-knowledge (ZK).
+
+> **Status:** experimental, non-normative, reviewed against public specifications on
+> 2026-08-25. This document does not alter VSTD semantics and does not imply IETF,
+> SCITT Working Group, or implementation-provider endorsement.
+
+## Result
+
+The working thesis survives with one important correction:
+
+> **SCITT authenticates statements and makes their policy-governed registration in a
+> verifiable data structure transparent and portable. VSTD is a standard domain
+> language for verification: an interlingua that standardizes the claim boundary and
+> portable result semantics by which a domain verifier or proof engine's bounded
+> result is represented, binding-checked, refuted, mapped, and composed with adjacent
+> evidence.**
+
+SCITT is not merely transport. It already specifies issuer/subject binding, signed
+statements, registration-policy evaluation, append-only and non-equivocating
+transparency, portable COSE receipts, and replayable registration audits. VSTD must
+not rename those mechanisms as VSTD inventions. Conversely, SCITT explicitly allows
+false statements to be registered and leaves payload truth to application-domain
+semantics. VSTD does not replace those application-domain semantics or engines. It
+is the general operator-language class; native verifiers, proof engines, and other
+evidence substrata are the orchestrated implementations whose own outputs and limits
+remain authoritative and visible. Explicit adapters make the mapping and any loss
+reviewable. That is the clean VSTD-shaped boundary.
+
+## Sources and exact status
+
+| Document | Status on 2026-08-25 | Relevance |
+|---|---|---|
+| [RFC 9943: SCITT Architecture](https://datatracker.ietf.org/doc/html/rfc9943) | IETF Standards Track RFC, **Proposed Standard**, June 2026 | Normative SCITT architecture, Signed Statements, Registration, Receipts, Transparent Statements, and security boundary. |
+| [RFC 9942: COSE Receipts](https://datatracker.ietf.org/doc/html/rfc9942) | IETF Standards Track RFC, **Proposed Standard**, June 2026 | COSE Receipt wrapper, VDS/VDP registries, RFC 9162 inclusion and consistency proof encodings. |
+| [draft-ietf-scitt-scrapi-11](https://datatracker.ietf.org/doc/html/draft-ietf-scitt-scrapi-11) | **Active SCITT WG Internet-Draft**, intended Proposed Standard, in the RFC Editor Queue; not yet an RFC | HTTP registration, asynchronous completion, receipt resolution, and TS key discovery. |
+| [draft-ietf-scitt-receipts-ccf-profile-04](https://datatracker.ietf.org/doc/html/draft-ietf-scitt-receipts-ccf-profile-04) | **Active SCITT WG Internet-Draft**, intended Proposed Standard, in IETF Last Call through 2026-09-07; not an RFC | CCF ledger VDS and inclusion-proof profile for COSE Receipts. |
+| [draft-nobuo-scitt-composite-evidence-verification-00](https://datatracker.ietf.org/doc/draft-nobuo-scitt-composite-evidence-verification/) | **Active individual Internet-Draft**, no WG adoption or formal standing | Closest work: composite verification of statements, receipts, bindings, relationships, freshness, conflicts, and bundles under a named profile. |
+| [draft-nobuo-scitt-protected-object-binding-00](https://datatracker.ietf.org/doc/draft-nobuo-scitt-protected-object-binding/) | **Active individual Internet-Draft**, no WG adoption or formal standing | Proposed object bindings and statement-graph relationships; explicitly does not establish payload truth. |
+| [draft-emirdag-scitt-ai-agent-execution-00](https://datatracker.ietf.org/doc/html/draft-emirdag-scitt-ai-agent-execution-00) | **Active individual Internet-Draft**, no stream or WG adoption; its draft header says intended Informational | Agent-execution records, sequence completeness, evidence custody, and redaction receipts. |
+| [draft-noa-scitt-ai-agent-receipt-01](https://datatracker.ietf.org/doc/html/draft-noa-scitt-ai-agent-receipt-01) | **Active individual Internet-Draft**, no stream or WG adoption; its draft header says Standards Track | Per-action receipt profile with narrow claims, validity/sufficiency separation, absence/indeterminacy semantics, and explicit external-world limits. |
+| [draft-dawkins-scitt-ai-article50-00](https://datatracker.ietf.org/doc/html/draft-dawkins-scitt-ai-article50-00) | **Active individual Internet-Draft**, no stream or WG adoption | AI-transparency receipt profile for selected EU AI Act Article 50 disclosure claims. |
+| [draft-mih-scitt-agent-action-capsule-02](https://datatracker.ietf.org/doc/html/draft-mih-scitt-agent-action-capsule-02) | **Active individual Internet-Draft**, no stream or WG adoption | Agent Action Capsule payload profile separating dispatched attempts, observed results, and human-in-the-loop records. |
+| [draft-mih-scitt-agent-action-capsule-sel-disc-00](https://datatracker.ietf.org/doc/html/draft-mih-scitt-agent-action-capsule-sel-disc-00) | **Active individual Internet-Draft**, no stream or WG adoption | Selective-disclosure construction and missing-required-field behavior for Agent Action Capsules. |
+| [draft-hillier-scitt-arp-03](https://datatracker.ietf.org/doc/html/draft-hillier-scitt-arp-03) | **Active individual Internet-Draft**, no stream or WG adoption | Attestation reconciliation, query binding, divergence axes, policy coordinates, and budget-exhaustion concerns. |
+| [draft-dogru-scitt-disclosure-evidence-07](https://datatracker.ietf.org/doc/html/draft-dogru-scitt-disclosure-evidence-07) | **Active individual Internet-Draft**, no stream or WG adoption | Transformation evidence and coverage reconciliation, including excluded and indeterminate coverage outcomes. |
+| [draft-le-scitt-derived-subjects-00](https://datatracker.ietf.org/doc/html/draft-le-scitt-derived-subjects-00) | **Active individual Internet-Draft**, no stream or WG adoption | Deterministic subject derivation across independently governed identifier schemes. |
+| [draft-mih-sokolov-scitt-payload-binding-01](https://datatracker.ietf.org/doc/html/draft-mih-sokolov-scitt-payload-binding-01) | **Active individual Internet-Draft**, no stream or WG adoption | Canonical payload binding and cross-profile digest references; appraisal remains in consuming profiles. |
+
+Internet-Drafts are work in progress. The individual drafts above are proposals by
+their authors, not IETF or SCITT WG positions. Earlier draft revisions that have been
+replaced or expired were not used as current authority. None of the documents relied
+on in this table is expired as of the review date.
+
+## Architecture decision
+
+The cleanest arrangement is **optional bidirectional composition with separate
+verdicts**. SCITT is not a prerequisite for VSTD and is not the default publication
+path for an identity-independent or witness-private VSTD profile:
+
+1. **VSTD inside SCITT:** a complete VSTD receipt is the application payload of an
+ RFC 9943 Signed Statement. The SCITT protected headers bind issuer, subject,
+ content type, and signature. A COSE Receipt proves registration/inclusion under
+ the selected TS, VDS, registration policy, key, and time assumptions.
+2. **SCITT evidence inside VSTD:** output from a native SCITT verifier may be VSTD
+ evidence for a narrowly stated transparency proposition, such as “this exact
+ statement was signed by an accepted issuer and included in this TS VDS under this
+ policy.” It is not evidence that silently settles the statement's computational
+ payload. SCITT is one orchestrated substrate, not a privileged source of truth.
+3. **Graph composition:** a SCITT statement-graph profile may identify registered
+ statements, object bindings, edges, supersession, and conflicts. VSTD-Graph can
+ evaluate bounded predicates over selected nodes and edges, but each graph's
+ native identifiers, status semantics, and policy remain visible.
+
+This is not recursive self-certification. SCITT and VSTD remain adjacent systems with
+different trust roots and different questions. Selecting SCITT deliberately adds
+issuer authentication, registration policy, transparency, and possible correlation;
+omitting SCITT leaves those properties unclaimed rather than making them UNKNOWN
+VSTD computational evidence.
+
+## Rigorous crosswalk
+
+| Concern | VSTD | SCITT | Overlap | Difference | Composition |
+|---|---|---|---|---|---|
+| Claim identity | Receipt and claim identifiers; VSTD-4 binds a claim string and coordinate. | Signed Statement bytes plus issuer/subject and payload media type identify a statement context. | Both bind an assertion to named coordinates. | SCITT identity is signed-statement identity; VSTD identity includes bounded computational semantics. | Carry the native VSTD receipt intact and bind its full payload digest in the SCITT statement. |
+| Actor identity | A bounded artifact claim need not identify a natural person, creator, or persistent actor; profile-specific device/verifier/witness identifiers do not imply authorship or authority. | A Signed Statement authenticates a declared issuer under a relying-party trust policy; the issuer can be a key or pseudonym but may be linkable. | Both may bind identifiers when the declared proposition needs them. | SCITT issuer authentication is central to accountability; actor identity is not required for every VSTD computation. | Make SCITT wrapping optional and never copy issuer reputation into the native VSTD verdict. |
+| Disclosure / zero knowledge | Core VSTD is disclosure-neutral; current receipts may disclose evidence, and experimental ZK profiles must supply real proof-system guarantees. | Registration makes signed statement material or commitments available under TS policy and can expose timing, subjects, and relationships. | Either can carry commitments or proofs defined by an application profile. | Neither RFC 9943 nor current VSTD core automatically provides witness confidentiality, anonymity, or unlinkability. | Treat privacy effects as an explicit profile property; do not label this full-disclosure example ZK or zero identity. |
+| Artifact TRUST, ROT, and RUST | Graph history can record mechanism-earned support, challenges, staleness, supersession, revocation, refutation, and recorded ancestry; no normative scalar reputation score exists. | Logs provide durable registration history and issuer accountability, not payload reputation or truth. | Both can contribute time-indexed observations about one artifact. | Repetition, age, issuer identity, and reputation do not increase epistemic strength. ROT is current-admissibility degradation; RUST is reverse diagnostic reachability, not guilt or causal localization. | Keep any TRUST transfer, ROT derivation, or RUST traversal separately mechanism-bound and unable to upgrade a native result. |
+| Subject identity | VSTD-2/VSTD-4 coordinate `subject`. | Protected CWT `sub` claim; issuer-defined and usable to correlate statements. | Both name what a claim is about. | Equal spelling does not prove equal interpretation. | Require exact subject equality under the experimental profile; reject mismatch. |
+| Predicates | Explicit VSTD predicate and parameters. | Payload/application profile defines predicate semantics; SCITT core is content-agnostic. | A VSTD predicate can be a SCITT payload predicate. | SCITT core does not define the VSTD predicate. | Preserve predicate and parameters in the payload projection and full receipt. |
+| Parameters | Bound into VSTD claim coordinates and canonical receipt. | May appear in opaque payload or profile-defined protected fields. | Both can integrity-bind parameters. | SCITT has no generic computational-parameter semantics. | Keep parameters in VSTD payload; only promote selected values to protected headers after profile review. |
+| Explicit limits | VSTD claim limitations, excluded claims, and refutation surface. | RFC 9943 states architectural/security limits; application payload profiles may add limits. | Both can document scope. | VSTD makes per-result bounds part of verification semantics. | Carry VSTD limits without translating them into SCITT registration-policy claims. |
+| Issuer identity | May occur in provenance, but VSTD core does not replace signing identity infrastructure. | Protected `iss`; signature and trust-anchor validation are mandatory registration concerns. | Both may record a producer. | SCITT owns signed issuer authentication; VSTD ownership/authorship is not inferred from integrity. | Reuse SCITT issuer authentication and keep it separate from VSTD computational outcome. |
+| Signatures | VSTD can consume signature evidence; it does not define a universal signing system. | COSE_Sign1 is normative for Signed Statements and Receipts. | VSTD can reference verified signature evidence. | SCITT already standardizes the envelope and signature placement. | When the SCITT profile is selected, use SCITT/COSE rather than inventing a competing envelope. |
+| Artifact binding | VSTD binds content-addressed subjects/evidence roots and checks wrong-artifact cases. | `sub`, payload hashes/detached payloads, and signed envelope bind statements to declared artifacts. | Both defend substitution through different mechanisms. | SCITT proves what bytes/subject the issuer signed, not that VSTD evaluated the intended artifact correctly. | Require exact VSTD artifact digests and SCITT payload digest; either mismatch fails composition. |
+| Statement registration | Not a VSTD core function. | TS applies registration policy, inserts the statement, and issues a receipt. | None needed. | SCITT already owns this function. | VSTD should consume the result, not recreate registration. |
+| Transparency | VSTD can record published artifacts but defines no generic transparency service. | Core objective: auditable, accountable signed-content transparency. | VSTD receipts are suitable transparent payloads. | SCITT provides the standardized transparency machinery. | Register through SCITT when public accountability is desired; do not require it for identity-independent/private verification. |
+| Append-only logs | VSTD-Graph records additive challenge history but is not a general public log protocol. | SCITT VDS must be append-only, non-equivocating, and replayable. | Both avoid rewriting history. | SCITT defines the log/VDS guarantees and receipts. | Use SCITT VDS rather than a VSTD-specific transparency log. |
+| Portable receipts | VSTD receipts carry computational evidence and bounds. | COSE Receipts carry signed VDS proofs and attach to Transparent Statements. | Both produce portable evidence artifacts. | “Receipt” names different proof targets. | Name both explicitly: VSTD computational receipt inside a SCITT Signed Statement; SCITT COSE Receipt outside it. |
+| Evidence bundles | VSTD receipts and graph collections may contain evidence references. | Core permits payloads; composite-evidence draft proposes bundles under profiles. | Both can package evidence sets. | The SCITT bundle model is currently an individual proposal, not a WG standard. | Use a VSTD payload now; discuss bundle alignment before standardizing graph exchange. |
+| Provenance graphs | VSTD-Graph records typed artifact/transformation lineage and computes candidate degradation from statuses already recorded in the Graph. | RFC 9943 correlates statements by subject; individual drafts propose object bindings and statement graphs. | Both can connect evidence about shared subjects. | SCITT core does not standardize the proposed statement-graph vocabulary; VSTD lineage is not causal proof. | Reference native SCITT statement IDs from VSTD-Graph without rewriting either graph. |
+| Statement graphs | VSTD-Graph has implemented graph structures, policy queries, and candidate-profile computation over caller-supplied ratings; conformance is `NOT_ESTABLISHED`. | Proposed by individual object-binding/composite drafts. | Both need explicit edge semantics and policy. | Maturity and graph objects differ. | Experimental bridge only; no claim of SCITT WG alignment. |
+| Dependencies | VSTD-4 can return `UNKNOWN/DEPENDENCY_UNAVAILABLE`; Graph evaluates transitive ancestors. | Composite draft proposes required statements and dependency edges. | Both surface unavailable dependencies. | SCITT core receipt validity does not settle application dependency completeness. | Preserve the native missing reason and let VSTD issue its own bounded indeterminacy certificate. |
+| Revocation | The challenge ledger derives append-only claim state. `AssuranceLedger.project_challenges` binds the complete record set into an additive Graph current-state view, and Graph recomputation degrades when a reached ancestor is `REVOKED`. | RFC 9943 discusses compromised-key handling but leaves revocation strategies out of scope; individual composite draft proposes revocation statements/checks. | Both can react to invalidated evidence. | VSTD's implemented projection consumes native VSTD challenge records, not SCITT revocation statements; SCITT-to-VSTD event mapping still requires an explicit application mechanism. | Preserve each native state. A SCITT application adapter must bind the exact statement, artifact, event, and policy before invoking the VSTD projection or another Graph status mechanism. |
+| Supersession | VSTD-Graph records `SUPERSEDED` without automatically making the older node inadmissible. | RFC 9943 permits later same-issuer/same-subject statements to supersede earlier ones; selection is relying-party policy. | Both preserve history. | Neither makes “newer” automatically “truer”; policy consequences differ. | Normalize `SUPERSEDED` without upgrading; require explicit current-evidence policy. |
+| Conflicts | VSTD preserves `CONFLICTED` where defined and graph blockers. | RFC 9943 allows conflicting issuers; individual composite draft proposes `conflict`. | Both refuse silent reconciliation. | SCITT core delegates issuer selection; VSTD may express a bounded conflict result. | Preserve `CONFLICTED` as distinct from UNKNOWN and FAIL. |
+| Freshness | VSTD bounds and evidence can include time/freshness; stale Graph artifacts are inadmissible to candidate Graph profiles. | Receipt state is true when issued; keys/policies can change; application policies determine freshness. SCRAPI can issue fresh receipts. | Both require time-indexed trust coordinates. | Inclusion is historical; it does not establish current payload validity. | Carry registration time, policy, key/VDS, and freshness decision separately. |
+| Verification profiles | VSTD numbered profiles and verifier descriptors define supported fragments. | RFC 9942 defines VDS profiles; RFC 9943 permits application profiles; composite draft proposes named verification profiles. | Both use explicit capability/profile identifiers. | VDS proof profile is not computational predicate profile. | Bind both profile identifiers; never collapse them. |
+| Resource bounds | VSTD-4 preflights verification cost, memory, and certificate size. | SCITT core has operational limits but no payload-domain computational-verdict resource model. | Both can reject over-limit inputs operationally. | SCRAPI 429/204 is protocol state, not epistemic UNKNOWN. | Keep VSTD bounds in payload and preserve resource exhaustion as VSTD UNKNOWN. |
+| Computational grounding | VSTD-4 binds variables/clauses to facts, subjects, rules, policy/evidence roots, and verifier code. | SCITT can register such a payload but does not define those semantics. | SCITT can integrity-protect grounding artifacts. | Grounding correctness is distinctively VSTD here. | SCITT carries and makes the grounded certificate transparent; VSTD kernel checks it. |
+| Reproduction | VSTD declares reproduction-fidelity states and executable falsification paths. | SCITT auditors reproduce registration checks from retained statements, collateral, policy, and trust anchors. | Both support independent replay. | They replay different decisions. | Report `VSTD_CHECK_REPLAY` and `SCITT_REGISTRATION_REPLAY` separately. |
+| Checker separation | VSTD has a small checker isolated from verdict-producing code. | SCITT relying parties verify issuer signatures and COSE Receipts offline; auditors check VDS consistency. | Both support separately executable checks. | The checked proposition differs, and neither mechanism alone establishes distinct producer/checker actors. | Demonstrate both checks in sequence, retain both native results, and reserve “independently verified” for evidence-bound actor and execution separation. |
+| Counterexamples | VSTD FAIL can carry a counterexample or refutation certificate. | SCITT receipt invalidity can carry verification failure, but core does not define domain counterexamples. | Both can expose detected failure. | A bad inclusion proof is not a counterexample to payload truth. | Keep SCITT integrity failure and VSTD predicate refutation as typed failures. |
+| PASS | Bounded proposition accepted with its required certificate/evidence. | Core SCITT has verified signature/receipt/registration, not a generic application `PASS`; the individual composite draft proposes profile `pass`. | Both can have successful checks. | The success domains are not equivalent. | Composed PASS requires native VSTD PASS and exact current SCITT verification; SCITT alone never creates it. |
+| FAIL | Evidenced predicate violation or rejected certificate, depending on the VSTD result surface. | Signature, receipt, inclusion, policy, or profile verification can fail. | Both can detect concrete failures. | Failure reasons apply to different mechanisms and coordinates. | Preserve native reason codes and identify which mechanism and coordinate failed. |
+| UNKNOWN | Bounded inability to decide, with VSTD-4 indeterminacy evidence. | No core RFC application verdict; individual composite draft uses `unknown` for unavailable evidence or unrecognized profile and separates missing/stale/conflict. | Both reject guessing. | They are not semantically equivalent. | See the taxonomy below; map by reason, never by label alone. |
+| Warnings | VSTD warnings cannot silently supply a missing coordinate or verdict. | Individual composite draft proposes `warning` when mandatory checks pass but a condition is surfaced. | Both can retain nonfatal findings. | A warning's acceptability is profile-specific. | Preserve warnings; do not map warning to VSTD PASS without full native VSTD verification. |
+| Cost/work claims | VSTD binds/checks verification work and receipt size at VSTD-4. | SCITT proves VDS properties; its protocol latency/status does not prove application checking cost. | Receipts can carry cost claims as payload data. | SCITT has no generic proof of VSTD work. | Carry the VSTD bound and checker result as payload semantics. |
+| Graph degradation | VSTD-Graph recomputes candidate profiles and blast radius without mutating history. | SCITT core preserves log history; individual graph draft proposes revocation/supersession/conflict checks. | Both favor additive history. | SCITT inclusion remains true even if a payload becomes disfavored; VSTD evidence ceiling may fall. | Keep historical inclusion true while lowering the current VSTD composition result. |
+| Real-world truth vs evidence validity | VSTD explicitly limits arbitrary truth claims to its declared evidence and predicate. | RFC 9943 states registration only proves the statement was produced by an issuer; issuers may be false. | Strong agreement on non-upgrade. | VSTD additionally specifies a checkable bounded computational proposition. | This is the central composition boundary. |
+
+## UNKNOWN is not one shared enum
+
+| Condition | SCITT core / draft treatment | VSTD treatment | Composition |
+|---|---|---|---|
+| Evidence unavailable | Core receipt may remain historically valid; individual composite draft: `unknown` or `missing`. | `UNKNOWN/DEPENDENCY_UNAVAILABLE` or `ARTIFACT_UNRETRIEVABLE` when relevant. | UNKNOWN with both native reasons. |
+| Incomplete bundle | Not a core RFC verdict; composite draft: `missing`. | UNKNOWN if required VSTD evidence is absent. | UNKNOWN, never PASS from registration alone. |
+| Resource budget exhausted | SCRAPI 204/429 are protocol/operation states, not application truth. | `UNKNOWN/PROOF_BOUND_EXCEEDED` or `DEPTH_BOUND_EXCEEDED`. | Preserve VSTD UNKNOWN even if the statement is registered. |
+| Predicate not established within the declared bound | SCITT core has no payload-domain undecidability result; the individual composite draft's `unknown` is profile/evidence-oriented. | A bounded VSTD check remains UNKNOWN with the native verifier reason; it is not proof that the predicate is globally undecidable. | Preserve the bounded inability to establish, without widening it into global undecidability or narrowing it into FAIL. |
+| Unsupported verification method/profile | A relying party cannot verify; composite draft: `unknown` for unrecognized profile. | `UNSUPPORTED` or `UNKNOWN/VERIFIER_UNAVAILABLE`, depending on mechanism and coordinate. | UNKNOWN or explicit UNSUPPORTED; no guess. |
+| Conflicting evidence | RFC 9943 permits conflicting statements; relying-party selection is external. Composite draft: `conflict`. | `CONFLICTED` where applicable. | CONFLICTED, not generic UNKNOWN. |
+| Stale evidence | Application policy; composite draft: `stale`. | `STALE` graph status or a bounded freshness failure. | Retain STALE and cap current composition. |
+| Revoked ancestor/key | Key-compromise response is discussed; universal revocation strategy is out of scope. | A recorded `REVOKED` ancestor lowers the Graph candidate and exposes blast radius; no challenge-to-Graph mutation is implemented. | Preserve historical inclusion and both native states; change current VSTD admissibility only through an explicit binding policy. |
+| Failed proof | Invalid SCITT signature/receipt/inclusion is concrete integrity failure. | Invalid decision certificate or evidenced counterexample is FAIL/rejection. | FAIL at the failing mechanism and coordinate, not UNKNOWN. |
+
+## What SCITT already does well
+
+- COSE Signed Statements and Receipt attachment.
+- Protected issuer and subject coordinates.
+- Registration policies and auditable policy history.
+- Append-only, non-equivocating VDS requirements.
+- Portable, offline-verifiable inclusion receipts.
+- Registration/receipt APIs through the active SCRAPI WG draft.
+- Multiple issuers, multiple TSs, and historical supersession without claiming
+ arbitrary payload truth.
+
+VSTD should reuse these mechanisms rather than define another signature envelope,
+transparency log, receipt-attachment convention, or registration API.
+
+## Current overlap and the narrower VSTD contribution
+
+Several active **individual** SCITT drafts now address concerns that must not be
+marketed as uniquely VSTD: narrow claim boundaries, validity versus sufficiency,
+missing/stale/conflicted evidence, statement graphs, evidence bundles, selective
+disclosure, canonical payload binding, coverage reconciliation, and typed
+application-profile outcomes. They remain work in progress without WG adoption, but
+their technical overlap is real.
+
+The narrower contribution demonstrated by the current VSTD implementation is not a
+new domain prover. It is a standard domain language and operator/result layer over
+orchestrated native verifier instances:
+
+- one domain-general claim coordinate for a computational predicate and parameters;
+- an implemented `VSTD4-GDC-1` grounded decision certificate binding proof variables
+ and clauses to named facts, subjects, policy/evidence roots, verifier code, and
+ resource ceilings;
+- a small checker isolated from verdict-producing code that returns evidence-bearing PASS, FAIL, or bounded
+ UNKNOWN and refuses over-budget work before proof replay;
+- separate refutation/challenge and Graph candidate-degradation mechanisms, with
+ cross-axis propagation explicitly `NOT_ESTABLISHED`; and
+- an adapter that requires separately bound native VSTD and native SCITT verifier
+ results, so neither declared payload success nor registration can create PASS.
+
+These are implementation and composition distinctions, not a claim that nobody else
+has proposed related semantics.
+
+## Positioning sentence
+
+> **SCITT can authenticate and make a VSTD receipt's registration transparently
+> auditable; VSTD supplies the verification interlingua that preserves the bounded
+> claim boundary and portable result semantics of the native verifier or proof engine
+> that produced the result.**
diff --git a/examples/experimental_workflow/README.md b/examples/experimental_workflow/README.md
new file mode 100644
index 0000000..65922ad
--- /dev/null
+++ b/examples/experimental_workflow/README.md
@@ -0,0 +1,24 @@
+# Experimental workflow example
+
+This deterministic example maps a normalized GitHub snapshot containing a successful
+workflow, an available artifact, a closed issue, a commit, and a merged pull request.
+All five records remain platform observations with `verification_effect = "NONE"`.
+The canonical profile manifest is indexed at
+[`experiments/github_verdict_neutrality/experiment.json`](../../experiments/github_verdict_neutrality/experiment.json).
+
+From the repository root:
+
+```bash
+PYTHONPATH=src python examples/experimental_workflow/demo.py
+```
+
+Expected boundary:
+
+```text
+events: 5
+vstd_verdicts_granted: 0
+```
+
+The example demonstrates portable workflow serialization and non-upgrade behavior. It
+does not contact GitHub, validate a signature, execute a domain verifier, or establish
+that the issue, commit, workflow, artifact, or merged change is correct.
diff --git a/examples/experimental_workflow/demo.py b/examples/experimental_workflow/demo.py
new file mode 100644
index 0000000..3b8b4f0
--- /dev/null
+++ b/examples/experimental_workflow/demo.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+"""Terminology: Verifier Standard (VSTD).
+
+Demonstrate that GitHub success and merge state grant no VSTD verdict."""
+
+from __future__ import annotations
+
+import json
+from pathlib import Path
+
+from verifier.experimental_workflow import github_snapshot_to_events, load_manifest
+
+
+HERE = Path(__file__).resolve().parent
+MANIFEST = HERE.parents[1] / "experiments" / "github_verdict_neutrality" / "experiment.json"
+
+
+def main() -> int:
+ snapshot = json.loads((HERE / "github_snapshot.json").read_text(encoding="utf-8"))
+ expected = load_manifest(MANIFEST)
+ events = github_snapshot_to_events(snapshot)
+ if list(events) != expected["workflow_events"]:
+ raise SystemExit("generated GitHub events do not match the bound manifest")
+ if any(event["verification_effect"] != "NONE" for event in events):
+ raise SystemExit("a platform event was incorrectly upgraded")
+ summary = {
+ "events": len(events),
+ "native_states": sorted({event["native_state"] for event in events}),
+ "vstd_verdicts_granted": 0,
+ "manifest_digest": expected["manifest_digest"],
+ }
+ print(json.dumps(summary, indent=2, sort_keys=True))
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/examples/experimental_workflow/github_snapshot.json b/examples/experimental_workflow/github_snapshot.json
new file mode 100644
index 0000000..d6bdadd
--- /dev/null
+++ b/examples/experimental_workflow/github_snapshot.json
@@ -0,0 +1,46 @@
+{
+ "commits": [
+ {
+ "committed_at": "2026-08-24T12:00:00Z",
+ "sha": "1111111111111111111111111111111111111111",
+ "subject": "Run bounded checker"
+ }
+ ],
+ "issues": [
+ {
+ "number": 41,
+ "state": "closed",
+ "title": "Test the bounded checker",
+ "updated_at": "2026-08-24T12:04:00Z"
+ }
+ ],
+ "pull_requests": [
+ {
+ "base_sha": "0000000000000000000000000000000000000000",
+ "head_sha": "1111111111111111111111111111111111111111",
+ "merged": true,
+ "number": 42,
+ "state": "closed",
+ "updated_at": "2026-08-24T12:05:00Z"
+ }
+ ],
+ "repository": "github:example/verifier-integration",
+ "workflow_runs": [
+ {
+ "artifacts": [
+ {
+ "digest": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
+ "expired": false,
+ "id": 9002,
+ "name": "checker-output"
+ }
+ ],
+ "conclusion": "success",
+ "head_sha": "1111111111111111111111111111111111111111",
+ "id": 9001,
+ "status": "completed",
+ "updated_at": "2026-08-24T12:03:00Z",
+ "workflow": "conformance"
+ }
+ ]
+}
diff --git a/examples/flagship_demo/README.md b/examples/flagship_demo/README.md
index 2fa6d93..edf5449 100644
--- a/examples/flagship_demo/README.md
+++ b/examples/flagship_demo/README.md
@@ -1,4 +1,6 @@
-# VSTD flagship adversarial demo
+# Verifier Standard (VSTD) flagship adversarial demo
+
+> **Acronym:** JavaScript Object Notation (JSON).
This is the shortest executable explanation of VSTD's intended behavior. It tests four
failure boundaries rather than presenting a happy-path receipt and asking the reader to
@@ -19,7 +21,7 @@ Only `--emit-specimens DIR` writes files, and only inside the named directory.
| `wrong-artifact` | The clause grounding names a different artifact from the grounded fact. | `REJECTED` |
| `honest-unknown` | A deterministic proof bound is exhausted. | `ACCEPTED/UNKNOWN` |
| `inflated-tier` | A Horn formula claims the more expensive general-resolution tier. | `REJECTED` |
-| `poisoned-ancestor` | Valid descendants conceal a transitive `REVOKED` source. | graph level `0`, named blocker, accepted refutation |
+| `poisoned-ancestor` | Valid descendants conceal a transitive `REVOKED` source. | Graph candidate `0`, named blocker, accepted refutation |
The poisoned-ancestor fixture declares object and edge ratings as inputs. Its graph
refutation checks the collection ceiling; it does not establish or upgrade the separate
diff --git a/examples/flagship_demo/specimens/honest-unknown.json b/examples/flagship_demo/specimens/honest-unknown.json
index 834134f..1fbfc7c 100644
--- a/examples/flagship_demo/specimens/honest-unknown.json
+++ b/examples/flagship_demo/specimens/honest-unknown.json
@@ -28,9 +28,9 @@
],
"deterministic": true,
"format_fragment": "UP,WIDTH-K,RES",
- "implementation_hash": "sha256:49afe96d327f99b12a518529d47b1b68c16228c4688be14eca279ea3e5b14d5b",
- "parser_hash": "sha256:a49fc58d15ec1b925b4e2bd48f5dfb350d9e9fca28044c5a0fa9ff051dc70c01",
- "specification_hash": "sha256:f2420f7826531ffdfadb1fcb9c0d3317d330d47a1cc367328ebd1c6921af36eb"
+ "implementation_hash": "sha256:a7d3c7b3123e9932395eff82f231c53e5b701888abbd203a47f155644cd10a64",
+ "parser_hash": "sha256:c657e7777a850e584b7c010f8f7ff778b5269507db3eb0b2ac362a7cdac4cd67",
+ "specification_hash": "sha256:e1eb5bea41c03b0cfc58c9af5cc23d47b1128253c504c8d3674f87bf6da7ed52"
}
},
"certificate": {
@@ -157,7 +157,7 @@
]
},
"header": {
- "binding": "aee206fd7bc450c01bda6c54a4e26ba228ebf752b4085df56f1d250055a7c9cd",
+ "binding": "a249fa8e24aed0815a9787a6e96000fcba043d7e07fef11c4f9d64ff9f08bf86",
"clause_count": 3,
"format": "VSTD4-GDC-1",
"literal_count": 4,
diff --git a/examples/flagship_demo/specimens/index.json b/examples/flagship_demo/specimens/index.json
index 0dde150..6587a75 100644
--- a/examples/flagship_demo/specimens/index.json
+++ b/examples/flagship_demo/specimens/index.json
@@ -37,11 +37,11 @@
"title": "Inflated verification-cost claim"
},
{
- "details": "collection:demo holds at graph level 0. Level 1 is refuted by: STATUS_ADMISSIBILITY: artifact:source is REVOKED.",
- "expected": "GRAPH-LEVEL-0; REVOKED blocker; checked refutation",
- "observed": "GRAPH-LEVEL-0; REVOKED",
+ "details": "collection:demo computes to candidate Graph profile 0 from caller-supplied ratings; conformance is not established. Graph profile 1 is refuted by: STATUS_ADMISSIBILITY: artifact:source is REVOKED.",
+ "expected": "GRAPH-CANDIDATE-0; REVOKED blocker; checked refutation",
+ "observed": "GRAPH-CANDIDATE-0; REVOKED",
"ok": true,
- "question": "Does a poisoned transitive ancestor cap the collection's graph level?",
+ "question": "Does a poisoned transitive ancestor cap the collection's candidate Graph profile?",
"scenario": "poisoned-ancestor",
"title": "Revoked ancestor behind valid descendants"
}
diff --git a/examples/flagship_demo/specimens/inflated-tier.json b/examples/flagship_demo/specimens/inflated-tier.json
index 481fe4d..2583be4 100644
--- a/examples/flagship_demo/specimens/inflated-tier.json
+++ b/examples/flagship_demo/specimens/inflated-tier.json
@@ -28,9 +28,9 @@
],
"deterministic": true,
"format_fragment": "UP,WIDTH-K,RES",
- "implementation_hash": "sha256:49afe96d327f99b12a518529d47b1b68c16228c4688be14eca279ea3e5b14d5b",
- "parser_hash": "sha256:a49fc58d15ec1b925b4e2bd48f5dfb350d9e9fca28044c5a0fa9ff051dc70c01",
- "specification_hash": "sha256:f2420f7826531ffdfadb1fcb9c0d3317d330d47a1cc367328ebd1c6921af36eb"
+ "implementation_hash": "sha256:a7d3c7b3123e9932395eff82f231c53e5b701888abbd203a47f155644cd10a64",
+ "parser_hash": "sha256:c657e7777a850e584b7c010f8f7ff778b5269507db3eb0b2ac362a7cdac4cd67",
+ "specification_hash": "sha256:e1eb5bea41c03b0cfc58c9af5cc23d47b1128253c504c8d3674f87bf6da7ed52"
}
},
"certificate": {
@@ -154,7 +154,7 @@
]
},
"header": {
- "binding": "aee206fd7bc450c01bda6c54a4e26ba228ebf752b4085df56f1d250055a7c9cd",
+ "binding": "a249fa8e24aed0815a9787a6e96000fcba043d7e07fef11c4f9d64ff9f08bf86",
"clause_count": 3,
"format": "VSTD4-GDC-1",
"literal_count": 4,
diff --git a/examples/flagship_demo/specimens/poisoned-ancestor.json b/examples/flagship_demo/specimens/poisoned-ancestor.json
index e0ded0b..284af57 100644
--- a/examples/flagship_demo/specimens/poisoned-ancestor.json
+++ b/examples/flagship_demo/specimens/poisoned-ancestor.json
@@ -1,9 +1,9 @@
{
- "details": "collection:demo holds at graph level 0. Level 1 is refuted by: STATUS_ADMISSIBILITY: artifact:source is REVOKED.",
- "expected": "GRAPH-LEVEL-0; REVOKED blocker; checked refutation",
- "observed": "GRAPH-LEVEL-0; REVOKED",
+ "details": "collection:demo computes to candidate Graph profile 0 from caller-supplied ratings; conformance is not established. Graph profile 1 is refuted by: STATUS_ADMISSIBILITY: artifact:source is REVOKED.",
+ "expected": "GRAPH-CANDIDATE-0; REVOKED blocker; checked refutation",
+ "observed": "GRAPH-CANDIDATE-0; REVOKED",
"ok": true,
- "question": "Does a poisoned transitive ancestor cap the collection's graph level?",
+ "question": "Does a poisoned transitive ancestor cap the collection's candidate Graph profile?",
"scenario": "poisoned-ancestor",
"specimen": {
"binding": {
@@ -12,13 +12,13 @@
"memory_bound": 10000,
"verification_cost_bound": 10000
},
- "claim": "compute the bounded graph level for collection:demo",
+ "claim": "compute the bounded candidate Graph profile for collection:demo",
"coordinate": {
"parameters": {},
"predicate": "vstd_graph_level",
"subject": "collection:demo"
},
- "evidence_root": "4cd50c61d6162451488a984b85a9a209e708095186132c33d1529adeeacd6ca5",
+ "evidence_root": "8d42243ad0124e5946c34fbd3d6175daf5712a3cd75560f47ec950885866e483",
"policy_root": "e8e31ddeae93b0e85ec8cb26487489781efeab36ccef7676922a9e18b36155d8",
"prior_commitment": "",
"verifier": {
@@ -28,9 +28,9 @@
],
"deterministic": true,
"format_fragment": "UP,WIDTH-K,RES",
- "implementation_hash": "sha256:49afe96d327f99b12a518529d47b1b68c16228c4688be14eca279ea3e5b14d5b",
- "parser_hash": "sha256:a49fc58d15ec1b925b4e2bd48f5dfb350d9e9fca28044c5a0fa9ff051dc70c01",
- "specification_hash": "sha256:f2420f7826531ffdfadb1fcb9c0d3317d330d47a1cc367328ebd1c6921af36eb"
+ "implementation_hash": "sha256:a7d3c7b3123e9932395eff82f231c53e5b701888abbd203a47f155644cd10a64",
+ "parser_hash": "sha256:c657e7777a850e584b7c010f8f7ff778b5269507db3eb0b2ac362a7cdac4cd67",
+ "specification_hash": "sha256:e1eb5bea41c03b0cfc58c9af5cc23d47b1128253c504c8d3674f87bf6da7ed52"
}
},
"collection": {
@@ -48,7 +48,7 @@
"artifact:source": 5
}
},
- "fixture_boundary": "Object and edge levels are declared scenario inputs. This graph-level refutation does not establish or upgrade their separate evidence.",
+ "fixture_boundary": "Object and edge profile ratings are declared scenario inputs. This Graph-profile refutation does not establish or upgrade their separate evidence.",
"graph_result": {
"blocking_obligations": [
{
@@ -60,10 +60,12 @@
}
],
"collection_id": "collection:demo",
- "explanation": "collection:demo holds at graph level 0. Level 1 is refuted by: STATUS_ADMISSIBILITY: artifact:source is REVOKED.",
+ "conformance_status": "NOT_ESTABLISHED",
+ "explanation": "collection:demo computes to candidate Graph profile 0 from caller-supplied ratings; conformance is not established. Graph profile 1 is refuted by: STATUS_ADMISSIBILITY: artifact:source is REVOKED.",
"level": 0,
"max_level": 5,
- "refutation_digest": "87e9b1889e745c83c4a2dfc3968eadc9a4015146884a99f892e70b2d04d27eed",
+ "rating_basis": "CALLER_SUPPLIED",
+ "refutation_digest": "022ac984bda6a4e63f65ba8e294f1fabdecb1660e70fe6442cb4819a27434d84",
"witness_digest": null
},
"hypergraph": {
@@ -120,6 +122,7 @@
"storage_uris": []
}
],
+ "conflicts": [],
"contributors": [],
"rights": [],
"transformations": [
@@ -589,7 +592,7 @@
]
},
"header": {
- "binding": "23c087d7ef52a1995c2f54f51940f82faef60dc5262627fcee68dd9c9d78eb8e",
+ "binding": "d17ad10464fab5b474fc36cc5ce9a9b2ba54a3aa081ccb0e8feceea68bf1d7b6",
"clause_count": 17,
"format": "VSTD4-GDC-1",
"literal_count": 25,
diff --git a/examples/flagship_demo/specimens/wrong-artifact.json b/examples/flagship_demo/specimens/wrong-artifact.json
index c419d74..82d915f 100644
--- a/examples/flagship_demo/specimens/wrong-artifact.json
+++ b/examples/flagship_demo/specimens/wrong-artifact.json
@@ -28,9 +28,9 @@
],
"deterministic": true,
"format_fragment": "UP,WIDTH-K,RES",
- "implementation_hash": "sha256:49afe96d327f99b12a518529d47b1b68c16228c4688be14eca279ea3e5b14d5b",
- "parser_hash": "sha256:a49fc58d15ec1b925b4e2bd48f5dfb350d9e9fca28044c5a0fa9ff051dc70c01",
- "specification_hash": "sha256:f2420f7826531ffdfadb1fcb9c0d3317d330d47a1cc367328ebd1c6921af36eb"
+ "implementation_hash": "sha256:a7d3c7b3123e9932395eff82f231c53e5b701888abbd203a47f155644cd10a64",
+ "parser_hash": "sha256:c657e7777a850e584b7c010f8f7ff778b5269507db3eb0b2ac362a7cdac4cd67",
+ "specification_hash": "sha256:e1eb5bea41c03b0cfc58c9af5cc23d47b1128253c504c8d3674f87bf6da7ed52"
}
},
"certificate": {
@@ -162,7 +162,7 @@
]
},
"header": {
- "binding": "aee206fd7bc450c01bda6c54a4e26ba228ebf752b4085df56f1d250055a7c9cd",
+ "binding": "a249fa8e24aed0815a9787a6e96000fcba043d7e07fef11c4f9d64ff9f08bf86",
"clause_count": 3,
"format": "VSTD4-GDC-1",
"literal_count": 4,
diff --git a/examples/generic_run/compute.py b/examples/generic_run/compute.py
index 209710d..accd052 100644
--- a/examples/generic_run/compute.py
+++ b/examples/generic_run/compute.py
@@ -1,11 +1,13 @@
#!/usr/bin/env python3
-"""Deterministic word-frequency computation used by the VSTD generic-run example.
+"""Terminology: Verifier Standard (VSTD).
+
+Word-frequency computation used by the VSTD generic-run example.
Pure standard library, no randomness, no floating point, and no wall-clock
dependence in its *output* — timing is recorded separately by the VSTD
-receipt as execution metadata, not baked into these artifacts. That is what
-lets this example legitimately declare ``determinism_declared: DETERMINISTIC``
-in manifest.json.
+receipt as execution metadata, not baked into these artifacts. The generic capture
+path does not independently establish determinism, so the manifest leaves that
+classification unknown and relies on explicit rerun comparison instead.
"""
from __future__ import annotations
diff --git a/examples/generic_run/input.txt b/examples/generic_run/input.txt
index f5a31eb..c2a3488 100644
--- a/examples/generic_run/input.txt
+++ b/examples/generic_run/input.txt
@@ -2,5 +2,5 @@ a claim is not merely logged
a claim is packaged with evidence
a claim is packaged with scope
a claim is packaged with provenance
-a claim is checked by an independent auditor
+a claim is checked by a separately implemented auditor
a claim becomes a challengeable receipt
diff --git a/examples/generic_run/manifest.json b/examples/generic_run/manifest.json
index a9bcd1e..484f47a 100644
--- a/examples/generic_run/manifest.json
+++ b/examples/generic_run/manifest.json
@@ -1,21 +1,21 @@
{
"claim": {
"id": "RUN-000001",
- "title": "Deterministic word-frequency computation over a fixed input corpus",
- "statement": "Running compute.py against the declared input.txt deterministically produces output.json (a sorted word-frequency table) and metrics.json (a total-token-count metric), reproducing byte-identically on rerun.",
- "scope": "A single, self-contained, dependency-free Python computation used to demonstrate the VERIFIABLE generic proof-carrying run primitive end-to-end: source -> inputs -> execution -> outputs -> claim -> receipt -> independent validation -> reproduction.",
+ "title": "Word-frequency computation over a fixed input corpus",
+ "statement": "The recorded command produced output.json (a sorted word-frequency table) and metrics.json (token-count metrics) from the declared input.txt; an explicit rerun can compare those output bytes.",
+ "scope": "A single, self-contained, dependency-free Python computation used to demonstrate the VSTD generic receipt-carrying run workflow end-to-end: source -> inputs -> execution -> outputs -> claim -> receipt -> separate validation -> reproduction.",
"limitations": [
- "This is a deliberately small worked example chosen for zero external dependencies and full determinism, not a claim about any production model, dataset, or benchmark.",
- "Determinism is declared only for this exact recorded Python version and platform; the computation performs no floating point and no hash-order-dependent operations, so cross-run determinism is expected but not independently proven for other environments.",
+ "This is a deliberately small worked example chosen for zero external dependencies and exact output comparison, not a claim about any production model, dataset, or benchmark.",
+ "The computation avoids floating point and hash-order-dependent output, but this generic capture path does not independently verify determinism or bind a complete execution environment.",
"No external evaluation evidence is claimed anywhere in this receipt — it is a purely local, self-contained computation."
],
- "falsification_condition": "If `verifiable reproduce --rerun` regenerates output.json/metrics.json with a different SHA-256 digest than recorded, or `verifiable validate` finds the recomputed canonical_digest does not match receipt.json's recorded canonical_digest, this claim is falsified."
+ "falsification_condition": "A `vstd validate` digest mismatch falsifies stable-content integrity. A `vstd reproduce --rerun` output mismatch falsifies byte-identical reproducibility for that rerun, not the recorded original execution by itself."
},
"command": ["python", "compute.py", "input.txt", "output.json", "metrics.json"],
"cwd": ".",
"repo_dir": "../..",
- "target_name": "verifiable-generic-run-example",
- "portable_repository_id": "github.com/TimeLordRaps/Verifiable",
+ "target_name": "vstd-generic-run-example",
+ "portable_repository_id": "github.com/TimeLordRaps/verifier",
"inputs": [
{"path": "input.txt", "role": "primary_input"},
{"path": "compute.py", "role": "entrypoint_source"}
@@ -24,7 +24,7 @@
{"path": "output.json", "role": "primary_output"},
{"path": "metrics.json", "role": "metrics"}
],
- "determinism_declared": "DETERMINISTIC",
+ "determinism_declared": "UNKNOWN",
"seed": null,
"evaluator_claims": [
{
diff --git a/examples/logits_constraint_kernel/README.md b/examples/logits_constraint_kernel/README.md
index 2df0606..deedb61 100644
--- a/examples/logits_constraint_kernel/README.md
+++ b/examples/logits_constraint_kernel/README.md
@@ -1,9 +1,11 @@
# Logits Constraint Kernel demo
+> **Acronym:** JavaScript Object Notation (JSON).
+
This example bypasses Outlines and calls `llguidance` 1.8.0 directly. It compiles a
strict JSON Schema containing `patternProperties`, computes the packed allowed-token
mask before every generated byte token, applies that mask to a real PyTorch logits
-tensor, advances the native matcher, and independently post-validates the completed
+tensor, advances the native matcher, and separately post-validates the completed
JSON with `jsonschema` Draft 2020-12.
```powershell
@@ -13,7 +15,7 @@ python examples/logits_constraint_kernel/demo.py
`llguidance` is the only grammar engine. `torch` is only the tensor adapter and is
normally already supplied by the model runtime; `jsonschema` comes through the test
-profile solely for the independently selected post-validation facet.
+profile solely for the separate post-validation facet.
The generated `trace.json` binds the source constraint, native compiled grammar,
tokenizer, every observed mask, every state transition, and the whole-output
diff --git a/examples/logits_constraint_kernel/demo.py b/examples/logits_constraint_kernel/demo.py
index 85c19df..a80b890 100644
--- a/examples/logits_constraint_kernel/demo.py
+++ b/examples/logits_constraint_kernel/demo.py
@@ -1,4 +1,6 @@
-"""Emit a real llguidance logits-mask trace for a schema Outlines 0.2.14 dropped."""
+"""Terminology: JavaScript Object Notation (JSON); Verifier Standard (VSTD).
+
+Emit a real llguidance logits-mask trace for a schema Outlines 0.2.14 dropped."""
from __future__ import annotations
diff --git a/examples/scitt_interop/README.md b/examples/scitt_interop/README.md
new file mode 100644
index 0000000..0662336
--- /dev/null
+++ b/examples/scitt_interop/README.md
@@ -0,0 +1,119 @@
+# Verifier Standard (VSTD)/Supply Chain Integrity, Transparency, and Trust (SCITT) cryptographic interoperability example
+
+> **Acronyms:** Concise Binary Object Representation (CBOR); CBOR Object Signing and Encryption (COSE); grounded decision certificate (GDC);
+> Internet Engineering Task Force (IETF); Request for Comments (RFC); Secure Hash Algorithm 256-bit (SHA-256);
+> verifiable data structure (VDS).
+
+> **Experimental and non-normative.** This example creates real COSE signatures and
+> an RFC 9162 SHA-256 inclusion receipt in a local one-entry test log.
+> It does not operate a production SCITT Transparency Service, publish to a public
+> log, or demonstrate third-party monitoring.
+
+## What it proves
+
+The example executes this chain:
+
+```text
+artifact bytes
+ -> grounded VSTD4-GDC-1 digest predicate
+ -> separately implemented kernel check returns PASS
+ -> deterministic experimental VSTD/SCITT payload
+ -> RFC 9943-style EdDSA COSE Signed Statement
+ -> RFC 9942 / RFC9162_SHA256 signed inclusion receipt
+ -> offline statement-signature and receipt verification
+ -> composed result preserving both native verdicts
+```
+
+It proves, under the emitted public keys and local test-log policy, that the exact
+Signed Statement is authentic and included in the one-entry VDS, and that the exact
+embedded VSTD certificate passes the separately implemented kernel check for the artifact digest
+predicate. The enclosing VSTD-4 candidate depth is a structural result with conformance
+`NOT_ESTABLISHED`; this example does not establish VSTD-4 conformance or VSTD-5
+readiness or distinct producer/checker actors. It also does not prove artifact safety,
+production-service registration,
+public witnessing, issuer authority outside the test, or arbitrary payload truth.
+
+## Identity and privacy boundary
+
+The VSTD receipt is produced and checkable before SCITT is applied. This example
+then deliberately adds a fixed issuer, signature, subject, registration time, and
+transparency-service coordinate because those are part of the selected SCITT
+profile. It is therefore **not** a zero-identity or zero-knowledge example: the
+payload is disclosed, and the issuer and statement can be correlated. SCITT is an
+optional accountability wrapper here, not a prerequisite for VSTD verification.
+
+Before issuing the local receipt, the example policy verifies the statement
+signature and requires the exact test issuer, VSTD subject, payload content type,
+and experimental profile identifier. The policy identifier is retained in the
+normalized SCITT observation.
+
+## Setup
+
+From the repository root:
+
+```bash
+python -m pip install -e ".[scitt]"
+```
+
+The optional extra is pinned in `pyproject.toml`:
+
+- `scitt-cose==0.2.2`
+- `cbor2==6.1.4`
+- `cryptography==50.0.0`
+
+`scitt-cose` is a separately maintained implementation, not an IETF publication or
+endorsement. The normative wire references are [RFC 9943](https://datatracker.ietf.org/doc/html/rfc9943), [RFC 9942](https://datatracker.ietf.org/doc/html/rfc9942), RFC 9052/9053, and RFC 9162.
+
+## Produce and verify
+
+```bash
+python examples/scitt_interop/demo.py produce
+python examples/scitt_interop/demo.py verify
+```
+
+The producer writes a deterministic canonical VSTD payload plus real COSE artifacts
+under `generated/`. Fresh ephemeral signing keys are generated on each production
+run, so the public keys, signatures, and their hashes intentionally change. The
+checked-in specimen remains deterministically verifiable, but producing a new
+specimen is not byte-reproducible without externally managed fixed keys. The verifier reads
+only those artifacts, the two public keys, the local artifact, and the documented
+trust coordinates. No private key is written or committed. The ephemeral keys have
+no authority outside this example.
+
+## Generated artifacts
+
+| File | Meaning |
+|---|---|
+| `vstd_receipt.json` | VSTD-4 structural candidate receipt and grounded decision certificate; conformance is `NOT_ESTABLISHED`. |
+| `vstd_scitt_payload.json` | Canonical application payload bytes carried by SCITT. |
+| `registration_template.json` | Human-readable normalized input; explicitly **not** COSE. |
+| `signed_statement.cose` | Real COSE_Sign1 Signed Statement. |
+| `receipt.cose` | Real signed RFC9162_SHA256 inclusion receipt. |
+| `transparent_statement.cose` | Signed Statement with receipt attached at COSE header label 394. |
+| `issuer_public.pem` | Public key for offline statement-signature verification. |
+| `log_public.pem` | Public key for offline receipt verification. |
+| `verification_result.json` | Native VSTD candidate-check result, explicit VSTD conformance `NOT_ESTABLISHED`, native SCITT observation, scoped composition, and hashes. |
+
+## Adversarial coverage
+
+`tests/test_scitt_interop.py` and `tests/test_scitt_crypto_example.py` cover:
+
+- deterministic serialization and round trips;
+- identity, claim-coordinate, artifact, and payload binding;
+- valid SCITT registration with VSTD FAIL or UNKNOWN;
+- missing, stale, revoked, superseded, conflicted, and unsupported evidence;
+- wrong issuer/subject and unaccepted policy coordinates;
+- malformed payloads and version mismatches;
+- corrupted COSE statement and receipt bytes;
+- the invariant that SCITT-only evidence returns
+ `computational_verdict = NOT_EVALUATED`.
+- the invariant that a composed PASS requires a native VSTD checker result bound to
+ the exact embedded receipt;
+- the invariant that the native VSTD payload contains no SCITT issuer, transparency
+ service, registration policy, or registration time.
+
+Run:
+
+```bash
+python -m pytest -q tests/test_scitt_interop.py tests/test_scitt_crypto_example.py
+```
diff --git a/examples/scitt_interop/artifact.txt b/examples/scitt_interop/artifact.txt
new file mode 100644
index 0000000..3518a08
--- /dev/null
+++ b/examples/scitt_interop/artifact.txt
@@ -0,0 +1 @@
+VSTD and SCITT compose without semantic upgrading.
diff --git a/examples/scitt_interop/demo.py b/examples/scitt_interop/demo.py
new file mode 100644
index 0000000..ea77bbf
--- /dev/null
+++ b/examples/scitt_interop/demo.py
@@ -0,0 +1,496 @@
+"""Terminology: Concise Binary Object Representation (CBOR);
+CBOR Object Signing and Encryption (COSE); CBOR Web Token (CWT);
+grounded decision certificate (GDC); Request for Comments (RFC);
+Supply Chain Integrity, Transparency, and Trust (SCITT); Secure Hash Algorithm 256-bit (SHA-256);
+verifiable data structure (VDS); Verifier Standard (VSTD).
+
+Cryptographic VSTD/SCITT interoperability specimen with a deterministic application
+payload and ephemeral-key COSE artifacts.
+
+The optional ``scitt`` extra supplies COSE and RFC 9162 receipt primitives. A
+one-entry local test log is used so the example is self-contained. This is a
+real signed statement, signed inclusion receipt, and offline native verification;
+it is not distinct-actor verification, a production Transparency Service, public
+anchoring, or endorsement.
+"""
+
+from __future__ import annotations
+
+import argparse
+import hashlib
+import json
+from dataclasses import replace
+from pathlib import Path
+from typing import Any
+
+from verifier.core.certificate import (
+ CertificateHeader,
+ ClaimBinding,
+ ClaimCoordinate,
+ ClauseGrounding,
+ CostTier,
+ DecisionBlock,
+ DecisionCertificate,
+ EncodingRule,
+ GroundedFact,
+ Grounding,
+ ResourceBounds,
+ VariableGrounding,
+ Verdict,
+ VerifierDescriptor,
+ canonical_bytes,
+ canonical_digest,
+ certificate_from_dict,
+)
+from verifier.core.kernel import KernelOutcome, check, reference_descriptor
+from verifier.interoperability.scitt import (
+ EXPERIMENTAL_CONTENT_TYPE,
+ EXPERIMENTAL_PROFILE,
+ ScittEvidenceState,
+ ScittVerificationEvidence,
+ VstdCoordinates,
+ VstdScittPayload,
+ VstdVerificationEvidence,
+ VstdVerificationState,
+ compose_results,
+ consume_scitt_evidence,
+ create_scitt_registration_template,
+)
+
+
+HERE = Path(__file__).resolve().parent
+ARTIFACT = HERE / "artifact.txt"
+ISSUER = "https://issuer.example/vstd-scitt-demo"
+LOCAL_LOG = "urn:example:vstd-scitt-local-test-log"
+POLICY = "urn:example:vstd-scitt-registration-policy:v1"
+
+
+def _crypto():
+ try:
+ import cbor2
+ from cryptography.hazmat.primitives import serialization
+ from cryptography.hazmat.primitives.asymmetric import ed25519
+ from scitt_cose import (
+ attach_receipts,
+ build_receipt,
+ build_signed_statement,
+ extract_receipts,
+ merkle_root,
+ parse_signed_statement,
+ sign_sign1,
+ verify_receipt,
+ )
+ except ImportError as exc: # pragma: no cover - exercised in base environment
+ raise SystemExit(
+ "Install the pinned optional dependencies with: "
+ "python -m pip install -e '.[scitt]'"
+ ) from exc
+ return {
+ "cbor2": cbor2,
+ "serialization": serialization,
+ "ed25519": ed25519,
+ "attach_receipts": attach_receipts,
+ "build_receipt": build_receipt,
+ "build_signed_statement": build_signed_statement,
+ "extract_receipts": extract_receipts,
+ "merkle_root": merkle_root,
+ "parse_signed_statement": parse_signed_statement,
+ "sign_sign1": sign_sign1,
+ "verify_receipt": verify_receipt,
+ }
+
+
+def _sha256(data: bytes) -> str:
+ return hashlib.sha256(data).hexdigest()
+
+
+def _public_key_pair():
+ crypto = _crypto()
+ serialization = crypto["serialization"]
+ key = crypto["ed25519"].Ed25519PrivateKey.generate()
+ private_pem = key.private_bytes(
+ serialization.Encoding.PEM,
+ serialization.PrivateFormat.PKCS8,
+ serialization.NoEncryption(),
+ )
+ public_pem = key.public_key().public_bytes(
+ serialization.Encoding.PEM,
+ serialization.PublicFormat.SubjectPublicKeyInfo,
+ )
+ return private_pem, public_pem
+
+
+def _claim_binding_from_dict(value: dict[str, Any]) -> ClaimBinding:
+ """Reconstruct the exact receipt binding for separate kernel checking."""
+
+ coordinate = value["coordinate"]
+ bounds = value["bounds"]
+ verifier = value["verifier"]
+ return ClaimBinding(
+ claim=value["claim"],
+ coordinate=ClaimCoordinate(
+ coordinate["subject"],
+ coordinate["predicate"],
+ dict(coordinate["parameters"]),
+ ),
+ policy_root=value["policy_root"],
+ evidence_root=value["evidence_root"],
+ verifier=VerifierDescriptor(
+ specification_hash=verifier["specification_hash"],
+ implementation_hash=verifier["implementation_hash"],
+ parser_hash=verifier["parser_hash"],
+ certificate_format=verifier["certificate_format"],
+ format_fragment=verifier["format_fragment"],
+ dependencies=tuple(verifier["dependencies"]),
+ deterministic=verifier["deterministic"],
+ ),
+ bounds=ResourceBounds(
+ bounds["verification_cost_bound"],
+ bounds["memory_bound"],
+ bounds["certificate_size_bound"],
+ ),
+ prior_commitment=value["prior_commitment"],
+ )
+
+
+def _apply_local_registration_policy(
+ parsed: dict[str, Any], coordinates: VstdCoordinates
+) -> None:
+ """Minimal explicit policy applied before the local log issues a receipt."""
+
+ if parsed.get("signature_verified") is not True:
+ raise RuntimeError("registration policy rejected an unverified statement")
+ if parsed.get("issuer") != ISSUER:
+ raise RuntimeError("registration policy rejected the issuer")
+ if parsed.get("subject") != coordinates.subject:
+ raise RuntimeError("registration policy rejected the subject")
+ if parsed.get("content_type") != EXPERIMENTAL_CONTENT_TYPE:
+ raise RuntimeError("registration policy rejected the payload content type")
+ if parsed.get("claims", {}).get("vstd_profile") != EXPERIMENTAL_PROFILE:
+ raise RuntimeError("registration policy rejected the VSTD profile")
+
+
+def build_vstd_receipt() -> tuple[dict[str, Any], VstdCoordinates]:
+ artifact_digest = _sha256(ARTIFACT.read_bytes())
+ subject = f"artifact:sha256:{artifact_digest}"
+ predicate = "content_digest_matches"
+ formula = ((1,),)
+ rule = EncodingRule("RULE:ASSERT_DIGEST_MATCH", ("artifact",), ((1, "artifact"),))
+ grounding = Grounding(
+ variables=(
+ VariableGrounding(
+ 1, GroundedFact(subject, predicate, "MATCH")
+ ),
+ ),
+ clauses=(
+ ClauseGrounding(0, rule.rule_id, {"artifact": 1}, {"artifact": subject}),
+ ),
+ rules=(rule,),
+ )
+ binding = ClaimBinding(
+ claim="the named artifact bytes have the declared SHA-256 digest",
+ coordinate=ClaimCoordinate(
+ subject, predicate, {"algorithm": "sha-256", "digest": artifact_digest}
+ ),
+ policy_root=canonical_digest(
+ {"algorithm": "sha-256", "predicate": predicate}
+ ),
+ evidence_root=artifact_digest,
+ verifier=reference_descriptor(),
+ bounds=ResourceBounds(100, 10, 20000),
+ )
+ certificate = DecisionCertificate(
+ CertificateHeader(
+ Verdict.PASS,
+ CostTier.UP,
+ n_vars=1,
+ clause_count=1,
+ literal_count=1,
+ step_count=0,
+ binding=binding.digest(),
+ ),
+ formula,
+ grounding,
+ DecisionBlock(model={1: True}),
+ )
+ result = check(certificate, budget=100, binding=binding)
+ if result.outcome is not KernelOutcome.ACCEPTED or result.verdict is not Verdict.PASS:
+ raise RuntimeError(f"VSTD kernel did not accept demo certificate: {result}")
+
+ receipt = {
+ "schema_version": "VSTD-4",
+ "receipt_id": "VFY-4-scitt-interop-demo",
+ "claim_id": "SCITT-INTEROP-DEMO-DIGEST",
+ "binding": binding.to_dict(),
+ "vstd4_depth": 14,
+ "conformance_status": "NOT_ESTABLISHED",
+ "rung_evidence": {
+ f"4.{index}": f"decision_certificate:{certificate.digest()}#4.{index}"
+ for index in range(1, 15)
+ },
+ "witness": certificate.to_dict(),
+ "ceiling_refutation": None,
+ "blocking_rungs": [],
+ "status": "VALID",
+ "refutation_surface": {
+ "admissible_refutations": [
+ "artifact bytes hash to a value other than the bound digest",
+ "the VSTD decision certificate fails separate kernel checking",
+ ],
+ "excluded_claims": [
+ "artifact safety",
+ "issuer authorization",
+ "truth outside the bounded digest predicate",
+ ],
+ },
+ }
+ receipt_digest = _sha256(canonical_bytes(receipt))
+ coordinates = VstdCoordinates(
+ receipt_id=receipt["receipt_id"],
+ schema_version=receipt["schema_version"],
+ claim_id=receipt["claim_id"],
+ subject=subject,
+ predicate=predicate,
+ parameters={"algorithm": "sha-256", "digest": artifact_digest},
+ native_result=result.verdict.value,
+ native_canonical_digest=receipt_digest,
+ evidence_bounds=binding.bounds.to_dict(),
+ artifact_digests={"primary": artifact_digest},
+ provenance_references=("urn:example:vstd-scitt-demo:artifact",),
+ )
+ return receipt, coordinates
+
+
+def produce(
+ output: Path, *, vstd_binding_tamper: bool = False
+) -> dict[str, Any]:
+ crypto = _crypto()
+ receipt, coordinates = build_vstd_receipt()
+ if vstd_binding_tamper:
+ receipt["witness"]["header"]["binding"] = "0" * 64
+ coordinates = replace(
+ coordinates,
+ native_canonical_digest=_sha256(canonical_bytes(receipt)),
+ )
+ template = create_scitt_registration_template(
+ receipt, coordinates, issuer=ISSUER, subject=coordinates.subject
+ )
+ payload_bytes = template.payload.to_bytes()
+
+ # Generate fresh, memory-only private keys. The public keys are emitted
+ # as explicit trust coordinates; private key material is never committed
+ # or written to the output directory.
+ issuer_private, issuer_public = _public_key_pair()
+ log_private, log_public = _public_key_pair()
+ issuer_kid = hashlib.sha256(issuer_public).digest()
+ log_kid = hashlib.sha256(log_public).digest()
+ statement = crypto["build_signed_statement"](
+ payload_bytes,
+ alg="EdDSA",
+ private_key_pem=issuer_private,
+ issuer=ISSUER,
+ subject=coordinates.subject,
+ content_type=EXPERIMENTAL_CONTENT_TYPE,
+ extra_cwt_claims={"vstd_profile": EXPERIMENTAL_PROFILE},
+ kid=issuer_kid,
+ )
+ _apply_local_registration_policy(
+ crypto["parse_signed_statement"](
+ statement, public_key_pem=issuer_public
+ ),
+ coordinates,
+ )
+ tree_entries = [statement.hex()]
+ base_receipt = crypto["build_receipt"](
+ leaf_entry_hex=statement.hex(),
+ leaf_index=0,
+ tree_entries_hex=tree_entries,
+ alg="EdDSA",
+ log_private_key_pem=log_private,
+ )
+ # The generic RFC 9942 builder supplies the VDS proof. Re-sign the same
+ # detached root with RFC 9943's mandatory protected CWT issuer/subject
+ # claims so this specimen is also a SCITT Receipt, not only a COSE Receipt.
+ decoded_base = crypto["cbor2"].loads(base_receipt)
+ root = bytes.fromhex(crypto["merkle_root"](tree_entries))
+ scitt_receipt = crypto["sign_sign1"](
+ root,
+ alg="EdDSA",
+ private_key_pem=log_private,
+ protected={
+ 4: log_kid,
+ 15: {1: LOCAL_LOG, 2: coordinates.subject},
+ 395: 1,
+ },
+ unprotected=decoded_base.value[1],
+ detached=True,
+ )
+ transparent = crypto["attach_receipts"](statement, [scitt_receipt])
+
+ output.mkdir(parents=True, exist_ok=True)
+ (output / "vstd_receipt.json").write_text(
+ json.dumps(receipt, indent=2, sort_keys=True) + "\n", encoding="utf-8"
+ )
+ (output / "vstd_scitt_payload.json").write_bytes(payload_bytes + b"\n")
+ (output / "registration_template.json").write_text(
+ json.dumps(template.to_dict(), indent=2, sort_keys=True) + "\n",
+ encoding="utf-8",
+ )
+ (output / "signed_statement.cose").write_bytes(statement)
+ (output / "receipt.cose").write_bytes(scitt_receipt)
+ (output / "transparent_statement.cose").write_bytes(transparent)
+ (output / "issuer_public.pem").write_bytes(issuer_public)
+ (output / "log_public.pem").write_bytes(log_public)
+ return verify(output)
+
+
+def verify(output: Path, *, vstd_budget: int = 100) -> dict[str, Any]:
+ crypto = _crypto()
+ payload_bytes = (output / "vstd_scitt_payload.json").read_bytes().rstrip(b"\n")
+ payload = VstdScittPayload.from_bytes(payload_bytes)
+ statement = (output / "signed_statement.cose").read_bytes()
+ scitt_receipt = (output / "receipt.cose").read_bytes()
+ transparent = (output / "transparent_statement.cose").read_bytes()
+ issuer_public = (output / "issuer_public.pem").read_bytes()
+ log_public = (output / "log_public.pem").read_bytes()
+
+ try:
+ parsed = crypto["parse_signed_statement"](
+ statement, public_key_pem=issuer_public
+ )
+ statement_structure = crypto["cbor2"].loads(statement)
+ statement_protected = crypto["cbor2"].loads(statement_structure.value[0])
+ except Exception as exc:
+ raise RuntimeError("malformed SCITT Signed Statement") from exc
+ receipt_result = crypto["verify_receipt"](
+ scitt_receipt,
+ leaf_entry_hex=statement.hex(),
+ log_public_key_pem=log_public,
+ )
+ attached = crypto["extract_receipts"](transparent)
+ receipt_structure = crypto["cbor2"].loads(scitt_receipt)
+ receipt_protected = crypto["cbor2"].loads(receipt_structure.value[0])
+ if parsed["signature_verified"] is not True:
+ raise RuntimeError("SCITT Signed Statement signature did not verify")
+ _apply_local_registration_policy(parsed, payload.coordinates)
+ if parsed["payload"] != payload_bytes:
+ raise RuntimeError("SCITT Signed Statement payload changed")
+ if parsed["issuer"] != ISSUER or parsed["subject"] != payload.coordinates.subject:
+ raise RuntimeError("SCITT Signed Statement identity coordinates changed")
+ if parsed["content_type"] != EXPERIMENTAL_CONTENT_TYPE:
+ raise RuntimeError("SCITT Signed Statement content type changed")
+ if statement_protected.get(4) != hashlib.sha256(issuer_public).digest():
+ raise RuntimeError("SCITT Signed Statement key identifier changed")
+ if not receipt_result.ok:
+ raise RuntimeError(f"COSE Receipt failed: {receipt_result.errors}")
+ if receipt_protected.get(15) != {
+ 1: LOCAL_LOG,
+ 2: payload.coordinates.subject,
+ }:
+ raise RuntimeError("SCITT Receipt issuer/subject claims changed")
+ if receipt_protected.get(4) != hashlib.sha256(log_public).digest():
+ raise RuntimeError("SCITT Receipt key identifier changed")
+ if attached != [scitt_receipt]:
+ raise RuntimeError("Transparent Statement did not preserve its receipt")
+
+ native_receipt = json.loads((output / "vstd_receipt.json").read_text())
+ certificate = certificate_from_dict(native_receipt["witness"])
+ binding = _claim_binding_from_dict(native_receipt["binding"])
+ vstd_result = check(certificate, budget=vstd_budget, binding=binding)
+ if vstd_result.outcome is KernelOutcome.ACCEPTED:
+ vstd_state = VstdVerificationState.VERIFIED
+ if vstd_result.verdict is None:
+ raise RuntimeError("VSTD checker returned no native verdict")
+ native_vstd_result = vstd_result.verdict.value
+ elif vstd_result.outcome is KernelOutcome.REFUSED:
+ vstd_state = VstdVerificationState.INDETERMINATE
+ native_vstd_result = "UNKNOWN"
+ else:
+ vstd_state = VstdVerificationState.REJECTED
+ native_vstd_result = "REJECTED"
+
+ vstd_observation = VstdVerificationEvidence(
+ state=vstd_state,
+ receipt_sha256=_sha256(canonical_bytes(native_receipt)),
+ native_result=native_vstd_result,
+ checker="verifier.core.kernel.check",
+ verification_profile="VSTD4-GDC-1/reference-kernel",
+ reason=vstd_result.details,
+ )
+
+ observation = ScittVerificationEvidence(
+ state=ScittEvidenceState.REGISTERED,
+ statement_sha256=_sha256(statement),
+ payload_sha256=_sha256(payload_bytes),
+ issuer=parsed["issuer"],
+ subject=parsed["subject"],
+ signed_statement_verified=True,
+ receipt_verified=True,
+ verification_profile="RFC9943+RFC9942/RFC9162_SHA256",
+ registration_policy=POLICY,
+ transparency_service=LOCAL_LOG,
+ vds="RFC9162_SHA256",
+ native_result="SIGNED_STATEMENT_AND_INCLUSION_RECEIPT_VERIFIED",
+ reason=(
+ "local one-entry test log; cryptographic inclusion verified, "
+ "without public anchoring or production-service claims"
+ ),
+ registered_at="2026-08-23T00:00:00Z",
+ )
+ composition = compose_results(
+ payload,
+ vstd_observation,
+ observation,
+ artifact_digests={"primary": _sha256(ARTIFACT.read_bytes())},
+ accepted_issuers=[ISSUER],
+ )
+ expected_composition = {
+ KernelOutcome.ACCEPTED: "PASS",
+ KernelOutcome.REFUSED: "UNKNOWN",
+ KernelOutcome.REJECTED: "FAIL",
+ }[vstd_result.outcome]
+ if composition.status.value != expected_composition:
+ raise RuntimeError(f"composition failed: {composition}")
+
+ scitt_as_vstd_evidence = consume_scitt_evidence(
+ observation,
+ expected_payload_sha256=payload.payload_sha256(),
+ expected_subject=payload.coordinates.subject,
+ accepted_issuers=[ISSUER],
+ )
+
+ result = {
+ "vstd_kernel": vstd_result.to_dict(),
+ "vstd_observation": vstd_observation.to_dict(),
+ "scitt_observation": observation.to_dict(),
+ "scitt_as_vstd_evidence": scitt_as_vstd_evidence,
+ "composition": composition.to_dict(),
+ "artifact_sha256": _sha256(ARTIFACT.read_bytes()),
+ "payload_sha256": _sha256(payload_bytes),
+ "statement_sha256": _sha256(statement),
+ "receipt_sha256": _sha256(scitt_receipt),
+ "transparent_statement_sha256": _sha256(transparent),
+ }
+ (output / "verification_result.json").write_text(
+ json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8"
+ )
+ return result
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("command", choices=("produce", "verify"))
+ parser.add_argument("--output", type=Path, default=HERE / "generated")
+ parser.add_argument("--vstd-budget", type=int, default=100)
+ args = parser.parse_args()
+ result = (
+ produce(args.output)
+ if args.command == "produce"
+ else verify(args.output, vstd_budget=args.vstd_budget)
+ )
+ print(json.dumps(result, indent=2, sort_keys=True))
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/examples/scitt_interop/generated/issuer_public.pem b/examples/scitt_interop/generated/issuer_public.pem
new file mode 100644
index 0000000..e078f47
--- /dev/null
+++ b/examples/scitt_interop/generated/issuer_public.pem
@@ -0,0 +1,3 @@
+-----BEGIN PUBLIC KEY-----
+MCowBQYDK2VwAyEAb26PMIwi29Ow1WGsGT/TfzRwSDRDwDHh2WPUR6VECmA=
+-----END PUBLIC KEY-----
diff --git a/examples/scitt_interop/generated/log_public.pem b/examples/scitt_interop/generated/log_public.pem
new file mode 100644
index 0000000..5b0b2a9
--- /dev/null
+++ b/examples/scitt_interop/generated/log_public.pem
@@ -0,0 +1,3 @@
+-----BEGIN PUBLIC KEY-----
+MCowBQYDK2VwAyEAoV97j2vB5HoFd7rxXDgAd/PEeel0IgquRuQ3nFLs6rI=
+-----END PUBLIC KEY-----
diff --git a/examples/scitt_interop/generated/receipt.cose b/examples/scitt_interop/generated/receipt.cose
new file mode 100644
index 0000000..1ab7272
Binary files /dev/null and b/examples/scitt_interop/generated/receipt.cose differ
diff --git a/examples/scitt_interop/generated/registration_template.json b/examples/scitt_interop/generated/registration_template.json
new file mode 100644
index 0000000..5014d66
--- /dev/null
+++ b/examples/scitt_interop/generated/registration_template.json
@@ -0,0 +1,175 @@
+{
+ "payload": {
+ "mapping_version": "0.1",
+ "profile": "vstd-scitt-interop-experimental-0.1",
+ "receipt_media_type": "application/vnd.verifier.vstd-receipt+json",
+ "receipt_sha256": "f8117247e97f12834206f5b024085c54b5d899e6880659d8afcebfeee2ddece1",
+ "vstd_coordinates": {
+ "artifact_digests": {
+ "primary": "39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"
+ },
+ "claim_coordinate": {
+ "parameters": {
+ "algorithm": "sha-256",
+ "digest": "39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"
+ },
+ "predicate": "content_digest_matches",
+ "subject": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"
+ },
+ "claim_id": "SCITT-INTEROP-DEMO-DIGEST",
+ "evidence_bounds": {
+ "certificate_size_bound": 20000,
+ "memory_bound": 10,
+ "verification_cost_bound": 100
+ },
+ "native_canonical_digest": "f8117247e97f12834206f5b024085c54b5d899e6880659d8afcebfeee2ddece1",
+ "native_result": "PASS",
+ "provenance_references": [
+ "urn:example:vstd-scitt-demo:artifact"
+ ],
+ "receipt_id": "VFY-4-scitt-interop-demo",
+ "schema_version": "VSTD-4"
+ },
+ "vstd_receipt": {
+ "binding": {
+ "bounds": {
+ "certificate_size_bound": 20000,
+ "memory_bound": 10,
+ "verification_cost_bound": 100
+ },
+ "claim": "the named artifact bytes have the declared SHA-256 digest",
+ "coordinate": {
+ "parameters": {
+ "algorithm": "sha-256",
+ "digest": "39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"
+ },
+ "predicate": "content_digest_matches",
+ "subject": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"
+ },
+ "evidence_root": "39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341",
+ "policy_root": "418c69bf2c7e119d75936d599903f860acaf5d3689817ae4ab9881d4659e6b2a",
+ "prior_commitment": "",
+ "verifier": {
+ "certificate_format": "VSTD4-GDC-1",
+ "dependencies": [
+ "python-stdlib"
+ ],
+ "deterministic": true,
+ "format_fragment": "UP,WIDTH-K,RES",
+ "implementation_hash": "sha256:94e4f7d4cb771f76d3e856ad93f0e7c3d151d47d862abef00fbcda23d7975e1f",
+ "parser_hash": "sha256:7d05d3810219b1ef8400accd5735fbde494c4fb310c4d86f08381d4979dcde5c",
+ "specification_hash": "sha256:9648fee5c94a8c41a581ec003226dd87eca59bc6e2356ed0383fcabbf02a1d5f"
+ }
+ },
+ "blocking_rungs": [],
+ "ceiling_refutation": null,
+ "claim_id": "SCITT-INTEROP-DEMO-DIGEST",
+ "conformance_status": "NOT_ESTABLISHED",
+ "receipt_id": "VFY-4-scitt-interop-demo",
+ "refutation_surface": {
+ "admissible_refutations": [
+ "artifact bytes hash to a value other than the bound digest",
+ "the VSTD decision certificate fails separate kernel checking"
+ ],
+ "excluded_claims": [
+ "artifact safety",
+ "issuer authorization",
+ "truth outside the bounded digest predicate"
+ ]
+ },
+ "rung_evidence": {
+ "4.1": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.1",
+ "4.10": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.10",
+ "4.11": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.11",
+ "4.12": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.12",
+ "4.13": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.13",
+ "4.14": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.14",
+ "4.2": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.2",
+ "4.3": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.3",
+ "4.4": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.4",
+ "4.5": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.5",
+ "4.6": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.6",
+ "4.7": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.7",
+ "4.8": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.8",
+ "4.9": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.9"
+ },
+ "schema_version": "VSTD-4",
+ "status": "VALID",
+ "vstd4_depth": 14,
+ "witness": {
+ "decision": {
+ "model": {
+ "1": true
+ },
+ "propagation": null,
+ "resolution": null,
+ "transcript": null
+ },
+ "formula": [
+ [
+ 1
+ ]
+ ],
+ "grounding": {
+ "clauses": [
+ {
+ "bindings": {
+ "artifact": 1
+ },
+ "clause_index": 0,
+ "rule_id": "RULE:ASSERT_DIGEST_MATCH",
+ "subjects": {
+ "artifact": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"
+ }
+ }
+ ],
+ "rules": [
+ {
+ "roles": [
+ "artifact"
+ ],
+ "rule_id": "RULE:ASSERT_DIGEST_MATCH",
+ "template": [
+ [
+ 1,
+ "artifact"
+ ]
+ ]
+ }
+ ],
+ "variables": [
+ {
+ "fact": {
+ "predicate": "content_digest_matches",
+ "subject": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341",
+ "value": "MATCH"
+ },
+ "var": 1
+ }
+ ]
+ },
+ "header": {
+ "binding": "6e7b912f47311920b5f0310c4b869e64b0985a309d75f5285360080ca605f6cd",
+ "clause_count": 1,
+ "format": "VSTD4-GDC-1",
+ "literal_count": 1,
+ "n_vars": 1,
+ "step_count": 0,
+ "tier": "UP",
+ "verdict": "PASS",
+ "width": 0
+ },
+ "hints": {}
+ }
+ }
+ },
+ "payload_sha256": "a0fc13840915e31f4d4787c7503f86f789be24e4a18fa76181991fa9aaecca63",
+ "representation": "normalized-registration-input-not-cose",
+ "required_protected_header_projection": {
+ "content_type": "application/vnd.verifier.vstd-receipt+json",
+ "issuer": "https://issuer.example/vstd-scitt-demo",
+ "payload_hash_algorithm": "sha-256",
+ "subject": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341",
+ "type": "vstd-scitt-interop-experimental-0.1"
+ }
+}
diff --git a/examples/scitt_interop/generated/signed_statement.cose b/examples/scitt_interop/generated/signed_statement.cose
new file mode 100644
index 0000000..20be3e0
--- /dev/null
+++ b/examples/scitt_interop/generated/signed_statement.cose
@@ -0,0 +1,2 @@
+҄Yx*application/vnd.verifier.vstd-receipt+jsonx&https://issuer.example/vstd-scitt-demoxPartifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341lvstd_profilex#vstd-scitt-interop-experimental-0.1X E.5(4}hhoV[9Hw|m'Y{"mapping_version":"0.1","profile":"vstd-scitt-interop-experimental-0.1","receipt_media_type":"application/vnd.verifier.vstd-receipt+json","receipt_sha256":"f8117247e97f12834206f5b024085c54b5d899e6880659d8afcebfeee2ddece1","vstd_coordinates":{"artifact_digests":{"primary":"39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"},"claim_coordinate":{"parameters":{"algorithm":"sha-256","digest":"39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"},"predicate":"content_digest_matches","subject":"artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"},"claim_id":"SCITT-INTEROP-DEMO-DIGEST","evidence_bounds":{"certificate_size_bound":20000,"memory_bound":10,"verification_cost_bound":100},"native_canonical_digest":"f8117247e97f12834206f5b024085c54b5d899e6880659d8afcebfeee2ddece1","native_result":"PASS","provenance_references":["urn:example:vstd-scitt-demo:artifact"],"receipt_id":"VFY-4-scitt-interop-demo","schema_version":"VSTD-4"},"vstd_receipt":{"binding":{"bounds":{"certificate_size_bound":20000,"memory_bound":10,"verification_cost_bound":100},"claim":"the named artifact bytes have the declared SHA-256 digest","coordinate":{"parameters":{"algorithm":"sha-256","digest":"39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"},"predicate":"content_digest_matches","subject":"artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"},"evidence_root":"39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341","policy_root":"418c69bf2c7e119d75936d599903f860acaf5d3689817ae4ab9881d4659e6b2a","prior_commitment":"","verifier":{"certificate_format":"VSTD4-GDC-1","dependencies":["python-stdlib"],"deterministic":true,"format_fragment":"UP,WIDTH-K,RES","implementation_hash":"sha256:94e4f7d4cb771f76d3e856ad93f0e7c3d151d47d862abef00fbcda23d7975e1f","parser_hash":"sha256:7d05d3810219b1ef8400accd5735fbde494c4fb310c4d86f08381d4979dcde5c","specification_hash":"sha256:9648fee5c94a8c41a581ec003226dd87eca59bc6e2356ed0383fcabbf02a1d5f"}},"blocking_rungs":[],"ceiling_refutation":null,"claim_id":"SCITT-INTEROP-DEMO-DIGEST","conformance_status":"NOT_ESTABLISHED","receipt_id":"VFY-4-scitt-interop-demo","refutation_surface":{"admissible_refutations":["artifact bytes hash to a value other than the bound digest","the VSTD decision certificate fails separate kernel checking"],"excluded_claims":["artifact safety","issuer authorization","truth outside the bounded digest predicate"]},"rung_evidence":{"4.1":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.1","4.10":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.10","4.11":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.11","4.12":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.12","4.13":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.13","4.14":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.14","4.2":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.2","4.3":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.3","4.4":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.4","4.5":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.5","4.6":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.6","4.7":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.7","4.8":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.8","4.9":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.9"},"schema_version":"VSTD-4","status":"VALID","vstd4_depth":14,"witness":{"decision":{"model":{"1":true},"propagation":null,"resolution":null,"transcript":null},"formula":[[1]],"grounding":{"clauses":[{"bindings":{"artifact":1},"clause_index":0,"rule_id":"RULE:ASSERT_DIGEST_MATCH","subjects":{"artifact":"artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"}}],"rules":[{"roles":["artifact"],"rule_id":"RULE:ASSERT_DIGEST_MATCH","template":[[1,"artifact"]]}],"variables":[{"fact":{"predicate":"content_digest_matches","subject":"artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341","value":"MATCH"},"var":1}]},"header":{"binding":"6e7b912f47311920b5f0310c4b869e64b0985a309d75f5285360080ca605f6cd","clause_count":1,"format":"VSTD4-GDC-1","literal_count":1,"n_vars":1,"step_count":0,"tier":"UP","verdict":"PASS","width":0},"hints":{}}}}X@kPX?ŀDNm#(
+L9w,ԙ!}aG h|@
\ No newline at end of file
diff --git a/examples/scitt_interop/generated/transparent_statement.cose b/examples/scitt_interop/generated/transparent_statement.cose
new file mode 100644
index 0000000..875acce
Binary files /dev/null and b/examples/scitt_interop/generated/transparent_statement.cose differ
diff --git a/examples/scitt_interop/generated/verification_result.json b/examples/scitt_interop/generated/verification_result.json
new file mode 100644
index 0000000..2060287
--- /dev/null
+++ b/examples/scitt_interop/generated/verification_result.json
@@ -0,0 +1,70 @@
+{
+ "artifact_sha256": "39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341",
+ "composition": {
+ "native_scitt_result": "SIGNED_STATEMENT_AND_INCLUSION_RECEIPT_VERIFIED",
+ "native_vstd_result": "PASS",
+ "reason": "native candidate-check result PASS (VSTD conformance NOT_ESTABLISHED) and exact current SCITT registration both verified",
+ "scitt_statement_sha256": "3e6f2a928abc6162511d648305a91b7fa242803f4b607c4beb91a0d8ab8391b5",
+ "status": "PASS",
+ "status_scope": "NATIVE_VSTD_RESULT_AND_SCITT_REGISTRATION",
+ "vstd_conformance_status": "NOT_ESTABLISHED",
+ "vstd_receipt_sha256": "f8117247e97f12834206f5b024085c54b5d899e6880659d8afcebfeee2ddece1"
+ },
+ "payload_sha256": "a0fc13840915e31f4d4787c7503f86f789be24e4a18fa76181991fa9aaecca63",
+ "receipt_sha256": "700df05d3c99098f8dbabb0757bf24fbe46073aa3823e1f5612bb373f426cfea",
+ "scitt_as_vstd_evidence": {
+ "computational_verdict": "NOT_EVALUATED",
+ "evidence_kind": "SCITT_TRANSPARENCY",
+ "native_scitt_result": "SIGNED_STATEMENT_AND_INCLUSION_RECEIPT_VERIFIED",
+ "normalized_state": "REGISTERED",
+ "payload_sha256": "a0fc13840915e31f4d4787c7503f86f789be24e4a18fa76181991fa9aaecca63",
+ "reason": "local one-entry test log; cryptographic inclusion verified, without public anchoring or production-service claims",
+ "registered_at": "2026-08-23T00:00:00Z",
+ "statement_sha256": "3e6f2a928abc6162511d648305a91b7fa242803f4b607c4beb91a0d8ab8391b5",
+ "trust_coordinates": {
+ "accepted_issuers": [
+ "https://issuer.example/vstd-scitt-demo"
+ ],
+ "registration_policy": "urn:example:vstd-scitt-registration-policy:v1",
+ "transparency_service": "urn:example:vstd-scitt-local-test-log",
+ "vds": "RFC9162_SHA256",
+ "verification_profile": "RFC9943+RFC9942/RFC9162_SHA256"
+ }
+ },
+ "scitt_observation": {
+ "issuer": "https://issuer.example/vstd-scitt-demo",
+ "native_result": "SIGNED_STATEMENT_AND_INCLUSION_RECEIPT_VERIFIED",
+ "payload_sha256": "a0fc13840915e31f4d4787c7503f86f789be24e4a18fa76181991fa9aaecca63",
+ "reason": "local one-entry test log; cryptographic inclusion verified, without public anchoring or production-service claims",
+ "receipt_verified": true,
+ "registered_at": "2026-08-23T00:00:00Z",
+ "registration_policy": "urn:example:vstd-scitt-registration-policy:v1",
+ "signed_statement_verified": true,
+ "state": "REGISTERED",
+ "statement_sha256": "3e6f2a928abc6162511d648305a91b7fa242803f4b607c4beb91a0d8ab8391b5",
+ "subject": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341",
+ "transparency_service": "urn:example:vstd-scitt-local-test-log",
+ "vds": "RFC9162_SHA256",
+ "verification_profile": "RFC9943+RFC9942/RFC9162_SHA256"
+ },
+ "statement_sha256": "3e6f2a928abc6162511d648305a91b7fa242803f4b607c4beb91a0d8ab8391b5",
+ "transparent_statement_sha256": "c75ff44dcc7b9630a05ad5c0040bc4c5dbb89651d47f345a18fdb4ddcb1bca7b",
+ "vstd_kernel": {
+ "details": "model satisfies all 1 grounded clauses",
+ "hints_present": false,
+ "literals_processed": 1,
+ "outcome": "ACCEPTED",
+ "reason": null,
+ "steps_checked": 0,
+ "verdict": "PASS"
+ },
+ "vstd_observation": {
+ "checker": "verifier.core.kernel.check",
+ "conformance_status": "NOT_ESTABLISHED",
+ "native_result": "PASS",
+ "reason": "model satisfies all 1 grounded clauses",
+ "receipt_sha256": "f8117247e97f12834206f5b024085c54b5d899e6880659d8afcebfeee2ddece1",
+ "state": "VERIFIED",
+ "verification_profile": "VSTD4-GDC-1/reference-kernel"
+ }
+}
diff --git a/examples/scitt_interop/generated/vstd_receipt.json b/examples/scitt_interop/generated/vstd_receipt.json
new file mode 100644
index 0000000..4ae623a
--- /dev/null
+++ b/examples/scitt_interop/generated/vstd_receipt.json
@@ -0,0 +1,132 @@
+{
+ "binding": {
+ "bounds": {
+ "certificate_size_bound": 20000,
+ "memory_bound": 10,
+ "verification_cost_bound": 100
+ },
+ "claim": "the named artifact bytes have the declared SHA-256 digest",
+ "coordinate": {
+ "parameters": {
+ "algorithm": "sha-256",
+ "digest": "39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"
+ },
+ "predicate": "content_digest_matches",
+ "subject": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"
+ },
+ "evidence_root": "39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341",
+ "policy_root": "418c69bf2c7e119d75936d599903f860acaf5d3689817ae4ab9881d4659e6b2a",
+ "prior_commitment": "",
+ "verifier": {
+ "certificate_format": "VSTD4-GDC-1",
+ "dependencies": [
+ "python-stdlib"
+ ],
+ "deterministic": true,
+ "format_fragment": "UP,WIDTH-K,RES",
+ "implementation_hash": "sha256:94e4f7d4cb771f76d3e856ad93f0e7c3d151d47d862abef00fbcda23d7975e1f",
+ "parser_hash": "sha256:7d05d3810219b1ef8400accd5735fbde494c4fb310c4d86f08381d4979dcde5c",
+ "specification_hash": "sha256:9648fee5c94a8c41a581ec003226dd87eca59bc6e2356ed0383fcabbf02a1d5f"
+ }
+ },
+ "blocking_rungs": [],
+ "ceiling_refutation": null,
+ "claim_id": "SCITT-INTEROP-DEMO-DIGEST",
+ "conformance_status": "NOT_ESTABLISHED",
+ "receipt_id": "VFY-4-scitt-interop-demo",
+ "refutation_surface": {
+ "admissible_refutations": [
+ "artifact bytes hash to a value other than the bound digest",
+ "the VSTD decision certificate fails separate kernel checking"
+ ],
+ "excluded_claims": [
+ "artifact safety",
+ "issuer authorization",
+ "truth outside the bounded digest predicate"
+ ]
+ },
+ "rung_evidence": {
+ "4.1": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.1",
+ "4.10": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.10",
+ "4.11": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.11",
+ "4.12": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.12",
+ "4.13": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.13",
+ "4.14": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.14",
+ "4.2": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.2",
+ "4.3": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.3",
+ "4.4": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.4",
+ "4.5": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.5",
+ "4.6": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.6",
+ "4.7": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.7",
+ "4.8": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.8",
+ "4.9": "decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.9"
+ },
+ "schema_version": "VSTD-4",
+ "status": "VALID",
+ "vstd4_depth": 14,
+ "witness": {
+ "decision": {
+ "model": {
+ "1": true
+ },
+ "propagation": null,
+ "resolution": null,
+ "transcript": null
+ },
+ "formula": [
+ [
+ 1
+ ]
+ ],
+ "grounding": {
+ "clauses": [
+ {
+ "bindings": {
+ "artifact": 1
+ },
+ "clause_index": 0,
+ "rule_id": "RULE:ASSERT_DIGEST_MATCH",
+ "subjects": {
+ "artifact": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"
+ }
+ }
+ ],
+ "rules": [
+ {
+ "roles": [
+ "artifact"
+ ],
+ "rule_id": "RULE:ASSERT_DIGEST_MATCH",
+ "template": [
+ [
+ 1,
+ "artifact"
+ ]
+ ]
+ }
+ ],
+ "variables": [
+ {
+ "fact": {
+ "predicate": "content_digest_matches",
+ "subject": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341",
+ "value": "MATCH"
+ },
+ "var": 1
+ }
+ ]
+ },
+ "header": {
+ "binding": "6e7b912f47311920b5f0310c4b869e64b0985a309d75f5285360080ca605f6cd",
+ "clause_count": 1,
+ "format": "VSTD4-GDC-1",
+ "literal_count": 1,
+ "n_vars": 1,
+ "step_count": 0,
+ "tier": "UP",
+ "verdict": "PASS",
+ "width": 0
+ },
+ "hints": {}
+ }
+}
diff --git a/examples/scitt_interop/generated/vstd_scitt_payload.json b/examples/scitt_interop/generated/vstd_scitt_payload.json
new file mode 100644
index 0000000..61a1dbd
--- /dev/null
+++ b/examples/scitt_interop/generated/vstd_scitt_payload.json
@@ -0,0 +1 @@
+{"mapping_version":"0.1","profile":"vstd-scitt-interop-experimental-0.1","receipt_media_type":"application/vnd.verifier.vstd-receipt+json","receipt_sha256":"f8117247e97f12834206f5b024085c54b5d899e6880659d8afcebfeee2ddece1","vstd_coordinates":{"artifact_digests":{"primary":"39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"},"claim_coordinate":{"parameters":{"algorithm":"sha-256","digest":"39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"},"predicate":"content_digest_matches","subject":"artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"},"claim_id":"SCITT-INTEROP-DEMO-DIGEST","evidence_bounds":{"certificate_size_bound":20000,"memory_bound":10,"verification_cost_bound":100},"native_canonical_digest":"f8117247e97f12834206f5b024085c54b5d899e6880659d8afcebfeee2ddece1","native_result":"PASS","provenance_references":["urn:example:vstd-scitt-demo:artifact"],"receipt_id":"VFY-4-scitt-interop-demo","schema_version":"VSTD-4"},"vstd_receipt":{"binding":{"bounds":{"certificate_size_bound":20000,"memory_bound":10,"verification_cost_bound":100},"claim":"the named artifact bytes have the declared SHA-256 digest","coordinate":{"parameters":{"algorithm":"sha-256","digest":"39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"},"predicate":"content_digest_matches","subject":"artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"},"evidence_root":"39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341","policy_root":"418c69bf2c7e119d75936d599903f860acaf5d3689817ae4ab9881d4659e6b2a","prior_commitment":"","verifier":{"certificate_format":"VSTD4-GDC-1","dependencies":["python-stdlib"],"deterministic":true,"format_fragment":"UP,WIDTH-K,RES","implementation_hash":"sha256:94e4f7d4cb771f76d3e856ad93f0e7c3d151d47d862abef00fbcda23d7975e1f","parser_hash":"sha256:7d05d3810219b1ef8400accd5735fbde494c4fb310c4d86f08381d4979dcde5c","specification_hash":"sha256:9648fee5c94a8c41a581ec003226dd87eca59bc6e2356ed0383fcabbf02a1d5f"}},"blocking_rungs":[],"ceiling_refutation":null,"claim_id":"SCITT-INTEROP-DEMO-DIGEST","conformance_status":"NOT_ESTABLISHED","receipt_id":"VFY-4-scitt-interop-demo","refutation_surface":{"admissible_refutations":["artifact bytes hash to a value other than the bound digest","the VSTD decision certificate fails separate kernel checking"],"excluded_claims":["artifact safety","issuer authorization","truth outside the bounded digest predicate"]},"rung_evidence":{"4.1":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.1","4.10":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.10","4.11":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.11","4.12":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.12","4.13":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.13","4.14":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.14","4.2":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.2","4.3":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.3","4.4":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.4","4.5":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.5","4.6":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.6","4.7":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.7","4.8":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.8","4.9":"decision_certificate:5f7e75463c755cab27d65ac363b86164e8f3d74233983d0120e4ad1157a13295#4.9"},"schema_version":"VSTD-4","status":"VALID","vstd4_depth":14,"witness":{"decision":{"model":{"1":true},"propagation":null,"resolution":null,"transcript":null},"formula":[[1]],"grounding":{"clauses":[{"bindings":{"artifact":1},"clause_index":0,"rule_id":"RULE:ASSERT_DIGEST_MATCH","subjects":{"artifact":"artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"}}],"rules":[{"roles":["artifact"],"rule_id":"RULE:ASSERT_DIGEST_MATCH","template":[[1,"artifact"]]}],"variables":[{"fact":{"predicate":"content_digest_matches","subject":"artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341","value":"MATCH"},"var":1}]},"header":{"binding":"6e7b912f47311920b5f0310c4b869e64b0985a309d75f5285360080ca605f6cd","clause_count":1,"format":"VSTD4-GDC-1","literal_count":1,"n_vars":1,"step_count":0,"tier":"UP","verdict":"PASS","width":0},"hints":{}}}}
diff --git a/examples/simulacrabench_synthetic/CORRECTION.md b/examples/simulacrabench_synthetic/CORRECTION.md
deleted file mode 100644
index b0336c8..0000000
--- a/examples/simulacrabench_synthetic/CORRECTION.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# Additive correction to `VSTD-SB-SYNTH-001`
-
-**Correction date:** 2026-08-22
-**Corrected packet:** `VSTD-SB-SYNTH-002`
-
-The first public specimen is preserved at immutable commit
-[`a37e6128fc6eccb66160a2f7c3af2f43341c227e`](https://github.com/TimeLordRaps/verifier/tree/a37e6128fc6eccb66160a2f7c3af2f43341c227e/examples/simulacrabench_synthetic).
-Its packet digest is
-`sha256:f182bfce5a5ae8e7137795300d42e285f365e6707b7c3517b3cee7b02331963b`;
-its challenge digest is
-`sha256:9ce25775826ef90f3eea0abdaa62268c4e5ce34092e63e2cc6cc88248a9395d6`.
-
-## What was wrong
-
-1. The packet used a locator scheme with no shipped resolver and treated nonempty locator
- and retention strings as enough to derive `AVAILABLE`.
-2. The public verifier did not retrieve any private artifact or receive observed bytes.
-3. A founder-authored transcript under the same trust root was accepted as an authorized
- adjudication, moving a deliberate mutant from `CHALLENGED` to `REVOKED` without public
- score recomputation or an independent adjudicator.
-
-Those statements overstated what the public artifacts established.
-
-## Correction
-
-- Private artifacts now have no invented locator and derive only `IDENTIFIED`.
-- The bundle fails the `AVAILABLE` requirement; public score reproduction remains
- `UNAVAILABLE`.
-- The challenge demonstration contains a filing but no private transcript and no
- adjudication. Its terminal public state is `CHALLENGED`.
-- `ArtifactAvailability` now requires an observed-byte retrieval binding before deriving
- `AVAILABLE` or `PORTABLE`; locator and retention declarations alone do not elevate it.
-- The recorded local `PASS` and `0.33` are retained only as a claim made under the same
- founder-operated trust root, not as a public rerun or independent result.
-
-The old commit and digests remain immutable. Current documentation and tests point to the
-corrected specimen rather than silently reinterpreting the historical bytes.
diff --git a/examples/simulacrabench_synthetic/CROSSWALK.md b/examples/simulacrabench_synthetic/CROSSWALK.md
deleted file mode 100644
index 5a7d18e..0000000
--- a/examples/simulacrabench_synthetic/CROSSWALK.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# SimulacraBench-to-VSTD crosswalk
-
-This crosswalk is pinned to the upstream commit recorded in [`UPSTREAM.md`](UPSTREAM.md).
-It maps observable public evaluator mechanics; it does not infer hidden infrastructure or
-organizer intent.
-
-| SimulacraBench public mechanic | Pinned evidence | VSTD representation in this example | Preserved limitation |
-| :-- | :-- | :-- | :-- |
-| A submission ZIP supplies `main.py`, optional `requirements.txt`, and `predict(frame, schema)` | `README.md`, `tools/check_submission_zip.py`, baseline files | Exact ZIP and source bytes are `SELF_CONTAINED` and content-addressed | Passing the ZIP checker does not establish a successful evaluation |
-| Dependencies are installed before the scored run | `score.py`, `config.yml` | Dependency declaration is committed separately from the run transcript | This local rehearsal did not reproduce the hosted image or hardware |
-| Runtime sockets are disabled before submission import | `score.py` | `network_control` is a claim-coordinate parameter and an admissible execution-receipt challenge target | The observed control was in-process socket denial, not container-level isolation |
-| Phase 1 exposes TRAIN and scores DEV under a 900-second prediction budget | `README.md`, `config.yml`, `score.py` | Phase, data view, timeout, source commit, and scoring seed are bounded execution fields | No protected TEST data or hosted API path was exercised |
-| The participant receives a privacy-processed aggregate and runtime; the organizer keeps raw detail | `README.md`, `score.py` | Saved participant-visible result is `SELF_CONTAINED`; raw log and synthetic fixture are access-controlled and only `IDENTIFIED` in the public packet | Public recomputation is `UNAVAILABLE`; a digest and retention promise are not retrieval evidence or proof of correctness |
-| A score mismatch can be challenged without publishing respondent rows | VSTD profile construction over the public evaluator interface | `metric_recomputation_mismatch` moves the filed mutant to `CHALLENGED` | No adjudication or revocation follows without separately evidenced authorized checking |
-
-## Exactness audit
-
-| Question | Answer |
-| :-- | :-- |
-| Are the upstream files pinned to a full commit and bundled byte-for-byte? | Yes |
-| Is the exact submitted ZIP bundled? | Yes |
-| Is every input to the measured run synthetic? | Yes; the private fixture was produced only by the pinned synthetic generator, public toy schema, configuration, and a private high-entropy seed |
-| Does the public packet demonstrate retrieval of every verdict-critical artifact? | No; it contains no retrieval observation and no private locator |
-| Can an arbitrary public reviewer retrieve the hidden fixture and raw log? | No |
-| Can the public verifier recompute the score? | No |
-| Was the fixture commitment externally timestamped before execution? | No |
-| Was hosted H100, CPU, memory, container, API, or leaderboard parity established? | No |
-| Was protected SimulacraBench data used? | No |
-| Has an organizer reviewed, adopted, or endorsed this mapping? | No |
-| Is the synthetic evaluator independent or a VSTD-5 witness? | No |
-| Does this example claim aggregate VSTD-4 depth? | No |
-
-## Failure semantics
-
-- A bundled-byte mismatch rejects the packet.
-- The private artifacts remain `IDENTIFIED` unless an additive observation binds actual
- retrieved bytes to the declared artifact, locator, observer, and observation time.
-- A filed `metric_recomputation_mismatch` leaves the targeted mutant `CHALLENGED` until a
- separate authorized adjudication is evidenced.
-- The declared retention horizon does not elevate availability and is not silently
- rewritten into a retrieval claim.
diff --git a/examples/simulacrabench_synthetic/README.md b/examples/simulacrabench_synthetic/README.md
deleted file mode 100644
index 0ed0290..0000000
--- a/examples/simulacrabench_synthetic/README.md
+++ /dev/null
@@ -1,80 +0,0 @@
-# SimulacraBench synthetic closed-evaluation packet
-
-> **Corrected specimen:** packet `VSTD-SB-SYNTH-002` supersedes the challenged
-> `VSTD-SB-SYNTH-001` specimen. See [`CORRECTION.md`](CORRECTION.md).
-
-This non-normative example maps one recorded local, synthetic run of the pinned
-SimulacraBench public evaluator into VSTD's disclosure and challenge mechanisms. It
-demonstrates what a public packet can honestly retain when verdict-critical private bytes
-are not available to the public checker.
-
-## Bounded recorded claim
-
-Under one founder-operated trust root, the pinned phase-1 scorer was recorded as
-evaluating the pinned marginal-counts baseline against a committed 12,000-respondent
-**synthetic** sandbox with scoring seed `20260822`. The saved participant-visible output
-is `PASS` with reported skill `0.33`.
-
-The public package establishes the identity and internal binding of the public artifacts
-and that saved aggregate. It does **not** rerun the score. It does not establish a
-protected-data run, hosted runner parity, leaderboard entry, organizer review, or
-independent verification.
-
-## Availability result
-
-The exact scored schema, hidden synthetic respondent table, organizer log, execution
-transcript, and generator seed have content addresses and a declared retention horizon.
-They have no public locator and no executed retrieval observation in this packet.
-Therefore their derived level is `IDENTIFIED`, not `AVAILABLE`.
-
-The bundle's public availability assessment is consequently:
-
-```text
-required: AVAILABLE
-derived floor: IDENTIFIED
-accepted: false
-public score reproduction: UNAVAILABLE
-```
-
-A retention promise is not retrieval evidence. An authorized party could later publish
-an additive retrieval observation, but that observation would remain scoped to its named
-trust root and would not automatically become independent verification.
-
-## Verify the public view
-
-From a VSTD source checkout:
-
-```bash
-PYTHONPATH=src python examples/simulacrabench_synthetic/verify_packet.py
-PYTHONPATH=src python examples/simulacrabench_synthetic/verify_packet.py --json
-```
-
-The verifier performs no network access and receives no hidden records. It checks:
-
-- canonical packet and challenge digests;
-- byte identity of the bundled upstream snapshot and public artifacts;
-- the `IDENTIFIED` availability floor and its limiting private artifacts;
-- the explicit disclosure, correction, and trust boundaries; and
-- admission of a non-disclosing challenge, which ends at `CHALLENGED`.
-
-It does not accept a private transcript, execute a retrieval, adjudicate the challenge,
-or move the mutant claim to `REVOKED`.
-
-## Public and private views
-
-| View | Can inspect | Can conclude | Cannot conclude |
-| :-- | :-- | :-- | :-- |
-| Public | Pinned source bytes, exact submission ZIP, generated schema view, commitments, saved participant-visible result, challenge filing | The corrected packet is internally bound; the private artifacts are identified; the mutant filing is `CHALLENGED` | The hidden-fixture score was recomputed; private bytes are available; the challenge was adjudicated; the evaluator is independent |
-| Private holder | Private bytes in addition to the public view | Only what a separately executed, recorded check actually observes under its declared trust root | Organizer endorsement, hosted parity, protected-data performance, public reproducibility, or independent verification |
-
-The deliberate mutant changes only the saved reported skill from `0.33` to `0.34`. Filing
-the declared mismatch challenge changes the mutant claim to `CHALLENGED`. No public
-artifact in this package authorizes an adjudication, so the verifier stops there.
-
-## What VSTD does not claim
-
-VSTD is not accredited or a consensus standard, and this mapping does not claim
-SimulacraBench adoption, endorsement, protected-data use, or independent implementation.
-
-See [`CROSSWALK.md`](CROSSWALK.md) for the source-to-VSTD mapping and
-[`UPSTREAM.md`](UPSTREAM.md) for exact provenance and licensing.
diff --git a/examples/simulacrabench_synthetic/UPSTREAM.md b/examples/simulacrabench_synthetic/UPSTREAM.md
deleted file mode 100644
index acbb65b..0000000
--- a/examples/simulacrabench_synthetic/UPSTREAM.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# Upstream provenance and license
-
-The source snapshot in this example is copied from:
-
-- Repository:
-- Commit: [`1bb2d46026fe0d91979448c3d916506be0608513`](https://github.com/SituatedEvals/public/commit/1bb2d46026fe0d91979448c3d916506be0608513)
-- License: MIT, reproduced byte-for-byte at [`source_snapshot/LICENSE`](source_snapshot/LICENSE)
-
-`public_packet.json` records the SHA-256 digest, byte length, pinned source URL, and local
-snapshot path for every copied file. Each snapshot is the canonical Git-blob byte stream,
-not a platform newline conversion. `verify_packet.py` refuses any mismatch.
-
-The copied files are:
-
-- `README.md`
-- `LICENSE`
-- `config.yml`
-- `data/sample.json`
-- `make_sandbox.py`
-- `score.py`
-- `baseline/marginal_counts/main.py`
-- `baseline/marginal_counts/requirements.txt`
-- `tools/check_submission_zip.py`
-
-The VSTD packet, crosswalk, verifier, and challenge demonstration are original to this
-repository. The snapshot is included to make the public, verdict-critical source bytes
-self-contained rather than treating a remote digest as availability.
diff --git a/examples/simulacrabench_synthetic/artifacts/marginal_counts_submission.zip b/examples/simulacrabench_synthetic/artifacts/marginal_counts_submission.zip
deleted file mode 100644
index 6e93261..0000000
Binary files a/examples/simulacrabench_synthetic/artifacts/marginal_counts_submission.zip and /dev/null differ
diff --git a/examples/simulacrabench_synthetic/artifacts/sandbox_schema.json b/examples/simulacrabench_synthetic/artifacts/sandbox_schema.json
deleted file mode 100644
index e0ff268..0000000
--- a/examples/simulacrabench_synthetic/artifacts/sandbox_schema.json
+++ /dev/null
@@ -1,133 +0,0 @@
-{
- "dataset": {
- "n_rows": 12000,
- "version": "2.0",
- "description": "A toy instrument, not a real survey. Ten items, few enough to print the whole schema and read it. It has one of everything the real schemas have: a frame block that is always visible, items that are scored, a gate chain two deep, and an EXCLUDE column the grader never shows anybody. The GIVEN block is deliberately the cheap half of a questionnaire -- the variables that already sit on a sampling frame, a census roster or another survey of the same households -- and the PREDICT block is the expensive half, the part that needs an enumerator and an interview. Use it to see the shape of the task; use the three real schemas to see whether a method works."
- },
- "items": {
- "region": {
- "question": "Which region do you live in?",
- "class": "GIVEN",
- "values": [
- "North",
- "Central",
- "South"
- ],
- "gate": null
- },
- "urban_rural": {
- "question": "Is the dwelling urban or rural?",
- "class": "GIVEN",
- "values": [
- "Urban",
- "Rural"
- ],
- "gate": null
- },
- "age_band": {
- "question": "How old are you?",
- "class": "GIVEN",
- "values": [
- "18-29",
- "30-44",
- "45-59",
- "60+"
- ],
- "gate": null
- },
- "household_size": {
- "question": "How many people live in this household?",
- "class": "GIVEN",
- "values": [
- "1",
- "2-3",
- "4-5",
- "6 or more"
- ],
- "gate": null
- },
- "household_has_children": {
- "question": "Are there children under 18 in your household?",
- "class": "GIVEN",
- "values": [
- "Yes",
- "No"
- ],
- "gate": null
- },
- "has_mobile_phone": {
- "question": "Does anyone in the household own a mobile phone?",
- "class": "GIVEN",
- "values": [
- "Yes",
- "No"
- ],
- "gate": null
- },
- "interviewer_notes": {
- "question": "Interviewer's free-text notes.",
- "class": "EXCLUDE",
- "values": null,
- "gate": null
- },
- "visited_clinic": {
- "question": "Have you visited a health clinic in the past 12 months?",
- "class": "PREDICT",
- "values": [
- "Yes",
- "No",
- "Prefer not to answer"
- ],
- "gate": null
- },
- "clinic_wait": {
- "question": "How long did you wait to be seen?",
- "class": "PREDICT",
- "values": [
- "Under 30 minutes",
- "30 minutes to 2 hours",
- "Over 2 hours"
- ],
- "gate": {
- "parent": "visited_clinic",
- "observed_if": [
- "Yes"
- ]
- }
- },
- "would_return": {
- "question": "Would you go back to that clinic?",
- "class": "PREDICT",
- "values": [
- "Yes",
- "No",
- "Not sure"
- ],
- "gate": {
- "parent": "clinic_wait",
- "observed_if": [
- "Under 30 minutes",
- "30 minutes to 2 hours",
- "Over 2 hours"
- ]
- }
- },
- "trusts_health_advice": {
- "question": "How much do you trust health advice from your local clinic?",
- "class": "PREDICT",
- "values": [
- "Not at all",
- "A little",
- "Somewhat",
- "A lot"
- ],
- "gate": null
- }
- },
- "split": {
- "n_train": 8000,
- "n_dev": 1900,
- "n_test": 2100
- },
- "gated_value": "NA_GATED"
-}
diff --git a/examples/simulacrabench_synthetic/challenge_demo.json b/examples/simulacrabench_synthetic/challenge_demo.json
deleted file mode 100644
index 9779468..0000000
--- a/examples/simulacrabench_synthetic/challenge_demo.json
+++ /dev/null
@@ -1,111 +0,0 @@
-{
- "challenge_digest": "sha256:e1565eb3add93bdbde5b9462b22b9edcaa43a1d9b7a2007f22f5ca53ccc326a9",
- "challenge_format": "VSTD-CLOSED-EVALUATION-CHALLENGE-0.2",
- "challenge_id": "VSTD-SB-SYNTH-002-CHALLENGE-001",
- "deliberate_mutation": {
- "field": "reported_result.reported_skill",
- "mutated": 0.34,
- "original": 0.33,
- "purpose": "Exercise the declared aggregate-result refutation without revealing hidden records."
- },
- "filing": {
- "challenge_certificate": "sha256:8e59af68d082f640c72d579d329c8a79e4e214dafc3bd0a13e45cd41278eb37b",
- "challenge_type": "metric_recomputation_mismatch",
- "challenged_predicate": "participant_visible_phase_1_score",
- "counterevidence": "The corrected public packet records reported skill 0.33; this filing challenges the deliberate 0.34 mutant. No private recomputation or adjudication is represented.",
- "filed_at": "2026-08-22T18:55:00Z",
- "target_certificate_id": "VSTD-SB-SYNTH-002",
- "target_claim_id": "VSTD-SB-SYNTH-002-RESULT-MUTANT"
- },
- "leak_check": {
- "hidden_item_ids": 0,
- "hidden_item_text": 0,
- "individual_records": 0,
- "labels": 0,
- "raw_predictions": 0,
- "raw_traceback": 0
- },
- "localized_effect": {
- "challenged": [
- "mutated aggregate-result claim"
- ],
- "unchanged": [
- "source commitments",
- "submission commitment",
- "synthetic fixture commitment",
- "existence of the recorded local run",
- "original aggregate-result claim"
- ]
- },
- "refutation_surface": {
- "admissible_refutations": [
- {
- "applies_to": [
- "phase",
- "scoring_seed",
- "source_commit"
- ],
- "overturning_evidence": "An authorized evaluator binds the committed submission, fixture, scorer, and seed, then obtains a different participant-visible status or reported skill.",
- "refutation_type": "metric_recomputation_mismatch",
- "resulting_status": "REVOKED"
- },
- {
- "applies_to": [
- "source_commit"
- ],
- "overturning_evidence": "Bytes retrieved or bundled for any verdict-critical artifact do not match its declared SHA-256 content address.",
- "refutation_type": "evidence_hash_mismatch",
- "resulting_status": "REVOKED"
- },
- {
- "applies_to": [
- "execution_mode",
- "network_control"
- ],
- "overturning_evidence": "The declared evaluator shows that the committed transcript or organizer log does not record the stated local controls or result.",
- "refutation_type": "invalid_execution_receipt",
- "resulting_status": "REVOKED"
- }
- ],
- "coordinate": {
- "parameters": {
- "execution_mode": "local synthetic rehearsal",
- "network_control": "score.py in-process socket denial",
- "phase": "1",
- "sandbox_size": "12000 synthetic respondents",
- "schema": "data/sample.json",
- "scoring_seed": "20260822",
- "source_commit": "1bb2d46026fe0d91979448c3d916506be0608513"
- },
- "predicate": "participant_visible_phase_1_score",
- "subject": "SimulacraBench marginal-counts baseline on a committed synthetic fixture"
- },
- "excluded_claims": [
- {
- "claim_id": "physical_world_completeness",
- "reason": "The observation boundary is this declared local synthetic run only."
- },
- {
- "claim_id": "hosted_competition_equivalence",
- "reason": "Hosted hardware, container, protected-data, API, and leaderboard behavior were not observed."
- },
- {
- "claim_id": "organizer_adoption_or_endorsement",
- "reason": "The example was produced independently and has not been reviewed by the organizers."
- },
- {
- "claim_id": "independent_verification",
- "reason": "The evaluator and challenger are founder-operated under the same trust root."
- }
- ]
- },
- "target_packet_digest": "sha256:6f64a1bfa97a83e10b3a3c034c7d397b853e8dba9baa2db256be0abcfd299296",
- "transitions": {
- "after_public_filing": "CHALLENGED"
- },
- "trust": {
- "adjudicated": false,
- "independent": false,
- "vstd5_witness": false
- }
-}
diff --git a/examples/simulacrabench_synthetic/public_packet.json b/examples/simulacrabench_synthetic/public_packet.json
deleted file mode 100644
index 643127c..0000000
--- a/examples/simulacrabench_synthetic/public_packet.json
+++ /dev/null
@@ -1,549 +0,0 @@
-{
- "availability_summary": {
- "accepted": false,
- "derived_floor": "IDENTIFIED",
- "limiting_artifacts": [
- "scored-sandbox-schema",
- "hidden-synthetic-fixture",
- "organizer-log",
- "execution-transcript"
- ],
- "public_reproduction": "UNAVAILABLE",
- "required": "AVAILABLE"
- },
- "claim": {
- "claim_id": "VSTD-SB-SYNTH-002-RESULT",
- "coordinate": {
- "parameters": {
- "execution_mode": "local synthetic rehearsal",
- "network_control": "score.py in-process socket denial",
- "phase": "1",
- "sandbox_size": "12000 synthetic respondents",
- "schema": "data/sample.json",
- "scoring_seed": "20260822",
- "source_commit": "1bb2d46026fe0d91979448c3d916506be0608513"
- },
- "predicate": "participant_visible_phase_1_score",
- "subject": "SimulacraBench marginal-counts baseline on a committed synthetic fixture"
- },
- "does_not_establish": [
- "a run on SimulacraBench protected data",
- "hosted runner or hardware parity",
- "a leaderboard entry",
- "public recomputation of the hidden-fixture score",
- "organizer adoption, endorsement, or review",
- "independent verification or a VSTD-5 witness",
- "an aggregate VSTD-4 depth claim"
- ],
- "statement": "The pinned SimulacraBench phase-1 scorer evaluated the pinned marginal-counts baseline against the committed 12,000-respondent synthetic sandbox in a local rehearsal with scoring seed 20260822 and returned PASS with participant-visible reported skill 0.33.",
- "status": "RECORDED_UNDER_DECLARED_SYNTHETIC_EVALUATOR"
- },
- "correction": {
- "historical_commit": "a37e6128fc6eccb66160a2f7c3af2f43341c227e",
- "reason": "The superseded packet treated unexecuted private locator and retention declarations as retrieval evidence and publicly adjudicated a founder-authored challenge transcript.",
- "supersedes_packet_digest": "sha256:f182bfce5a5ae8e7137795300d42e285f365e6707b7c3517b3cee7b02331963b",
- "supersedes_packet_id": "VSTD-SB-SYNTH-001"
- },
- "disclosure_interface": {
- "checker_receives": [
- "the declared evaluator receives all committed bytes",
- "the public checker receives commitments, public source, the exact submission archive, and aggregate output only"
- ],
- "checker_returns": [
- "match or mismatch for the participant-visible aggregate",
- "artifact availability or hash failure",
- "no record-level data"
- ],
- "committed": [
- "submission archive and source",
- "official scorer and configuration",
- "synthetic fixture",
- "organizer log",
- "execution transcript",
- "participant-visible result"
- ],
- "does_not_follow": [
- "A public reader cannot recompute the hidden-fixture score.",
- "Availability to the declared evaluator is not portability to arbitrary reviewers.",
- "A digest alone does not prove the committed private bytes are retrievable or correct."
- ],
- "predicate_checked": "Whether the committed scorer, submission, phase, seed, and hidden synthetic fixture produce the committed participant-visible status and reported skill."
- },
- "evidence_inventory": [
- {
- "anonymous_access": false,
- "artifact_id": "upstream-01-README-md",
- "assessed_level": "SELF_CONTAINED",
- "bundle_path": "source_snapshot/README.md",
- "content_address": "sha256:a552ccd52d88607ee3e2da8c8ad46d8a01b0187a61b526ae9a8486e0ead58371",
- "declared_level": "SELF_CONTAINED",
- "disclosure": "public",
- "embedded": true,
- "locator": "",
- "retention": null,
- "retrieval_procedure": "Read source_snapshot/README.md from this example.",
- "role": "upstream protocol documentation",
- "verdict_critical": false
- },
- {
- "anonymous_access": false,
- "artifact_id": "upstream-02-LICENSE",
- "assessed_level": "SELF_CONTAINED",
- "bundle_path": "source_snapshot/LICENSE",
- "content_address": "sha256:f38d690effe75689378dd6cb4376ac4204e41cac540990fa4a5800d15d4f5663",
- "declared_level": "SELF_CONTAINED",
- "disclosure": "public",
- "embedded": true,
- "locator": "",
- "retention": null,
- "retrieval_procedure": "Read source_snapshot/LICENSE from this example.",
- "role": "upstream license",
- "verdict_critical": false
- },
- {
- "anonymous_access": false,
- "artifact_id": "upstream-03-config-yml",
- "assessed_level": "SELF_CONTAINED",
- "bundle_path": "source_snapshot/config.yml",
- "content_address": "sha256:1257f878c9345225c4904108f7d83e6fa680ef2efde1809a0a2d5d4907fbd474",
- "declared_level": "SELF_CONTAINED",
- "disclosure": "public",
- "embedded": true,
- "locator": "",
- "retention": null,
- "retrieval_procedure": "Read source_snapshot/config.yml from this example.",
- "role": "runner and phase configuration",
- "verdict_critical": true
- },
- {
- "anonymous_access": false,
- "artifact_id": "upstream-04-data-sample-json",
- "assessed_level": "SELF_CONTAINED",
- "bundle_path": "source_snapshot/data/sample.json",
- "content_address": "sha256:49a159de7082ba661bf7f642f4758207f74bf7a94ef62cca10c95b653502c4dc",
- "declared_level": "SELF_CONTAINED",
- "disclosure": "public",
- "embedded": true,
- "locator": "",
- "retention": null,
- "retrieval_procedure": "Read source_snapshot/data/sample.json from this example.",
- "role": "public schema source",
- "verdict_critical": true
- },
- {
- "anonymous_access": false,
- "artifact_id": "upstream-05-make_sandbox-py",
- "assessed_level": "SELF_CONTAINED",
- "bundle_path": "source_snapshot/make_sandbox.py",
- "content_address": "sha256:7121c6e0fb6e5e7e1d0810126969c3273f37f0c3f1a9adf675772b6763ace98b",
- "declared_level": "SELF_CONTAINED",
- "disclosure": "public",
- "embedded": true,
- "locator": "",
- "retention": null,
- "retrieval_procedure": "Read source_snapshot/make_sandbox.py from this example.",
- "role": "synthetic fixture generator",
- "verdict_critical": true
- },
- {
- "anonymous_access": false,
- "artifact_id": "upstream-06-score-py",
- "assessed_level": "SELF_CONTAINED",
- "bundle_path": "source_snapshot/score.py",
- "content_address": "sha256:d1853f2af6630d3cace2a57c94be51e7b317ff697b531b21553aea21c11f8090",
- "declared_level": "SELF_CONTAINED",
- "disclosure": "public",
- "embedded": true,
- "locator": "",
- "retention": null,
- "retrieval_procedure": "Read source_snapshot/score.py from this example.",
- "role": "official phase scorer",
- "verdict_critical": true
- },
- {
- "anonymous_access": false,
- "artifact_id": "upstream-07-baseline-marginal_counts-main-py",
- "assessed_level": "SELF_CONTAINED",
- "bundle_path": "source_snapshot/baseline/marginal_counts/main.py",
- "content_address": "sha256:a283c391b2598bc1cb4c108e02fc9f96e019a5fdcec23880bd0458c1ae1308e7",
- "declared_level": "SELF_CONTAINED",
- "disclosure": "public",
- "embedded": true,
- "locator": "",
- "retention": null,
- "retrieval_procedure": "Read source_snapshot/baseline/marginal_counts/main.py from this example.",
- "role": "submitted baseline program",
- "verdict_critical": true
- },
- {
- "anonymous_access": false,
- "artifact_id": "upstream-08-baseline-marginal_counts-requirements-txt",
- "assessed_level": "SELF_CONTAINED",
- "bundle_path": "source_snapshot/baseline/marginal_counts/requirements.txt",
- "content_address": "sha256:21fd07ab6f4f4ce9795aeb82fc039e44ca0d32c553894099c121bb8e840227ac",
- "declared_level": "SELF_CONTAINED",
- "disclosure": "public",
- "embedded": true,
- "locator": "",
- "retention": null,
- "retrieval_procedure": "Read source_snapshot/baseline/marginal_counts/requirements.txt from this example.",
- "role": "submitted dependency declaration",
- "verdict_critical": true
- },
- {
- "anonymous_access": false,
- "artifact_id": "upstream-09-tools-check_submission_zip-py",
- "assessed_level": "SELF_CONTAINED",
- "bundle_path": "source_snapshot/tools/check_submission_zip.py",
- "content_address": "sha256:e1f7316642f440ece2aedd6e149f5f0a47c108fc836c602815fdd026aeee3d8f",
- "declared_level": "SELF_CONTAINED",
- "disclosure": "public",
- "embedded": true,
- "locator": "",
- "retention": null,
- "retrieval_procedure": "Read source_snapshot/tools/check_submission_zip.py from this example.",
- "role": "official submission archive checker",
- "verdict_critical": false
- },
- {
- "anonymous_access": false,
- "artifact_id": "submission-archive",
- "assessed_level": "SELF_CONTAINED",
- "bundle_path": "artifacts/marginal_counts_submission.zip",
- "content_address": "sha256:81cf1a7300431176a7546349580343401732f181f234f62e8f61ada2b25408a8",
- "declared_level": "SELF_CONTAINED",
- "disclosure": "public",
- "embedded": true,
- "locator": "",
- "retention": null,
- "retrieval_procedure": "Read artifacts/marginal_counts_submission.zip from this example.",
- "role": "exact submitted ZIP accepted by the pinned official archive checker",
- "verdict_critical": true
- },
- {
- "anonymous_access": false,
- "artifact_id": "public-sandbox-schema-view",
- "assessed_level": "SELF_CONTAINED",
- "bundle_path": "artifacts/sandbox_schema.json",
- "content_address": "sha256:a1436f8d21626de77f3ee4ad2ac31954d33fa005a72f1633e1698c82f13009ba",
- "declared_level": "SELF_CONTAINED",
- "disclosure": "public",
- "embedded": true,
- "locator": "",
- "retention": null,
- "retrieval_procedure": "Read artifacts/sandbox_schema.json from this example.",
- "role": "LF-normalized public rendering of the generated sandbox schema",
- "verdict_critical": false
- },
- {
- "anonymous_access": false,
- "artifact_id": "scored-sandbox-schema",
- "assessed_level": "IDENTIFIED",
- "bundle_path": "",
- "content_address": "sha256:93862e714744038052a7cd9e4e9be15506ec607ebb9d9de97944499c65f82a67",
- "declared_level": "IDENTIFIED",
- "disclosure": "access-controlled",
- "embedded": false,
- "locator": "",
- "retention": {
- "custodian": "VSTD synthetic evaluator; founder-operated; not independent",
- "horizon": "2026-09-30T23:59:59Z",
- "replicas": 1
- },
- "retrieval_procedure": "",
- "role": "exact schema bytes materialized into the scored synthetic sandbox",
- "verdict_critical": true
- },
- {
- "anonymous_access": false,
- "artifact_id": "hidden-synthetic-fixture",
- "assessed_level": "IDENTIFIED",
- "bundle_path": "",
- "content_address": "sha256:6dd95a59a115fa3bd6e1bee79949d482158f29bb8e4bf94702cc7c161fe7ebf2",
- "declared_level": "IDENTIFIED",
- "disclosure": "access-controlled",
- "embedded": false,
- "locator": "",
- "retention": {
- "custodian": "VSTD synthetic evaluator; founder-operated; not independent",
- "horizon": "2026-09-30T23:59:59Z",
- "replicas": 1
- },
- "retrieval_procedure": "",
- "role": "synthetic respondent table used by the local evaluator",
- "verdict_critical": true
- },
- {
- "anonymous_access": false,
- "artifact_id": "organizer-log",
- "assessed_level": "IDENTIFIED",
- "bundle_path": "",
- "content_address": "sha256:3116827beaf96ad9b31b064b8a48fa8b55fbc8a030adf914f92e347bf674423a",
- "declared_level": "IDENTIFIED",
- "disclosure": "access-controlled",
- "embedded": false,
- "locator": "",
- "retention": {
- "custodian": "VSTD synthetic evaluator; founder-operated; not independent",
- "horizon": "2026-09-30T23:59:59Z",
- "replicas": 1
- },
- "retrieval_procedure": "",
- "role": "raw evaluator log containing non-public score detail",
- "verdict_critical": true
- },
- {
- "anonymous_access": false,
- "artifact_id": "execution-transcript",
- "assessed_level": "IDENTIFIED",
- "bundle_path": "",
- "content_address": "sha256:1df317e2a67c574c40d3d4534739e4e4171690f86a65eec9653949677fb00cb6",
- "declared_level": "IDENTIFIED",
- "disclosure": "access-controlled",
- "embedded": false,
- "locator": "",
- "retention": {
- "custodian": "VSTD synthetic evaluator; founder-operated; not independent",
- "horizon": "2026-09-30T23:59:59Z",
- "replicas": 1
- },
- "retrieval_procedure": "",
- "role": "local scorer transcript containing evaluator-local locations and raw log output",
- "verdict_critical": true
- },
- {
- "anonymous_access": false,
- "artifact_id": "generator-seed",
- "assessed_level": "IDENTIFIED",
- "bundle_path": "",
- "content_address": "sha256:ce566dfde785baa481e550316633c0a42aadc4b10afef89525ab779512ea17c1",
- "declared_level": "IDENTIFIED",
- "disclosure": "access-controlled",
- "embedded": false,
- "locator": "",
- "retention": {
- "custodian": "VSTD synthetic evaluator; founder-operated; not independent",
- "horizon": "2026-09-30T23:59:59Z",
- "replicas": 1
- },
- "retrieval_procedure": "",
- "role": "high-entropy seed retained to regenerate the synthetic fixture",
- "verdict_critical": false
- },
- {
- "anonymous_access": false,
- "artifact_id": "participant-visible-result",
- "assessed_level": "SELF_CONTAINED",
- "bundle_path": "",
- "content_address": "sha256:f49bba1d5df20d195b9d58ee890fd544a815ba7cc565ec198d1d1bf0b04ed7e6",
- "declared_level": "SELF_CONTAINED",
- "disclosure": "public",
- "embedded": true,
- "locator": "",
- "retention": null,
- "retrieval_procedure": "Read reported_result in public_packet.json.",
- "role": "exact participant-visible aggregate returned by the pinned scorer",
- "verdict_critical": true
- }
- ],
- "execution": {
- "mode": "LOCAL_SYNTHETIC_REHEARSAL",
- "observed_local_controls": {
- "held_out_respondents": 1900,
- "network_control": "in-process socket denial from pinned score.py",
- "official_source_bytes_pinned": true,
- "phase_view_respondents": 9900,
- "predict_timeout_seconds": 900,
- "scored_cells": 7600,
- "scoring_seed": 20260822,
- "submission_archive_checker": "PASS",
- "synthetic_respondents": 12000
- },
- "official_policy": {
- "development_rows_scored": true,
- "network_removed_before_submission_import": true,
- "organizer_retains_raw_log": true,
- "participant_return": [
- "reported score",
- "runtime"
- ],
- "phase": 1,
- "predict_timeout_seconds": 900,
- "training_rows_visible": true
- },
- "prior_commitment": {
- "externally_timestamped": false,
- "fixture_frozen_before_execution": true,
- "limitation": "Content addresses were recorded after the local run; the package does not claim an externally witnessed prior commitment."
- },
- "unobserved_hosted_controls": [
- "H100 GPU, 8 CPU, and 16 GB hosted allocation",
- "container-level network isolation",
- "protected benchmark data",
- "submission API and leaderboard path"
- ]
- },
- "limits": {
- "reason": "The public packet has no retrieval observations for verdict-critical private artifacts, so the bundle remains below the VSTD-4 availability requirement.",
- "retention_declaration_horizon": "2026-09-30T23:59:59Z",
- "vstd4_depth_claim": null
- },
- "packet_digest": "sha256:6f64a1bfa97a83e10b3a3c034c7d397b853e8dba9baa2db256be0abcfd299296",
- "packet_format": "VSTD-CLOSED-EVALUATION-PROFILE-0.2",
- "packet_id": "VSTD-SB-SYNTH-002",
- "profile": {
- "name": "SimulacraBench synthetic closed-evaluation crosswalk",
- "normative": false,
- "version": "0.2"
- },
- "refutation_surface": {
- "admissible_refutations": [
- {
- "applies_to": [
- "phase",
- "scoring_seed",
- "source_commit"
- ],
- "overturning_evidence": "An authorized evaluator binds the committed submission, fixture, scorer, and seed, then obtains a different participant-visible status or reported skill.",
- "refutation_type": "metric_recomputation_mismatch",
- "resulting_status": "REVOKED"
- },
- {
- "applies_to": [
- "source_commit"
- ],
- "overturning_evidence": "Bytes retrieved or bundled for any verdict-critical artifact do not match its declared SHA-256 content address.",
- "refutation_type": "evidence_hash_mismatch",
- "resulting_status": "REVOKED"
- },
- {
- "applies_to": [
- "execution_mode",
- "network_control"
- ],
- "overturning_evidence": "The declared evaluator shows that the committed transcript or organizer log does not record the stated local controls or result.",
- "refutation_type": "invalid_execution_receipt",
- "resulting_status": "REVOKED"
- }
- ],
- "coordinate": {
- "parameters": {
- "execution_mode": "local synthetic rehearsal",
- "network_control": "score.py in-process socket denial",
- "phase": "1",
- "sandbox_size": "12000 synthetic respondents",
- "schema": "data/sample.json",
- "scoring_seed": "20260822",
- "source_commit": "1bb2d46026fe0d91979448c3d916506be0608513"
- },
- "predicate": "participant_visible_phase_1_score",
- "subject": "SimulacraBench marginal-counts baseline on a committed synthetic fixture"
- },
- "excluded_claims": [
- {
- "claim_id": "physical_world_completeness",
- "reason": "The observation boundary is this declared local synthetic run only."
- },
- {
- "claim_id": "hosted_competition_equivalence",
- "reason": "Hosted hardware, container, protected-data, API, and leaderboard behavior were not observed."
- },
- {
- "claim_id": "organizer_adoption_or_endorsement",
- "reason": "The example was produced independently and has not been reviewed by the organizers."
- },
- {
- "claim_id": "independent_verification",
- "reason": "The evaluator and challenger are founder-operated under the same trust root."
- }
- ]
- },
- "reported_result": {
- "phase": 1,
- "printed_result": "PASS 0.3300 (35.7s)",
- "privacy_policy": {
- "epsilon": 10.0,
- "item_scores_disclosed": false,
- "laplace_noise": true,
- "raw_skill_disclosed": false,
- "round_to": 0.01
- },
- "reported_skill": 0.33,
- "status": "PASS"
- },
- "source": {
- "artifacts": [
- {
- "bundle_path": "source_snapshot/README.md",
- "bytes": 31507,
- "path": "README.md",
- "sha256": "a552ccd52d88607ee3e2da8c8ad46d8a01b0187a61b526ae9a8486e0ead58371",
- "url": "https://github.com/SituatedEvals/public/blob/1bb2d46026fe0d91979448c3d916506be0608513/README.md"
- },
- {
- "bundle_path": "source_snapshot/LICENSE",
- "bytes": 1132,
- "path": "LICENSE",
- "sha256": "f38d690effe75689378dd6cb4376ac4204e41cac540990fa4a5800d15d4f5663",
- "url": "https://github.com/SituatedEvals/public/blob/1bb2d46026fe0d91979448c3d916506be0608513/LICENSE"
- },
- {
- "bundle_path": "source_snapshot/config.yml",
- "bytes": 2884,
- "path": "config.yml",
- "sha256": "1257f878c9345225c4904108f7d83e6fa680ef2efde1809a0a2d5d4907fbd474",
- "url": "https://github.com/SituatedEvals/public/blob/1bb2d46026fe0d91979448c3d916506be0608513/config.yml"
- },
- {
- "bundle_path": "source_snapshot/data/sample.json",
- "bytes": 3025,
- "path": "data/sample.json",
- "sha256": "49a159de7082ba661bf7f642f4758207f74bf7a94ef62cca10c95b653502c4dc",
- "url": "https://github.com/SituatedEvals/public/blob/1bb2d46026fe0d91979448c3d916506be0608513/data/sample.json"
- },
- {
- "bundle_path": "source_snapshot/make_sandbox.py",
- "bytes": 15241,
- "path": "make_sandbox.py",
- "sha256": "7121c6e0fb6e5e7e1d0810126969c3273f37f0c3f1a9adf675772b6763ace98b",
- "url": "https://github.com/SituatedEvals/public/blob/1bb2d46026fe0d91979448c3d916506be0608513/make_sandbox.py"
- },
- {
- "bundle_path": "source_snapshot/score.py",
- "bytes": 32101,
- "path": "score.py",
- "sha256": "d1853f2af6630d3cace2a57c94be51e7b317ff697b531b21553aea21c11f8090",
- "url": "https://github.com/SituatedEvals/public/blob/1bb2d46026fe0d91979448c3d916506be0608513/score.py"
- },
- {
- "bundle_path": "source_snapshot/baseline/marginal_counts/main.py",
- "bytes": 1686,
- "path": "baseline/marginal_counts/main.py",
- "sha256": "a283c391b2598bc1cb4c108e02fc9f96e019a5fdcec23880bd0458c1ae1308e7",
- "url": "https://github.com/SituatedEvals/public/blob/1bb2d46026fe0d91979448c3d916506be0608513/baseline/marginal_counts/main.py"
- },
- {
- "bundle_path": "source_snapshot/baseline/marginal_counts/requirements.txt",
- "bytes": 294,
- "path": "baseline/marginal_counts/requirements.txt",
- "sha256": "21fd07ab6f4f4ce9795aeb82fc039e44ca0d32c553894099c121bb8e840227ac",
- "url": "https://github.com/SituatedEvals/public/blob/1bb2d46026fe0d91979448c3d916506be0608513/baseline/marginal_counts/requirements.txt"
- },
- {
- "bundle_path": "source_snapshot/tools/check_submission_zip.py",
- "bytes": 24437,
- "path": "tools/check_submission_zip.py",
- "sha256": "e1f7316642f440ece2aedd6e149f5f0a47c108fc836c602815fdd026aeee3d8f",
- "url": "https://github.com/SituatedEvals/public/blob/1bb2d46026fe0d91979448c3d916506be0608513/tools/check_submission_zip.py"
- }
- ],
- "commit": "1bb2d46026fe0d91979448c3d916506be0608513",
- "repository": "https://github.com/SituatedEvals/public"
- },
- "trust": {
- "evaluator": "VSTD synthetic evaluator; founder-operated; not independent",
- "independent": false,
- "organizer_affiliation": "NONE",
- "vstd5_witness": false
- }
-}
diff --git a/examples/simulacrabench_synthetic/source_snapshot/LICENSE b/examples/simulacrabench_synthetic/source_snapshot/LICENSE
deleted file mode 100644
index 0c402b0..0000000
--- a/examples/simulacrabench_synthetic/source_snapshot/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2026 SituatedEvals (Yegor Denisov-Blanch, José Ramón Enríquez, Andreas Haupt)
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/examples/simulacrabench_synthetic/source_snapshot/README.md b/examples/simulacrabench_synthetic/source_snapshot/README.md
deleted file mode 100644
index 830c74b..0000000
--- a/examples/simulacrabench_synthetic/source_snapshot/README.md
+++ /dev/null
@@ -1,615 +0,0 @@
-# SimulacraBench
-
-This README is the submission documentation of the
-[SimulacraBench competition](https://www.codabench.org/profiles/organization/4076/). The participation contract is contained under the Terms tab of that page. Submission of a model is conditional on consent with these terms.
-
-> **This repository is public and holds no microdata.** The schemas in `data/`
-> describe settings, questions answer options only, and declares prediction targets only.
-
-## Quickstart
-
-```
-pip install -r requirements.txt
-python make_sandbox.py --schema data/sample.json --out _sandbox/sample
-python score.py --data _sandbox/sample --schema data/sample.json --phase 1
-```
-
-`score.py` scores a submission against a frame produced from data contained in the directory given in `--data`. The shape of the frame consists of some full respondents, and some masked respondents. Each missing answer is graded separately using the [proper](https://www.wikiwand.com/en/Scoring_rule) log score. It also contains a `--schema` file, which defines how a submission (described below) will be interpreted in as an answer.
-
-You do not have the real data, so `make_sandbox.py` writes a stand-in of the same shape based on the `--schema` to `--out`. This includes skip logig, types, and missingness.
-
-To inspect the mechanics on a small dataset, the above quickstart for `data/sample.json` produces a small, 400-respondent sample.
-
-For submission, copy `baseline/marginal_counts` into a new directory, change
-`predict()` in its `main.py`, add dependencies to its `requirements.txt`, and add files that should be run at runtime, and list in `models.txt` Huggingface model identifiers. Then compress:
-```bash
-cd my_submission && zip -r ../my_submission.zip .
-```
-and upload the resulting `.zip` file. Before uploading, check that the archive
-itself is well-formed — this validates the `.zip`, not your score:
-
-```bash
-python tools/check_submission_zip.py my_submission.zip
-```
-
-If you fitted something offline, copy `baseline/bundled_artifact` instead: it
-loads weights from a file bundled in the `.zip`, which is the mechanism a model
-would use.
-
-The code in this repo includes containerization for transparency, but is not required for local development. It is documented in [Running under Docker](#running-under-docker).
-
-`tutorials/en.ipynb` runs entirely on `data/sample.json`. It walks through the
-shape of the task — schema, frame, gating, canonical order, scoring — and then
-works through what a good model is actually for: making a survey estimate more
-precise without replacing the survey. To use it, run:
-
-```
-pip install -r requirements.txt jupyter
-jupyter notebook tutorials/en.ipynb
-```
-
-The same notebook is available in each official language of the United Nations —
-`ar`, `zh`, `en`, `fr`, `ru`, `es`. All six are built from one source by
-`tutorials/build.py`, so the code is identical and only the prose, the comments
-and the printed labels differ; to change the tutorial, edit `tutorials/build.py`
-and `tutorials/translations.yml` and rebuild rather than editing a notebook.
-
----
-
-## Task
-
-Probabilistic completion of a **respondent × question** grid. Each instrument is
-one wide table: `respondent_id` plus one column per question. Every respondent's
-`GIVEN` block is visible. A number of respondents given in the `--schema` arrives complete. The rest are **held out**. You see their `GIVEN` block and nothing else; every
-`PREDICT` cell is blank, and every blank is scored using the log scoring rule.
-
-For each blank you return a **probability distribution over that question's
-option list** — not a guess at the answer. Every scored answer is categorical,
-drawn from that question's own options. The metric is a strictly proper scoring
-rule, so your best expected score comes from reporting what you actually
-believe.
-
-The three datasets are documented in the schemas contained in `/data`. The leaderboard gives per-Dataset and the mean of *skills*:
-
-```math
-\mathrm{skill} = 1 + \frac{\mathcal{L}}{U}
-\qquad\qquad
-\mathrm{Skill} = \tfrac{1}{3}\left(\mathrm{skill}_{\text{UNICEF}} + \mathrm{skill}_{\text{World Bank}} + \mathrm{skill}_{\text{UNHCR}}\right)
-```
-
-where the log score and the uniform reference are
-
-```math
-\mathcal{L} = \frac{1}{|C|} \sum_{(i,j)\in C} \log p_{ij} \left[ y_{ij} \right]
-\qquad\qquad
-U = \frac{1}{|S|} \sum_{j \in S} \log K_j
-```
-
-and the vector that is actually scored is yours, renormalized and mixed with a
-flat one:
-
-```math
-p_{ij} = \varepsilon + \left(1 - K_j\,\varepsilon\right)\frac{q_{ij}}{\sum_{k} q_{ij,k}},
-\qquad \varepsilon = 10^{-3}
-```
-
-$S$ is the instrument's scored items, $K_j$ the option count of item $j$, $C$ the blank cells, $q_{ij}$ the vector you returned and $y_{ij}$ the answer that was actually there. So **0 is a uniform guess and 1 is perfection**; the full account is under [Scoring](#scoring).
-
-A `predict()` does not need to perform well on all datasets, but must produce legal outputs (a probability distribution per output) for all of them.
-
-### Phases
-
-There are two phases: a development and a test phase. They are distinguished by the frame given, the compute allocated (for module import, model loading, per compute call), and the returned values. These are documented in `config.yml`, which is the authority.
-
-| | Phase 1 — Development | Phase 2 — Final |
-|---|---|---|
-| Visible, answers included | `TRAIN` | `TRAIN` and `DEV` |
-| `GIVEN` only, `PREDICT` masked and scored | `DEV` | `TEST` |
-| Wall-clock Compute Budget | 900 s (`phases.1.timeout_seconds`) | 3600 s |
-| Submissions | 1 per day | 1 |
-| Score returned | Laplace-noised, rounded to `phases.1.round_to` (0.01) | exact |
-
-Every respondent carries one of three roles — `TRAIN`, `DEV` or `TEST` — in the
-`role` column of the delivered file, assigned once when the dataset is built and
-never redrawn. How many respondents carry each is declared in the schema's
-`split` block, as counts. Nothing is subsampled in either phase: a phase takes
-every row of every role it is entitled to.
-
-The phases therefore do not nest: `TEST` respondents are not shipped at all in
-phase 1, so a phase-1 leaderboard probed all through development is not an
-answer key for phase 2, and `DEV` respondents return in phase 2 as visible rows,
-answers included, which is where they are worth most.
-
-Because the scored set is fixed within a phase, every submission is scored on
-the same cells and two leaderboard entries are directly comparable — the
-sampling variability they share cancels in the difference between them.
-
-The budget covers the **whole run**: module import, model loading, and all three
-`predict()` calls together.
-
----
-
-## Submission
-
-All submissions are **code submissions**. You upload a `.zip`; the platform runs
-your code against the phase's hidden slice and writes the predictions on your
-behalf.
-
-```text
-submission.zip/
- main.py # required, at the `.zip` root
- requirements.txt # pinned dependencies, installed before the run
- models.txt # Huggingface identifier, e.g., `google-bert/bert-base-cased`
- any_other_files/ # weights, lookup tables, fitted state
-```
-
-Build the archive from **inside** your submission directory, so `main.py` sits
-at the archive root rather than inside a folder:
-
-```bash
-cd my_submission && zip -r ../my_submission.zip .
-```
-
-Do not upload a `.zip` that contains another `.zip` — the entry module has to be a
-real file at the root. Archives with absolute or parent-escaping paths, or with
-implausible file counts or compression ratios, are rejected. The `.zip` is
-capped at 1 GB. `requirements.txt` and `models.txt` are optional, hosted and
-under `score.py` alike: the image already carries numpy, pandas, torch,
-transformers and the rest, and `requirements.txt` exists only to add what the
-image does not have.
-
-```python
-def predict(frame, schema):
- ...
- return vectors # list[list[float]]
-```
-
-`predict()` is called **once per instrument** — not once per cell — with two
-positional arguments, and returns one probability vector per blank cell.
-
-There is no runtime training hook, and training must happen **offline**.
-Module-level code runs once when the container starts, before `predict()` is
-called, so that is where weights, tokenizers, lookup tables and fitted state
-should be loaded — not inside `predict()`, which is on the clock. An import or
-setup failure there fails the submission before any predictions are made.
-
-### `frame`
-
-A wide DataFrame: `respondent_id` plus one column per shipped item, in schema
-key order. Visible respondents come complete; held-out respondents have every
-`PREDICT` cell `NaN`. Only `GIVEN` and `PREDICT` items are shipped — `EXCLUDE`
-records stay in the schema and never appear as columns, so filter on `class`
-rather than assuming the two line up.
-
-**`NaN` means exactly one thing: this cell is held out, predict it.** It never
-means "they did not answer". Genuine non-response is an ordinary level —
-`Prefer not to answer`, `98. I don't know`, `99. Refused to answer` — and
-`load_schema` rejects a schema with a null in an option list. Every non-`NaN` cell is a real
-answer you need to assign a probability to.
-
-### `schema`
-
-The file in `data/`, plus `schema["gated_value"]` filled in from `config.yml`.
-
-| Key | Holds |
-|---|---|
-| `dataset` | `n_rows`, `version`, and a prose `description` of the instrument |
-| `items` | one record per item, in the grader's order |
-| `split` | `n_train`, `n_dev` and `n_test`, counts of respondents summing to `dataset.n_rows` |
-| `gated_value` | the level meaning "this person was never asked" (`NA_GATED`) |
-
-Each `items` record holds four keys:
-
-| Key | Holds |
-|---|---|
-| `question` | the wording, as asked |
-| `class` | `GIVEN`, `PREDICT` or `EXCLUDE` |
-| `values` | the allowed answers, never null |
-| `gate` | `{parent, observed_if}`; null or absent when the item is always asked |
-
-| Class | Meaning |
-|---|---|
-| `GIVEN` | Always visible, for everybody. Never scored. |
-| `PREDICT` | Held out and scored. What the competition is about. |
-| `EXCLUDE` | Identifiers, record keys, free text, admin fields. Never shown, never scored. |
-
-### Option order
-
-Your vector follows `schema["items"][item]["values"]` in order, **plus a final
-slot for `schema["gated_value"]` for the probability this question being gated.**
-
-Read it from the schema, never from the data — an option nobody chose still has a
-slot. Skip-logic gating is not missingness: being never asked is a real answer,
-scored like any other, so predicting who gets skipped is worth as much as
-predicting what they say. `gate` tells you which earlier answer decides it, and
-a gated item's answer is determined whenever its parent is visible.
-
-### Question order
-
-Rows top to bottom, and within a row, items in `schema["items"]` key order —
-**not** `frame.columns` order, which may differ.
-
-### Return value
-
-A list of lists of floats, one vector per blank cell, each as long as
-that item's option list with the gate sentinel included. Ingestion validates the
-return tup before anything is written, and applies exactly these rules:
-
-| Rule | Failure |
-|---|---|
-| The return value is a `list` or `tuple` | a wrong type fails the submission |
-| One vector per blank cell | a wrong count fails the submission |
-| Each vector is numeric and finite and as wide as that item's option list, sentinel included | a wrong width fails the submission, in canonical order |
-| Every entry is finite and non-negative | `NaN`, infinity, or negative numbers fail the submission |
-| Each vector sums to more than zero | an all-zero vector fails the submission |
-
-You do not need to floor or normalize: the grader renormalizes every vector and
-mixes it with a flat vector before scoring. An exception raised inside
-`predict()` fails the submission, as does a failure while importing your module.
-
-### What `predict()` may and may not do
-
-Read whatever you bundled — weights, lookup tables, fitted state — from inside
-your own directory, and import whatever the image provides or your
-`requirements.txt` and `models.txt` declare. No other downloads. The grader
-removes the network before your code is imported, and will raise an exception, failing the submission.
-
----
-
-## What you can build
-
-The task is open-ended. Fit statistical or psychometric models (IRT, low-rank completion, tabular generative models) on the schema and your own practice data; build features from the `GIVEN` block and the item descriptions.
-
-The two obvious levers over the crowd-marginal baseline are the skip logic,
-which determines a gated item's answer whenever its parent is visible, and
-whatever the `GIVEN` block tells you about a respondent you have never seen.
-
-The organizers provide `torch_measure` in the runtime image for latent-trait /
-IRT-style modeling of survey responses. Use it only if it helps your approach.
-
----
-
-## The hosted runtime
-
-Every submission runs on the same hardware. There is no routing, no tier
-selection and no way to request different hardware — a `gpu:` line in `metadata`
-is ignored. Resource exhaustion fails a submission:
-
-| | |
-|---|---|
-| GPU | 1 × H100 |
-| Memory | 16 GB |
-| CPU | 8 cores |
-| Wall-clock budget | 900 s in Development, 3600 s in Final |
-| Network | none |
-| Python | 3.13, with pre-installs `numpy pandas pyarrow scipy scikit-learn
-torch torchvision Pillow
-transformers sentence-transformers tokenizers sentencepiece tiktoken
-huggingface_hub accelerate safetensors bitsandbytes autoawq protobuf
-torch_measure` |
-
-Memory is a hard limit, not a target: exceeding it terminates the run. The data
-itself is small — about 50 MB for all three instruments — so the budget is there
-for your model. The wall-clock budget covers all three instruments together, not
-900 s each. Each submission runs in a fresh container that is destroyed
-afterwards, so module-level state does not persist between submissions.
-
-`requirements.txt` is installed while the runtime image is built, before your
-container exists and before your clock starts, so the install does not spend
-your run budget — but it has its own ceiling, and exceeding it fails the
-submission with `HSCC-BUILD-002`. Normal named pip requirements only: avoid pip
-options, editable installs and source-build-only packages, and **pin exact
-versions**, since an unpinned requirement makes pip search many candidates.
-
-### Bringing a model
-
-There is no network access at runtime, so nothing can be downloaded while your
-code runs, and nothing is pre-fetched for you. Either bundle weights directly into your submission (`.pt`, `.pth`, `.safetensors`, `.bin`, `.ckpt`,
-`.pkl`, `.joblib`, `.npy` are all accepted, for example), below 1GB. You may also use models hosted on HuggingFace, by including their identifier (e.g., `google-bert/bert-base-cased`) in `models.txt`.
-
----
-
-## What happens when you submit
-
-1. The `.zip` is validated for archive safety and layout, and your `main.py` is
- statically checked for referenced files that are missing from the `.zip`.
-2. Any packages in `requirements.txt` are installed while the runtime image is
- built, before your container exists and before your clock starts, and Huggingface models are loaded with `for repo in lines:
- p = snapshot_download(repo, cache_dir=os.environ["HF_HUB_CACHE"])`
-3. The orchestrator materializes the phase's hidden slice for each instrument —
- the frame with held-out cells blanked, the schema, and the canonical cell
- order. The answer key is not among them and never enters the container.
-4. Your container starts, network-isolated, and imports `main.py` once.
-5. For each instrument in turn: `predict(frame, schema)` is called once, the
- returned vectors are validated, and they are written out aligned to the
- canonical cell order.
-6. The orchestrator scores each instrument, applies
- the phase's privacy mechanism, and posts the result to the leaderboard.
-
-As the data is airgapped, we do not provide you with `stdout` or tracebacks. We only provide you with codes to localize the error.
-
-A diagnostic may carry the failure phase, a sanitized exception type, the
-exception text and a line number and frame context **only for load/import
-diagnostics**, your file's basename, output count and type facts, safe
-dependency names for load/import failures, timeout and resource facts, and
-approximate progress counts. Hidden-runtime diagnostics drop to the basename
-alone, such as `main.py`. They never include raw tracebacks, submitted source
-line text, absolute paths, hidden item IDs, hidden item text, labels, URLs,
-tokens, or hidden-derived runtime names.
-
-```text
-[HSCC-DEPS-001] Missing package: your code tried to import a module that is not installed.
-Detail: ModuleNotFoundError: No module named 'missing_pkg'
-Participant frames: main.py:1 in
-Facts: missing module: missing_pkg.
-
-[HSCC-PREDICT-001] Runtime error in predict(): your predict() function raised KeyError at main.py.
-Participant file: main.py
-
-[HSCC-PREDICT-002] Invalid predict() output: predict() must return one probability vector per blank cell.
-Participant file: main.py
-Facts: vector count: returned 1, expected 144 (one per blank cell).
-```
-
-Note what the second one does **not** say. Your exception's message is dropped —
-only its type survives — because a message can carry hidden data the moment your
-code interpolates a value into it. Same reason there is no traceback and no line
-number inside `predict()`. Debug locally, where you get all three.
-
-| Code family | What it means | What to fix |
-|---|---|---|
-| `HSCC-ZIP-*` | The uploaded `.zip` layout is wrong or unsafe (as deemed by our code analysis). | Put `main.py` at the `.zip` root; do not upload a folder-wrapped `.zip` or a `.zip` containing another `.zip`. |
-| `HSCC-ARTIFACT-001` | Your code referenced a local file that was not bundled. | Add the named file, such as `ncf_head.pt` or `features.npy`, to the `.zip` or update the path in your code. |
-| `HSCC-IMPORT-*` / `HSCC-DEPS-*` | `main.py` could not load. | Fix imports, syntax, missing packages, or module-level setup; rerun `tools/check_submission_zip.py`. |
-| `HSCC-HF-CACHE` | Your code tried to download model files at runtime. | Bundle the weights in the `.zip` and load them from a local path. Nothing is pre-fetched for you. |
-| `HSCC-BUILD-001` | The hosted runtime image could not be built from your dependency choices. | Simplify `requirements.txt`, remove unsupported packages or pins, or use pre-installed packages. |
-| `HSCC-BUILD-002` | The dependency install exceeded its own time ceiling. | Pin exact versions so pip does not search many candidates. |
-| `HSCC-NETWORK-*` | Runtime code tried to make a blocked third-party network call. | Bundle what you need in the `.zip`; do not fetch internet resources inside `predict()`. |
-| `HSCC-PREDICT-*` | `predict()` raised, or returned the wrong number of vectors, the wrong width, or non-finite / negative / all-zero values. | Return one vector per blank cell in canonical order, each as wide as that item's option list plus the gate slot; test on all three schemas. |
-| `HSCC-SCORING-*` | The returned vectors could not be matched to the scored cells. | Ensure `predict()` returns a vector for every blank cell, in the frame's canonical cell order. |
-| `HSCC-TIMEOUT-*` / `HSCC-CONTAINER-*` | The run timed out, exited early, or likely ran out of memory. | Move training offline, load compact artifacts at module import, and reduce per-call work. |
-| `HSCC-INFRA-*` | The platform could not queue, archive, collect, or retain enough run detail. | Retry once, then start a forum post |
-| `HSCC-UNKNOWN-001` | The failure did not match a safe known pattern. | Run the local tools and check `main.py`, `requirements.txt`, and bundled files before starting a forum post |
-
-The numeric failure modes ingestion distinguishes:
-
-| Code | Meaning |
-|---:|---|
-| `10` | No entry module — `main.py` was not at the `.zip` root |
-| `11` | `main.py` could not be imported, or defines no callable `predict(frame, schema)` |
-| `20` | Staged instrument data was missing or unreadable (organizer-side; retry, then report it) |
-| `40` | `predict()` raised an exception |
-| `41` | `predict()` exceeded a per-instrument cap, when one is configured |
-| `42` | `predict()` returned invalid output — wrong type, wrong vector count, wrong width, non-finite, negative, or all-zero |
-| `50` | The run exceeded the phase's wall-clock budget |
-| `1` | Unexpected error |
-
-`HSCC-INFRA-*` and `HSCC-UNKNOWN-001` mean the platform could not classify the
-failure safely: retry once, then start a forum post.
-
----
-
-## Scoring
-
-**Log score.** For each blank cell, `log(p)` of the probability you gave the
-answer that was actually there, averaged over cells. At most 0. **Higher is
-better.**
-
-**Skill** puts that on a scale the instruments share:
-
-```
-skill = 1 + log_score / U U = mean over scored items of log K
-```
-
-`K` is an item's option count, sentinel included. `U` is the surprisal of a
-uniform guess, in nats, and it comes from the schema alone — no data, fixed
-before anybody submits. So `skill` is **0** for a uniform guess, **1** for
-perfection, and negative for worse than guessing. It is the leaderboard metric;
-see `leaderboard` in `config.yml`.
-
-Every vector is renormalized and mixed with a flat vector before scoring, so no
-probability falls below `scoring.floor` in `config.yml` while the vector still
-sums to 1:
-
-```text
-p <- p / sum(p)
-p <- floor + (1 - K*floor) * p floor = 1e-3
-```
-
-Mixing rather than clipping is what keeps both promises at once, and it is what
-bounds the cost of a single cell. A zero therefore costs `log(1e-3)` ≈ −6.9
-rather than negative infinity.
-
-That does not make confident wrong answers cheap. A confidently wrong cell
-costs the full `log(floor)`, against about −1.4 for an honest hedge over four
-options, and the whole distance between a uniform guess and a good crowd
-marginal is far smaller than that.
-
-The rule is proper: your best expected score comes from reporting what you
-actually believe.
-
-The skills across datasets are taken as a flat mean for the grand prize number.
-
-In **phase 1** you get back your skill plus a Laplace draw, rounded to
-`phases.1.round_to`. In **phase 2** you get the exact number.
-
-## Files
-
-| File | What it is | You edit it? |
-|---|---|---|
-| `data/*.json` | one schema per instrument: items, options, order | no |
-| `make_sandbox.py` | writes practice data of the schema's exact shape | no |
-| `score.py` | runs a submission the way the grader will, and scores it | no, run it |
-| `tools/check_submission_zip.py` | validates an upload `.zip` against the contract — says nothing about your score | no, run it |
-| `config.yml` | phases, time limits, privacy, sandbox knobs | no |
-| `baseline/marginal_counts/` | reference submission: the crowd marginal, the thing to beat | copy it |
-| `baseline/bundled_artifact/` | the same model, reading a bundled artifact — the shape to copy if you fitted something offline | copy it |
-| `tutorials/{ar,zh,en,fr,ru,es}.ipynb` | the task on `data/sample.json`, then what a good model buys a survey | no, run it |
-| `tutorials/build.py`, `tutorials/translations.yml` | one source for all six notebooks | only to change the tutorial |
-
-The two baselines differ only in where their numbers come from.
-`marginal_counts` reads the visible answers and nothing else;
-`bundled_artifact` adds prior weights it loads from `artifacts/prior_weights.csv`
-at module import, which is the mechanism a bundled model would use — the CSV is
-a stand-in for a `.joblib`, `.safetensors` or `.pt` file, and only the loader
-line changes.
-
-### `make_sandbox.py`
-
-```
-python make_sandbox.py --schema data/unicef.json --out _sandbox/unicef
-```
-
-Writes two files, which together are what a delivered dataset looks like:
-
-| File | Holds |
-|---|---|
-| `respondents.parquet` | every respondent, plus a `role` column |
-| `schema.json` | what `predict()` receives |
-
-Roles are assigned here, once, in the counts the schema's `split` block declares
-— not in `score.py`, which only looks them up. Nothing is sampled: the first
-`n_train` rows are `TRAIN`, the next `n_dev` are `DEV` and the remaining
-`n_test` are `TEST`, so the file's composition is exactly what the schema says:
-
-| Role | Phase 1 | Phase 2 |
-|---|---|---|
-| `TRAIN` | visible, answers included | visible, answers included |
-| `DEV` | `GIVEN` only, `PREDICT` masked and scored | visible, answers included |
-| `TEST` | not shipped at all | `GIVEN` only, `PREDICT` masked and scored |
-
-Whole respondents go one way: splitting cells instead would leave a gated child
-visible while its parent was hidden, which gives the parent away. Row count
-comes from `dataset.n_rows`: the sandbox is the shape of the real file, not a
-sample, and there is no flag to change it.
-
-### `score.py`
-
-```
-python score.py --submission baseline/marginal_counts --data _sandbox/unicef \
- --schema data/unicef.json --phase 1
-```
-
-`--data` is a directory holding `respondents.parquet`. In order:
-
-1. **Ingests** the file and type checks it against the schema — every declared
- column present, `respondent_id` unique, every role one of the three and
- present in the count `split` declares for it, every value one the schema
- lists. A bad dataset fails in a second rather than an hour into an H100.
-2. **Selects** this phase's visible and hidden roles — all of them, nothing
- subsampled — and blanks every `PREDICT` cell of the hidden rows.
-3. **Installs** `requirements.txt` into a fresh venv. The only moment anything
- reaches the network.
-4. **Runs** `predict()` with the network gone, under the phase's time limit.
- `socket.socket` is replaced with a class that raises before your code is
- imported.
-5. Checks every vector, floors, scores, privatizes.
-
-On success it prints `PASS`, the score, and how long the whole run took — that
-is the entirety of what the grader returns. Flags:
-`--phase {1,2}`, `--seed`, `--timeout`, `--keep`, `--docker`, and — locally
-only, never on the worker — `--log FILE` and `--show-log` for the organizer-side
-diagnostics.
-
-### `tools/check_submission_zip.py`
-
-```
-python tools/check_submission_zip.py my_submission.zip
-```
-
-**This validates the `.zip`, not your model.** It answers one question — *would
-the platform accept this archive and get legal output out of it?* — and says
-nothing whatever about your score. `score.py` is the tool for that, and the two
-are not substitutes: a submission can pass this and score below a uniform guess,
-or score well and still be rejected for a layout mistake that costs you a day's
-quota.
-
-It takes the `.zip` itself, not a directory, because the archive is what gets
-uploaded and most rejections are properties of the archive. It runs the same
-checks hosted ingestion runs, in the order ingestion runs them:
-
-| Check | Catches |
-|---|---|
-| Archive safety and layout | absolute or parent-escaping paths, a `.zip` inside the `.zip`, `main.py` missing or nested inside a folder |
-| `requirements.txt` | a file outside the root, and lines that are not plain named packages — pip options, URLs, local paths, nested requirements |
-| Bundled artifacts | a literal path your code opens that is not in the `.zip` — the `HSCC-ARTIFACT-001` failure, found before it costs you a submission |
-| Import | `main.py` failing to import, or defining no callable `predict(frame, schema)` |
-| `predict()` | an exception, and a return value that is the wrong type, count, or width, or holds non-finite or negative entries |
-
-The last two run `predict()` on a tiny instrument built in-process — three
-respondents, one gated item — so the vectors are checked against a real option
-list with a sentinel slot. That instrument carries **no signal**: passing it
-means your code is well-formed, not that it predicts anything.
-
-Prints `OK` and exits 0, or one `ERROR:` line and exits 1. Unlike a hosted run,
-you get the whole message, so debug here rather than against an `HSCC-*` code.
-
-### Running under Docker (Optional)
-
-By default the network is cut inside the interpreter. `--docker` runs the same driver
-under `docker run --network=none --read-only`, enforcing it at the kernel and
-building your `requirements.txt` into a clean `python:3.13-slim` — the Python of
-the hosted image. The container is capped at the worker's own limits, read from
-`runner` in `config.yml`: **16 GB and 8 CPUs**. A run that fits here fits there,
-and one that does not is killed here, where you can see why.
-
-The base image is bare, though, where the hosted one arrives with torch,
-transformers and the rest already installed. Anything you want under `--docker`
-has to be in your `requirements.txt`, even if the hosted image would have
-provided it. There is no GPU in the local container; the worker has one H100.
-
-| Platform | Install |
-|---|---|
-| macOS | `brew install --cask docker`, then launch Docker Desktop once |
-| Windows | Docker Desktop from docker.com; needs WSL 2 |
-| Debian / Ubuntu | `curl -fsSL https://get.docker.com \| sh`, then `sudo usermod -aG docker $USER` and re-login |
-| Fedora / RHEL | `sudo dnf install docker-ce docker-ce-cli containerd.io`, then `sudo systemctl enable --now docker` |
-
-Verify with `docker run --rm hello-world`; the daemon must be running. The first
-`--docker` run pulls the base image and installs your requirements; later runs
-reuse the cached layer.
-
----
-
-## Before you upload
-
-- `score.py` prints `PASS` on all three real schemas. A `predict()` that assumes
- one instrument's shape fails on the others.
-- Option order comes from the schema, never from the data.
-- Every import is either in the hosted image or in `requirements.txt`, pinned.
-- Every weight file, lookup table and fitted artifact your code opens is inside
- the zip, loaded from a local path. Nothing is downloaded at runtime.
-- You tuned on cells you hid from yourself, not on the cells you are scored on.
-- `main.py` is at the **top level** of the zip, not inside a folder, and the zip
- contains no other zip. Build it with `cd my_submission && zip -r ../sub.zip .`.
-
-Which starter to copy:
-
-- **`baseline/marginal_counts/`** — the crowd marginal: each item's smoothed
- visible distribution, ignoring the individual respondent. This is what you
- have to beat. Start here.
-- **`baseline/bundled_artifact/`** — the same model, plus prior weights read
- from a bundled CSV at module import. Copy this shape if you fitted something
- offline; the CSV stands in for a model file, and only the loader changes.
-
-Copy one, then build the `.zip` from inside it and run both checks:
-
-```bash
-cp -R baseline/marginal_counts my_submission
-(cd my_submission && zip -r ../my_submission.zip .)
-
-python tools/check_submission_zip.py my_submission.zip # will the platform accept it?
-python score.py --submission my_submission \
- --data _sandbox/unicef --schema data/unicef.json --phase 1
-```
-
-The two answer different questions and you want both. The first validates the
-archive against the contract — layout, requirements, bundled files, import,
-`predict()` output — and tells you nothing about your score. The second gives
-you a score, on practice data, and does not look at your `.zip` at all. Run the
-second on all three instruments: a `predict()` that assumes one instrument's
-shape fails on the others.
-
-## Getting help
-
-Use the forum on [Codabench](https://www.codabench.org/profiles/organization/4076/)
\ No newline at end of file
diff --git a/examples/simulacrabench_synthetic/source_snapshot/baseline/marginal_counts/main.py b/examples/simulacrabench_synthetic/source_snapshot/baseline/marginal_counts/main.py
deleted file mode 100644
index 0b0b833..0000000
--- a/examples/simulacrabench_synthetic/source_snapshot/baseline/marginal_counts/main.py
+++ /dev/null
@@ -1,42 +0,0 @@
-"""Your submission. Edit predict(). See README.md for the rules and the
-contract: what you are given, what you return, and in what order.
-"""
-
-import numpy as np
-import pandas as pd
-
-
-def predict(frame, schema):
- """Return one probability vector per blank cell, in canonical order.
-
- frame respondent_id plus one column per item. NaN means "held out,
- predict this"; every other cell is an answer you may use.
- schema the instrument schema, as in data/, plus schema["gated_value"].
-
- The baseline here predicts the crowd: for each item, the smoothed
- distribution of the answers that are visible. It ignores everything about
- the individual respondent, which is exactly what you are trying to beat.
- """
- items = [name for name, record in schema["items"].items()
- if record["class"] in ("GIVEN", "PREDICT")]
-
- options = {}
- for item in items:
- record = schema["items"][item]
- options[item] = list(record["values"]) + (
- [schema["gated_value"]] if record.get("gate") else [])
-
- marginals = {}
- for item in items:
- counts = frame[item].value_counts()
- # Half a count on every option, so an option nobody chose is unlikely
- # rather than impossible. A zero here would cost you the run.
- weights = np.array([counts.get(option, 0) + 0.5
- for option in options[item]], float)
- marginals[item] = weights / weights.sum()
-
- values = frame[items].to_numpy(dtype=object)
- return [marginals[items[column]]
- for row in range(values.shape[0])
- for column in range(len(items))
- if pd.isna(values[row, column])]
diff --git a/examples/simulacrabench_synthetic/source_snapshot/baseline/marginal_counts/requirements.txt b/examples/simulacrabench_synthetic/source_snapshot/baseline/marginal_counts/requirements.txt
deleted file mode 100644
index 24cd59e..0000000
--- a/examples/simulacrabench_synthetic/source_snapshot/baseline/marginal_counts/requirements.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-# Everything your submission imports must be named here. This file is
-# installed while the network is still up; after that there is no way to get a
-# package, so anything you forgot is an ImportError at scoring time
-# Pin your own dependencies exactly (package==1.2.3)
-numpy>=1.26
-pandas>=2.2
diff --git a/examples/simulacrabench_synthetic/source_snapshot/config.yml b/examples/simulacrabench_synthetic/source_snapshot/config.yml
deleted file mode 100644
index ac6b52a..0000000
--- a/examples/simulacrabench_synthetic/source_snapshot/config.yml
+++ /dev/null
@@ -1,74 +0,0 @@
-leaderboard:
- # Mean log probability the submission gave the answer that was actually
- # there, divided by the schema's uniform reference and shifted so that a
- # uniform guess scores 0 and a perfect one scores 1. Higher is better.
- metric: skill
- direction: maximize
- # Across instruments: the plain mean of their skills. The normalisation is
- # the weighting -- see README, "Weighting across instruments".
- combine: mean
-
-# The answer meaning "this person was never asked". One string for all three
-# instruments, so it lives here rather than three times over in data/.
-# load_spec puts it into the spec, which is what predict() receives, so a
-# submission reads it as spec["gated_value"].
-gated_value: NA_GATED
-
-privacy:
- mechanism: laplace
- epsilon: 10.0
-
-# Who is visible and who is scored is decided by the respondent roles in the
-# delivered dataset, not here: phase 1 shows TRAIN in full and scores DEV,
-# phase 2 shows TRAIN and DEV in full and scores TEST. Nothing is sampled or
-# thinned in either phase -- a phase takes every row of every role it is
-# entitled to, so every submission in a phase is scored on the same cells, and
-# that is what makes two leaderboard entries comparable to each other. How many
-# respondents carry each role is in the schema's `split` block, as counts.
-phases:
- 1:
- name: Development
- timeout_seconds: 900
- noised: true
- round_to: 0.01
- logging: verbose
- 2:
- name: Final
- timeout_seconds: 3600
- noised: false
- round_to: null
- logging: verbose
-
-
-sandbox:
- # How skewed the invented per-item marginals are. Below 1 the Dirichlet
- # concentrates on a few options, which is what real survey items look like and
- # what makes the crowd-marginal baseline meaningfully better than uniform.
- dirichlet_alpha: 0.9
- # Spread of the per-item loadings on the one hidden trait each invented
- # respondent carries. It sets how much the items know about each other. Too
- # low and the sandbox is hostile to every method that looks at a respondent
- # rather than a column: at 0.6 a whole demographic block explained about 1%
- # of the variance of an attitude item, which is far less than a real
- # instrument, and left nothing for a conditional model to find.
- trait_scale: 2.0
-
-scoring:
- floor: 1.0e-3
- bootstrap_draws: 2000
- # Base image for `score.py --docker`. Tracks the Python of the hosted image,
- # so a submission that imports cleanly here imports cleanly there. It is a
- # bare slim image, not the hosted one: everything the hosted image
- # pre-installs has to be in the submission's requirements.txt to appear
- # locally.
- docker_image: python:3.13-slim
-
-
-# The single machine every submission runs on. `score.py --docker` caps the
-# container at these limits, so a run that fits locally fits on the worker.
-runner:
- gpu: H100
- gpu_count: 1
- cpus: 8
- memory_gb: 16
- network: none
diff --git a/examples/simulacrabench_synthetic/source_snapshot/data/sample.json b/examples/simulacrabench_synthetic/source_snapshot/data/sample.json
deleted file mode 100644
index 1757bb0..0000000
--- a/examples/simulacrabench_synthetic/source_snapshot/data/sample.json
+++ /dev/null
@@ -1,86 +0,0 @@
-{
- "dataset": {
- "n_rows": 12000,
- "version": "2.0",
- "description": "A toy instrument, not a real survey. Ten items, few enough to print the whole schema and read it. It has one of everything the real schemas have: a frame block that is always visible, items that are scored, a gate chain two deep, and an EXCLUDE column the grader never shows anybody. The GIVEN block is deliberately the cheap half of a questionnaire -- the variables that already sit on a sampling frame, a census roster or another survey of the same households -- and the PREDICT block is the expensive half, the part that needs an enumerator and an interview. Use it to see the shape of the task; use the three real schemas to see whether a method works."
- },
- "items": {
- "region": {
- "question": "Which region do you live in?",
- "class": "GIVEN",
- "values": ["North", "Central", "South"],
- "gate": null
- },
- "urban_rural": {
- "question": "Is the dwelling urban or rural?",
- "class": "GIVEN",
- "values": ["Urban", "Rural"],
- "gate": null
- },
- "age_band": {
- "question": "How old are you?",
- "class": "GIVEN",
- "values": ["18-29", "30-44", "45-59", "60+"],
- "gate": null
- },
- "household_size": {
- "question": "How many people live in this household?",
- "class": "GIVEN",
- "values": ["1", "2-3", "4-5", "6 or more"],
- "gate": null
- },
- "household_has_children": {
- "question": "Are there children under 18 in your household?",
- "class": "GIVEN",
- "values": ["Yes", "No"],
- "gate": null
- },
- "has_mobile_phone": {
- "question": "Does anyone in the household own a mobile phone?",
- "class": "GIVEN",
- "values": ["Yes", "No"],
- "gate": null
- },
- "interviewer_notes": {
- "question": "Interviewer's free-text notes.",
- "class": "EXCLUDE",
- "values": null,
- "gate": null
- },
- "visited_clinic": {
- "question": "Have you visited a health clinic in the past 12 months?",
- "class": "PREDICT",
- "values": ["Yes", "No", "Prefer not to answer"],
- "gate": null
- },
- "clinic_wait": {
- "question": "How long did you wait to be seen?",
- "class": "PREDICT",
- "values": ["Under 30 minutes", "30 minutes to 2 hours", "Over 2 hours"],
- "gate": {
- "parent": "visited_clinic",
- "observed_if": ["Yes"]
- }
- },
- "would_return": {
- "question": "Would you go back to that clinic?",
- "class": "PREDICT",
- "values": ["Yes", "No", "Not sure"],
- "gate": {
- "parent": "clinic_wait",
- "observed_if": ["Under 30 minutes", "30 minutes to 2 hours", "Over 2 hours"]
- }
- },
- "trusts_health_advice": {
- "question": "How much do you trust health advice from your local clinic?",
- "class": "PREDICT",
- "values": ["Not at all", "A little", "Somewhat", "A lot"],
- "gate": null
- }
- },
- "split": {
- "n_train": 8000,
- "n_dev": 1900,
- "n_test": 2100
- }
-}
diff --git a/examples/simulacrabench_synthetic/source_snapshot/make_sandbox.py b/examples/simulacrabench_synthetic/source_snapshot/make_sandbox.py
deleted file mode 100644
index 8e2e98c..0000000
--- a/examples/simulacrabench_synthetic/source_snapshot/make_sandbox.py
+++ /dev/null
@@ -1,337 +0,0 @@
-"""Build a synthetic practice dataset from an instrument schema.
-
- python make_sandbox.py --schema data/unicef.json --out _sandbox
-
-Reads one of the schemas in data/ and writes a parquet file of invented
-respondents, as many rows as the delivered file has. score.py imports
-load_config, load_schema, options_for and make_sandbox from here, so the
-generator and the grader cannot disagree about option order.
-"""
-
-import argparse
-import json
-import os
-
-import numpy as np
-import pandas as pd
-import yaml
-
-# The one stream the sandbox draws from. Roles do not draw at all -- they are
-# counted off the schema -- so there is no second stream to keep separate from
-# the hidden trait that decides how a respondent answers.
-STREAM_DATA = 11
-
-# What a respondent is for, decided once when the dataset is built and never
-# recomputed. TRAIN is visible in both phases; DEV is scored in phase 1 and
-# becomes visible in phase 2; TEST is scored in phase 2 and is not shipped at
-# all before then, so those answers never enter a submission's container while
-# they are still the thing being predicted.
-ROLES = ("TRAIN", "DEV", "TEST")
-ROLE_COUNTS = ("n_train", "n_dev", "n_test")
-ROLE_COLUMN = "role"
-
-
-def load_config(path="config.yml"):
- """Read the organizer-side configuration and check the phases are sane."""
- with open(path, encoding="utf-8") as fh:
- config = yaml.safe_load(fh)
-
- for number, phase in config["phases"].items():
- # Explicit, never defaulted: whether a phase's score is noised decides
- # whether the leaderboard leaks, and it should not be silently on.
- if not isinstance(phase.get("noised"), bool):
- raise ValueError("phase %s: noised must be true or false" % number)
- if config["privacy"]["mechanism"] != "laplace":
- raise ValueError("unknown privacy mechanism %r"
- % config["privacy"]["mechanism"])
- return config
-
-
-def load_schema(path, config):
- """Read and validate schema.
-
- Option lists are taken as given. The delivered files are preprocessed so
- that every variable arrives categorical with a declared set of levels,
- which is why there is no numeric case here: a schema that enumerated what a
- continuous variable contained would be listing observed responses, and
- banding it is the preprocessing step's job rather than the grader's.
-
- Preprocessing also means no option is ever null. NaN in the frame has
- exactly one meaning -- this cell is held out, predict it -- and an item
- whose option list contained a missing value would make a blank cell
- ambiguous between "predict this" and "they did not answer". Genuine item
- non-response is a level like any other: "Prefer not to answer",
- "98. I don't know", "99. Refused to answer". The check below enforces it.
- """
- with open(path, encoding="utf-8") as fh:
- schema = json.load(fh)
-
- # One definition of the sentinel for all three instruments.
- schema["gated_value"] = config["gated_value"]
-
- items = schema["items"]
- for name, rec in items.items():
- if rec["class"] in ("GIVEN", "PREDICT") and not rec.get("values"):
- raise ValueError("%s is %s but has no values to draw from"
- % (name, rec["class"]))
- if any(v is None for v in rec.get("values") or ()):
- raise ValueError(
- "%s lists a null option. NaN means 'held out, predict this' "
- "and cannot also mean an answer: give non-response its own "
- "level instead." % name)
- stray = [v for v in rec.get("values") or () if not isinstance(v, str)]
- if stray:
- raise ValueError(
- "%s lists %r as a number. Options have to be strings: a gated "
- "column carries the sentinel alongside them, which no numeric "
- "column can hold, and a CSV round trip would bring them back "
- "as text and stop matching. Quote them." % (name, stray[:3]))
- if schema["gated_value"] in (rec.get("values") or ()):
- raise ValueError(
- "%s lists %r among its values. The sentinel is appended by "
- "options_for, never enumerated." % (name, schema["gated_value"]))
- gate = rec.get("gate")
- if gate and gate["parent"] not in items:
- raise ValueError("%s gates on %r, which is not in the schema"
- % (name, gate["parent"]))
-
- dataset = schema["dataset"]
- for key in ("n_rows", "version", "description"):
- if key not in dataset:
- raise ValueError("dataset is missing %r" % key)
- if not isinstance(dataset["n_rows"], int) or dataset["n_rows"] <= 0:
- raise ValueError("dataset.n_rows must be a positive integer, not %r"
- % (dataset["n_rows"],))
-
- # Counts, not shares. How many respondents carry each role is the thing
- # worth reading -- it says outright how much is visible in a phase and how
- # much is scored -- and a count cannot drift from n_rows through a rounding
- # step the way a share can. All three are written out, so the one thing
- # that can go wrong is that they stop agreeing with n_rows.
- split = schema["split"]
- missing = [key for key in ROLE_COUNTS if key not in split]
- if missing:
- raise ValueError("split is missing %s" % ", ".join(missing))
- for key in ROLE_COUNTS:
- count = split[key]
- if isinstance(count, bool) or not isinstance(count, int) or count < 0:
- raise ValueError("split.%s must be a count of respondents, a "
- "non-negative integer, not %r" % (key, count))
- total = sum(split[key] for key in ROLE_COUNTS)
- if total != dataset["n_rows"]:
- raise ValueError("%s sum to %d, but dataset.n_rows is %d: every "
- "respondent has exactly one role"
- % (" + ".join(ROLE_COUNTS), total, dataset["n_rows"]))
- for role, key in zip(ROLES[1:], ROLE_COUNTS[1:]):
- if split[key] < 1:
- raise ValueError("split.%s must be at least 1: a phase with no %s "
- "respondents has nothing to score" % (key, role))
-
- return schema
-
-
-def generated_items(schema):
- """Items the sandbox invents, in schema order.
-
- EXCLUDE items are identifiers, record keys, free text and administrative
- fields. They are never generated, never shown and never scored: the frame
- carries its own respondent_id, so a delivered key column is one more thing
- a submission could key on and nothing it could learn from.
- """
- return [name for name, rec in schema["items"].items()
- if rec["class"] in ("GIVEN", "PREDICT")]
-
-
-def options_for(schema, name):
- """The option list for an item, in the one order that counts.
-
- A gated item can legitimately be "never asked", so the gate sentinel is a
- real option for it rather than a missing value, and it goes last. This is
- the single definition of option order: the generator, the grader and your
- predict() all have to agree about it, because your probability vector is
- read in this order.
- """
- rec = schema["items"][name]
- options = list(rec["values"])
- if rec.get("gate"):
- options.append(schema["gated_value"])
- return options
-
-
-def scored_items(schema):
- """Items that can be held out and scored."""
- return [name for name, rec in schema["items"].items()
- if rec["class"] == "PREDICT"]
-
-
-def _generation_order(schema, items):
- """Items sorted so that every gate parent precedes its children."""
- records = schema["items"]
- remaining = list(items)
- placed, order = set(), []
- while remaining:
- ready = [name for name in remaining
- if not records[name].get("gate")
- or records[name]["gate"]["parent"] not in remaining]
- if not ready:
- raise ValueError("gate definitions form a cycle among: %s"
- % ", ".join(sorted(remaining)))
- for name in ready:
- order.append(name)
- placed.add(name)
- remaining = [name for name in remaining if name not in placed]
- return order
-
-
-# -------------------------------------------------------------- generation --
-
-def make_sandbox(schema, config, seed=0):
- """Invent the schema's worth of respondents, obeying its supports and skips.
-
- The row count comes from the schema rather than the caller. The sandbox is
- meant to be exactly the shape of the delivered file, and a size that can be
- passed in is a size that will eventually disagree with it.
- """
- settings = config["sandbox"]
- n = schema["dataset"]["n_rows"]
- rng = np.random.default_rng([seed, STREAM_DATA])
- items = generated_items(schema)
- if not items:
- raise ValueError("schema has no GIVEN or PREDICT items")
-
- # One hidden number per respondent, revealed by no column, nudging many
- # answers at once. Without it a respondent's answers would be independent
- # given their demographics, and the sandbox would be hostile to every
- # latent-factor method by construction.
- trait = rng.normal(size=n)
-
- columns = {}
- for name in _generation_order(schema, items):
- # Draw from the instrument's own support, not from options_for: the
- # gate sentinel is a legitimate answer for the grader to score, but it
- # is only ever produced by the gate below, never drawn at random.
- options = list(schema["items"][name]["values"])
- width = len(options)
-
- # An invented marginal for this item, skewed the way survey items are.
- base = np.log(rng.dirichlet(np.full(width, settings["dirichlet_alpha"]))
- + 1e-12)
- logits = np.tile(base, (n, 1))
-
- # Everyone's answers shift together with the hidden trait.
- logits += np.outer(trait, rng.normal(scale=settings["trait_scale"],
- size=width))
-
- weights = np.exp(logits - logits.max(axis=1, keepdims=True))
- weights /= weights.sum(axis=1, keepdims=True)
- chosen = (weights.cumsum(axis=1) > rng.random((n, 1))).argmax(axis=1)
- values = np.asarray(options, dtype=object)[chosen]
-
- # Skip logic. A respondent whose gate did not open was never asked, so
- # the true value of the cell is the sentinel, not a missing value. A
- # parent that is itself gated carries the sentinel, which is never in
- # observed_if, so chains close without any special handling.
- gate = schema["items"][name].get("gate")
- if gate:
- parent = np.asarray(columns[gate["parent"]], dtype=object)
- skipped = ~np.isin(parent, np.asarray(gate["observed_if"],
- dtype=object))
- values = np.where(skipped, schema["gated_value"], values)
-
- columns[name] = values
-
- frame = pd.DataFrame({name: columns[name] for name in items})
- frame.insert(0, "respondent_id", ["R%06d" % i for i in range(1, n + 1)])
- return frame.astype(object)
-
-
-def assign_roles(schema, frame):
- """Give every respondent one role, in the counts the schema declares.
-
- Nothing is sampled. The first `n_train` rows are TRAIN, the next `n_dev`
- are DEV and the rest are TEST, so the composition of the file is exactly
- what the schema says it is rather than what a draw happened to produce, and
- the counts printed here are the counts a participant reads in the schema.
-
- Decided once and written to disk, not something the grader recomputes on
- every run. Fixing it is what keeps the leaderboard paired: every submission
- is scored on the same cells, so the variability they share cancels in the
- differences between them, which is all a leaderboard reports.
-
- Whole respondents go one way: a scored respondent has every PREDICT answer
- withheld, and splitting cells instead would leave a gated child visible
- while its parent was hidden, which gives the parent away.
- """
- counts = [schema["split"][key] for key in ROLE_COUNTS]
- if sum(counts) != len(frame):
- raise ValueError("split assigns %d roles but the frame has %d rows"
- % (sum(counts), len(frame)))
- out = frame.copy()
- out[ROLE_COLUMN] = np.repeat(np.asarray(ROLES, dtype=object), counts)
- return out
-
-
-def write_sandbox(schema, config, out, seed=0):
- """Write respondents.parquet and schema.json into `out`.
-
- One file with a role column rather than one file per role, because the
- thing worth checking is a property of the whole set -- every respondent has
- exactly one role -- and that is checkable in a single file and merely
- conventional across several.
- """
- os.makedirs(out, exist_ok=True)
- frame = assign_roles(schema, make_sandbox(schema, config, seed=seed))
-
- path = os.path.join(out, "respondents.parquet")
- try:
- frame.to_parquet(path, index=False)
- except (ImportError, ValueError) as exc:
- path = os.path.join(out, "respondents.csv")
- frame.to_csv(path, index=False)
- print("parquet unavailable (%s); wrote CSV instead" % exc)
-
- # The schema is what predict() receives, verbatim. There is no thinned-down
- # view of it: every key in the file is already public, and a second copy of
- # the schema would be a second thing to keep in step with the first.
- schema_path = os.path.join(out, "schema.json")
- with open(schema_path, "w", encoding="utf-8") as fh:
- json.dump(schema, fh, indent=2, ensure_ascii=False)
- fh.write("\n")
- return path, schema_path
-
-
-def main():
- parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
- parser.add_argument("--schema", default="data/unicef.json",
- help="instrument schema to build from")
- parser.add_argument("--config", default="config.yml")
- parser.add_argument("--seed", type=int, default=0)
- parser.add_argument("--out", default="_sandbox",
- help="directory to write into (git-ignored)")
- args = parser.parse_args()
-
- config = load_config(args.config)
- schema = load_schema(args.schema, config)
- path, _ = write_sandbox(schema, config, args.out, seed=args.seed)
- items = generated_items(schema)
- frame = _read_any(path)
- counts = frame[ROLE_COLUMN].value_counts()
- print("%s -> %s" % (args.schema, args.out))
- print(" %d respondents, %d items (%d scored), %d gated"
- % (schema["dataset"]["n_rows"], len(items), len(scored_items(schema)),
- sum(1 for name in items if schema["items"][name].get("gate"))))
- print(" respondents.parquet")
- for role, held in (("TRAIN", "visible in both phases"),
- ("DEV", "scored in phase 1, visible in phase 2"),
- ("TEST", "scored in phase 2, not shipped before then")):
- print(" %-6s %6d rows %s" % (role, counts.get(role, 0), held))
- print(" schema.json what predict() receives")
-
-
-def _read_any(path):
- return (pd.read_csv(path, dtype=object) if path.endswith(".csv")
- else pd.read_parquet(path))
-
-
-if __name__ == "__main__":
- main()
diff --git a/examples/simulacrabench_synthetic/source_snapshot/score.py b/examples/simulacrabench_synthetic/source_snapshot/score.py
deleted file mode 100644
index 4c88c66..0000000
--- a/examples/simulacrabench_synthetic/source_snapshot/score.py
+++ /dev/null
@@ -1,734 +0,0 @@
-"""Run a submission the way the grader will, then score it.
-
- python score.py --submission baseline/marginal_counts \
- --data _sandbox --schema data/unicef.json --phase 1
-
-Reads a delivered dataset -- respondents.parquet, roles already assigned --
-takes this phase's rows, blanks what has to be predicted, installs the
-submission's requirements with the network up, cuts the network, calls
-predict(), and returns one noised number. See README.md.
-"""
-
-import argparse
-import json
-import os
-import shutil
-import subprocess
-import sys
-import tempfile
-import time
-
-import numpy as np
-import pandas as pd
-
-from make_sandbox import (ROLE_COLUMN, ROLE_COUNTS, ROLES, generated_items,
- load_config, load_schema, options_for, scored_items)
-
-# Runs inside the prepared environment. Reads what score.py staged, calls
-# predict once, writes the vectors back out. Nothing else.
-#
-# The network is removed in the first statements, before anything else is
-# imported, so no submission code and no package a submission pulls in has a
-# socket to use. This is done here rather than through sitecustomize because a
-# sitecustomize in the interpreter's own stdlib silently shadows one dropped
-# into a virtual environment, and a guard that quietly does not run is worse
-# than no guard at all.
-DRIVER = '''\
-import socket
-
-
-class NetworkAccessDenied(OSError):
- pass
-
-
-def _denied(*args, **kwargs):
- raise NetworkAccessDenied(
- "the submission tried to use the network; scoring runs offline")
-
-
-class _DeniedSocket(socket.socket):
- """Refuses to be opened.
-
- Stays a class rather than becoming a function because the standard library
- subclasses socket.socket -- ssl does `class SSLSocket(socket)` at import
- time -- and a function there raises a baffling TypeError instead of a
- message that tells the participant what they actually did wrong.
- """
-
- def __init__(self, *args, **kwargs):
- _denied()
-
-
-socket.socket = _DeniedSocket
-
-for _name in ("create_connection", "socketpair", "getaddrinfo",
- "gethostbyname", "gethostbyname_ex", "create_server"):
- if hasattr(socket, _name):
- setattr(socket, _name, _denied)
-
-import json
-import sys
-
-import pandas as pd
-
-work, submission = sys.argv[1], sys.argv[2]
-sys.path.insert(0, submission)
-
-with open(work + "/data.json", encoding="utf-8") as fh:
- payload = json.load(fh)
-frame = pd.DataFrame(payload["data"], columns=payload["columns"])
-
-with open(work + "/schema.json", encoding="utf-8") as fh:
- schema = json.load(fh)
-
-import main
-
-vectors = main.predict(frame, schema)
-
-with open(work + "/predictions.json", "w", encoding="utf-8") as fh:
- json.dump([[float(x) for x in vector] for vector in vectors], fh)
-'''
-
-
-# What the driver above needs to read the staged frame, and nothing else. It is
-# the floor of the container built by --docker, standing in for the much larger
-# set the hosted image pre-installs.
-DRIVER_PACKAGES = ("numpy", "pandas")
-
-
-class SubmissionError(Exception):
- """The submission broke a rule. Reported as FAIL, never scored."""
-
-
-# ------------------------------------------------------------------ ingest --
-
-def _read(path):
- if path.endswith(".csv"):
- return pd.read_csv(path, dtype=object).astype(object)
- return pd.read_parquet(path).astype(object)
-
-
-def load_frames(path, schema):
- """Read respondents.parquet and check it against the schema.
-
- A delivered dataset is one file carrying every respondent and the role that
- says what they are for. The roles are decided when the dataset is built,
- not here.
-
- The checks are the point. A column that has drifted from the schema, or a
- value nobody declared, would otherwise surface much later as an unscoreable
- cell, and by then the run has cost an hour of H100 time.
- """
- for extension in (".parquet", ".csv"):
- candidate = os.path.join(path, "respondents" + extension)
- if os.path.exists(candidate):
- frame = _read(candidate)
- break
- else:
- raise ValueError("%s holds no respondents.parquet or respondents.csv"
- % path)
-
- items = generated_items(schema)
- if "respondent_id" not in frame.columns:
- raise ValueError("%s has no respondent_id column" % path)
- if ROLE_COLUMN not in frame.columns:
- raise ValueError("%s has no %s column. A delivered dataset says what "
- "each respondent is for." % (path, ROLE_COLUMN))
- missing = [item for item in items if item not in frame.columns]
- if missing:
- raise ValueError("%s is missing %d column(s) the schema declares: %s"
- % (path, len(missing), ", ".join(missing[:5])))
-
- stray_roles = set(frame[ROLE_COLUMN].unique()) - set(ROLES)
- if stray_roles:
- raise ValueError("%s holds role(s) outside %s: %s"
- % (path, ", ".join(ROLES),
- ", ".join(sorted(map(str, stray_roles)))))
-
- # The schema declares each role as a count, so the count is checkable and
- # is checked: a file that ships a different number of DEV respondents than
- # the schema says was built from a different schema, and every number a
- # participant read off the split block is wrong. A role that is absent
- # altogether is not an error -- a phase-1 delivery ships no TEST rows at
- # all -- and which roles a phase actually needs is sample_rows's business.
- present = frame[ROLE_COLUMN].value_counts()
- for role, key in zip(ROLES, ROLE_COUNTS):
- found, declared = int(present.get(role, 0)), schema["split"][key]
- if found not in (0, declared):
- raise ValueError("%s holds %d %s respondents; the schema declares "
- "%s = %d" % (path, found, role, key, declared))
-
- for item in items:
- allowed = set(options_for(schema, item))
- column = frame[item]
- stray = set(column[column.notna()].unique()) - allowed
- if stray:
- raise ValueError(
- "%s: %s holds %d value(s) the schema does not list, e.g. %r"
- % (path, item, len(stray), sorted(stray, key=str)[:3]))
-
- if frame["respondent_id"].duplicated().any():
- raise ValueError("%s repeats a respondent_id" % path)
-
- # Schema order, not file order: everything downstream goes by position.
- return frame[["respondent_id", ROLE_COLUMN] + items].reset_index(drop=True)
-
-
-# ------------------------------------------------------------------ sample --
-
-PHASE_ROLES = {1: {"visible": ("TRAIN",), "hidden": "DEV"},
- 2: {"visible": ("TRAIN", "DEV"), "hidden": "TEST"}}
-
-
-def sample_rows(schema, respondents, phase):
- """Take this phase's rows and blank what the submission has to predict.
-
- Roles decide it, and they were decided when the dataset was built. Phase 1
- shows TRAIN complete and scores DEV; phase 2 shows TRAIN and DEV complete,
- answers included, which is where DEV is worth most, and scores TEST. So a
- phase-1 leaderboard probed all through development is not an answer key for
- phase 2, and TEST answers never enter a container while they are still the
- thing being predicted.
-
- Nothing is sampled here and nothing is thinned. A phase takes every row of
- every role it is entitled to, so every submission in a phase sees the same
- rows and is scored on exactly the same cells, which is what makes two
- leaderboard entries comparable to each other.
-
- Returns (frame, cells, truth). `frame` is what predict() receives: visible
- respondents complete, hidden respondents with every PREDICT cell NaN.
- `cells` is the canonical ordering -- rows top to bottom, and within a row,
- items in schema order.
- """
- items = generated_items(schema)
- scored = scored_items(schema)
- roles = PHASE_ROLES[phase]
-
- absent = [role for role in roles["visible"] + (roles["hidden"],)
- if not (respondents[ROLE_COLUMN] == role).any()]
- if absent:
- raise ValueError("phase %d needs %s respondents and the dataset holds "
- "none" % (phase, " and ".join(absent)))
-
- visible = respondents[respondents[ROLE_COLUMN].isin(roles["visible"])]
- hidden = respondents[respondents[ROLE_COLUMN] == roles["hidden"]]
-
- visible = visible[["respondent_id"] + items].reset_index(drop=True)
- hidden = hidden[["respondent_id"] + items].reset_index(drop=True)
-
- blanked = hidden.copy()
- blanked[scored] = np.nan
- frame = pd.concat([visible, blanked], ignore_index=True)
-
- offset = len(visible)
- cells, truth = [], []
- ids = hidden["respondent_id"].to_numpy(dtype=object)
- on = set(scored)
- for row in range(len(hidden)):
- for item in items:
- if item in on:
- cells.append((offset + row, ids[row], item))
- truth.append(hidden[item].iloc[row])
- return frame, cells, truth
-
-
-# ------------------------------------------------------------------ privacy --
-
-def privatize(value, config, n_respondents, phase, uniform_reference,
- rng=None):
- """The one number that leaves the grader.
-
- Whether it is noised is a property of the phase. Phase 1 scores the DEV
- respondents, the same ones on every submission across a whole development
- period, so the leaderboard is a query channel and the answer has to be
- noised. Phase 2 scores TEST, once per team, against respondents phase 1
- never touched -- there is no sequence to difference, so the score is exact.
-
- That the scored set is fixed within a phase is what makes noising the right
- defence rather than a workaround: the repetition is the whole exposure, and
- it is bounded and accountable. Redrawing who is scored per submission would
- spread the exposure over every respondent instead, and buy no amplification
- in return, because the frame says outright which rows are held out.
-
- When noise does apply: every held-out respondent contributes exactly one
- cell per PREDICT item, so the log score is a plain mean over respondents
- and dropping one of them moves it by at most the per-cell bound over the
- respondent count. That bound exists only because `floored` puts every
- probability at or above scoring.floor -- without the floor a single
- confident miss is unbounded, and so is the sensitivity. Skill divides the
- log score by the schema's uniform reference, so its sensitivity divides by
- the same constant.
-
- The draw is deliberately not seeded from --seed. A participant who could
- reproduce the noise could subtract it, and the mechanism would be theatre.
- """
- settings = config["phases"][phase]
- round_to = settings.get("round_to")
-
- if not settings.get("noised", True):
- reported = value if not round_to else round(
- round(value / round_to) * round_to, 10)
- return float(reported), {"noised": False, "round_to": round_to}
-
- privacy = config["privacy"]
- sensitivity = (-np.log(config["scoring"]["floor"])
- / (n_respondents * uniform_reference))
- scale = sensitivity / privacy["epsilon"]
-
- rng = np.random.default_rng() if rng is None else rng
- noised = float(value + rng.laplace(0.0, scale))
- if round_to:
- noised = round(round(noised / round_to) * round_to, 10)
- return noised, {"noised": True, "sensitivity": sensitivity, "scale": scale,
- "epsilon": privacy["epsilon"], "round_to": round_to}
-
-
-# ------------------------------------------------------------- environment --
-
-def _declares_anything(requirements):
- """Does this requirements.txt actually ask for a package?
-
- A file holding only comments is the same as no file: it would otherwise
- buy an empty venv, and a submission that imports pandas -- which the hosted
- image has -- would fail here for a reason the worker does not have.
- """
- if not os.path.exists(requirements):
- return False
- with open(requirements, encoding="utf-8") as fh:
- return any(line.strip() and not line.strip().startswith("#")
- for line in fh)
-
-
-def prepare_environment(submission, workdir, python):
- """Create the venv, install requirements with the network up, then cut it.
-
- requirements.txt is optional, as it is on the worker: the hosted image
- already carries numpy, pandas, torch, transformers and the rest, and the
- file exists only to add what the image does not have. A submission that
- declares nothing therefore runs in the environment score.py itself is
- running in, which is what stands in for that image here -- a fresh venv
- would not even hold pandas, and the local harness would fail submissions
- the worker runs happily. A submission that does declare something gets the
- isolated venv, where an undeclared import is the ImportError it would be
- on the worker. `--docker` is the strict path either way.
-
- Returns the interpreter to run the driver with.
- """
- requirements = os.path.join(submission, "requirements.txt")
- if not _declares_anything(requirements):
- print("[install] nothing declared; running in this environment, "
- "which stands in for the hosted image")
- return python
-
- venv = os.path.join(workdir, "venv")
- binary = os.path.join(venv, "Scripts" if os.name == "nt" else "bin",
- "python.exe" if os.name == "nt" else "python")
-
- subprocess.run([python, "-m", "venv", venv], check=True, capture_output=True)
- print("[install] %s, network up" % requirements)
- done = subprocess.run([binary, "-m", "pip", "install", "--quiet",
- "-r", requirements],
- capture_output=True, text=True)
- if done.returncode != 0:
- raise SubmissionError(
- "requirements.txt did not install:\n" + done.stderr.strip())
- return binary
-
-
-def stage(schema, masked, workdir):
- """Write what the driver hands to predict().
-
- The schema goes across verbatim: predict() receives the same file that is in
- data/, so there is no second view of the schema to keep in step with the
- first. JSON rather than parquet for the frame, so the submission's
- environment needs nothing beyond what it declared and values arrive as the
- exact objects the schema lists rather than whatever a CSV round trip infers.
- """
- items = generated_items(schema)
- columns = ["respondent_id"] + items
- rows = [[None if pd.isna(value) else value for value in row]
- for row in masked[columns].to_numpy(dtype=object)]
- with open(os.path.join(workdir, "data.json"), "w", encoding="utf-8") as fh:
- json.dump({"columns": columns, "data": rows}, fh)
- with open(os.path.join(workdir, "schema.json"), "w", encoding="utf-8") as fh:
- json.dump(schema, fh, ensure_ascii=False)
-
-
-def run_submission(binary, submission, workdir, timeout, image, runner,
- docker=False, verbose=True):
- """Call predict() with the network off. Returns the raw vectors.
-
- `verbose` is the phase's logging level. A crash in phase 1 comes back with
- its whole traceback, because development is when a participant has to be
- able to fix things. In phase 2 they get the exception line and nothing
- else: the traceback of a run over the full data can carry values out of it.
- The full text goes to the run log either way.
- """
- driver = os.path.join(workdir, "driver.py")
- with open(driver, "w", encoding="utf-8") as fh:
- fh.write(DRIVER)
-
- if docker:
- command = _docker_command(submission, workdir, image, runner)
- print("[run] docker --network=none, %dg, %d cpus, network off"
- % (runner["memory_gb"], runner["cpus"]))
- else:
- command = [binary, driver, workdir, os.path.abspath(submission)]
- print("[run] sockets disabled in-process, network off")
-
- environment = dict(os.environ)
- environment.update({"HF_HUB_OFFLINE": "1", "TRANSFORMERS_OFFLINE": "1",
- "HF_HUB_DISABLE_TELEMETRY": "1", "WANDB_MODE": "disabled",
- "PYTHONDONTWRITEBYTECODE": "1"})
-
- try:
- done = subprocess.run(command, capture_output=True, text=True,
- timeout=timeout, env=environment)
- except subprocess.TimeoutExpired:
- raise SubmissionError("predict() did not finish within %ds" % timeout)
-
- if done.returncode != 0:
- # Lead with the exception itself. Python puts it on the last line, and
- # a participant reading a wall of traceback should not have to hunt.
- trace = done.stderr.strip()
- reason = trace.splitlines()[-1] if trace else "no output"
- error = SubmissionError("predict() failed: %s%s"
- % (reason, "\n\n" + trace if verbose else ""))
- error.detail = trace
- raise error
-
- output = os.path.join(workdir, "predictions.json")
- if not os.path.exists(output):
- raise SubmissionError("predict() returned nothing the driver could write")
- with open(output, encoding="utf-8") as fh:
- return json.load(fh)
-
-
-def _docker_command(submission, workdir, image, runner):
- """The container, capped at the worker's own limits.
-
- Memory and CPU come from `runner` in config.yml rather than being written
- here, so the local rehearsal is bounded the way the worker is: a submission
- that fits locally fits there, and one that does not is killed here, where
- the participant can see why.
- """
- if shutil.which("docker") is None:
- raise SubmissionError("--docker was requested but docker is not installed")
- context = os.path.join(workdir, "image")
- os.makedirs(context, exist_ok=True)
- # The driver itself reads the frame with pandas, so the base image needs it
- # whether or not the submission declares anything. That is the floor, and
- # nothing above it is implied: the hosted image ships far more, and a
- # submission that leans on the rest of it has to say so in requirements.txt
- # to see it here.
- lines = ["FROM %s" % image,
- "RUN pip install --no-cache-dir %s" % " ".join(DRIVER_PACKAGES)]
- requirements = os.path.join(submission, "requirements.txt")
- if os.path.exists(requirements):
- shutil.copy(requirements, context)
- lines += ["COPY requirements.txt .",
- "RUN pip install --no-cache-dir -r requirements.txt"]
- with open(os.path.join(context, "Dockerfile"), "w", encoding="utf-8") as fh:
- fh.write("\n".join(lines) + "\n")
- # The build is the install phase: it is the only step with a network.
- print("[install] docker build, network up")
- subprocess.run(["docker", "build", "--quiet", "-t", "sbench-submission",
- context], check=True, capture_output=True)
- return ["docker", "run", "--rm", "--network=none", "--read-only",
- "--memory=%dg" % runner["memory_gb"],
- "--cpus=%d" % runner["cpus"], "--pids-limit=256",
- "--tmpfs", "/tmp",
- "-v", "%s:/work" % os.path.abspath(workdir),
- "-v", "%s:/submission:ro" % os.path.abspath(submission),
- "sbench-submission", "python", "/work/driver.py", "/work",
- "/submission"]
-
-
-# ---------------------------------------------------------------- scoring --
-
-def check(schema, vectors, cells):
- """Every rule the grader enforces. Raises on the first thing that is wrong."""
- if len(vectors) != len(cells):
- raise SubmissionError(
- "predict() returned %d vectors for %d hidden cells. Check the "
- "canonical order: rows top to bottom, items in schema key order."
- % (len(vectors), len(cells)))
-
- for index, (vector, (_, _, item)) in enumerate(zip(vectors, cells)):
- width = len(options_for(schema, item))
- p = np.asarray(vector, dtype=float)
- if p.shape != (width,):
- raise SubmissionError(
- "vector %d is for %s and should have %d entries, not %d"
- % (index, item, width, p.size))
- if not np.isfinite(p).all():
- raise SubmissionError("vector %d for %s contains NaN or infinity"
- % (index, item))
- if (p < 0).any():
- raise SubmissionError("vector %d for %s contains a negative value"
- % (index, item))
- if p.sum() <= 0:
- raise SubmissionError("vector %d for %s sums to zero" % (index, item))
-
-
-def floored(vectors, schema, cells, floor):
- """Renormalise, then mix with a flat vector so nothing is ever zero.
-
- Mixing rather than clipping keeps both promises at once: every entry is at
- least `floor` and the vector still sums to 1. Clip-then-renormalise
- pushes the clipped entries back under the floor and keeps neither.
- """
- out = []
- for vector, (_, _, item) in zip(vectors, cells):
- p = np.asarray(vector, dtype=float)
- p = p / p.sum()
- width = p.size
- out.append(floor + (1.0 - width * floor) * p)
- return out
-
-
-def uniform_reference(schema):
- """Mean surprisal of a uniform guess, in nats, from the schema alone.
-
- log K averaged over the scored items, where K counts an item's options with
- the gate sentinel included. No data is involved: this is a property of the
- instrument, fixed before anybody submits anything, and it is what makes the
- three instruments comparable. ERPIS asks 213 mostly-binary questions and
- the Skills Assessment asks 73 much wider ones, so a nat is not worth the
- same in each.
- """
- scored = scored_items(schema)
- if not scored:
- raise ValueError("schema has no PREDICT items")
- return float(np.mean([np.log(len(options_for(schema, item)))
- for item in scored]))
-
-
-def score(schema, config, vectors, truth, cells, seed=0):
- """Mean log score and the skill it normalises to.
-
- The metric is the log probability the submission gave the answer that was
- actually there, averaged over blank cells. It is at most 0 and at least
- log(floor); higher is better.
-
- `skill` puts that on a scale the three instruments share: 0 is a uniform
- guess, 1 is perfect, negative is worse than guessing. Both terms come from
- the log score and the schema, so nothing about it can be tuned.
- """
- logp, briers, items = [], [], []
-
- for vector, actual, (_, _, item) in zip(vectors, truth, cells):
- options = options_for(schema, item)
- if actual not in options:
- raise ValueError("%s holds %r, which is not one of its options"
- % (item, actual))
- k = options.index(actual)
- logp.append(np.log(vector[k]))
- target = np.zeros(len(options))
- target[k] = 1.0
- briers.append(float(((vector - target) ** 2).sum()))
- items.append(item)
-
- if not logp:
- raise ValueError("nothing was held out, so there is nothing to score")
-
- logp = np.asarray(logp)
- uniform = uniform_reference(schema)
- by_item = (pd.DataFrame({"item": items, "log_score": logp})
- .groupby("item")["log_score"].agg(["mean", "size"]))
-
- return {
- "log_score": float(logp.mean()),
- "uniform_reference": uniform,
- "skill": 1.0 + float(logp.mean()) / uniform,
- "std_error": cluster_std_error(
- logp, [cell[1] for cell in cells],
- draws=config["scoring"]["bootstrap_draws"], seed=seed),
- "item_normalized": float(by_item["mean"].mean()),
- "brier": float(np.mean(briers)),
- "n_cells": int(logp.size),
- "by_item": by_item,
- }
-
-
-def cluster_std_error(values, respondents, draws=2000, seed=0):
- """Standard error that respects clustering of cells within respondents.
-
- One respondent contributes many cells and their scores move together.
- Treating cells as independent understates the uncertainty, sometimes by a
- factor of two. Resample whole respondents instead.
- """
- values = np.asarray(values, dtype=float)
- _, index = np.unique(np.asarray(respondents, dtype=object),
- return_inverse=True)
- totals = np.bincount(index, weights=values)
- counts = np.bincount(index).astype(float)
- rng = np.random.default_rng(seed)
- picks = rng.integers(0, totals.size, size=(draws, totals.size))
- means = totals[picks].sum(axis=1) / counts[picks].sum(axis=1)
- return float(means.std(ddof=1))
-
-
-def baselines(schema, truth, cells, floor):
- """What a model has to beat, as log scores: uniform, and the crowd marginal.
-
- Both are higher-is-better, on the same scale as what a submission gets.
- """
- uniform, counts = [], {}
- for actual, (_, _, item) in zip(truth, cells):
- counts.setdefault(item, {}).setdefault(actual, 0)
- counts[item][actual] += 1
- uniform.append(-np.log(len(options_for(schema, item))))
-
- crowd = []
- for actual, (_, _, item) in zip(truth, cells):
- table = counts[item]
- total = sum(table.values())
- crowd.append(np.log(max(table[actual] / total, floor)))
- return float(np.mean(uniform)), float(np.mean(crowd))
-
-
-# -------------------------------------------------------------------- main --
-
-def main():
- parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
- parser.add_argument("--submission", default="baseline/marginal_counts",
- help="directory holding main.py and requirements.txt")
- parser.add_argument("--data", required=True,
- help="the delivered parquet, or a directory holding "
- "data.parquet (what make_sandbox.py writes)")
- parser.add_argument("--schema", default="data/unicef.json")
- parser.add_argument("--config", default="config.yml",
- help="organizer-side configuration")
- parser.add_argument("--phase", type=int, choices=[1, 2], default=1,
- help="competition phase; sets which roles are visible "
- "and which are scored, the timeout, the "
- "rounding and the logging level")
- parser.add_argument("--seed", type=int, default=0,
- help="seeds the clustered bootstrap behind std_error. "
- "Who is held out does not depend on it: roles are "
- "in the delivered file, not drawn here")
- parser.add_argument("--timeout", type=int, default=None,
- help="override the phase's timeout, in seconds")
- parser.add_argument("--python", default=sys.executable,
- help="interpreter to build the submission's venv from")
- parser.add_argument("--docker", action="store_true",
- help="enforce the network cut with a container")
- parser.add_argument("--keep", action="store_true",
- help="keep the work directory for inspection")
- parser.add_argument("--log", default=None,
- help="write the organizer-side log dict here as JSON")
- parser.add_argument("--show-log", action="store_true",
- help="print the organizer-side log. Never available to "
- "a participant; this is the local rehearsal only")
- args = parser.parse_args()
-
- started = time.time()
- config = load_config(args.config)
- settings = config["phases"][args.phase]
- timeout = args.timeout if args.timeout is not None \
- else settings["timeout_seconds"]
- verbose = settings["logging"] == "verbose"
-
- schema = load_schema(args.schema, config)
- # No row-count warning here: load_frames has already checked every role
- # against the count the schema declares for it, which says the same thing
- # and says which role is wrong.
- respondents = load_frames(args.data, schema)
- masked, cells, truth = sample_rows(schema, respondents, args.phase)
- # Held-out respondents, whatever role they carry: DEV in phase 1, TEST
- # in phase 2. Not to be read as "the TEST rows".
- n_held_out = len({cell[1] for cell in cells})
- print("[phase] %d (%s): %s visible, %s scored, %ds for predict()"
- % (args.phase, settings["name"],
- "+".join(PHASE_ROLES[args.phase]["visible"]),
- PHASE_ROLES[args.phase]["hidden"], timeout))
- print("[data] %s against %s: %d respondents, %d held out, %d cells"
- % (args.data, args.schema, len(masked), n_held_out, len(cells)))
-
- # Everything the grader learns. Written to the run log, never returned:
- # only `score` below goes back to the participant.
- logs = {"data": args.data, "schema": args.schema, "phase": args.phase,
- "seed": args.seed,
- "n_respondents": len(masked), "n_held_out": n_held_out,
- "n_cells": len(cells)}
-
- workdir = tempfile.mkdtemp(prefix="sbench-")
- try:
- stage(schema, masked, workdir)
- # In docker mode the image build is the install stage and the container
- # is the isolation, so there is no venv to make.
- binary = (None if args.docker
- else prepare_environment(args.submission, workdir,
- args.python))
- vectors = run_submission(binary, args.submission, workdir, timeout,
- config["scoring"]["docker_image"],
- config["runner"],
- docker=args.docker, verbose=verbose)
- check(schema, vectors, cells)
- vectors = floored(vectors, schema, cells,
- config["scoring"]["floor"])
- result = score(schema, config, vectors, truth, cells, seed=args.seed)
- except SubmissionError as exc:
- logs["status"] = "FAIL"
- logs["error"] = getattr(exc, "detail", str(exc))
- write_log(logs, args, verbose)
- print("\nFAIL %s" % exc)
- return 1
- finally:
- if args.keep:
- print("[work] %s" % workdir)
- else:
- shutil.rmtree(workdir, ignore_errors=True)
-
- uniform, crowd = baselines(schema, truth, cells,
- config["scoring"]["floor"])
- reported, mechanism = privatize(result["skill"], config, n_held_out,
- args.phase, result["uniform_reference"])
- elapsed = time.time() - started
-
- logs.update({
- "status": "PASS",
- "skill": result["skill"],
- "reported_skill": reported,
- "log_score": result["log_score"],
- "uniform_reference": result["uniform_reference"],
- "privacy": mechanism,
- "std_error": result["std_error"],
- "item_normalized": result["item_normalized"],
- "brier": result["brier"],
- "baseline_uniform": uniform,
- "baseline_crowd": crowd,
- "seconds": elapsed,
- "by_item": {item: {"log_score": float(row["mean"]),
- "n_cells": int(row["size"])}
- for item, row in result["by_item"].iterrows()},
- })
- write_log(logs, args, verbose)
-
- # The whole of what a participant gets back.
- print("\nPASS %.4f (%.1fs)" % (reported, elapsed))
- return 0
-
-
-def write_log(logs, args, verbose):
- """Put the organizer-side quantities somewhere they are kept, not returned.
-
- `verbose` drops the per-item breakdown in phase 2. It is the most useful
- thing in here and the most re-identifying: a per-item loss over a small
- held-out set is close to a query about particular people.
- """
- if not verbose:
- logs.pop("by_item", None)
- if args.log:
- with open(args.log, "w", encoding="utf-8") as fh:
- json.dump(logs, fh, indent=2, ensure_ascii=False, default=str)
- fh.write("\n")
- print("[log] %s" % args.log)
- if args.show_log:
- print("\n--- organizer-side log, not returned to the participant ---")
- print(json.dumps(logs, indent=2, ensure_ascii=False, default=str))
-
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/examples/simulacrabench_synthetic/source_snapshot/tools/check_submission_zip.py b/examples/simulacrabench_synthetic/source_snapshot/tools/check_submission_zip.py
deleted file mode 100644
index 0f5be6d..0000000
--- a/examples/simulacrabench_synthetic/source_snapshot/tools/check_submission_zip.py
+++ /dev/null
@@ -1,681 +0,0 @@
-#!/usr/bin/env python3
-"""Validate a SimulacraBench submission ZIP."""
-
-from __future__ import annotations
-
-import argparse
-import ast
-import importlib.util
-import math
-import numbers
-import os
-import re
-import sys
-import tempfile
-import zipfile
-from pathlib import Path
-
-
-# Keep in sync with orchestrator/codabench_shared/submissions/static_checks.py.
-SENTENCE_TRANSFORMER_BASIC_MODEL_IDS = {
- "albert-base-v1",
- "albert-base-v2",
- "albert-large-v1",
- "albert-large-v2",
- "albert-xlarge-v1",
- "albert-xlarge-v2",
- "albert-xxlarge-v1",
- "albert-xxlarge-v2",
- "bert-base-cased-finetuned-mrpc",
- "bert-base-cased",
- "bert-base-chinese",
- "bert-base-german-cased",
- "bert-base-german-dbmdz-cased",
- "bert-base-german-dbmdz-uncased",
- "bert-base-multilingual-cased",
- "bert-base-multilingual-uncased",
- "bert-base-uncased",
- "bert-large-cased-whole-word-masking-finetuned-squad",
- "bert-large-cased-whole-word-masking",
- "bert-large-cased",
- "bert-large-uncased-whole-word-masking-finetuned-squad",
- "bert-large-uncased-whole-word-masking",
- "bert-large-uncased",
- "camembert-base",
- "ctrl",
- "distilbert-base-cased-distilled-squad",
- "distilbert-base-cased",
- "distilbert-base-german-cased",
- "distilbert-base-multilingual-cased",
- "distilbert-base-uncased-distilled-squad",
- "distilbert-base-uncased-finetuned-sst-2-english",
- "distilbert-base-uncased",
- "distilgpt2",
- "distilroberta-base",
- "gpt2-large",
- "gpt2-medium",
- "gpt2-xl",
- "gpt2",
- "openai-gpt",
- "roberta-base-openai-detector",
- "roberta-base",
- "roberta-large-mnli",
- "roberta-large-openai-detector",
- "roberta-large",
- "t5-11b",
- "t5-3b",
- "t5-base",
- "t5-large",
- "t5-small",
- "transfo-xl-wt103",
- "xlm-clm-ende-1024",
- "xlm-clm-enfr-1024",
- "xlm-mlm-100-1280",
- "xlm-mlm-17-1280",
- "xlm-mlm-en-2048",
- "xlm-mlm-ende-1024",
- "xlm-mlm-enfr-1024",
- "xlm-mlm-enro-1024",
- "xlm-mlm-tlm-xnli15-1024",
- "xlm-mlm-xnli15-1024",
- "xlm-roberta-base",
- "xlm-roberta-large-finetuned-conll02-dutch",
- "xlm-roberta-large-finetuned-conll02-spanish",
- "xlm-roberta-large-finetuned-conll03-english",
- "xlm-roberta-large-finetuned-conll03-german",
- "xlm-roberta-large",
- "xlnet-base-cased",
- "xlnet-large-cased",
-}
-SAFE_ARTIFACT_SUFFIXES = {
- ".bin",
- ".ckpt",
- ".csv",
- ".joblib",
- ".json",
- ".model",
- ".npy",
- ".npz",
- ".parquet",
- ".pickle",
- ".pkl",
- ".pt",
- ".pth",
- ".safetensors",
- ".txt",
- ".ubj",
- ".yaml",
- ".yml",
-}
-LOAD_CALL_SUFFIXES = (
- "open",
- ".open",
- ".read_csv",
- ".read_parquet",
- ".read_pickle",
- ".read_json",
- ".read_excel",
- ".load",
- ".loadtxt",
- ".genfromtxt",
- ".load_model",
- ".read_text",
- ".read_bytes",
- ".with_name",
-)
-# A one-instrument frame in the shape predict() receives in phase 1: two
-# visible TRAIN respondents to fit on, one held-out DEV respondent with its
-# scored cells blank. The one TEST respondent the split declares is not shipped
-# in phase 1, which is why the dataset has four rows and the frame has three.
-# The gated item exercises the sentinel slot, which is a real answer and goes
-# last.
-SMOKE_SCHEMA = {
- "dataset": {"n_rows": 4, "version": "1.0", "description": "local smoke check"},
- "items": {
- "region": {"question": "Which region?", "class": "GIVEN",
- "values": ["North", "South"], "gate": None},
- "visited_clinic": {"question": "Did you visit a clinic?", "class": "PREDICT",
- "values": ["Yes", "No", "Prefer not to answer"], "gate": None},
- "clinic_wait": {"question": "How long did you wait?", "class": "PREDICT",
- "values": ["Under 30 minutes", "Over 30 minutes"],
- "gate": {"parent": "visited_clinic", "observed_if": ["Yes"]}},
- },
- "split": {"n_train": 2, "n_dev": 1, "n_test": 1},
- "gated_value": "NA_GATED",
-}
-
-# Canonical order: rows top to bottom, items in schema key order. Only the
-# held-out respondent's PREDICT cells are blank, so there are two of them.
-SMOKE_WIDTHS = [3, 3] # visited_clinic, clinic_wait (+1 for NA_GATED)
-
-
-def main() -> int:
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument("submission_zip", type=Path)
- args = parser.parse_args()
-
- try:
- validate_submission_zip(args.submission_zip)
- except (RuntimeError, ValueError) as exc:
- print(f"ERROR: {exc}", file=sys.stderr)
- return 1
-
- print(f"OK: {args.submission_zip} looks like a valid submission ZIP.")
- return 0
-
-
-def validate_submission_zip(zip_path: Path) -> None:
- if not zip_path.exists():
- raise ValueError(f"ZIP not found: {zip_path}")
- if not zipfile.is_zipfile(zip_path):
- raise ValueError(f"Not a valid ZIP file: {zip_path}")
-
- with zipfile.ZipFile(zip_path) as zf:
- names = [name for name in zf.namelist() if not name.endswith("/")]
- normalized = {name.replace("\\", "/") for name in names}
- _reject_unsafe_members(normalized)
-
- if any(name.lower().endswith(".zip") for name in normalized):
- raise ValueError("Do not upload a ZIP that contains another ZIP. Upload the submission files directly.")
-
- if "main.py" not in normalized:
- nested_model = sorted(
- name for name in normalized
- if name.endswith("/main.py")
- )
- if nested_model:
- raise ValueError(
- "main.py is nested inside a folder. Zip the contents of your submission directory, "
- "not the directory itself."
- )
- raise ValueError("main.py must be at the ZIP root.")
-
- with tempfile.TemporaryDirectory(prefix="submission-check-") as tmpdir:
- zf.extractall(tmpdir)
- submission_dir = Path(tmpdir)
- _check_requirements(submission_dir)
- _check_missing_local_artifacts(submission_dir)
- _check_model(submission_dir)
-
-
-def _reject_unsafe_members(names: set[str]) -> None:
- for name in names:
- path = Path(name)
- if path.is_absolute() or ".." in path.parts:
- raise ValueError("ZIP contains an unsafe file path. Recreate it from the submission directory contents.")
-
-
-def _check_missing_local_artifacts(submission_dir: Path) -> None:
- for source_path in _runtime_python_files(submission_dir):
- try:
- source = source_path.read_text(errors="replace")
- tree = ast.parse(source, filename=source_path.name)
- except (OSError, SyntaxError):
- continue
- constants = _module_string_constants(tree)
- for node in ast.walk(tree):
- if not isinstance(node, ast.Call):
- continue
- call_name = _call_name(node.func) or ""
- if not _call_may_load_local_file(call_name):
- continue
- if _call_is_write_open(call_name, node, constants):
- continue
- rel_source_path = source_path.relative_to(submission_dir)
- for value in _literal_call_strings(node, constants, rel_source_path):
- missing = _missing_artifact_path(submission_dir, value)
- if missing:
- rel_source = rel_source_path.as_posix()
- artifact_name = Path(missing).name
- raise ValueError(
- f"{rel_source}:{node.lineno} references bundled file {artifact_name!r}, "
- "but it is not in the ZIP. Add the file or update the relative path."
- )
-
-
-def _runtime_python_files(submission_dir: Path) -> list[Path]:
- parsed: dict[Path, ast.Module] = {}
- module_to_path: dict[str, Path] = {}
- for path in sorted(submission_dir.rglob("*.py")):
- if "__pycache__" in path.parts:
- continue
- try:
- tree = ast.parse(path.read_text(errors="replace"), filename=path.name)
- except (OSError, SyntaxError):
- continue
- parsed[path] = tree
- relpath = path.relative_to(submission_dir).with_suffix("")
- parts = list(relpath.parts)
- if parts and parts[-1] == "__init__":
- parts = parts[:-1]
- if parts:
- module_to_path[".".join(parts)] = path
-
- entrypoints = [submission_dir / "main.py"]
- selected: list[Path] = []
- stack = [path for path in entrypoints if path in parsed]
- while stack:
- path = stack.pop()
- if path in selected:
- continue
- selected.append(path)
- tree = parsed[path]
- relpath = path.relative_to(submission_dir)
- for module_name in _local_import_candidates(tree, relpath):
- imported = module_to_path.get(module_name)
- if imported and imported not in selected:
- stack.append(imported)
- return selected or [path for path in entrypoints if path.exists()]
-
-
-def _local_import_candidates(tree: ast.Module, relpath: Path) -> set[str]:
- visitor = _LocalImportCandidateVisitor(relpath)
- visitor.visit(tree)
- return visitor.candidates
-
-
-class _LocalImportCandidateVisitor(ast.NodeVisitor):
- def __init__(self, relpath: Path):
- self.candidates: set[str] = set()
- current_module = ".".join(relpath.with_suffix("").parts)
- if current_module.endswith(".__init__"):
- self.current_package = current_module.rsplit(".", 1)[0]
- else:
- self.current_package = current_module.rsplit(".", 1)[0] if "." in current_module else ""
-
- def visit_If(self, node: ast.If) -> None:
- if _is_main_guard(node.test):
- for child in node.orelse:
- self.visit(child)
- return
- self.generic_visit(node)
-
- def visit_Import(self, node: ast.Import) -> None:
- for alias in node.names:
- self.candidates.add(alias.name)
- self.candidates.add(alias.name.split(".", 1)[0])
-
- def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
- if node.level:
- base = self.current_package.split(".") if self.current_package else []
- if node.level > len(base) + 1:
- return
- prefix_parts = base[:len(base) - node.level + 1]
- if node.module:
- prefix_parts.extend(node.module.split("."))
- prefix = ".".join(part for part in prefix_parts if part)
- else:
- prefix = node.module or ""
- if prefix:
- self.candidates.add(prefix)
- for alias in node.names:
- if prefix:
- self.candidates.add(f"{prefix}.{alias.name}")
- elif alias.name != "*":
- self.candidates.add(alias.name)
-
-
-def _module_string_constants(tree: ast.Module) -> dict[str, str]:
- constants: dict[str, str] = {}
- for node in tree.body:
- if isinstance(node, ast.Assign) and len(node.targets) == 1:
- target = node.targets[0]
- value = node.value
- elif isinstance(node, ast.AnnAssign):
- target = node.target
- value = node.value
- else:
- continue
- if isinstance(target, ast.Name) and isinstance(value, ast.Constant) and isinstance(value.value, str):
- constants[target.id] = value.value
- elif isinstance(target, ast.Name):
- constants.pop(target.id, None)
- return constants
-
-
-def _call_name(node: ast.AST) -> str | None:
- parts: list[str] = []
- current = node
- while isinstance(current, ast.Attribute):
- parts.append(current.attr)
- current = current.value
- if isinstance(current, ast.Name):
- parts.append(current.id)
- return ".".join(reversed(parts))
- if parts:
- return ".".join(reversed(parts))
- return None
-
-
-def _call_may_load_local_file(call_name: str) -> bool:
- if call_name in {"open", "load_model", "read_text", "read_bytes"}:
- return True
- return any(call_name.endswith(suffix) for suffix in LOAD_CALL_SUFFIXES)
-
-
-def _call_is_write_open(call_name: str, node: ast.Call, constants: dict[str, str]) -> bool:
- if not (call_name == "open" or call_name.endswith(".open")):
- return False
- mode = ""
- if len(node.args) >= 2:
- mode = _string_literal(node.args[1], constants) or ""
- for keyword in node.keywords:
- if keyword.arg == "mode":
- mode = _string_literal(keyword.value, constants) or mode
- return any(flag in mode for flag in ("w", "a", "x", "+"))
-
-
-def _literal_call_strings(
- node: ast.Call,
- constants: dict[str, str],
- source_path: Path | None = None,
-) -> list[str]:
- values: list[str] = []
- for arg in node.args[:2]:
- literal = _path_literal(arg, constants, source_path)
- if literal is not None:
- values.append(literal)
- for keyword in node.keywords:
- if keyword.arg in {"path", "filepath", "filename", "file", "fname"}:
- literal = _path_literal(keyword.value, constants, source_path)
- if literal is not None:
- values.append(literal)
- if isinstance(node.func, ast.Attribute):
- literal = _path_literal(node.func.value, constants, source_path)
- if literal is not None:
- values.append(literal)
- return values
-
-
-def _string_literal(node: ast.AST, constants: dict[str, str]) -> str | None:
- if isinstance(node, ast.Constant) and isinstance(node.value, str):
- return node.value or None
- if isinstance(node, ast.Name):
- return constants.get(node.id) or None
- return None
-
-
-def _path_literal(
- node: ast.AST,
- constants: dict[str, str],
- source_path: Path | None = None,
-) -> str | None:
- literal = _string_literal(node, constants)
- if literal is not None:
- return literal
- if isinstance(node, ast.Call):
- call_name = _call_name(node.func) or ""
- if call_name in {"Path", "pathlib.Path"} and node.args:
- return _path_literal(node.args[0], constants, source_path)
- if call_name in {"os.path.join", "posixpath.join", "ntpath.join"}:
- parts: list[str] = []
- for arg in node.args:
- part = _path_literal(arg, constants, source_path)
- if part is None:
- return None
- parts.append(part)
- return os.path.join(*parts) if parts else None
- if call_name.endswith(".with_name") and node.args:
- return _source_relative_with_name(
- node.func,
- _path_literal(node.args[0], constants, source_path),
- source_path,
- )
- if isinstance(node.func, ast.Attribute) and node.func.attr == "with_name" and node.args:
- return _source_relative_with_name(
- node.func,
- _path_literal(node.args[0], constants, source_path),
- source_path,
- )
- if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
- left = _path_literal(node.left, constants, source_path)
- right = _path_literal(node.right, constants, source_path)
- if left is not None and right is not None:
- return left + right
- return None
-
-
-def _source_relative_with_name(
- func_node: ast.AST,
- filename: str | None,
- source_path: Path | None,
-) -> str | None:
- if filename is None:
- return None
- if not isinstance(func_node, ast.Attribute):
- return filename
- if source_path is None or not _path_expr_is_dunder_file(func_node.value):
- return filename
- source_dir = source_path.parent
- return (source_dir / filename).as_posix() if source_dir.parts else filename
-
-
-def _path_expr_is_dunder_file(node: ast.AST) -> bool:
- if isinstance(node, ast.Name) and node.id == "__file__":
- return True
- if isinstance(node, ast.Call):
- call_name = _call_name(node.func) or ""
- if call_name in {"Path", "pathlib.Path"} and node.args:
- return _path_expr_is_dunder_file(node.args[0])
- if isinstance(node.func, ast.Attribute) and node.func.attr in {"resolve", "absolute"}:
- return _path_expr_is_dunder_file(node.func.value)
- return False
-
-
-def _missing_artifact_path(submission_dir: Path, value: str) -> str:
- value = (value or "").strip()
- if not value or "://" in value or value.startswith(("~", "$")):
- return ""
- if any(marker in value for marker in ("{", "}", "*", "?")):
- return ""
- path = Path(value)
- if path.is_absolute() or ".." in path.parts:
- return ""
- if path.name == "requirements.txt":
- return ""
- if re.search(
- r"secret|token|password|hidden|source[_-]?item|item[_-]?id|"
- r"source[_-]?id|label[_-]?id|ground[_-]?truth|answer",
- path.name,
- re.IGNORECASE,
- ):
- return ""
- if path.suffix.lower() not in SAFE_ARTIFACT_SUFFIXES:
- return ""
- candidate = (submission_dir / path).resolve()
- try:
- candidate.relative_to(submission_dir.resolve())
- except ValueError:
- return ""
- if candidate.exists():
- return ""
- return path.as_posix()
-
-
-def _check_requirements(submission_dir: Path) -> None:
- requirements = submission_dir / "requirements.txt"
- nested = sorted(
- path.relative_to(submission_dir).as_posix()
- for path in submission_dir.rglob("requirements.txt")
- if path != requirements
- )
- if nested:
- raise ValueError("requirements.txt must be at the ZIP root, not inside a folder.")
- if not requirements.exists():
- return
- for lineno, raw_line in enumerate(requirements.read_text(errors="replace").splitlines(), start=1):
- line = re.sub(r"\s+#.*$", "", raw_line.strip()).strip()
- if not line or line.startswith("#"):
- continue
- if line.startswith("-") or "://" in line or line.startswith(("./", "../", "/")):
- raise ValueError(
- f"requirements.txt line {lineno} is unsupported. Use named pip packages only; "
- "pip option lines, URLs, local paths, and nested requirements files are not supported."
- )
- if not re.match(
- r"^[A-Za-z0-9][A-Za-z0-9_.-]*(?:\[[^\]]+\])?(?:\s*(?:===|==|~=|!=|<=|>=|<|>|;).*)?$",
- line,
- ):
- raise ValueError(
- f"requirements.txt line {lineno} is unsupported. Use named pip packages only."
- )
-
-
-def _pretrained_model_reference(node: ast.Call, constants: dict[str, str]) -> str | None:
- if node.args:
- literal = _string_literal(node.args[0], constants)
- if literal is not None:
- return literal
- for keyword in node.keywords:
- if keyword.arg in {
- "pretrained_model_name_or_path",
- "model_name_or_path",
- "model_name",
- "model",
- "model_id",
- "repo_id",
- "path",
- }:
- literal = _string_literal(keyword.value, constants)
- if literal is not None:
- return literal
- return None
-
-
-def _is_local_model_reference(submission_dir: Path, model_ref: str) -> bool:
- if not model_ref or os.path.isabs(model_ref):
- return False
- if "://" in model_ref or model_ref.startswith(("~", "$")):
- return False
- candidate = (submission_dir / model_ref).resolve()
- try:
- candidate.relative_to(submission_dir.resolve())
- except ValueError:
- return False
- return candidate.exists()
-
-
-def _declared_model_ref_for_call(model_ref: str, call_name: str) -> str:
- if (
- "SentenceTransformer" in call_name
- and "/" not in model_ref
- and model_ref.lower() not in SENTENCE_TRANSFORMER_BASIC_MODEL_IDS
- ):
- return f"sentence-transformers/{model_ref}"
- return model_ref
-
-
-def _is_main_guard(node: ast.AST) -> bool:
- if not isinstance(node, ast.Compare) or len(node.ops) != 1 or len(node.comparators) != 1:
- return False
- if not isinstance(node.ops[0], ast.Eq):
- return False
- left, right = node.left, node.comparators[0]
- return (
- isinstance(left, ast.Name)
- and left.id == "__name__"
- and isinstance(right, ast.Constant)
- and right.value == "__main__"
- ) or (
- isinstance(right, ast.Name)
- and right.id == "__name__"
- and isinstance(left, ast.Constant)
- and left.value == "__main__"
- )
-
-
-def _check_model(submission_dir: Path) -> None:
- entry = submission_dir / "main.py"
- if not entry.exists():
- entry = submission_dir / "main.py"
- model = _load_module(entry, "submission_model", submission_dir)
- predict = getattr(model, "predict", None)
- if not callable(predict):
- raise ValueError(f"{entry.name} must define callable predict(frame, schema).")
-
- import numpy as _np
- import pandas as _pd
-
- frame = _pd.DataFrame({
- "respondent_id": ["R1", "R2", "R3"],
- "region": ["North", "South", "North"],
- "visited_clinic": ["Yes", "No", _np.nan],
- "clinic_wait": ["Under 30 minutes", "NA_GATED", _np.nan],
- })
- try:
- value = predict(frame, dict(SMOKE_SCHEMA))
- except TypeError as exc:
- raise ValueError(
- "predict() must accept two positional arguments: "
- "predict(frame, schema)."
- ) from exc
- except Exception as exc:
- raise ValueError("predict() raised during the local smoke check.") from exc
- _assert_vectors(value, SMOKE_WIDTHS, "predict()")
-
-
-def _load_module(path: Path, module_name: str, submission_dir: Path):
- previous_path = list(sys.path)
- sys.path.insert(0, str(submission_dir))
- try:
- spec = importlib.util.spec_from_file_location(module_name, path)
- if spec is None or spec.loader is None:
- raise ValueError(f"Could not import {path.name}.")
- module = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(module)
- return module
- finally:
- sys.path[:] = previous_path
-
-
-def _assert_vectors(value, widths: list[int], label: str) -> None:
- """Validate a predict() return exactly as the hosted ingestion does.
-
- One vector per blank cell, in canonical order, each as long as that item's
- option list — `values` plus a final slot for the gate sentinel when the item
- is gated. Probabilities must be finite and non-negative with a positive sum;
- the hosted scorer renormalizes and floors, so the local check must not be
- stricter than hosting.
- """
- if not isinstance(value, (list, tuple)):
- raise ValueError(
- f"{label} must return a list of probability vectors, one per blank "
- f"cell, got {type(value).__name__}."
- )
- if len(value) != len(widths):
- raise ValueError(
- f"{label} returned {len(value)} vectors for {len(widths)} blank "
- "cells. Canonical order is rows top to bottom, and within a row, "
- "items in schema key order."
- )
- for index, (vector, width) in enumerate(zip(value, widths)):
- try:
- numbers = [_assert_finite_number(p, label) for p in vector]
- except TypeError:
- raise ValueError(f"{label} vector {index} is not a sequence.") from None
- if len(numbers) != width:
- raise ValueError(
- f"{label} vector {index} has {len(numbers)} entries, but that "
- f"item has {width} options. Read the option list from the "
- "schema, not from the data — the gate sentinel gets a slot too."
- )
- if any(p < 0.0 for p in numbers):
- raise ValueError(f"{label} vector {index} contains a negative value.")
- if sum(numbers) <= 0.0:
- raise ValueError(f"{label} vector {index} sums to zero.")
-
-
-def _assert_finite_number(value, label: str) -> float:
- if isinstance(value, bool):
- raise ValueError(f"{label} must return finite numeric probabilities.")
- try:
- number = float(value)
- except (TypeError, ValueError):
- raise ValueError(f"{label} must return finite numeric probabilities.") from None
- if not math.isfinite(number):
- raise ValueError(f"{label} must return finite numeric probabilities.")
- return number
-
-
-if __name__ == "__main__":
- raise SystemExit(main())
diff --git a/examples/simulacrabench_synthetic/verify_packet.py b/examples/simulacrabench_synthetic/verify_packet.py
deleted file mode 100644
index 6375118..0000000
--- a/examples/simulacrabench_synthetic/verify_packet.py
+++ /dev/null
@@ -1,663 +0,0 @@
-"""Verify the public SimulacraBench synthetic closed-evaluation packet.
-
-This verifier performs no network access and never receives the hidden synthetic
-respondent fixture. It checks public commitments, derives an ``IDENTIFIED`` floor for
-the unobserved private artifacts, and admits a structural challenge. It does not
-recompute the private-data score, execute retrieval, adjudicate the challenge, or create
-an independent trust root.
-"""
-
-from __future__ import annotations
-
-import argparse
-import hashlib
-import json
-import re
-from pathlib import Path
-from typing import Any, Mapping
-
-from verifier.core.certificate import ClaimCoordinate, canonical_bytes, canonical_digest
-from verifier.data.models import ArtifactStatus
-from verifier.layer4.availability import (
- ArtifactAvailability,
- AvailabilityLevel,
- RetentionPolicy,
- assess_bundle,
-)
-from verifier.layer4.challenge import Challenge, ChallengeLedger
-from verifier.layer4.surface import (
- AdmissibleRefutation,
- ExcludedClaim,
- RefutationSurface,
- RefutationType,
-)
-
-
-ROOT = Path(__file__).resolve().parent
-DEFAULT_PACKET = ROOT / "public_packet.json"
-DEFAULT_CHALLENGE = ROOT / "challenge_demo.json"
-SHA256 = re.compile(r"^sha256:[0-9a-f]{64}$")
-COMMIT = re.compile(r"^[0-9a-f]{40}$")
-RETENTION_HORIZON = "2026-09-30T23:59:59Z"
-UPSTREAM_REPOSITORY = "https://github.com/SituatedEvals/public"
-PINNED_COMMIT = "1bb2d46026fe0d91979448c3d916506be0608513"
-SOURCE_PATHS = (
- "README.md",
- "LICENSE",
- "config.yml",
- "data/sample.json",
- "make_sandbox.py",
- "score.py",
- "baseline/marginal_counts/main.py",
- "baseline/marginal_counts/requirements.txt",
- "tools/check_submission_zip.py",
-)
-EVIDENCE_POLICY = {
- "upstream-01-README-md": (False, "public", "SELF_CONTAINED"),
- "upstream-02-LICENSE": (False, "public", "SELF_CONTAINED"),
- "upstream-03-config-yml": (True, "public", "SELF_CONTAINED"),
- "upstream-04-data-sample-json": (True, "public", "SELF_CONTAINED"),
- "upstream-05-make_sandbox-py": (True, "public", "SELF_CONTAINED"),
- "upstream-06-score-py": (True, "public", "SELF_CONTAINED"),
- "upstream-07-baseline-marginal_counts-main-py": (
- True,
- "public",
- "SELF_CONTAINED",
- ),
- "upstream-08-baseline-marginal_counts-requirements-txt": (
- True,
- "public",
- "SELF_CONTAINED",
- ),
- "upstream-09-tools-check_submission_zip-py": (
- False,
- "public",
- "SELF_CONTAINED",
- ),
- "submission-archive": (True, "public", "SELF_CONTAINED"),
- "public-sandbox-schema-view": (False, "public", "SELF_CONTAINED"),
- "scored-sandbox-schema": (True, "access-controlled", "IDENTIFIED"),
- "hidden-synthetic-fixture": (True, "access-controlled", "IDENTIFIED"),
- "organizer-log": (True, "access-controlled", "IDENTIFIED"),
- "execution-transcript": (True, "access-controlled", "IDENTIFIED"),
- "generator-seed": (False, "access-controlled", "IDENTIFIED"),
- "participant-visible-result": (True, "public", "SELF_CONTAINED"),
-}
-
-
-class PacketError(ValueError):
- """Raised when the public specimen overstates or contradicts its evidence."""
-
-
-def _record(value: Any, required: set[str], label: str) -> Mapping[str, Any]:
- if not isinstance(value, Mapping):
- raise PacketError(f"{label} must be an object")
- missing = sorted(required - set(value))
- if missing:
- raise PacketError(f"{label} is missing fields: {missing}")
- extra = sorted(set(value) - required)
- if extra:
- raise PacketError(f"{label} has unexpected fields: {extra}")
- return value
-
-
-def _load(path: Path) -> dict[str, Any]:
- try:
- value = json.loads(path.read_text(encoding="utf-8"))
- except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
- raise PacketError(f"cannot read {path}: {exc}") from exc
- if not isinstance(value, dict):
- raise PacketError(f"{path} root must be an object")
- return value
-
-
-def _verify_document_digest(document: dict[str, Any], field: str) -> str:
- stated = document.get(field)
- if not isinstance(stated, str) or not SHA256.fullmatch(stated):
- raise PacketError(f"{field} must be a sha256 content address")
- payload = dict(document)
- del payload[field]
- observed = f"sha256:{canonical_digest(payload)}"
- if observed != stated:
- raise PacketError(f"{field} mismatch: stated {stated}, observed {observed}")
- return observed
-
-
-def _availability_item(
- entry: Mapping[str, Any], reported_result: Mapping[str, Any]
-) -> ArtifactAvailability:
- required = {
- "artifact_id",
- "role",
- "disclosure",
- "content_address",
- "verdict_critical",
- "embedded",
- "bundle_path",
- "locator",
- "anonymous_access",
- "retrieval_procedure",
- "retention",
- "declared_level",
- "assessed_level",
- }
- item = _record(entry, required, f"evidence_inventory[{entry.get('artifact_id', '?')}]")
- artifact_id = item["artifact_id"]
- if not isinstance(artifact_id, str) or artifact_id not in EVIDENCE_POLICY:
- raise PacketError(f"unexpected evidence artifact ID: {artifact_id!r}")
- if not isinstance(item["role"], str) or not item["role"].strip():
- raise PacketError(f"{artifact_id} has no role")
- if type(item["verdict_critical"]) is not bool:
- raise PacketError(f"{artifact_id}.verdict_critical must be a boolean")
- if type(item["embedded"]) is not bool:
- raise PacketError(f"{artifact_id}.embedded must be a boolean")
- if type(item["anonymous_access"]) is not bool:
- raise PacketError(f"{artifact_id}.anonymous_access must be a boolean")
- policy = EVIDENCE_POLICY[artifact_id]
- observed_policy = (
- item["verdict_critical"],
- item["disclosure"],
- item["assessed_level"],
- )
- if observed_policy != policy:
- raise PacketError(
- f"{artifact_id} evidence policy is {observed_policy}, expected {policy}"
- )
- address = str(item["content_address"])
- if not SHA256.fullmatch(address):
- raise PacketError(f"{item['artifact_id']} has an invalid content address")
-
- retention_value = item["retention"]
- retention = None
- if retention_value is not None:
- retention_record = _record(
- retention_value,
- {"horizon", "custodian", "replicas"},
- f"{item['artifact_id']}.retention",
- )
- retention = RetentionPolicy(
- str(retention_record["horizon"]),
- str(retention_record["custodian"]),
- retention_record["replicas"],
- )
- if (
- not retention.horizon
- or not retention.custodian.strip()
- or type(retention.replicas) is not int
- or retention.replicas != 1
- ):
- raise PacketError(f"{artifact_id}.retention is malformed")
-
- embedded_bytes = None
- if item["embedded"]:
- if item["artifact_id"] == "participant-visible-result":
- embedded_bytes = canonical_bytes(reported_result)
- if f"sha256:{canonical_digest(reported_result)}" != address:
- raise PacketError("embedded participant result does not match its content address")
- else:
- bundle_path = str(item["bundle_path"])
- local = (ROOT / bundle_path).resolve()
- try:
- local.relative_to(ROOT)
- except ValueError as exc:
- raise PacketError(f"{item['artifact_id']} bundle path escapes the example") from exc
- if not local.is_file():
- raise PacketError(f"{item['artifact_id']} bundle path does not exist")
- embedded_bytes = local.read_bytes()
- if f"sha256:{hashlib.sha256(embedded_bytes).hexdigest()}" != address:
- raise PacketError(f"{item['artifact_id']} bundled bytes do not match their content address")
- elif item["bundle_path"]:
- raise PacketError(f"{item['artifact_id']} names a bundle path but is not embedded")
-
- try:
- declared = AvailabilityLevel(str(item["declared_level"]))
- except ValueError as exc:
- raise PacketError(f"{item['artifact_id']} has an invalid declared level") from exc
-
- artifact = ArtifactAvailability(
- artifact_id=artifact_id,
- content_address=address,
- verdict_critical=bool(item["verdict_critical"]),
- embedded_bytes=embedded_bytes,
- locator=str(item["locator"]),
- anonymous_access=bool(item["anonymous_access"]),
- retrieval_procedure=str(item["retrieval_procedure"]),
- retention=retention,
- declared_level=declared,
- )
- if artifact.assess().value != item["assessed_level"]:
- raise PacketError(
- f"{item['artifact_id']} assessed level is {artifact.assess().value}, "
- f"not {item['assessed_level']}"
- )
- if item["disclosure"] == "access-controlled" and artifact.assess() in {
- AvailabilityLevel.PORTABLE,
- AvailabilityLevel.SELF_CONTAINED,
- }:
- raise PacketError(f"{item['artifact_id']} overstates access-controlled evidence")
- return artifact
-
-
-def verify_packet(document: dict[str, Any]) -> dict[str, Any]:
- required = {
- "packet_format",
- "packet_id",
- "profile",
- "source",
- "claim",
- "execution",
- "reported_result",
- "evidence_inventory",
- "availability_summary",
- "disclosure_interface",
- "refutation_surface",
- "trust",
- "limits",
- "correction",
- "packet_digest",
- }
- _record(document, required, "packet")
- if document["packet_format"] != "VSTD-CLOSED-EVALUATION-PROFILE-0.2":
- raise PacketError("unexpected packet format")
- if document["packet_id"] != "VSTD-SB-SYNTH-002":
- raise PacketError("unexpected packet ID")
- _verify_document_digest(document, "packet_digest")
-
- correction = _record(
- document["correction"],
- {
- "supersedes_packet_id",
- "supersedes_packet_digest",
- "historical_commit",
- "reason",
- },
- "correction",
- )
- if correction["supersedes_packet_id"] != "VSTD-SB-SYNTH-001":
- raise PacketError("correction does not name the superseded packet")
- if correction["supersedes_packet_digest"] != (
- "sha256:f182bfce5a5ae8e7137795300d42e285f365e6707b7c3517b3cee7b02331963b"
- ):
- raise PacketError("correction does not bind the superseded packet digest")
- if correction["historical_commit"] != (
- "a37e6128fc6eccb66160a2f7c3af2f43341c227e"
- ):
- raise PacketError("correction does not bind the historical public commit")
- if not str(correction["reason"]).strip():
- raise PacketError("correction reason is empty")
-
- profile = _record(document["profile"], {"name", "version", "normative"}, "profile")
- if profile != {
- "name": "SimulacraBench synthetic closed-evaluation crosswalk",
- "version": "0.2",
- "normative": False,
- }:
- raise PacketError("the target-specific profile must remain non-normative")
-
- source = _record(document["source"], {"repository", "commit", "artifacts"}, "source")
- commit = str(source["commit"])
- if not COMMIT.fullmatch(commit):
- raise PacketError("source commit must be a full Git commit")
- if source["repository"] != UPSTREAM_REPOSITORY or commit != PINNED_COMMIT:
- raise PacketError("source is not the pinned official repository commit")
- if not isinstance(source["artifacts"], list):
- raise PacketError("source.artifacts must be a list")
- observed_paths = [str(item.get("path", "")) for item in source["artifacts"] if isinstance(item, Mapping)]
- if observed_paths != list(SOURCE_PATHS):
- raise PacketError("source artifact paths or ordering differ from the pinned snapshot")
- for artifact in source["artifacts"]:
- record = _record(
- artifact,
- {"path", "url", "sha256", "bytes", "bundle_path"},
- "source.artifact",
- )
- expected_prefix = f"https://github.com/SituatedEvals/public/blob/{commit}/"
- if str(record["url"]) != expected_prefix + str(record["path"]):
- raise PacketError(f"source URL is not pinned to {commit}: {record['url']}")
- if not re.fullmatch(r"[0-9a-f]{64}", str(record["sha256"])):
- raise PacketError(f"source artifact {record['path']} has an invalid digest")
- if type(record["bytes"]) is not int or record["bytes"] < 1:
- raise PacketError(f"source artifact {record['path']} has an invalid size")
- expected_bundle_path = f"source_snapshot/{record['path']}"
- if record["bundle_path"] != expected_bundle_path:
- raise PacketError(f"source artifact {record['path']} has the wrong bundle path")
- local = (ROOT / expected_bundle_path).resolve()
- try:
- local.relative_to(ROOT)
- except ValueError as exc:
- raise PacketError(f"source artifact {record['path']} escapes the example") from exc
- if not local.is_file() or local.stat().st_size != record["bytes"]:
- raise PacketError(f"source artifact {record['path']} size does not match its snapshot")
- if hashlib.sha256(local.read_bytes()).hexdigest() != record["sha256"]:
- raise PacketError(f"source artifact {record['path']} digest does not match its snapshot")
-
- claim = _record(
- document["claim"],
- {"claim_id", "statement", "coordinate", "status", "does_not_establish"},
- "claim",
- )
- if claim["status"] != "RECORDED_UNDER_DECLARED_SYNTHETIC_EVALUATOR":
- raise PacketError("claim status exceeds the synthetic evaluator boundary")
- if claim["claim_id"] != "VSTD-SB-SYNTH-002-RESULT":
- raise PacketError("unexpected claim ID")
- if not claim["does_not_establish"]:
- raise PacketError("claim must state explicit exclusions")
- surface = _surface(document["refutation_surface"])
- surface_check = surface.validate()
- if not surface_check.accepted:
- raise PacketError(surface_check.details)
- if surface.coordinate.to_dict() != claim["coordinate"]:
- raise PacketError("refutation surface is not bound to the claim coordinate")
-
- execution = _record(
- document["execution"],
- {
- "mode",
- "official_policy",
- "observed_local_controls",
- "unobserved_hosted_controls",
- "prior_commitment",
- },
- "execution",
- )
- if execution["mode"] != "LOCAL_SYNTHETIC_REHEARSAL":
- raise PacketError("the specimen must not present itself as a hosted competition run")
- prior_commitment = _record(
- execution["prior_commitment"],
- {"fixture_frozen_before_execution", "externally_timestamped", "limitation"},
- "execution.prior_commitment",
- )
- if prior_commitment["externally_timestamped"] is not False:
- raise PacketError("the local sequence has no external precommitment timestamp")
-
- reported = _record(
- document["reported_result"],
- {"status", "reported_skill", "printed_result", "phase", "privacy_policy"},
- "reported_result",
- )
- if (
- reported["status"] != "PASS"
- or reported["reported_skill"] != 0.33
- or reported["printed_result"] != "PASS 0.3300 (35.7s)"
- or reported["phase"] != 1
- ):
- raise PacketError("reported result is malformed")
-
- inventory = document["evidence_inventory"]
- if not isinstance(inventory, list) or not inventory:
- raise PacketError("evidence_inventory must be non-empty")
- artifacts = tuple(_availability_item(entry, reported) for entry in inventory)
- ids = [item.artifact_id for item in artifacts]
- if len(ids) != len(set(ids)):
- raise PacketError("evidence_inventory repeats an artifact ID")
- if set(ids) != set(EVIDENCE_POLICY):
- raise PacketError("evidence_inventory is not the closed expected artifact set")
- assessment = assess_bundle(artifacts, required=AvailabilityLevel.AVAILABLE)
- summary = _record(
- document["availability_summary"],
- {"required", "derived_floor", "accepted", "limiting_artifacts", "public_reproduction"},
- "availability_summary",
- )
- expected_summary = {
- "required": AvailabilityLevel.AVAILABLE.value,
- "derived_floor": assessment.level.value,
- "accepted": assessment.accepted,
- "limiting_artifacts": list(assessment.limiting_artifacts),
- "public_reproduction": "UNAVAILABLE",
- }
- if dict(summary) != expected_summary:
- raise PacketError(f"availability summary mismatch: expected {expected_summary}")
-
- disclosure = _record(
- document["disclosure_interface"],
- {"committed", "checker_receives", "predicate_checked", "checker_returns", "does_not_follow"},
- "disclosure_interface",
- )
- if not disclosure["checker_receives"] or not disclosure["does_not_follow"]:
- raise PacketError("disclosure interface is incomplete")
-
- trust = _record(
- document["trust"],
- {"evaluator", "independent", "vstd5_witness", "organizer_affiliation"},
- "trust",
- )
- if trust["independent"] is not False or trust["vstd5_witness"] is not False:
- raise PacketError("founder-operated synthetic evaluation is not independent")
- if trust["organizer_affiliation"] != "NONE":
- raise PacketError("the specimen must not imply organizer affiliation")
-
- limits = _record(
- document["limits"],
- {"vstd4_depth_claim", "reason", "retention_declaration_horizon"},
- "limits",
- )
- if limits["vstd4_depth_claim"] is not None:
- raise PacketError("component checks do not establish an aggregate VSTD-4 depth")
- if limits["retention_declaration_horizon"] != RETENTION_HORIZON:
- raise PacketError(
- "retention_declaration_horizon must equal the private-artifact declaration"
- )
- for artifact in artifacts:
- if (
- artifact.retention is not None
- and artifact.retention.horizon != limits["retention_declaration_horizon"]
- ):
- raise PacketError(
- f"{artifact.artifact_id} retention does not match the packet declaration"
- )
-
- return {
- "packet_id": document["packet_id"],
- "packet_digest": document["packet_digest"],
- "availability_floor": assessment.level.value,
- "public_reproduction": summary["public_reproduction"],
- "claim_status": claim["status"],
- }
-
-
-def _surface(value: Mapping[str, Any]) -> RefutationSurface:
- value = _record(
- value,
- {"coordinate", "admissible_refutations", "excluded_claims"},
- "refutation_surface",
- )
- coordinate_record = _record(value["coordinate"], {"subject", "predicate", "parameters"}, "coordinate")
- if not isinstance(coordinate_record["parameters"], Mapping):
- raise PacketError("coordinate.parameters must be an object")
- if not isinstance(value["admissible_refutations"], list):
- raise PacketError("admissible_refutations must be a list")
- coordinate = ClaimCoordinate(
- str(coordinate_record["subject"]),
- str(coordinate_record["predicate"]),
- {str(k): str(v) for k, v in coordinate_record["parameters"].items()},
- )
- admissible = []
- for raw in value["admissible_refutations"]:
- item = _record(
- raw,
- {"refutation_type", "applies_to", "overturning_evidence", "resulting_status"},
- "admissible_refutation",
- )
- admissible.append(
- AdmissibleRefutation(
- RefutationType(str(item["refutation_type"])),
- tuple(str(entry) for entry in item["applies_to"]),
- str(item["overturning_evidence"]),
- str(item["resulting_status"]),
- )
- )
- if not isinstance(value["excluded_claims"], list):
- raise PacketError("excluded_claims must be a list")
- excluded = tuple(
- ExcludedClaim(
- str(_record(item, {"claim_id", "reason"}, "excluded_claim")["claim_id"]),
- str(_record(item, {"claim_id", "reason"}, "excluded_claim")["reason"]),
- )
- for item in value["excluded_claims"]
- )
- return RefutationSurface(coordinate, tuple(admissible), excluded)
-
-
-def verify_challenge(packet: dict[str, Any], document: dict[str, Any]) -> dict[str, Any]:
- required = {
- "challenge_format",
- "challenge_id",
- "target_packet_digest",
- "deliberate_mutation",
- "refutation_surface",
- "filing",
- "transitions",
- "localized_effect",
- "leak_check",
- "trust",
- "challenge_digest",
- }
- _record(document, required, "challenge_demo")
- if document["challenge_format"] != "VSTD-CLOSED-EVALUATION-CHALLENGE-0.2":
- raise PacketError("unexpected challenge format")
- if document["challenge_id"] != "VSTD-SB-SYNTH-002-CHALLENGE-001":
- raise PacketError("unexpected challenge ID")
- _verify_document_digest(document, "challenge_digest")
- if document["target_packet_digest"] != packet["packet_digest"]:
- raise PacketError("challenge is not bound to the public packet")
-
- mutation = _record(
- document["deliberate_mutation"],
- {"field", "original", "mutated", "purpose"},
- "deliberate_mutation",
- )
- if mutation["field"] != "reported_result.reported_skill":
- raise PacketError("challenge must localize to the aggregate result")
- if mutation["original"] != packet["reported_result"]["reported_skill"]:
- raise PacketError("challenge original does not match the packet")
- if mutation["mutated"] != 0.34:
- raise PacketError("challenge mutation must be the declared 0.34 mutant")
-
- surface = _surface(document["refutation_surface"])
- surface_check = surface.validate()
- if not surface_check.accepted:
- raise PacketError(surface_check.details)
- if surface.to_dict() != packet["refutation_surface"]:
- raise PacketError("challenge refutation surface differs from the target packet")
-
- filing = _record(
- document["filing"],
- {
- "target_claim_id",
- "target_certificate_id",
- "challenged_predicate",
- "challenge_type",
- "counterevidence",
- "filed_at",
- "challenge_certificate",
- },
- "filing",
- )
- challenge = Challenge(
- str(document["challenge_id"]),
- str(filing["target_claim_id"]),
- str(filing["target_certificate_id"]),
- str(filing["challenged_predicate"]),
- RefutationType(str(filing["challenge_type"])),
- str(filing["counterevidence"]),
- str(filing["filed_at"]),
- str(filing["challenge_certificate"]),
- )
- if challenge.target_claim_id != "VSTD-SB-SYNTH-002-RESULT-MUTANT":
- raise PacketError("challenge does not target the declared mutant claim")
- if challenge.target_certificate_id != packet["packet_id"]:
- raise PacketError("challenge target certificate differs from the packet")
- if challenge.challenged_predicate != packet["claim"]["coordinate"]["predicate"]:
- raise PacketError("challenge predicate differs from the packet coordinate")
- ledger = ChallengeLedger()
- admission = ledger.file(challenge, surface)
- if not admission.admitted:
- raise PacketError(admission.details)
- public_status = ledger.status(challenge.target_claim_id)
-
- transitions = _record(
- document["transitions"],
- {"after_public_filing"},
- "transitions",
- )
- if transitions["after_public_filing"] != public_status.status.value:
- raise PacketError("public filing transition mismatch")
- if public_status.status is not ArtifactStatus.CHALLENGED:
- raise PacketError("public filing must leave the aggregate claim CHALLENGED")
-
- leak = _record(
- document["leak_check"],
- {"individual_records", "hidden_item_ids", "hidden_item_text", "labels", "raw_predictions", "raw_traceback"},
- "leak_check",
- )
- if any(value not in (0, False) for value in leak.values()):
- raise PacketError("challenge demo leaks a prohibited hidden-data field")
- trust = _record(
- document["trust"],
- {"independent", "vstd5_witness", "adjudicated"},
- "challenge.trust",
- )
- if (
- trust["independent"] is not False
- or trust["vstd5_witness"] is not False
- or trust["adjudicated"] is not False
- ):
- raise PacketError("public filing is neither independent nor adjudicated")
- localized = _record(
- document["localized_effect"],
- {"challenged", "unchanged"},
- "localized_effect",
- )
- if localized["challenged"] != ["mutated aggregate-result claim"]:
- raise PacketError("challenge filing is not localized to the mutated aggregate")
- if not localized["unchanged"]:
- raise PacketError("challenge demo must name the evidence left unchanged")
-
- return {
- "challenge_id": document["challenge_id"],
- "challenge_digest": document["challenge_digest"],
- "after_public_filing": public_status.status.value,
- "adjudicated": trust["adjudicated"],
- "records_disclosed": leak["individual_records"],
- }
-
-
-def verify_all(packet_path: Path = DEFAULT_PACKET, challenge_path: Path = DEFAULT_CHALLENGE) -> dict[str, Any]:
- packet = _load(packet_path)
- challenge = _load(challenge_path)
- return {
- "packet": verify_packet(packet),
- "challenge": verify_challenge(packet, challenge),
- }
-
-
-def main() -> int:
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument("--packet", type=Path, default=DEFAULT_PACKET)
- parser.add_argument("--challenge", type=Path, default=DEFAULT_CHALLENGE)
- parser.add_argument("--json", action="store_true")
- args = parser.parse_args()
- try:
- result = verify_all(args.packet, args.challenge)
- except PacketError as exc:
- print(f"[FAIL] {exc}")
- return 1
- if args.json:
- print(json.dumps(result, indent=2, sort_keys=True))
- else:
- print(
- "[PASS] synthetic closed-evaluation packet: "
- f"availability={result['packet']['availability_floor']}, "
- f"public_reproduction={result['packet']['public_reproduction']}"
- )
- print(
- "[PASS] non-disclosing challenge: "
- f"status={result['challenge']['after_public_filing']}, "
- f"adjudicated={result['challenge']['adjudicated']}, "
- f"records_disclosed={result['challenge']['records_disclosed']}"
- )
- return 0
-
-
-if __name__ == "__main__":
- raise SystemExit(main())
diff --git a/examples/verification_geometry_residual/README.md b/examples/verification_geometry_residual/README.md
index 2add039..3126e2e 100644
--- a/examples/verification_geometry_residual/README.md
+++ b/examples/verification_geometry_residual/README.md
@@ -1,7 +1,10 @@
# Reconstruction residual and bounded closure
-This example is the smallest VSTD-0.2 verification-geometry vertical slice. Its
-machine-readable form is [`geometry.json`](geometry.json).
+> **Acronym:** Verifier Standard (VSTD).
+
+This example is the smallest VSTD-2 verification-geometry vertical slice. Its
+machine-readable form is [`geometry.json`](geometry.json) under the current `VSTD-2`
+identifier.
## 1. Apparently complete decomposition
@@ -53,8 +56,9 @@ the bounded geometry earn self-closure.
## 5. Higher-order verification without infinite workflow abstraction
-`layer:v0` verifies the formatter surface. `layer:v1` treats the V0 geometry as a
-secondary subject and verifies the immediately preceding layer. The typed validator
-requires orders to be finite, contiguous, and adjacent. A skipped or recursively
-invented workflow layer is invalid; an inability to continue is represented as a
+The serialized compatibility identifiers `layer:v0` and `layer:v1` denote VSTD-2
+**verification orders**, not numbered VSTD profiles. V0 verifies the formatter surface;
+V1 treats the V0 geometry as a secondary subject and verifies the immediately preceding
+order. The typed validator requires orders to be finite, contiguous, and adjacent. A
+skipped or recursively invented verification order is invalid; an inability to continue is represented as a
horizon rather than hidden behind a trust assumption.
diff --git a/examples/verification_geometry_residual/geometry.json b/examples/verification_geometry_residual/geometry.json
index 841be14..53be8f8 100644
--- a/examples/verification_geometry_residual/geometry.json
+++ b/examples/verification_geometry_residual/geometry.json
@@ -204,7 +204,7 @@
"seam_id": "seam:locale-render"
}
],
- "schema_version": "VSTD-0.2",
+ "schema_version": "VSTD-2",
"seams": [
{
"label": "parsed value to renderer",
diff --git a/examples/zizk_artifact_first/README.md b/examples/zizk_artifact_first/README.md
new file mode 100644
index 0000000..f5ce12f
--- /dev/null
+++ b/examples/zizk_artifact_first/README.md
@@ -0,0 +1,35 @@
+# Artifact-first reference surfaces
+
+> **Acronyms:** reduced instruction set computer (RISC); Verifier Standard (VSTD);
+> zero-identity/zero-knowledge (ZIZK).
+
+VSTD's governing ZIZK artifact-first architecture is normative in
+[`standard/LADDER.md` section 1.1](../../standard/LADDER.md#11-artifact-first-causal-provenance-orientation).
+It gives actor identity and reputation no assurance weight and evaluates bounded process
+claims represented by software and artifacts. Its formal semantic names are TRUST for
+mechanism-earned forward support, ROT for typed time-indexed loss of current admissibility,
+and RUST for inverse-TRUST diagnostic traversal toward recorded ancestors. They are not
+acronyms, actor ratings, serialized receipt values, scalar scores, or references to the Rust programming
+language.
+
+This directory contains bounded reference mechanisms under that architecture. A
+mechanism may be optional without making the architecture optional.
+
+## Bounded identity-disclosure evaluator
+
+[`zero_identity/`](zero_identity/) is a standard-library reference evaluator that
+preserves the identity, authorization, provenance, `UNKNOWN`, and `CONFLICTED`
+boundaries exposed by a disclosure record. It earns no identity-derived trust, carries
+no serialized receipt identifier, and establishes no VSTD conformance result.
+
+## RISC Zero hidden-witness mechanism
+
+[`risc0/`](risc0/) contains the pinned Rust prover/verifier, its claim boundary and
+threat model, and the exact tracked public artifacts from one real composite scalable
+transparent argument of knowledge proof. The private witness is excluded. Start with
+[`risc0/README.md`](risc0/README.md) to verify the recorded receipt offline.
+
+The proof establishes only execution of its fixed hidden-witness predicate under the
+named image identifier and proof-system assumptions. It does not establish external
+truth, identity, authorization, independence, complete VSTD trichotomy semantics, or a
+general VSTD conformance result.
diff --git a/examples/zizk_artifact_first/risc0/.gitignore b/examples/zizk_artifact_first/risc0/.gitignore
new file mode 100644
index 0000000..279e18f
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/.gitignore
@@ -0,0 +1,4 @@
+target/
+local-artifacts/
+private-witness.json
+private-*.json
diff --git a/examples/zizk_artifact_first/risc0/CLAIM_BOUNDARY.md b/examples/zizk_artifact_first/risc0/CLAIM_BOUNDARY.md
new file mode 100644
index 0000000..8059d8a
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/CLAIM_BOUNDARY.md
@@ -0,0 +1,60 @@
+# Claim boundary
+
+> **Acronyms:** identifier (ID); JavaScript Object Notation (JSON); reduced instruction set computer (RISC);
+> Verifier Standard (VSTD); zero-identity/zero-knowledge (ZIZK);
+> zero-knowledge virtual machine (zkVM).
+
+## Permitted claim after the recorded real-proof run
+
+This bounded reference mechanism demonstrates that RISC Zero zkVM 3.0.6 can produce a real,
+locally verified zero-knowledge receipt for one fixed bounded predicate, while keeping
+the mechanism's private witness out of the serialized public artifact package.
+
+The concrete verified statement is:
+
+> The program identified by the expected image ID halted successfully and authenticated
+> a journal stating that its private encoded input satisfied the fixed mechanism
+> predicate and was bound to the journal's subject, policy, challenge, threshold, and
+> salted evidence commitment.
+
+The repository's governed offline command additionally builds the tracked guest with
+the locked toolchain and refuses the receipt unless that build's image ID equals the
+recorded image ID. Proof verification alone binds an image identifier; this separate
+comparison is what connects the tracked source-facing build to that identifier.
+
+The zero-knowledge basis is the selected protocol and implementation, not merely the
+absence of witness text from JSON. The artifact scan is an additional serialization
+check, not a proof of zero knowledge.
+
+## Prohibited claims
+
+The reference mechanism does not prove:
+
+- that the hidden evidence is true, complete, authentic, fresh, or lawfully obtained;
+- that its producer is authorized, unique, independent, honest, or non-revoked;
+- that the private `Supported` tag was assigned correctly;
+- that the subject or policy digest resolves to trustworthy external content;
+- freshness beyond possession of the journal's challenge;
+- prevention of replay for the same challenge;
+- host confidentiality, constant-time behavior, or side-channel resistance;
+- security of every RISC Zero component or transitive dependency;
+- independent implementation or external adoption;
+- VSTD conformance for this mechanism; or
+- that VSTD should require zero knowledge for full-disclosure receipts.
+
+An `Unknown` or `Conflicted` mechanism input is rejected by this particular predicate.
+That rejection does not turn uncertainty into falsity, and it never upgrades either
+state into a clean result. Other VSTD mechanisms must continue to preserve `UNKNOWN` and
+`CONFLICTED` when those are the evidence-supported outcomes.
+
+## Architecture consequence
+
+This mechanism implements one bounded proof-carrying privacy path under VSTD's governing
+ZIZK artifact-first architecture. Specifically, it places a cryptographic zero-knowledge
+enclosure around the architectural rule that no unevidenced proposition is presumed. The
+proof binds one exact program, predicate, public commitment set, output, parameter set,
+and verifier while withholding its witness; prover identity and reputation add no TRUST,
+VSTD's formal name for mechanism-earned artifact support.
+It neither creates the architecture nor makes its specific proof system mandatory. No
+serialized receipt identifier, schema, canonical digest, lifecycle token, console alias, or
+existing receipt interpretation changes.
diff --git a/examples/zizk_artifact_first/risc0/Cargo.lock b/examples/zizk_artifact_first/risc0/Cargo.lock
new file mode 100644
index 0000000..6490386
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/Cargo.lock
@@ -0,0 +1,3674 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
+
+[[package]]
+name = "ark-bn254"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-r1cs-std",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-crypto-primitives"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0c292754729c8a190e50414fd1a37093c786c709899f29c9f7daccecfa855e"
+dependencies = [
+ "ahash",
+ "ark-crypto-primitives-macros",
+ "ark-ec",
+ "ark-ff",
+ "ark-relations",
+ "ark-serialize",
+ "ark-snark",
+ "ark-std",
+ "blake2",
+ "derivative",
+ "digest",
+ "fnv",
+ "merlin",
+ "sha2",
+]
+
+[[package]]
+name = "ark-crypto-primitives-macros"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7e89fe77d1f0f4fe5b96dfc940923d88d17b6a773808124f21e764dfb063c6a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce"
+dependencies = [
+ "ahash",
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "educe",
+ "fnv",
+ "hashbrown 0.15.5",
+ "itertools 0.13.0",
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "arrayvec",
+ "digest",
+ "educe",
+ "itertools 0.13.0",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60"
+dependencies = [
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "ark-groth16"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88f1d0f3a534bb54188b8dcc104307db6c56cdae574ddc3212aec0625740fc7e"
+dependencies = [
+ "ark-crypto-primitives",
+ "ark-ec",
+ "ark-ff",
+ "ark-poly",
+ "ark-relations",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27"
+dependencies = [
+ "ahash",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "educe",
+ "fnv",
+ "hashbrown 0.15.5",
+]
+
+[[package]]
+name = "ark-r1cs-std"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-relations",
+ "ark-std",
+ "educe",
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+ "tracing",
+]
+
+[[package]]
+name = "ark-relations"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384"
+dependencies = [
+ "ark-ff",
+ "ark-std",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "arrayvec",
+ "digest",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "ark-snark"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d368e2848c2d4c129ce7679a7d0d2d612b6a274d3ea6a13bad4445d61b381b88"
+dependencies = [
+ "ark-ff",
+ "ark-relations",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a"
+dependencies = [
+ "num-traits",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "arraydeque"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "base64ct"
+version = "1.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
+
+[[package]]
+name = "blake2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bonsai-sdk"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fc4edab3bb401344292b3de527d15663b6bbcba76d98485d96b1bd3061c7987"
+dependencies = [
+ "duplicate",
+ "maybe-async",
+ "reqwest",
+ "serde",
+ "thiserror",
+]
+
+[[package]]
+name = "borsh"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f"
+dependencies = [
+ "borsh-derive",
+ "cfg_aliases",
+]
+
+[[package]]
+name = "borsh-derive"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8f347189c62a579b8cd5f80714efa178f52e461dc2e6d701d264f5ff22e566c"
+dependencies = [
+ "once_cell",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "bs58"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
+
+[[package]]
+name = "bytemuck"
+version = "1.25.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "camino"
+version = "1.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "cc"
+version = "1.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
+
+[[package]]
+name = "chacha20"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "chrono"
+version = "0.4.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
+dependencies = [
+ "iana-time-zone",
+ "num-traits",
+ "serde",
+ "windows-link",
+]
+
+[[package]]
+name = "cobs"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "core-graphics-types"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "libc",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "darling"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
+dependencies = [
+ "darling_core 0.20.11",
+ "darling_macro 0.20.11",
+]
+
+[[package]]
+name = "darling"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
+dependencies = [
+ "darling_core 0.23.0",
+ "darling_macro 0.23.0",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
+dependencies = [
+ "darling_core 0.20.11",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
+dependencies = [
+ "darling_core 0.23.0",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "defmt"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
+dependencies = [
+ "bitflags 1.3.2",
+ "defmt-macros",
+]
+
+[[package]]
+name = "defmt-macros"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
+dependencies = [
+ "defmt-parser",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "defmt-parser"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
+name = "der"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
+dependencies = [
+ "const-oid",
+ "pem-rfc7468",
+ "zeroize",
+]
+
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_builder"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
+dependencies = [
+ "derive_builder_macro",
+]
+
+[[package]]
+name = "derive_builder_core"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
+dependencies = [
+ "darling 0.20.11",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "derive_builder_macro"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
+dependencies = [
+ "derive_builder_core",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "derive_more"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 2.0.119",
+ "unicode-xid",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "const-oid",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "dirs"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "docker-generate"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf673e0848ef09fa4aeeba78e681cf651c0c7d35f76ee38cec8e55bc32fa111"
+
+[[package]]
+name = "downcast-rs"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
+
+[[package]]
+name = "duplicate"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e92f10a49176cbffacaedabfaa11d51db1ea0f80a83c26e1873b43cd1742c24"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "proc-macro2-diagnostics",
+]
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
+
+[[package]]
+name = "educe"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417"
+dependencies = [
+ "enum-ordinalize",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "either"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
+
+[[package]]
+name = "elf"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b"
+
+[[package]]
+name = "embedded-io"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
+
+[[package]]
+name = "embedded-io"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "enum-ordinalize"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677"
+dependencies = [
+ "enum-ordinalize-derive",
+]
+
+[[package]]
+name = "enum-ordinalize-derive"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a65863d15a4ce2888bd2f0f543cc963d3879c3a022c8ee43f6141d479a3ac815"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
+
+[[package]]
+name = "futures-io"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
+
+[[package]]
+name = "futures-task"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
+
+[[package]]
+name = "futures-util"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
+dependencies = [
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi 5.3.0",
+ "wasip2",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "r-efi 6.0.0",
+ "rand_core 0.10.1",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "allocator-api2",
+ "foldhash",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
+name = "hashlink"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
+dependencies = [
+ "hashbrown 0.15.5",
+]
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hex-literal"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
+
+[[package]]
+name = "http"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
+dependencies = [
+ "bytes",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "hyper"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+ "webpki-roots",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "utf8_iter",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
+
+[[package]]
+name = "icu_properties"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
+
+[[package]]
+name = "icu_provider"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "include_bytes_aligned"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ee796ad498c8d9a1d68e477df8f754ed784ef875de1414ebdaf169f70a6a784"
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+ "serde",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.17.1",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
+
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
+[[package]]
+name = "jiff"
+version = "0.2.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc"
+dependencies = [
+ "defmt",
+ "jiff-core",
+ "jiff-static",
+ "jiff-tzdb-platform",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde_core",
+ "windows-link",
+]
+
+[[package]]
+name = "jiff-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09"
+dependencies = [
+ "defmt",
+]
+
+[[package]]
+name = "jiff-static"
+version = "0.2.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204"
+dependencies = [
+ "jiff-core",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "jiff-tzdb"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e"
+
+[[package]]
+name = "jiff-tzdb-platform"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8"
+dependencies = [
+ "jiff-tzdb",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "keccak"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
+dependencies = [
+ "cpufeatures 0.2.17",
+]
+
+[[package]]
+name = "lazy-regex"
+version = "3.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4994ba703f78b083e2f7946dac9251abd83fd43a0365f030e99b69be5b4b9ef9"
+dependencies = [
+ "lazy-regex-proc_macros",
+ "once_cell",
+ "regex",
+]
+
+[[package]]
+name = "lazy-regex-proc_macros"
+version = "3.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd97232314824e6dbef1918a871bb93f51070455e3715bf26e19a6d01aa977a0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "regex",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+dependencies = [
+ "spin",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.189"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+
+[[package]]
+name = "libm"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
+
+[[package]]
+name = "libredox"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
+name = "litemap"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
+
+[[package]]
+name = "log"
+version = "0.4.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
+
+[[package]]
+name = "lru-slab"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "maybe-async"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "memchr"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
+name = "merlin"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d"
+dependencies = [
+ "byteorder",
+ "keccak",
+ "rand_core 0.6.4",
+ "zeroize",
+]
+
+[[package]]
+name = "metal"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21"
+dependencies = [
+ "bitflags 2.13.1",
+ "block",
+ "core-graphics-types",
+ "foreign-types",
+ "log",
+ "objc",
+ "paste",
+]
+
+[[package]]
+name = "mio"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "no_std_strings"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5b0c77c1b780822bc749a33e39aeb2c07584ab93332303babeabb645298a76e"
+
+[[package]]
+name = "num-bigint"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint-dig"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
+dependencies = [
+ "lazy_static",
+ "libm",
+ "num-integer",
+ "num-iter",
+ "num-traits",
+ "rand 0.8.5",
+ "smallvec",
+ "zeroize",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
+
+[[package]]
+name = "num-integer"
+version = "0.1.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
+dependencies = [
+ "num_enum_derive",
+ "rustversion",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pem-rfc7468"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
+dependencies = [
+ "base64ct",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
+name = "pkcs1"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
+dependencies = [
+ "der",
+ "pkcs8",
+ "spki",
+]
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "portable-atomic"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
+
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
+dependencies = [
+ "portable-atomic",
+]
+
+[[package]]
+name = "postcard"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
+dependencies = [
+ "cobs",
+ "embedded-io 0.4.0",
+ "embedded-io 0.6.1",
+ "serde",
+]
+
+[[package]]
+name = "potential_utf"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
+dependencies = [
+ "toml_edit 0.25.13+spec-1.1.0",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "proc-macro2-diagnostics"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "version_check",
+]
+
+[[package]]
+name = "proptest"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
+dependencies = [
+ "bitflags 2.13.1",
+ "num-traits",
+ "rand 0.9.5",
+ "rand_chacha 0.9.0",
+ "rand_xorshift",
+ "unarray",
+]
+
+[[package]]
+name = "prost"
+version = "0.13.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
+dependencies = [
+ "bytes",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.13.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
+dependencies = [
+ "anyhow",
+ "itertools 0.14.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "quinn"
+version = "0.11.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2",
+ "thiserror",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83"
+dependencies = [
+ "bytes",
+ "getrandom 0.4.3",
+ "lru-slab",
+ "rand 0.10.2",
+ "rand_pcg",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
+dependencies = [
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2",
+ "tracing",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
+dependencies = [
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "rand"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
+dependencies = [
+ "chacha20",
+ "getrandom 0.4.3",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.17",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
+
+[[package]]
+name = "rand_core"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+
+[[package]]
+name = "rand_pcg"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
+dependencies = [
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
+dependencies = [
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
+dependencies = [
+ "getrandom 0.2.17",
+ "libredox",
+ "thiserror",
+]
+
+[[package]]
+name = "ref-cast"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3"
+dependencies = [
+ "ref-cast-impl",
+]
+
+[[package]]
+name = "ref-cast-impl"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "regex"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
+
+[[package]]
+name = "reqwest"
+version = "0.12.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "percent-encoding",
+ "pin-project-lite",
+ "quinn",
+ "rustls",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-rustls",
+ "tokio-util",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-streams",
+ "web-sys",
+ "webpki-roots",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.17",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "risc0-binfmt"
+version = "3.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d836c6ad82f4ced7c61d5feedf905a17780312e393aa681d29cc0bbc5131672b"
+dependencies = [
+ "anyhow",
+ "borsh",
+ "bytemuck",
+ "derive_more",
+ "elf",
+ "lazy_static",
+ "postcard",
+ "rand 0.9.5",
+ "risc0-zkp",
+ "risc0-zkvm-platform",
+ "ruint",
+ "semver",
+ "serde",
+ "tracing",
+]
+
+[[package]]
+name = "risc0-build"
+version = "3.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd8216cdd9f573808a94769767480b06ad1e74ae60841c9582fdf51b8e29ba53"
+dependencies = [
+ "anyhow",
+ "cargo_metadata",
+ "derive_builder",
+ "dirs",
+ "docker-generate",
+ "hex",
+ "risc0-binfmt",
+ "risc0-zkos-v1compat",
+ "risc0-zkp",
+ "risc0-zkvm-platform",
+ "rzup",
+ "semver",
+ "serde",
+ "serde_json",
+ "stability",
+ "tempfile",
+]
+
+[[package]]
+name = "risc0-circuit-keccak"
+version = "4.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c731e12429eb4457e1ddc69c56ee7343a1e10b86e4aa55bc8f4d2b13734abb9"
+dependencies = [
+ "anyhow",
+ "bytemuck",
+ "paste",
+ "risc0-binfmt",
+ "risc0-circuit-recursion",
+ "risc0-core",
+ "risc0-zkp",
+ "tracing",
+]
+
+[[package]]
+name = "risc0-circuit-recursion"
+version = "4.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40dd640122abcc67d4d4e4f055c68cbc3ad2efb8589c65c2b23d354632971b60"
+dependencies = [
+ "anyhow",
+ "bytemuck",
+ "hex",
+ "metal",
+ "risc0-core",
+ "risc0-zkp",
+ "tracing",
+]
+
+[[package]]
+name = "risc0-circuit-rv32im"
+version = "4.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb11231aa4b74bcc0c8d16597893fbd7ea6f6a9ebbc35e16bfd06b467c7ee104"
+dependencies = [
+ "anyhow",
+ "bit-vec",
+ "bytemuck",
+ "derive_more",
+ "paste",
+ "risc0-binfmt",
+ "risc0-core",
+ "risc0-zkp",
+ "serde",
+ "tracing",
+]
+
+[[package]]
+name = "risc0-core"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6eb2d2b2c6cac0e43cbb2202daacee1a2f24d0dfa03fd08887a11dc6defdcc1"
+dependencies = [
+ "bytemuck",
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "risc0-groth16"
+version = "3.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0ca702ea7d0162766defe7ed6a79bda4a747ad9e2684000a6edd14df0a6d1f3"
+dependencies = [
+ "anyhow",
+ "ark-bn254",
+ "ark-ec",
+ "ark-ff",
+ "ark-groth16",
+ "ark-serialize",
+ "bytemuck",
+ "hex",
+ "num-bigint",
+ "num-traits",
+ "risc0-binfmt",
+ "risc0-zkp",
+ "serde",
+]
+
+[[package]]
+name = "risc0-zkos-v1compat"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8b0b598ba7946354b10ca5c56e382de801e6c7fce9fccad0396ec436bc5072b"
+dependencies = [
+ "include_bytes_aligned",
+ "no_std_strings",
+ "risc0-zkvm-platform",
+]
+
+[[package]]
+name = "risc0-zkp"
+version = "3.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21c0c921e5e2d44197940d387a45e29c6165e318b5a168fdfdbd50f50ba03678"
+dependencies = [
+ "anyhow",
+ "blake2",
+ "borsh",
+ "bytemuck",
+ "cfg-if",
+ "digest",
+ "hex",
+ "hex-literal",
+ "metal",
+ "paste",
+ "rand_core 0.9.5",
+ "risc0-core",
+ "risc0-zkvm-platform",
+ "serde",
+ "sha2",
+ "stability",
+ "tracing",
+]
+
+[[package]]
+name = "risc0-zkvm"
+version = "3.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5d4f24ec767f71a1663a4d24cf9d02b6bfee44c64647cae677227817051007a"
+dependencies = [
+ "anyhow",
+ "bincode",
+ "bonsai-sdk",
+ "borsh",
+ "bytemuck",
+ "bytes",
+ "derive_more",
+ "hex",
+ "lazy-regex",
+ "prost",
+ "risc0-binfmt",
+ "risc0-build",
+ "risc0-circuit-keccak",
+ "risc0-circuit-recursion",
+ "risc0-circuit-rv32im",
+ "risc0-core",
+ "risc0-groth16",
+ "risc0-zkos-v1compat",
+ "risc0-zkp",
+ "risc0-zkvm-platform",
+ "rrs-lib",
+ "rzup",
+ "semver",
+ "serde",
+ "sha2",
+ "stability",
+ "tempfile",
+ "tracing",
+]
+
+[[package]]
+name = "risc0-zkvm-platform"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2eb37a97ff7e8e4ee1b2a1c43ec143b4887759883c343507af9e4787a57914cd"
+dependencies = [
+ "bytemuck",
+ "cfg-if",
+ "getrandom 0.2.17",
+ "getrandom 0.3.4",
+ "libm",
+ "num_enum",
+ "paste",
+ "stability",
+]
+
+[[package]]
+name = "rmp"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "rmp-serde"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db"
+dependencies = [
+ "byteorder",
+ "rmp",
+ "serde",
+]
+
+[[package]]
+name = "rrs-lib"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4382d3af3a4ebdae7f64ba6edd9114fff92c89808004c4943b393377a25d001"
+dependencies = [
+ "downcast-rs",
+ "paste",
+]
+
+[[package]]
+name = "rsa"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
+dependencies = [
+ "const-oid",
+ "digest",
+ "num-bigint-dig",
+ "num-integer",
+ "num-traits",
+ "pkcs1",
+ "pkcs8",
+ "rand_core 0.6.4",
+ "signature",
+ "spki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "ruint"
+version = "1.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5e99bff0393163bb25029a6af25d3d8d202ba5b5438a74d1bd8789f5c822970"
+dependencies = [
+ "borsh",
+ "proptest",
+ "rand 0.8.5",
+ "rand 0.9.5",
+ "ruint-macro",
+ "serde_core",
+ "valuable",
+ "zeroize",
+]
+
+[[package]]
+name = "ruint-macro"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18"
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags 2.13.1",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
+dependencies = [
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
+dependencies = [
+ "web-time",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
+
+[[package]]
+name = "ryu"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+
+[[package]]
+name = "rzup"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96909a7ea8fdf7e18da727d7facbc43eea8a4f77635e7ec75a69794dede16fb6"
+dependencies = [
+ "hex",
+ "rsa",
+ "semver",
+ "serde",
+ "serde_with",
+ "sha2",
+ "strum",
+ "tempfile",
+ "thiserror",
+ "toml",
+ "yaml-rust2",
+]
+
+[[package]]
+name = "schemars"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+dependencies = [
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.145"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_with"
+version = "3.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a"
+dependencies = [
+ "base64",
+ "bs58",
+ "chrono",
+ "hex",
+ "indexmap 1.9.3",
+ "indexmap 2.14.0",
+ "jiff",
+ "schemars 0.9.0",
+ "schemars 1.2.2",
+ "serde_core",
+ "serde_json",
+ "serde_with_macros",
+ "time",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "3.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46"
+dependencies = [
+ "darling 0.23.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.2.17",
+ "digest",
+]
+
+[[package]]
+name = "shlex"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "digest",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "smallvec"
+version = "1.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
+
+[[package]]
+name = "socket2"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
+
+[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "stability"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac"
+dependencies = [
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "strum"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
+dependencies = [
+ "strum_macros",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
+dependencies = [
+ "fastrand",
+ "getrandom 0.4.3",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "time"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
+dependencies = [
+ "deranged",
+ "num-conv",
+ "powerfmt",
+ "serde_core",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
+
+[[package]]
+name = "time-macros"
+version = "0.2.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "socket2",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime 0.6.11",
+ "toml_edit 0.22.27",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "1.1.1+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.22.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
+dependencies = [
+ "indexmap 2.14.0",
+ "serde",
+ "serde_spanned",
+ "toml_datetime 0.6.11",
+ "toml_write",
+ "winnow 0.7.15",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.25.13+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
+dependencies = [
+ "indexmap 2.14.0",
+ "toml_datetime 1.1.1+spec-1.1.0",
+ "toml_parser",
+ "winnow 1.0.4",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.1.3+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
+dependencies = [
+ "winnow 1.0.4",
+]
+
+[[package]]
+name = "toml_write"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
+
+[[package]]
+name = "tower"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
+dependencies = [
+ "bitflags 2.13.1",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "url",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71"
+dependencies = [
+ "tracing-core",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "typenum"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
+
+[[package]]
+name = "unarray"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "url"
+version = "2.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "valuable"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "vstd-zk-host"
+version = "0.1.0"
+dependencies = [
+ "hex",
+ "rand 0.8.5",
+ "risc0-zkvm",
+ "rmp-serde",
+ "serde",
+ "serde_json",
+ "vstd-zk-methods",
+ "vstd-zk-types",
+]
+
+[[package]]
+name = "vstd-zk-methods"
+version = "0.1.0"
+dependencies = [
+ "risc0-build",
+]
+
+[[package]]
+name = "vstd-zk-types"
+version = "0.1.0"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.4+wasi-0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wasm-streams"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
+dependencies = [
+ "futures-util",
+ "js-sys",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "winnow"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "winnow"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.57.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
+
+[[package]]
+name = "writeable"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
+
+[[package]]
+name = "yaml-rust2"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9"
+dependencies = [
+ "arraydeque",
+ "encoding_rs",
+ "hashlink",
+]
+
+[[package]]
+name = "yoke"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "zerotrie"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
diff --git a/examples/zizk_artifact_first/risc0/Cargo.toml b/examples/zizk_artifact_first/risc0/Cargo.toml
new file mode 100644
index 0000000..8f57aea
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/Cargo.toml
@@ -0,0 +1,11 @@
+[workspace]
+resolver = "2"
+members = ["host", "methods", "types"]
+
+# RISC Zero guest builds are prohibitively slow without optimization.
+[profile.dev]
+opt-level = 3
+
+[profile.release]
+debug = 1
+lto = true
diff --git a/examples/zizk_artifact_first/risc0/README.md b/examples/zizk_artifact_first/risc0/README.md
new file mode 100644
index 0000000..936bd7a
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/README.md
@@ -0,0 +1,213 @@
+# Proof-carrying reference mechanism for Verifier Standard (VSTD)
+
+> **Acronyms:** gigabyte (GB); identifier (ID); random-access memory (RAM); reduced instruction set computer (RISC);
+> RISC Zero (RISC0); software development kit (SDK); Secure Hash Algorithm 256-bit (SHA-256);
+> scalable transparent argument of knowledge (STARK); Windows Subsystem for Linux 2 (WSL2);
+> zero-knowledge virtual machine (zkVM).
+
+**Status:** bounded reference mechanism under VSTD's governing
+zero-identity/zero-knowledge (ZIZK) artifact-first architecture; not a numbered VSTD
+profile, serialized receipt identifier, conformance result, or compatibility promise. The proof backend is optional;
+the artifact-first and zero-actor-trust architecture is not.
+
+This directory answers one narrow question: can a prover show that a hidden, bounded
+evidence payload satisfies a fixed predicate while publishing enough authenticated
+coordinates for another party to verify the proof? It is a cryptographic zero-knowledge
+enclosure around VSTD's architectural zero-knowledge starting rule: no unevidenced
+proposition is presumed, and the proof earns only its exact artifact-bound result. The
+enclosure binds the program, predicate, commitments, output, parameters, and verifier
+without importing prover identity into TRUST, VSTD's formal name for mechanism-earned
+artifact support. It does not make all VSTD receipts
+cryptographically zero knowledge. Existing full-disclosure receipts remain valid and
+unchanged.
+
+## Selected system
+
+The reference mechanism selects exactly one proof system: **RISC Zero zkVM 3.0.6**, using its
+local composite STARK receipt. The selection is pinned in every Cargo manifest and in
+`Cargo.lock`.
+
+Reasons for selection:
+
+- the official SDK describes a `Receipt` as a zero-knowledge proof of execution;
+- `Receipt::verify` checks successful execution, the expected image ID, and journal
+ integrity;
+- arbitrary Rust guest code can express the bounded predicate without designing a
+ new arithmetic circuit;
+- the composite STARK path uses transparent setup rather than a mechanism-specific
+ trusted ceremony; and
+- the documented local prover requires at least 16 GB of RAM, which the tested Linux
+ x86-64 environment satisfies.
+
+Primary references:
+
+- [RISC Zero installation](https://dev.risczero.com/api/zkvm/install)
+- [RISC Zero real-proof quick start](https://dev.risczero.com/api/zkvm/quickstart)
+- [`Receipt` verification contract](https://docs.rs/risc0-zkvm/3.0.6/risc0_zkvm/struct.Receipt.html)
+- [`DevModeProver` warning](https://docs.rs/risc0-zkvm/3.0.6/risc0_zkvm/struct.DevModeProver.html)
+- [RISC Zero proof-system analysis](https://dev.risczero.com/proof-system-in-detail.pdf)
+
+The host crate enables `disable-dev-mode`. It also rejects the `Fake` receipt variant
+and refuses a truthy `RISC0_DEV_MODE` setting. Development-mode output cannot satisfy
+this reference mechanism.
+
+## Statement, witness, and public output
+
+The fixed predicate is defined byte-for-byte by `PREDICATE_TEXT` in the shared types
+crate. A successful proof establishes that one private input accepted by the pinned
+guest program contained:
+
+- a nonempty evidence byte string no longer than 64 bytes;
+- a mechanism-local `Supported` input tag rather than `Unknown` or `Conflicted`;
+- a private measurement at least as large as the public threshold; and
+- a private 32-byte salt used in the evidence commitment.
+
+The private witness consists of the evidence bytes, salt, measurement, and candidate
+state. The authenticated public journal contains:
+
+- SHA-256 digests of the historical mechanism profile and exact predicate text;
+- subject and policy digests;
+- a public challenge;
+- the public threshold;
+- a salted commitment to the private evidence, length, and measurement; and
+- the Boolean result of the fixed predicate.
+
+The RISC Zero image ID is the program trust coordinate. The verifier supplies or uses
+the compiled expected image ID; it does not trust the convenience metadata in
+`public.json`. RISC Zero receipt metadata is not cryptographically bound and is not an
+acceptance input here.
+
+Canonical evidence commitment input:
+
+`UTF8` means Unicode Transformation Format, 8-bit (UTF-8) encoding; `U32_BE` and
+`U64_BE` mean unsigned 32-bit and unsigned 64-bit big-endian encoding.
+
+```text
+UTF8("vstd-zk-evidence-commitment-v1\\0")
+|| U32_BE(evidence_length)
+|| evidence_bytes
+|| salt_32_bytes
+|| U64_BE(private_measurement)
+```
+
+The commitment is SHA-256 of those bytes. The journal itself is encoded by the pinned
+RISC Zero serde codec and authenticated by the receipt.
+
+## Platform and pinned setup
+
+The tested platform is Linux x86-64 under WSL2. The RISC Zero documentation lists
+x86-64 Linux as a supported installer target. The selected components are:
+
+```text
+rzup 0.5.0
+cargo-risczero 3.0.6
+r0vm 3.0.6
+RISC Zero Rust guest toolchain 1.97.0-dev
+risc0-zkvm 3.0.6
+```
+
+Install the official tool manager and then the pinned components:
+
+```bash
+curl --proto '=https' --tlsv1.2 -fsSL https://risczero.com/install -o /tmp/rzup-install.sh
+bash /tmp/rzup-install.sh
+export PATH="$HOME/.risc0/bin:$HOME/.cargo/bin:$PATH"
+rzup install cargo-risczero 3.0.6
+rzup install r0vm 3.0.6
+rzup install rust 1.97.0
+rzup default cargo-risczero 3.0.6
+rzup default r0vm 3.0.6
+rzup default rust 1.97.0
+```
+
+No dependency from this Rust workspace is added to the `verifier-standard` Python
+distribution.
+
+## Verify the recorded public proof artifact
+
+The exact non-secret artifacts from the recorded run are tracked under
+[`recorded-proof/`](recorded-proof/):
+
+| Artifact | Bytes | Secure Hash Algorithm 256-bit (SHA-256) |
+|---|---:|---|
+| `receipt.msgpack` | 301835 | `04813c4757ba4efbdad9d51d50d7402f3a98f6c23e53b9b58cce8af12ef9caa2` |
+| `public.json` | 2590 | `188098e6ba1ac940475f15e0a4304ff08d678d98a9ed708dbe41dc6dde596b76` |
+| `self-test-results.json` | 377 | `e4c1bff21fb6161221276157fa96af6661af8635da35970ba12e462881f2c6fe` |
+
+The private witness and salt are not tracked and are not required for verification.
+After installing the pinned toolchain and obtaining the locked Cargo dependencies, run
+this command from this directory:
+
+```bash
+./scripts/verify_recorded_proof.sh
+```
+
+The script first builds the tracked guest with the locked toolchain and requires its
+image ID to equal the recorded proof's image ID. It then executes the direct verifier:
+
+```bash
+export PATH="$HOME/.risc0/bin:$HOME/.cargo/bin:$PATH"
+export CARGO_TARGET_DIR="${HOME}/.cache/vstd-zk-target"
+export RISC0_DEV_MODE=0
+EXPECTED_IMAGE_ID="91df751f5764f81ba4995994afb43e87928dc32d23c81799c767794c27eabcff"
+ACTUAL_IMAGE_ID="$(cargo run --locked --release -q -p vstd-zk-host -- image-id)"
+test "${ACTUAL_IMAGE_ID}" = "${EXPECTED_IMAGE_ID}"
+cargo run --locked --release -p vstd-zk-host -- \
+ verify recorded-proof/receipt.msgpack recorded-proof/public.json \
+ "${EXPECTED_IMAGE_ID}"
+```
+
+The final argument is the exact RISC Zero guest image identifier recorded by the
+public envelope. The preceding comparison binds that identifier to the program built
+from this checkout's tracked source and lock files. It is an explicit program trust
+coordinate, not actor identity or reputation. The host verifier can separately accept an
+explicit historical image ID, but that direct operation alone does not establish that the
+current source builds the historical program. To require Cargo to use only an already
+populated local cache, run
+`CARGO_NET_OFFLINE=true ./scripts/verify_recorded_proof.sh`.
+
+The expected successful output is:
+
+```text
+PASS: real RISC Zero receipt and public statement verified
+```
+
+## Reproduce the proof and negative tests
+
+From this directory in the supported Linux environment:
+
+```bash
+export PATH="$HOME/.risc0/bin:$HOME/.cargo/bin:$PATH"
+export CARGO_TARGET_DIR="${HOME}/.cache/vstd-zk-target"
+export RISC0_DEV_MODE=0
+cargo run --locked --release -p vstd-zk-host -- self-test local-artifacts/self-test
+```
+
+The self-test produces one real receipt, verifies it, and then exercises the negative
+fixtures described in `fixtures/README.md`. Generated receipts and private inputs are
+ignored by Git.
+
+For a separate prove/verify flow:
+
+```bash
+mkdir -p local-artifacts/manual
+cargo run --locked --release -p vstd-zk-host -- \
+ generate-inputs local-artifacts/private-witness.json local-artifacts/manual/statement.json
+cargo run --locked --release -p vstd-zk-host -- \
+ prove local-artifacts/private-witness.json local-artifacts/manual/statement.json \
+ local-artifacts/manual/receipt.msgpack local-artifacts/manual/public.json
+rm local-artifacts/private-witness.json
+cargo run --locked --release -p vstd-zk-host -- \
+ verify local-artifacts/manual/receipt.msgpack local-artifacts/manual/public.json
+```
+
+The last command is the verifier path. It needs the receipt, public envelope, pinned
+verifier implementation, and expected image ID. It does not need the private witness or
+a network service; Cargo itself may need the network until the locked dependencies and
+toolchain have been installed or cached.
+
+## Interpretation
+
+The reference mechanism provides a concrete cryptographic privacy option for one bounded
+predicate. It does not establish that zero knowledge should be mandatory for VSTD.
+See `CLAIM_BOUNDARY.md` and `THREAT_MODEL.md` before making any public claim.
diff --git a/examples/zizk_artifact_first/risc0/ROUND1_ZERO_KNOWLEDGE_REPORT.md b/examples/zizk_artifact_first/risc0/ROUND1_ZERO_KNOWLEDGE_REPORT.md
new file mode 100644
index 0000000..1872826
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/ROUND1_ZERO_KNOWLEDGE_REPORT.md
@@ -0,0 +1,234 @@
+# Recorded reduced instruction set computer (RISC) Zero proof-mechanism report
+
+> **Acronyms:** gigabyte (GB); identifier (ID); random-access memory (RAM); reduced instruction set computer (RISC);
+> RISC Zero (RISC0); random number generator (RNG); software development kit (SDK);
+> Secure Hash Algorithm 256-bit (SHA-256); scalable transparent argument of knowledge (STARK);
+> Verifier Standard (VSTD); Windows Subsystem for Linux 2 (WSL2); zero-identity/zero-knowledge (ZIZK);
+> zero-knowledge virtual machine (zkVM).
+
+**Original run:** 2026-08-23
+**Recorded artifact refresh:** 2026-08-31
+**Status:** completed real-proof run refreshed against the tracked guest image; non-secret
+proof artifacts tracked as a bounded reference mechanism; no VSTD receipt mapping
+
+## Repository coordinates
+
+- Repository: `TimeLordRaps/verifier`
+- Original development base: `598c545be3833d6d81bb7e252ca5837f3bb2a449`
+- Regeneration source commit: `e9d2b13eb22342934789bf94ee894bb5faed6d98`
+- Regeneration source tree: `6fcf0a4d391815f7218a61fb32ca6bac0b71df63`
+- Branch: `codex/post-1.2-professionalization`
+- Worktree: the pull-request worktree; its machine-specific absolute path is
+ intentionally excluded from this public report
+- Existing serialized receipt identifiers modified: no
+- The proof command performed no Git or network publication action. No merge, tag,
+ release, or publication was performed.
+
+## Selected proof system
+
+Exactly one proof system was selected and used:
+
+| Coordinate | Value |
+|---|---|
+| SDK and verifier | RISC Zero zkVM `3.0.6` |
+| Receipt kind | composite STARK |
+| Program trust coordinate | RISC Zero image ID |
+| Image ID | `91df751f5764f81ba4995994afb43e87928dc32d23c81799c767794c27eabcff` |
+| Tool manager | `rzup 0.5.0` |
+| Prover executable | `r0vm 3.0.6` |
+| Guest build tool | `cargo-risczero 3.0.6` |
+| Guest Rust toolchain | `rustc 1.97.0-dev` |
+| Tested platform | Linux x86-64 under WSL2 |
+| Trusted setup | transparent STARK setup; no experiment-specific ceremony |
+
+The official installer script used in the local environment had SHA-256
+`5699878af779351ec0f931fa84c3d5e35263279f66bd915af225f530a77341bf`.
+The experiment pins every direct Rust dependency and commits both host and guest lock
+files:
+
+- workspace `Cargo.lock`: `9b6f1a739c2acbe01581828fa37691af7288adb4642d158cb5f6a7383470483d`
+- guest `Cargo.lock`: `1c1ef45133eb24090dfc136a479c1e007b0d2a6bab9b9ae0954a25f03dea27e9`
+
+No alternative proof system was attempted.
+
+## Selection basis
+
+RISC Zero was selected because its official 3.0 documentation supports local real-proof
+generation on x86-64 Linux, describes `Receipt` as a zero-knowledge proof of execution,
+binds verification to an image ID and authenticated journal, and provides a transparent
+STARK path. The local environment had more than the documented 16 GB minimum RAM.
+
+The host crate compiles with `disable-dev-mode`, rejects `InnerReceipt::Fake`, requires
+the selected `Composite` receipt variant, and rejects a truthy `RISC0_DEV_MODE` value.
+
+## Proved predicate
+
+The private witness contains:
+
+- one to 64 evidence bytes;
+- a private 32-byte salt;
+- a private measurement; and
+- an experiment-local candidate state.
+
+The fixed guest accepts only an experiment-local `Supported` candidate state and a
+measurement at least as large as the public threshold. It commits an authenticated public
+journal containing the exact profile and predicate digests, subject digest, policy
+digest, challenge, threshold, salted evidence commitment, and satisfied result.
+
+The proof does not establish whether the private input was truthful or whether the
+`Supported` tag was assigned correctly.
+
+## Completeness, soundness, and zero-knowledge basis
+
+### Completeness
+
+One satisfying input produced a receipt that verified against the expected image ID and
+authenticated journal. This is direct implementation evidence for the tested program and
+environment, not a general proof about every possible input or platform.
+
+### Soundness
+
+The soundness basis is the selected RISC Zero STARK construction and its published
+analysis, including the Fiat-Shamir transformation and documented hash assumptions. The
+negative tests below provide implementation-level falsification attempts; they do not
+replace the cryptographic analysis or an independent audit.
+
+### Zero knowledge
+
+The zero-knowledge basis is the RISC Zero protocol and verified non-fake receipt, which
+hide guest execution inputs while exposing the journal. The exact private evidence and
+salt byte strings were additionally scanned against every generated public artifact and
+were absent. That byte scan checks this serializer path only; absence from files alone is
+not a proof of zero knowledge.
+
+## Commands and observed results
+
+Toolchain and build:
+
+```text
+rzup show
+cargo-risczero 3.0.6; r0vm 3.0.6; rust 1.97.0
+
+cargo check --locked --workspace
+PASS
+
+cargo build --locked --release -p vstd-zk-host
+PASS
+
+two clean target directories:
+vstd-zk-host image-id
+91df751f5764f81ba4995994afb43e87928dc32d23c81799c767794c27eabcff
+```
+
+Real proof plus automated negative cases:
+
+```text
+RISC0_DEV_MODE=0 CARGO_NET_OFFLINE=true \
+ ./scripts/run_real_proof.sh
+PASS
+```
+
+Offline verifier invocation without the witness:
+
+```text
+CARGO_NET_OFFLINE=true ./scripts/verify_recorded_proof.sh
+PASS
+```
+
+Repository validation:
+
+```text
+python -m pytest -q
+552 passed, 38 skipped
+
+python scripts/build_experiment_index.py --check
+[EXPERIMENT INDEX OK] manifests and repository artifacts verified
+
+python scripts/build_reference.py --check
+[REFERENCE OK] docs/reference.html matches the implementation
+
+python scripts/check_presentation.py
+[PRESENTATION OK] links, accessibility, versions, boundaries, paths, maturity, transient
+status, visual assets, generated reference, experiment index, acronym expansion, and
+structural terminology
+
+python -m compileall -q src scripts
+PASS
+```
+
+The three guest panic messages printed during self-test are the expected rejection paths
+for below-threshold, `Unknown`, and `Conflicted` inputs. They do not contain witness bytes.
+
+## Negative-test results
+
+| Test | Result |
+|---|---|
+| valid proof and matching public inputs | pass |
+| below-threshold private measurement | rejected |
+| experiment-local `Unknown` input | rejected |
+| experiment-local `Conflicted` input | rejected |
+| mutated public threshold | rejected |
+| wrong image ID | rejected |
+| corrupted proof bytes | rejected |
+| tampered authenticated journal | rejected |
+| subject and challenge transplantation | rejected |
+| private evidence or salt copied to public artifacts | not detected; test passed |
+
+All ten recorded Boolean checks were `true`.
+
+## Recorded public artifacts
+
+The exact receipt, public envelope, and self-test result are tracked under
+[`recorded-proof/`](recorded-proof/) so a consumer can verify the recorded run rather than
+only generating a new proof. The governed offline command first requires the locked build
+of the tracked guest to reproduce the recorded image ID. The ephemeral private witness
+and salt remain excluded.
+
+| Artifact | Bytes | SHA-256 |
+|---|---:|---|
+| `receipt.msgpack` | 301835 | `04813c4757ba4efbdad9d51d50d7402f3a98f6c23e53b9b58cce8af12ef9caa2` |
+| `public.json` | 2590 | `188098e6ba1ac940475f15e0a4304ff08d678d98a9ed708dbe41dc6dde596b76` |
+| `self-test-results.json` | 377 | `e4c1bff21fb6161221276157fa96af6661af8635da35970ba12e462881f2c6fe` |
+| `corrupted-receipt.msgpack` | 301835 | `a37fbceb5cd234a991deb8c530d29551534fe04be881defa716d9fdcf1cbcf1f` |
+| `tampered-journal.msgpack` | 301835 | `e210702b95557d76d4a2c285b14c738f4ddb7160017e92ae082dd6d9ef48b4f8` |
+| `mutated-public.json` | 2590 | `c04651e41f50682bdae40049bdd73084d1ac2a98816f8e051ace7ed3ca24d360` |
+| `transplanted-public.json` | 2590 | `997ac9107ab917838517acddbaf2bf2d01ad074e4c08b01e251b1bb3d7dc1db7` |
+
+## Unresolved assumptions
+
+- The selected cryptographic implementation and transitive dependencies were not
+ independently audited in this work.
+- Two clean builds in the same recorded WSL2 environment produced the same image ID. A
+ build on an independent host and an independent implementation remain unavailable.
+- The host, compiler, installer, and operating system remain trusted for witness secrecy.
+- The experiment does not establish constant-time or side-channel-resistant proving.
+- The challenge is cryptographically bound, but challenge issuance, expiry, uniqueness,
+ and replay storage are external.
+- Salt quality is generated from the host operating-system RNG but is not itself proved.
+- The public subject and policy digests need external resolution and provenance rules.
+- A private `Supported` tag is merely an input to this predicate, not independently
+ established VSTD evidence.
+
+## Public claims currently justified
+
+The local evidence justifies saying that the bounded RISC Zero 3.0.6 reference mechanism
+produced and re-verified a real composite STARK receipt for one bounded hidden-witness
+predicate, with the recorded negative cases rejected, and that two clean builds under the
+same recorded local environment produced its recorded image ID. It does not establish an
+independent build environment, implementation, or distinct prover/verifier actors.
+
+It also supports keeping VSTD core disclosure-neutral: this result demonstrates one
+optional privacy mechanism without requiring or invalidating full-disclosure receipts.
+
+## Claims still prohibited
+
+Do not claim that this experiment proves:
+
+- real-world truth, completeness, provenance, authorization, independence, identity,
+ uniqueness, freshness, revocation, or legal compliance;
+- protection against a malicious or compromised prover host;
+- general zero-knowledge support for every VSTD predicate;
+- independent implementation, third-party audit, external adoption, or production
+ readiness;
+- a frozen `ZIZK-VSTD` wire profile; or
+- that zero knowledge should be mandatory for VSTD.
diff --git a/examples/zizk_artifact_first/risc0/THREAT_MODEL.md b/examples/zizk_artifact_first/risc0/THREAT_MODEL.md
new file mode 100644
index 0000000..8cfba7a
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/THREAT_MODEL.md
@@ -0,0 +1,98 @@
+# Threat model
+
+> **Acronyms:** Executable and Linkable Format (ELF); identifier (ID); JavaScript Object Notation (JSON);
+> reduced instruction set computer (RISC); Secure Hash Algorithm 256-bit (SHA-256);
+> scalable transparent argument of knowledge (STARK); zero-identity/zero-knowledge (ZIZK);
+> zero-knowledge virtual machine (zkVM).
+
+**Scope:** this bounded optional zero-knowledge proof mechanism only; not the governing
+ZIZK artifact-first architecture as a whole.
+
+## Protected secret
+
+The intended secret is the private witness supplied to the pinned guest: evidence
+bytes, a 32-byte salt, a measurement, and a mechanism-local candidate state. The
+receipt intentionally reveals the public journal. Subject, policy, challenge,
+threshold, predicate result, and salted evidence commitment are not secrets.
+
+## Trust roots
+
+Acceptance depends on all of the following:
+
+1. the expected RISC Zero image ID reproduced from the tracked guest source and locked
+ toolchain by the governed offline command;
+2. RISC Zero zkVM 3.0.6 verification code and its proof-system parameters;
+3. the pinned Rust sources and `Cargo.lock`;
+4. SHA-256 collision and preimage resistance for the public digests;
+5. correct public-statement comparison after receipt verification; and
+6. the governed source-build comparison or another verifier obtaining the expected image
+ ID independently rather than trusting an unbound metadata field supplied by the
+ prover.
+
+The composite STARK uses transparent public setup. Its non-interactive security relies
+on the proof system's Fiat-Shamir construction and its documented hash assumptions.
+This repository does not independently prove the cryptographic reduction.
+
+## Attacks tested
+
+| Attack | Required result |
+|---|---|
+| private measurement below threshold | proof attempt rejected |
+| private `Unknown` candidate state | proof attempt rejected |
+| private `Conflicted` candidate state | proof attempt rejected |
+| mutated public threshold | wrapper verification rejected |
+| different subject or challenge | statement transplantation rejected |
+| wrong image ID | receipt verification rejected |
+| corrupted receipt bytes | decoding or verification rejected |
+| authenticated journal mutation | receipt verification rejected |
+| private byte strings copied to public files | serialization scan rejected |
+
+## Residual risks
+
+### Host compromise and operational leakage
+
+The proof system hides guest inputs from a receipt verifier. It does not protect the
+witness from the prover's operating system, shell history, swap, crash dumps, malware,
+debuggers, or a modified host binary. The manual workflow writes a temporary private
+JSON file and requires the operator to protect and remove it.
+
+### Side channels
+
+The reference mechanism does not claim constant-time host behavior, traffic-analysis resistance,
+or protection from proof-time, memory-use, file-size, power, or hardware side channels.
+The evidence length is hidden by the proof but could be correlated with prover-side
+observations.
+
+### Low-entropy evidence
+
+The public commitment includes a private random 32-byte salt to impede offline guessing.
+Weak or reused salts, disclosure of the salt, or host compromise can make low-entropy
+evidence guessable. The proof does not certify salt quality.
+
+### Replay and freshness
+
+The public challenge is authenticated by the journal, so a proof cannot be transplanted
+to a different challenge without rejection. The same valid proof can still be replayed
+for the same challenge. Challenge issuance, uniqueness, expiry, clock trust, and replay
+storage are outside this mechanism and must remain explicit assumptions or UNKNOWN.
+
+### Parser and denial of service
+
+Receipt and envelope reads have size limits. MessagePack is used because RISC Zero's
+receipt documentation recommends a serde format with depth limits for untrusted input.
+The reference mechanism does not establish a complete resource-exhaustion bound for all malformed
+receipts.
+
+### Supply chain
+
+Version pins and a committed lock file constrain dependencies but do not independently
+audit every transitive crate, compiler binary, installer, or build host. Two clean builds
+under the same recorded Windows Subsystem for Linux 2 environment reproduced the image
+ID; a build on another trusted host and an independent implementation remain unavailable.
+
+### Semantic overreach
+
+A prover selects the private bytes and candidate tag. The proof does not show that those
+bytes are truthful, complete, authorized, fresh, legally valid, independently sourced,
+or causally connected to the real world. It proves only execution of the fixed predicate
+over the committed input.
diff --git a/examples/zizk_artifact_first/risc0/fixtures/README.md b/examples/zizk_artifact_first/risc0/fixtures/README.md
new file mode 100644
index 0000000..eb81d24
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/fixtures/README.md
@@ -0,0 +1,25 @@
+# Generated fixtures
+
+> **Acronym:** scalable transparent argument of knowledge (STARK).
+
+The real-proof self-test creates fixtures under the ignored `local-artifacts/` directory
+instead of committing a reusable private witness or a large proof binary.
+
+Generated positive fixtures:
+
+- `receipt.msgpack` — real composite STARK receipt;
+- `public.json` — authenticated journal plus non-authoritative convenience metadata.
+
+Generated negative fixtures:
+
+- `mutated-public.json` — changed public threshold;
+- `transplanted-public.json` — changed subject and challenge;
+- `corrupted-receipt.msgpack` — corrupted serialized receipt;
+- `tampered-journal.msgpack` — decoded journal changed without regenerating the seal.
+
+Additional negative witnesses are generated only in memory: below-threshold,
+`Unknown`, and `Conflicted`. The self-test requires every negative case to be rejected
+and writes the Boolean results to `self-test-results.json`.
+
+This layout avoids publishing the private witness bytes in a fixture while retaining a
+reproducible generator and verifier.
diff --git a/examples/zizk_artifact_first/risc0/host/Cargo.toml b/examples/zizk_artifact_first/risc0/host/Cargo.toml
new file mode 100644
index 0000000..a93f508
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/host/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "vstd-zk-host"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[dependencies]
+hex = "=0.4.3"
+rand = "=0.8.5"
+risc0-zkvm = { version = "=3.0.6", features = ["disable-dev-mode"] }
+rmp-serde = "=1.3.0"
+serde = { version = "=1.0.228", features = ["derive"] }
+serde_json = "=1.0.145"
+vstd-zk-methods = { path = "../methods" }
+vstd-zk-types = { path = "../types" }
diff --git a/examples/zizk_artifact_first/risc0/host/src/main.rs b/examples/zizk_artifact_first/risc0/host/src/main.rs
new file mode 100644
index 0000000..ad45c0e
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/host/src/main.rs
@@ -0,0 +1,457 @@
+//! Terminology: Executable and Linkable Format (ELF); identifier (ID);
+//! reduced instruction set computer (RISC); RISC Zero (RISC0);
+//! Secure Hash Algorithm 256-bit (SHA-256); scalable transparent argument of knowledge (STARK);
+//! Verifier Standard (VSTD); zero-knowledge (ZK).
+use hex::FromHex;
+use rand::{rngs::OsRng, RngCore};
+use risc0_zkvm::{
+ default_prover,
+ sha::{Digest, Impl, Sha256},
+ ExecutorEnv, InnerReceipt, Receipt,
+};
+use serde::Serialize;
+use std::{
+ env,
+ error::Error,
+ fs,
+ io,
+ path::Path,
+};
+use vstd_zk_methods::{VSTD_ZK_GUEST_ELF, VSTD_ZK_GUEST_ID};
+use vstd_zk_types::{
+ CandidateState, PrivateWitness, ProverInput, PublicEnvelope, PublicJournal,
+ PublicStatement, COMMITMENT_DOMAIN, PREDICATE_TEXT, PROFILE_LABEL,
+};
+
+const PROOF_SYSTEM: &str = "risc0-zkvm-3.0.6-composite-stark";
+const MAX_RECEIPT_BYTES: u64 = 32 * 1024 * 1024;
+const MAX_ENVELOPE_BYTES: u64 = 1024 * 1024;
+
+type AppResult = Result>;
+
+#[derive(Serialize)]
+struct SelfTestResults {
+ real_proof_verified: bool,
+ unsatisfied_witness_rejected: bool,
+ unknown_rejected: bool,
+ conflicted_rejected: bool,
+ mutated_public_input_rejected: bool,
+ wrong_image_id_rejected: bool,
+ corrupted_proof_rejected: bool,
+ tampered_journal_rejected: bool,
+ statement_transplant_rejected: bool,
+ private_bytes_absent_from_public_artifacts: bool,
+}
+
+fn main() {
+ if let Err(error) = run() {
+ eprintln!("error: {error}");
+ std::process::exit(1);
+ }
+}
+
+fn run() -> AppResult<()> {
+ let args: Vec = env::args().collect();
+ match args.get(1).map(String::as_str) {
+ Some("generate-inputs") if args.len() == 4 => {
+ generate_inputs(Path::new(&args[2]), Path::new(&args[3]))
+ }
+ Some("prove") if args.len() == 6 => prove_from_files(
+ Path::new(&args[2]),
+ Path::new(&args[3]),
+ Path::new(&args[4]),
+ Path::new(&args[5]),
+ ),
+ Some("verify") if args.len() == 4 || args.len() == 5 => {
+ let expected_id = args.get(4).map(|value| parse_digest(value)).transpose()?;
+ verify_artifacts(Path::new(&args[2]), Path::new(&args[3]), expected_id)?;
+ println!("PASS: real RISC Zero receipt and public statement verified");
+ Ok(())
+ }
+ Some("image-id") if args.len() == 2 => {
+ println!("{}", method_id());
+ Ok(())
+ }
+ Some("self-test") if args.len() == 3 => self_test(Path::new(&args[2])),
+ _ => Err(usage_error()),
+ }
+}
+
+fn usage_error() -> Box {
+ io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "usage:\n vstd-zk-host generate-inputs PRIVATE.json STATEMENT.json\n vstd-zk-host prove PRIVATE.json STATEMENT.json RECEIPT.bin PUBLIC.json\n vstd-zk-host verify RECEIPT.bin PUBLIC.json [EXPECTED_IMAGE_ID]\n vstd-zk-host image-id\n vstd-zk-host self-test OUTPUT_DIR",
+ )
+ .into()
+}
+
+fn method_id() -> Digest {
+ Digest::from(VSTD_ZK_GUEST_ID)
+}
+
+fn parse_digest(value: &str) -> AppResult {
+ Ok(Digest::from_hex(value)?)
+}
+
+fn digest_bytes(value: &[u8]) -> [u8; 32] {
+ let digest = Impl::hash_bytes(value);
+ digest.as_bytes().try_into().expect("SHA-256 is 32 bytes")
+}
+
+fn digest_hex(value: &[u8]) -> String {
+ hex::encode(digest_bytes(value))
+}
+
+fn evidence_commitment(witness: &PrivateWitness) -> [u8; 32] {
+ let mut input = Vec::with_capacity(
+ COMMITMENT_DOMAIN.len() + 4 + witness.evidence.len() + 32 + 8,
+ );
+ input.extend_from_slice(COMMITMENT_DOMAIN);
+ input.extend_from_slice(&(witness.evidence.len() as u32).to_be_bytes());
+ input.extend_from_slice(&witness.evidence);
+ input.extend_from_slice(&witness.salt);
+ input.extend_from_slice(&witness.measurement.to_be_bytes());
+ digest_bytes(&input)
+}
+
+fn random_array() -> [u8; 32] {
+ let mut value = [0_u8; 32];
+ OsRng.fill_bytes(&mut value);
+ value
+}
+
+fn sample_inputs() -> (PrivateWitness, PublicStatement) {
+ let mut evidence = vec![0_u8; 48];
+ OsRng.fill_bytes(&mut evidence);
+ let witness = PrivateWitness {
+ evidence,
+ salt: random_array(),
+ measurement: 73,
+ candidate_state: CandidateState::Supported,
+ };
+ let statement = PublicStatement {
+ subject_digest: random_array(),
+ policy_digest: digest_bytes(b"vstd-zk-fixed-threshold-policy-v1"),
+ challenge: random_array(),
+ threshold: 70,
+ };
+ (witness, statement)
+}
+
+fn generate_inputs(private_path: &Path, statement_path: &Path) -> AppResult<()> {
+ let (witness, statement) = sample_inputs();
+ write_json(private_path, &witness)?;
+ write_json(statement_path, &statement)?;
+ println!(
+ "generated a local private witness and public statement; do not publish {}",
+ private_path.display()
+ );
+ Ok(())
+}
+
+fn ensure_real_mode() -> AppResult<()> {
+ if let Ok(value) = env::var("RISC0_DEV_MODE") {
+ let normalized = value.trim().to_ascii_lowercase();
+ if !normalized.is_empty() && normalized != "0" && normalized != "false" {
+ return Err(io::Error::new(
+ io::ErrorKind::PermissionDenied,
+ "RISC0_DEV_MODE must be unset, 0, or false; this binary also compiles with disable-dev-mode",
+ )
+ .into());
+ }
+ }
+ Ok(())
+}
+
+fn prove_from_files(
+ private_path: &Path,
+ statement_path: &Path,
+ receipt_path: &Path,
+ public_path: &Path,
+) -> AppResult<()> {
+ ensure_real_mode()?;
+ let witness: PrivateWitness = read_json_bounded(private_path, MAX_ENVELOPE_BYTES)?;
+ let statement: PublicStatement = read_json_bounded(statement_path, MAX_ENVELOPE_BYTES)?;
+ prove_to_files(&witness, &statement, receipt_path, public_path)?;
+ println!("wrote a verified real receipt and public envelope");
+ Ok(())
+}
+
+fn prove_to_files(
+ witness: &PrivateWitness,
+ statement: &PublicStatement,
+ receipt_path: &Path,
+ public_path: &Path,
+) -> AppResult {
+ ensure_real_mode()?;
+ let input = ProverInput {
+ statement: statement.clone(),
+ witness: witness.clone(),
+ };
+ let env = ExecutorEnv::builder().write(&input)?.build()?;
+ let prove_info = default_prover().prove(env, VSTD_ZK_GUEST_ELF)?;
+ let receipt = prove_info.receipt;
+ require_composite_receipt(&receipt)?;
+ receipt.verify(method_id())?;
+
+ let journal: PublicJournal = receipt.journal.decode()?;
+ validate_public_journal(&journal, statement)?;
+ if journal.evidence_commitment != evidence_commitment(witness) {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "authenticated evidence commitment does not match the supplied witness",
+ )
+ .into());
+ }
+
+ let receipt_bytes = rmp_serde::to_vec_named(&receipt)?;
+ let envelope = PublicEnvelope {
+ experiment_profile: String::from_utf8(PROFILE_LABEL.to_vec())?,
+ proof_system: PROOF_SYSTEM.to_string(),
+ image_id: method_id().to_string(),
+ receipt_sha256: digest_hex(&receipt_bytes),
+ receipt_size: receipt_bytes.len() as u64,
+ journal,
+ };
+ write_bytes(receipt_path, &receipt_bytes)?;
+ write_json(public_path, &envelope)?;
+ Ok(envelope)
+}
+
+fn require_composite_receipt(receipt: &Receipt) -> AppResult<()> {
+ match &receipt.inner {
+ InnerReceipt::Composite(_) => Ok(()),
+ InnerReceipt::Fake(_) => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "fake RISC Zero receipt rejected",
+ )
+ .into()),
+ _ => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "receipt kind differs from the selected composite STARK path",
+ )
+ .into()),
+ }
+}
+
+fn validate_public_journal(
+ journal: &PublicJournal,
+ expected: &PublicStatement,
+) -> AppResult<()> {
+ if journal.profile_digest != digest_bytes(PROFILE_LABEL)
+ || journal.predicate_digest != digest_bytes(PREDICATE_TEXT)
+ || journal.subject_digest != expected.subject_digest
+ || journal.policy_digest != expected.policy_digest
+ || journal.challenge != expected.challenge
+ || journal.threshold != expected.threshold
+ || !journal.predicate_satisfied
+ {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "authenticated journal does not match the expected public statement",
+ )
+ .into());
+ }
+ Ok(())
+}
+
+fn verify_artifacts(
+ receipt_path: &Path,
+ public_path: &Path,
+ expected_id: Option,
+) -> AppResult {
+ let receipt_bytes = read_bytes_bounded(receipt_path, MAX_RECEIPT_BYTES)?;
+ let envelope: PublicEnvelope = read_json_bounded(public_path, MAX_ENVELOPE_BYTES)?;
+ let receipt: Receipt = rmp_serde::from_slice(&receipt_bytes)?;
+ require_composite_receipt(&receipt)?;
+
+ let trusted_id = expected_id.unwrap_or_else(method_id);
+ receipt.verify(trusted_id)?;
+ let journal: PublicJournal = receipt.journal.decode()?;
+
+ if envelope.image_id != trusted_id.to_string()
+ || envelope.receipt_sha256 != digest_hex(&receipt_bytes)
+ || envelope.receipt_size != receipt_bytes.len() as u64
+ || envelope.experiment_profile != String::from_utf8(PROFILE_LABEL.to_vec())?
+ || envelope.proof_system != PROOF_SYSTEM
+ || envelope.journal != journal
+ {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "public envelope, receipt, image ID, or authenticated journal mismatch",
+ )
+ .into());
+ }
+
+ let expected_statement = PublicStatement {
+ subject_digest: envelope.journal.subject_digest,
+ policy_digest: envelope.journal.policy_digest,
+ challenge: envelope.journal.challenge,
+ threshold: envelope.journal.threshold,
+ };
+ validate_public_journal(&journal, &expected_statement)?;
+ Ok(journal)
+}
+
+fn proof_attempt_rejected(
+ witness: &PrivateWitness,
+ statement: &PublicStatement,
+) -> AppResult {
+ let input = ProverInput {
+ statement: statement.clone(),
+ witness: witness.clone(),
+ };
+ let env = ExecutorEnv::builder().write(&input)?.build()?;
+ match default_prover().prove(env, VSTD_ZK_GUEST_ELF) {
+ Ok(prove_info) => Ok(prove_info.receipt.verify(method_id()).is_err()),
+ Err(_) => Ok(true),
+ }
+}
+
+fn self_test(output_dir: &Path) -> AppResult<()> {
+ ensure_real_mode()?;
+ if output_dir.exists() {
+ fs::remove_dir_all(output_dir)?;
+ }
+ fs::create_dir_all(output_dir)?;
+
+ let (witness, statement) = sample_inputs();
+ let receipt_path = output_dir.join("receipt.msgpack");
+ let public_path = output_dir.join("public.json");
+ let envelope = prove_to_files(&witness, &statement, &receipt_path, &public_path)?;
+ let real_proof_verified = verify_artifacts(&receipt_path, &public_path, None).is_ok();
+
+ let mut low_witness = witness.clone();
+ low_witness.measurement = statement.threshold.saturating_sub(1);
+ let unsatisfied_witness_rejected = proof_attempt_rejected(&low_witness, &statement)?;
+
+ let mut unknown_witness = witness.clone();
+ unknown_witness.candidate_state = CandidateState::Unknown;
+ let unknown_rejected = proof_attempt_rejected(&unknown_witness, &statement)?;
+
+ let mut conflicted_witness = witness.clone();
+ conflicted_witness.candidate_state = CandidateState::Conflicted;
+ let conflicted_rejected = proof_attempt_rejected(&conflicted_witness, &statement)?;
+
+ let mut mutated_envelope = envelope.clone();
+ mutated_envelope.journal.threshold = mutated_envelope.journal.threshold.saturating_add(1);
+ let mutated_path = output_dir.join("mutated-public.json");
+ write_json(&mutated_path, &mutated_envelope)?;
+ let mutated_public_input_rejected =
+ verify_artifacts(&receipt_path, &mutated_path, None).is_err();
+
+ let mut transplanted = envelope.clone();
+ transplanted.journal.subject_digest[0] ^= 1;
+ transplanted.journal.challenge[0] ^= 1;
+ let transplanted_path = output_dir.join("transplanted-public.json");
+ write_json(&transplanted_path, &transplanted)?;
+ let statement_transplant_rejected =
+ verify_artifacts(&receipt_path, &transplanted_path, None).is_err();
+
+ let mut wrong_id = method_id();
+ wrong_id.as_mut_bytes()[0] ^= 1;
+ let wrong_image_id_rejected =
+ verify_artifacts(&receipt_path, &public_path, Some(wrong_id)).is_err();
+
+ let receipt_bytes = read_bytes_bounded(&receipt_path, MAX_RECEIPT_BYTES)?;
+ let mut corrupted_bytes = receipt_bytes.clone();
+ let corrupt_index = corrupted_bytes.len() / 2;
+ corrupted_bytes[corrupt_index] ^= 1;
+ let corrupted_path = output_dir.join("corrupted-receipt.msgpack");
+ write_bytes(&corrupted_path, &corrupted_bytes)?;
+ let corrupted_proof_rejected =
+ verify_artifacts(&corrupted_path, &public_path, None).is_err();
+
+ let mut tampered_receipt: Receipt = rmp_serde::from_slice(&receipt_bytes)?;
+ if tampered_receipt.journal.bytes.is_empty() {
+ return Err(io::Error::new(io::ErrorKind::InvalidData, "empty journal").into());
+ }
+ tampered_receipt.journal.bytes[0] ^= 1;
+ let tampered_path = output_dir.join("tampered-journal.msgpack");
+ write_bytes(&tampered_path, &rmp_serde::to_vec_named(&tampered_receipt)?)?;
+ let tampered_journal_rejected =
+ verify_artifacts(&tampered_path, &public_path, None).is_err();
+
+ let private_bytes_absent_from_public_artifacts = !directory_contains(
+ output_dir,
+ &[witness.evidence.as_slice(), witness.salt.as_slice()],
+ )?;
+
+ let results = SelfTestResults {
+ real_proof_verified,
+ unsatisfied_witness_rejected,
+ unknown_rejected,
+ conflicted_rejected,
+ mutated_public_input_rejected,
+ wrong_image_id_rejected,
+ corrupted_proof_rejected,
+ tampered_journal_rejected,
+ statement_transplant_rejected,
+ private_bytes_absent_from_public_artifacts,
+ };
+ let all_passed = results.real_proof_verified
+ && results.unsatisfied_witness_rejected
+ && results.unknown_rejected
+ && results.conflicted_rejected
+ && results.mutated_public_input_rejected
+ && results.wrong_image_id_rejected
+ && results.corrupted_proof_rejected
+ && results.tampered_journal_rejected
+ && results.statement_transplant_rejected
+ && results.private_bytes_absent_from_public_artifacts;
+ write_json(&output_dir.join("self-test-results.json"), &results)?;
+ println!("{}", serde_json::to_string_pretty(&results)?);
+ if !all_passed {
+ return Err(io::Error::new(io::ErrorKind::Other, "one or more self-tests failed").into());
+ }
+ Ok(())
+}
+
+fn directory_contains(directory: &Path, needles: &[&[u8]]) -> AppResult {
+ for entry in fs::read_dir(directory)? {
+ let path = entry?.path();
+ if !path.is_file() {
+ continue;
+ }
+ let bytes = fs::read(path)?;
+ for needle in needles {
+ if !needle.is_empty() && bytes.windows(needle.len()).any(|window| window == *needle) {
+ return Ok(true);
+ }
+ }
+ }
+ Ok(false)
+}
+
+fn read_bytes_bounded(path: &Path, maximum: u64) -> AppResult> {
+ let metadata = fs::metadata(path)?;
+ if metadata.len() > maximum {
+ return Err(io::Error::new(io::ErrorKind::InvalidData, "input exceeds size bound").into());
+ }
+ Ok(fs::read(path)?)
+}
+
+fn read_json_bounded(path: &Path, maximum: u64) -> AppResult
+where
+ T: serde::de::DeserializeOwned,
+{
+ let bytes = read_bytes_bounded(path, maximum)?;
+ Ok(serde_json::from_slice(&bytes)?)
+}
+
+fn write_bytes(path: &Path, bytes: &[u8]) -> AppResult<()> {
+ if let Some(parent) = path.parent() {
+ fs::create_dir_all(parent)?;
+ }
+ fs::write(path, bytes)?;
+ Ok(())
+}
+
+fn write_json(path: &Path, value: &T) -> AppResult<()>
+where
+ T: Serialize,
+{
+ let mut bytes = serde_json::to_vec_pretty(value)?;
+ bytes.push(b'\n');
+ write_bytes(path, &bytes)
+}
diff --git a/examples/zizk_artifact_first/risc0/methods/Cargo.toml b/examples/zizk_artifact_first/risc0/methods/Cargo.toml
new file mode 100644
index 0000000..1baf35a
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/methods/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "vstd-zk-methods"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[build-dependencies]
+risc0-build = { version = "=3.0.6" }
+
+[package.metadata.risc0]
+methods = ["guest"]
diff --git a/examples/zizk_artifact_first/risc0/methods/build.rs b/examples/zizk_artifact_first/risc0/methods/build.rs
new file mode 100644
index 0000000..08a8a4e
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/methods/build.rs
@@ -0,0 +1,3 @@
+fn main() {
+ risc0_build::embed_methods();
+}
diff --git a/examples/zizk_artifact_first/risc0/methods/guest/Cargo.lock b/examples/zizk_artifact_first/risc0/methods/guest/Cargo.lock
new file mode 100644
index 0000000..9591684
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/methods/guest/Cargo.lock
@@ -0,0 +1,1485 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "anyhow"
+version = "1.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
+
+[[package]]
+name = "ark-bn254"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-r1cs-std",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-crypto-primitives"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0c292754729c8a190e50414fd1a37093c786c709899f29c9f7daccecfa855e"
+dependencies = [
+ "ahash",
+ "ark-crypto-primitives-macros",
+ "ark-ec",
+ "ark-ff",
+ "ark-relations",
+ "ark-serialize",
+ "ark-snark",
+ "ark-std",
+ "blake2",
+ "derivative",
+ "digest",
+ "fnv",
+ "merlin",
+ "sha2",
+]
+
+[[package]]
+name = "ark-crypto-primitives-macros"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7e89fe77d1f0f4fe5b96dfc940923d88d17b6a773808124f21e764dfb063c6a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce"
+dependencies = [
+ "ahash",
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "educe",
+ "fnv",
+ "hashbrown 0.15.5",
+ "itertools",
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "arrayvec",
+ "digest",
+ "educe",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60"
+dependencies = [
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "ark-groth16"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88f1d0f3a534bb54188b8dcc104307db6c56cdae574ddc3212aec0625740fc7e"
+dependencies = [
+ "ark-crypto-primitives",
+ "ark-ec",
+ "ark-ff",
+ "ark-poly",
+ "ark-relations",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27"
+dependencies = [
+ "ahash",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "educe",
+ "fnv",
+ "hashbrown 0.15.5",
+]
+
+[[package]]
+name = "ark-r1cs-std"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-relations",
+ "ark-std",
+ "educe",
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+ "tracing",
+]
+
+[[package]]
+name = "ark-relations"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384"
+dependencies = [
+ "ark-ff",
+ "ark-std",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "arrayvec",
+ "digest",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "ark-snark"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d368e2848c2d4c129ce7679a7d0d2d612b6a274d3ea6a13bad4445d61b381b88"
+dependencies = [
+ "ark-ff",
+ "ark-relations",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a"
+dependencies = [
+ "num-traits",
+ "rand 0.8.7",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
+
+[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
+
+[[package]]
+name = "blake2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "borsh"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f"
+dependencies = [
+ "borsh-derive",
+ "cfg_aliases",
+]
+
+[[package]]
+name = "borsh-derive"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8f347189c62a579b8cd5f80714efa178f52e461dc2e6d701d264f5ff22e566c"
+dependencies = [
+ "once_cell",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "bytemuck"
+version = "1.25.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
+
+[[package]]
+name = "cobs"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "core-graphics-types"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "libc",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_more"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 2.0.119",
+ "unicode-xid",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "const-oid",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
+
+[[package]]
+name = "educe"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417"
+dependencies = [
+ "enum-ordinalize",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "either"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
+
+[[package]]
+name = "elf"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b"
+
+[[package]]
+name = "embedded-io"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
+
+[[package]]
+name = "embedded-io"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
+
+[[package]]
+name = "enum-ordinalize"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677"
+dependencies = [
+ "enum-ordinalize-derive",
+]
+
+[[package]]
+name = "enum-ordinalize-derive"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a65863d15a4ce2888bd2f0f543cc963d3879c3a022c8ee43f6141d479a3ac815"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "allocator-api2",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hex-literal"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
+
+[[package]]
+name = "include_bytes_aligned"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ee796ad498c8d9a1d68e477df8f754ed784ef875de1414ebdaf169f70a6a784"
+
+[[package]]
+name = "indexmap"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.17.1",
+]
+
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "keccak"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
+dependencies = [
+ "cpufeatures",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+dependencies = [
+ "spin",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.189"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+
+[[package]]
+name = "libm"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
+
+[[package]]
+name = "log"
+version = "0.4.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
+
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memchr"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
+name = "merlin"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d"
+dependencies = [
+ "byteorder",
+ "keccak",
+ "rand_core 0.6.4",
+ "zeroize",
+]
+
+[[package]]
+name = "metal"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21"
+dependencies = [
+ "bitflags 2.13.1",
+ "block",
+ "core-graphics-types",
+ "foreign-types",
+ "log",
+ "objc",
+ "paste",
+]
+
+[[package]]
+name = "no_std_strings"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5b0c77c1b780822bc749a33e39aeb2c07584ab93332303babeabb645298a76e"
+
+[[package]]
+name = "num-bigint"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
+dependencies = [
+ "num_enum_derive",
+ "rustversion",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
+name = "postcard"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
+dependencies = [
+ "cobs",
+ "embedded-io 0.4.0",
+ "embedded-io 0.6.1",
+ "serde",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
+dependencies = [
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "proptest"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
+dependencies = [
+ "bitflags 2.13.1",
+ "num-traits",
+ "rand 0.9.5",
+ "rand_chacha 0.9.0",
+ "rand_xorshift",
+ "unarray",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "rand"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
+dependencies = [
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
+dependencies = [
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
+[[package]]
+name = "rand_core"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
+
+[[package]]
+name = "rand_xorshift"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
+dependencies = [
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "risc0-binfmt"
+version = "3.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d836c6ad82f4ced7c61d5feedf905a17780312e393aa681d29cc0bbc5131672b"
+dependencies = [
+ "anyhow",
+ "borsh",
+ "bytemuck",
+ "derive_more",
+ "elf",
+ "lazy_static",
+ "postcard",
+ "rand 0.9.5",
+ "risc0-zkp",
+ "risc0-zkvm-platform",
+ "ruint",
+ "semver",
+ "serde",
+ "tracing",
+]
+
+[[package]]
+name = "risc0-circuit-keccak"
+version = "4.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c731e12429eb4457e1ddc69c56ee7343a1e10b86e4aa55bc8f4d2b13734abb9"
+dependencies = [
+ "anyhow",
+ "bytemuck",
+ "paste",
+ "risc0-binfmt",
+ "risc0-circuit-recursion",
+ "risc0-core",
+ "risc0-zkp",
+ "tracing",
+]
+
+[[package]]
+name = "risc0-circuit-recursion"
+version = "4.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40dd640122abcc67d4d4e4f055c68cbc3ad2efb8589c65c2b23d354632971b60"
+dependencies = [
+ "anyhow",
+ "bytemuck",
+ "hex",
+ "metal",
+ "risc0-core",
+ "risc0-zkp",
+ "tracing",
+]
+
+[[package]]
+name = "risc0-circuit-rv32im"
+version = "4.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb11231aa4b74bcc0c8d16597893fbd7ea6f6a9ebbc35e16bfd06b467c7ee104"
+dependencies = [
+ "anyhow",
+ "bit-vec",
+ "bytemuck",
+ "derive_more",
+ "paste",
+ "risc0-binfmt",
+ "risc0-core",
+ "risc0-zkp",
+ "serde",
+ "tracing",
+]
+
+[[package]]
+name = "risc0-core"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6eb2d2b2c6cac0e43cbb2202daacee1a2f24d0dfa03fd08887a11dc6defdcc1"
+dependencies = [
+ "bytemuck",
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "risc0-groth16"
+version = "3.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0ca702ea7d0162766defe7ed6a79bda4a747ad9e2684000a6edd14df0a6d1f3"
+dependencies = [
+ "anyhow",
+ "ark-bn254",
+ "ark-ec",
+ "ark-ff",
+ "ark-groth16",
+ "ark-serialize",
+ "bytemuck",
+ "hex",
+ "num-bigint",
+ "num-traits",
+ "risc0-binfmt",
+ "risc0-zkp",
+ "serde",
+]
+
+[[package]]
+name = "risc0-zkos-v1compat"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8b0b598ba7946354b10ca5c56e382de801e6c7fce9fccad0396ec436bc5072b"
+dependencies = [
+ "include_bytes_aligned",
+ "no_std_strings",
+ "risc0-zkvm-platform",
+]
+
+[[package]]
+name = "risc0-zkp"
+version = "3.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21c0c921e5e2d44197940d387a45e29c6165e318b5a168fdfdbd50f50ba03678"
+dependencies = [
+ "anyhow",
+ "blake2",
+ "borsh",
+ "bytemuck",
+ "cfg-if",
+ "digest",
+ "hex",
+ "hex-literal",
+ "metal",
+ "paste",
+ "rand_core 0.9.5",
+ "risc0-core",
+ "risc0-zkvm-platform",
+ "serde",
+ "sha2",
+ "stability",
+ "tracing",
+]
+
+[[package]]
+name = "risc0-zkvm"
+version = "3.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5d4f24ec767f71a1663a4d24cf9d02b6bfee44c64647cae677227817051007a"
+dependencies = [
+ "anyhow",
+ "borsh",
+ "bytemuck",
+ "derive_more",
+ "hex",
+ "risc0-binfmt",
+ "risc0-circuit-keccak",
+ "risc0-circuit-recursion",
+ "risc0-circuit-rv32im",
+ "risc0-core",
+ "risc0-groth16",
+ "risc0-zkos-v1compat",
+ "risc0-zkp",
+ "risc0-zkvm-platform",
+ "rrs-lib",
+ "semver",
+ "serde",
+ "sha2",
+ "stability",
+ "tracing",
+]
+
+[[package]]
+name = "risc0-zkvm-platform"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2eb37a97ff7e8e4ee1b2a1c43ec143b4887759883c343507af9e4787a57914cd"
+dependencies = [
+ "bytemuck",
+ "cfg-if",
+ "getrandom 0.2.17",
+ "getrandom 0.3.4",
+ "libm",
+ "num_enum",
+ "paste",
+ "stability",
+]
+
+[[package]]
+name = "rrs-lib"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4382d3af3a4ebdae7f64ba6edd9114fff92c89808004c4943b393377a25d001"
+dependencies = [
+ "downcast-rs",
+ "paste",
+]
+
+[[package]]
+name = "ruint"
+version = "1.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5e99bff0393163bb25029a6af25d3d8d202ba5b5438a74d1bd8789f5c822970"
+dependencies = [
+ "borsh",
+ "proptest",
+ "rand 0.8.7",
+ "rand 0.9.5",
+ "ruint-macro",
+ "serde_core",
+ "valuable",
+ "zeroize",
+]
+
+[[package]]
+name = "ruint-macro"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
+
+[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+dependencies = [
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
+
+[[package]]
+name = "stability"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac"
+dependencies = [
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "1.1.1+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.25.13+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "toml_parser",
+ "winnow",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.1.3+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
+dependencies = [
+ "winnow",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71"
+dependencies = [
+ "tracing-core",
+]
+
+[[package]]
+name = "typenum"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
+
+[[package]]
+name = "unarray"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[package]]
+name = "valuable"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "vstd-zk-guest"
+version = "0.1.0"
+dependencies = [
+ "risc0-zkvm",
+ "vstd-zk-types",
+]
+
+[[package]]
+name = "vstd-zk-types"
+version = "0.1.0"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.4+wasi-0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "winnow"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.57.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
+
+[[package]]
+name = "zerocopy"
+version = "0.8.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
diff --git a/examples/zizk_artifact_first/risc0/methods/guest/Cargo.toml b/examples/zizk_artifact_first/risc0/methods/guest/Cargo.toml
new file mode 100644
index 0000000..6f11780
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/methods/guest/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "vstd-zk-guest"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[workspace]
+
+[dependencies]
+risc0-zkvm = { version = "=3.0.6", default-features = false, features = ["std"] }
+vstd-zk-types = { path = "../../types" }
diff --git a/examples/zizk_artifact_first/risc0/methods/guest/src/main.rs b/examples/zizk_artifact_first/risc0/methods/guest/src/main.rs
new file mode 100644
index 0000000..e15ddd7
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/methods/guest/src/main.rs
@@ -0,0 +1,70 @@
+//! Terminology: Secure Hash Algorithm 256-bit (SHA-256); Verifier Standard (VSTD).
+use risc0_zkvm::{
+ guest::env,
+ sha::{Impl, Sha256},
+};
+use vstd_zk_types::{
+ CandidateState, ProverInput, PublicJournal, COMMITMENT_DOMAIN, MAX_EVIDENCE_LEN,
+ MAX_THRESHOLD, PREDICATE_TEXT, PROFILE_LABEL,
+};
+
+fn digest_bytes(value: &[u8]) -> [u8; 32] {
+ let digest = Impl::hash_bytes(value);
+ digest.as_bytes().try_into().expect("SHA-256 is 32 bytes")
+}
+
+fn main() {
+ let input: ProverInput = env::read();
+
+ assert!(!input.witness.evidence.is_empty(), "evidence must not be empty");
+ assert!(
+ input.witness.evidence.len() <= MAX_EVIDENCE_LEN,
+ "evidence exceeds the bounded predicate"
+ );
+ assert!(
+ input.witness.candidate_state == CandidateState::Supported,
+ "UNKNOWN and CONFLICTED inputs do not satisfy this predicate"
+ );
+ assert!(
+ input.statement.threshold <= MAX_THRESHOLD,
+ "threshold exceeds the experiment bound"
+ );
+ assert!(
+ input.witness.measurement >= input.statement.threshold,
+ "private measurement is below the public threshold"
+ );
+ assert!(
+ input.statement.subject_digest != [0_u8; 32],
+ "subject digest must be explicit"
+ );
+ assert!(
+ input.statement.policy_digest != [0_u8; 32],
+ "policy digest must be explicit"
+ );
+ assert!(
+ input.statement.challenge != [0_u8; 32],
+ "challenge must be explicit"
+ );
+
+ let mut commitment_input = Vec::with_capacity(
+ COMMITMENT_DOMAIN.len() + 4 + input.witness.evidence.len() + 32 + 8,
+ );
+ commitment_input.extend_from_slice(COMMITMENT_DOMAIN);
+ commitment_input.extend_from_slice(&(input.witness.evidence.len() as u32).to_be_bytes());
+ commitment_input.extend_from_slice(&input.witness.evidence);
+ commitment_input.extend_from_slice(&input.witness.salt);
+ commitment_input.extend_from_slice(&input.witness.measurement.to_be_bytes());
+
+ let journal = PublicJournal {
+ profile_digest: digest_bytes(PROFILE_LABEL),
+ predicate_digest: digest_bytes(PREDICATE_TEXT),
+ subject_digest: input.statement.subject_digest,
+ policy_digest: input.statement.policy_digest,
+ challenge: input.statement.challenge,
+ threshold: input.statement.threshold,
+ evidence_commitment: digest_bytes(&commitment_input),
+ predicate_satisfied: true,
+ };
+
+ env::commit(&journal);
+}
diff --git a/examples/zizk_artifact_first/risc0/methods/src/lib.rs b/examples/zizk_artifact_first/risc0/methods/src/lib.rs
new file mode 100644
index 0000000..1bdb308
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/methods/src/lib.rs
@@ -0,0 +1 @@
+include!(concat!(env!("OUT_DIR"), "/methods.rs"));
diff --git a/examples/zizk_artifact_first/risc0/recorded-proof/public.json b/examples/zizk_artifact_first/risc0/recorded-proof/public.json
new file mode 100644
index 0000000..16e1bc5
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/recorded-proof/public.json
@@ -0,0 +1,215 @@
+{
+ "experiment_profile": "ZIZK-VSTD-ZK-EXPERIMENT-0.1",
+ "proof_system": "risc0-zkvm-3.0.6-composite-stark",
+ "image_id": "91df751f5764f81ba4995994afb43e87928dc32d23c81799c767794c27eabcff",
+ "receipt_sha256": "04813c4757ba4efbdad9d51d50d7402f3a98f6c23e53b9b58cce8af12ef9caa2",
+ "receipt_size": 301835,
+ "journal": {
+ "profile_digest": [
+ 95,
+ 251,
+ 195,
+ 21,
+ 230,
+ 229,
+ 109,
+ 1,
+ 28,
+ 110,
+ 20,
+ 30,
+ 223,
+ 234,
+ 203,
+ 26,
+ 63,
+ 120,
+ 214,
+ 9,
+ 248,
+ 115,
+ 124,
+ 108,
+ 213,
+ 9,
+ 253,
+ 71,
+ 110,
+ 244,
+ 139,
+ 20
+ ],
+ "predicate_digest": [
+ 255,
+ 145,
+ 56,
+ 237,
+ 74,
+ 58,
+ 230,
+ 50,
+ 99,
+ 139,
+ 147,
+ 194,
+ 19,
+ 245,
+ 53,
+ 122,
+ 137,
+ 163,
+ 150,
+ 155,
+ 154,
+ 8,
+ 215,
+ 119,
+ 34,
+ 42,
+ 211,
+ 189,
+ 129,
+ 194,
+ 229,
+ 33
+ ],
+ "subject_digest": [
+ 15,
+ 27,
+ 245,
+ 182,
+ 140,
+ 250,
+ 168,
+ 118,
+ 49,
+ 177,
+ 132,
+ 61,
+ 122,
+ 52,
+ 67,
+ 176,
+ 80,
+ 20,
+ 6,
+ 114,
+ 246,
+ 141,
+ 33,
+ 166,
+ 94,
+ 91,
+ 230,
+ 232,
+ 144,
+ 225,
+ 222,
+ 195
+ ],
+ "policy_digest": [
+ 75,
+ 139,
+ 199,
+ 64,
+ 37,
+ 3,
+ 206,
+ 42,
+ 111,
+ 213,
+ 121,
+ 136,
+ 173,
+ 42,
+ 208,
+ 52,
+ 146,
+ 86,
+ 2,
+ 12,
+ 221,
+ 139,
+ 62,
+ 39,
+ 68,
+ 25,
+ 86,
+ 9,
+ 132,
+ 100,
+ 82,
+ 95
+ ],
+ "challenge": [
+ 225,
+ 150,
+ 94,
+ 113,
+ 151,
+ 146,
+ 163,
+ 246,
+ 56,
+ 62,
+ 239,
+ 168,
+ 129,
+ 100,
+ 213,
+ 168,
+ 45,
+ 135,
+ 142,
+ 10,
+ 221,
+ 172,
+ 125,
+ 16,
+ 32,
+ 183,
+ 56,
+ 228,
+ 230,
+ 67,
+ 10,
+ 173
+ ],
+ "threshold": 70,
+ "evidence_commitment": [
+ 233,
+ 126,
+ 33,
+ 208,
+ 156,
+ 20,
+ 122,
+ 55,
+ 35,
+ 170,
+ 125,
+ 169,
+ 60,
+ 246,
+ 6,
+ 43,
+ 38,
+ 186,
+ 172,
+ 112,
+ 173,
+ 147,
+ 163,
+ 165,
+ 219,
+ 117,
+ 22,
+ 212,
+ 215,
+ 34,
+ 24,
+ 33
+ ],
+ "predicate_satisfied": true
+ }
+}
diff --git a/examples/zizk_artifact_first/risc0/recorded-proof/receipt.msgpack b/examples/zizk_artifact_first/risc0/recorded-proof/receipt.msgpack
new file mode 100644
index 0000000..852ab29
Binary files /dev/null and b/examples/zizk_artifact_first/risc0/recorded-proof/receipt.msgpack differ
diff --git a/examples/zizk_artifact_first/risc0/recorded-proof/self-test-results.json b/examples/zizk_artifact_first/risc0/recorded-proof/self-test-results.json
new file mode 100644
index 0000000..72c3645
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/recorded-proof/self-test-results.json
@@ -0,0 +1,12 @@
+{
+ "real_proof_verified": true,
+ "unsatisfied_witness_rejected": true,
+ "unknown_rejected": true,
+ "conflicted_rejected": true,
+ "mutated_public_input_rejected": true,
+ "wrong_image_id_rejected": true,
+ "corrupted_proof_rejected": true,
+ "tampered_journal_rejected": true,
+ "statement_transplant_rejected": true,
+ "private_bytes_absent_from_public_artifacts": true
+}
diff --git a/examples/zizk_artifact_first/risc0/rust-toolchain.toml b/examples/zizk_artifact_first/risc0/rust-toolchain.toml
new file mode 100644
index 0000000..c6096c7
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/rust-toolchain.toml
@@ -0,0 +1,4 @@
+[toolchain]
+channel = "1.97"
+components = ["rust-src"]
+profile = "minimal"
diff --git a/examples/zizk_artifact_first/risc0/scripts/run_real_proof.sh b/examples/zizk_artifact_first/risc0/scripts/run_real_proof.sh
new file mode 100755
index 0000000..68511f1
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/scripts/run_real_proof.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+# Terminology: reduced instruction set computer (RISC); RISC Zero (RISC0); Verifier Standard (VSTD).
+set -euo pipefail
+
+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
+MECHANISM_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
+
+export PATH="${HOME}/.risc0/bin:${HOME}/.cargo/bin:${PATH}"
+export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-${HOME}/.cache/vstd-zk-target}"
+export RISC0_DEV_MODE=0
+
+cd "${MECHANISM_DIR}"
+cargo run --locked --release -p vstd-zk-host -- \
+ self-test local-artifacts/self-test
diff --git a/examples/zizk_artifact_first/risc0/scripts/verify_recorded_proof.sh b/examples/zizk_artifact_first/risc0/scripts/verify_recorded_proof.sh
new file mode 100755
index 0000000..2fa08be
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/scripts/verify_recorded_proof.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+# Terminology: identifier (ID); reduced instruction set computer (RISC); RISC Zero (RISC0);
+# Verifier Standard (VSTD).
+set -euo pipefail
+
+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
+MECHANISM_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
+
+export PATH="${HOME}/.risc0/bin:${HOME}/.cargo/bin:${PATH}"
+export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-${HOME}/.cache/vstd-zk-target}"
+export RISC0_DEV_MODE=0
+
+cd "${MECHANISM_DIR}"
+EXPECTED_IMAGE_ID="91df751f5764f81ba4995994afb43e87928dc32d23c81799c767794c27eabcff"
+ACTUAL_IMAGE_ID="$(
+ cargo run --locked --release -q -p vstd-zk-host -- image-id
+)"
+if [[ "${ACTUAL_IMAGE_ID}" != "${EXPECTED_IMAGE_ID}" ]]; then
+ printf 'FAIL: tracked guest image ID %s differs from recorded proof image ID %s\n' \
+ "${ACTUAL_IMAGE_ID}" "${EXPECTED_IMAGE_ID}" >&2
+ exit 1
+fi
+
+cargo run --locked --release -p vstd-zk-host -- \
+ verify recorded-proof/receipt.msgpack recorded-proof/public.json \
+ "${EXPECTED_IMAGE_ID}"
diff --git a/examples/zizk_artifact_first/risc0/types/Cargo.toml b/examples/zizk_artifact_first/risc0/types/Cargo.toml
new file mode 100644
index 0000000..66ec870
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/types/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "vstd-zk-types"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[dependencies]
+serde = { version = "=1.0.228", features = ["derive"] }
diff --git a/examples/zizk_artifact_first/risc0/types/src/lib.rs b/examples/zizk_artifact_first/risc0/types/src/lib.rs
new file mode 100644
index 0000000..f10f4d2
--- /dev/null
+++ b/examples/zizk_artifact_first/risc0/types/src/lib.rs
@@ -0,0 +1,62 @@
+//! Terminology: Verifier Standard (VSTD); zero-identity/zero-knowledge (ZIZK); zero-knowledge (ZK).
+//!
+//! Shared, experiment-local types for the ZIZK-VSTD zero-knowledge probe.
+
+use serde::{Deserialize, Serialize};
+
+pub const PROFILE_LABEL: &[u8] = b"ZIZK-VSTD-ZK-EXPERIMENT-0.1";
+pub const PREDICATE_TEXT: &[u8] = b"A private bounded evidence payload has a nonempty byte string of at most 64 bytes, an experiment-local SUPPORTED input tag, and a private measurement greater than or equal to the public threshold.";
+pub const COMMITMENT_DOMAIN: &[u8] = b"vstd-zk-evidence-commitment-v1\0";
+pub const MAX_EVIDENCE_LEN: usize = 64;
+pub const MAX_THRESHOLD: u64 = 1_000_000;
+
+#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
+pub enum CandidateState {
+ Supported,
+ Unknown,
+ Conflicted,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+pub struct PrivateWitness {
+ pub evidence: Vec,
+ pub salt: [u8; 32],
+ pub measurement: u64,
+ pub candidate_state: CandidateState,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+pub struct PublicStatement {
+ pub subject_digest: [u8; 32],
+ pub policy_digest: [u8; 32],
+ pub challenge: [u8; 32],
+ pub threshold: u64,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+pub struct ProverInput {
+ pub statement: PublicStatement,
+ pub witness: PrivateWitness,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+pub struct PublicJournal {
+ pub profile_digest: [u8; 32],
+ pub predicate_digest: [u8; 32],
+ pub subject_digest: [u8; 32],
+ pub policy_digest: [u8; 32],
+ pub challenge: [u8; 32],
+ pub threshold: u64,
+ pub evidence_commitment: [u8; 32],
+ pub predicate_satisfied: bool,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+pub struct PublicEnvelope {
+ pub experiment_profile: String,
+ pub proof_system: String,
+ pub image_id: String,
+ pub receipt_sha256: String,
+ pub receipt_size: u64,
+ pub journal: PublicJournal,
+}
diff --git a/examples/zizk_artifact_first/zero_identity/README.md b/examples/zizk_artifact_first/zero_identity/README.md
new file mode 100644
index 0000000..e0f575b
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/README.md
@@ -0,0 +1,67 @@
+# Bounded identity disclosure reference evaluator
+
+> **Acronyms:** Verifier Standard (VSTD); zero-identity/zero-knowledge (ZIZK).
+
+**Status:** bounded non-normative reference mechanism. Not part of any numbered VSTD or
+receipt profile, not implemented by
+the `verifier` package, and not referenced by any receipt. Nothing here carries a serialized receipt
+identifier, a schema `$id`, or a canonical digest.
+
+## The question
+
+Can "Zero Identity" be an operationally safe optional VSTD mode, or is the correct
+mechanism something bounded — identity minimization, pseudonymity, selective disclosure?
+
+## The answer
+
+**The label is rejected for public use.** The construction it names does not remove
+identity; it withholds *civil* identity while retaining a pseudonymous coordinate, a key
+binding, a trust root, an issuer, and a revocation source — every one of which is an
+identity coordinate and a correlation handle. Calling that "zero identity" overstates the
+privacy achieved and hides the coordinates that remain. The mechanism this evaluation
+retains is **bounded identity disclosure**: civil identity withheld, authorization
+semantically reevaluable from public coordinates conditional on declared external checks,
+and every other identity property reported honestly as `UNKNOWN`,
+`CONFLICTED`, or `REFUTED` rather than assumed.
+
+This rejects “zero identity” as a privacy-profile claim. It does not reject VSTD's
+architecture-wide zero-identity rule, which says only that identity or reputation alone
+cannot strengthen an artifact-bound result.
+
+Full reasoning and the exact claims that are and are not justified:
+[`ROUND1_ZERO_IDENTITY_REPORT.md`](ROUND1_ZERO_IDENTITY_REPORT.md).
+
+## Contents
+
+| Path | What it is |
+|---|---|
+| [`SEMANTIC_MODEL.md`](SEMANTIC_MODEL.md) | term separation, statuses, minimum coordinates, prohibited inferences |
+| [`THREAT_MODEL.md`](THREAT_MODEL.md) | sixteen threats, mitigations, residual risk, falsification conditions |
+| [`model/zero_identity_model.json`](model/zero_identity_model.json) | the machine-readable model |
+| [`evaluate.py`](evaluate.py) | standard-library evaluator over one disclosure record |
+| [`fixtures/`](fixtures) | positive, negative, `UNKNOWN`, and `CONFLICTED` records with expected results |
+| [`tests/test_zero_identity.py`](tests/test_zero_identity.py) | validation suite, one test per blocked inference |
+| [`run_validation.py`](run_validation.py) | pytest-free runner for the same fixtures |
+
+## Running it
+
+```bash
+python examples/zizk_artifact_first/zero_identity/run_validation.py
+python -m pytest examples/zizk_artifact_first/zero_identity/tests -q
+```
+
+The repository suite (`python -m pytest -q`) sets `testpaths = ["tests"]` and does not
+collect this directory, which is deliberate: an optional reference mechanism must not
+gate conformance.
+
+## Constraints observed
+
+- No dependency added to `verifier-standard`; the evaluator is standard library only.
+- No serialized receipt identifier, schema `$id`, receipt digest, console alias, lifecycle token,
+ or conformance behavior is touched. See
+ [`../../../standard/WIRE_IDENTIFIERS.md`](../../../standard/WIRE_IDENTIFIERS.md).
+- No cryptographic guarantee is invented. Signature and revocation results are fixture
+ inputs here. A deployment would have to produce them through a named real protocol; the
+ model decides only what may be concluded from the asserted results.
+- `UNKNOWN` and `CONFLICTED` are preserved as results, per
+ [`../../../AGENTS.md`](../../../AGENTS.md) section 2.
diff --git a/examples/zizk_artifact_first/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md b/examples/zizk_artifact_first/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md
new file mode 100644
index 0000000..906be9b
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md
@@ -0,0 +1,313 @@
+# Round 1 report: bounded identity disclosure under the zero-identity/zero-knowledge (ZIZK) Verifier Standard (VSTD) architecture
+
+> **Acronym:** carriage return and line feed (CRLF).
+
+**Status:** bounded non-normative reference result. No adoption is claimed or implied.
+
+Reading rule for this report: where evidence is insufficient the result is `UNKNOWN`, and
+where evidence contradicts itself the result is `CONFLICTED`. Both are retained as results.
+Neither is a gap to be filled, and neither may be read as authorization, independence,
+uniqueness, Sybil resistance, privacy, or safety.
+
+## 1. Coordinates
+
+- Base commit: `598c545be3833d6d81bb7e252ca5837f3bb2a449`
+- Branch: `claude/zizk-zero-identity`
+- Worktree label: `zizk-zi-claude` (isolated; its absolute host path is intentionally
+ excluded from this public report; the primary checkout and separate ZIZK roadmap
+ worktree were not modified)
+- Remote: `github.com/TimeLordRaps/verifier`
+- Layer: none. This reference evaluation discharges no ladder rung.
+- Seam: `examples/zizk_artifact_first/zero_identity/` only.
+
+## 2. Terminology decision
+
+**"Zero Identity" is rejected as a public label for a privacy profile.** It is retained
+only as the name of the question this reference evaluation answered, never as a
+description of what the profile provides. This does not reject the architecture-level
+rule that identity or reputation alone cannot strengthen an artifact-bound result.
+
+The falsification succeeded. A profile that "removes identity" was tested against its own
+required coordinates and the requirement survived: bounded reverification needs a
+pseudonymous coordinate, a key identifier, a trust root, an issuer, a grant, and a
+revocation source. Those are identity coordinates. What is actually removed is *civil*
+identity, and removing it changes nothing about correlation, uniqueness, or independence.
+
+Accepted term: **bounded identity disclosure**. Where a shorter phrase is needed,
+*identity minimization* is accurate and *selective disclosure* is accurate only if a real
+selective-disclosure protocol is actually deployed. "Anonymous" is rejected outright: the
+profile is pseudonymous, and a stable pseudonym is a correlation handle.
+
+## 3. Identity properties this profile supports
+
+| Property | Best attainable here | Basis and boundary |
+|---|---|---|
+| Authentication | `SUPPORTED` | semantic result over an asserted external signature check and a declared trust root; no signature is verified here |
+| Authorization | `SUPPORTED` | semantic result over authentication, an asserted grant, liveness inputs, and scope coverage |
+| Authority liveness | `SUPPORTED` / `REFUTED` | semantic result over asserted revocation state plus validity window against the evaluation instant |
+| Freshness | `SUPPORTED` / `REFUTED` | challenge coordinate and verifier-held nonce history |
+| Attribution | not separately evaluated | the record binds a pseudonymous coordinate; any real-world actor binding is `ATTESTED` at best, never inferred |
+| Authorship degree | `ATTESTED` / `REFUTED` | declared role and remove, checked against the recorded delegation hops |
+| Credential ancestry | `ATTESTED` / `REFUTED` | recorded chain from a declared trust root to the signing key |
+| Accountability | `ATTESTED` | a declared escalation authority that can act on the coordinate |
+| Uniqueness / Sybil resistance | `ATTESTED` | only with an attested mechanism; default `UNKNOWN` |
+| Verifier independence | `ATTESTED` | only from named attested evidence; shared or distinct pseudonyms alone leave actor independence `UNKNOWN` |
+| Recovery | `ATTESTED` | a declared credential-loss mechanism; strength not evaluated |
+| Unlinkability | `ASSUMED` | never `SUPPORTED`; assumptions must be declared |
+| Confidentiality | not evaluated | out of scope; any declaration remains an assumption, not an evaluator result |
+| Civil identity | `UNSUPPORTED_BY_DESIGN` | withheld deliberately |
+
+`ACCEPTED_BOUNDED` means exactly: this key was authorized for this claim scope at this
+instant. It means nothing about who the actor is, whether they are one actor, whether two
+records came from independent actors, or whether the signer authored what it signed.
+
+Authorship degree and credential ancestry were added after the first round, on the
+observation that authorization alone cannot tell a first-party claim from a relayed one.
+Three questions are now kept apart: authorization asks whether this key was permitted this
+scope; authorship degree asks who is speaking and at what remove; credential ancestry asks
+how the key came to hold the authority. A record can be fully authorized with `UNKNOWN`
+authorship, and that pairing is reported rather than merged. Neither new property can ever
+reach `SUPPORTED`: both are assertions about the world outside the record, so `ATTESTED` is
+their ceiling.
+
+### 3.1 Evidence classes, kept separate
+
+The four classes below are never merged, and no verdict promotes one into another. A
+reader who collapses them recovers exactly the overclaim this reference evaluation exists to block.
+
+| Class | What it means | Handling in this reference evaluator | Ceiling in this model |
+|---|---|---|---|
+| Semantic result | decided by the stated rules from coordinates present in the record | any reader running `evaluate.py` on the record | `SUPPORTED`, `REFUTED`, `UNKNOWN`, `CONFLICTED` |
+| External attestation | a named third party asserts a fact this model records but does not check | a deployment may authenticate it under an external protocol; this evaluator does neither that nor truth validation | `ATTESTED` |
+| Declared assumption | the record states a condition it needs and cannot demonstrate | carried unchanged and never established by this record | `ASSUMED` |
+| Protocol guarantee | whatever an actual named cryptographic protocol provides | absent here; it would be checked under that protocol outside this evaluator | not represented; enters only as an input |
+
+Concretely: `authentication` is a semantic result *about an asserted signature check*, not
+a cryptographic guarantee — this model never verifies a signature. `uniqueness`,
+`verifier_independence`, `authorship_degree`, and `credential_ancestry` are attestations at
+their ceiling. `unlinkability` is an assumption at its ceiling; `confidentiality` is not an
+evaluator output at all. No protocol guarantee is claimed anywhere, because no protocol is
+bound yet.
+
+## 4. Prohibited inferences
+
+Each is encoded in `model/zero_identity_model.json` and guarded by at least one test:
+
+1. absent civil identity implies anonymity;
+2. absent civil identity implies unlinkability;
+3. a pseudonym implies a distinct actor;
+4. a shared pseudonym implies a single actor;
+5. two distinct pseudonyms imply two independent actors;
+6. a verified signature implies authorization;
+7. a grant implies currently active authority;
+8. absent revocation evidence implies active authority;
+9. absent uniqueness evidence implies Sybil resistance;
+10. hashing, redaction, encryption, omission, or pseudonymity alone implies zero identity;
+11. disclosure minimization preserves the original claim boundary;
+12. missing evidence implies safety;
+13. a signer is the author of the claim;
+14. a relayed, delegated, or aggregated claim is first-party authorship;
+15. an absent authorship role means degree zero;
+16. a recorded ancestry chain establishes that authority survived every hop;
+17. no ancestor marked revoked means every ancestor is valid;
+18. a rotation link merges two key coordinates into one actor;
+19. a delegation may carry a scope its ancestor did not hold.
+
+Inferences 16 and 17 are the credential-side form of the recorded-lineage discipline
+already normative in `standard/VSTD-Graph-1.md`, which states that an edge records ancestry
+without establishing influence, and that no ancestor being marked revoked does not
+establish that every ancestor is valid.
+
+## 5. Trust roots and revocation dependencies
+
+The profile does not reduce trust-root dependence; it makes it explicit. A reader who
+accepts an `ACCEPTED_BOUNDED` verdict is accepting, at minimum:
+
+- the issuer named in `authorization.issuer`;
+- the trust root named in `actor.key_binding.trust_root`;
+- the revocation service named in `revocation.source`, as of `revocation.checked_at`;
+- whatever protocol produced `signature_verified`, which this model does not check;
+- every attestor named in the recorded credential ancestry, one per link.
+
+Recorded ancestry increases the number of parties a reader depends on rather than reducing
+it, and the report states that plainly: each delegation hop adds an attestor whose honesty
+is assumed. A chain is refused when an ancestor is recorded as revoked or when a delegation
+carries a scope its ancestor never held; it stays `UNKNOWN` when any link is unattested,
+when it does not begin at a declared trust root, or when it does not terminate at the
+signing key. A truncated chain therefore cannot be laundered into a clean one without also
+declaring the shorter root as trusted; the model cannot establish whether that declaration
+is honest.
+
+Revocation is a liveness dependency with a staleness bound, not a one-time check. A
+record whose revocation source is absent is `UNKNOWN`; a record whose minimization request
+deleted that source is `REJECTED` as unevaluable. Minimization is enforced by deletion
+before evaluation, so a withheld coordinate cannot be silently read anyway.
+
+## 6. Privacy and correlation leak analysis
+
+Retained and observable in every `ACCEPTED_BOUNDED` record: the pseudonymous coordinate, the key
+identifier, the trust root, the issuer, the scope name, the validity window, the
+evaluation instant, and the revocation source. Any two of these are joinable across
+records. Publication timing and volume are not addressed at all.
+
+Recorded credential ancestry makes this strictly worse, and the trade is deliberate. Every
+link publishes a parent coordinate, a child coordinate, a link type, and an attestor, so a
+chain is a durable join key across every record that carries it: two records sharing one
+delegation hop are linkable even when their pseudonyms differ, and a rotation link is an
+explicit statement that two key coordinates are related. Authorship provenance and
+unlinkability are therefore in direct tension. This reference evaluation resolves the tension toward
+provenance and reports the cost rather than claiming both.
+
+Consequence: an observer who sees two records under one pseudonym learns they share an
+actor coordinate, not that they share one natural person. An observer who sees two records
+under one issuer learns that they name the same issuer, not necessarily the same trust
+root. Withholding civil identity does not remove either correlation handle. Coercion risk
+is not removed either — it may move to an issuer that holds a civil binding. This is a
+displacement of risk, not a demonstrated reduction, and the reference evaluation reports it as such.
+
+## 7. Test results
+
+All required checks pass at the committed state. **Failed tests: none.** No assertion was
+weakened, skipped, or marked expected-failure to reach this state.
+
+| Check | Result |
+|---|---|
+| `python examples/zizk_artifact_first/zero_identity/run_validation.py` | 22 fixtures, 0 failures |
+| `python -m pytest examples/zizk_artifact_first/zero_identity/tests -q` | 65 passed |
+| `python -m pytest -q` (repository suite) | 255 passed, 3 skipped |
+| `python scripts/check_presentation.py` | passes |
+
+The repository suite sets `testpaths = ["tests"]` and does not collect this directory. That
+is deliberate: a non-normative reference evaluator must not gate conformance. The 3 skips are pre-existing and
+unrelated to this work. On a machine where another checkout of the package is installed,
+the repository suite needs the `PYTHONPATH=src` prefix described in `AGENTS.md` section 3;
+that is an environment condition, not a repository defect.
+
+### 7.1 Diff inspection
+
+The complete diff against the base is confined to `examples/zizk_artifact_first/zero_identity/`:
+30 files, 3734 added lines, **zero files changed outside that directory**. A pattern scan
+over every added line reports:
+
+| Category | Findings |
+|---|---|
+| Private filesystem paths | none |
+| Private model identifiers | none |
+| Credentials or secrets | none |
+| Email addresses | none |
+| Business plans | none |
+| Unsupported adoption claims | none |
+| Unsupported privacy or anonymity claims | none in assertion position |
+| Recorded ancestry described as causal | none |
+| CRLF line endings | none |
+
+Literal pattern hits were adjudicated and retained deliberately, because each occurs
+in negating or guarding position rather than as a claim: the word *untraceable* appears
+only in section 10 as a prohibited claim; the four serialized receipt identifiers appear only in a
+test asserting that no fixture may bind one; and `$id` appears only in prose stating that
+none is introduced.
+
+### 7.2 Non-regression of frozen surfaces
+
+Verified directly against the base commit, not assumed:
+
+- `pyproject.toml` is byte-unchanged, and `dependencies = []` still holds. The evaluator
+ imports only `copy`, `dataclasses`, `json`, `pathlib`, and `typing`; `pytest` appears
+ only in the reference evaluator's own tests, which the repository suite does not collect.
+- Zero files changed under `standard/`, `receipts/schema/`, `src/`, `examples/`, or
+ `scripts/`. No serialized receipt identifier, schema `$id`, receipt digest, console alias, or
+ lifecycle token is added, renamed, or rebound.
+- The stdlib-purity smoke check (`python -S -c "import verifier; ..."`) reports `1.1.3`.
+- Existing conformance behavior is untouched: this reference evaluation adds no code path that any
+ shipped module imports.
+
+Fixture coverage, one per required case:
+
+| Fixture | Verdict |
+|---|---|
+| `positive_bounded_authorization` | `ACCEPTED_BOUNDED` |
+| `positive_minimized_boundary_narrowed` | `ACCEPTED_BOUNDED` |
+| `unknown_missing_authorization` | `UNKNOWN` |
+| `unknown_distinct_pseudonyms` | `UNKNOWN` |
+| `unknown_uniqueness_absent` | `UNKNOWN` |
+| `conflicted_identity_evidence` | `CONFLICTED` |
+| `rejected_revoked_authority` | `REJECTED` |
+| `rejected_expired_authority` | `REJECTED` |
+| `unknown_shared_pseudonym_independence` | `UNKNOWN` |
+| `rejected_unlinkability_erases_trust_root` | `REJECTED` |
+| `rejected_replayed_challenge` | `REJECTED` |
+| `rejected_missing_challenge` | `REJECTED` |
+| `rejected_minimization_widens_boundary` | `REJECTED` |
+| `rejected_minimization_erases_key_binding` | `REJECTED` |
+| `rejected_key_compromise` | `REJECTED` |
+| `unknown_absent_authorship` | `UNKNOWN` |
+| `unknown_unattested_ancestry_link` | `UNKNOWN` |
+| `unknown_unattested_rotation` | `UNKNOWN` |
+| `conflicted_authorship_degree_vs_chain` | `CONFLICTED` |
+| `rejected_relay_claims_origination` | `REJECTED` |
+| `rejected_revoked_ancestor` | `REJECTED` |
+| `rejected_delegation_widens_scope` | `REJECTED` |
+
+No final test failed. No assertion was weakened to obtain a green suite. Validation instead
+closed two fail-open surfaces: a minimizer cannot evade a protected leaf by deleting its
+parent object, and a shared pseudonym no longer becomes a claim about how many actors use
+that credential.
+
+## 8. Unresolved assumptions
+
+1. `signature_verified` and `revocation.state` are consumed as asserted evidence. No
+ protocol is bound yet, so no protocol's assumptions have been inherited or checked.
+2. Attestation quality is unmodelled. `ATTESTED` records that someone said so. This now
+ carries more weight than it did in the first round, because every ancestry link and
+ every authorship role rests on it.
+3. An internally consistent but dishonest authorship role is undetectable from the record.
+ The model catches a relay that contradicts its own chain; it cannot catch a relay that
+ lies consistently.
+4. Chain truncation before publication is only partially addressed. A chain that does not
+ reach a declared trust root stays `UNKNOWN`, but a chain trimmed to a plausible shorter
+ root is not distinguishable from an honest short chain.
+5. Rotation is treated conservatively in one direction only: an unattested rotation does
+ not merge two coordinates. An actor rotating keys to shed a history is not detected.
+6. Nonce history is verifier-held state that this model does not carry; replay detection
+ is only as good as that history.
+7. No selective-disclosure or unlinkable-presentation scheme has been selected. Until one
+ is named, `unlinkability` stays `ASSUMED` at best.
+8. Timing and volume side channels are out of scope and unmitigated.
+9. Whether an issuer that grants many coordinates to one operator can be detected at all
+ from published records is open, and probably not decidable within one record.
+10. Whether this profile should ever become normative is not decided here. Nothing in this
+ round argues that it should.
+
+## 9. Public claims currently justified
+
+- "Civil identity can be withheld while the evaluator can recompute a bounded
+ authorization result from public coordinates, conditional on asserted external checks
+ and declared trust roots."
+- "Missing identity evidence yields `UNKNOWN`; conflicting identity evidence yields
+ `CONFLICTED`; revoked or expired authority yields a refutation."
+- "The reference evaluation enumerates the identity coordinates that remain, rather than implying
+ none remain."
+- "Authorship degree and credential ancestry are recorded and checked for internal
+ consistency; a relayed claim cannot be read as first-party authorship, and a chain from a
+ revoked ancestor is refused."
+- "A recorded ancestry chain is recorded ancestry, not proof that authority survived every
+ hop."
+- "The reference evaluation adds no required package dependency and the complete base-to-branch diff
+ does not modify a serialized receipt identifier or conformance implementation."
+
+## 10. Public claims still prohibited
+
+- "VSTD supports a zero-identity privacy mode", or any privacy claim using "zero identity" without the qualification
+ that civil identity alone is withheld.
+- "Anonymous", "untraceable", "uncorrelatable", or "privacy-preserving" as unqualified
+ descriptions of this profile.
+- Any claim that hashing, redaction, encryption, omission, or a pseudonym provides
+ unlinkability.
+- Any claim of Sybil resistance, actor uniqueness, or verifier independence that is not
+ backed by named attested evidence.
+- Any claim that a zero-knowledge proof system is used, implemented, or relied upon. None
+ is present in this reference evaluation.
+- "Provenance is verified", or any phrasing that reads recorded ancestry as established
+ authority, established influence, or a verified chain of custody.
+- Any claim that authorship is proven. Authorship degree is `ATTESTED` at its ceiling.
+- Any statement that this profile is production-ready, adopted, reviewed, or standardised.
diff --git a/examples/zizk_artifact_first/zero_identity/SEMANTIC_MODEL.md b/examples/zizk_artifact_first/zero_identity/SEMANTIC_MODEL.md
new file mode 100644
index 0000000..9c3535f
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/SEMANTIC_MODEL.md
@@ -0,0 +1,124 @@
+# Semantic model: bounded identity disclosure
+
+> **Acronym:** Verifier Standard (VSTD).
+
+**Status:** bounded non-normative reference model. No serialized receipt identifier, schema route, or receipt digest.
+
+This document defines what the evaluator means by each identity-adjacent term, which
+properties a record can support, and which inferences are prohibited. The executable
+form is [`model/zero_identity_model.json`](model/zero_identity_model.json) and
+[`evaluate.py`](evaluate.py); where prose and code disagree, the code plus its fixtures
+are the artifact under test and this document is the defect.
+
+## 1. Separated terms
+
+These are distinct properties. None implies another.
+
+| Term | Meaning here | Profile position |
+|---|---|---|
+| Civil or legal identity | a natural or legal person recognised by a jurisdiction | withheld; `UNSUPPORTED_BY_DESIGN` |
+| Persistent public identity | a durable public name reused across contexts | out of scope; the profile uses a pseudonymous coordinate instead |
+| Key or credential coordinate | `key_id`, its trust root, and the grant that references it | required |
+| Authentication | evidence that a given key signed the record | evaluable, may be `SUPPORTED` |
+| Authorization | evidence that the signer was permitted this claim scope | evaluable, may be `SUPPORTED` |
+| Accountability | a named authority that can act on the pseudonymous coordinate | at best `ATTESTED` |
+| Attribution | binding a record to a pseudonymous coordinate, never to a person | at best `ATTESTED` |
+| Authorship degree | how far the signing party sits from the origin of the claim: originator, delegate, relay, aggregator | at best `ATTESTED`, default `UNKNOWN` |
+| Credential ancestry | the recorded chain of issuance, delegation, and rotation links from a trust root to the signing key | at best `ATTESTED`, refutable |
+| Uniqueness / Sybil resistance | evidence that one coordinate corresponds to one actor | at best `ATTESTED`, default `UNKNOWN` |
+| Verifier independence | evidence that two receipts came from actors that do not share a root | at best `ATTESTED`, refutable |
+| Revocation and expiry | current liveness of a grant | evaluable, refutable |
+| Confidentiality | protection of the record in transit and at rest | out of scope, at best `ASSUMED` |
+| Unlinkability | inability of an observer to join two records to one actor | never `SUPPORTED`, at best `ASSUMED` |
+| Anonymity / pseudonymity | absence of any actor coordinate versus a stable non-civil one | the profile is pseudonymous, never anonymous |
+
+## 2. Statuses
+
+`SUPPORTED` — decided from coordinates present in the record under stated rules.
+`ATTESTED` — an external party asserts it; the assertion is recorded, not checked here.
+`ASSUMED` — declared by the record as an assumption, carried forward as an assumption.
+`UNKNOWN` — the coordinate needed to decide is absent. This is a result, not a gap to fill.
+`CONFLICTED` — two retained pieces of evidence disagree. Terminal; never resolved by preference.
+`REFUTED` — a positive negative result: the property is contradicted by evidence.
+`UNSUPPORTED_BY_DESIGN` — the profile deliberately withholds the coordinate.
+
+Record verdicts are `ACCEPTED_BOUNDED`, `UNKNOWN`, `CONFLICTED`, and `REJECTED`. They are
+aggregated without erasing per-property uncertainty: any `REFUTED` property makes the
+record `REJECTED`; otherwise any `CONFLICTED` property makes it `CONFLICTED`.
+`ACCEPTED_BOUNDED` requires `SUPPORTED` authentication and authorization plus satisfaction
+of every explicitly claimed property. An `UNKNOWN` ancillary property remains visible but
+does not widen or erase that bounded authorization result. Every other record is `UNKNOWN`.
+`ACCEPTED_BOUNDED` therefore asserts exactly one thing: authentication and authorization
+hold for the declared claim scope at the declared instant. It asserts nothing about
+uniqueness, independence, unlinkability, or the actor behind the coordinate.
+
+## 3. Minimum public actor coordinates
+
+Bounded authorization reverification without civil identity needs all of:
+
+- `actor.pseudonym` — the coordinate a verdict attaches to;
+- `actor.key_binding.key_id`, `.signature_verified`, `.trust_root`;
+- `authorization.grant_id`, `.issuer`, `.scope`, `.not_before`, `.not_after`;
+- `revocation.source`, `.state`, `.checked_at`;
+- `trust_roots` — the roots the reader must already accept.
+
+The provenance extension may additionally disclose:
+
+- `authorship.role`, `.degree`, `.attested_by` — the asserted author role and remove;
+- `credential_ancestry[].parent`, `.child`, `.link_type`, `.attested_by` — the recorded path
+ by which the signing key obtained its authority.
+
+Authorship degree and credential ancestry are distinct from authorization. Authorization
+asks whether this key was permitted this scope; authorship asks who is speaking and at what
+remove; ancestry asks how the key came to hold the authority at all. A record can be fully
+authorized while its authorship is `UNKNOWN`, and that combination is reported, not merged.
+
+Remove a required coordinate from an ordinary record and the dependent property becomes
+`UNKNOWN`. Remove a required coordinate under a minimization request — whether by naming
+the leaf or a parent path — and the record is `REJECTED` as unevaluable. Minimization is
+enforced, not trusted: `evaluate.py` checks the requested paths and then deletes every
+withheld coordinate before evaluating, so a coordinate an actor asked to withhold cannot
+quietly still be read.
+
+## 4. Prohibited inferences
+
+Encoded in the model and each guarded by a test:
+
+1. Absent civil identity implies anonymity or unlinkability.
+2. A pseudonym implies a distinct actor.
+3. A shared pseudonym implies a single actor.
+4. Two distinct pseudonyms imply two independent actors.
+5. A verified signature implies authorization.
+6. A grant implies that the authority is currently active.
+7. Absent revocation evidence implies active authority.
+8. Absent uniqueness evidence implies Sybil resistance.
+9. Hashing, redaction, encryption, omission, or pseudonymity alone implies zero identity.
+10. Disclosure minimization preserves the original claim boundary.
+11. Missing evidence implies safety.
+12. A signer is the author of the claim.
+13. A relayed, delegated, or aggregated claim is first-party authorship.
+14. An absent authorship role means degree zero.
+15. A recorded ancestry chain establishes that authority survived every hop.
+16. No ancestor marked revoked means every ancestor is valid.
+17. A rotation link merges two key coordinates into one actor.
+18. A delegation may carry a scope its ancestor did not hold.
+
+Inferences 15 and 16 mirror the recorded-lineage discipline of
+[`../../../standard/VSTD-Graph-1.md`](../../../standard/VSTD-Graph-1.md): an edge records
+ancestry, and a clean-ancestor policy must require validity explicitly rather than reading
+it out of the absence of a revocation mark.
+
+## 5. Relationship to cryptography
+
+This model contains no cryptographic construction and asserts no cryptographic guarantee.
+`signature_verified`, `state`, and any proof result are *inputs*: a deployment obtains them
+from a real protocol and the model decides what may be concluded from them. If a
+deployment wants selective disclosure or unlinkable presentation, it must name the actual
+scheme it uses, state that scheme's assumptions, and record the outcome as evidence here.
+Nothing in this reference evaluator substitutes for that.
+
+## 6. Relationship to VSTD
+
+Nothing here changes a serialized receipt identifier, a schema `$id`, a console alias, a lifecycle
+token, or any conformance behavior. See [`../../../standard/WIRE_IDENTIFIERS.md`](../../../standard/WIRE_IDENTIFIERS.md).
+The profile adds no dependency: `evaluate.py` is standard library only.
diff --git a/examples/zizk_artifact_first/zero_identity/THREAT_MODEL.md b/examples/zizk_artifact_first/zero_identity/THREAT_MODEL.md
new file mode 100644
index 0000000..207e6ba
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/THREAT_MODEL.md
@@ -0,0 +1,53 @@
+# Threat model: bounded identity disclosure
+
+**Status:** bounded non-normative reference threat model.
+
+Scope: one bounded disclosure record and the conclusions a reader may draw from it.
+Out of scope: transport security, storage security, the correctness of any cryptographic
+protocol, and the honesty of an issuer's internal process.
+
+The adversary is assumed to be able to read every published record, to submit records of
+their own, to create as many pseudonymous coordinates as an issuer will grant, and to
+observe timing and volume of publication. The adversary is not assumed to break signature
+schemes; where a key fails, it fails by compromise or misuse, not by cryptanalysis.
+
+| # | Threat | What the model does | Residual risk |
+|---|---|---|---|
+| T1 | Correlation across receipts | Omits a civil-identity field; `unlinkability` is never `SUPPORTED`, at best `ASSUMED` under declared assumptions | Real. Remaining coordinates or side information may resolve to civil identity. A stable pseudonym, key, issuer, and publication timing are all joinable |
+| T2 | Replay | When `freshness.required` is set, an absent challenge fails closed and a previously observed challenge is `REFUTED` | A verifier that never requires freshness gets `UNKNOWN`, which is honest but not protective. Nonce history must be kept by the verifier |
+| T3 | Key compromise | `key_compromised_during_interval` refutes authentication and therefore authorization | The model learns of compromise only when someone reports it. Silent compromise is indistinguishable from normal signing |
+| T4 | Revoked or expired authority | Revocation state `revoked`, or an evaluation instant outside the validity window, is `REFUTED`, never `UNKNOWN`; a missing revocation source is `UNKNOWN`, never active | Revocation freshness is bounded by `revocation.checked_at`; the model does not fetch status |
+| T5 | One actor presenting as many independent actors | Independence requires attested evidence with distinct trust roots; distinct pseudonyms alone leave it `UNKNOWN` | An issuer that grants many credentials to one operator can produce evidence that looks distinct. Independence is `ATTESTED` at best, never proven here |
+| T6 | Many actors sharing one credential | A shared pseudonymous coordinate cannot supply independent corroboration, but actor independence and `uniqueness` stay `UNKNOWN` | The model cannot detect sharing from a single record. Attribution binds a coordinate, never a person |
+| T7 | Coerced identity disclosure | The profile omits a civil-identity field and explicitly retains the remaining correlation coordinates | Side information may still identify an actor. Coercion also moves to the issuer, which may hold a civil binding. This displaces risk rather than removing it |
+| T8 | Metadata and timing leakage | Not mitigated. Declared as out of scope and reported as such | Publication time, volume, scope names, and issuer choice remain observable |
+| T9 | Colluding issuers or verifiers | Trust roots must be declared explicitly, so a reader can see that two records share one root | Collusion between a declared issuer and a declared verifier defeats the profile. The model surfaces the shared root; it cannot rule collusion out |
+| T10 | Unverifiable claims of independence | `verifier_independence` never becomes `SUPPORTED`; a claim of it that lacks evidence downgrades the record verdict to `UNKNOWN` | Attestation quality is outside the model |
+| T11 | Missing authorization | A record with no grant is `UNKNOWN`; it never fails open | A verifier that treats `UNKNOWN` as permission defeats this. The verdict is honest; the deployment must respect it |
+| T12 | Recovery after credential loss | `recovery` is `ATTESTED` only when a mechanism is declared, otherwise `UNKNOWN` | Any recovery path is also an impersonation path. The model records that a path exists; it does not evaluate its strength |
+| T13 | Authorship inflation: a relay or aggregator presenting a claim as its own | Role and degree are asserted and checked for internal consistency; a non-originator that claims origination is `REFUTED`; an absent role stays `UNKNOWN` | The role itself is an assertion about the world. A dishonest originator claim that is internally consistent is not detectable from the record |
+| T14 | Delegation laundering: manufacturing authority the issuer never granted | A delegation whose scope exceeds its ancestor scope is `REFUTED`; a chain from a revoked ancestor is `REFUTED`; an unattested link stays `UNKNOWN` | Ancestor state is as fresh as the evidence supplied. A chain can be truncated before publication, which is why a chain that misses a declared trust root stays `UNKNOWN` |
+| T15 | Identity merge through key rotation | An unattested rotation leaves the chain `UNKNOWN`; two key coordinates are not merged into one actor without attestation | The inverse also holds and is unaddressed: an actor can rotate to escape a reputation history, which this model cannot detect |
+| T16 | Privacy laundering through minimization | A minimization request that removes a required trust root makes the record `REJECTED`; a request that widens the claim boundary is `REJECTED` | An actor can still choose to publish less and accept a weaker verdict, which is the intended trade |
+
+## Falsification conditions
+
+This reference mechanism is refuted if any of the following can be demonstrated:
+
+- a record reaches `ACCEPTED_BOUNDED` while any property is `REFUTED`;
+- a `CONFLICTED` property is resolved to a favourable status by adding no new evidence;
+- `unlinkability`, `authorship_degree`, or `credential_ancestry` reaches `SUPPORTED`;
+- a non-originator role is read as first-party authorship;
+- a chain containing a revoked ancestor evaluates as anything other than a refutation;
+- absence of a required evidence coordinate produces a favourable property result;
+- a minimization request removes a required public coordinate, directly or through a
+ parent path, and the record still evaluates as anything other than `REJECTED`.
+
+These conditions are asserted in [`tests/test_zero_identity.py`](tests/test_zero_identity.py),
+including leaf-path and parent-path minimization fixtures.
+
+## What this threat model does not claim
+
+It does not claim that the profile provides anonymity, that it defeats correlation, or
+that it is safe to deploy. It claims only that the evaluator refuses to convert missing
+identity information into a favourable conclusion.
diff --git a/examples/zizk_artifact_first/zero_identity/evaluate.py b/examples/zizk_artifact_first/zero_identity/evaluate.py
new file mode 100644
index 0000000..61684a7
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/evaluate.py
@@ -0,0 +1,546 @@
+#!/usr/bin/env python3
+"""Terminology: Verifier Standard (VSTD); zero-identity/zero-knowledge (ZIZK).
+
+Bounded reference evaluator for identity disclosure under the ZIZK-VSTD architecture.
+
+Discharges no VSTD closure coordinate. This module is non-normative scaffolding for
+the terminology and safety question recorded in ``SEMANTIC_MODEL.md``: it decides
+which identity-adjacent properties a bounded disclosure record can support, and it
+fails closed everywhere else.
+
+The evaluator never verifies a signature, a revocation list, or a proof. It consumes
+*asserted* evidence coordinates and decides what may be concluded from them. Any
+cryptographic verification happens outside this module and enters here as evidence.
+"""
+
+from __future__ import annotations
+
+import copy
+from dataclasses import dataclass, field
+import json
+from pathlib import Path
+from typing import Any
+
+MODEL_FILE = Path(__file__).resolve().parent / "model" / "zero_identity_model.json"
+
+SUPPORTED = "SUPPORTED"
+ATTESTED = "ATTESTED"
+ASSUMED = "ASSUMED"
+UNKNOWN = "UNKNOWN"
+CONFLICTED = "CONFLICTED"
+REFUTED = "REFUTED"
+UNSUPPORTED_BY_DESIGN = "UNSUPPORTED_BY_DESIGN"
+
+ACCEPTED_BOUNDED = "ACCEPTED_BOUNDED"
+REJECTED = "REJECTED"
+
+REQUIRED_PUBLIC_COORDINATES = (
+ "trust_roots",
+ "actor.pseudonym",
+ "actor.key_binding.key_id",
+ "actor.key_binding.trust_root",
+ "authorization.issuer",
+ "revocation.source",
+)
+
+
+def load_model() -> dict[str, Any]:
+ """Return the bounded non-normative machine-readable model."""
+
+ return json.loads(MODEL_FILE.read_text(encoding="utf-8"))
+
+
+@dataclass(frozen=True)
+class Evaluation:
+ """Result of evaluating one bounded disclosure record."""
+
+ verdict: str
+ properties: dict[str, str]
+ reasons: list[str] = field(default_factory=list)
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "verdict": self.verdict,
+ "properties": dict(self.properties),
+ "reasons": list(self.reasons),
+ }
+
+
+def _get(record: dict[str, Any], dotted: str) -> Any:
+ node: Any = record
+ for part in dotted.split("."):
+ if not isinstance(node, dict) or part not in node:
+ return None
+ node = node[part]
+ return node
+
+
+def _conflicted(record: dict[str, Any], prop: str) -> bool:
+ for entry in record.get("conflicts", []) or []:
+ if entry.get("property") == prop:
+ return True
+ return False
+
+
+def _evaluate_civil_identity(record: dict[str, Any], reasons: list[str]) -> str:
+ if _conflicted(record, "civil_identity"):
+ reasons.append("civil_identity: conflicting evidence retained as CONFLICTED")
+ return CONFLICTED
+ disclosed = _get(record, "actor.civil_identity")
+ if disclosed not in (None, "withheld"):
+ reasons.append("civil_identity: a disclosed value is outside this profile")
+ return CONFLICTED
+ reasons.append(
+ "civil_identity: withheld by profile; absence is neither anonymity nor unlinkability"
+ )
+ return UNSUPPORTED_BY_DESIGN
+
+
+def _evaluate_authentication(record: dict[str, Any], reasons: list[str]) -> str:
+ if _conflicted(record, "authentication"):
+ reasons.append("authentication: conflicting evidence retained as CONFLICTED")
+ return CONFLICTED
+ binding = _get(record, "actor.key_binding")
+ if not isinstance(binding, dict):
+ reasons.append("authentication: no key binding coordinate")
+ return UNKNOWN
+ if not _get(record, "actor.pseudonym"):
+ reasons.append("authentication: no pseudonymous actor coordinate")
+ return UNKNOWN
+ if not binding.get("key_id"):
+ reasons.append("authentication: no signing-key coordinate")
+ return UNKNOWN
+ if binding.get("key_compromised_during_interval") is True:
+ reasons.append("authentication: signing key reported compromised for the interval")
+ return REFUTED
+ verified = binding.get("signature_verified")
+ if verified is False:
+ reasons.append("authentication: asserted signature verification failed")
+ return REFUTED
+ if verified is not True:
+ reasons.append("authentication: signature verification result absent")
+ return UNKNOWN
+ root = binding.get("trust_root")
+ if root not in (record.get("trust_roots") or []):
+ reasons.append("authentication: key trust root is not among the declared trust roots")
+ return UNKNOWN
+ return SUPPORTED
+
+
+def _evaluate_authority_active(record: dict[str, Any], reasons: list[str]) -> str:
+ if _conflicted(record, "authority_active"):
+ reasons.append("authority_active: conflicting evidence retained as CONFLICTED")
+ return CONFLICTED
+ grant = record.get("authorization")
+ revocation = record.get("revocation")
+ if not isinstance(grant, dict):
+ reasons.append("authority_active: no authorization grant to evaluate")
+ return UNKNOWN
+ if not isinstance(revocation, dict) or not revocation.get("source"):
+ reasons.append("authority_active: no revocation source; absence is not liveness")
+ return UNKNOWN
+ state = revocation.get("state")
+ if state == "revoked":
+ reasons.append("authority_active: authority is revoked")
+ return REFUTED
+ if state != "active":
+ reasons.append("authority_active: revocation state is not asserted active")
+ return UNKNOWN
+ evaluated_at = record.get("evaluated_at")
+ not_before = grant.get("not_before")
+ not_after = grant.get("not_after")
+ if not (evaluated_at and not_before and not_after):
+ reasons.append("authority_active: validity window or evaluation instant absent")
+ return UNKNOWN
+ if not (not_before <= evaluated_at <= not_after):
+ reasons.append("authority_active: evaluation instant is outside the validity window")
+ return REFUTED
+ if not revocation.get("checked_at"):
+ reasons.append("authority_active: revocation check instant absent")
+ return UNKNOWN
+ return SUPPORTED
+
+
+def _evaluate_authorization(
+ record: dict[str, Any], authentication: str, authority: str, reasons: list[str]
+) -> str:
+ if _conflicted(record, "authorization"):
+ reasons.append("authorization: conflicting evidence retained as CONFLICTED")
+ return CONFLICTED
+ grant = record.get("authorization")
+ if not isinstance(grant, dict) or not grant.get("grant_id"):
+ reasons.append("authorization: no grant coordinate; missing authorization stays UNKNOWN")
+ return UNKNOWN
+ issuer = grant.get("issuer")
+ if not issuer:
+ reasons.append("authorization: no issuer coordinate")
+ return UNKNOWN
+ if issuer not in (record.get("trust_roots") or []):
+ reasons.append("authorization: issuer is not among the declared trust roots")
+ return UNKNOWN
+ if authority == REFUTED:
+ reasons.append("authorization: refuted because the authority is not active")
+ return REFUTED
+ if authentication == REFUTED:
+ reasons.append("authorization: refuted because authentication is refuted")
+ return REFUTED
+ if authentication != SUPPORTED or authority != SUPPORTED:
+ reasons.append("authorization: preconditions are not both SUPPORTED")
+ return UNKNOWN
+ scope = grant.get("scope") or []
+ claim_scope = record.get("claim_scope")
+ if not claim_scope:
+ reasons.append("authorization: record declares no claim scope to cover")
+ return UNKNOWN
+ if claim_scope not in scope:
+ reasons.append("authorization: grant scope does not cover the claim scope")
+ return REFUTED
+ return SUPPORTED
+
+
+def _evaluate_freshness(record: dict[str, Any], reasons: list[str]) -> str:
+ freshness = record.get("freshness") or {}
+ if not freshness.get("required"):
+ reasons.append("freshness: not required by this record; replay is not excluded")
+ return UNKNOWN
+ nonce = freshness.get("nonce")
+ if not nonce or not freshness.get("challenge_source"):
+ reasons.append("freshness: required but the challenge coordinate is absent; fails closed")
+ return REFUTED
+ if nonce in (freshness.get("previously_observed_nonces") or []):
+ reasons.append("freshness: challenge value was previously observed; replay detected")
+ return REFUTED
+ return SUPPORTED
+
+
+def _evaluate_uniqueness(record: dict[str, Any], reasons: list[str]) -> str:
+ if _conflicted(record, "uniqueness"):
+ reasons.append("uniqueness: conflicting evidence retained as CONFLICTED")
+ return CONFLICTED
+ evidence = record.get("uniqueness_evidence") or []
+ if not [entry for entry in evidence if entry.get("attested_by")]:
+ reasons.append(
+ "uniqueness: no attested mechanism; absence does not imply Sybil resistance"
+ )
+ return UNKNOWN
+ return ATTESTED
+
+
+def _evaluate_independence(record: dict[str, Any], reasons: list[str]) -> str:
+ if _conflicted(record, "verifier_independence"):
+ reasons.append("verifier_independence: conflicting evidence retained as CONFLICTED")
+ return CONFLICTED
+ peers = record.get("peer_receipts") or []
+ if not peers:
+ reasons.append("verifier_independence: no peer receipt to compare; independence UNKNOWN")
+ return UNKNOWN
+ own = _get(record, "actor.pseudonym")
+ for peer in peers:
+ if peer.get("pseudonym") == own:
+ reasons.append(
+ "verifier_independence: peer shares this pseudonymous coordinate; the "
+ "coordinate cannot supply independent corroboration, but credential sharing "
+ "means actor independence remains UNKNOWN"
+ )
+ return UNKNOWN
+ evidence = record.get("independence_evidence") or []
+ attested = [
+ entry
+ for entry in evidence
+ if entry.get("attested_by") and entry.get("distinct_trust_root")
+ ]
+ if not attested:
+ reasons.append(
+ "verifier_independence: distinct pseudonyms are not evidence of distinct actors"
+ )
+ return UNKNOWN
+ return ATTESTED
+
+
+AUTHORSHIP_ROLES = ("ORIGINATOR", "DELEGATE", "RELAY", "AGGREGATOR")
+
+
+def _evaluate_authorship_degree(record: dict[str, Any], reasons: list[str]) -> str:
+ """Decide how far the signing party sits from the origin of the claim.
+
+ Degree is asserted, never inferred. An absent role does not default to
+ ORIGINATOR, and a relay is never readable as first-party authorship.
+ """
+
+ if _conflicted(record, "authorship_degree"):
+ reasons.append("authorship_degree: conflicting evidence retained as CONFLICTED")
+ return CONFLICTED
+ authorship = record.get("authorship")
+ if not isinstance(authorship, dict):
+ reasons.append(
+ "authorship_degree: no authorship coordinate; a signer is not assumed to be an author"
+ )
+ return UNKNOWN
+ role = authorship.get("role")
+ degree = authorship.get("degree")
+ if role not in AUTHORSHIP_ROLES or type(degree) is not int or degree < 0:
+ reasons.append("authorship_degree: role or degree absent or unrecognised")
+ return UNKNOWN
+ if (role == "ORIGINATOR") != (degree == 0):
+ reasons.append("authorship_degree: declared role and declared degree disagree")
+ return CONFLICTED
+ chain = record.get("credential_ancestry") or []
+ delegations = [link for link in chain if link.get("link_type") == "delegation"]
+ if chain and degree != len(delegations):
+ reasons.append(
+ "authorship_degree: declared degree disagrees with the number of recorded "
+ "delegation hops"
+ )
+ return CONFLICTED
+ if role != "ORIGINATOR" and "authorship_origination" in (
+ record.get("claimed_properties") or []
+ ):
+ reasons.append(
+ f"authorship_degree: a {role} record claims origination; relayed authorship "
+ "is not first-party authorship"
+ )
+ return REFUTED
+ if not authorship.get("attested_by"):
+ reasons.append("authorship_degree: role is declared but not attested")
+ return UNKNOWN
+ return ATTESTED
+
+
+def _evaluate_credential_ancestry(record: dict[str, Any], reasons: list[str]) -> str:
+ """Decide what the recorded chain from a trust root to this credential supports.
+
+ The chain records ancestry; it does not by itself establish that authority
+ survived every hop. An unattested link stays UNKNOWN, and a revoked ancestor
+ refutes the chain rather than leaving it merely uncertain.
+ """
+
+ if _conflicted(record, "credential_ancestry"):
+ reasons.append("credential_ancestry: conflicting evidence retained as CONFLICTED")
+ return CONFLICTED
+ chain = record.get("credential_ancestry")
+ if not chain:
+ reasons.append(
+ "credential_ancestry: no recorded chain; an authority origin is not assumed"
+ )
+ return UNKNOWN
+ for link in chain:
+ if link.get("parent_state") == "revoked":
+ reasons.append(
+ "credential_ancestry: a recorded ancestor is revoked; authority does not "
+ "survive delegation from a revoked ancestor"
+ )
+ return REFUTED
+ parent_scope = link.get("parent_scope")
+ child_scope = link.get("child_scope")
+ if parent_scope is not None and child_scope is not None:
+ if not set(child_scope) <= set(parent_scope):
+ reasons.append(
+ "credential_ancestry: a delegation widens scope beyond its ancestor"
+ )
+ return REFUTED
+ if not all(link.get("attested_by") for link in chain):
+ reasons.append(
+ "credential_ancestry: a recorded link is unattested; an unattested chain is "
+ "not a verified chain"
+ )
+ return UNKNOWN
+ if chain[0].get("parent") not in (record.get("trust_roots") or []):
+ reasons.append(
+ "credential_ancestry: the chain does not begin at a declared trust root"
+ )
+ return UNKNOWN
+ for older, newer in zip(chain, chain[1:]):
+ if older.get("child") != newer.get("parent"):
+ reasons.append("credential_ancestry: the recorded chain is not contiguous")
+ return CONFLICTED
+ if chain[-1].get("child") != _get(record, "actor.key_binding.key_id"):
+ reasons.append(
+ "credential_ancestry: the chain does not terminate at the signing key"
+ )
+ return UNKNOWN
+ if any(
+ link.get("link_type") == "rotation" and not link.get("same_actor_attested_by")
+ for link in chain
+ ):
+ reasons.append(
+ "credential_ancestry: an unattested rotation does not merge two key "
+ "coordinates into one actor"
+ )
+ return UNKNOWN
+ return ATTESTED
+
+
+def _evaluate_unlinkability(record: dict[str, Any], reasons: list[str]) -> str:
+ request = record.get("disclosure_minimization") or {}
+ if not request:
+ reasons.append("unlinkability: not requested")
+ return UNKNOWN
+ if not request.get("declared_assumptions"):
+ reasons.append("unlinkability: requested without declared assumptions")
+ return UNKNOWN
+ reasons.append(
+ "unlinkability: ASSUMED under declared assumptions only; this model cannot observe "
+ "the correlation surface available to an adversary"
+ )
+ return ASSUMED
+
+
+def _evaluate_accountability(record: dict[str, Any], reasons: list[str]) -> str:
+ if not record.get("escalation_authority"):
+ reasons.append("accountability: no escalation authority bound to the pseudonym")
+ return UNKNOWN
+ return ATTESTED
+
+
+def _evaluate_recovery(record: dict[str, Any], reasons: list[str]) -> str:
+ recovery = record.get("recovery") or {}
+ if not recovery.get("mechanism"):
+ reasons.append("recovery: no credential-loss recovery mechanism declared")
+ return UNKNOWN
+ return ATTESTED
+
+
+def _apply_minimization(record: dict[str, Any]) -> dict[str, Any]:
+ """Return a copy of the record with every withheld coordinate actually removed.
+
+ Minimization is enforced rather than trusted: a coordinate the actor asked to
+ withhold is deleted before evaluation, so a removed trust root really does make
+ the dependent property unevaluable instead of quietly remaining available.
+ """
+
+ request = record.get("disclosure_minimization") or {}
+ withheld = request.get("withheld_coordinates") or []
+ if not withheld:
+ return record
+ reduced = copy.deepcopy(record)
+ for dotted in withheld:
+ parts = dotted.split(".")
+ node: Any = reduced
+ for part in parts[:-1]:
+ if not isinstance(node, dict) or part not in node:
+ node = None
+ break
+ node = node[part]
+ if isinstance(node, dict):
+ node.pop(parts[-1], None)
+ return reduced
+
+
+def _removes_coordinate(withheld: str, required: str) -> bool:
+ """Return whether withholding a path removes a required coordinate.
+
+ Withholding ``actor.key_binding`` removes its ``trust_root`` child just as surely as
+ naming the leaf itself. Descendant paths do not remove their parent coordinate.
+ """
+
+ return withheld == required or required.startswith(withheld + ".")
+
+
+def _check_structural_rejections(record: dict[str, Any], reasons: list[str]) -> list[str]:
+ """Return the reasons that make a record unevaluable, that is, REJECTED outright."""
+
+ fatal: list[str] = []
+ request = record.get("disclosure_minimization") or {}
+ withheld = set(request.get("withheld_coordinates") or [])
+ for coordinate in REQUIRED_PUBLIC_COORDINATES:
+ removing_path = next(
+ (
+ path
+ for path in withheld
+ if isinstance(path, str) and _removes_coordinate(path, coordinate)
+ ),
+ None,
+ )
+ if removing_path is not None:
+ fatal.append(
+ f"minimization path {removing_path} removed required public coordinate "
+ f"{coordinate}; "
+ "disclosure minimization cannot erase coordinates required for bounded "
+ "reverification"
+ )
+ before = request.get("claim_boundary_before")
+ after = request.get("claim_boundary_after")
+ if before is not None and after is not None:
+ before_set = set(before if isinstance(before, list) else [before])
+ after_set = set(after if isinstance(after, list) else [after])
+ if not after_set <= before_set:
+ fatal.append(
+ "minimization widened the claim boundary; minimization may only narrow it"
+ )
+ reasons.extend(fatal)
+ return fatal
+
+
+def evaluate(record: dict[str, Any]) -> Evaluation:
+ """Evaluate one bounded disclosure record, failing closed on missing coordinates."""
+
+ reasons: list[str] = []
+ fatal = _check_structural_rejections(record, reasons)
+ record = _apply_minimization(record)
+
+ properties: dict[str, str] = {}
+ properties["civil_identity"] = _evaluate_civil_identity(record, reasons)
+ properties["authentication"] = _evaluate_authentication(record, reasons)
+ properties["authority_active"] = _evaluate_authority_active(record, reasons)
+ properties["authorization"] = _evaluate_authorization(
+ record, properties["authentication"], properties["authority_active"], reasons
+ )
+ properties["freshness"] = _evaluate_freshness(record, reasons)
+ properties["uniqueness"] = _evaluate_uniqueness(record, reasons)
+ properties["verifier_independence"] = _evaluate_independence(record, reasons)
+ properties["unlinkability"] = _evaluate_unlinkability(record, reasons)
+ properties["authorship_degree"] = _evaluate_authorship_degree(record, reasons)
+ properties["credential_ancestry"] = _evaluate_credential_ancestry(record, reasons)
+ properties["accountability"] = _evaluate_accountability(record, reasons)
+ properties["recovery"] = _evaluate_recovery(record, reasons)
+
+ if fatal:
+ return Evaluation(REJECTED, properties, reasons)
+
+ values = set(properties.values())
+ if REFUTED in values:
+ verdict = REJECTED
+ elif CONFLICTED in values:
+ verdict = CONFLICTED
+ elif properties["authorization"] == SUPPORTED and properties["authentication"] == SUPPORTED:
+ verdict = ACCEPTED_BOUNDED
+ else:
+ verdict = UNKNOWN
+
+ unmet = [
+ name
+ for name in (record.get("claimed_properties") or [])
+ if properties.get(name, UNKNOWN) not in (SUPPORTED, ATTESTED)
+ ]
+ if unmet and verdict == ACCEPTED_BOUNDED:
+ reasons.append(
+ "verdict: claimed properties "
+ + ", ".join(sorted(unmet))
+ + " are not supported; the record stays UNKNOWN rather than widening"
+ )
+ verdict = UNKNOWN
+ return Evaluation(verdict, properties, reasons)
+
+
+def evaluate_file(path: Path) -> Evaluation:
+ """Evaluate the ``record`` object stored in a fixture file."""
+
+ fixture = json.loads(Path(path).read_text(encoding="utf-8"))
+ return evaluate(fixture["record"])
+
+
+def main(argv: list[str] | None = None) -> int:
+ import sys
+
+ args = list(sys.argv[1:] if argv is None else argv)
+ if not args:
+ print("usage: evaluate.py FIXTURE [FIXTURE ...]")
+ return 2
+ for raw in args:
+ result = evaluate_file(Path(raw))
+ print(json.dumps({"fixture": raw, **result.to_dict()}, indent=2, sort_keys=True))
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/conflicted_authorship_degree_vs_chain.json b/examples/zizk_artifact_first/zero_identity/fixtures/conflicted_authorship_degree_vs_chain.json
new file mode 100644
index 0000000..aa8d88f
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/conflicted_authorship_degree_vs_chain.json
@@ -0,0 +1,96 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "SUPPORTED",
+ "authorship_degree": "CONFLICTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "CONFLICTED"
+ },
+ "falsification_question": "Is the more convenient of two disagreeing degree claims preferred?",
+ "fixture_id": "conflicted_authorship_degree_vs_chain",
+ "intent": "A declared degree that disagrees with the recorded chain stays CONFLICTED.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:delegate-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 2,
+ "role": "DELEGATE"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorization"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ },
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:delegate-1",
+ "child_scope": [
+ "vstd4-refutation-run"
+ ],
+ "link_type": "delegation",
+ "parent": "key:alpha-1",
+ "parent_scope": [
+ "vstd4-refutation-run"
+ ]
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-authorship-degree-conflict",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/conflicted_identity_evidence.json b/examples/zizk_artifact_first/zero_identity/fixtures/conflicted_identity_evidence.json
new file mode 100644
index 0000000..833c7ef
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/conflicted_identity_evidence.json
@@ -0,0 +1,92 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "SUPPORTED",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "CONFLICTED",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "CONFLICTED"
+ },
+ "falsification_question": "Can a conflict be resolved by preferring the convenient source?",
+ "fixture_id": "conflicted_identity_evidence",
+ "intent": "Conflicting identity evidence is retained as CONFLICTED.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:alpha-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 0,
+ "role": "ORIGINATOR"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorization"
+ ],
+ "conflicts": [
+ {
+ "evidence": [
+ "issuer directory binds this pseudonym to one subject",
+ "operator attestation binds the same pseudonym to a different subject"
+ ],
+ "property": "civil_identity"
+ }
+ ],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-conflicted",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/positive_bounded_authorization.json b/examples/zizk_artifact_first/zero_identity/fixtures/positive_bounded_authorization.json
new file mode 100644
index 0000000..fef352d
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/positive_bounded_authorization.json
@@ -0,0 +1,84 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "SUPPORTED",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "ACCEPTED_BOUNDED"
+ },
+ "falsification_question": "Does withholding civil identity remove the ability to reverify authorization?",
+ "fixture_id": "positive_bounded_authorization",
+ "intent": "Civil identity is withheld while a bounded authorization coordinate stays verifiable.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:alpha-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 0,
+ "role": "ORIGINATOR"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorization"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-positive-1",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/positive_minimized_boundary_narrowed.json b/examples/zizk_artifact_first/zero_identity/fixtures/positive_minimized_boundary_narrowed.json
new file mode 100644
index 0000000..797dc3e
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/positive_minimized_boundary_narrowed.json
@@ -0,0 +1,100 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "SUPPORTED",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "ASSUMED",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "ACCEPTED_BOUNDED"
+ },
+ "falsification_question": "Does narrowing disclosure silently weaken the retained claim?",
+ "fixture_id": "positive_minimized_boundary_narrowed",
+ "intent": "Minimization that narrows the boundary keeps the bounded authorization result.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:alpha-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 0,
+ "role": "ORIGINATOR"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorization"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ }
+ ],
+ "disclosure_minimization": {
+ "claim_boundary_after": [
+ "vstd4-refutation-run"
+ ],
+ "claim_boundary_before": [
+ "vstd4-refutation-run",
+ "vstd4-availability-run"
+ ],
+ "declared_assumptions": [
+ "issuer does not collude with the verifier"
+ ],
+ "requested_by": "actor",
+ "withheld_coordinates": [
+ "actor.civil_identity"
+ ]
+ },
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-minimized-narrowed",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/rejected_delegation_widens_scope.json b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_delegation_widens_scope.json
new file mode 100644
index 0000000..a995ec5
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_delegation_widens_scope.json
@@ -0,0 +1,97 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "SUPPORTED",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "REFUTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "REJECTED"
+ },
+ "falsification_question": "Can delegation manufacture authority the issuer never granted?",
+ "fixture_id": "rejected_delegation_widens_scope",
+ "intent": "A delegation may not carry a scope its ancestor did not hold.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:delegate-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 1,
+ "role": "DELEGATE"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorization"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ },
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:delegate-1",
+ "child_scope": [
+ "vstd4-refutation-run",
+ "vstd4-availability-run"
+ ],
+ "link_type": "delegation",
+ "parent": "key:alpha-1",
+ "parent_scope": [
+ "vstd4-refutation-run"
+ ]
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-ancestry-scope-escalation",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/rejected_expired_authority.json b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_expired_authority.json
new file mode 100644
index 0000000..9eee4cf
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_expired_authority.json
@@ -0,0 +1,84 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "REFUTED",
+ "authorization": "REFUTED",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "REJECTED"
+ },
+ "falsification_question": "Does an expired window silently remain usable?",
+ "fixture_id": "rejected_expired_authority",
+ "intent": "An evaluation instant outside the validity window refutes the authority.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:alpha-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 0,
+ "role": "ORIGINATOR"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorization"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2027-02-01T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-expired",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2027-02-01T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/rejected_key_compromise.json b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_key_compromise.json
new file mode 100644
index 0000000..5bc7443
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_key_compromise.json
@@ -0,0 +1,85 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "REFUTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "REFUTED",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "REJECTED"
+ },
+ "falsification_question": "Does a syntactically valid signature survive key compromise?",
+ "fixture_id": "rejected_key_compromise",
+ "intent": "A key reported compromised for the signing interval refutes authentication.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_compromised_during_interval": true,
+ "key_id": "key:alpha-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 0,
+ "role": "ORIGINATOR"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorization"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-key-compromise",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/rejected_minimization_erases_key_binding.json b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_minimization_erases_key_binding.json
new file mode 100644
index 0000000..ff4c50d
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_minimization_erases_key_binding.json
@@ -0,0 +1,99 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "UNKNOWN",
+ "authority_active": "SUPPORTED",
+ "authorization": "UNKNOWN",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "UNKNOWN",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "ASSUMED",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "REJECTED"
+ },
+ "falsification_question": "Can minimization bypass a protected leaf by deleting its parent object?",
+ "fixture_id": "rejected_minimization_erases_key_binding",
+ "intent": "Withholding actor.key_binding removes required key coordinates and is rejected.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:alpha-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 0,
+ "role": "ORIGINATOR"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorization"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ }
+ ],
+ "disclosure_minimization": {
+ "claim_boundary_after": [
+ "vstd4-refutation-run"
+ ],
+ "claim_boundary_before": [
+ "vstd4-refutation-run"
+ ],
+ "declared_assumptions": [
+ "issuer does not collude with the verifier"
+ ],
+ "requested_by": "actor",
+ "withheld_coordinates": [
+ "actor.key_binding"
+ ]
+ },
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-minimization-parent-path",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/rejected_minimization_widens_boundary.json b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_minimization_widens_boundary.json
new file mode 100644
index 0000000..a7432af
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_minimization_widens_boundary.json
@@ -0,0 +1,100 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "SUPPORTED",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "ASSUMED",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "REJECTED"
+ },
+ "falsification_question": "Can redaction be used to enlarge what a receipt asserts?",
+ "fixture_id": "rejected_minimization_widens_boundary",
+ "intent": "Disclosure minimization may only narrow the claim boundary.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:alpha-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 0,
+ "role": "ORIGINATOR"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorization"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ }
+ ],
+ "disclosure_minimization": {
+ "claim_boundary_after": [
+ "vstd4-refutation-run",
+ "vstd4-availability-run"
+ ],
+ "claim_boundary_before": [
+ "vstd4-refutation-run"
+ ],
+ "declared_assumptions": [
+ "issuer does not collude with the verifier"
+ ],
+ "requested_by": "actor",
+ "withheld_coordinates": [
+ "actor.civil_identity"
+ ]
+ },
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-boundary-widened",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/rejected_missing_challenge.json b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_missing_challenge.json
new file mode 100644
index 0000000..b13274f
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_missing_challenge.json
@@ -0,0 +1,82 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "SUPPORTED",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "REFUTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "REJECTED"
+ },
+ "falsification_question": "Does an absent nonce read as freshness?",
+ "fixture_id": "rejected_missing_challenge",
+ "intent": "Required freshness with no challenge coordinate fails closed.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:alpha-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 0,
+ "role": "ORIGINATOR"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorization"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-missing-challenge",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/rejected_relay_claims_origination.json b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_relay_claims_origination.json
new file mode 100644
index 0000000..5581fc8
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_relay_claims_origination.json
@@ -0,0 +1,96 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "SUPPORTED",
+ "authorship_degree": "REFUTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "REJECTED"
+ },
+ "falsification_question": "Can a relay present a claim as its own?",
+ "fixture_id": "rejected_relay_claims_origination",
+ "intent": "A relayed claim is not first-party authorship.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:delegate-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 1,
+ "role": "RELAY"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorship_origination"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ },
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:delegate-1",
+ "child_scope": [
+ "vstd4-refutation-run"
+ ],
+ "link_type": "delegation",
+ "parent": "key:alpha-1",
+ "parent_scope": [
+ "vstd4-refutation-run"
+ ]
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-authorship-relay",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/rejected_replayed_challenge.json b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_replayed_challenge.json
new file mode 100644
index 0000000..f0ee98b
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_replayed_challenge.json
@@ -0,0 +1,86 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "SUPPORTED",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "REFUTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "REJECTED"
+ },
+ "falsification_question": "Is a reused challenge indistinguishable from a fresh one?",
+ "fixture_id": "rejected_replayed_challenge",
+ "intent": "A previously observed challenge value is a detected replay.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:alpha-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 0,
+ "role": "ORIGINATOR"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorization"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [
+ "challenge:0001"
+ ],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-replay",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/rejected_revoked_ancestor.json b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_revoked_ancestor.json
new file mode 100644
index 0000000..02e5494
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_revoked_ancestor.json
@@ -0,0 +1,97 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "SUPPORTED",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "REFUTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "REJECTED"
+ },
+ "falsification_question": "Does a revoked ancestor leave its descendants merely uncertain?",
+ "fixture_id": "rejected_revoked_ancestor",
+ "intent": "Authority does not survive delegation from a revoked ancestor.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:delegate-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 1,
+ "role": "DELEGATE"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorization"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ },
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:delegate-1",
+ "child_scope": [
+ "vstd4-refutation-run"
+ ],
+ "link_type": "delegation",
+ "parent": "key:alpha-1",
+ "parent_scope": [
+ "vstd4-refutation-run"
+ ],
+ "parent_state": "revoked"
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-ancestry-revoked",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/rejected_revoked_authority.json b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_revoked_authority.json
new file mode 100644
index 0000000..92200d2
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_revoked_authority.json
@@ -0,0 +1,84 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "REFUTED",
+ "authorization": "REFUTED",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "REJECTED"
+ },
+ "falsification_question": "Can a revoked grant still be treated as active?",
+ "fixture_id": "rejected_revoked_authority",
+ "intent": "Revoked authority is refuted rather than degraded to UNKNOWN.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:alpha-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 0,
+ "role": "ORIGINATOR"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorization"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-revoked",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "revoked"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/rejected_unlinkability_erases_trust_root.json b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_unlinkability_erases_trust_root.json
new file mode 100644
index 0000000..c2ce20b
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/rejected_unlinkability_erases_trust_root.json
@@ -0,0 +1,99 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "UNKNOWN",
+ "authorization": "UNKNOWN",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "ASSUMED",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "REJECTED"
+ },
+ "falsification_question": "Can privacy be bought by deleting the revocation source?",
+ "fixture_id": "rejected_unlinkability_erases_trust_root",
+ "intent": "An unlinkability request may not remove a required trust-root coordinate.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:alpha-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 0,
+ "role": "ORIGINATOR"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorization"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ }
+ ],
+ "disclosure_minimization": {
+ "claim_boundary_after": [
+ "vstd4-refutation-run"
+ ],
+ "claim_boundary_before": [
+ "vstd4-refutation-run"
+ ],
+ "declared_assumptions": [
+ "issuer does not collude with the verifier"
+ ],
+ "requested_by": "actor",
+ "withheld_coordinates": [
+ "revocation.source"
+ ]
+ },
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-minimization-trust-root",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/unknown_absent_authorship.json b/examples/zizk_artifact_first/zero_identity/fixtures/unknown_absent_authorship.json
new file mode 100644
index 0000000..d76c339
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/unknown_absent_authorship.json
@@ -0,0 +1,79 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "SUPPORTED",
+ "authorship_degree": "UNKNOWN",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "UNKNOWN"
+ },
+ "falsification_question": "Does signing a record make you its author?",
+ "fixture_id": "unknown_absent_authorship",
+ "intent": "A signer is not assumed to be the author of the claim.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:alpha-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorship_degree"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-authorship-absent",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/unknown_distinct_pseudonyms.json b/examples/zizk_artifact_first/zero_identity/fixtures/unknown_distinct_pseudonyms.json
new file mode 100644
index 0000000..55f97f9
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/unknown_distinct_pseudonyms.json
@@ -0,0 +1,89 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "SUPPORTED",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "UNKNOWN"
+ },
+ "falsification_question": "Do two pseudonyms establish two actors?",
+ "fixture_id": "unknown_distinct_pseudonyms",
+ "intent": "Two distinct pseudonyms are not evidence of two distinct actors.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:alpha-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 0,
+ "role": "ORIGINATOR"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "verifier_independence"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [
+ {
+ "pseudonym": "pseudonym:beta",
+ "receipt_id": "peer:2"
+ }
+ ],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-independence-distinct",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/unknown_missing_authorization.json b/examples/zizk_artifact_first/zero_identity/fixtures/unknown_missing_authorization.json
new file mode 100644
index 0000000..ab9f6c3
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/unknown_missing_authorization.json
@@ -0,0 +1,75 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "UNKNOWN",
+ "authorization": "UNKNOWN",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "UNKNOWN"
+ },
+ "falsification_question": "Can a missing grant be read as permission?",
+ "fixture_id": "unknown_missing_authorization",
+ "intent": "A record with no authorization grant stays UNKNOWN and never fails open.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:alpha-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 0,
+ "role": "ORIGINATOR"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "authorization"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-unknown-authorization",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/unknown_shared_pseudonym_independence.json b/examples/zizk_artifact_first/zero_identity/fixtures/unknown_shared_pseudonym_independence.json
new file mode 100644
index 0000000..496039f
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/unknown_shared_pseudonym_independence.json
@@ -0,0 +1,89 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "SUPPORTED",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "UNKNOWN"
+ },
+ "falsification_question": "Does a repeated pseudonym supply independent corroboration?",
+ "fixture_id": "unknown_shared_pseudonym_independence",
+ "intent": "A shared pseudonymous coordinate cannot supply independent corroboration and does not establish how many actors use it.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:alpha-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 0,
+ "role": "ORIGINATOR"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "verifier_independence"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [
+ {
+ "pseudonym": "pseudonym:alpha",
+ "receipt_id": "peer:1"
+ }
+ ],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-independence-shared",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/unknown_unattested_ancestry_link.json b/examples/zizk_artifact_first/zero_identity/fixtures/unknown_unattested_ancestry_link.json
new file mode 100644
index 0000000..a5e02a0
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/unknown_unattested_ancestry_link.json
@@ -0,0 +1,95 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "SUPPORTED",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "UNKNOWN",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "UNKNOWN"
+ },
+ "falsification_question": "Does a written-down chain establish that authority survived every hop?",
+ "fixture_id": "unknown_unattested_ancestry_link",
+ "intent": "An unattested link in a recorded chain is not a verified chain.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:delegate-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 1,
+ "role": "DELEGATE"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "credential_ancestry"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ },
+ {
+ "child": "key:delegate-1",
+ "child_scope": [
+ "vstd4-refutation-run"
+ ],
+ "link_type": "delegation",
+ "parent": "key:alpha-1",
+ "parent_scope": [
+ "vstd4-refutation-run"
+ ]
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-ancestry-unattested",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/unknown_unattested_rotation.json b/examples/zizk_artifact_first/zero_identity/fixtures/unknown_unattested_rotation.json
new file mode 100644
index 0000000..5c42d87
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/unknown_unattested_rotation.json
@@ -0,0 +1,90 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "SUPPORTED",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "UNKNOWN",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "UNKNOWN"
+ },
+ "falsification_question": "Do two keys become one actor because a rotation was recorded?",
+ "fixture_id": "unknown_unattested_rotation",
+ "intent": "An unattested rotation does not merge two key coordinates into one actor.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:alpha-2",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 0,
+ "role": "ORIGINATOR"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "credential_ancestry"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ },
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-2",
+ "link_type": "rotation",
+ "parent": "key:alpha-1"
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-ancestry-rotation",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/fixtures/unknown_uniqueness_absent.json b/examples/zizk_artifact_first/zero_identity/fixtures/unknown_uniqueness_absent.json
new file mode 100644
index 0000000..8a42f63
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/fixtures/unknown_uniqueness_absent.json
@@ -0,0 +1,84 @@
+{
+ "expected": {
+ "properties": {
+ "accountability": "ATTESTED",
+ "authentication": "SUPPORTED",
+ "authority_active": "SUPPORTED",
+ "authorization": "SUPPORTED",
+ "authorship_degree": "ATTESTED",
+ "civil_identity": "UNSUPPORTED_BY_DESIGN",
+ "credential_ancestry": "ATTESTED",
+ "freshness": "SUPPORTED",
+ "recovery": "ATTESTED",
+ "uniqueness": "UNKNOWN",
+ "unlinkability": "UNKNOWN",
+ "verifier_independence": "UNKNOWN"
+ },
+ "verdict": "UNKNOWN"
+ },
+ "falsification_question": "Does the absence of duplicates prove there are none?",
+ "fixture_id": "unknown_uniqueness_absent",
+ "intent": "Absent uniqueness evidence does not imply Sybil resistance.",
+ "record": {
+ "actor": {
+ "civil_identity": "withheld",
+ "key_binding": {
+ "key_id": "key:alpha-1",
+ "signature_verified": true,
+ "trust_root": "root:issuer-a"
+ },
+ "pseudonym": "pseudonym:alpha"
+ },
+ "authorization": {
+ "grant_id": "grant:alpha-1",
+ "issuer": "root:issuer-a",
+ "not_after": "2026-12-31T00:00:00Z",
+ "not_before": "2026-01-01T00:00:00Z",
+ "scope": [
+ "vstd4-refutation-run"
+ ]
+ },
+ "authorship": {
+ "attested_by": "root:issuer-a",
+ "degree": 0,
+ "role": "ORIGINATOR"
+ },
+ "claim_scope": "vstd4-refutation-run",
+ "claimed_properties": [
+ "uniqueness"
+ ],
+ "conflicts": [],
+ "credential_ancestry": [
+ {
+ "attested_by": "root:issuer-a",
+ "child": "key:alpha-1",
+ "link_type": "issuance",
+ "parent": "root:issuer-a"
+ }
+ ],
+ "escalation_authority": "root:issuer-a",
+ "evaluated_at": "2026-08-23T00:00:00Z",
+ "freshness": {
+ "challenge_source": "verifier:v1",
+ "nonce": "challenge:0001",
+ "previously_observed_nonces": [],
+ "required": true
+ },
+ "independence_evidence": [],
+ "peer_receipts": [],
+ "profile": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "record_id": "zi-uniqueness-absent",
+ "recovery": {
+ "mechanism": "issuer reissue on quorum of two custodians"
+ },
+ "revocation": {
+ "checked_at": "2026-08-23T00:00:00Z",
+ "source": "root:issuer-a/status",
+ "state": "active"
+ },
+ "trust_roots": [
+ "root:issuer-a"
+ ],
+ "uniqueness_evidence": []
+ }
+}
diff --git a/examples/zizk_artifact_first/zero_identity/model/zero_identity_model.json b/examples/zizk_artifact_first/zero_identity/model/zero_identity_model.json
new file mode 100644
index 0000000..e2916d4
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/model/zero_identity_model.json
@@ -0,0 +1,311 @@
+{
+ "model_id": "zizk-vstd/bounded-identity-disclosure/reference-0",
+ "status": "REFERENCE_NON_NORMATIVE",
+ "normative": false,
+ "serialized_receipt_identifier": null,
+ "notes": [
+ "This bounded reference model carries no serialized receipt identifier, no schema $id route, and no receipt digest.",
+ "It does not alter, extend, or reinterpret any VSTD serialized receipt identifier.",
+ "Nothing in this model asserts a cryptographic guarantee. Cryptographic mechanisms named here are inputs whose verification is performed elsewhere and asserted as evidence."
+ ],
+ "terminology_decision": {
+ "public_label_zero_identity": "REJECTED_AS_UNQUALIFIED_PUBLIC_LABEL",
+ "accepted_label": "bounded identity disclosure",
+ "rationale": "The profile never removes identity; it withholds civil identity while retaining cryptographic and authorization coordinates. The architecture-wide zero-identity rule means only that identity or reputation alone cannot strengthen an artifact-bound result; it is not a privacy guarantee."
+ },
+ "identity_dimensions": [
+ "civil_identity",
+ "persistent_public_identity",
+ "key_or_credential_coordinate",
+ "authentication",
+ "authorization",
+ "accountability",
+ "attribution",
+ "authorship_degree",
+ "credential_ancestry",
+ "uniqueness",
+ "verifier_independence",
+ "revocation_or_expiry",
+ "confidentiality",
+ "unlinkability",
+ "anonymity_or_pseudonymity"
+ ],
+ "property_statuses": [
+ "SUPPORTED",
+ "ATTESTED",
+ "ASSUMED",
+ "UNKNOWN",
+ "CONFLICTED",
+ "REFUTED",
+ "UNSUPPORTED_BY_DESIGN"
+ ],
+ "verdicts": [
+ "ACCEPTED_BOUNDED",
+ "UNKNOWN",
+ "CONFLICTED",
+ "REJECTED"
+ ],
+ "verdict_aggregation": {
+ "terminal_property_results": [
+ "any REFUTED property makes the record REJECTED",
+ "otherwise any CONFLICTED property makes the record CONFLICTED"
+ ],
+ "acceptance_boundary": "otherwise authentication and authorization must both be SUPPORTED and every explicitly claimed property must be SUPPORTED or ATTESTED",
+ "ancillary_unknowns": "UNKNOWN on an unclaimed ancillary property remains visible and does not widen the ACCEPTED_BOUNDED authorization result",
+ "otherwise": "UNKNOWN"
+ },
+ "minimum_public_actor_coordinates": [
+ "actor.pseudonym",
+ "actor.key_binding.key_id",
+ "actor.key_binding.signature_verified",
+ "actor.key_binding.trust_root",
+ "authorization.grant_id",
+ "authorization.issuer",
+ "authorization.scope",
+ "authorization.not_before",
+ "authorization.not_after",
+ "revocation.source",
+ "revocation.state",
+ "revocation.checked_at",
+ "trust_roots"
+ ],
+ "optional_provenance_coordinates": [
+ "authorship.role",
+ "authorship.degree",
+ "authorship.attested_by",
+ "credential_ancestry[].parent",
+ "credential_ancestry[].child",
+ "credential_ancestry[].link_type",
+ "credential_ancestry[].attested_by"
+ ],
+ "prohibited_inferences": [
+ "absent civil identity implies anonymity",
+ "absent civil identity implies unlinkability",
+ "a pseudonym implies a distinct actor",
+ "a shared pseudonym implies a single actor",
+ "two distinct pseudonyms imply two independent actors",
+ "a verified signature implies authorization",
+ "an authorization grant implies that authority is currently active",
+ "absent revocation evidence implies active authority",
+ "absent uniqueness evidence implies Sybil resistance",
+ "hashing, redaction, encryption, omission, or pseudonymity alone implies zero identity",
+ "disclosure minimization preserves the original claim boundary",
+ "missing evidence implies safety",
+ "a signer is the author of the claim",
+ "a relayed or delegated claim is first-party authorship",
+ "an absent authorship role means degree zero",
+ "a recorded ancestry chain establishes that authority survived every hop",
+ "no ancestor marked revoked means every ancestor is valid",
+ "a key rotation link merges two key coordinates into one actor",
+ "a delegation may carry a scope its ancestor did not hold"
+ ],
+ "properties": {
+ "civil_identity": {
+ "profile_intent": "withheld",
+ "attainable_statuses": [
+ "UNSUPPORTED_BY_DESIGN",
+ "CONFLICTED"
+ ]
+ },
+ "authentication": {
+ "attainable_statuses": [
+ "SUPPORTED",
+ "REFUTED",
+ "UNKNOWN",
+ "CONFLICTED"
+ ],
+ "requires": [
+ "actor.key_binding.signature_verified",
+ "resolvable trust_root"
+ ]
+ },
+ "authority_active": {
+ "attainable_statuses": [
+ "SUPPORTED",
+ "REFUTED",
+ "UNKNOWN",
+ "CONFLICTED"
+ ],
+ "requires": [
+ "revocation.state",
+ "revocation.source",
+ "validity window containing evaluated_at"
+ ]
+ },
+ "authorization": {
+ "attainable_statuses": [
+ "SUPPORTED",
+ "REFUTED",
+ "UNKNOWN",
+ "CONFLICTED"
+ ],
+ "requires": [
+ "authentication SUPPORTED",
+ "authority_active SUPPORTED",
+ "scope covers claim_scope"
+ ]
+ },
+ "attribution": {
+ "attainable_statuses": [
+ "ATTESTED",
+ "UNKNOWN",
+ "CONFLICTED"
+ ],
+ "bound_to": "pseudonymous coordinate only, never civil identity"
+ },
+ "uniqueness": {
+ "attainable_statuses": [
+ "ATTESTED",
+ "UNKNOWN",
+ "CONFLICTED"
+ ],
+ "default_when_absent": "UNKNOWN"
+ },
+ "verifier_independence": {
+ "attainable_statuses": [
+ "ATTESTED",
+ "UNKNOWN",
+ "CONFLICTED"
+ ],
+ "default_when_absent": "UNKNOWN"
+ },
+ "freshness": {
+ "attainable_statuses": [
+ "SUPPORTED",
+ "REFUTED",
+ "UNKNOWN"
+ ],
+ "fail_closed_when_required_and_absent": true
+ },
+ "unlinkability": {
+ "attainable_statuses": [
+ "ASSUMED",
+ "UNKNOWN",
+ "REFUTED"
+ ],
+ "never": "SUPPORTED",
+ "reason": "This model observes one record at a time and cannot observe the adversary's full correlation surface."
+ },
+ "accountability": {
+ "attainable_statuses": [
+ "ATTESTED",
+ "UNKNOWN"
+ ],
+ "requires": [
+ "a named escalation authority that can act on the pseudonymous coordinate"
+ ]
+ },
+ "confidentiality": {
+ "attainable_statuses": [
+ "ASSUMED",
+ "UNKNOWN"
+ ],
+ "reason": "Transport and storage confidentiality are outside this record."
+ },
+ "recovery": {
+ "attainable_statuses": [
+ "ATTESTED",
+ "UNKNOWN"
+ ],
+ "default_when_absent": "UNKNOWN"
+ },
+ "authorship_degree": {
+ "attainable_statuses": [
+ "ATTESTED",
+ "REFUTED",
+ "UNKNOWN",
+ "CONFLICTED"
+ ],
+ "never": "SUPPORTED",
+ "roles": [
+ "ORIGINATOR",
+ "DELEGATE",
+ "RELAY",
+ "AGGREGATOR"
+ ],
+ "default_when_absent": "UNKNOWN",
+ "reason": "Authorship distance is an assertion about the world outside the record; this model can check it for internal consistency but cannot observe who wrote a claim."
+ },
+ "credential_ancestry": {
+ "attainable_statuses": [
+ "ATTESTED",
+ "REFUTED",
+ "UNKNOWN",
+ "CONFLICTED"
+ ],
+ "never": "SUPPORTED",
+ "default_when_absent": "UNKNOWN",
+ "reason": "The chain records ancestry. It does not by itself establish that authority survived every hop, mirroring the recorded-lineage discipline of VSTD-Graph-1."
+ }
+ },
+ "rules": [
+ {
+ "id": "ZI-R1",
+ "statement": "A missing coordinate yields UNKNOWN, never a favourable status."
+ },
+ {
+ "id": "ZI-R2",
+ "statement": "CONFLICTED is terminal for the property and propagates to the record verdict."
+ },
+ {
+ "id": "ZI-R3",
+ "statement": "Revoked or expired authority is REFUTED, never UNKNOWN."
+ },
+ {
+ "id": "ZI-R4",
+ "statement": "A shared pseudonymous coordinate cannot supply independent corroboration, but it leaves actor independence UNKNOWN because multiple actors may share one credential."
+ },
+ {
+ "id": "ZI-R5",
+ "statement": "Distinct pseudonymous coordinates leave both independence and actor-distinctness UNKNOWN."
+ },
+ {
+ "id": "ZI-R6",
+ "statement": "A minimization request that removes a required public coordinate, whether directly or through a parent path, makes the record unevaluable and is REJECTED."
+ },
+ {
+ "id": "ZI-R7",
+ "statement": "When freshness is required, an absent challenge coordinate fails closed and a replayed challenge is REFUTED."
+ },
+ {
+ "id": "ZI-R8",
+ "statement": "A claim boundary may only narrow under minimization; widening is REJECTED."
+ },
+ {
+ "id": "ZI-R9",
+ "statement": "A key marked compromised for the signing interval REFUTES authentication."
+ },
+ {
+ "id": "ZI-R10",
+ "statement": "unlinkability is never SUPPORTED by this model; at best it is ASSUMED under declared assumptions."
+ },
+ {
+ "id": "ZI-R11",
+ "statement": "Authorship degree is asserted, never inferred; an absent role stays UNKNOWN and never defaults to ORIGINATOR."
+ },
+ {
+ "id": "ZI-R12",
+ "statement": "A relay, delegate, or aggregator that claims origination is REFUTED."
+ },
+ {
+ "id": "ZI-R13",
+ "statement": "A revoked recorded ancestor REFUTES the chain; authority does not survive delegation from a revoked ancestor."
+ },
+ {
+ "id": "ZI-R14",
+ "statement": "A delegation whose scope exceeds its ancestor scope is REFUTED."
+ },
+ {
+ "id": "ZI-R15",
+ "statement": "An unattested link, a chain that misses a declared trust root, or a chain that misses the signing key stays UNKNOWN."
+ },
+ {
+ "id": "ZI-R16",
+ "statement": "An unattested rotation does not merge two key coordinates into one actor."
+ },
+ {
+ "id": "ZI-R17",
+ "statement": "A declared degree that disagrees with the recorded chain length is CONFLICTED."
+ }
+ ]
+}
diff --git a/examples/zizk_artifact_first/zero_identity/run_validation.py b/examples/zizk_artifact_first/zero_identity/run_validation.py
new file mode 100644
index 0000000..e195922
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/run_validation.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+"""Run the complete validation suite for this reference evaluator.
+
+Uses the standard library only, so it runs without pytest. When pytest is present,
+``python -m pytest examples/zizk_artifact_first/zero_identity/tests -q`` runs the same
+fixtures plus the inference-blocking assertions.
+"""
+
+from __future__ import annotations
+
+import json
+from pathlib import Path
+import sys
+
+HERE = Path(__file__).resolve().parent
+if str(HERE) not in sys.path:
+ sys.path.insert(0, str(HERE))
+
+from evaluate import evaluate # noqa: E402
+
+
+def main() -> int:
+ failures: list[str] = []
+ fixtures = sorted((HERE / "fixtures").glob("*.json"))
+ if not fixtures:
+ print("no fixtures found")
+ return 1
+ for path in fixtures:
+ fixture = json.loads(path.read_text(encoding="utf-8"))
+ outcome = evaluate(fixture["record"])
+ expected = fixture["expected"]
+ if outcome.verdict != expected["verdict"]:
+ failures.append(
+ f"{path.name}: verdict {outcome.verdict} != {expected['verdict']}"
+ )
+ for name, want in expected["properties"].items():
+ got = outcome.properties.get(name)
+ if got != want:
+ failures.append(f"{path.name}: {name} {got} != {want}")
+ print(f"{outcome.verdict:<17} {path.stem}")
+ for failure in failures:
+ print(f"FAIL {failure}")
+ print(f"{len(fixtures)} fixtures, {len(failures)} failures")
+ return 1 if failures else 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/examples/zizk_artifact_first/zero_identity/tests/test_zero_identity.py b/examples/zizk_artifact_first/zero_identity/tests/test_zero_identity.py
new file mode 100644
index 0000000..1d2d465
--- /dev/null
+++ b/examples/zizk_artifact_first/zero_identity/tests/test_zero_identity.py
@@ -0,0 +1,310 @@
+"""Terminology: Verifier Standard (VSTD).
+
+Validation suite for the bounded identity disclosure reference evaluator.
+
+Each test names the inference it exists to block. A test that starts passing because
+a status was upgraded to something more favourable is a defect, not a fix.
+"""
+
+from __future__ import annotations
+
+import json
+from pathlib import Path
+import sys
+
+import pytest
+
+REFERENCE_SURFACE = Path(__file__).resolve().parents[1]
+if str(REFERENCE_SURFACE) not in sys.path:
+ sys.path.insert(0, str(REFERENCE_SURFACE))
+
+from evaluate import ( # noqa: E402
+ ACCEPTED_BOUNDED,
+ ATTESTED,
+ CONFLICTED,
+ REFUTED,
+ REJECTED,
+ SUPPORTED,
+ UNKNOWN,
+ evaluate,
+ load_model,
+)
+
+FIXTURES = sorted((REFERENCE_SURFACE / "fixtures").glob("*.json"))
+
+
+def load(name: str) -> dict:
+ return json.loads(
+ (REFERENCE_SURFACE / "fixtures" / f"{name}.json").read_text(encoding="utf-8")
+ )
+
+
+def result(name: str):
+ return evaluate(load(name)["record"])
+
+
+def test_fixture_corpus_is_non_empty() -> None:
+ assert FIXTURES, "the fixture corpus must not be empty"
+
+
+@pytest.mark.parametrize("path", FIXTURES, ids=lambda p: p.stem)
+def test_fixture_matches_declared_expectation(path: Path) -> None:
+ fixture = json.loads(path.read_text(encoding="utf-8"))
+ outcome = evaluate(fixture["record"])
+ assert outcome.verdict == fixture["expected"]["verdict"]
+ assert outcome.properties == fixture["expected"]["properties"]
+ assert outcome.reasons, "every evaluation must carry at least one stated reason"
+
+
+def test_civil_identity_withheld_keeps_authorization_verifiable() -> None:
+ outcome = result("positive_bounded_authorization")
+ assert outcome.verdict == ACCEPTED_BOUNDED
+ assert outcome.properties["civil_identity"] == "UNSUPPORTED_BY_DESIGN"
+ assert outcome.properties["authorization"] == SUPPORTED
+
+
+def test_bounded_acceptance_does_not_imply_uniqueness_or_independence() -> None:
+ outcome = result("positive_bounded_authorization")
+ assert outcome.properties["uniqueness"] == UNKNOWN
+ assert outcome.properties["verifier_independence"] == UNKNOWN
+ assert outcome.properties["unlinkability"] == UNKNOWN
+
+
+def test_missing_authorization_stays_unknown() -> None:
+ outcome = result("unknown_missing_authorization")
+ assert outcome.verdict == UNKNOWN
+ assert outcome.properties["authorization"] == UNKNOWN
+
+
+def test_revoked_authority_is_refuted_not_unknown() -> None:
+ outcome = result("rejected_revoked_authority")
+ assert outcome.verdict == REJECTED
+ assert outcome.properties["authority_active"] == REFUTED
+
+
+def test_expired_authority_is_refuted() -> None:
+ outcome = result("rejected_expired_authority")
+ assert outcome.properties["authority_active"] == REFUTED
+
+
+def test_shared_pseudonym_does_not_establish_actor_independence_or_nonindependence() -> None:
+ outcome = result("unknown_shared_pseudonym_independence")
+ assert outcome.properties["verifier_independence"] == UNKNOWN
+ assert outcome.verdict == UNKNOWN
+
+
+def test_distinct_pseudonyms_do_not_establish_distinct_actors() -> None:
+ outcome = result("unknown_distinct_pseudonyms")
+ assert outcome.properties["verifier_independence"] == UNKNOWN
+ assert outcome.verdict == UNKNOWN
+
+
+def test_minimization_cannot_delete_a_required_trust_root() -> None:
+ outcome = result("rejected_unlinkability_erases_trust_root")
+ assert outcome.verdict == REJECTED
+ assert outcome.properties["authority_active"] == UNKNOWN
+ assert any("revocation.source" in reason for reason in outcome.reasons)
+
+
+def test_minimization_cannot_bypass_a_protected_leaf_by_deleting_its_parent() -> None:
+ outcome = result("rejected_minimization_erases_key_binding")
+ assert outcome.verdict == REJECTED
+ assert outcome.properties["authentication"] == UNKNOWN
+ assert any("actor.key_binding" in reason for reason in outcome.reasons)
+
+
+def test_replayed_challenge_is_detected() -> None:
+ outcome = result("rejected_replayed_challenge")
+ assert outcome.properties["freshness"] == REFUTED
+ assert outcome.verdict == REJECTED
+
+
+def test_required_freshness_without_a_challenge_fails_closed() -> None:
+ outcome = result("rejected_missing_challenge")
+ assert outcome.properties["freshness"] == REFUTED
+
+
+def test_absent_uniqueness_evidence_is_not_sybil_resistance() -> None:
+ outcome = result("unknown_uniqueness_absent")
+ assert outcome.properties["uniqueness"] == UNKNOWN
+ assert outcome.verdict == UNKNOWN
+
+
+def test_conflicting_identity_evidence_stays_conflicted() -> None:
+ outcome = result("conflicted_identity_evidence")
+ assert outcome.properties["civil_identity"] == CONFLICTED
+ assert outcome.verdict == CONFLICTED
+
+
+def test_minimization_may_not_widen_the_claim_boundary() -> None:
+ outcome = result("rejected_minimization_widens_boundary")
+ assert outcome.verdict == REJECTED
+ assert any("widened" in reason for reason in outcome.reasons)
+
+
+def test_minimization_that_narrows_keeps_the_bounded_result() -> None:
+ outcome = result("positive_minimized_boundary_narrowed")
+ assert outcome.verdict == ACCEPTED_BOUNDED
+ assert outcome.properties["unlinkability"] == "ASSUMED"
+
+
+def test_key_compromise_refutes_authentication() -> None:
+ outcome = result("rejected_key_compromise")
+ assert outcome.properties["authentication"] == REFUTED
+ assert outcome.verdict == REJECTED
+
+
+def test_unlinkability_is_never_supported() -> None:
+ for path in FIXTURES:
+ fixture = json.loads(path.read_text(encoding="utf-8"))
+ assert evaluate(fixture["record"]).properties["unlinkability"] != SUPPORTED
+
+
+def test_no_fixture_reaches_acceptance_with_a_refuted_property() -> None:
+ for path in FIXTURES:
+ outcome = evaluate(json.loads(path.read_text(encoding="utf-8"))["record"])
+ if REFUTED in outcome.properties.values():
+ assert outcome.verdict == REJECTED
+
+
+def test_accountability_requires_a_bound_escalation_authority() -> None:
+ record = load("positive_bounded_authorization")["record"]
+ assert evaluate(record).properties["accountability"] == ATTESTED
+ record.pop("escalation_authority")
+ assert evaluate(record).properties["accountability"] == UNKNOWN
+
+
+def test_recovery_absence_stays_unknown() -> None:
+ record = load("positive_bounded_authorization")["record"]
+ record.pop("recovery")
+ assert evaluate(record).properties["recovery"] == UNKNOWN
+
+
+def test_unknown_trust_root_does_not_authenticate() -> None:
+ record = load("positive_bounded_authorization")["record"]
+ record["trust_roots"] = ["root:other"]
+ outcome = evaluate(record)
+ assert outcome.properties["authentication"] == UNKNOWN
+ assert outcome.verdict == UNKNOWN
+
+
+@pytest.mark.parametrize("coordinate", ["pseudonym", "key_id", "issuer"])
+def test_required_public_identity_coordinates_cannot_be_omitted(coordinate: str) -> None:
+ record = load("positive_bounded_authorization")["record"]
+ if coordinate == "pseudonym":
+ record["actor"].pop("pseudonym")
+ elif coordinate == "key_id":
+ record["actor"]["key_binding"].pop("key_id")
+ else:
+ record["authorization"].pop("issuer")
+ outcome = evaluate(record)
+ assert outcome.verdict == UNKNOWN
+
+
+def test_undeclared_issuer_does_not_authorize() -> None:
+ record = load("positive_bounded_authorization")["record"]
+ record["authorization"]["issuer"] = "root:undeclared"
+ outcome = evaluate(record)
+ assert outcome.properties["authorization"] == UNKNOWN
+ assert outcome.verdict == UNKNOWN
+
+
+def test_scope_mismatch_is_refuted() -> None:
+ record = load("positive_bounded_authorization")["record"]
+ record["claim_scope"] = "vstd4-availability-run"
+ outcome = evaluate(record)
+ assert outcome.properties["authorization"] == REFUTED
+
+
+def test_signing_is_not_authorship() -> None:
+ outcome = result("unknown_absent_authorship")
+ assert outcome.properties["authorship_degree"] == UNKNOWN
+ assert outcome.verdict == UNKNOWN
+
+
+def test_relayed_claim_is_not_first_party_authorship() -> None:
+ outcome = result("rejected_relay_claims_origination")
+ assert outcome.properties["authorship_degree"] == REFUTED
+ assert outcome.verdict == REJECTED
+
+
+def test_declared_degree_must_agree_with_recorded_delegation_hops() -> None:
+ outcome = result("conflicted_authorship_degree_vs_chain")
+ assert outcome.properties["authorship_degree"] == CONFLICTED
+ assert outcome.verdict == CONFLICTED
+
+
+@pytest.mark.parametrize("degree", [True, -1])
+def test_authorship_degree_must_be_a_nonnegative_integer(degree: object) -> None:
+ record = load("positive_bounded_authorization")["record"]
+ record["authorship"]["degree"] = degree
+ assert evaluate(record).properties["authorship_degree"] == UNKNOWN
+
+
+def test_unattested_ancestry_link_is_not_a_verified_chain() -> None:
+ outcome = result("unknown_unattested_ancestry_link")
+ assert outcome.properties["credential_ancestry"] == UNKNOWN
+
+
+def test_authority_does_not_survive_a_revoked_ancestor() -> None:
+ outcome = result("rejected_revoked_ancestor")
+ assert outcome.properties["credential_ancestry"] == REFUTED
+ assert outcome.verdict == REJECTED
+
+
+def test_delegation_may_not_widen_scope_beyond_its_ancestor() -> None:
+ outcome = result("rejected_delegation_widens_scope")
+ assert outcome.properties["credential_ancestry"] == REFUTED
+
+
+def test_unattested_rotation_does_not_merge_two_key_coordinates() -> None:
+ outcome = result("unknown_unattested_rotation")
+ assert outcome.properties["credential_ancestry"] == UNKNOWN
+
+
+def test_absent_ancestry_chain_stays_unknown() -> None:
+ record = load("positive_bounded_authorization")["record"]
+ record.pop("credential_ancestry")
+ assert evaluate(record).properties["credential_ancestry"] == UNKNOWN
+
+
+def test_chain_must_terminate_at_the_signing_key() -> None:
+ record = load("positive_bounded_authorization")["record"]
+ record["credential_ancestry"][0]["child"] = "key:someone-else"
+ assert evaluate(record).properties["credential_ancestry"] == UNKNOWN
+
+
+def test_chain_must_begin_at_a_declared_trust_root() -> None:
+ record = load("positive_bounded_authorization")["record"]
+ record["credential_ancestry"][0]["parent"] = "root:undeclared"
+ assert evaluate(record).properties["credential_ancestry"] == UNKNOWN
+
+
+def test_authorship_and_ancestry_are_never_supported() -> None:
+ for path in FIXTURES:
+ outcome = evaluate(json.loads(path.read_text(encoding="utf-8"))["record"])
+ assert outcome.properties["authorship_degree"] != SUPPORTED
+ assert outcome.properties["credential_ancestry"] != SUPPORTED
+
+
+def test_model_declares_the_terminology_decision_and_prohibited_inferences() -> None:
+ model = load_model()
+ assert model["status"] == "REFERENCE_NON_NORMATIVE"
+ assert model["serialized_receipt_identifier"] is None
+ decision = model["terminology_decision"]["public_label_zero_identity"]
+ assert decision == "REJECTED_AS_UNQUALIFIED_PUBLIC_LABEL"
+ assert "verdict_aggregation" in model
+ assert "verdict_precedence" not in model
+ assert len(model["prohibited_inferences"]) >= 10
+
+
+def test_model_never_lists_unlinkability_as_supported() -> None:
+ model = load_model()
+ assert SUPPORTED not in model["properties"]["unlinkability"]["attainable_statuses"]
+
+
+def test_reference_surface_declares_no_new_serialized_receipt_identifier() -> None:
+ for path in (REFERENCE_SURFACE / "fixtures").glob("*.json"):
+ text = path.read_text(encoding="utf-8")
+ for identifier in ("VSTD-1", "VSTD-2", "VSTD-3.0", "VSTD-DATA-0.1"):
+ assert identifier not in text, f"{path.name} must not bind a VSTD serialized receipt identifier"
diff --git a/experiments/INDEX.md b/experiments/INDEX.md
new file mode 100644
index 0000000..4299675
--- /dev/null
+++ b/experiments/INDEX.md
@@ -0,0 +1,22 @@
+# Experimental work index
+
+> **Acronym:** Verifier Standard (VSTD).
+
+> **Experimental and non-normative.** Inclusion means that a profile manifest
+> is structurally valid and its `repo:` artifacts match their bound digests. It
+> does not establish a hypothesis, verifier, publication, or VSTD verdict.
+
+Regenerate or check this file with:
+
+```bash
+PYTHONPATH=src python scripts/build_experiment_index.py --check
+```
+
+| Experiment | State | Question | Publication | Open horizons | Manifest |
+|---|---|---|---|---:|---|
+| experiment-artifact-first-mechanisms | RUNNING | Which remaining domain-specific TRUST (mechanism-earned forward artifact support) transfer rules, ROT (typed time-indexed current-admissibility degradation) policies, RUST (inverse-TRUST diagnostic backtrace) localization mechanisms, hidden-witness trichotomy mechanisms, and independent implementations can extend the shipped reference event/dispatch substrate without actor reputation, scalar cancellation, causal-localization overclaim, or making the governing orientation contingent on the study? | CANDIDATE | 6 | [`experiments/artifact_first_mechanisms/experiment.json`](artifact_first_mechanisms/experiment.json) `sha256:81ce95f54a1ca98cd9fb897cf0568329aff03d5cb7334ffd367c915c2f6835dc` |
+| experiment-github-verdict-neutrality | COMPLETED | Does the GitHub adapter preserve successful workflow and merge states without converting them into a VSTD verdict? | INTERNAL | 1 | [`experiments/github_verdict_neutrality/experiment.json`](github_verdict_neutrality/experiment.json) `sha256:3b98310d35c20e7099d242e2c655e4bf8dc62d91298adc04e4dc2f56f2f79d89` |
+
+Platform events, including successful workflows and merges, retain
+`verification_effect = NONE` unless a separate native result is explicitly
+mapped through a bound VSTD receipt.
diff --git a/experiments/artifact_first_mechanisms/README.md b/experiments/artifact_first_mechanisms/README.md
new file mode 100644
index 0000000..71bc436
--- /dev/null
+++ b/experiments/artifact_first_mechanisms/README.md
@@ -0,0 +1,28 @@
+# Experimental artifact-first mechanisms
+
+> **Acronyms:** reduced instruction set computer (RISC); Verifier Standard (VSTD);
+> zero-identity/zero-knowledge (ZIZK).
+
+This directory does **not** make VSTD's ZIZK artifact-first architecture experimental.
+That governing orientation is normative in
+[`standard/LADDER.md` section 1.1](../../standard/LADDER.md#11-artifact-first-causal-provenance-orientation).
+
+TRUST is mechanism-earned forward artifact support; ROT is typed, time-indexed
+degradation of current admissibility; and RUST is the inverse-TRUST diagnostic backtrace
+toward recorded ancestors. These are formal semantic names, not acronyms, actor ratings,
+serialized receipt values, scalar scores, or references to the Rust programming language.
+
+Only the following unfinished mechanisms are experimental here:
+
+- event serialization;
+- bounded TRUST-transfer algebra;
+- ROT derivation and propagation;
+- RUST concentration and localization;
+- complete hidden-witness trichotomy derivation; and
+- specific optional proof backends while they remain unfinished.
+
+The bounded identity-disclosure evaluator and tracked RISC Zero proof-carrying reference
+mechanism are under
+[`examples/zizk_artifact_first/`](../../examples/zizk_artifact_first/). The
+[`experiment.json`](experiment.json) manifest records the mechanism studies and their
+remaining horizons without assigning experimental status to the governing architecture.
diff --git a/experiments/artifact_first_mechanisms/experiment.json b/experiments/artifact_first_mechanisms/experiment.json
new file mode 100644
index 0000000..63a17da
--- /dev/null
+++ b/experiments/artifact_first_mechanisms/experiment.json
@@ -0,0 +1,436 @@
+{
+ "profile": {
+ "id": "vstd.experimental-workflow",
+ "version": "0.1",
+ "status": "EXPERIMENTAL_NON_NORMATIVE"
+ },
+ "experiment": {
+ "id": "experiment-artifact-first-mechanisms",
+ "title": "Mechanism completion under VSTD's governing ZIZK artifact-first architecture",
+ "question": "Which remaining domain-specific TRUST (mechanism-earned forward artifact support) transfer rules, ROT (typed time-indexed current-admissibility degradation) policies, RUST (inverse-TRUST diagnostic backtrace) localization mechanisms, hidden-witness trichotomy mechanisms, and independent implementations can extend the shipped reference event/dispatch substrate without actor reputation, scalar cancellation, causal-localization overclaim, or making the governing orientation contingent on the study?",
+ "state": "RUNNING",
+ "started_at": "2026-08-23T00:00:00Z"
+ },
+ "hypotheses": [
+ {
+ "id": "hypothesis-hidden-witness",
+ "statement": "A real proof can establish the fixed reference-mechanism predicate without publishing the private witness bytes.",
+ "falsification_condition": "The accepted public artifacts disclose the witness bytes or a documented verifier accepts a proof not bound to the fixed predicate and program identifier.",
+ "state": "SUPPORTED"
+ },
+ {
+ "id": "hypothesis-identity-boundary",
+ "statement": "The bounded identity evaluator preserves UNKNOWN and CONFLICTED rather than inferring uniqueness, independence, or authorization from absent identity information.",
+ "falsification_condition": "A fixture with absent or contradictory identity evidence produces an unqualified accepted identity inference.",
+ "state": "SUPPORTED"
+ },
+ {
+ "id": "hypothesis-trustless-reverification",
+ "statement": "Reverification can reproduce a verdict over bound public coordinates without accumulating actor reputation or historical trust.",
+ "falsification_condition": "The proposed substrate requires actor identity or prior reputation to reproduce the bounded verdict, or repeated identical receipts increase epistemic strength without new evidence.",
+ "state": "OPEN"
+ },
+ {
+ "id": "hypothesis-artifact-first-zero-actor-trust",
+ "statement": "An operational reverification protocol can derive acceptance from bound artifacts, evidence, predicates, mechanisms, and declared trust roots while preventing actor identity, popularity, repetition, or reputation from strengthening the verdict.",
+ "falsification_condition": "Changing only actor identity or reputation changes acceptance, repeated equivalent actor events raise status, or an unbound artifact is accepted.",
+ "state": "OPEN"
+ },
+ {
+ "id": "hypothesis-contextual-actor-artifact-roles",
+ "statement": "An event schema can preserve actor and artifact as contextual roles, so a coding agent may be an artifact when created or evaluated and an actor when it creates or transforms another artifact.",
+ "falsification_condition": "The model requires permanent disjoint actor and artifact categories, loses a claim-relevant creation edge, or treats a role assignment as identity, authority, or trust.",
+ "state": "OPEN"
+ },
+ {
+ "id": "hypothesis-rust-memetic-backtrace",
+ "statement": "An operational ledger can transfer typed RUST backward from an observed child deviation through admissible bound creation paths and concentrate independent backtraces on shared ancestor claims without reporting ancestry as localized causation.",
+ "falsification_condition": "RUST cannot reproduce its child-to-ancestor paths, fails to concentrate distinct comparable sources, crosses non-contributing edges, or reports concentration as direct observation, proven causation, actor reputation, or a VSTD verdict.",
+ "state": "OPEN"
+ },
+ {
+ "id": "hypothesis-rot-current-admissibility",
+ "statement": "A typed current-state mechanism can derive ROT from exact lifecycle or dependency evidence and expose affected descendants without rewriting historical receipts or treating age as falsity.",
+ "falsification_condition": "Historical receipt bytes or results are mutated, age or actor reputation alone creates ROT, an inadmissible required dependency remains clean for current use, or ROT is treated as proof that the historical result was false.",
+ "state": "OPEN"
+ },
+ {
+ "id": "hypothesis-dual-causal-propagation",
+ "statement": "An operational substrate can carry scoped TRUST from parent to child, derive typed ROT for current admissibility, and carry diagnostic RUST from child to parent without collapsing them into one scalar or allowing any relation to bypass claim-local evidence.",
+ "falsification_condition": "TRUST flows backward, RUST flows forward as inherited guilt, ROT rewrites historical truth, parent TRUST automatically proves a child, missing or conflicted support becomes clean, or actor identity changes any relation.",
+ "state": "OPEN"
+ }
+ ],
+ "preregistration": {
+ "state": "AMENDED",
+ "recorded_at": "2026-08-24T00:00:00Z",
+ "artifact_id": "artifact-round2-design",
+ "limitations": [
+ "This experimental workflow manifest records remaining domain mechanisms and external evidence; it does not classify VSTD's governing ZIZK artifact-first architecture or the shipped reference event/dispatch substrate as experimental.",
+ "Round 2 remains a design synthesis for the domain-independent transfer algebra and external protocol, not a denial of the bounded reference implementation.",
+ "The bounded identity-disclosure reference evaluator is semantic and does not itself provide cryptographic anonymity or unlinkability."
+ ]
+ },
+ "artifacts": [
+ {
+ "id": "artifact-zk-report",
+ "role": "round-1-zero-knowledge-report",
+ "media_type": "text/markdown",
+ "digest": "sha256:a3b0cdc18e81216cedd3adcaaa65119d20cd755eaa530e2a5d9219d26eda1758",
+ "locator": "repo:examples/zizk_artifact_first/risc0/ROUND1_ZERO_KNOWLEDGE_REPORT.md"
+ },
+ {
+ "id": "artifact-zk-receipt",
+ "role": "recorded-risc0-proof-receipt",
+ "media_type": "application/msgpack",
+ "digest": "sha256:04813c4757ba4efbdad9d51d50d7402f3a98f6c23e53b9b58cce8af12ef9caa2",
+ "locator": "repo:examples/zizk_artifact_first/risc0/recorded-proof/receipt.msgpack"
+ },
+ {
+ "id": "artifact-zk-public-envelope",
+ "role": "recorded-risc0-public-envelope",
+ "media_type": "application/json",
+ "digest": "sha256:188098e6ba1ac940475f15e0a4304ff08d678d98a9ed708dbe41dc6dde596b76",
+ "locator": "repo:examples/zizk_artifact_first/risc0/recorded-proof/public.json"
+ },
+ {
+ "id": "artifact-zk-self-test",
+ "role": "recorded-risc0-self-test-result",
+ "media_type": "application/json",
+ "digest": "sha256:e4c1bff21fb6161221276157fa96af6661af8635da35970ba12e462881f2c6fe",
+ "locator": "repo:examples/zizk_artifact_first/risc0/recorded-proof/self-test-results.json"
+ },
+ {
+ "id": "artifact-zi-report",
+ "role": "round-1-zero-identity-report",
+ "media_type": "text/markdown",
+ "digest": "sha256:f303cf6a2e047c09517187a74a9bc850214b1971ca98f51997e5898ec04765ae",
+ "locator": "repo:examples/zizk_artifact_first/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md"
+ },
+ {
+ "id": "artifact-round2-design",
+ "role": "round-2-reverification-design",
+ "media_type": "text/markdown",
+ "digest": "sha256:7c119945d1446ad2253dc3d9d51ef15024e32be86d92924eca0ab27587d09a46",
+ "locator": "repo:experiments/artifact_first_mechanisms/reverification/ROUND2_DESIGN_NOTE.md"
+ }
+ ],
+ "budgets": [
+ {
+ "id": "budget-zk-proof",
+ "resource": "real-proof-runs",
+ "limit": 1,
+ "consumed": 1,
+ "unit": "proof-run",
+ "scope": "RISC Zero round-1 self-test"
+ },
+ {
+ "id": "budget-identity-fixtures",
+ "resource": "semantic-fixture-evaluations",
+ "limit": 22,
+ "consumed": 22,
+ "unit": "fixture",
+ "scope": "bounded identity round-1 fixture corpus"
+ },
+ {
+ "id": "budget-round2-design",
+ "resource": "design-synthesis",
+ "limit": 1,
+ "consumed": 1,
+ "unit": "bounded-review",
+ "scope": "trustless reverification round-2 note"
+ }
+ ],
+ "actions": [
+ {
+ "id": "action-zk-proof",
+ "kind": "CRYPTOGRAPHIC_PROOF_EXPERIMENT",
+ "target": "fixed hidden-evidence predicate and public VSTD-facing coordinates",
+ "state": "COMPLETED",
+ "priority": 1,
+ "selected_because": "A zero-knowledge claim required a real proof and offline native verification rather than a commitment-only or development-mode substitute.",
+ "selection_evidence_ids": [
+ "hypothesis-hidden-witness"
+ ],
+ "alternatives_considered": [
+ "full disclosure",
+ "commitment-only evidence"
+ ],
+ "budget_ids": [
+ "budget-zk-proof"
+ ],
+ "depends_on": [],
+ "triggered_by": [],
+ "expected_artifact_effect": "Produce a bounded proof-carrying reference mechanism and retrievable public proof artifacts without changing core VSTD receipts.",
+ "substrate": {
+ "kind": "proof-engine",
+ "name": "RISC Zero zkVM",
+ "version": "3.0.6",
+ "coordinate": "repo:examples/zizk_artifact_first/risc0"
+ },
+ "native_result_ids": [
+ "result-zk-proof"
+ ],
+ "produced_artifact_ids": [
+ "artifact-zk-report",
+ "artifact-zk-receipt",
+ "artifact-zk-public-envelope",
+ "artifact-zk-self-test"
+ ]
+ },
+ {
+ "id": "action-identity-evaluation",
+ "kind": "SEMANTIC_NEGATIVE_TESTING",
+ "target": "identity minimization, authorization boundaries, ancestry, and prohibited inferences",
+ "state": "COMPLETED",
+ "priority": 2,
+ "selected_because": "Identity minimization needed explicit negative fixtures before any operational protocol could be justified.",
+ "selection_evidence_ids": [
+ "hypothesis-identity-boundary"
+ ],
+ "alternatives_considered": [
+ "actor reputation scoring",
+ "implicit identity inference"
+ ],
+ "budget_ids": [
+ "budget-identity-fixtures"
+ ],
+ "depends_on": [],
+ "triggered_by": [],
+ "expected_artifact_effect": "Expose UNKNOWN, CONFLICTED, and rejected identity inferences in a reproducible fixture corpus.",
+ "substrate": {
+ "kind": "semantic-evaluator",
+ "name": "ZIZK zero-identity fixture evaluator",
+ "version": "round-1",
+ "coordinate": "repo:examples/zizk_artifact_first/zero_identity"
+ },
+ "native_result_ids": [
+ "result-identity-fixtures"
+ ],
+ "produced_artifact_ids": [
+ "artifact-zi-report"
+ ]
+ },
+ {
+ "id": "action-reverification-synthesis",
+ "kind": "DESIGN_SYNTHESIS",
+ "target": "candidate operational mechanics for normative artifact-first TRUST, time-indexed ROT, and backward memetic RUST across contextual actor/artifact roles",
+ "state": "COMPLETED",
+ "priority": 3,
+ "selected_because": "The normative verification complex fixes the causal-provenance orientation, while the proof and identity studies expose the still-open event, transfer, concentration, and localization mechanics needed to implement it without actor reputation or causal-localization overclaim.",
+ "selection_evidence_ids": [
+ "observation-zk-proof",
+ "observation-identity-boundary",
+ "hypothesis-contextual-actor-artifact-roles",
+ "hypothesis-rust-memetic-backtrace",
+ "hypothesis-artifact-first-zero-actor-trust",
+ "hypothesis-dual-causal-propagation",
+ "hypothesis-rot-current-admissibility"
+ ],
+ "alternatives_considered": [
+ "identity-bound trust",
+ "actor reputation accumulation",
+ "object-only RUST with erased actor-artifact relations",
+ "permanent disjoint actor and artifact categories"
+ ],
+ "budget_ids": [
+ "budget-round2-design"
+ ],
+ "depends_on": [
+ "action-zk-proof",
+ "action-identity-evaluation"
+ ],
+ "triggered_by": [
+ "observation-zk-proof",
+ "observation-identity-boundary"
+ ],
+ "expected_artifact_effect": "Specify candidate contextual-role, TRUST, ROT, RUST, and concentration mechanics while leaving event schemas, transfer/derivation algebras, and localization protocols open.",
+ "substrate": {
+ "kind": "design-review",
+ "name": "ZIZK artifact-first actor-artifact reverification synthesis",
+ "version": "round-2",
+ "coordinate": "repo:experiments/artifact_first_mechanisms/reverification"
+ },
+ "native_result_ids": [],
+ "produced_artifact_ids": [
+ "artifact-round2-design"
+ ]
+ }
+ ],
+ "observations": [
+ {
+ "id": "observation-zk-proof",
+ "action_id": "action-zk-proof",
+ "recorded_at": "2026-08-23T00:00:00Z",
+ "statement": "The recorded round-1 run generated and re-verified one real RISC Zero receipt within the reference program and exercised the declared negative cases; distinct actors were not established.",
+ "status": "OBSERVED",
+ "evidence_artifact_ids": [
+ "artifact-zk-report",
+ "artifact-zk-receipt",
+ "artifact-zk-public-envelope",
+ "artifact-zk-self-test"
+ ],
+ "limitations": [
+ "The private witness and salt are excluded; the exact non-secret receipt, public envelope, self-test result, implementation, and report are tracked and digest-bound."
+ ]
+ },
+ {
+ "id": "observation-identity-boundary",
+ "action_id": "action-identity-evaluation",
+ "recorded_at": "2026-08-24T00:00:00Z",
+ "statement": "The recorded fixture suite preserved bounded acceptance, rejection, UNKNOWN, and CONFLICTED outcomes across the declared cases.",
+ "status": "OBSERVED",
+ "evidence_artifact_ids": [
+ "artifact-zi-report"
+ ],
+ "limitations": [
+ "Semantic fixture behavior is not a cryptographic privacy, anonymity, authorization, or Sybil-resistance guarantee."
+ ]
+ }
+ ],
+ "native_results": [
+ {
+ "id": "result-zk-proof",
+ "action_id": "action-zk-proof",
+ "verifier": {
+ "kind": "proof-verifier",
+ "name": "RISC Zero Receipt::verify",
+ "version": "3.0.6",
+ "coordinate": "repo:examples/zizk_artifact_first/risc0"
+ },
+ "native_status": "REAL_RECEIPT_VERIFIED_AND_NEGATIVE_CASES_REJECTED",
+ "result_artifact_id": "artifact-zk-receipt",
+ "mapping": {
+ "status": "NOT_EVALUATED",
+ "vstd_verdict": null,
+ "mapping_profile": null,
+ "receipt_artifact_id": null,
+ "reason": "The proof engine's native result is recorded without inventing a VSTD receipt mapping."
+ }
+ },
+ {
+ "id": "result-identity-fixtures",
+ "action_id": "action-identity-evaluation",
+ "verifier": {
+ "kind": "fixture-evaluator",
+ "name": "zero_identity.evaluate",
+ "version": "round-1",
+ "coordinate": "repo:examples/zizk_artifact_first/zero_identity/evaluate.py"
+ },
+ "native_status": "22_FIXTURES_0_FAILURES",
+ "result_artifact_id": "artifact-zi-report",
+ "mapping": {
+ "status": "NOT_EVALUATED",
+ "vstd_verdict": null,
+ "mapping_profile": null,
+ "receipt_artifact_id": null,
+ "reason": "Fixture validation is preserved as its native result and does not become a core VSTD verdict."
+ }
+ }
+ ],
+ "adaptations": [
+ {
+ "id": "adaptation-round2-trustless-boundary",
+ "trigger_ids": [
+ "observation-zk-proof",
+ "observation-identity-boundary"
+ ],
+ "decision": "Treat standard/LADDER.md section 1.1 and VSTD-GRAPH-ASSURANCE-1 as the controlling semantic and implemented reference boundaries; confine Round 2 to domain-specific TRUST transfer, ROT derivation/propagation, RUST backtrace/concentration and localization mechanisms, independent cross-implementation replay, and remaining proof research.",
+ "reason": "The architecture and bounded reference event/dispatch mechanisms are implemented; the complete domain-independent transfer algebra, complete trichotomy derivation, external independent replay, and specific unfinished optional proof backends remain experimental.",
+ "action_ids": [
+ "action-reverification-synthesis"
+ ],
+ "artifact_ids": [
+ "artifact-round2-design"
+ ]
+ }
+ ],
+ "amendments": [
+ {
+ "id": "amendment-round2-design",
+ "recorded_at": "2026-08-24T00:00:00Z",
+ "reason": "Round 1 findings narrowed the design from identity-bound trust to artifact-bound trustless reverification.",
+ "supersedes": [
+ "hypothesis-trustless-reverification"
+ ],
+ "artifact_id": "artifact-round2-design"
+ },
+ {
+ "id": "amendment-actor-artifact-rust-correction",
+ "recorded_at": "2026-08-25T00:00:00Z",
+ "reason": "Correct the object-only framing and distinguish the normative causal-provenance orientation and implemented reference events from remaining domain transfer, localization, trichotomy, and optional proof-backend research: artifact support propagates ancestor-to-descendant, while RUST memetically backtraces descendant-to-ancestor without becoming actor reputation, scalar cancellation, causal localization, or guilt.",
+ "supersedes": [
+ "amendment-round2-design"
+ ],
+ "artifact_id": "artifact-round2-design"
+ },
+ {
+ "id": "amendment-trust-rot-rust-architecture",
+ "recorded_at": "2026-08-27T00:00:00Z",
+ "reason": "Formalize artifact/process-only TRUST, ROT, and RUST under zero identity and zero unevidenced knowledge, with cryptographic zero knowledge enclosing confidential-witness mechanisms rather than importing prover identity into verdict weight.",
+ "supersedes": [
+ "amendment-actor-artifact-rust-correction"
+ ],
+ "artifact_id": "artifact-round2-design"
+ }
+ ],
+ "challenges": [],
+ "horizons": [
+ {
+ "id": "horizon-operational-protocol",
+ "status": "UNKNOWN",
+ "description": "Independent end-to-end deployment of the reference reverification protocol",
+ "reason": "The repository ships a bounded operational reference substrate; no external independent implementation or deployment evidence is included."
+ },
+ {
+ "id": "horizon-anonymity",
+ "status": "OUT_OF_SCOPE",
+ "description": "Universal anonymity or unlinkability",
+ "reason": "Neither bounded reference mechanism establishes anonymity, unlinkability, identity uniqueness, or resistance to correlation outside its declared predicate."
+ },
+ {
+ "id": "horizon-independent-reimplementation",
+ "status": "UNKNOWN",
+ "description": "Independent implementation by an unrelated party",
+ "reason": "No independent implementation has been demonstrated."
+ },
+ {
+ "id": "horizon-contextual-role-protocol",
+ "status": "UNKNOWN",
+ "description": "Operational actor/artifact role and creation-edge protocol",
+ "reason": "The design states the contextual-role invariant but includes no approved schema or implementation."
+ },
+ {
+ "id": "horizon-rot-current-admissibility",
+ "status": "UNKNOWN",
+ "description": "Domain-specific ROT policies beyond the reference dispatcher",
+ "reason": "AssuranceLedger implements typed ROT and challenge-ledger projection without rewriting history; production lifecycle mechanisms and cross-implementation evidence remain unavailable."
+ },
+ {
+ "id": "horizon-rust-memetic-backtrace",
+ "status": "UNKNOWN",
+ "description": "Independent domain localization beyond structural RUST concentration",
+ "reason": "AssuranceLedger implements typed backward RUST, deduplicated structural concentration, and explicit localization dispatch; no external intervention mechanism or independent replay is included."
+ },
+ {
+ "id": "horizon-forward-artifact-trust",
+ "status": "UNKNOWN",
+ "description": "Complete domain-independent TRUST transfer algebra",
+ "reason": "AssuranceLedger implements evidence-bound forward TRUST over recorded ancestry; no universal algebra can replace the domain mechanism that checks each exact support proposition."
+ }
+ ],
+ "publication": {
+ "state": "CANDIDATE",
+ "artifact_ids": [
+ "artifact-zk-report",
+ "artifact-zk-self-test",
+ "artifact-zk-public-envelope",
+ "artifact-zk-receipt",
+ "artifact-zi-report",
+ "artifact-round2-design"
+ ]
+ },
+ "workflow_events": [],
+ "interventions": [],
+ "manifest_digest": "sha256:81ce95f54a1ca98cd9fb897cf0568329aff03d5cb7334ffd367c915c2f6835dc"
+}
diff --git a/experiments/artifact_first_mechanisms/reverification/ROUND2_DESIGN_NOTE.md b/experiments/artifact_first_mechanisms/reverification/ROUND2_DESIGN_NOTE.md
new file mode 100644
index 0000000..6e01181
--- /dev/null
+++ b/experiments/artifact_first_mechanisms/reverification/ROUND2_DESIGN_NOTE.md
@@ -0,0 +1,638 @@
+# Round 2 design note: operationalizing artifact-first reverification and RUST
+
+> **Acronyms:** Common Vulnerabilities and Exposures (CVE); identifier (ID); reduced instruction set computer (RISC);
+> Secure Hash Algorithm 256-bit (SHA-256); scalable transparent argument of knowledge (STARK);
+> Verifier Standard (VSTD); zero-identity (ZI); zero-knowledge (ZK); zero-knowledge virtual machine (zkVM).
+
+**Status:** experimental design note; non-normative; no wire profile is defined.
+
+TRUST, ROT, and RUST are formal Verifier Standard semantic names, not acronyms, wire
+values, scalar scores, actor ratings, or references to the Rust programming language.
+
+This note uses **trustless** in one bounded sense: acceptance of a submitted
+reverification result must not require knowing or trusting the submitter. It does not
+mean assumption-free, trust-root-free, or immune to compromised software, unavailable
+evidence, or false observations.
+
+The controlling semantic orientation is normative in
+[`standard/LADDER.md` section 1.1](../../../standard/LADDER.md#11-artifact-first-causal-provenance-orientation).
+This experiment does not decide whether that orientation is valid repository architecture;
+it tests the still-open event format, transfer algebra, concentration rule, and localization
+mechanics needed to operationalize it.
+
+The design decisions are:
+
+> Tier 0 is artifact-first: it binds claims, artifacts, predicates, verifier mechanisms,
+> boundary snapshots, proofs, and checkable results. Actor identity, popularity, and
+> reputation contribute no verdict weight and accumulate no TRUST.
+
+> Actor and artifact are contextual roles on creation and operation events, not disjoint
+> kinds of entity. A coding agent can be an artifact when created, serialized, versioned,
+> or evaluated and an actor when it performs a transformation or creates another artifact.
+
+> TRUST moves forward through checked developmental claim space; ROT describes typed,
+> time-indexed degradation of current admissibility; and RUST genetically backtraces from
+> descendant deviations toward recorded ancestor states. The three are artifact/process
+> semantics, never actor standing, and do not collapse into a scalar.
+
+> Architectural zero knowledge presumes no unevidenced proposition. When its witness is
+> confidential, cryptographic zero knowledge encloses the exact program, predicate, public
+> commitments, output, proof parameters, and verifier without attaching TRUST to a prover.
+
+Authorship, authorization, issuer identity, organizational accountability, and descriptive
+history may exist in adjacent optional profiles. Tier 0 may bind their coordinates when a
+claim requires them, but their mere presence cannot strengthen the result.
+
+## 1. Where the research components stand
+
+Round 1 began from commit `598c545be3833d6d81bb7e252ca5837f3bb2a449`.
+
+| Work | Source coordinate | What it established | Round 2 treatment |
+|---|---|---|---|
+| Zero Identity | `claude/zizk-zero-identity` at `48fab87b05ad5ddaf24d08b6391cde99d05fc8f1` | A bounded identity-disclosure reference evaluation, with 22 fixtures and 65 focused tests | Retained as an adjacent reference mechanism; its coordinates carry only the claim meaning explicitly checked |
+| Zero Knowledge | `codex/zizk-zero-knowledge` at `14d31e0426656c5208f2b6579a5217af3a6bb2bd` | A real RISC Zero zkVM 3.0.6 composite STARK receipt for one hidden-witness predicate | Retained as the confidential-evidence mechanism; its bearer, artifact-bound form is compatible with Tier 0 |
+| Zero actor trust | this Round 2 design | No actor identity, popularity, or reputation may strengthen a result | Open as an operational protocol; stated here as a required invariant |
+| TRUST | existing VSTD artifact, evidence, mechanism, and predicate bindings | Bounded positive support can move from verified parent artifacts into the declared obligations of descendants | Retained as the Tier 0 starting point; child obligations remain separately checked |
+| ROT | `standard/LADDER.md` section 1.1 plus existing lifecycle status, current-admission, and blast-radius machinery | Typed lifecycle evidence can degrade current admissibility without rewriting historical receipts | Open as a general derivation and propagation protocol; some current Graph queries implement bounded consequences |
+| Actor-artifact role semantics | no prior implementation | An entity's role depends on the creation or operation event; coding agents can occupy both roles | Open; this note corrects the earlier object-only partition |
+| RUST | `standard/LADDER.md` section 1.1 | The inverse-TRUST diagnostic mechanic: a viral backtrace of measured deviation through bound creation ancestry, never an actor score or verdict | This note tests candidate relation-bound transfer and concentration mechanics beneath the governing term |
+
+One premise in the initial Round 2 plan is corrected here. The two finished halves did
+**not** both put trust in credentials:
+
+- The Zero Identity half intentionally modeled a pseudonym, signing key, trust root,
+ issuer, authorization grant, and revocation source. Its conclusion follows from that
+ actor-bound problem definition.
+- The Zero Knowledge half binds a subject digest, policy digest, challenge, threshold,
+ image ID, authenticated journal, and proof. It adds no actor coordinate and expressly
+ prohibits inferring identity, authorization, uniqueness, or independence.
+
+The architectural correction applies to the evidentiary effect of the Zero Identity
+model, not to the existence of actor coordinates or to the Zero Knowledge proof. The
+identity model is not discarded: it remains an optional adjacent profile for deployments
+that need authorization or accountability. Its coordinates cannot become actor trust,
+and the actor/artifact role model below prevents the profile boundary from becoming a
+false permanent partition between parties and things.
+
+VSTD already contains much of the required substrate discipline. In particular,
+`standard/VSTD-4.md` requires post-verdict checking without cooperation from the
+declarant, disallows undeclared state from becoming verdict material, defines verifier
+descriptors through content hashes, requires a checker that shares no verdict-producing
+code, and requires a declared verification interface for confidential evidence. That
+does not make every VSTD layer identity-free: observational evidence and external trust
+roots still have sources. It makes actor identity unnecessary as verdict weight while
+preserving any actor-artifact relation needed to state the bounded claim.
+
+## 2. Zero actor trust through artifact-first convergent recomputation
+
+### 2.1 Reverification unit
+
+A Tier 0 reverification attempt is defined over these public coordinates:
+
+1. **subject coordinate** — an artifact digest or an immutable receipt digest;
+2. **statement coordinate** — the canonical claim and predicate digest;
+3. **declared inputs** — content digests for every sealed input;
+4. **boundary snapshot** — the content-addressed result of resolving every declared
+ external dependency under a pinned resolver policy;
+5. **mechanism descriptor** — specification, implementation, and parser digests, plus a
+ proof-system program identifier or verification key when applicable;
+6. **expected result** — the result committed by the claim being reverified; and
+7. **observed result and trace** — enough public material to repeat the check, or to
+ verify a proof when the witness is confidential; and
+8. **role-relation snapshot, when claim-relevant** — content-addressed creation,
+ execution, input, and output edges without converting an endpoint into verdict weight.
+
+An experimental event body can be modeled as:
+
+```text
+ReverificationEventBody = {
+ subject_digest,
+ statement_digest,
+ declared_input_digests,
+ boundary_snapshot_digest,
+ resolver_policy_digest,
+ mechanism_descriptor_digest,
+ expected_result_digest,
+ observed_result_digest,
+ outcome,
+ trace_or_proof_digest,
+ role_relation_snapshot_digest?,
+ prior_event_digest
+}
+
+event_id = SHA-256(canonicalize(ReverificationEventBody))
+```
+
+This is a design sketch, not a new schema. Its names are not reserved serialized receipt identifiers.
+Canonicalization, supported digest algorithms, event-chain rules, and admissible outcome
+values require a later approved experiment before any schema can be proposed.
+
+No field identifies the submitter merely to weight the result. A claim-relevant role edge
+may identify a bounded entity coordinate, but possession of a valid event confers no
+authorization and proves no authorship.
+
+### 2.2 Actor and artifact are event-relative roles
+
+The model must not define permanent disjoint `Actor` and `Artifact` universes. It records
+roles on bound events:
+
+- `produced_by(event, entity, output)` places `entity` in an actor role and `output` in an
+ artifact role for that creation event;
+- `created_as(event, entity)` places the created entity in an artifact role;
+- `executed_as(event, entity)` places a running entity in an actor role; and
+- `used_as_input(event, entity)` may place the same entity in an artifact role for a
+ different operation.
+
+A coding-agent model, package, checkpoint, or executable is therefore an artifact of its
+training or build event. A bound execution of it is an actor in a patch-producing event,
+and the patch is an artifact. The roles follow declared creation and operation semantics;
+they do not establish civil identity, authorship, ownership, authorization, independence,
+or reputation.
+
+The precise event schema, instance coordinate, and relation vocabulary remain `OPEN`.
+This note establishes only that erasing the relation or forcing a permanent category is
+incorrect.
+
+### 2.3 Agreement is not trust
+
+Repeating the same deterministic implementation over the same frozen inputs is expected
+to return the same result. Ten, one thousand, or one million matching submissions do not
+make the result more true. They must not be counted as votes, averaged, or converted into
+standing.
+
+Agreement can establish only the bounded fact that the accepted traces produced matching
+outputs under their declared coordinates. An independently implemented checker can add a
+different falsification opportunity because it may expose a specification or
+implementation disagreement. Even then, agreement does not establish actor independence,
+real-world truth, or a probability of correctness.
+
+### 2.4 Divergence is a falsification candidate, not self-certifying truth
+
+A submitted divergence is admissible only when the verifier can establish all of the
+following without trusting the submitter:
+
+- both results bind the same subject, statement, declared inputs, resolver policy, and
+ comparison unit;
+- the mechanism coordinates are explicit;
+- the claim under test declared the relevant computation deterministic or otherwise
+ declared the expected equivalence relation;
+- the divergent trace can be repeated, or its proof can be checked; and
+- no hidden input, unpinned dependency, or incomparable environment explains the
+ difference.
+
+An admissible divergence can refute a declared determinacy or reproducibility claim. It
+does not, by itself, determine which output is correct or establish truth outside the
+predicate. If comparability is insufficient, the result is `UNKNOWN`. If admissible
+evidence supports incompatible results, the result is `CONFLICTED`.
+
+Calling divergence **self-certifying** would be too strong. A malformed or
+non-reproducible divergence report certifies nothing. Actor identity and Sybil resistance
+are unnecessary for verdict material because duplicate or invalid reports cannot change
+the result; however, anonymous spam can still create storage, bandwidth, and triage costs.
+Rate limiting and admission control may address that operational denial-of-service risk,
+but must not become evidence about correctness.
+
+## 3. Forward TRUST without actor-trust accumulation
+
+Tier 0 records both bounded TRUST and accepted opportunities to refute
+a claim. It never turns either into a producer's or verifier's reputation, and a claim
+does not gain standing merely by surviving repeated attempts.
+
+TRUST is a positive signal bound to an exact artifact, process claim, predicate,
+mechanism, evidence set, boundary snapshot, and time coordinate. It moves forward only
+through a declared creation or dependency edge whose transformation obligations pass. A
+child receives the intersection of applicable parent support, capped by the weakest
+required parent and edge; it does not receive a sum, vote, average, or confidence boost.
+The child must still discharge every new predicate, transformation, and boundary
+obligation it introduces.
+
+In schematic form:
+
+```text
+development: ancestor artifact --TRUST through a checked transformation--> descendant
+lifecycle: recorded TRUST --ROT under typed current-state evidence--> reassessment
+diagnosis: descendant deviation --RUST memetic causal backtrace--> ancestor candidates
+```
+
+`UNKNOWN`, `CONFLICTED`, revoked, unavailable, or out-of-scope parent support cannot be
+laundered into a clean child signal. Repeating the same parent coordinate does not create
+additional support. Actor identity and reputation do not participate in the transfer.
+
+| Tier 0 event outcome | Bounded interpretation | Forbidden interpretation |
+|---|---|---|
+| matching result | this accepted check matched the committed result and may satisfy one declared child obligation | the claim, submitter, or mechanism is globally trustworthy |
+| admissible divergence | the declared equivalence or reproducibility condition has a checkable counterexample | the divergent result is automatically the true result |
+| unresolved boundary | required material could not be resolved or checked; preserve `UNKNOWN` | missing evidence is clean evidence |
+| incompatible admissible records | preserve `CONFLICTED` and expose both records | choose the more popular result |
+
+The Tier 0 state is a function over immutable records. Its TRUST is
+typed and scoped; it does not have a cumulative confidence counter, majority rule, actor
+weight, or time-decayed reputation.
+
+Tier 1 may provide descriptive analysis over Tier 0 events. Tier 1 is optional,
+non-normative, and forbidden from supplying `PASS`, `FAIL`, `UNKNOWN`, `CONFLICTED`,
+`VALID`, `STALE`, or any ladder result. Evidence in one layer does not silently supply
+evidence in another.
+
+## 4. RUST: an optional relation-bound deviation ledger
+
+**RUST** is the governing name for the inverse-TRUST diagnostic mechanic. This section
+tests one candidate Tier 1 view of past measured deviation. Its inverse is directional and
+diagnostic, not arithmetic: RUST does not cancel TRUST, create actor distrust, supply a
+verdict, or predict behavior.
+
+### 4.1 Bound relations, not actor reputation
+
+RUST may bind only to immutable coordinates and explicitly typed relations:
+
+- an artifact digest;
+- a statement or predicate digest;
+- a specification, implementation, and parser digest tuple; or
+- a content-addressed creation or transformation edge;
+- a contextual actor-role/artifact relation for one declared event; or
+- an explicit basin coordinate defining a common comparison unit.
+
+It does not create a scalar score for a person, pseudonym, account, organization, author,
+issuer, key holder, submitter, or coding agent. An actor coordinate may be a relation
+endpoint, but RUST cannot aggregate upward across unrelated artifacts, predicates,
+operations, or basins and cannot change a Tier 0 verdict.
+
+A coding agent may therefore participate only through exact software-artifact, execution,
+creation, or transformation coordinates. RUST attaches to those bounded process or
+relation coordinates, never to the person, account, organization, or agent identity as a
+good/bad score. Records remain separate unless an explicit common comparison unit and
+evidence justify composition.
+
+Content addressing prevents an unchanged byte sequence from shedding its history while
+keeping the same digest. It does **not** eliminate whitewashing in general: a trivial
+repackaging, semantically near-identical fork, or changed mechanism descriptor creates a
+new coordinate. Because recorded ancestry does not establish that a defect transferred,
+the parent's RUST cannot be copied into the descendant. The old coordinate and relation
+records remain, while any inference about the new entity or relation remains `UNKNOWN`
+until measured.
+
+### 4.2 Exposure denominator and deduplication
+
+RUST requires an exposure denominator so that no observation and many observations are
+not confused. An exposure is not a submitted run. It is a unique, admissible opportunity
+for the declared expectation to fail.
+
+The proposed exposure key is the digest of:
+
+```text
+(subject, statement, mechanism, role relation, comparison unit, boundary snapshot)
+```
+
+Identical submissions collapse to one exposure. A different submitter does not create a
+new exposure. A new exposure requires a distinct admissible test vector, independently
+implemented mechanism, or resolved boundary snapshot that can reveal something not fixed
+by the earlier event.
+
+For every basin, report at least:
+
+- `exposure_count` — unique admissible exposure keys;
+- the ordered deviation observations;
+- the deviation mean for each declared horizon;
+- dispersion for each horizon; and
+- the number of `UNKNOWN` and `CONFLICTED` comparisons excluded from numerical
+ aggregation and still reported separately.
+
+`exposure_count = 0` is `UNKNOWN`, not clean. A positive exposure count with zero measured
+deviation is still only a history of observed matches, not a favorable verdict.
+
+### 4.3 Reference and measured quantity
+
+The reference is the claim's own declared expectation: committed output, tolerance,
+falsification condition, availability condition, reproducibility level, or other bounded
+predicate. RUST therefore records **deviation from a declared expectation**, not error
+against unknowable real-world truth.
+
+Each expectation type needs a fixed comparison rule outside the measured relation
+endpoints' control. Examples include binary mismatch, normalized numeric error under a
+declared unit, or set-distance under a fixed canonicalizer. A measured endpoint must not
+choose a weaker penalty after seeing a result.
+
+`UNKNOWN` and `CONFLICTED` are not numeric zero. They remain typed observations. A
+comparison that lacks a common unit is not forced into a number.
+
+### 4.4 Memetic backtrace, basins, and horizons
+
+A **basin** is an explicitly described analytical grouping of events that share a
+predicate, mechanism family, comparison unit, and deviation rule. Clustering may suggest
+a basin, but a clustering algorithm does not establish that the members are comparable.
+The basin definition and its digest must be published with the view.
+
+RUST acts as a viral **truth-disease backtrace** through causal provenance. A directly
+measured descendant deviation is the source event, and the recorded creation and input
+graph determines which ancestor states receive the memetic trace. The genetic metaphor
+names transmission through developmental ancestry: actor/artifact role edges allow the
+trace to cross a coding-agent execution into the bound model, package, checkpoint, or
+executable that acted and then into that artifact's own creation ancestry. Transmission
+establishes provenance reachability; localization still requires its own evidence.
+
+Propagation is typed rather than silently re-described as direct observation:
+
+| RUST state | Meaning |
+|---|---|
+| `OBSERVED` | the deviation was measured directly at this descendant coordinate |
+| `TRANSFERRED` | an observed RUST event reached this ancestor through a recorded admissible path |
+| `LOCALIZED` | additional evidence identifies this ancestor or edge as contributing to the deviation |
+| `UNKNOWN` | the required lineage or edge semantics are incomplete or unavailable |
+| `CONFLICTED` | admissible backtraces disagree about the relation or contribution |
+
+For each admissible path from ancestor `a` to rusted descendant `d`, the transferred state
+must bind at least the source RUST event, `a`, `d`, every traversed edge digest, the
+predicate, mechanism, comparison unit, basin, and transfer rule. A source event, ancestor,
+and path tuple is counted once. Mere co-occurrence, reference, authorship, or identity is
+not a transmission edge. An unknown edge stops that path and preserves `UNKNOWN`.
+
+RUST **concentrates** where distinct descendant infection events share an ancestor. For an
+ancestor and basin, the concentration record is the set of unique source RUST event and
+admissible path digests that reach it. Multiple paths or duplicate reports of one source
+event remain visible but do not multiply its weight. Intersections of independent
+backtraces prioritize earlier claims, predicates, mechanisms, or artifacts for diagnostic
+examination because they are common candidate loci of falsehood.
+
+`TRANSFERRED` is actual RUST inheritance, but it is not a claim that the ancestor was
+directly measured or proved causal. `LOCALIZED` requires an intervention, ablation,
+an independently bound execution or mechanism, or other declared evidence that
+distinguishes contribution from ancestry. The backtrace is append-only, does not decay,
+and does not alter existing
+Verifier Standard status or blast-radius calculations until a separate normative rule is
+approved.
+
+Horizon summaries are indexed by accepted exposures rather than wall-clock time, for
+example the last 10, 100, and 1,000 exposures plus lifetime. The complete vector and its
+dispersion are reported. It is not collapsed into one rankable scalar.
+
+### 4.5 Dual causal representation
+
+Forward TRUST and backward RUST are messages over the same directed development graph,
+not positive and negative values on one scalar. The forward message asks which
+bounded parent obligations are available to a child. The backward message asks which
+recorded ancestors can explain an observed child deviation. A node may carry both without
+cancellation: positive support for one predicate does not erase RUST for another, and
+RUST on one descendant does not erase unrelated support.
+
+The recorded causal-provenance graph therefore represents both the generative direction
+used to explain how claims and artifacts develop and the diagnostic direction used to
+identify where a later contradiction may have entered the architecture. This memetic
+propagation is causally meaningful as recorded provenance without, by traversal alone,
+establishing intervention-level physical causality or causal localization.
+
+### 4.6 Prior art boundary
+
+Proper scoring rules provide a lower-is-better penalty analogy when a claim is genuinely
+probabilistic; the original references include [Brier (1950)](https://journals.ametsoc.org/view/journals/mwre/78/1/1520-0493_1950_078_0001_vofeit_2_0_co_2.xml)
+and [Good (1952)](https://rss.onlinelibrary.wiley.com/doi/10.1111/j.2517-6161.1952.tb00104.x).
+RUST is not itself a proper scoring rule unless its declared expectation and comparison
+rule satisfy the corresponding conditions.
+
+[Friedman and Resnick (2001)](https://onlinelibrary.wiley.com/doi/abs/10.1111/j.1430-9134.2001.00173.x)
+analyze the social cost of cheap pseudonyms in party reputation systems. Content and
+relation binding change the coordinate being measured, but, as noted above, do not prove
+that semantically equivalent repackaging is impossible.
+
+[CVE](https://www.cve.org/), [OpenSSF Scorecard](https://openssf.org/scorecard/), and
+[Certificate Transparency](https://www.rfc-editor.org/rfc/rfc9162.html) are useful
+comparisons for public negative signals, automated project checks, and append-only public
+records. They are not equivalent mechanisms and do not validate this design. This note
+claims only a proposed composition of relation-bound deviation, memetic causal backtrace,
+exposure deduplication, typed uncertainty, and separation from verdict material. It makes
+no novelty claim.
+
+## 5. The information-free limit
+
+Re-running the exact same pure deterministic function, implementation, and frozen inputs
+is expected to be information-poor after the first successful check. It exercises the
+plumbing again but introduces no new world state.
+
+Reverification can add information in three places:
+
+1. **independent implementation:** a checker with no shared verdict-producing code may
+ reveal a specification or implementation disagreement;
+2. **new bounded test vector:** a previously unexercised input may falsify a general
+ declaration; and
+3. **boundary re-resolution:** an external artifact can be rehashed, a reference can be
+ resolved again under a pinned policy, or a declared revocation/availability source can
+ expose a changed state.
+
+The sealed core and the changing boundary must remain distinguishable. A boundary
+snapshot is content-addressed; the fact that it was retrieved later is not itself verdict
+material. If a current boundary cannot be resolved, freshness is `UNKNOWN`. Absence of a
+new event never proves that an old event remains current.
+
+For a hidden witness, cryptographic zero knowledge encloses architectural zero knowledge:
+proof verification is the repeatable public interface around the exact program, predicate,
+commitments, journal, parameters, and verifier. It proves only the execution and journal
+bound by the selected proof system. It does not reveal or independently observe the
+witness, establish that the witness was externally truthful, or add prover-identity TRUST.
+
+## 6. ROT: proposed `STALE` entry and successor semantics
+
+ROT is typed, time-indexed degradation of current admissibility. It is not passive decay,
+an actor-reputation penalty, or a rewrite of historical truth. This section tests one
+possible ROT entry condition for the existing `STALE` states; it does not define a general
+ROT serialized receipt format or transition algebra.
+
+This repository currently has two distinct `STALE` enum members:
+
+- `CoordinateStatus.STALE` in `src/verifier/core/geometry.py`, serialized by the
+ VSTD-2 receipt schema; and
+- `ArtifactStatus.STALE` in `src/verifier/data/models.py`, consumed by graph admission and
+ the Layer 4 degradation order.
+
+They are not the same type. Both tokens are existing wire vocabulary, and this note does
+not change or reserve their meaning.
+
+The proposed non-normative entry rule is:
+
+> A coordinate or artifact is derivably `STALE` only when an accepted append-only
+> reverification event, under the subject's declared resolver policy, shows that an
+> external binding on which the earlier result depended no longer resolves to the content
+> or admissible state committed by that earlier result.
+
+Required event evidence includes the prior receipt digest, subject kind and digest,
+resolver-policy digest, previous boundary snapshot digest, newly resolved boundary
+snapshot digest, and a repeatable trace or verifiable proof of the mismatch. Mere age,
+wall-clock passage, missing availability, accumulated RUST, popularity, or a reporter's
+assertion is not a `STALE` entry condition.
+
+If the boundary cannot be resolved, the result is `UNKNOWN`, not `STALE`. If two
+admissible current snapshots are incompatible and the resolver policy does not order
+them, the result is `CONFLICTED`, not silently selected.
+
+There is no historical mutation and therefore no literal exit from `STALE`. The earlier
+receipt and its derived stale event remain immutable. Recovery creates a successor
+receipt or coordinate bound to the new boundary state. That successor can be evaluated
+on its own evidence; it does not cleanse the earlier coordinate. A current-view function
+may follow an append-only, digest-linked event chain to the declared head, but an absent or
+unavailable head leaves the current view `UNKNOWN`.
+
+Before implementation, separate transition functions are required for
+`CoordinateStatus` and `ArtifactStatus`; shared prose is not permission to conflate the
+two frozen enum families. Ledger ordering, fork handling, inclusion proofs, and resolver
+trust coordinates also remain to be specified.
+
+RUST has no ROT status-transition role. A RUST view may describe deviations that accompanied
+a stale event, but no magnitude of historical deviation is sufficient to produce or clear
+`STALE`.
+
+## 7. Zero-knowledge trichotomy correction
+
+The Round 1 guest accepts only `CandidateState::Supported` and always commits
+`predicate_satisfied: true`. A valid proof therefore authenticates one favorable path.
+Failure to present a proof is ambiguous among no attempt, prover failure, an unsatisfied
+witness, `UNKNOWN`, and `CONFLICTED`.
+
+The exact proposed journal shape change is:
+
+```rust
+pub struct PublicJournal {
+ // Existing binding fields remain.
+ pub verdict: CandidateState,
+ pub predicate_satisfied: bool,
+}
+```
+
+Required invariant:
+
+```text
+predicate_satisfied == true if and only if verdict == Supported
+ and the fixed predicate is satisfied
+```
+
+`Unknown` and `Conflicted` must be valid authenticated journal outcomes when the guest's
+fixed rules derive them. They must never be encoded as a missing proof. The existing
+assertion that rejects both states would be replaced by a total verdict calculation, and
+the public-envelope checker would compare both fields to the authenticated journal.
+
+This structural correction is necessary but not sufficient. The current private witness
+contains a caller-supplied candidate state and only one measurement. It lacks the evidence
+structure needed to **derive** a conflict or to distinguish genuine insufficiency from a
+caller merely labeling an input `Unknown`. Publishing a private input tag as a public
+verdict would authenticate the tag, not establish the verdict. Before implementation, the
+fixed predicate must define how all three states are derived from bounded witness data and
+must add enough witness structure to derive `Conflicted`.
+
+No change is made in Round 2. The existing proof remains accurately described as one real
+proof for one favorable bounded predicate, not as a full trichotomy implementation.
+
+## 8. What the Zero Identity half keeps and loses
+
+### Kept as portable discipline
+
+- Missing evidence stays `UNKNOWN`.
+- Contradictory admissible evidence stays `CONFLICTED`.
+- Minimization may narrow a claim boundary but must not widen one.
+- Recorded ancestry does not establish that an authority, property, or defect transferred
+ across every edge.
+- Semantic results, external attestations, declared assumptions, and protocol guarantees
+ remain separate evidence classes.
+- The 19 prohibited inferences remain useful in the optional actor-facing profile. The
+ actor-agnostic subset also constrains Tier 0: missing evidence is not safety; recorded
+ ancestry is not established influence; and one evidence class cannot silently upgrade
+ another.
+
+### Forbidden as automatic trust or verdict weight
+
+- pseudonym or signing-key identity;
+- issuer, authorization grant, actor trust root, or revocation source;
+- uniqueness, Sybil-resistance, independence, or accountability claims; and
+- authorship degree or credential ancestry.
+
+These coordinates may still be bound when the declared claim needs them. None is a
+general trust signal, none upgrades an artifact result, and none permanently classifies an
+entity as an actor rather than an artifact.
+
+Deployments may still use the bounded identity-disclosure reference
+model alongside Tier 0 when they need authenticated authorization. Its results must not
+raise or lower the artifact-bound reverification result.
+
+### Recorded model-to-code drift
+
+`model/zero_identity_model.json` lists 13 `minimum_public_actor_coordinates` and seven
+`optional_provenance_coordinates`. `evaluate.py` defines six structural
+`REQUIRED_PUBLIC_COORDINATES`; other coordinates are checked later by individual rules.
+The tests currently do not enforce equality between the declarative list and the
+structural list.
+
+This mismatch does not justify a favorable result from missing evidence—the individual
+rules generally preserve `UNKNOWN` or reject—but it makes the declarative contract stale.
+The follow-on should establish one source of truth and add a containment test. It is not
+changed in this design-only round.
+
+## 9. Explicit non-claims
+
+This design does not establish or claim:
+
+- anonymity, unlinkability, untraceability, confidentiality, or protection from traffic
+ analysis;
+- actor uniqueness, actor independence, authorization, accountability, or Sybil
+ resistance;
+- that all operational abuse is harmless; identity-less submission still permits spam
+ and resource exhaustion;
+- real-world truth, complete evidence, honest witnesses, or correct external observations;
+- that a divergent output is automatically correct;
+- that repeated agreement increases trust, probability, ladder level, or status;
+- that trustless means no trust roots, no cryptographic assumptions, or no trusted
+ software;
+- that pure recomputation supplies new information under unchanged coordinates;
+- that `STALE` can be inferred from elapsed time, RUST, missing records, or popularity;
+- that a RUST history predicts future behavior; it records only past measured deviation;
+- that RUST is comparable across predicates, units, or basins;
+- that a relation-bound RUST ledger prevents semantically equivalent repackaging;
+- that `TRANSFERRED` proves direct observation, causation, intent, or fault at an ancestor;
+- that RUST concentration is a probability of guilt or a substitute for localization;
+- that forward TRUST proves a child claim without its own transformation and
+ predicate evidence;
+- that authorship is actor identity, or that either is required by Tier 0;
+- independent implementation, external audit, external adoption, production readiness,
+ or a security review of this synthesis;
+- a new ladder rung, conformance requirement, schema, lifecycle token, or serialized receipt contract
+ identifier; or
+- novelty of the individual ingredients or of their proposed composition.
+
+## 10. Open questions
+
+1. **Transmission rules:** which typed creation, input, execution, and transformation
+ edges admit RUST transfer, and which reference-only edges stop it?
+2. **Localization:** which intervention or independent evidence promotes inherited
+ `TRANSFERRED` RUST to `LOCALIZED` contribution?
+3. **Role coordinates:** how are a coding-agent artifact and its bound acting instance
+ related without claiming they are identical or permanently assigning either category?
+4. **Forward trust transfer:** which parent evidence classes and edge checks supply a
+ bounded positive signal to each child obligation, and how is the weakest required
+ support preserved?
+5. **Concentration:** which independence and basin conditions let intersecting backtraces
+ prioritize a common ancestor without converting frequency into causal proof?
+6. **Deviation rules:** which fixed magnitude rule applies to each expectation type, and
+ who may define it without letting the measured object tune its own penalty?
+7. **Cross-basin comparison:** should comparison be explicitly undefined unless predicate,
+ unit, mechanism class, and deviation rule all match?
+8. **Exposure admission:** which distinct test vectors and boundary snapshots are
+ sufficiently non-duplicative to count as new falsification opportunities without
+ converting an actor-role coordinate into actor trust?
+9. **Independent implementation:** what evidence is sufficient to show that two checkers
+ share no verdict-producing code?
+10. **Ledger convergence:** how are concurrent append-only event branches, unavailable log
+ heads, and resolver equivocation represented without a privileged mutable registry?
+11. **`STALE` governance:** should `CoordinateStatus.STALE` and `ArtifactStatus.STALE` share
+ one abstract event model while retaining separate transition functions and schemas?
+12. **ZK trichotomy:** what bounded private witness structure lets the guest derive
+ `Supported`, `Unknown`, and `Conflicted` rather than authenticate a caller's label?
+13. **Observational evidence:** VSTD-3 device observations cannot be recreated from artifact
+ bytes. What source testimony and attestation assumptions must a boundary snapshot expose
+ without turning the observer's identity into verdict weight?
+14. **Author is not actor:** if optional authorship is later marked inside artifact bytes,
+ the mark changes the content digest. Should authorship instead use a detached,
+ separately content-addressed statement, and what claim could it safely support?
+15. **ZI declarative drift:** should the evaluator import a generated coordinate contract,
+ or should a repository containment test require the model and code lists to agree?
+16. **Operational controls:** how can anonymous admission control limit denial-of-service
+ without becoming a correctness signal or a de facto identity requirement?
+
+Round 2 takes the normative role and propagation directions as input but implements none
+of these open mechanics. The next safe step is a bounded dual-direction event-ledger
+experiment with no new serialized receipt identifier, followed separately by the ZK trichotomy
+experiment once its derivation rule is specified.
diff --git a/experiments/github_verdict_neutrality/experiment.json b/experiments/github_verdict_neutrality/experiment.json
new file mode 100644
index 0000000..e9e5b62
--- /dev/null
+++ b/experiments/github_verdict_neutrality/experiment.json
@@ -0,0 +1,190 @@
+{
+ "profile": {
+ "id": "vstd.experimental-workflow",
+ "version": "0.1",
+ "status": "EXPERIMENTAL_NON_NORMATIVE"
+ },
+ "experiment": {
+ "id": "experiment-github-verdict-neutrality",
+ "title": "GitHub workflow observations remain verdict-neutral",
+ "question": "Does the GitHub adapter preserve successful workflow and merge states without converting them into a VSTD verdict?",
+ "state": "COMPLETED",
+ "started_at": "2026-08-24T12:00:00Z"
+ },
+ "hypotheses": [
+ {
+ "id": "hypothesis-platform-non-upgrade",
+ "statement": "Every supported GitHub observation maps with verification_effect NONE.",
+ "falsification_condition": "Any supported snapshot produces a workflow event that grants or implies a VSTD verdict.",
+ "state": "SUPPORTED"
+ }
+ ],
+ "preregistration": {
+ "state": "NONE",
+ "recorded_at": null,
+ "artifact_id": null,
+ "limitations": [
+ "This is a deterministic adapter specimen rather than a preregistered empirical study."
+ ]
+ },
+ "artifacts": [],
+ "budgets": [
+ {
+ "id": "budget-github-events",
+ "resource": "normalized-platform-events",
+ "limit": 5,
+ "consumed": 5,
+ "unit": "event",
+ "scope": "checked-in GitHub snapshot"
+ }
+ ],
+ "actions": [
+ {
+ "id": "action-map-github-snapshot",
+ "kind": "WORKFLOW_ADAPTER_MAPPING",
+ "target": "normalized GitHub issue, commit, workflow, artifact, and pull-request states",
+ "state": "COMPLETED",
+ "priority": 1,
+ "selected_because": "The public repository is hosted on GitHub, so the first adapter must demonstrate that native repository success does not become verification success.",
+ "selection_evidence_ids": [
+ "hypothesis-platform-non-upgrade"
+ ],
+ "alternatives_considered": [
+ "treat Git history as the experiment record",
+ "map workflow success to PASS"
+ ],
+ "budget_ids": [
+ "budget-github-events"
+ ],
+ "depends_on": [],
+ "triggered_by": [],
+ "expected_artifact_effect": "Produce a portable event record whose platform successes have no verification effect.",
+ "substrate": {
+ "kind": "workflow-platform-adapter",
+ "name": "VSTD normalized GitHub adapter",
+ "version": "0.1",
+ "coordinate": "repo:src/verifier/experimental_workflow/github.py"
+ },
+ "native_result_ids": [],
+ "produced_artifact_ids": []
+ }
+ ],
+ "observations": [
+ {
+ "id": "observation-five-neutral-events",
+ "action_id": "action-map-github-snapshot",
+ "recorded_at": "2026-08-24T12:06:00Z",
+ "statement": "Five supported GitHub observations were mapped and every event retained verification_effect NONE.",
+ "status": "OBSERVED",
+ "evidence_artifact_ids": [],
+ "limitations": [
+ "The normalized snapshot is a checked-in specimen and is not a live GitHub API observation."
+ ]
+ }
+ ],
+ "native_results": [],
+ "adaptations": [],
+ "amendments": [],
+ "challenges": [],
+ "horizons": [
+ {
+ "id": "horizon-underlying-correctness",
+ "status": "UNKNOWN",
+ "description": "Correctness of the change represented by the workflow and pull request",
+ "reason": "Repository state alone does not include a bound domain-verifier result and VSTD receipt."
+ }
+ ],
+ "publication": {
+ "state": "INTERNAL",
+ "artifact_ids": []
+ },
+ "workflow_events": [
+ {
+ "id": "github-event-16194f8f10fe0e61e766",
+ "kind": "PLATFORM_WORKFLOW_RUN",
+ "recorded_at": "2026-08-24T12:03:00Z",
+ "source": {
+ "platform": "github",
+ "repository": "github:example/verifier-integration",
+ "coordinate": "workflow-run:9001"
+ },
+ "native_state": "completed/success",
+ "verification_effect": "NONE",
+ "details": {
+ "id": 9001,
+ "workflow": "conformance",
+ "head_sha": "1111111111111111111111111111111111111111"
+ }
+ },
+ {
+ "id": "github-event-542a5a9a6b0b9b37d3a3",
+ "kind": "PLATFORM_PULL_REQUEST",
+ "recorded_at": "2026-08-24T12:05:00Z",
+ "source": {
+ "platform": "github",
+ "repository": "github:example/verifier-integration",
+ "coordinate": "pull-request:42"
+ },
+ "native_state": "closed/MERGED",
+ "verification_effect": "NONE",
+ "details": {
+ "number": 42,
+ "head_sha": "1111111111111111111111111111111111111111",
+ "base_sha": "0000000000000000000000000000000000000000",
+ "merged": true
+ }
+ },
+ {
+ "id": "github-event-6d179bfe41711ce7be21",
+ "kind": "PLATFORM_COMMIT",
+ "recorded_at": "2026-08-24T12:00:00Z",
+ "source": {
+ "platform": "github",
+ "repository": "github:example/verifier-integration",
+ "coordinate": "commit:1111111111111111111111111111111111111111"
+ },
+ "native_state": "RECORDED",
+ "verification_effect": "NONE",
+ "details": {
+ "sha": "1111111111111111111111111111111111111111",
+ "subject": "Run bounded checker"
+ }
+ },
+ {
+ "id": "github-event-bef92a6556f4ddf05651",
+ "kind": "PLATFORM_ISSUE",
+ "recorded_at": "2026-08-24T12:04:00Z",
+ "source": {
+ "platform": "github",
+ "repository": "github:example/verifier-integration",
+ "coordinate": "issue:41"
+ },
+ "native_state": "closed",
+ "verification_effect": "NONE",
+ "details": {
+ "number": 41,
+ "title": "Test the bounded checker"
+ }
+ },
+ {
+ "id": "github-event-d32d5b76ae8d24c7d5f7",
+ "kind": "PLATFORM_ARTIFACT",
+ "recorded_at": "2026-08-24T12:03:00Z",
+ "source": {
+ "platform": "github",
+ "repository": "github:example/verifier-integration",
+ "coordinate": "workflow-artifact:9002"
+ },
+ "native_state": "AVAILABLE",
+ "verification_effect": "NONE",
+ "details": {
+ "id": 9002,
+ "name": "checker-output",
+ "digest": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
+ "run_id": 9001
+ }
+ }
+ ],
+ "interventions": [],
+ "manifest_digest": "sha256:3b98310d35c20e7099d242e2c655e4bf8dc62d91298adc04e4dc2f56f2f79d89"
+}
diff --git a/pyproject.toml b/pyproject.toml
index bca87c6..e8e70e8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,14 +4,22 @@ build-backend = "setuptools.build_meta"
[project]
name = "verifier-standard"
-version = "1.1.3"
-description = "Reference implementation for bounded verification receipts and provenance."
+version = "1.2.0"
+description = "Verification-domain language and reference implementation for bounded computational claims."
readme = "README.md"
requires-python = ">=3.10"
license = "Apache-2.0"
license-files = ["LICENSE", "NOTICE"]
authors = [{name = "TimeLordRaps"}]
-keywords = ["verification", "provenance", "reproducibility", "artificial-intelligence"]
+keywords = [
+ "verification",
+ "evidence",
+ "provenance",
+ "refutability",
+ "reproducibility",
+ "software-supply-chain",
+ "artificial-intelligence",
+]
classifiers = [
"Development Status :: 3 - Alpha",
"Programming Language :: Python :: 3",
@@ -25,14 +33,24 @@ dependencies = []
[project.urls]
Homepage = "https://github.com/TimeLordRaps/verifier"
Documentation = "https://timelordraps.github.io/verifier/"
+"API Policy" = "https://github.com/TimeLordRaps/verifier/blob/main/docs/API_STABILITY.md"
Issues = "https://github.com/TimeLordRaps/verifier/issues"
+Specification = "https://github.com/TimeLordRaps/verifier/tree/main/standard"
+Changelog = "https://github.com/TimeLordRaps/verifier/blob/main/CHANGELOG.md"
+Security = "https://github.com/TimeLordRaps/verifier/security/policy"
[project.optional-dependencies]
yaml = ["pyyaml>=6.0"]
llguidance = ["llguidance==1.8.0"]
torch = ["torch>=2.2"]
jsonschema = ["jsonschema>=4.18"]
-test = ["pytest>=8.0", "pyyaml>=6.0", "jsonschema>=4.18"]
+seal = ["cryptography==50.0.0"]
+scitt = [
+ "scitt-cose==0.2.2",
+ "cbor2==6.1.4",
+ "cryptography==50.0.0",
+]
+test = ["pytest>=8.0", "coverage==7.15.4", "pyyaml>=6.0", "jsonschema>=4.18"]
release = ["build==1.5.0", "twine==7.0.0"]
[project.scripts]
@@ -41,16 +59,19 @@ release = ["build==1.5.0", "twine==7.0.0"]
vstd = "verifier.runtime.public_cli:main"
# Retain the project-name alias for compatibility on platforms where it is unambiguous.
verifier = "verifier.runtime.public_cli:main"
-# `verifiable` is retained as a deprecated alias and MUST NOT be removed: published
-# VSTD receipts bind falsification conditions that invoke it by name (see
-# examples/generic_run/receipt.json). Removing it would render already-published
-# refutation instructions unrunnable. The alias is a command name only; it no longer
-# corresponds to any import package.
+# `verifiable` is retained as a deprecated alias and MUST NOT be removed: receipts
+# published in the v0.1.0 and v0.2.0 release artifacts, which predate the rename, bind
+# falsification conditions that invoke it by name. Removing it would render those
+# already-published refutation instructions unrunnable. No file in the current tree
+# binds it. The alias is a command name only; it no longer corresponds to any import
+# package.
verifiable = "verifier.runtime.public_cli:main"
[tool.setuptools.package-data]
-verifier = ["hardware/*.json", "specifications/*.md"]
+verifier = ["artifact_control/*.json", "hardware/*.json", "specifications/*.md"]
[tool.pytest.ini_options]
+pythonpath = ["src"]
testpaths = ["tests"]
norecursedirs = ["artifacts_tmp", "build", "dist", ".git", ".venv"]
+asyncio_default_fixture_loop_scope = "function"
diff --git a/receipts/schema/vstd1_generic_run_receipt.json b/receipts/schema/vstd1_generic_run_receipt.json
new file mode 100644
index 0000000..63fbbfd
--- /dev/null
+++ b/receipts/schema/vstd1_generic_run_receipt.json
@@ -0,0 +1,311 @@
+{
+ "$comment": "Terminology: JavaScript Object Notation (JSON); Secure Hash Algorithm 256-bit (SHA-256); Verifier Standard (VSTD).",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://timelordraps.github.io/verifier/schemas/vstd1_generic_run_receipt.json",
+ "title": "VSTD-1 Generic Computational Run Receipt",
+ "description": "Strict shape for the VSTD-1 generic_computational_run profile.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "schema_version",
+ "receipt_kind",
+ "receipt_id",
+ "canonical_digest",
+ "claim_title",
+ "claim_statement",
+ "claim_scope",
+ "claim_limitations",
+ "falsification_condition",
+ "source_state",
+ "inputs",
+ "outputs",
+ "execution",
+ "claims",
+ "provenance_linkage",
+ "reproducibility",
+ "assessment_context"
+ ],
+ "properties": {
+ "schema_version": { "const": "VSTD-1" },
+ "receipt_kind": { "const": "generic_computational_run" },
+ "receipt_id": { "type": "string", "minLength": 1 },
+ "canonical_digest": { "$ref": "#/$defs/sha256" },
+ "claim_title": { "type": "string" },
+ "claim_statement": { "type": "string" },
+ "claim_scope": { "type": "string" },
+ "claim_limitations": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "falsification_condition": { "type": "string" },
+ "source_state": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "target_name",
+ "portable_repository_id",
+ "local_repository_path",
+ "git",
+ "runtime",
+ "captured_at_utc",
+ "command_executed",
+ "source_file_hashes"
+ ],
+ "properties": {
+ "target_name": { "type": "string" },
+ "portable_repository_id": { "type": "string" },
+ "local_repository_path": { "type": "string" },
+ "captured_at_utc": { "type": "string" },
+ "command_executed": { "type": "string" },
+ "source_file_hashes": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#/$defs/sha256" }
+ },
+ "git": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["commit_sha", "branch", "is_dirty"],
+ "properties": {
+ "commit_sha": { "type": "string" },
+ "branch": { "type": "string" },
+ "is_dirty": { "type": "boolean" },
+ "dirty_files": { "type": "array", "items": { "type": "string" } },
+ "untracked_files": { "type": "array", "items": { "type": "string" } },
+ "remote_origin": { "type": "string" }
+ }
+ },
+ "runtime": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["python_version", "platform_system"],
+ "properties": {
+ "python_version": { "type": "string" },
+ "python_implementation": { "type": "string" },
+ "platform_system": { "type": "string" },
+ "platform_release": { "type": "string" },
+ "platform_machine": { "type": "string" },
+ "hostname_masked": { "type": "string" }
+ }
+ }
+ }
+ },
+ "inputs": {
+ "type": "array",
+ "items": { "$ref": "#/$defs/artifact" }
+ },
+ "outputs": {
+ "type": "array",
+ "items": { "$ref": "#/$defs/artifact" }
+ },
+ "execution": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "command",
+ "cwd",
+ "started_at_utc",
+ "ended_at_utc",
+ "elapsed_ms",
+ "exit_code",
+ "outcome",
+ "python_version",
+ "platform_system",
+ "determinism_declared",
+ "seed_declared",
+ "stdout_sha256",
+ "stderr_sha256",
+ "stdout_snippet",
+ "stderr_snippet"
+ ],
+ "properties": {
+ "command": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "type": "string" }
+ },
+ "cwd": { "type": "string" },
+ "started_at_utc": { "type": "string" },
+ "ended_at_utc": { "type": "string" },
+ "elapsed_ms": { "type": "number", "minimum": 0 },
+ "exit_code": { "type": ["integer", "null"] },
+ "outcome": {
+ "enum": ["COMPLETED", "NONZERO_EXIT", "MISSING_INPUT", "MISSING_OUTPUT", "TIMEOUT", "EXCEPTION"]
+ },
+ "python_version": { "type": "string" },
+ "platform_system": { "type": "string" },
+ "determinism_declared": {
+ "enum": ["DETERMINISTIC", "NONDETERMINISTIC", "UNKNOWN"]
+ },
+ "seed_declared": { "type": ["string", "null"] },
+ "stdout_sha256": { "$ref": "#/$defs/sha256" },
+ "stderr_sha256": { "$ref": "#/$defs/sha256" },
+ "stdout_snippet": { "type": "string" },
+ "stderr_snippet": { "type": "string" }
+ }
+ },
+ "claims": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "execution_completed",
+ "output_digests_recorded",
+ "all_declared_artifacts_present",
+ "evaluator_claims",
+ "external_evaluation"
+ ],
+ "properties": {
+ "execution_completed": { "type": "boolean" },
+ "output_digests_recorded": { "type": "boolean" },
+ "all_declared_artifacts_present": { "type": ["boolean", "null"] },
+ "evaluator_claims": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["evaluator_name", "metric_name", "value", "computed_by", "verified_independently"],
+ "properties": {
+ "evaluator_name": { "type": "string" },
+ "metric_name": { "type": "string" },
+ "value": {},
+ "computed_by": {
+ "enum": ["bound_output_extraction", "declared_by_manifest_author"]
+ },
+ "verified_independently": { "const": false }
+ }
+ }
+ },
+ "external_evaluation": {
+ "oneOf": [
+ { "type": "null" },
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["source", "description", "reported_value", "evidence_kind", "evidence_ref", "attested"],
+ "properties": {
+ "source": { "type": "string" },
+ "description": { "type": "string" },
+ "reported_value": {},
+ "evidence_kind": { "type": "string" },
+ "evidence_ref": { "type": ["string", "null"] },
+ "attested": { "const": false }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "provenance_linkage": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["dataset_receipt_path", "artifact_id", "found_in_hypergraph", "ancestor_count", "ancestor_ids"],
+ "properties": {
+ "dataset_receipt_path": { "type": "string" },
+ "artifact_id": { "type": "string" },
+ "found_in_hypergraph": { "type": "boolean" },
+ "ancestor_count": { "type": ["integer", "null"], "minimum": 0 },
+ "ancestor_ids": { "type": "array", "items": { "type": "string" } }
+ }
+ }
+ },
+ "reproducibility": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["highest_demonstrated_level", "declared_ceiling", "supported_levels", "reproduction_command"],
+ "properties": {
+ "highest_demonstrated_level": { "type": ["string", "null"] },
+ "declared_ceiling": { "type": "string" },
+ "supported_levels": { "type": "array", "items": { "type": "string" } },
+ "reproduction_command": { "type": "string" }
+ }
+ },
+ "assessment_context": {
+ "type": "object",
+ "description": "Mechanism, declared resource-bound, commitment, and refutation coordinates for this VSTD-1 generic run. The container does not establish VSTD-4 conformance.",
+ "additionalProperties": false,
+ "required": ["verifier", "resource_bounds", "prior_commitment", "refutation_surface"],
+ "properties": {
+ "verifier": {
+ "type": "object",
+ "description": "Identity and implementation coordinates for the generic-run mechanism; these fields do not establish actor or implementation independence.",
+ "additionalProperties": false,
+ "required": ["specification_hash", "implementation_hash", "parser_hash", "certificate_format", "format_fragment", "dependencies", "deterministic"],
+ "properties": {
+ "specification_hash": { "$ref": "#/$defs/specificationBinding" },
+ "implementation_hash": { "$ref": "#/$defs/prefixedSha256" },
+ "parser_hash": { "$ref": "#/$defs/prefixedSha256" },
+ "certificate_format": { "type": "string" },
+ "format_fragment": { "type": "string" },
+ "dependencies": { "type": "array", "items": { "type": "string" } },
+ "deterministic": { "type": "boolean" }
+ }
+ },
+ "resource_bounds": {
+ "type": "object",
+ "description": "Manifest-declared assessment bounds recorded by the generic writer; presence does not establish enforcement.",
+ "additionalProperties": false,
+ "required": ["verification_cost_bound", "memory_bound", "certificate_size_bound"],
+ "properties": {
+ "verification_cost_bound": { "type": "integer", "minimum": 0 },
+ "memory_bound": { "type": "integer", "minimum": 0 },
+ "certificate_size_bound": { "type": "integer", "minimum": 0 }
+ }
+ },
+ "prior_commitment": {
+ "type": "string",
+ "description": "Recorded commitment declaration; receipt inclusion alone does not establish that it preceded execution."
+ },
+ "refutation_surface": {
+ "type": "object",
+ "description": "Domain-refutation map whose named fields have fixed meanings; additional fields remain declarations and earn no result without an applicable mechanism.",
+ "required": ["admissible_refutations", "excluded_claims", "falsification_condition"],
+ "properties": {
+ "admissible_refutations": { "type": "array", "items": { "type": "string" } },
+ "excluded_claims": { "type": "array", "items": { "type": "string" } },
+ "falsification_condition": { "type": "string" }
+ }
+ }
+ }
+ }
+ },
+ "$defs": {
+ "sha256": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{64}$"
+ },
+ "prefixedSha256": {
+ "type": "string",
+ "pattern": "^sha256:[0-9a-f]{64}$"
+ },
+ "specificationBinding": {
+ "oneOf": [
+ { "$ref": "#/$defs/prefixedSha256" },
+ { "type": "string", "pattern": "^UNAVAILABLE:.+$" }
+ ]
+ },
+ "artifact": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["path", "role", "present", "sha256", "byte_size"],
+ "properties": {
+ "path": { "type": "string" },
+ "role": { "type": "string" },
+ "present": { "type": "boolean" },
+ "sha256": { "oneOf": [{ "$ref": "#/$defs/sha256" }, { "type": "null" }] },
+ "byte_size": { "type": ["integer", "null"], "minimum": 0 }
+ },
+ "allOf": [
+ {
+ "if": { "properties": { "present": { "const": true } } },
+ "then": {
+ "properties": {
+ "sha256": { "$ref": "#/$defs/sha256" },
+ "byte_size": { "type": "integer", "minimum": 0 }
+ }
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/receipts/schema/vstd1_receipt.json b/receipts/schema/vstd1_receipt.json
index ac2bd90..814c39e 100644
--- a/receipts/schema/vstd1_receipt.json
+++ b/receipts/schema/vstd1_receipt.json
@@ -1,11 +1,13 @@
{
+ "$comment": "Terminology: Verifier Standard (VSTD).",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://timelordraps.github.io/verifier/schemas/vstd1_receipt.json",
"title": "VSTD-1 Claim Mechanics Receipt",
- "description": "Schema for VSTD-1 claim-mechanics receipts. The VSTD-0.1 wire identifier is frozen for compatibility.",
+ "description": "Schema for VSTD-1 claim-mechanics receipts.",
"type": "object",
"required": [
"schema_version",
+ "receipt_kind",
"receipt_id",
"canonical_digest",
"claim",
@@ -18,7 +20,10 @@
"properties": {
"schema_version": {
"type": "string",
- "enum": ["VSTD-0.1"]
+ "enum": ["VSTD-1"]
+ },
+ "receipt_kind": {
+ "const": "claim_mechanics"
},
"receipt_id": {
"type": "string",
@@ -92,7 +97,39 @@
"grounding_result": { "type": "object" },
"structural_integrity_passed": { "type": "boolean" },
"trusted_computing_base": { "type": "object" },
- "audit_notes": { "type": "array", "items": { "type": "string" } }
+ "audit_notes": { "type": "array", "items": { "type": "string" } },
+ "independence_basis": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["independently_verified", "actor_independence", "implementation_separation", "runtime_separation", "evidence"],
+ "properties": {
+ "independently_verified": {
+ "type": "boolean",
+ "description": "A conformance claim, not a structural inference. VSTD 1.2.0's bundled runtime accepts only false because it has no actor/execution evidence-binding validator."
+ },
+ "actor_independence": { "enum": ["EVIDENCED", "DECLARED", "NOT_DEMONSTRATED", "CONFLICTED"] },
+ "implementation_separation": { "enum": ["EVIDENCED", "DECLARED", "NOT_DEMONSTRATED", "CONFLICTED"] },
+ "runtime_separation": { "enum": ["EVIDENCED", "DECLARED", "NOT_DEMONSTRATED", "CONFLICTED"] },
+ "evidence": {
+ "type": "array",
+ "description": "References are declarations until an implemented profile resolves and validates their execution bindings.",
+ "items": { "type": "string", "minLength": 1 }
+ }
+ },
+ "allOf": [
+ {
+ "if": { "properties": { "independently_verified": { "const": true } } },
+ "then": {
+ "properties": {
+ "actor_independence": { "const": "EVIDENCED" },
+ "implementation_separation": { "const": "EVIDENCED" },
+ "runtime_separation": { "const": "EVIDENCED" },
+ "evidence": { "minItems": 1 }
+ }
+ }
+ }
+ ]
+ }
}
},
"provenance": {
diff --git a/receipts/schema/vstd2_receipt.json b/receipts/schema/vstd2_receipt.json
index 3047bdb..6861202 100644
--- a/receipts/schema/vstd2_receipt.json
+++ b/receipts/schema/vstd2_receipt.json
@@ -1,8 +1,9 @@
{
+ "$comment": "Terminology: abstract syntax tree (AST); intermediate representation (IR); Verifier Standard (VSTD).",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://timelordraps.github.io/verifier/schemas/vstd2_receipt.json",
"title": "VSTD-2 Verification Surface Receipt",
- "description": "VSTD-2 verification-surface document. The VSTD-0.2 wire identifier is frozen; semantic closure rules are enforced by verifier.core.geometry.validate_geometry.",
+ "description": "VSTD-2 verification-surface document. Semantic closure rules are enforced by verifier.core.geometry.validate_geometry.",
"type": "object",
"additionalProperties": false,
"required": [
@@ -16,7 +17,7 @@
"surface"
],
"properties": {
- "schema_version": {"const": "VSTD-0.2"},
+ "schema_version": {"const": "VSTD-2"},
"geometry_id": {"$ref": "#/$defs/nonEmpty"},
"primary_subject_id": {"$ref": "#/$defs/nonEmpty"},
"secondary_subject_id": {"type": ["string", "null"]},
diff --git a/receipts/schema/vstd3_accelerator_profile.json b/receipts/schema/vstd3_accelerator_profile.json
index 30e5031..5a96fc1 100644
--- a/receipts/schema/vstd3_accelerator_profile.json
+++ b/receipts/schema/vstd3_accelerator_profile.json
@@ -1,4 +1,5 @@
{
+ "$comment": "Terminology: artificial intelligence (AI); application-specific integrated circuit (ASIC); graphics processing unit (GPU); JavaScript Object Notation (JSON); neural processing unit (NPU); tensor processing unit (TPU); Verifier Standard (VSTD).",
"$defs": {
"AcceleratorProfile": {
"additionalProperties": false,
diff --git a/receipts/schema/vstd3_receipt.json b/receipts/schema/vstd3_receipt.json
index 322b923..252b9bd 100644
--- a/receipts/schema/vstd3_receipt.json
+++ b/receipts/schema/vstd3_receipt.json
@@ -1,4 +1,5 @@
{
+ "$comment": "Terminology: artificial intelligence (AI); application-specific integrated circuit (ASIC); graphics processing unit (GPU); JavaScript Object Notation (JSON); neural processing unit (NPU); tensor processing unit (TPU); Verifier Standard (VSTD).",
"$defs": {
"AcceleratorDescriptor": {
"additionalProperties": false,
diff --git a/receipts/schema/vstd4_certificate.json b/receipts/schema/vstd4_certificate.json
index c939b44..9e94237 100644
--- a/receipts/schema/vstd4_certificate.json
+++ b/receipts/schema/vstd4_certificate.json
@@ -1,8 +1,9 @@
{
+ "$comment": "Terminology: grounded decision certificate (GDC); Boolean satisfiability problem (SAT).",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://timelordraps.github.io/verifier/schemas/vstd4_certificate.json",
"title": "VSTD4-GDC-1 Decision Certificate",
- "description": "Grounded decision certificate for PASS, FAIL, or bounded UNKNOWN. Semantic and proof checks remain mandatory in the independent kernel.",
+ "description": "Grounded decision certificate for PASS, FAIL, or bounded UNKNOWN. Semantic and proof checks remain mandatory in the separately implemented kernel; this does not establish distinct actors.",
"type": "object",
"additionalProperties": false,
"required": ["header", "formula", "grounding", "decision", "hints"],
diff --git a/receipts/schema/vstd4_receipt.json b/receipts/schema/vstd4_receipt.json
index 83ebe2e..0de6dfc 100644
--- a/receipts/schema/vstd4_receipt.json
+++ b/receipts/schema/vstd4_receipt.json
@@ -1,7 +1,9 @@
{
+ "$comment": "Terminology: grounded decision certificate (GDC); Verifier Standard (VSTD).",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://timelordraps.github.io/verifier/schemas/vstd4_receipt.json",
"title": "VSTD-4 Refutability Receipt",
+ "description": "Legacy-compatible VSTD-4 shape. Caller-supplied rung references remain a candidate with conformance NOT_ESTABLISHED. The additive EVIDENCE_BOUND form embeds exact proposition bindings and evidence bytes so registered mechanisms can be rerun; schema validity alone never establishes conformance.",
"type": "object",
"additionalProperties": false,
"required": ["schema_version", "receipt_id", "claim_id", "binding", "vstd4_depth", "rung_evidence", "witness", "ceiling_refutation", "blocking_rungs", "status"],
@@ -10,7 +12,10 @@
"receipt_id": {"type": "string", "pattern": "^VFY-4-[A-Za-z0-9._:-]+$"},
"claim_id": {"type": "string", "minLength": 1},
"binding": {"$ref": "#/$defs/binding"},
- "vstd4_depth": {"type": "integer", "minimum": 0, "maximum": 14},
+ "vstd4_depth": {"type": "integer", "minimum": 0, "maximum": 14, "description": "Candidate depth unless depth_kind is EVIDENCE_BOUND and all replay checks establish the exact preconditions and rungs."},
+ "depth_kind": {"enum": ["CANDIDATE", "EVIDENCE_BOUND"]},
+ "conformance_status": {"enum": ["NOT_ESTABLISHED", "ESTABLISHED"]},
+ "kernel_outcome": {"enum": ["ACCEPTED", "REJECTED", "REFUSED"]},
"rung_evidence": {
"type": "object",
"additionalProperties": false,
@@ -27,23 +32,93 @@
"witness": {"anyOf": [{"$ref": "vstd4_certificate.json"}, {"type": "null"}]},
"ceiling_refutation": {"anyOf": [{"$ref": "vstd4_certificate.json"}, {"type": "null"}]},
"blocking_rungs": {"type": "array", "items": {"pattern": "^4\\.(?:[1-9]|1[0-4])$"}, "uniqueItems": true},
- "status": {"enum": ["VALID", "CHALLENGED", "REVOKED", "STALE", "UNKNOWN"]},
+ "status": {"enum": ["VALID", "CHALLENGED", "REVOKED", "STALE", "UNKNOWN"], "description": "Current append-only challenge-ledger state. VALID means no admitted challenge currently disqualifies the claim; it does not establish VSTD-4 conformance."},
"refutation_surface": {"type": "object"},
"precommitment_envelope": {"type": "object"},
"availability": {"type": "array", "items": {"type": "object"}},
"challenge_records": {"type": "array", "items": {"type": "object"}},
- "refutability_closure": {"type": ["object", "null"]}
+ "refutability_closure": {"type": ["object", "null"]},
+ "evidence_bindings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["prerequisites", "rungs"],
+ "properties": {
+ "prerequisites": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["1", "2", "3"],
+ "properties": {
+ "1": {"$ref": "#/$defs/evidenceBinding"},
+ "2": {"$ref": "#/$defs/evidenceBinding"},
+ "3": {"$ref": "#/$defs/evidenceBinding"}
+ }
+ },
+ "rungs": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["4.1", "4.2", "4.3", "4.4", "4.5", "4.6", "4.7", "4.8", "4.9", "4.10", "4.11", "4.12", "4.13", "4.14"],
+ "properties": {
+ "4.1": {"$ref": "#/$defs/evidenceBinding"}, "4.2": {"$ref": "#/$defs/evidenceBinding"},
+ "4.3": {"$ref": "#/$defs/evidenceBinding"}, "4.4": {"$ref": "#/$defs/evidenceBinding"},
+ "4.5": {"$ref": "#/$defs/evidenceBinding"}, "4.6": {"$ref": "#/$defs/evidenceBinding"},
+ "4.7": {"$ref": "#/$defs/evidenceBinding"}, "4.8": {"$ref": "#/$defs/evidenceBinding"},
+ "4.9": {"$ref": "#/$defs/evidenceBinding"}, "4.10": {"$ref": "#/$defs/evidenceBinding"},
+ "4.11": {"$ref": "#/$defs/evidenceBinding"}, "4.12": {"$ref": "#/$defs/evidenceBinding"},
+ "4.13": {"$ref": "#/$defs/evidenceBinding"}, "4.14": {"$ref": "#/$defs/evidenceBinding"}
+ }
+ }
+ }
+ },
+ "evidence_payloads": {
+ "type": "object",
+ "propertyNames": {"pattern": "^sha256:[0-9a-f]{64}$"},
+ "additionalProperties": {"type": "string", "contentEncoding": "base64"}
+ }
},
"allOf": [
{
"if": {"properties": {"vstd4_depth": {"const": 14}}},
"then": {"properties": {"witness": {"type": "object"}, "ceiling_refutation": {"type": "null"}}},
"else": {"properties": {"ceiling_refutation": {"type": "object"}}}
+ },
+ {
+ "if": {"required": ["conformance_status"], "properties": {"conformance_status": {"const": "ESTABLISHED"}}},
+ "then": {
+ "required": ["depth_kind", "kernel_outcome", "evidence_bindings", "evidence_payloads"],
+ "properties": {
+ "vstd4_depth": {"const": 14},
+ "depth_kind": {"const": "EVIDENCE_BOUND"},
+ "kernel_outcome": {"const": "ACCEPTED"},
+ "blocking_rungs": {"maxItems": 0},
+ "witness": {"type": "object"},
+ "ceiling_refutation": {"type": "null"}
+ }
+ }
}
],
"$defs": {
"digest": {"type": "string", "pattern": "^(?:sha256:)?[0-9a-f]{64}$"},
- "evidenceRef": {"type": "string", "minLength": 1},
+ "evidenceRef": {"type": "string", "minLength": 1, "description": "Caller-supplied reference. Schema validity does not establish retrieval, content binding, the rung proposition, or prerequisite-profile conformance."},
+ "evidenceBinding": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["subject_id", "predicate", "expected", "mechanism_id", "mechanism_digest", "evidence_refs", "trust_roots", "bounds", "parameters"],
+ "properties": {
+ "subject_id": {"type": "string", "minLength": 1},
+ "predicate": {"type": "string", "minLength": 1},
+ "expected": {},
+ "mechanism_id": {"type": "string", "minLength": 1},
+ "mechanism_digest": {"type": "string", "pattern": "^sha256:[0-9a-f]{64}$"},
+ "evidence_refs": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "pattern": "^sha256:[0-9a-f]{64}$"}},
+ "trust_roots": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
+ "bounds": {
+ "type": "object", "additionalProperties": false,
+ "required": ["max_evidence_items", "max_evidence_bytes"],
+ "properties": {"max_evidence_items": {"type": "integer", "minimum": 0}, "max_evidence_bytes": {"type": "integer", "minimum": 0}}
+ },
+ "parameters": {"type": "object", "additionalProperties": {"type": "string"}}
+ }
+ },
"coordinate": {
"type": "object", "additionalProperties": false,
"required": ["subject", "predicate", "parameters"],
diff --git a/receipts/schema/vstd5_receipt.json b/receipts/schema/vstd5_receipt.json
index dcbbcd6..3f2328b 100644
--- a/receipts/schema/vstd5_receipt.json
+++ b/receipts/schema/vstd5_receipt.json
@@ -1,64 +1,160 @@
{
+ "$comment": "Terminology: Verifier Standard (VSTD). Witness identity is a coordinate, not computational trust.",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://timelordraps.github.io/verifier/schemas/vstd5_receipt.json",
- "title": "VSTD-5 Witness Corroboration Receipt (DRAFT)",
- "$comment": "Draft interface only. A document matching this schema is not proof of independent corroboration.",
+ "title": "VSTD-5 Witness Corroboration Receipt",
+ "description": "Replayable evidence-bound witness record. Schema validity establishes shape only. The reference runtime must recheck the admitted evidence-bound VSTD-4 result, identity evidence availability, every independence dimension, every corroboration mechanism, embedded evidence bytes, and the derived result.",
"type": "object",
"additionalProperties": false,
- "required": ["schema_version", "status", "receipt_id", "claim_id", "claim_binding", "entry_vstd4_depth", "witnesses", "corroborations", "disagreements", "computed_independence"],
+ "required": ["schema_version", "receipt_id", "entry_vstd4", "bundle", "evidence_payloads", "result"],
"properties": {
- "schema_version": {"const": "VSTD-5-DRAFT"},
- "status": {"const": "DRAFT"},
+ "schema_version": {"const": "VSTD-5"},
"receipt_id": {"type": "string", "pattern": "^VFY-5-[A-Za-z0-9._:-]+$"},
- "claim_id": {"type": "string", "minLength": 1},
- "claim_binding": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
- "entry_vstd4_depth": {"const": 14},
- "witnesses": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/witness"}},
- "corroborations": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/corroboration"}},
- "disagreements": {"type": "array", "items": {"$ref": "#/$defs/disagreement"}},
- "computed_independence": {"enum": ["INDEPENDENT", "PARTIALLY_INDEPENDENT", "NOT_INDEPENDENT", "UNKNOWN", "CONFLICTED"]}
+ "entry_vstd4": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["result_digest", "depth", "conformance_status", "witness_digest"],
+ "properties": {
+ "result_digest": {"$ref": "#/$defs/digest"},
+ "depth": {"const": 14},
+ "conformance_status": {"const": "ESTABLISHED"},
+ "witness_digest": {"$ref": "#/$defs/digest"}
+ }
+ },
+ "bundle": {"$ref": "#/$defs/bundle"},
+ "evidence_payloads": {
+ "type": "object",
+ "propertyNames": {"pattern": "^sha256:[0-9a-f]{64}$"},
+ "additionalProperties": {"type": "string", "contentEncoding": "base64"}
+ },
+ "result": {"$ref": "#/$defs/result"}
},
"$defs": {
- "digest": {"type": "string", "pattern": "^(?:sha256:)?[0-9a-f]{64}$"},
+ "digest": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
+ "digestRef": {"type": "string", "pattern": "^(?:sha256:)?[0-9a-f]{64}$"},
+ "bundle": {
+ "type": "object", "additionalProperties": false,
+ "required": ["claim_id", "declarant_id", "claim_binding_digest", "witnesses", "independence_assertions", "corroborations"],
+ "properties": {
+ "claim_id": {"type": "string", "minLength": 1},
+ "declarant_id": {"type": "string", "minLength": 1},
+ "claim_binding_digest": {"$ref": "#/$defs/digest"},
+ "witnesses": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/witness"}},
+ "independence_assertions": {"type": "array", "items": {"$ref": "#/$defs/independenceAssertion"}},
+ "corroborations": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/corroboration"}}
+ }
+ },
"witness": {
"type": "object", "additionalProperties": false,
- "required": ["witness_id", "identity_evidence", "independence"],
+ "required": ["witness_id", "identity_evidence_ref"],
"properties": {
"witness_id": {"type": "string", "minLength": 1},
- "identity_evidence": {"$ref": "#/$defs/digest"},
- "independence": {
+ "identity_evidence_ref": {"$ref": "#/$defs/digestRef"}
+ }
+ },
+ "independenceAssertion": {
+ "type": "object", "additionalProperties": false,
+ "required": ["witness_id", "dimensions"],
+ "properties": {
+ "witness_id": {"type": "string", "minLength": 1},
+ "dimensions": {
"type": "object", "additionalProperties": false,
- "required": ["shared_control", "shared_code", "shared_trust_root", "shared_evidence_source", "shared_infrastructure", "financial_dependence", "jurisdictional_dependence", "evidence"],
+ "required": ["control", "verdict_code", "trust_root", "evidence_source", "infrastructure", "financial_dependence", "jurisdictional_dependence"],
"properties": {
- "shared_control": {"$ref": "#/$defs/triState"}, "shared_code": {"$ref": "#/$defs/triState"},
- "shared_trust_root": {"$ref": "#/$defs/triState"}, "shared_evidence_source": {"$ref": "#/$defs/triState"},
- "shared_infrastructure": {"$ref": "#/$defs/triState"}, "financial_dependence": {"$ref": "#/$defs/triState"},
- "jurisdictional_dependence": {"$ref": "#/$defs/triState"}, "evidence": {"type": "array", "items": {"$ref": "#/$defs/digest"}}
+ "control": {"$ref": "#/$defs/dimension"},
+ "verdict_code": {"$ref": "#/$defs/dimension"},
+ "trust_root": {"$ref": "#/$defs/dimension"},
+ "evidence_source": {"$ref": "#/$defs/dimension"},
+ "infrastructure": {"$ref": "#/$defs/dimension"},
+ "financial_dependence": {"$ref": "#/$defs/dimension"},
+ "jurisdictional_dependence": {"$ref": "#/$defs/dimension"}
}
}
}
},
- "triState": {"enum": ["YES", "NO", "UNKNOWN"]},
- "corroboration": {
+ "dimension": {
"type": "object", "additionalProperties": false,
- "required": ["corroboration_id", "witness_id", "class", "vstd4_certificate_digest", "checker_descriptor_digest", "observed_evidence", "result", "observed_at"],
+ "required": ["state", "binding"],
"properties": {
- "corroboration_id": {"type": "string", "minLength": 1}, "witness_id": {"type": "string", "minLength": 1},
- "class": {"enum": ["PROCUREMENT", "POWER_THERMAL_ENVELOPE", "NETWORK_EGRESS", "VENDOR_TELEMETRY", "FINANCIAL_ATTESTATION", "PHYSICAL_INSPECTION"]},
- "vstd4_certificate_digest": {"$ref": "#/$defs/digest"}, "checker_descriptor_digest": {"$ref": "#/$defs/digest"},
- "observed_evidence": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/digest"}},
- "result": {"enum": ["CORROBORATED", "REFUTED", "UNKNOWN"]}, "observed_at": {"type": "string", "format": "date-time"}
+ "state": {"enum": ["SHARED", "SEPARATE", "UNKNOWN"]},
+ "binding": {"anyOf": [{"$ref": "https://timelordraps.github.io/verifier/schemas/vstd4_receipt.json#/$defs/evidenceBinding"}, {"type": "null"}]}
}
},
- "disagreement": {
+ "corroboration": {
"type": "object", "additionalProperties": false,
- "required": ["disagreement_id", "corroboration_ids", "status_effect", "recorded_at"],
+ "required": ["corroboration_id", "witness_id", "claim_binding_digest", "vstd4_certificate_digest", "checker_descriptor_digest", "observed_evidence_refs", "result", "observed_at", "verification", "corroboration_class"],
"properties": {
- "disagreement_id": {"type": "string", "minLength": 1},
- "corroboration_ids": {"type": "array", "minItems": 2, "items": {"type": "string"}, "uniqueItems": true},
- "status_effect": {"enum": ["CHALLENGED", "REVOKED", "UNKNOWN", "CONFLICTED"]},
- "recorded_at": {"type": "string", "format": "date-time"}
+ "corroboration_id": {"type": "string", "minLength": 1},
+ "witness_id": {"type": "string", "minLength": 1},
+ "claim_binding_digest": {"$ref": "#/$defs/digest"},
+ "vstd4_certificate_digest": {"$ref": "#/$defs/digestRef"},
+ "checker_descriptor_digest": {"$ref": "#/$defs/digestRef"},
+ "observed_evidence_refs": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/digestRef"}},
+ "result": {"enum": ["CORROBORATED", "REFUTED", "UNKNOWN"]},
+ "observed_at": {"type": "string", "format": "date-time"},
+ "verification": {"$ref": "https://timelordraps.github.io/verifier/schemas/vstd4_receipt.json#/$defs/evidenceBinding"},
+ "corroboration_class": {"type": "string", "minLength": 1}
}
+ },
+ "evaluation": {"$ref": "https://timelordraps.github.io/verifier/schemas/vstd-graph-assurance-1.schema.json#/$defs/evaluation"},
+ "result": {
+ "type": "object", "additionalProperties": false,
+ "required": ["claim_id", "status", "conformance_status", "computed_independence", "independence_evaluations", "corroboration_evaluations", "disagreements", "binding_errors", "identity_errors", "separation_errors", "corroboration_errors", "errors", "limitations"],
+ "properties": {
+ "claim_id": {"type": "string", "minLength": 1},
+ "status": {"enum": ["CORROBORATED", "REFUTED", "UNKNOWN", "CONFLICTED"]},
+ "conformance_status": {"enum": ["ESTABLISHED", "NOT_ESTABLISHED"]},
+ "computed_independence": {"enum": ["INDEPENDENT", "UNKNOWN"]},
+ "independence_evaluations": {
+ "type": "array",
+ "items": {"type": "object", "additionalProperties": false, "required": ["witness_id", "dimension", "evaluation"], "properties": {"witness_id": {"type": "string"}, "dimension": {"type": "string"}, "evaluation": {"$ref": "#/$defs/evaluation"}}}
+ },
+ "corroboration_evaluations": {
+ "type": "array",
+ "items": {"type": "object", "additionalProperties": false, "required": ["corroboration_id", "evaluation"], "properties": {"corroboration_id": {"type": "string"}, "evaluation": {"$ref": "#/$defs/evaluation"}}}
+ },
+ "disagreements": {"type": "array", "items": {"type": "array", "minItems": 2, "uniqueItems": true, "items": {"type": "string"}}},
+ "binding_errors": {"type": "array", "items": {"type": "string"}},
+ "identity_errors": {"type": "array", "items": {"type": "string"}},
+ "separation_errors": {"type": "array", "items": {"type": "string"}},
+ "corroboration_errors": {"type": "array", "items": {"type": "string"}},
+ "errors": {"type": "array", "items": {"type": "string"}},
+ "limitations": {"type": "array", "items": {"type": "string"}}
+ },
+ "allOf": [
+ {
+ "if": {"properties": {"status": {"const": "CORROBORATED"}}, "required": ["status"]},
+ "then": {
+ "properties": {
+ "conformance_status": {"const": "ESTABLISHED"},
+ "computed_independence": {"const": "INDEPENDENT"}
+ }
+ }
+ },
+ {
+ "if": {"properties": {"computed_independence": {"const": "INDEPENDENT"}}, "required": ["computed_independence"]},
+ "then": {
+ "properties": {
+ "binding_errors": {"maxItems": 0},
+ "identity_errors": {"maxItems": 0},
+ "separation_errors": {"maxItems": 0}
+ }
+ }
+ },
+ {
+ "if": {"properties": {"conformance_status": {"const": "ESTABLISHED"}}, "required": ["conformance_status"]},
+ "then": {
+ "properties": {
+ "computed_independence": {"const": "INDEPENDENT"},
+ "binding_errors": {"maxItems": 0},
+ "identity_errors": {"maxItems": 0},
+ "separation_errors": {"maxItems": 0},
+ "corroboration_errors": {"maxItems": 0},
+ "errors": {"maxItems": 0}
+ }
+ }
+ }
+ ]
}
}
}
diff --git a/receipts/schema/vstd_graph_receipt.json b/receipts/schema/vstd_graph_receipt.json
index dad3e63..315c194 100644
--- a/receipts/schema/vstd_graph_receipt.json
+++ b/receipts/schema/vstd_graph_receipt.json
@@ -1,8 +1,9 @@
{
+ "$comment": "Terminology: Verifier Standard (VSTD).",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://timelordraps.github.io/verifier/schemas/vstd_graph_receipt.json",
"title": "VSTD-Graph Provenance Hypergraph Receipt",
- "description": "VSTD-Graph receipt. The VSTD-DATA-0.1 wire identifier is frozen for historical Graph-1 receipts; computed_graph_level records the independently computed 1-5 profile when present.",
+ "description": "VSTD-Graph receipt. The VSTD-DATA-0.1 serialized receipt identifier is frozen for historical Graph-1 receipts; computed_graph_level and its level field are compatibility names for a candidate Graph profile number over caller-supplied ratings and do not establish conformance. Historical blocks without rating_basis or conformance_status have the same unestablished interpretation.",
"type": "object",
"required": [
"schema_version",
@@ -43,10 +44,47 @@
"type": "object",
"required": ["artifacts", "transformations", "contributors", "rights"],
"properties": {
- "artifacts": { "type": "array" },
+ "artifacts": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["artifact_id", "status"],
+ "properties": {
+ "artifact_id": { "type": "string" },
+ "status": {
+ "enum": ["VALID", "CHALLENGED", "STALE", "SUPERSEDED", "REVOKED", "UNKNOWN"]
+ }
+ }
+ }
+ },
"transformations": { "type": "array" },
"contributors": { "type": "array" },
- "rights": { "type": "array" }
+ "rights": { "type": "array" },
+ "conflicts": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["conflict_id", "subject_id", "predicate", "competing_values", "evidence_refs"],
+ "properties": {
+ "conflict_id": { "type": "string", "minLength": 1 },
+ "subject_id": { "type": "string", "minLength": 1 },
+ "predicate": { "type": "string", "minLength": 1 },
+ "competing_values": {
+ "type": "array",
+ "minItems": 2,
+ "uniqueItems": true,
+ "items": { "type": "string" }
+ },
+ "evidence_refs": {
+ "type": "array",
+ "minItems": 2,
+ "uniqueItems": true,
+ "items": { "type": "string" }
+ }
+ }
+ }
+ }
}
},
"completeness_metrics": {
@@ -75,7 +113,41 @@
},
"independent_audit": {
"type": "object",
- "required": ["overall_verdict", "acyclic_hypergraph", "integrity_passed", "trusted_computing_base"]
+ "required": ["overall_verdict", "acyclic_hypergraph", "integrity_passed", "trusted_computing_base"],
+ "properties": {
+ "independence_basis": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["independently_verified", "actor_independence", "implementation_separation", "runtime_separation", "evidence"],
+ "properties": {
+ "independently_verified": {
+ "type": "boolean",
+ "description": "A conformance claim, not a structural inference. VSTD 1.2.0's bundled runtime accepts only false because it has no actor/execution evidence-binding validator."
+ },
+ "actor_independence": { "enum": ["EVIDENCED", "DECLARED", "NOT_DEMONSTRATED", "CONFLICTED"] },
+ "implementation_separation": { "enum": ["EVIDENCED", "DECLARED", "NOT_DEMONSTRATED", "CONFLICTED"] },
+ "runtime_separation": { "enum": ["EVIDENCED", "DECLARED", "NOT_DEMONSTRATED", "CONFLICTED"] },
+ "evidence": {
+ "type": "array",
+ "description": "References are declarations until an implemented profile resolves and validates their execution bindings.",
+ "items": { "type": "string", "minLength": 1 }
+ }
+ },
+ "allOf": [
+ {
+ "if": { "properties": { "independently_verified": { "const": true } } },
+ "then": {
+ "properties": {
+ "actor_independence": { "const": "EVIDENCED" },
+ "implementation_separation": { "const": "EVIDENCED" },
+ "runtime_separation": { "const": "EVIDENCED" },
+ "evidence": { "minItems": 1 }
+ }
+ }
+ }
+ ]
+ }
+ }
},
"provenance": {
"type": "object",
@@ -93,11 +165,53 @@
"collection_id": {"type": "string", "minLength": 1},
"level": {"type": "integer", "minimum": 0, "maximum": 5},
"max_level": {"const": 5},
+ "rating_basis": {"enum": ["CALLER_SUPPLIED", "MECHANISM_EVALUATED"]},
+ "conformance_status": {"enum": ["NOT_ESTABLISHED", "ESTABLISHED"]},
"blocking_obligations": {"type": "array", "items": {"type": "object"}},
"witness_digest": {"type": ["string", "null"], "pattern": "^[0-9a-f]{64}$"},
"refutation_digest": {"type": ["string", "null"], "pattern": "^[0-9a-f]{64}$"},
- "explanation": {"type": "string"}
- }
+ "explanation": {"type": "string"},
+ "kernel_outcome": {"enum": ["ACCEPTED", "REJECTED", "REFUSED"]},
+ "members": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
+ "binding": {"$ref": "https://timelordraps.github.io/verifier/schemas/vstd4_receipt.json#/$defs/binding"},
+ "object_evaluations": {
+ "type": "object",
+ "additionalProperties": {"$ref": "https://timelordraps.github.io/verifier/schemas/vstd-graph-assurance-1.schema.json#/$defs/evaluation"}
+ },
+ "edge_evaluations": {
+ "type": "object",
+ "additionalProperties": {"$ref": "https://timelordraps.github.io/verifier/schemas/vstd-graph-assurance-1.schema.json#/$defs/evaluation"}
+ },
+ "binding_errors": {"type": "array", "items": {"type": "string"}},
+ "evidence_bindings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["objects", "edges"],
+ "properties": {
+ "objects": {"type": "object", "additionalProperties": {"$ref": "https://timelordraps.github.io/verifier/schemas/vstd4_receipt.json#/$defs/evidenceBinding"}},
+ "edges": {"type": "object", "additionalProperties": {"$ref": "https://timelordraps.github.io/verifier/schemas/vstd4_receipt.json#/$defs/evidenceBinding"}}
+ }
+ },
+ "evidence_payloads": {
+ "type": "object",
+ "propertyNames": {"pattern": "^sha256:[0-9a-f]{64}$"},
+ "additionalProperties": {"type": "string", "contentEncoding": "base64"}
+ }
+ },
+ "allOf": [
+ {
+ "if": {"required": ["conformance_status"], "properties": {"conformance_status": {"const": "ESTABLISHED"}}},
+ "then": {
+ "required": ["rating_basis", "kernel_outcome", "members", "binding", "object_evaluations", "edge_evaluations", "binding_errors", "evidence_bindings", "evidence_payloads"],
+ "properties": {
+ "rating_basis": {"const": "MECHANISM_EVALUATED"},
+ "level": {"minimum": 1},
+ "kernel_outcome": {"const": "ACCEPTED"},
+ "binding_errors": {"maxItems": 0}
+ }
+ }
+ }
+ ]
}
}
}
diff --git a/scripts/build_docs.py b/scripts/build_docs.py
new file mode 100644
index 0000000..2ea6d7b
--- /dev/null
+++ b/scripts/build_docs.py
@@ -0,0 +1,728 @@
+#!/usr/bin/env python3
+"""Terminology: Hypertext Markup Language (HTML); uniform resource locator (URL);
+Verifier Standard (VSTD).
+
+Render repository Markdown into the navigable GitHub Pages documentation site.
+
+The repository Markdown remains authoritative. This builder changes presentation and
+links only; it does not maintain a second hand-edited copy of any specification or guide.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+import html
+import os
+from pathlib import Path, PurePosixPath
+import re
+from typing import Iterable, Mapping
+from urllib.parse import quote
+
+
+ROOT = Path(__file__).resolve().parents[1]
+CANONICAL_BASE = "https://timelordraps.github.io/verifier/"
+SOURCE_REPOSITORY = "https://github.com/TimeLordRaps/verifier"
+
+
+@dataclass(frozen=True)
+class Document:
+ source: Path
+ route: PurePosixPath
+ group: str
+ title: str
+
+
+@dataclass(frozen=True)
+class OrientationDefinition:
+ concept: str
+ definition: str
+
+
+ORIENTATION_LINK = re.compile(
+ r'^\[([^]]+)\]\((https://en\.wikipedia\.org/wiki/[^)\s]+)\s+"Wikipedia orientation;[^"]+"\)$'
+)
+
+
+def _plain_markdown(value: str) -> str:
+ value = re.sub(r"\[([^]]+)\]\([^)]+\)", r"\1", value)
+ value = value.replace("`", "")
+ value = re.sub(r"\*\*([^*]+)\*\*", r"\1", value)
+ value = re.sub(r"~~([^~]+)~~", r"\1", value)
+ return re.sub(r"\s+", " ", value).strip()
+
+
+def orientation_definitions() -> dict[str, OrientationDefinition]:
+ """Read the versioned hover-card definitions from the concepts glossary."""
+
+ definitions: dict[str, OrientationDefinition] = {}
+ source = ROOT / "docs/CONCEPTS_AND_PRECEDENTS.md"
+ for line in source.read_text(encoding="utf-8").splitlines():
+ if not line.startswith("|"):
+ continue
+ cells = [cell.strip() for cell in line.strip().strip("|").split("|")]
+ if len(cells) != 3:
+ continue
+ match = ORIENTATION_LINK.fullmatch(cells[1])
+ if not match:
+ continue
+ definitions.setdefault(
+ match.group(2),
+ OrientationDefinition(
+ concept=_plain_markdown(cells[0]),
+ definition=_plain_markdown(cells[2]),
+ ),
+ )
+
+ # Two VSTD concepts deliberately use the same adjacent precedent. The popup
+ # defines that shared precedent neutrally; each table row retains its own bound.
+ definitions["https://en.wikipedia.org/wiki/Proof-carrying_code"] = (
+ OrientationDefinition(
+ concept="Proof-carrying code",
+ definition=(
+ "An untrusted producer supplies a result with a consumer-checkable "
+ "certificate under a declared policy. VSTD treats this as an adjacent "
+ "engineering precedent, not an inherited safety theorem."
+ ),
+ )
+ )
+ if not definitions:
+ raise ValueError("orientation glossary contains no repository definitions")
+ return definitions
+
+
+def _first_heading(path: Path) -> str:
+ for line in path.read_text(encoding="utf-8").splitlines():
+ match = re.match(r"^#\s+(.+?)\s*$", line)
+ if match:
+ return re.sub(r"[`*_]", "", match.group(1)).strip()
+ return path.stem.replace("_", " ").replace("-", " ")
+
+
+def _standard_sort(path: Path) -> tuple[int, int, str]:
+ name = path.stem
+ if name == "LADDER":
+ return (0, 0, name)
+ object_match = re.fullmatch(r"VSTD-(\d+)", name)
+ if object_match:
+ return (1, int(object_match.group(1)), name)
+ graph_match = re.fullmatch(r"VSTD-Graph-(\d+)", name)
+ if graph_match:
+ return (2, int(graph_match.group(1)), name)
+ if name == "ARTIFACT_CONTROL":
+ return (3, 0, name)
+ if name == "WIRE_IDENTIFIERS":
+ return (4, 0, name)
+ return (5, 0, name)
+
+
+def documents() -> tuple[Document, ...]:
+ """Return every Markdown source that is intentionally rendered on the site."""
+
+ found: list[Document] = []
+ standards = sorted((ROOT / "standard").glob("*.md"), key=_standard_sort)
+ for source in standards:
+ route = (
+ PurePosixPath("standard/index.html")
+ if source.name == "LADDER.md"
+ else PurePosixPath("standard") / f"{source.stem}.html"
+ )
+ found.append(Document(source, route, "Normative specifications", _first_heading(source)))
+
+ guides = sorted((ROOT / "docs").rglob("*.md"))
+ for source in guides:
+ relative = source.relative_to(ROOT).with_suffix(".html")
+ group = "Guides and concepts"
+ if "profiles" in relative.parts or "standards" in relative.parts:
+ group = "Profiles and interoperability"
+ found.append(
+ Document(
+ source,
+ PurePosixPath(relative.as_posix()),
+ group,
+ _first_heading(source),
+ )
+ )
+
+ experiments = sorted((ROOT / "experiments").rglob("*.md"))
+ for source in experiments:
+ relative = source.relative_to(ROOT).with_suffix(".html")
+ if source.name == "INDEX.md":
+ relative = Path("experiments/index.html")
+ found.append(
+ Document(
+ source,
+ PurePosixPath(relative.as_posix()),
+ "Experiments",
+ _first_heading(source),
+ )
+ )
+
+ project_names = (
+ "README.md",
+ "ROADMAP.md",
+ "GOVERNANCE.md",
+ "CONTRIBUTING.md",
+ "SECURITY.md",
+ "RELEASING.md",
+ "CODE_OF_CONDUCT.md",
+ "CHANGELOG.md",
+ "HUMANS.md",
+ "AGENTS.md",
+ "TIME.md",
+ )
+ for name in project_names:
+ source = ROOT / name
+ if source.is_file():
+ found.append(
+ Document(
+ source,
+ PurePosixPath("project") / f"{source.stem}.html",
+ "Project and contribution",
+ _first_heading(source),
+ )
+ )
+ return tuple(found)
+
+
+def _slug(value: str) -> str:
+ plain = re.sub(r"<[^>]*>", "", value)
+ plain = re.sub(r"[`*_~]", "", plain).lower()
+ plain = re.sub(r"[^a-z0-9\s-]", "", plain)
+ return re.sub(r"[-\s]+", "-", plain).strip("-") or "section"
+
+
+def _relative_link(source_route: PurePosixPath, target_route: PurePosixPath) -> str:
+ return PurePosixPath(
+ os.path.relpath(target_route.as_posix(), source_route.parent.as_posix()).replace("\\", "/")
+ ).as_posix()
+
+
+class MarkdownRenderer:
+ """Small deterministic renderer for the Markdown constructs used in this repository."""
+
+ FENCE = re.compile(r"^\s*(```+|~~~+)\s*([^\s`]*)\s*$")
+ HEADING = re.compile(r"^(#{1,6})\s+(.+?)\s*#*\s*$")
+ LIST_ITEM = re.compile(r"^(\s*)([-+*]|\d+[.)])\s+(.+)$")
+ TABLE_RULE = re.compile(r"^\s*\|?\s*:?-{3,}:?\s*(?:\|\s*:?-{3,}:?\s*)+\|?\s*$")
+ HORIZONTAL_RULE = re.compile(r"^\s*(?:-{3,}|\*\s*\*\s*\*|_{3,})\s*$")
+ INLINE_CODE = re.compile(r"`([^`]+)`")
+ INLINE_MATH = re.compile(r"(? None:
+ self.source = source.resolve()
+ self.route = route
+ self.route_map = route_map
+ self.source_ref = "main" if source_ref == "WORKTREE" else source_ref
+ self.orientation_map = orientation_map
+ self.heading_counts: dict[str, int] = {}
+ self.outline: list[tuple[int, str, str]] = []
+
+ def _repository_link(self, relative: Path, *, directory: bool) -> str:
+ operation = "tree" if directory else "blob"
+ ref = quote(self.source_ref, safe="")
+ encoded = "/".join(quote(part) for part in relative.parts)
+ return f"{SOURCE_REPOSITORY}/{operation}/{ref}/{encoded}"
+
+ def _target(self, raw: str, *, image: bool = False) -> str:
+ repository_relative = False
+ repository_prefix = f"{SOURCE_REPOSITORY}/blob/main/"
+ if raw.startswith(repository_prefix):
+ raw = raw.removeprefix(repository_prefix)
+ repository_relative = True
+ if raw.startswith(("#", "http://", "https://", "mailto:", "data:")):
+ return raw
+ target_text, separator, fragment = raw.partition("#")
+ base = ROOT if repository_relative else self.source.parent
+ resolved = (base / target_text).resolve()
+ if resolved in self.route_map:
+ rewritten = _relative_link(self.route, self.route_map[resolved])
+ else:
+ try:
+ relative = resolved.relative_to(ROOT)
+ except ValueError:
+ return raw
+ parts = relative.parts
+ if parts == ("docs", "reference.html"):
+ rewritten = _relative_link(self.route, PurePosixPath("reference.html"))
+ elif parts[:2] == ("docs", "assets"):
+ asset_route = PurePosixPath(*parts[1:])
+ rewritten = _relative_link(self.route, asset_route)
+ elif (
+ parts[:2] in {("receipts", "schema"), ("standard", "schemas")}
+ and resolved.is_file()
+ ):
+ rewritten = _relative_link(
+ self.route, PurePosixPath("schemas") / relative.name
+ )
+ else:
+ rewritten = self._repository_link(relative, directory=resolved.is_dir())
+ if separator and fragment:
+ rewritten += "#" + quote(fragment, safe="-._~")
+ return rewritten
+
+ def inline(self, value: str) -> str:
+ tokens: list[str] = []
+
+ def token(rendered: str) -> str:
+ marker = f"\x00{len(tokens)}\x00"
+ tokens.append(rendered)
+ return marker
+
+ def render_link(match: re.Match[str]) -> str:
+ target = self._target(match.group(2))
+ title = match.group(3)
+ orientation = self.orientation_map.get(match.group(2))
+ attributes = [f'href="{html.escape(target, quote=True)}"']
+ if orientation and title and title.startswith("Wikipedia orientation"):
+ attributes.extend(
+ (
+ 'class="orientation-link"',
+ 'data-orientation-preview="repository"',
+ f'data-orientation-concept="{html.escape(orientation.concept, quote=True)}"',
+ f'data-orientation-definition="{html.escape(orientation.definition, quote=True)}"',
+ f'data-orientation-boundary="{html.escape(title, quote=True)}"',
+ 'rel="noreferrer"',
+ )
+ )
+ elif title:
+ attributes.append(f'title="{html.escape(title, quote=True)}"')
+ return f'{self.inline(match.group(1))} '
+
+ value = self.INLINE_CODE.sub(
+ lambda match: token(f"{html.escape(match.group(1))}"), value
+ )
+ value = self.INLINE_MATH.sub(
+ lambda match: token(
+ f''
+ f"{html.escape(match.group(1))} "
+ ),
+ value,
+ )
+ value = self.IMAGE.sub(
+ lambda match: token(
+ f' '
+ ),
+ value,
+ )
+ value = self.LINK.sub(
+ lambda match: token(render_link(match)),
+ value,
+ )
+ value = html.escape(value, quote=False)
+ value = re.sub(r"\*\*([^*]+)\*\*", r"\1 ", value)
+ value = re.sub(r"__([^_]+)__", r"\1 ", value)
+ value = re.sub(r"(?\1", value)
+ value = re.sub(r"(?\1", value)
+ value = re.sub(r"~~([^~]+)~~", r"\1", value)
+ value = re.sub(
+ r"<(https?://[^&]+)>",
+ lambda match: f'{match.group(1)} ',
+ value,
+ )
+ for index in reversed(range(len(tokens))):
+ value = value.replace(f"\x00{index}\x00", tokens[index])
+ return value
+
+ @staticmethod
+ def _table_cells(line: str) -> list[str]:
+ stripped = line.strip().strip("|")
+ cells = re.split(r"(? tuple[str, int]:
+ headings = self._table_cells(lines[index])
+ index += 2
+ rows: list[list[str]] = []
+ while index < len(lines) and "|" in lines[index] and lines[index].strip():
+ rows.append(self._table_cells(lines[index]))
+ index += 1
+ head = "".join(f"{self.inline(cell)} " for cell in headings)
+ body = ""
+ for row in rows:
+ padded = row + [""] * max(0, len(headings) - len(row))
+ body += "" + "".join(
+ f"{self.inline(cell)} " for cell in padded[: len(headings)]
+ ) + " \n"
+ return (
+ ''
+ + head
+ + " \n"
+ + body
+ + "
",
+ index,
+ )
+
+ def _render_list(self, lines: list[str], index: int) -> tuple[str, int]:
+ first = self.LIST_ITEM.match(lines[index])
+ assert first is not None
+ base_indent = len(first.group(1).replace("\t", " "))
+ ordered = first.group(2)[0].isdigit()
+ tag = "ol" if ordered else "ul"
+ start = int(first.group(2)[:-1]) if ordered else 1
+ start_attribute = f' start="{start}"' if ordered and start != 1 else ""
+ items: list[str] = []
+ while index < len(lines):
+ match = self.LIST_ITEM.match(lines[index])
+ if match is None:
+ break
+ indent = len(match.group(1).replace("\t", " "))
+ is_ordered = match.group(2)[0].isdigit()
+ if indent != base_indent or is_ordered != ordered:
+ break
+ parts = [match.group(3).strip()]
+ nested: list[str] = []
+ index += 1
+ while index < len(lines):
+ next_match = self.LIST_ITEM.match(lines[index])
+ if next_match:
+ next_indent = len(next_match.group(1).replace("\t", " "))
+ next_ordered = next_match.group(2)[0].isdigit()
+ if next_indent == base_indent and next_ordered == ordered:
+ break
+ if next_indent <= base_indent:
+ break
+ child, index = self._render_list(lines, index)
+ nested.append(child)
+ continue
+ if lines[index].strip() and (
+ len(lines[index]) - len(lines[index].lstrip()) > base_indent
+ ):
+ parts.append(lines[index].strip())
+ index += 1
+ continue
+ break
+ items.append(self.inline(" ".join(parts)) + "".join(nested))
+ return (
+ f"<{tag}{start_attribute}>"
+ + "".join(f"{item} " for item in items)
+ + f"{tag}>",
+ index,
+ )
+
+ def render(self, markdown: str) -> str:
+ lines = markdown.replace("\r\n", "\n").replace("\r", "\n").split("\n")
+ output: list[str] = []
+ paragraph: list[str] = []
+
+ def flush_paragraph() -> None:
+ if paragraph:
+ joined = " ".join(part.strip() for part in paragraph)
+ output.append(f"{self.inline(joined)}
")
+ paragraph.clear()
+
+ index = 0
+ while index < len(lines):
+ line = lines[index]
+ display_math = re.match(r"^\s*\$\$(.+)\$\$\s*$", line)
+ if display_math:
+ flush_paragraph()
+ output.append(
+ ''
+ + html.escape(display_math.group(1).strip())
+ + "
"
+ )
+ index += 1
+ continue
+ fence = self.FENCE.match(line)
+ if fence:
+ flush_paragraph()
+ marker, language = fence.groups()
+ index += 1
+ code: list[str] = []
+ while index < len(lines) and not lines[index].lstrip().startswith(marker[:3]):
+ code.append(lines[index])
+ index += 1
+ if index < len(lines):
+ index += 1
+ language_class = (
+ f' class="language-{html.escape(language, quote=True)}"' if language else ""
+ )
+ output.append(
+ f"{html.escape(chr(10).join(code))} "
+ )
+ continue
+ heading = self.HEADING.match(line)
+ if heading:
+ flush_paragraph()
+ level = len(heading.group(1))
+ text = heading.group(2)
+ anchor = _slug(text)
+ count = self.heading_counts.get(anchor, 0)
+ self.heading_counts[anchor] = count + 1
+ if count:
+ anchor = f"{anchor}-{count}"
+ output.append(
+ f'{self.inline(text)}'
+ f'# '
+ f" "
+ )
+ self.outline.append((level, re.sub(r"[`*_~]", "", text), anchor))
+ index += 1
+ continue
+ if (
+ index + 1 < len(lines)
+ and "|" in line
+ and self.TABLE_RULE.match(lines[index + 1])
+ ):
+ flush_paragraph()
+ table, index = self._render_table(lines, index)
+ output.append(table)
+ continue
+ if self.LIST_ITEM.match(line):
+ flush_paragraph()
+ rendered_list, index = self._render_list(lines, index)
+ output.append(rendered_list)
+ continue
+ if line.lstrip().startswith(">"):
+ flush_paragraph()
+ quote_lines: list[str] = []
+ while index < len(lines) and lines[index].lstrip().startswith(">"):
+ quote_lines.append(re.sub(r"^\s*>\s?", "", lines[index]))
+ index += 1
+ quoted = MarkdownRenderer(
+ self.source,
+ self.route,
+ self.route_map,
+ self.source_ref,
+ self.orientation_map,
+ ).render("\n".join(quote_lines))
+ output.append(f"{quoted} ")
+ continue
+ if self.HORIZONTAL_RULE.match(line):
+ flush_paragraph()
+ output.append(" ")
+ index += 1
+ continue
+ if line.startswith(" "):
+ flush_paragraph()
+ code = []
+ while index < len(lines) and (lines[index].startswith(" ") or not lines[index]):
+ code.append(lines[index][4:] if lines[index].startswith(" ") else "")
+ index += 1
+ output.append(f"{html.escape(chr(10).join(code).rstrip())} ")
+ continue
+ if re.match(r"^\s*
'
+ )
+ index += 1
+ continue
+ if re.match(r"^\s*?div(?:\s[^>]*)?>\s*$", line, re.IGNORECASE):
+ flush_paragraph()
+ index += 1
+ continue
+ if not line.strip():
+ flush_paragraph()
+ index += 1
+ continue
+ paragraph.append(line)
+ index += 1
+ flush_paragraph()
+ return "\n".join(output)
+
+
+def _canonical(route: PurePosixPath) -> str:
+ value = route.as_posix()
+ if value.endswith("/index.html"):
+ value = value[: -len("index.html")]
+ return CANONICAL_BASE + value
+
+
+def _top_navigation(route: PurePosixPath, *, current: str) -> str:
+ prefix = _relative_link(route, PurePosixPath("index.html"))
+ root = prefix.removesuffix("index.html")
+ links = (
+ ("Overview", root + "index.html", "overview"),
+ ("Guides", root + "guides.html", "guides"),
+ ("Reference", root + "reference.html", "reference"),
+ ("Demo", "https://github.com/TimeLordRaps/verifier#30-60-second-demonstration", "demo"),
+ ("Standard", root + "standard/", "standard"),
+ ("Experiments", root + "experiments/", "experiments"),
+ ("Project", root + "project/ROADMAP.html", "project"),
+ ("GitHub", "https://github.com/TimeLordRaps/verifier", "github"),
+ )
+ rendered = []
+ for label, target, key in links:
+ marker = ' aria-current="page"' if key == current else ""
+ rendered.append(f'{label} ')
+ return (
+ ''
+ f'VSTD '
+ f'{"".join(rendered)}
'
+ )
+
+
+def _sidebar(
+ current: Document,
+ all_documents: Iterable[Document],
+ outline: Iterable[tuple[int, str, str]],
+) -> str:
+ groups: dict[str, list[Document]] = {}
+ for document in all_documents:
+ groups.setdefault(document.group, []).append(document)
+ sections: list[str] = []
+ on_this_page = [entry for entry in outline if entry[0] in {2, 3}]
+ if on_this_page:
+ links = "".join(
+ f'{html.escape(title)} '
+ for level, title, anchor in on_this_page
+ )
+ sections.append(f'')
+ for group, entries in groups.items():
+ links = []
+ for document in entries:
+ target = _relative_link(current.route, document.route)
+ marker = ' aria-current="page"' if document.route == current.route else ""
+ links.append(
+ f'{html.escape(document.title)} '
+ )
+ sections.append(
+ f''
+ )
+ return (
+ '"
+ )
+
+
+def render_document(
+ document: Document,
+ all_documents: tuple[Document, ...],
+ *,
+ source_ref: str,
+ orientation_map: Mapping[str, OrientationDefinition],
+) -> str:
+ route_map = {item.source.resolve(): item.route for item in all_documents}
+ markdown = document.source.read_text(encoding="utf-8")
+ renderer = MarkdownRenderer(
+ document.source, document.route, route_map, source_ref, orientation_map
+ )
+ content = renderer.render(markdown)
+ source_relative = document.source.relative_to(ROOT).as_posix()
+ source_link = renderer._repository_link(Path(source_relative), directory=False)
+ if document.group == "Normative specifications":
+ current = "standard"
+ elif document.group == "Experiments":
+ current = "experiments"
+ elif document.group == "Project and contribution":
+ current = "project"
+ else:
+ current = "guides"
+ if document.group == "Normative specifications":
+ status = "Normative source"
+ elif document.group == "Experiments":
+ status = "Non-normative experiment record"
+ else:
+ status = "Maintained repository documentation"
+ orientation_script = ""
+ if 'data-orientation-preview="repository"' in content:
+ script_source = _relative_link(
+ document.route, PurePosixPath("assets/orientation-previews.js")
+ )
+ orientation_script = f''
+ return f"""
+
+
+
+
+
+
+
+
+ {html.escape(document.title)} — VSTD documentation
+
+
+
+
+ Skip to documentation
+ {_top_navigation(document.route, current=current)}
+
+ {_sidebar(document, all_documents, renderer.outline)}
+
+
+ Rendered from {html.escape(source_relative)} at build time without changing its status. The repository source controls if this presentation differs.
+ {content}
+
+
+
+ {orientation_script}
+
+
+"""
+
+
+def build(output: Path, *, source_ref: str = "WORKTREE") -> tuple[Path, ...]:
+ """Render the documentation into an existing Pages output directory."""
+
+ output = output.resolve()
+ if not output.is_dir():
+ raise ValueError(f"documentation output directory does not exist: {output}")
+ all_documents = documents()
+ orientation_map = orientation_definitions()
+ used_orientation_urls = {
+ match.group(2)
+ for document in all_documents
+ for line in document.source.read_text(encoding="utf-8").splitlines()
+ for match in MarkdownRenderer.LINK.finditer(line)
+ if match.group(2).startswith("https://en.wikipedia.org/wiki/")
+ and (match.group(3) or "").startswith("Wikipedia orientation")
+ }
+ missing = sorted(used_orientation_urls - orientation_map.keys())
+ if missing:
+ raise ValueError(
+ "orientation links lack repository definitions: " + ", ".join(missing)
+ )
+ written: list[Path] = []
+ targets = [output / Path(document.route.as_posix()) for document in all_documents]
+ existing = [target for target in targets if target.exists()]
+ if existing:
+ names = ", ".join(target.relative_to(output).as_posix() for target in existing[:3])
+ raise ValueError(f"refusing to overwrite generated documentation: {names}")
+ for document, target in zip(all_documents, targets):
+ target.parent.mkdir(parents=True, exist_ok=True)
+ target.write_text(
+ render_document(
+ document,
+ all_documents,
+ source_ref=source_ref,
+ orientation_map=orientation_map,
+ ),
+ encoding="utf-8",
+ newline="\n",
+ )
+ written.append(target)
+ return tuple(written)
+
+
+def main() -> int:
+ import argparse
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--output", type=Path, required=True)
+ parser.add_argument("--source-ref", default="WORKTREE")
+ args = parser.parse_args()
+ written = build(args.output, source_ref=args.source_ref)
+ print(f"[DOCS OK] rendered {len(written)} navigable pages")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/build_experiment_index.py b/scripts/build_experiment_index.py
new file mode 100644
index 0000000..88b2fc0
--- /dev/null
+++ b/scripts/build_experiment_index.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+"""Terminology: Verifier Standard (VSTD).
+
+Validate experimental manifests and build their deterministic public index."""
+
+from __future__ import annotations
+
+import argparse
+from pathlib import Path
+import sys
+
+ROOT = Path(__file__).resolve().parents[1]
+SOURCE_ROOT = ROOT / "src"
+if str(SOURCE_ROOT) not in sys.path:
+ sys.path.insert(0, str(SOURCE_ROOT))
+
+from verifier.experimental_workflow import load_manifest, verify_repo_artifacts
+
+
+EXPERIMENTS = ROOT / "experiments"
+INDEX = EXPERIMENTS / "INDEX.md"
+
+
+def _cell(value: object) -> str:
+ return str(value).replace("|", "\\|").replace("\n", " ")
+
+
+def discover(root: Path = ROOT) -> tuple[tuple[Path, dict[str, object]], ...]:
+ """Load every intentional experiment manifest and verify bound repo artifacts."""
+
+ experiments = root / "experiments"
+ records: list[tuple[Path, dict[str, object]]] = []
+ for path in sorted(experiments.glob("**/experiment.json")):
+ payload = load_manifest(path)
+ verify_repo_artifacts(payload, root)
+ records.append((path.relative_to(root), payload))
+ if not records:
+ raise RuntimeError("no experiments/**/experiment.json manifests were found")
+ return tuple(records)
+
+
+def render(records: tuple[tuple[Path, dict[str, object]], ...]) -> str:
+ """Render a stable, human-readable view without granting experiment verdicts."""
+
+ lines = [
+ "# Experimental work index",
+ "",
+ "> **Acronym:** Verifier Standard (VSTD).",
+ "",
+ "> **Experimental and non-normative.** Inclusion means that a profile manifest",
+ "> is structurally valid and its `repo:` artifacts match their bound digests. It",
+ "> does not establish a hypothesis, verifier, publication, or VSTD verdict.",
+ "",
+ "Regenerate or check this file with:",
+ "",
+ "```bash",
+ "PYTHONPATH=src python scripts/build_experiment_index.py --check",
+ "```",
+ "",
+ "| Experiment | State | Question | Publication | Open horizons | Manifest |",
+ "|---|---|---|---|---:|---|",
+ ]
+ for relative, payload in records:
+ experiment = payload["experiment"]
+ publication = payload["publication"]
+ horizons = payload["horizons"]
+ digest = payload["manifest_digest"]
+ assert isinstance(experiment, dict)
+ assert isinstance(publication, dict)
+ assert isinstance(horizons, list)
+ assert isinstance(digest, str)
+ unresolved = sum(
+ 1
+ for horizon in horizons
+ if isinstance(horizon, dict)
+ and horizon.get("status") in {"UNKNOWN", "CONFLICTED", "BLOCKED"}
+ )
+ path_text = relative.as_posix()
+ link_text = relative.relative_to("experiments").as_posix()
+ lines.append(
+ "| {identifier} | {state} | {question} | {publication} | {horizons} | "
+ "[`{path}`]({link}) `{digest}` |".format(
+ identifier=_cell(experiment["id"]),
+ state=_cell(experiment["state"]),
+ question=_cell(experiment["question"]),
+ publication=_cell(publication["state"]),
+ horizons=unresolved,
+ path=path_text,
+ link=link_text,
+ digest=digest,
+ )
+ )
+ lines.extend(
+ [
+ "",
+ "Platform events, including successful workflows and merges, retain",
+ "`verification_effect = NONE` unless a separate native result is explicitly",
+ "mapped through a bound VSTD receipt.",
+ "",
+ ]
+ )
+ return "\n".join(lines)
+
+
+def main(argv: list[str] | None = None) -> int:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--check", action="store_true", help="Fail if INDEX.md is stale.")
+ args = parser.parse_args(argv)
+ expected = render(discover())
+ if args.check:
+ if not INDEX.is_file() or INDEX.read_text(encoding="utf-8") != expected:
+ print("[EXPERIMENT INDEX FAILED] experiments/INDEX.md is stale")
+ return 1
+ print("[EXPERIMENT INDEX OK] manifests and repository artifacts verified")
+ return 0
+ INDEX.write_text(expected, encoding="utf-8", newline="\n")
+ print(f"[EXPERIMENT INDEX WRITTEN] {INDEX.relative_to(ROOT).as_posix()}")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/build_pages.py b/scripts/build_pages.py
index eb8a241..4d45832 100644
--- a/scripts/build_pages.py
+++ b/scripts/build_pages.py
@@ -1,25 +1,69 @@
#!/usr/bin/env python3
-"""Assemble the exact GitHub Pages artifact without duplicating schema sources."""
+"""Terminology: uniform resource locator (URL).
+
+Assemble the exact GitHub Pages artifact without duplicating schema sources.
+"""
from __future__ import annotations
import argparse
+import importlib.util
import json
from pathlib import Path
+import re
import shutil
+import sys
ROOT = Path(__file__).resolve().parents[1]
DOCS = ROOT / "docs"
-SCHEMAS = ROOT / "receipts/schema"
+SCHEMA_SOURCES = (ROOT / "receipts/schema", ROOT / "standard/schemas")
PUBLIC_SCHEMA_PREFIX = "https://timelordraps.github.io/verifier/schemas/"
+CANONICAL_BASE_URL = "https://timelordraps.github.io/verifier/"
class PagesBuildError(RuntimeError):
pass
-def build(output: Path) -> tuple[Path, ...]:
+def _build_documentation(output: Path, *, source_ref: str) -> tuple[Path, ...]:
+ path = ROOT / "scripts/build_docs.py"
+ spec = importlib.util.spec_from_file_location("vstd_build_docs", path)
+ if spec is None or spec.loader is None:
+ raise PagesBuildError("cannot load scripts/build_docs.py")
+ module = importlib.util.module_from_spec(spec)
+ sys.modules[spec.name] = module
+ try:
+ spec.loader.exec_module(module)
+ return module.build(output, source_ref=source_ref)
+ except Exception as exc:
+ raise PagesBuildError(f"documentation rendering failed: {exc}") from exc
+ finally:
+ sys.modules.pop(spec.name, None)
+
+
+def _documentation_coordinate(source_ref: str) -> dict[str, str | int]:
+ project = (ROOT / "pyproject.toml").read_text(encoding="utf-8")
+ version_match = re.search(r'^version\s*=\s*"([^"]+)"\s*$', project, re.MULTILINE)
+ if version_match is None:
+ raise PagesBuildError("project version is not readable")
+ version = version_match.group(1)
+ changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
+ heading = re.search(rf"^## {re.escape(version)} - (.+)$", changelog, re.MULTILINE)
+ if heading is None:
+ raise PagesBuildError(f"changelog has no coordinate for version {version}")
+ release_state = "UNRELEASED_CANDIDATE" if heading.group(1) == "UNRELEASED" else "RELEASED"
+ return {
+ "schema_version": 1,
+ "documentation_version": version,
+ "release_state": release_state,
+ "source_ref": source_ref,
+ "canonical_base_url": CANONICAL_BASE_URL,
+ "normative_source": "standard/",
+ }
+
+
+def build(output: Path, *, source_ref: str = "WORKTREE") -> tuple[Path, ...]:
"""Build into a new or empty directory and return every copied schema path."""
output = output.resolve()
if output == ROOT:
@@ -33,7 +77,13 @@ def build(output: Path) -> tuple[Path, ...]:
schema_output = output / "schemas"
schema_output.mkdir()
copied: list[Path] = []
- for source in sorted(SCHEMAS.glob("*.json")):
+ sources = sorted(
+ (source for directory in SCHEMA_SOURCES for source in directory.glob("*.json")),
+ key=lambda path: path.name,
+ )
+ if len({source.name for source in sources}) != len(sources):
+ raise PagesBuildError("public schema source names must be unique")
+ for source in sources:
payload = json.loads(source.read_text(encoding="utf-8"))
schema_id = payload.get("$id", "")
expected_id = PUBLIC_SCHEMA_PREFIX + source.name
@@ -47,15 +97,26 @@ def build(output: Path) -> tuple[Path, ...]:
if not copied:
raise PagesBuildError("no public schemas were assembled")
+ coordinate = _documentation_coordinate(source_ref)
+ (output / "documentation-coordinate.json").write_text(
+ json.dumps(coordinate, indent=2, sort_keys=True) + "\n",
+ encoding="utf-8",
+ newline="\n",
+ )
+ _build_documentation(output, source_ref=source_ref)
return tuple(copied)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--output", type=Path, required=True)
+ parser.add_argument("--source-ref", default="WORKTREE")
args = parser.parse_args(argv)
- copied = build(args.output)
- print(f"[PAGES OK] site assembled with {len(copied)} schema routes")
+ copied = build(args.output, source_ref=args.source_ref)
+ print(
+ f"[PAGES OK] site assembled with {len(copied)} schema routes "
+ "and navigable documentation"
+ )
return 0
diff --git a/scripts/build_reference.py b/scripts/build_reference.py
new file mode 100644
index 0000000..57dac5f
--- /dev/null
+++ b/scripts/build_reference.py
@@ -0,0 +1,480 @@
+#!/usr/bin/env python3
+"""Terminology: application programming interface (API); command-line interface (CLI);
+hash-based message authentication code (HMAC); International Organization for Standardization (ISO);
+JavaScript Object Notation (JSON); Secure Hash Algorithm 256-bit (SHA-256);
+Verifier Standard (VSTD); YAML Ain't Markup Language (YAML).
+
+Generate the public CLI and top-level API reference page from the live implementation.
+
+Nothing on the generated page is hand-written prose about behaviour: every command,
+option, top-level export, signature, and listed pipeline edge is read out of the
+importable package at build time. `scripts/check_presentation.py` and
+`tests/test_presentation_surface.py` regenerate this file and fail closed when the
+committed page drifts from the code."""
+
+from __future__ import annotations
+
+import argparse
+import enum
+import html
+import importlib
+import inspect
+from pathlib import Path
+import sys
+
+
+ROOT = Path(__file__).resolve().parents[1]
+SOURCE_ROOT = ROOT / "src"
+if str(SOURCE_ROOT) not in sys.path:
+ sys.path.insert(0, str(SOURCE_ROOT))
+OUTPUT = ROOT / "docs/reference.html"
+SOURCE_BASE = "https://github.com/TimeLordRaps/verifier/blob/main/"
+
+# command -> the declared implementation stages it dispatches into. Every target is
+# imported during generation, so a rename or removal breaks the build rather than
+# silently publishing a stale pipeline map.
+PIPELINE: tuple[tuple[str, str, tuple[str, ...]], ...] = (
+ (
+ "vstd demo",
+ "Runs the four adversarial specimens in-process and reports whether each "
+ "defensive outcome matched its declared invariant.",
+ ("verifier.runtime.demo:run_demo", "verifier.runtime.demo:demo_report"),
+ ),
+ (
+ "vstd plan",
+ "Resolves a manifest's command and declared paths without executing anything.",
+ (
+ "verifier.core.run_planning:load_manifest",
+ "verifier.core.run_planning:describe_run_plan",
+ ),
+ ),
+ (
+ "vstd run",
+ "Executes a trusted manifest without sandboxing, captures the observed "
+ "execution, and writes a canonically digested receipt.",
+ (
+ "verifier.core.run_planning:load_manifest",
+ "verifier.core.run:capture_run",
+ "verifier.core.receipt:compute_canonical_digest",
+ ),
+ ),
+ (
+ "vstd validate",
+ "Dispatches on the receipt's serialized `schema_version` identifier and runs its implemented "
+ "checks. Generic-run validation enforces its required structure and stable "
+ "digest; other receipt kinds enforce their separately documented structure "
+ "and evidence rules.",
+ (
+ "verifier.core.run_validation:validate_run_receipt",
+ "verifier.data.receipt:validate_data_receipt",
+ "verifier.hardware.validation:validate_vstd3_receipt",
+ ),
+ ),
+ (
+ "vstd inspect",
+ "Prints the claim coordinate, digest, and verdict surface of a stored receipt.",
+ (
+ "verifier.core.run_inspection:inspect_run_receipt",
+ "verifier.hardware.receipt:load_vstd3_receipt",
+ ),
+ ),
+ (
+ "vstd reproduce",
+ "Replays only the mechanisms a stored receipt actually carries; physical "
+ "hardware execution is refused rather than simulated.",
+ (
+ "verifier.core.run_reproduction:reproduce_run_receipt",
+ "verifier.data.receipt:reproduce_data_receipt",
+ ),
+ ),
+ (
+ "vstd impact",
+ "Finds stored run receipts whose recorded ancestry reaches a revoked "
+ "provenance artifact.",
+ ("verifier.core.run_impact:find_run_receipts_impacted_by_revocation",),
+ ),
+ (
+ "vstd data",
+ "Traces, renders, or exports the provenance hypergraph carried by a "
+ "VSTD-Graph receipt.",
+ ("verifier.data.models:ProvenanceHypergraph",),
+ ),
+ (
+ "vstd artifact",
+ "Freezes exact regular-file bytes, adds or verifies finite self-closing "
+ "seals, and creates observable copy-on-write thaw descendants.",
+ (
+ "verifier.artifact_control:freeze_artifact",
+ "verifier.artifact_control:seal_artifact",
+ "verifier.artifact_control:verify_frozen_artifact",
+ "verifier.artifact_control:thaw_artifact",
+ ),
+ ),
+ (
+ "vstd experiment",
+ "Validates experimental workflow manifests or maps normalized GitHub snapshots "
+ "without granting a VSTD verdict.",
+ (
+ "verifier.runtime.experimental_workflow_cli:handle_experiment_command",
+ "verifier.experimental_workflow.profile:load_manifest",
+ "verifier.experimental_workflow.github:github_snapshot_to_events",
+ ),
+ ),
+ (
+ "vstd hardware / continuity / fleet / evidence / claims",
+ "Evaluates VSTD-3 substrate-accountability receipts, their continuity and "
+ "fleet evidence, and their declared claims.",
+ (
+ "verifier.runtime.hardware_cli:handle_vstd3_command",
+ "verifier.hardware.validation:validate_vstd3_receipt",
+ ),
+ ),
+)
+
+
+class ReferenceBuildError(RuntimeError):
+ pass
+
+
+def _resolve(target: str) -> tuple[object, str]:
+ module_name, _, attribute = target.partition(":")
+ try:
+ module = importlib.import_module(module_name)
+ except ImportError as exc:
+ raise ReferenceBuildError(f"pipeline target is not importable: {target}: {exc}") from exc
+ if not hasattr(module, attribute):
+ raise ReferenceBuildError(f"pipeline target no longer exists: {target}")
+ return getattr(module, attribute), module_name
+
+
+def _source_link(module_name: str) -> str:
+ relative = "src/" + module_name.replace(".", "/") + ".py"
+ if not (ROOT / relative).is_file():
+ package_relative = "src/" + module_name.replace(".", "/") + "/__init__.py"
+ if not (ROOT / package_relative).is_file():
+ raise ReferenceBuildError(f"cannot locate source file for {module_name}")
+ relative = package_relative
+ return SOURCE_BASE + relative
+
+
+def _summary(obj: object) -> str:
+ doc = inspect.getdoc(obj) or ""
+ return doc.split("\n\n", 1)[0].strip().replace("\n", " ")
+
+
+def _esc(text: str) -> str:
+ return html.escape(text, quote=False)
+
+
+def _subparser_actions(parser: argparse.ArgumentParser) -> list[argparse._SubParsersAction]:
+ return [
+ action
+ for action in parser._actions # noqa: SLF001 - argparse exposes no public walk
+ if isinstance(action, argparse._SubParsersAction) # noqa: SLF001
+ ]
+
+
+def _walk(parser: argparse.ArgumentParser, help_text: str = "") -> list[dict[str, object]]:
+ arguments: list[dict[str, str]] = []
+ for action in parser._actions: # noqa: SLF001
+ if isinstance(action, argparse._SubParsersAction) or action.dest == "help": # noqa: SLF001
+ continue
+ name = ", ".join(action.option_strings) if action.option_strings else (
+ action.metavar or action.dest
+ )
+ choices = ""
+ if action.choices:
+ choices = "one of: " + ", ".join(str(choice) for choice in action.choices)
+ arguments.append(
+ {
+ "name": str(name),
+ "kind": "optional" if action.option_strings else "positional",
+ "choices": choices,
+ "default": "" if action.default in (None, False, [], "") else str(action.default),
+ "help": action.help or "",
+ }
+ )
+ commands: list[dict[str, object]] = [
+ {"prog": parser.prog, "help": help_text, "arguments": arguments}
+ ]
+ for action in _subparser_actions(parser):
+ help_by_name = {
+ choice.dest: choice.help or "" for choice in action._choices_actions # noqa: SLF001
+ }
+ for name, subparser in action.choices.items():
+ commands.extend(_walk(subparser, help_by_name.get(name, "")))
+ return commands
+
+
+def _cli_section() -> str:
+ from verifier.runtime.public_cli import build_parser
+
+ blocks: list[str] = []
+ for command in _walk(build_parser()):
+ prog = str(command["prog"])
+ anchor = "cli-" + prog.replace(" ", "-")
+ rows = ""
+ for argument in command["arguments"]: # type: ignore[union-attr]
+ detail = " ".join(
+ part
+ for part in (
+ argument["help"],
+ f"({argument['choices']})" if argument["choices"] else "",
+ f"[default: {argument['default']}]" if argument["default"] else "",
+ )
+ if part
+ )
+ rows += (
+ f"{_esc(argument['name'])} "
+ f"{_esc(argument['kind'])} "
+ f"{_esc(detail)} \n"
+ )
+ table = (
+ "Argument Kind Meaning "
+ f"\n{rows}
"
+ if rows
+ else 'No arguments; this command only groups subcommands.
'
+ )
+ help_text = str(command["help"]) or "Subcommand group."
+ blocks.append(
+ f'\n'
+ f"{_esc(prog)} \n"
+ f'{_esc(help_text)}
\n'
+ f"{table}\n "
+ )
+ return "\n".join(blocks)
+
+
+def _api_section() -> str:
+ package = importlib.import_module("verifier")
+ blocks: list[str] = []
+ for name in sorted(package.__all__):
+ value = getattr(package, name)
+ module_name = value.__module__
+ if inspect.isclass(value):
+ kind = "enum" if issubclass(value, enum.Enum) else "class"
+ elif inspect.isfunction(value):
+ kind = "function"
+ else:
+ kind = type(value).__name__
+ signature = ""
+ if kind != "enum":
+ try:
+ signature = f"{name}{inspect.signature(value)}"
+ except (TypeError, ValueError):
+ signature = name
+ members = ""
+ if kind == "enum":
+ values = ", ".join(member.name for member in value)
+ members = f'Members: {_esc(values)}
'
+ elif kind == "class":
+ rows = ""
+ for member_name, member in sorted(inspect.getmembers(value, inspect.isfunction)):
+ if member_name.startswith("_"):
+ continue
+ try:
+ member_signature = f"{member_name}{inspect.signature(member)}"
+ except (TypeError, ValueError):
+ member_signature = member_name
+ rows += (
+ f"{_esc(member_signature)} "
+ f"{_esc(_summary(member))} \n"
+ )
+ if rows:
+ members = (
+ "Method Summary "
+ f"\n{rows}
"
+ )
+ # ``str, Enum`` can inherit a version-specific builtin ``str`` docstring when
+ # no class docstring is declared. Never publish that as VSTD documentation.
+ summary = _summary(value)
+ if kind == "enum" and (not summary or summary.startswith("str(")):
+ summary = "Enumeration of the exported result values."
+ if not summary or summary.startswith(f"{name}("):
+ # A dataclass with no docstring of its own repeats its signature; that is
+ # not documentation, so say so instead of publishing the repetition.
+ summary = (
+ "No docstring is declared for this export; the signature above is its "
+ "whole declared surface."
+ )
+ signature_html = (
+ f'{_esc(signature)} \n'
+ if signature
+ else ""
+ )
+ blocks.append(
+ f'\n'
+ f'{_esc(name)} {_esc(kind)} \n'
+ + signature_html
+ + f'{_esc(summary)}
\n'
+ f'Defined in '
+ f"{_esc(module_name)}
\n"
+ f"{members}\n "
+ )
+ return "\n".join(blocks)
+
+
+def _pipeline_section() -> str:
+ rows = ""
+ for command, description, targets in PIPELINE:
+ links = []
+ for target in targets:
+ _, module_name = _resolve(target)
+ links.append(f'{_esc(target)} ')
+ rows += (
+ f"{_esc(command)}{_esc(description)} "
+ f"{' '.join(links)} \n"
+ )
+ return (
+ "Command What it does "
+ f"Implementation entry points \n{rows}
"
+ )
+
+
+def render() -> str:
+ package = importlib.import_module("verifier")
+ version = package.__version__
+ standard = package.__standard__
+ standard_status = package.__standard_status__
+ return f"""
+
+
+
+
+
+
+
+
+
+
+ VSTD docs — command-line interface (CLI) and application programming interface (API) reference
+
+
+
+
+
+ Skip to content
+
+
+
+
+
Reference · verifier-standard {_esc(version)} · {_esc(standard)} {_esc(standard_status)}
+
Inspect the whole pipeline.
+
Terms used below: hash-based message authentication
+ code (HMAC); International Organization for Standardization (ISO); JavaScript Object
+ Notation (JSON); Secure Hash Algorithm 256-bit (SHA-256); and YAML Ain't Markup Language
+ (YAML).
+
Every command, argument, top-level export, and listed dispatch edge below
+ is read out of the installed package when this page is built, by
+ scripts/build_reference.py, and the presentation tests fail closed when the
+ committed page drifts — so it cannot describe behaviour the implementation no
+ longer has.
+
This page states the declared public surface of one implementation. It
+ does not establish that any individual claim checked by these commands is true, nor that
+ an external implementation exists.
+
+
+
+
+
+
Pipeline
+
Command to implementation, without a gap.
+
Each entry point below is imported while this page is built. A
+ rename, move, or deletion fails the build instead of publishing a stale map.
+
{_pipeline_section()}
+
+
+
+
+
+
CLI
+
The vstd command reference.
+
Extracted from the live argument parser in
+ verifier.runtime.public_cli .
+ vstd is the canonical cross-platform command; verifier is
+ retained as an alias only on platforms where it is unambiguous.
+
{_cli_section()}
+
+
+
+
+
+
API
+
Top-level Python exports.
+
The names in verifier.__all__, with their live
+ signatures and declared docstrings, are the supported runtime surface under the
+ Python API stability policy .
+ Subpackage imports are internal unless a published policy names them.
+
{_api_section()}
+
+
+
+
+
+
Wire
+
Canonical schemas and identifiers.
+
Receipt schemas are served from this site at their canonical
+ $id routes, and their serialized `schema_version` identifiers are listed in the
+ standard.
+
+
+
+
+
+
+
+
+"""
+
+
+def main(argv: list[str] | None = None) -> int:
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "--check",
+ action="store_true",
+ help="Fail instead of writing when the committed page is out of date.",
+ )
+ args = parser.parse_args(argv)
+ rendered = render()
+ if args.check:
+ current = OUTPUT.read_text(encoding="utf-8") if OUTPUT.exists() else ""
+ if current != rendered:
+ print(
+ "[REFERENCE DRIFT] docs/reference.html is stale; "
+ "run python scripts/build_reference.py",
+ file=sys.stderr,
+ )
+ return 1
+ print("[REFERENCE OK] docs/reference.html matches the implementation")
+ return 0
+ OUTPUT.write_text(rendered, encoding="utf-8")
+ print(f"[REFERENCE OK] wrote {OUTPUT.relative_to(ROOT).as_posix()}")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/check_acronyms.py b/scripts/check_acronyms.py
new file mode 100644
index 0000000..efaff61
--- /dev/null
+++ b/scripts/check_acronyms.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python3
+"""Terminology: Verifier Standard (VSTD).
+
+Enforce newcomer-readable acronym expansion across Verifier Standard (VSTD) prose."""
+
+from __future__ import annotations
+
+from pathlib import Path
+import re
+import sys
+
+
+ROOT = Path(__file__).resolve().parents[1]
+GLOSSARY = ROOT / "docs" / "ACRONYMS.md"
+GLOSSARY_ROW = re.compile(r"^\| `([^`]+)` \| ([^|]+?) \|", re.MULTILINE)
+SOURCE_SUFFIXES = {".py", ".rs", ".sh"}
+DOCUMENT_SUFFIXES = {".cff", ".html", ".md", ".svg"}
+IGNORED_PARTS = {".git", ".pytest_cache", ".venv", "build", "dist", "__pycache__"}
+
+
+def load_expansions() -> dict[str, str]:
+ """Read the one canonical acronym key used by prose and the checker."""
+
+ text = GLOSSARY.read_text(encoding="utf-8")
+ expansions = {term: expansion.strip() for term, expansion in GLOSSARY_ROW.findall(text)}
+ if not expansions or "VSTD" not in expansions:
+ raise ValueError("docs/ACRONYMS.md has no parseable VSTD expansion table")
+ return expansions
+
+
+def _is_schema(path: Path) -> bool:
+ relative = path.relative_to(ROOT).as_posix()
+ return (
+ relative.startswith("receipts/schema/") and path.suffix == ".json"
+ ) or relative.endswith(".schema.json")
+
+
+def _is_issue_form(path: Path) -> bool:
+ relative = path.relative_to(ROOT).as_posix()
+ return relative.startswith(".github/ISSUE_TEMPLATE/") and path.suffix in {".yml", ".yaml"}
+
+
+def public_reader_files() -> list[Path]:
+ """Return standalone prose and source surfaces, excluding generated dependency data."""
+
+ files: list[Path] = []
+ for path in ROOT.rglob("*"):
+ if not path.is_file() or any(
+ part in IGNORED_PARTS for part in path.relative_to(ROOT).parts
+ ):
+ continue
+ if path == GLOSSARY:
+ continue
+ if (
+ path.suffix.lower() in SOURCE_SUFFIXES | DOCUMENT_SUFFIXES
+ or _is_schema(path)
+ or _is_issue_form(path)
+ or path.name == ".zenodo.json"
+ ):
+ files.append(path)
+ return sorted(files)
+
+
+def _term_pattern(term: str) -> re.Pattern[str]:
+ return re.compile(
+ rf"(? re.Pattern[str]:
+ """Match a definition even when Markdown wraps it across physical lines."""
+
+ words = re.split(r"\s+", expansion.strip())
+ expanded = r"\s+".join(re.escape(word) for word in words)
+ return re.compile(rf"{expanded}\s+\({re.escape(term)}\)")
+
+
+def _required_terms(text: str, expansions: dict[str, str]) -> set[str]:
+ return {
+ term for term in expansions if _term_pattern(term).search(text) is not None
+ }
+
+
+def validate_repo() -> list[str]:
+ """Return every missing or late first-use expansion."""
+
+ expansions = load_expansions()
+ errors: list[str] = []
+ for path in public_reader_files():
+ text = path.read_text(encoding="utf-8")
+ for term in sorted(_required_terms(text, expansions)):
+ definition = f"{expansions[term]} ({term})"
+ definition_match = _definition_pattern(expansions[term], term).search(text)
+ definition_at = -1 if definition_match is None else definition_match.start()
+ first = _term_pattern(term).search(text)
+ if definition_at < 0:
+ errors.append(
+ f"{path.relative_to(ROOT).as_posix()}: {term} is not expanded as "
+ f"{definition!r}"
+ )
+ elif first is not None and definition_at > first.start():
+ line = text.count("\n", 0, first.start()) + 1
+ errors.append(
+ f"{path.relative_to(ROOT).as_posix()}:{line}: {term} appears before "
+ "its expansion"
+ )
+ return errors
+
+
+def main() -> int:
+ errors = validate_repo()
+ if errors:
+ for error in errors:
+ print(f"[ACRONYM FAIL] {error}", file=sys.stderr)
+ return 1
+ print("[ACRONYM OK] registered terms are expanded at first use")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/check_external_links.py b/scripts/check_external_links.py
new file mode 100644
index 0000000..fac9720
--- /dev/null
+++ b/scripts/check_external_links.py
@@ -0,0 +1,163 @@
+#!/usr/bin/env python3
+"""Audit external Hypertext Transfer Protocol (HTTP) documentation links."""
+
+from __future__ import annotations
+
+import argparse
+from concurrent.futures import ThreadPoolExecutor
+from dataclasses import asdict, dataclass
+from datetime import datetime, timezone
+from html.parser import HTMLParser
+import json
+from pathlib import Path
+import re
+import time
+from typing import Iterable
+from urllib.error import HTTPError, URLError
+from urllib.parse import urldefrag
+from urllib.request import Request, urlopen
+
+
+ROOT = Path(__file__).resolve().parents[1]
+DEFAULT_ALLOWLIST = ROOT / ".github/external-links-allowlist.txt"
+MARKDOWN_LINK = re.compile(r"!?\[[^\]]*\]\((https?://[^)\s]+)(?:\s+[^)]*)?\)")
+
+
+class _HtmlLinks(HTMLParser):
+ def __init__(self) -> None:
+ super().__init__()
+ self.links: list[str] = []
+
+ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
+ if tag not in {"a", "img", "link", "script"}:
+ return
+ attributes = dict(attrs)
+ value = attributes.get("href") or attributes.get("src")
+ if value and value.startswith(("http://", "https://")):
+ self.links.append(value)
+
+
+@dataclass(frozen=True)
+class Result:
+ url: str
+ status: str
+ detail: str
+
+
+def collect_links(paths: Iterable[Path]) -> tuple[str, ...]:
+ links: set[str] = set()
+ for path in paths:
+ text = path.read_text(encoding="utf-8")
+ if path.suffix.lower() == ".html":
+ parser = _HtmlLinks()
+ parser.feed(text)
+ links.update(parser.links)
+ elif path.suffix.lower() == ".md":
+ links.update(match.group(1) for match in MARKDOWN_LINK.finditer(text))
+ return tuple(sorted({urldefrag(link)[0] for link in links}))
+
+
+def documentation_paths(root: Path) -> tuple[Path, ...]:
+ excluded = {".git", "build", "dist", "artifacts_tmp"}
+ return tuple(
+ path
+ for path in sorted(root.rglob("*"))
+ if path.is_file()
+ and path.suffix.lower() in {".html", ".md"}
+ and not excluded.intersection(path.relative_to(root).parts)
+ )
+
+
+def read_allowlist(path: Path) -> tuple[tuple[str, str], ...]:
+ entries: list[tuple[str, str]] = []
+ for number, raw in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
+ if not raw or raw.startswith("#"):
+ continue
+ try:
+ pattern, reason = raw.split("\t", 1)
+ except ValueError as exc:
+ raise ValueError(f"{path}:{number}: allowlist entry needs a tab and reason") from exc
+ if not pattern.startswith(("http://", "https://")) or not reason.strip():
+ raise ValueError(f"{path}:{number}: invalid allowlist entry")
+ entries.append((pattern, reason.strip()))
+ return tuple(entries)
+
+
+def allowlist_reason(url: str, entries: Iterable[tuple[str, str]]) -> str | None:
+ for pattern, reason in entries:
+ if (pattern.endswith("*") and url.startswith(pattern[:-1])) or url == pattern:
+ return reason
+ return None
+
+
+def _request(url: str, method: str, timeout: float) -> int:
+ headers = {"User-Agent": "TimeLordRaps-verifier-link-audit/1.0"}
+ if method == "GET":
+ headers["Range"] = "bytes=0-0"
+ request = Request(url, headers=headers, method=method)
+ with urlopen(request, timeout=timeout) as response:
+ return int(response.status)
+
+
+def probe(url: str, *, retries: int, timeout: float) -> Result:
+ last = "no attempt"
+ for attempt in range(retries + 1):
+ try:
+ return Result(url, "OK", str(_request(url, "HEAD", timeout)))
+ except HTTPError as exc:
+ if exc.code in {403, 405}:
+ try:
+ return Result(url, "OK", str(_request(url, "GET", timeout)))
+ except (HTTPError, URLError, TimeoutError, OSError) as get_exc:
+ last = str(get_exc)
+ else:
+ last = str(exc)
+ except (URLError, TimeoutError, OSError) as exc:
+ last = str(exc)
+ if attempt < retries:
+ time.sleep(2**attempt)
+ return Result(url, "FAILED", last)
+
+
+def main(argv: list[str] | None = None) -> int:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--allowlist", type=Path, default=DEFAULT_ALLOWLIST)
+ parser.add_argument("--report", type=Path, default=Path("external-links.json"))
+ parser.add_argument("--retries", type=int, default=2)
+ parser.add_argument("--timeout", type=float, default=15.0)
+ parser.add_argument("--workers", type=int, default=8)
+ args = parser.parse_args(argv)
+
+ entries = read_allowlist(args.allowlist)
+ pending: list[str] = []
+ results: list[Result] = []
+ for url in collect_links(documentation_paths(ROOT)):
+ reason = allowlist_reason(url, entries)
+ if reason:
+ results.append(Result(url, "ALLOWLISTED", reason))
+ else:
+ pending.append(url)
+ with ThreadPoolExecutor(max_workers=args.workers) as executor:
+ results.extend(
+ executor.map(
+ lambda url: probe(url, retries=args.retries, timeout=args.timeout), pending
+ )
+ )
+ results.sort(key=lambda result: result.url)
+ payload = {
+ "schema_version": 1,
+ "checked_at_utc": datetime.now(timezone.utc).isoformat(),
+ "results": [asdict(result) for result in results],
+ }
+ args.report.write_text(
+ json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8", newline="\n"
+ )
+ failures = [result for result in results if result.status == "FAILED"]
+ print(f"[LINK AUDIT] checked={len(results)} failed={len(failures)}")
+ for result in failures:
+ print(f"[FAILED] {result.url}: {result.detail}")
+ return 1 if failures else 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/check_presentation.py b/scripts/check_presentation.py
index 8c70c8c..10f265b 100644
--- a/scripts/check_presentation.py
+++ b/scripts/check_presentation.py
@@ -1,9 +1,15 @@
#!/usr/bin/env python3
-"""Fail closed when public presentation surfaces drift from executable truth."""
+"""Terminology: artificial intelligence (AI); application programming interface (API);
+Amazon Web Services (AWS); Concise Binary Object Representation (CBOR); CBOR Object Signing and
+Encryption (COSE); command-line interface (CLI); Supply Chain Integrity, Transparency, and
+Trust (SCITT); reduced instruction set computer (RISC); Verifier Standard (VSTD).
+
+Fail closed when public presentation surfaces drift from executable truth."""
from __future__ import annotations
from html.parser import HTMLParser
+import importlib.util
import json
from pathlib import Path
import re
@@ -29,6 +35,12 @@
".yaml",
".yml",
}
+RETIRED_SURFACES = (
+ "VSTD-" + "0.1",
+ "VSTD-" + "0.2",
+ "layer" + "4_binding",
+ "vstd" + "4_conformance",
+)
MARKDOWN_LINK = re.compile(r"!?\[[^\]]*\]\(([^)]+)\)")
LOCAL_WINDOWS_PATH = re.compile(
r"(?i)(?:[A-Za-z]:[\\/](?:Users|Documents and Settings)[\\/]|"
@@ -79,18 +91,83 @@
re.compile(r"(?i)\bcausally\s+contribut(?:e|ed|es|ing)\b"),
),
)
+CURRENT_TIME_STATUS = re.compile(
+ r"(?i)\bTIME(?:\.md)?`?\s+(?:is|=|==|has\s+status|status\s*(?:is|=|:))\s+"
+ r"(?:`?Status:\s*)?`?(?:CLEAR|OPEN)\b"
+)
+CURRENT_FACING_SURFACES = (
+ "README.md",
+ "docs/CLAIMS_AND_LIMITS.md",
+ "docs/QUICKSTART.md",
+ "docs/guides.html",
+ "docs/index.html",
+)
+MATURITY_CONFORMANCE = {
+ "VSTD-1": "Implemented reference subset",
+ "VSTD-2": "Implemented vertical slice",
+ "VSTD-3": "Implemented reference surface",
+ "VSTD-4": "Candidate path `NOT_ESTABLISHED`; evidence-bound path can establish conformance",
+ "VSTD-5": "Mechanism can establish a bounded result; a positive observation with unresolved independence remains overall `UNKNOWN`; no repository claim of a real independent witness",
+ "VSTD-Graph-1": "Implemented reference subset",
+ "VSTD-Graph-2": "Candidate `NOT_ESTABLISHED`; evidence-bound profile 1–5 path can establish; profile zero cannot",
+ "VSTD-Graph-3": "Candidate `NOT_ESTABLISHED`; evidence-bound path can establish",
+ "VSTD-Graph-4": "Candidate `NOT_ESTABLISHED`; evidence-bound path can establish",
+ "VSTD-Graph-5": "Candidate `NOT_ESTABLISHED`; evidence-bound path can establish",
+ "Generic run": "Implemented VSTD-1 profile",
+ "Experimental workflow": "No VSTD conformance claim",
+ "Supply Chain Integrity, Transparency, and Trust (SCITT) interoperability": (
+ "VSTD-4 remains `NOT_ESTABLISHED`"
+ ),
+ "zero-identity/zero-knowledge (ZIZK) artifact-first TRUST": (
+ "Implemented reference mechanism; no universal support score or actor trust"
+ ),
+ "RISC Zero proof-carrying reference mechanism": (
+ "Native proof verified; no VSTD receipt mapping"
+ ),
+}
class LinkCollector(HTMLParser):
def __init__(self) -> None:
super().__init__()
self.links: list[str] = []
+ self.html_lang = ""
+ self.has_viewport = False
+ self.in_title = False
+ self.title = ""
+ self.main_ids: list[str] = []
+ self.skip_targets: list[str] = []
+ self.images_without_alt = 0
+ self.unlabelled_navs = 0
def handle_starttag(self, tag: str, attrs) -> None:
+ attributes = dict(attrs)
+ if tag == "html":
+ self.html_lang = attributes.get("lang", "")
+ elif tag == "meta" and attributes.get("name") == "viewport":
+ self.has_viewport = True
+ elif tag == "title":
+ self.in_title = True
+ elif tag == "main":
+ self.main_ids.append(attributes.get("id", ""))
+ elif tag == "a" and "skip-link" in attributes.get("class", "").split():
+ self.skip_targets.append(attributes.get("href", ""))
+ elif tag == "img" and "alt" not in attributes:
+ self.images_without_alt += 1
+ elif tag == "nav" and not attributes.get("aria-label"):
+ self.unlabelled_navs += 1
for name, value in attrs:
if name in {"href", "src"} and value:
self.links.append(value)
+ def handle_endtag(self, tag: str) -> None:
+ if tag == "title":
+ self.in_title = False
+
+ def handle_data(self, data: str) -> None:
+ if self.in_title:
+ self.title += data
+
def _public_files() -> list[Path]:
return sorted(
@@ -112,7 +189,24 @@ def _local_target(source: Path, raw: str) -> Path | None:
return (source.parent / relative).resolve()
+def _generated_documentation_routes() -> set[str]:
+ path = ROOT / "scripts/build_docs.py"
+ spec = importlib.util.spec_from_file_location("build_docs_links", path)
+ if spec is None or spec.loader is None:
+ return set()
+ module = importlib.util.module_from_spec(spec)
+ sys.modules[spec.name] = module
+ try:
+ spec.loader.exec_module(module)
+ return {document.route.as_posix() for document in module.documents()}
+ except Exception:
+ return set()
+ finally:
+ sys.modules.pop(spec.name, None)
+
+
def check_local_links(errors: list[str]) -> None:
+ generated = _generated_documentation_routes()
for source in _public_files():
suffix = source.suffix.lower()
text = source.read_text(encoding="utf-8")
@@ -126,11 +220,40 @@ def check_local_links(errors: list[str]) -> None:
for raw in links:
target = _local_target(source, raw)
if target is not None and not target.exists():
+ try:
+ site_relative = target.relative_to(ROOT / "docs").as_posix()
+ except ValueError:
+ site_relative = ""
+ if site_relative in generated or f"{site_relative}/index.html" in generated:
+ continue
errors.append(
f"broken local link in {source.relative_to(ROOT)}: {raw}"
)
+def check_html_accessibility(errors: list[str]) -> None:
+ """Enforce the small structural accessibility floor for every Pages document."""
+
+ for path in sorted((ROOT / "docs").glob("*.html")):
+ parser = LinkCollector()
+ parser.feed(path.read_text(encoding="utf-8"))
+ name = path.relative_to(ROOT).as_posix()
+ if not parser.html_lang:
+ errors.append(f"{name} has no html language")
+ if not parser.title.strip():
+ errors.append(f"{name} has no document title")
+ if not parser.has_viewport:
+ errors.append(f"{name} has no viewport metadata")
+ if len(parser.main_ids) != 1 or not parser.main_ids[0]:
+ errors.append(f"{name} must have exactly one identified main region")
+ elif f"#{parser.main_ids[0]}" not in parser.skip_targets:
+ errors.append(f"{name} has no skip link to its main region")
+ if parser.images_without_alt:
+ errors.append(f"{name} has {parser.images_without_alt} image(s) without alt text")
+ if parser.unlabelled_navs:
+ errors.append(f"{name} has {parser.unlabelled_navs} navigation region(s) without labels")
+
+
def check_versions(errors: list[str]) -> None:
pyproject = (ROOT / "pyproject.toml").read_text(encoding="utf-8")
project_section = pyproject.split("[project]", 1)
@@ -142,9 +265,10 @@ def check_versions(errors: list[str]) -> None:
expected = project_match.group(1)
init_text = (ROOT / "src/verifier/__init__.py").read_text(encoding="utf-8")
init_match = re.search(r'^__version__ = "([^"]+)"$', init_text, re.MULTILINE)
+ citation_text = (ROOT / "CITATION.cff").read_text(encoding="utf-8")
citation_match = re.search(
r"^version:\s*([^\s]+)$",
- (ROOT / "CITATION.cff").read_text(encoding="utf-8"),
+ citation_text,
re.MULTILINE,
)
zenodo = json.loads((ROOT / ".zenodo.json").read_text(encoding="utf-8"))
@@ -157,31 +281,159 @@ def check_versions(errors: list[str]) -> None:
for label, version in found.items():
if version != expected:
errors.append(f"version mismatch: pyproject={expected}, {label}={version}")
- if not re.search(rf"^## {re.escape(expected)} - \d{{4}}-\d{{2}}-\d{{2}}$", changelog, re.MULTILINE):
- errors.append(f"CHANGELOG.md has no dated {expected} release heading")
+ dated = re.search(
+ rf"^## {re.escape(expected)} - (\d{{4}}-\d{{2}}-\d{{2}})$",
+ changelog,
+ re.MULTILINE,
+ )
+ unreleased = re.search(
+ rf"^## {re.escape(expected)} - UNRELEASED$", changelog, re.MULTILINE
+ )
+ citation_date = re.search(r"^date-released:\s*(\d{4}-\d{2}-\d{2})$", citation_text, re.MULTILINE)
+ if dated is None and unreleased is None:
+ errors.append(f"CHANGELOG.md has no dated or UNRELEASED {expected} heading")
+ elif unreleased is not None:
+ if citation_date is not None:
+ errors.append("unreleased CITATION.cff must not fabricate date-released")
+ if "release candidate" not in citation_text.lower():
+ errors.append("unreleased CITATION.cff must identify the release candidate")
+ elif citation_date is None or citation_date.group(1) != dated.group(1):
+ errors.append("CITATION.cff date-released must match the dated CHANGELOG heading")
+
+
+def maturity_table_violations(readme: str) -> list[str]:
+ """Require one reviewable status row for every advertised major surface."""
+
+ heading = "## Current maturity"
+ if heading not in readme:
+ return ["README.md has no canonical current-maturity section"]
+ section = readme.split(heading, 1)[1].split("\n## ", 1)[0]
+ header = (
+ "| Surface | Normative status | Reference implementation | Evidence binding | "
+ "Conformance status | Missing mechanism or evidence |"
+ )
+ errors: list[str] = []
+ if header not in section:
+ errors.append("README.md maturity table does not expose all six required fields")
+ rows: dict[str, list[str]] = {}
+ for line in section.splitlines():
+ if not line.startswith("|") or line.startswith("|---"):
+ continue
+ cells = [cell.strip() for cell in line.strip().strip("|").split("|")]
+ if cells and cells[0] != "Surface":
+ rows.setdefault(cells[0], []).append(line)
+ if len(cells) != 6:
+ errors.append(
+ f"README.md maturity row {cells[0]!r} has {len(cells)} fields, expected 6"
+ )
+ for surface, conformance in MATURITY_CONFORMANCE.items():
+ observed = rows.get(surface, [])
+ if len(observed) != 1:
+ errors.append(
+ f"README.md maturity table requires exactly one {surface!r} row, "
+ f"observed {len(observed)}"
+ )
+ elif conformance not in observed[0]:
+ errors.append(
+ f"README.md maturity row {surface!r} is missing conformance boundary "
+ f"{conformance!r}"
+ )
+ return errors
+
+
+def transient_time_status_violations(text: str) -> list[str]:
+ """Find transient TIME state copied into long-lived explanatory prose."""
+
+ return [match.group(0) for match in CURRENT_TIME_STATUS.finditer(text)]
def check_claim_boundaries(errors: list[str]) -> None:
readme = (ROOT / "README.md").read_text(encoding="utf-8")
roadmap = (ROOT / "ROADMAP.md").read_text(encoding="utf-8")
wire = (ROOT / "standard/WIRE_IDENTIFIERS.md").read_text(encoding="utf-8")
+ reference = (ROOT / "docs/reference.html").read_text(encoding="utf-8")
+ changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
+ scitt_demo = (ROOT / "examples/scitt_interop/demo.py").read_text(encoding="utf-8")
+ scitt_result = json.loads(
+ (ROOT / "examples/scitt_interop/generated/verification_result.json").read_text(
+ encoding="utf-8"
+ )
+ )
required_readme = (
"Portable, bounded, refutable evidence for computational claims.",
+ "VSTD is a verification-domain language and Python reference implementation",
+ "does **not**\nreplace native domain verifiers",
+ "## 30–60 second demonstration",
+ "## What a result means",
+ "## Current maturity",
+ "## Why VSTD exists",
"vstd demo",
- "founder-maintained **alpha project specification**",
- "A higher-layer result does **not** supply",
- "It cannot prove general AI safety",
+ "A later-profile result does **not** supply",
+ "It cannot prove general AI",
+ "[Normative specifications](standard/LADDER.md)",
+ "[Report an ambiguity or counterexample]",
+ "[Report a vulnerability privately]",
+ "SCITT registration proves neither payload",
+ "VSTD evaluates bounded validity propositions about computational processes",
+ "RUST is the inverse-TRUST diagnostic mechanic",
+ "cryptographic zero knowledge can enclose",
+ "The current checkout is an unreleased",
)
for phrase in required_readme:
if phrase not in readme:
errors.append(f"README.md is missing presentation boundary: {phrase!r}")
- if "`vstd` is the canonical cross-platform command" not in readme:
+ if "`vstd` is the canonical cross-platform CLI name" not in readme:
errors.append("README.md does not disclose the canonical cross-platform CLI")
+ errors.extend(maturity_table_violations(readme))
+ expected_order = (
+ "VSTD is a verification-domain language",
+ "## 30–60 second demonstration",
+ "## What a result means",
+ "## Current maturity",
+ "## Why VSTD exists",
+ "## Architecture",
+ "## Install and use",
+ "## Interoperability",
+ "## Reproducibility and releases",
+ "## Claims, security, and contribution",
+ "## Citation and license",
+ )
+ positions = [readme.find(marker) for marker in expected_order]
+ if any(position < 0 for position in positions) or positions != sorted(positions):
+ errors.append("README.md first-view information hierarchy has drifted")
+ for relative in CURRENT_FACING_SURFACES:
+ text = (ROOT / relative).read_text(encoding="utf-8")
+ for match in transient_time_status_violations(text):
+ errors.append(f"transient TIME state copied into {relative}: {match!r}")
+ for relative in (
+ "README.md",
+ "AGENTS.md",
+ "CODE_OF_CONDUCT.md",
+ "GOVERNANCE.md",
+ "docs/index.html",
+ "docs/guides.html",
+ "docs/assets/vstd-overview.svg",
+ ):
+ if "founder-maintained" in (ROOT / relative).read_text(encoding="utf-8").lower():
+ errors.append(f"{relative} uses reputation-centric founder-maintained wording")
if "`vstd` is the canonical cross-platform CLI name" not in wire:
errors.append("WIRE_IDENTIFIERS.md does not preserve the CLI compatibility rule")
+ if "VSTD-5 PROJECT SPECIFICATION; EVIDENCE-BOUND REFERENCE MECHANISM" not in reference:
+ errors.append("generated reference does not report its VSTD-5 mechanism status")
+ if "reproducible COSE specimen" in changelog or "reproducible specimen" in roadmap:
+ errors.append("SCITT ephemeral-key specimen is described as byte-reproducible")
+ if "ephemeral-key COSE artifacts" not in scitt_demo:
+ errors.append("SCITT producer does not disclose its ephemeral-key artifact boundary")
+ if scitt_result.get("vstd_observation", {}).get("conformance_status") != "NOT_ESTABLISHED":
+ errors.append("SCITT verification result drops VSTD conformance status")
+ composition = scitt_result.get("composition", {})
+ if composition.get("vstd_conformance_status") != "NOT_ESTABLISHED":
+ errors.append("SCITT composition drops VSTD conformance status")
+ if composition.get("status_scope") != "NATIVE_VSTD_RESULT_AND_SCITT_REGISTRATION":
+ errors.append("SCITT composition does not state the scope of PASS")
if "## Explicit non-goals" not in roadmap or "operational condition" not in roadmap:
errors.append("ROADMAP.md lacks its capability and non-goal boundary")
- if (ROOT / "docs/layers/vstd-3/migration.md").exists():
+ if (ROOT / "docs/profiles/vstd-3/migration.md").exists():
errors.append("obsolete adopter-migration path has reappeared")
@@ -223,6 +475,20 @@ def check_lineage_claims(errors: list[str]) -> None:
errors.append(f"{label} in {path.relative_to(ROOT)}:{line}")
+def check_retired_surfaces(errors: list[str]) -> None:
+ """Prevent removed partial-profile identifiers and developmental fields from returning."""
+
+ for path in _public_files():
+ text = path.read_text(encoding="utf-8")
+ for retired in RETIRED_SURFACES:
+ offset = text.find(retired)
+ if offset >= 0:
+ line = text.count("\n", 0, offset) + 1
+ errors.append(
+ f"retired surface {retired!r} returned in {path.relative_to(ROOT)}:{line}"
+ )
+
+
def check_visual_assets(errors: list[str]) -> None:
svg = ROOT / "docs/assets/vstd-overview.svg"
try:
@@ -239,18 +505,18 @@ def check_visual_assets(errors: list[str]) -> None:
"vstd-1": "REF. SUBSET",
"vstd-2": "EXPERIMENTAL",
"vstd-3": "IMPLEMENTED",
- "vstd-4": "IMPLEMENTED",
- "vstd-5": "DRAFT",
+ "vstd-4": "REF. MECH.",
+ "vstd-5": "REF. MECH.",
"graph-1": "REF. SUBSET",
- "graph-2": "IMPLEMENTED",
- "graph-3": "IMPLEMENTED",
- "graph-4": "IMPLEMENTED",
- "graph-5": "DRAFT",
+ "graph-2": "REF. MECH.",
+ "graph-3": "REF. MECH.",
+ "graph-4": "REF. MECH.",
+ "graph-5": "REF. MECH.",
}
observed_status = {
- element.attrib["data-layer"]: "".join(element.itertext()).strip()
+ element.attrib["data-profile"]: "".join(element.itertext()).strip()
for element in root.iter()
- if "data-layer" in element.attrib
+ if "data-profile" in element.attrib
}
if observed_status != expected_status:
errors.append(
@@ -272,14 +538,155 @@ def check_visual_assets(errors: list[str]) -> None:
)
+def check_generated_reference(errors: list[str]) -> None:
+ """The published CLI/API reference must still match the importable package."""
+
+ path = ROOT / "scripts/build_reference.py"
+ spec = importlib.util.spec_from_file_location("build_reference", path)
+ if spec is None or spec.loader is None:
+ errors.append("cannot load scripts/build_reference.py")
+ return
+ module = importlib.util.module_from_spec(spec)
+ try:
+ spec.loader.exec_module(module)
+ rendered = module.render()
+ except Exception as exc: # noqa: BLE001 - any failure is a presentation failure
+ errors.append(f"reference page cannot be generated: {exc}")
+ return
+ target = ROOT / "docs/reference.html"
+ if not target.is_file():
+ errors.append("docs/reference.html is missing; run python scripts/build_reference.py")
+ return
+ if target.read_text(encoding="utf-8") != rendered:
+ errors.append(
+ "docs/reference.html drifted from the implementation; "
+ "run python scripts/build_reference.py"
+ )
+ index = (ROOT / "docs/index.html").read_text(encoding="utf-8")
+ for link in ('Guides ', 'Reference '):
+ if link not in index:
+ errors.append(f"docs/index.html navigation is missing {link}")
+
+
+def check_generated_documentation(errors: list[str]) -> None:
+ """Every maintained Markdown source must have one unambiguous site route."""
+
+ path = ROOT / "scripts/build_docs.py"
+ spec = importlib.util.spec_from_file_location("build_docs", path)
+ if spec is None or spec.loader is None:
+ errors.append("cannot load scripts/build_docs.py")
+ return
+ module = importlib.util.module_from_spec(spec)
+ sys.modules[spec.name] = module
+ try:
+ spec.loader.exec_module(module)
+ documents = module.documents()
+ except Exception as exc: # any generation failure blocks publication
+ errors.append(f"documentation routes cannot be generated: {exc}")
+ return
+ finally:
+ sys.modules.pop(spec.name, None)
+ routes = [document.route.as_posix() for document in documents]
+ if not routes:
+ errors.append("documentation renderer declares no source pages")
+ if len(routes) != len(set(routes)):
+ errors.append("documentation renderer declares duplicate site routes")
+ for document in documents:
+ if not document.source.is_file():
+ errors.append(f"documentation source is missing: {document.source}")
+
+ pages = {
+ name: (ROOT / name).read_text(encoding="utf-8")
+ for name in ("docs/index.html", "docs/guides.html", "docs/reference.html")
+ }
+ required = (
+ 'href="standard/"',
+ 'href="experiments/"',
+ 'href="project/ROADMAP.html"',
+ )
+ for name, page in pages.items():
+ for link in required:
+ if link not in page:
+ errors.append(f"{name} navigation is missing the on-site route {link}")
+ if '>Standard' not in page or '>Specifications' in page:
+ errors.append(f"{name} navigation must label standard/ as Standard")
+ guides = pages["docs/guides.html"]
+ if "github.com/TimeLordRaps/verifier/blob/main/docs/" in guides:
+ errors.append("docs/guides.html sends maintained guides to the GitHub file viewer")
+ if "github.com/TimeLordRaps/verifier/blob/main/standard/" in guides:
+ errors.append("docs/guides.html sends specifications to the GitHub file viewer")
+
+
+def check_experiment_index(errors: list[str]) -> None:
+ """Profile manifests, bound repo artifacts, and the public index must agree."""
+
+ path = ROOT / "scripts/build_experiment_index.py"
+ spec = importlib.util.spec_from_file_location("build_experiment_index", path)
+ if spec is None or spec.loader is None:
+ errors.append("cannot load scripts/build_experiment_index.py")
+ return
+ module = importlib.util.module_from_spec(spec)
+ try:
+ spec.loader.exec_module(module)
+ rendered = module.render(module.discover(ROOT))
+ except Exception as exc: # the gate reports any bounded generation failure
+ errors.append(f"experiment index cannot be generated: {exc}")
+ return
+ target = ROOT / "experiments/INDEX.md"
+ if not target.is_file() or target.read_text(encoding="utf-8") != rendered:
+ errors.append(
+ "experiments/INDEX.md drifted from profile manifests; "
+ "run python scripts/build_experiment_index.py"
+ )
+
+
+def check_acronyms(errors: list[str]) -> None:
+ """Require first-use expansion on every registered reader-facing surface."""
+
+ path = ROOT / "scripts/check_acronyms.py"
+ spec = importlib.util.spec_from_file_location("check_acronyms", path)
+ if spec is None or spec.loader is None:
+ errors.append("cannot load scripts/check_acronyms.py")
+ return
+ module = importlib.util.module_from_spec(spec)
+ try:
+ spec.loader.exec_module(module)
+ errors.extend(module.validate_repo())
+ except Exception as exc: # any glossary or scan failure is a presentation failure
+ errors.append(f"acronym presentation gate failed: {exc}")
+
+
+def check_terminology(errors: list[str]) -> None:
+ """Reject ambiguous structural terms on current public surfaces."""
+
+ path = ROOT / "scripts/check_terminology.py"
+ spec = importlib.util.spec_from_file_location("check_terminology", path)
+ if spec is None or spec.loader is None:
+ errors.append("cannot load scripts/check_terminology.py")
+ return
+ module = importlib.util.module_from_spec(spec)
+ try:
+ spec.loader.exec_module(module)
+ errors.extend(module.validate_repo())
+ except Exception as exc: # any terminology scan failure is a presentation failure
+ errors.append(f"terminology presentation gate failed: {exc}")
+
+
def run() -> list[str]:
errors: list[str] = []
check_local_links(errors)
+ check_html_accessibility(errors)
check_versions(errors)
check_claim_boundaries(errors)
check_public_paths(errors)
check_lineage_claims(errors)
+ check_retired_surfaces(errors)
check_visual_assets(errors)
+ check_generated_reference(errors)
+ check_experiment_index(errors)
+ check_generated_documentation(errors)
+ check_acronyms(errors)
+ check_terminology(errors)
return errors
@@ -289,7 +696,11 @@ def main() -> int:
for error in errors:
print(f"[PRESENTATION FAIL] {error}", file=sys.stderr)
return 1
- print("[PRESENTATION OK] links, versions, boundaries, paths, and visual assets")
+ print(
+ "[PRESENTATION OK] links, accessibility, versions, boundaries, paths, "
+ "maturity, transient status, visual assets, generated reference, experiment "
+ "index, acronym expansion, and structural terminology"
+ )
return 0
diff --git a/scripts/check_release_boundary.py b/scripts/check_release_boundary.py
index ef5908b..cc4d4e4 100644
--- a/scripts/check_release_boundary.py
+++ b/scripts/check_release_boundary.py
@@ -1,5 +1,7 @@
#!/usr/bin/env python3
-"""Fail closed when a release archive contains private or secret-shaped text."""
+"""Terminology: Unicode Transformation Format, 8-bit (UTF-8); Verifier Standard (VSTD).
+
+Fail closed when a release archive contains private or secret-shaped text."""
from __future__ import annotations
@@ -70,6 +72,9 @@ def check_artifact(path: Path, errors: list[str]) -> int:
return _scan_zip(path, errors)
if path.name.endswith(".tar.gz"):
return _scan_tar(path, errors)
+ if path.name.endswith(".json"):
+ _scan_text(path, path.name, path.read_bytes(), errors)
+ return 1
raise ValueError(f"unsupported release artifact: {path}")
diff --git a/scripts/check_release_metadata.py b/scripts/check_release_metadata.py
new file mode 100644
index 0000000..7c38575
--- /dev/null
+++ b/scripts/check_release_metadata.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python3
+"""Require finalized, internally consistent metadata before tag publication."""
+
+from __future__ import annotations
+
+import argparse
+from datetime import date
+import json
+from pathlib import Path
+import re
+import sys
+
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+def _single(pattern: str, text: str, label: str) -> str:
+ matches = re.findall(pattern, text, re.MULTILINE)
+ if len(matches) != 1:
+ raise ValueError(f"{label} must appear exactly once; observed {len(matches)}")
+ return str(matches[0])
+
+
+def require_finalized(root: Path, version: str) -> None:
+ """Reject release-candidate or inconsistent metadata for ``version``."""
+
+ pyproject = (root / "pyproject.toml").read_text(encoding="utf-8")
+ project = pyproject.split("[project]", 1)
+ project_text = "" if len(project) != 2 else project[1].split("\n[", 1)[0]
+ package_version = _single(
+ r'^version\s*=\s*"([^"]+)"$', project_text, "pyproject [project] version"
+ )
+ if package_version != version:
+ raise ValueError(
+ f"release version {version} does not match package version {package_version}"
+ )
+
+ changelog = (root / "CHANGELOG.md").read_text(encoding="utf-8")
+ if re.search(
+ rf"^## {re.escape(version)} - UNRELEASED$", changelog, re.MULTILINE
+ ):
+ raise ValueError(f"CHANGELOG {version} is still UNRELEASED")
+ release_date = _single(
+ rf"^## {re.escape(version)} - (\d{{4}}-\d{{2}}-\d{{2}})$",
+ changelog,
+ f"dated CHANGELOG {version} heading",
+ )
+ try:
+ date.fromisoformat(release_date)
+ except ValueError as exc:
+ raise ValueError(f"CHANGELOG release date is invalid: {release_date}") from exc
+
+ citation = (root / "CITATION.cff").read_text(encoding="utf-8")
+ citation_version = _single(
+ r"^version:\s*([^\s]+)$", citation, "CITATION version"
+ )
+ citation_date = _single(
+ r"^date-released:\s*(\d{4}-\d{2}-\d{2})$",
+ citation,
+ "CITATION date-released",
+ )
+ if citation_version != version or citation_date != release_date:
+ raise ValueError(
+ "CITATION version/date must match the package and CHANGELOG release coordinate"
+ )
+ if "release candidate" in citation.lower():
+ raise ValueError("CITATION still describes a release candidate")
+
+ zenodo = json.loads((root / ".zenodo.json").read_text(encoding="utf-8"))
+ if zenodo.get("version") != version:
+ raise ValueError("Zenodo version does not match the release coordinate")
+ description = str(zenodo.get("description", "")).lower()
+ if "release-candidate" in description or "after the release exists" in description:
+ raise ValueError("Zenodo metadata still describes an unpublished candidate")
+
+
+def main(argv: list[str] | None = None) -> int:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--version", required=True)
+ parser.add_argument("--root", type=Path, default=ROOT)
+ args = parser.parse_args(argv)
+ try:
+ require_finalized(args.root, args.version)
+ except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
+ print(f"[RELEASE METADATA BLOCKED] {exc}", file=sys.stderr)
+ return 1
+ print(f"[RELEASE METADATA FINAL] {args.version}")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/check_terminology.py b/scripts/check_terminology.py
new file mode 100644
index 0000000..210043b
--- /dev/null
+++ b/scripts/check_terminology.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python3
+"""Terminology: Verifier Standard (VSTD).
+
+Reject prose that treats numbered VSTD profiles as interchangeable layers or
+scalar levels. Public compatibility identifiers remain unchanged and are not
+matched by this prose-focused gate.
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+import re
+import subprocess
+
+
+ROOT = Path(__file__).resolve().parents[1]
+TEXT_SUFFIXES = {
+ ".cff",
+ ".html",
+ ".json",
+ ".md",
+ ".py",
+ ".svg",
+ ".toml",
+ ".txt",
+ ".yaml",
+ ".yml",
+}
+IGNORED_PARTS = {
+ ".git",
+ ".pytest_cache",
+ ".venv",
+ "build",
+ "dist",
+ "__pycache__",
+}
+GENERATED_PREFIXES = (
+ "examples/flagship_demo/specimens/",
+ "src/verifier/specifications/",
+)
+SCAN_EXCLUSIONS = {
+ "examples/zizk_artifact_first/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md",
+ "experiments/artifact_first_mechanisms/reverification/ROUND2_DESIGN_NOTE.md",
+ "tests/test_presentation_surface.py",
+}
+AMBIGUOUS_PATTERNS = (
+ ("VSTD profiles called layers", re.compile(r"(?i)\bVSTD\s+layers?\b")),
+ ("object profiles called layers", re.compile(r"(?i)\bobject\s+layers?\b")),
+ ("Graph profiles called layers", re.compile(r"(?i)\bGraph\s+layers?\b")),
+ ("Graph profile called a level", re.compile(r"(?i)\bGraph\s+levels?\b")),
+ (
+ "candidate Graph profile called a level",
+ re.compile(r"(?i)\bcandidate\s+graph\s+levels?\b"),
+ ),
+ ("profile dependency called lower-layer", re.compile(r"(?i)\blower[- ]layers?\b")),
+ ("profile dependency called higher-layer", re.compile(r"(?i)\bhigher[- ]layers?\b")),
+ ("numbered profile called a layer", re.compile(r"(?i)\bnumbered\s+layers?\b")),
+ ("profile result called a layer result", re.compile(r"(?i)\blayer\s+results?\b")),
+ (
+ "profile conformance called layer conformance",
+ re.compile(r"(?i)\blayer\s+conformance\b"),
+ ),
+ ("verification complex called a VSTD ladder", re.compile(r"(?i)\bVSTD\s+ladder\b")),
+ ("VSTD-4 rung called a ladder rung", re.compile(r"(?i)\bladder\s+rungs?\b")),
+ ("VSTD-4 depth left unqualified", re.compile(r"(?i)\bVSTD-4\s+depth\b")),
+ (
+ "VSTD-4 candidate depth inverted into a structural depth candidate",
+ re.compile(r"(?i)\bstructural\s+depth\s+candidate\b"),
+ ),
+)
+
+
+def terminology_violations(text: str) -> list[tuple[str, int]]:
+ """Return ambiguous phrase labels and one-based line numbers."""
+
+ violations: list[tuple[str, int]] = []
+ for label, pattern in AMBIGUOUS_PATTERNS:
+ for match in pattern.finditer(text):
+ violations.append((label, text.count("\n", 0, match.start()) + 1))
+ return violations
+
+
+def _tracked_text_files() -> list[Path]:
+ result = subprocess.run(
+ ["git", "ls-files", "-z"],
+ cwd=ROOT,
+ check=True,
+ capture_output=True,
+ )
+ paths: list[Path] = []
+ for raw_relative in result.stdout.decode("utf-8").split("\0"):
+ if not raw_relative:
+ continue
+ relative = Path(raw_relative).as_posix()
+ path = ROOT / raw_relative
+ if path.suffix.lower() not in TEXT_SUFFIXES:
+ continue
+ if any(part in IGNORED_PARTS for part in Path(relative).parts):
+ continue
+ if relative == "scripts/check_terminology.py":
+ continue
+ if relative in SCAN_EXCLUSIONS:
+ continue
+ if relative.startswith(GENERATED_PREFIXES):
+ continue
+ paths.append(path)
+ return sorted(paths)
+
+
+def validate_repo() -> list[str]:
+ """Validate current source surfaces; generated copies are checked elsewhere."""
+
+ errors: list[str] = []
+ for path in _tracked_text_files():
+ text = path.read_text(encoding="utf-8")
+ for label, line in terminology_violations(text):
+ errors.append(f"{label} in {path.relative_to(ROOT)}:{line}")
+ return errors
+
+
+def main() -> int:
+ errors = validate_repo()
+ if errors:
+ for error in errors:
+ print(f"[TERMINOLOGY FAIL] {error}")
+ return 1
+ print("[TERMINOLOGY OK] numbered profiles and closure coordinates remain distinct")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/check_time_status.py b/scripts/check_time_status.py
new file mode 100644
index 0000000..e03955d
--- /dev/null
+++ b/scripts/check_time_status.py
@@ -0,0 +1,36 @@
+"""Fail closed unless the named TIME file has exactly ``Status: CLEAR``."""
+
+from __future__ import annotations
+
+from pathlib import Path
+import sys
+
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+def require_clear(path: Path) -> None:
+ lines = [
+ line for line in path.read_text(encoding="utf-8").splitlines()
+ if line.startswith("Status:")
+ ]
+ if lines != ["Status: CLEAR"]:
+ raise ValueError(
+ f"release requires exactly one Status: CLEAR line; observed {lines or ['MISSING']}"
+ )
+
+
+def main(argv: list[str] | None = None) -> int:
+ args = sys.argv[1:] if argv is None else argv
+ path = Path(args[0]) if args else ROOT / "TIME.md"
+ try:
+ require_clear(path)
+ except (OSError, UnicodeError, ValueError) as exc:
+ print(f"[TIME BLOCKED] {exc}", file=sys.stderr)
+ return 1
+ print("[TIME CLEAR] release invariant satisfied")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/release_artifacts.py b/scripts/release_artifacts.py
index cd3a6b5..ace0685 100644
--- a/scripts/release_artifacts.py
+++ b/scripts/release_artifacts.py
@@ -1,4 +1,7 @@
-"""Build and verify public release artifacts from an exact public Git ref.
+"""Terminology: Secure Hash Algorithm 256-bit (SHA-256); Software Bill of Materials
+(SBOM); uniform resource locator (URL); Verifier Standard (VSTD); ZIP archive format (ZIP).
+
+Build and verify public release artifacts from an exact public Git ref.
The release manifest is an artifact beside the source ZIP, not a tracked file inside
the source tree. That avoids a self-referential commit hash and makes ``source_commit``
@@ -28,10 +31,11 @@
import tempfile
import zipfile
from pathlib import Path
-from typing import Any
+from typing import Any, Mapping
SCHEMA_VERSION = "VSTD-PUBLIC-RELEASE-1.1"
+CYCLONEDX_SPEC_VERSION = "1.6"
# Archive stem for releases built from this tree. Releases up to and including v1.1.1
# were published as `verifiable-standard-.zip`; their manifests carry that
# prefix and are still verified from the manifest itself.
@@ -75,6 +79,95 @@ def _file_record(path: Path) -> dict[str, Any]:
return {"byte_size": len(data), "sha256": _sha256(data)}
+def _cyclonedx_component(filename: str, record: Mapping[str, Any]) -> dict[str, Any]:
+ return {
+ "type": "file",
+ "bom-ref": f"artifact:{filename}",
+ "name": filename,
+ "hashes": [{"alg": "SHA-256", "content": str(record["sha256"])}],
+ "properties": [{"name": "vstd:byte-size", "value": str(record["byte_size"])}],
+ }
+
+
+def _cyclonedx_payload(
+ *,
+ commit: str,
+ epoch: str,
+ release: str,
+ artifacts: Mapping[str, Mapping[str, Any]],
+) -> dict[str, Any]:
+ timestamp = datetime.fromtimestamp(int(epoch), timezone.utc).isoformat().replace(
+ "+00:00", "Z"
+ )
+ root_ref = f"pkg:pypi/{DISTRIBUTION_NAME}@{release}"
+ components = [
+ _cyclonedx_component(name, artifacts[name]) for name in sorted(artifacts)
+ ]
+ return {
+ "$schema": "https://cyclonedx.org/schema/bom-1.6.schema.json",
+ "bomFormat": "CycloneDX",
+ "specVersion": CYCLONEDX_SPEC_VERSION,
+ "version": 1,
+ "metadata": {
+ "timestamp": timestamp,
+ "component": {
+ "type": "library",
+ "bom-ref": root_ref,
+ "name": DISTRIBUTION_NAME,
+ "version": release,
+ "purl": root_ref,
+ "licenses": [{"license": {"id": "Apache-2.0"}}],
+ "properties": [{"name": "vstd:source-commit", "value": commit}],
+ },
+ },
+ "components": components,
+ "dependencies": [
+ {
+ "ref": root_ref,
+ "dependsOn": [component["bom-ref"] for component in components],
+ }
+ ],
+ }
+
+
+def _write_cyclonedx_sbom(
+ destination: Path,
+ *,
+ commit: str,
+ epoch: str,
+ release: str,
+ artifacts: Mapping[str, Mapping[str, Any]],
+) -> None:
+ destination.write_text(
+ json.dumps(
+ _cyclonedx_payload(
+ commit=commit, epoch=epoch, release=release, artifacts=artifacts
+ ),
+ indent=2,
+ sort_keys=True,
+ )
+ + "\n",
+ encoding="utf-8",
+ newline="\n",
+ )
+
+
+def _verify_cyclonedx_sbom(
+ path: Path,
+ *,
+ commit: str,
+ epoch: str,
+ release: str,
+ artifacts: Mapping[str, Mapping[str, Any]],
+) -> None:
+ payload = json.loads(path.read_text(encoding="utf-8"))
+ expected = _cyclonedx_payload(
+ commit=commit, epoch=epoch, release=release, artifacts=artifacts
+ )
+ if payload != expected:
+ raise ReleaseError("CycloneDX SBOM bytes do not match the bound release subjects")
+
+
def _resolved_commit(repo: Path, ref: str) -> str:
return _run(repo, "git", "rev-parse", f"{ref}^{{commit}}").decode().strip()
@@ -522,7 +615,7 @@ def _build_sdist_once(
def build_all(
repo: Path, ref: str, release: str, output_dir: Path
-) -> tuple[Path, Path, Path, Path]:
+) -> tuple[Path, Path, Path, Path, Path]:
archive, manifest_path = build_source(repo, ref, release, output_dir)
commit = _resolved_commit(repo.resolve(), ref)
epoch = _run(repo.resolve(), "git", "show", "-s", "--format=%ct", commit).decode().strip()
@@ -550,11 +643,26 @@ def build_all(
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
manifest["artifacts"][wheel.name] = _file_record(wheel)
manifest["artifacts"][sdist.name] = _file_record(sdist)
+ sbom = output_dir / f"{ARCHIVE_STEM}-{release}.cdx.json"
+ _write_cyclonedx_sbom(
+ sbom,
+ commit=commit,
+ epoch=epoch,
+ release=release,
+ artifacts=manifest["artifacts"],
+ )
+ manifest["sbom"] = {
+ "filename": sbom.name,
+ "format": "CycloneDX",
+ "spec_version": CYCLONEDX_SPEC_VERSION,
+ "subjects": sorted(manifest["artifacts"]),
+ }
+ manifest["artifacts"][sbom.name] = _file_record(sbom)
manifest_path.write_text(
json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8", newline="\n"
)
verify_manifest(repo, manifest_path, output_dir)
- return archive, wheel, sdist, manifest_path
+ return archive, wheel, sdist, sbom, manifest_path
def compare_artifact_directories(first: Path, second: Path) -> int:
@@ -614,6 +722,29 @@ def verify_manifest(repo: Path, manifest_path: Path, artifact_dir: Path | None =
if not path.is_file() or _file_record(path) != expected:
raise ReleaseError(f"artifact digest or byte size mismatch: {filename}")
+ sbom_record = manifest.get("sbom")
+ if sbom_record is not None:
+ sbom_name = str(sbom_record.get("filename", ""))
+ subjects = sorted(name for name in artifacts if name != sbom_name)
+ if sbom_record != {
+ "filename": sbom_name,
+ "format": "CycloneDX",
+ "spec_version": CYCLONEDX_SPEC_VERSION,
+ "subjects": subjects,
+ }:
+ raise ReleaseError("release manifest SBOM binding is not canonical")
+ if not sbom_name.endswith(".cdx.json") or sbom_name not in artifacts:
+ raise ReleaseError("release manifest does not bind its declared SBOM")
+ _verify_cyclonedx_sbom(
+ artifact_dir / sbom_name,
+ commit=commit,
+ epoch=_run(repo, "git", "show", "-s", "--format=%ct", commit)
+ .decode()
+ .strip(),
+ release=str(manifest["release"]),
+ artifacts={name: artifacts[name] for name in subjects},
+ )
+
distribution = manifest.get("distribution")
if distribution is not None:
expected_distribution = {
@@ -690,12 +821,13 @@ def main(argv: list[str] | None = None) -> int:
print(f"[PASS] source archive: {archive}")
print(f"[PASS] release manifest: {manifest}")
elif args.command == "build":
- archive, wheel, sdist, manifest = build_all(
+ archive, wheel, sdist, sbom, manifest = build_all(
args.repo, args.ref, args.release, args.output_dir
)
print(f"[PASS] source archive: {archive}")
print(f"[PASS] reproducible wheel: {wheel}")
print(f"[PASS] reproducible sdist: {sdist}")
+ print(f"[PASS] CycloneDX SBOM: {sbom}")
print(f"[PASS] release manifest: {manifest}")
elif args.command == "verify":
verify_manifest(args.repo, args.manifest, args.artifact_dir)
diff --git a/src/verifier/__init__.py b/src/verifier/__init__.py
index 83a7232..31ffced 100644
--- a/src/verifier/__init__.py
+++ b/src/verifier/__init__.py
@@ -1,14 +1,33 @@
-"""VSTD reference implementation public API."""
+"""Terminology: application programming interface (API); Verifier Standard (VSTD).
+
+VSTD reference implementation public API."""
from __future__ import annotations
from importlib import import_module
+import warnings
from typing import TYPE_CHECKING, Any
-__version__ = "1.1.3"
-__standard__ = "VSTD-4"
+__version__ = "1.2.0"
+# This names the highest project-specification coordinate exposed by the package;
+# it is not a conformance claim. Keep the adjacent status when presenting it.
+__standard__ = "VSTD-5"
+__standard_status__ = "PROJECT SPECIFICATION; EVIDENCE-BOUND REFERENCE MECHANISM"
_LAZY_EXPORTS = {
+ "ArtifactControlError": ("verifier.artifact_control", "ArtifactControlError"),
+ "ArtifactVerification": ("verifier.artifact_control", "ArtifactVerification"),
+ "freeze_artifact": ("verifier.artifact_control", "freeze_artifact"),
+ "seal_artifact": ("verifier.artifact_control", "seal_artifact"),
+ "thaw_artifact": ("verifier.artifact_control", "thaw_artifact"),
+ "thawed_artifact_status": (
+ "verifier.artifact_control",
+ "thawed_artifact_status",
+ ),
+ "verify_frozen_artifact": (
+ "verifier.artifact_control",
+ "verify_frozen_artifact",
+ ),
"VerificationVerdict": ("verifier.core.checker", "VerificationVerdict"),
"VstdReceipt": ("verifier.core.receipt", "VstdReceipt"),
"compute_canonical_digest": ("verifier.core.receipt", "compute_canonical_digest"),
@@ -22,9 +41,60 @@
"certificate_from_canonical_bytes",
),
"vstd4_depth": ("verifier.core.depth", "vstd4_depth"),
+ "establish_vstd4": ("verifier.core.depth", "establish_vstd4"),
+ "build_evidence_bound_vstd4_receipt": (
+ "verifier.core.depth",
+ "build_evidence_bound_vstd4_receipt",
+ ),
+ "claim_binding_from_dict": ("verifier.core.depth", "claim_binding_from_dict"),
+ "recheck_evidence_bound_vstd4_receipt": (
+ "verifier.core.depth",
+ "recheck_evidence_bound_vstd4_receipt",
+ ),
"require_vstd5_entry": ("verifier.core.depth", "require_vstd5_entry"),
+ "BoundProposition": ("verifier.core.evidence", "BoundProposition"),
+ "EvidenceBounds": ("verifier.core.evidence", "EvidenceBounds"),
+ "EvidenceStore": ("verifier.core.evidence", "EvidenceStore"),
+ "EvidenceBindingError": ("verifier.core.evidence", "EvidenceBindingError"),
+ "MechanismDecision": ("verifier.core.evidence", "MechanismDecision"),
+ "MechanismOutcome": ("verifier.core.evidence", "MechanismOutcome"),
+ "VerificationSession": ("verifier.core.evidence", "VerificationSession"),
+ "WitnessBundle": ("verifier.core.witness", "WitnessBundle"),
+ "assess_witness_corroboration": (
+ "verifier.core.witness",
+ "assess_witness_corroboration",
+ ),
+ "build_vstd5_receipt": ("verifier.core.witness", "build_vstd5_receipt"),
+ "recheck_vstd5_receipt": ("verifier.core.witness", "recheck_vstd5_receipt"),
+ "AssuranceLedger": ("verifier.data.assurance", "AssuranceLedger"),
+ "ObligationCoordinate": ("verifier.data.assurance", "ObligationCoordinate"),
+ "recheck_assurance_log": (
+ "verifier.data.assurance",
+ "recheck_assurance_log",
+ ),
+ "ProvenanceHypergraph": ("verifier.data.models", "ProvenanceHypergraph"),
+ "establish_graph_level": (
+ "verifier.data.graph_level",
+ "establish_graph_level",
+ ),
+ "build_evidence_bound_graph_level_record": (
+ "verifier.data.graph_level",
+ "build_evidence_bound_graph_level_record",
+ ),
+ "graph_collection_binding_digest": (
+ "verifier.data.graph_level",
+ "graph_collection_binding_digest",
+ ),
+ "recheck_evidence_bound_graph_level_record": (
+ "verifier.data.graph_level",
+ "recheck_evidence_bound_graph_level_record",
+ ),
}
+# Supported names remain in _LAZY_EXPORTS while deprecated. Each entry records the
+# first release carrying the warning and the supported replacement.
+_API_DEPRECATIONS: dict[str, tuple[str, str]] = {}
+
__all__ = list(_LAZY_EXPORTS)
@@ -32,6 +102,14 @@ def __getattr__(name: str) -> Any:
target = _LAZY_EXPORTS.get(name)
if target is None:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+ deprecation = _API_DEPRECATIONS.get(name)
+ if deprecation is not None:
+ since, replacement = deprecation
+ warnings.warn(
+ f"verifier.{name} is deprecated since {since}; use {replacement}",
+ DeprecationWarning,
+ stacklevel=2,
+ )
module_name, attribute_name = target
value = getattr(import_module(module_name), attribute_name)
globals()[name] = value
@@ -43,6 +121,15 @@ def __dir__() -> list[str]:
if TYPE_CHECKING:
+ from verifier.artifact_control import (
+ ArtifactControlError as ArtifactControlError,
+ ArtifactVerification as ArtifactVerification,
+ freeze_artifact as freeze_artifact,
+ seal_artifact as seal_artifact,
+ thaw_artifact as thaw_artifact,
+ thawed_artifact_status as thawed_artifact_status,
+ verify_frozen_artifact as verify_frozen_artifact,
+ )
from verifier.core.checker import VerificationVerdict as VerificationVerdict
from verifier.core.geometry import VerificationGeometry as VerificationGeometry
from verifier.core.certificate import (
@@ -50,9 +137,40 @@ def __dir__() -> list[str]:
certificate_from_canonical_bytes as certificate_from_canonical_bytes,
)
from verifier.core.depth import (
+ build_evidence_bound_vstd4_receipt as build_evidence_bound_vstd4_receipt,
+ claim_binding_from_dict as claim_binding_from_dict,
+ establish_vstd4 as establish_vstd4,
+ recheck_evidence_bound_vstd4_receipt as recheck_evidence_bound_vstd4_receipt,
require_vstd5_entry as require_vstd5_entry,
vstd4_depth as vstd4_depth,
)
+ from verifier.core.evidence import (
+ BoundProposition as BoundProposition,
+ EvidenceBindingError as EvidenceBindingError,
+ EvidenceBounds as EvidenceBounds,
+ EvidenceStore as EvidenceStore,
+ MechanismDecision as MechanismDecision,
+ MechanismOutcome as MechanismOutcome,
+ VerificationSession as VerificationSession,
+ )
+ from verifier.core.witness import (
+ WitnessBundle as WitnessBundle,
+ assess_witness_corroboration as assess_witness_corroboration,
+ build_vstd5_receipt as build_vstd5_receipt,
+ recheck_vstd5_receipt as recheck_vstd5_receipt,
+ )
+ from verifier.data.assurance import (
+ AssuranceLedger as AssuranceLedger,
+ ObligationCoordinate as ObligationCoordinate,
+ recheck_assurance_log as recheck_assurance_log,
+ )
+ from verifier.data.graph_level import (
+ build_evidence_bound_graph_level_record as build_evidence_bound_graph_level_record,
+ establish_graph_level as establish_graph_level,
+ graph_collection_binding_digest as graph_collection_binding_digest,
+ recheck_evidence_bound_graph_level_record as recheck_evidence_bound_graph_level_record,
+ )
+ from verifier.data.models import ProvenanceHypergraph as ProvenanceHypergraph
from verifier.core.receipt import (
VstdReceipt as VstdReceipt,
compute_canonical_digest as compute_canonical_digest,
diff --git a/src/verifier/artifact_control/__init__.py b/src/verifier/artifact_control/__init__.py
new file mode 100644
index 0000000..b5c68f8
--- /dev/null
+++ b/src/verifier/artifact_control/__init__.py
@@ -0,0 +1,1240 @@
+"""Artifact preservation, self-closing seals, and copy-on-write thawing.
+
+Terminology: American Standard Code for Information Interchange (ASCII);
+identifier (ID); JavaScript Object Notation (JSON); Privacy-Enhanced Mail (PEM); Secure Hash
+Algorithm 256-bit (SHA-256); Secure Hash Algorithm 3 256-bit (SHA3-256);
+Verifier Standard (VSTD).
+
+Freezing preserves exact bytes. Sealing is a separate action that closes a
+verified freeze manifest with a finite Ed25519 construction; it is not
+encryption. Thawing creates a mutable descendant and never changes the frozen
+parent. A valid seal establishes only artifact identity and closure under the
+declared mechanisms. It does not establish correctness, freshness, ownership,
+authorization, or actor reputation.
+"""
+
+from __future__ import annotations
+
+import base64
+import hashlib
+import json
+import os
+import re
+import shutil
+import stat
+import tempfile
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Any, Iterable, Mapping
+
+
+FREEZE_SCHEMA = "VSTD-ARTIFACT-FREEZE-1"
+SEAL_SCHEMA = "VSTD-ARTIFACT-SEAL-1"
+SEAL_PAYLOAD_SCHEMA = "VSTD-ARTIFACT-SEAL-CLOSURE-1"
+THAW_SCHEMA = "VSTD-ARTIFACT-THAW-1"
+CANONICALIZATION = "VSTD-ARTIFACT-CANONICAL-1"
+SIGNATURE_ALGORITHM = "Ed25519"
+_DIGEST_NAMES = ("sha256", "sha3-256")
+_HEX_256 = re.compile(r"[0-9a-f]{64}\Z")
+_DUAL_ID = re.compile(
+ r"vstd-(?:artifact|content|freeze|seal|thaw)-1:sha256:[0-9a-f]{64}:"
+ r"sha3-256:[0-9a-f]{64}\Z"
+)
+_MECHANISM = {
+ "name": "vstd-reference-freezer",
+ "version": "1",
+ "canonicalization": CANONICALIZATION,
+ "write_guard": "PORTABLE_READ_ONLY_TREE",
+}
+
+
+class ArtifactControlError(ValueError):
+ """Raised when an artifact-control action cannot fail closed."""
+
+
+@dataclass(frozen=True)
+class ArtifactVerification:
+ """Result of independently recomputing a frozen artifact and its seals."""
+
+ state: str
+ artifact_id: str | None
+ content_id: str | None
+ freeze_id: str | None
+ freeze_valid: bool
+ guard_valid: bool
+ valid_seal_ids: tuple[str, ...]
+ key_ids: tuple[str, ...]
+ external_anchor: str
+ errors: tuple[str, ...]
+ warnings: tuple[str, ...]
+
+ @property
+ def sealed(self) -> bool:
+ return self.state == "SEALED"
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "state": self.state,
+ "artifact_id": self.artifact_id,
+ "content_id": self.content_id,
+ "freeze_id": self.freeze_id,
+ "freeze_valid": self.freeze_valid,
+ "guard_valid": self.guard_valid,
+ "valid_seal_ids": list(self.valid_seal_ids),
+ "key_ids": list(self.key_ids),
+ "external_anchor": self.external_anchor,
+ "errors": list(self.errors),
+ "warnings": list(self.warnings),
+ }
+
+
+def _canonical_bytes(value: Any) -> bytes:
+ return json.dumps(
+ value,
+ ensure_ascii=False,
+ allow_nan=False,
+ sort_keys=True,
+ separators=(",", ":"),
+ ).encode("utf-8")
+
+
+def _digests_bytes(value: bytes) -> dict[str, str]:
+ return {
+ "sha256": hashlib.sha256(value).hexdigest(),
+ "sha3-256": hashlib.sha3_256(value).hexdigest(),
+ }
+
+
+def _digests_file(path: Path) -> tuple[dict[str, str], int]:
+ sha256 = hashlib.sha256()
+ sha3 = hashlib.sha3_256()
+ size = 0
+ with path.open("rb") as stream:
+ for block in iter(lambda: stream.read(1024 * 1024), b""):
+ size += len(block)
+ sha256.update(block)
+ sha3.update(block)
+ return {"sha256": sha256.hexdigest(), "sha3-256": sha3.hexdigest()}, size
+
+
+def _identity(prefix: str, value: bytes) -> str:
+ digests = _digests_bytes(value)
+ return (
+ f"{prefix}:sha256:{digests['sha256']}:"
+ f"sha3-256:{digests['sha3-256']}"
+ )
+
+
+def _strict_object(
+ value: Any, expected: set[str], label: str
+) -> Mapping[str, Any]:
+ if not isinstance(value, Mapping):
+ raise ArtifactControlError(f"{label} must be an object")
+ actual = set(value)
+ if actual != expected:
+ missing = sorted(expected - actual)
+ extra = sorted(actual - expected)
+ detail = []
+ if missing:
+ detail.append("missing " + ", ".join(missing))
+ if extra:
+ detail.append("unknown " + ", ".join(extra))
+ raise ArtifactControlError(f"{label} has invalid fields: {'; '.join(detail)}")
+ return value
+
+
+def _parse_json_object(value: str | bytes, label: str, path: Path) -> dict[str, Any]:
+ def reject_duplicates(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
+ result: dict[str, Any] = {}
+ for key, value in pairs:
+ if key in result:
+ raise ArtifactControlError(f"{label} contains duplicate key {key!r}")
+ result[key] = value
+ return result
+
+ def reject_constant(value: str) -> None:
+ raise ArtifactControlError(f"{label} contains non-finite number {value}")
+
+ try:
+ parsed = json.loads(
+ value,
+ object_pairs_hook=reject_duplicates,
+ parse_constant=reject_constant,
+ )
+ except (json.JSONDecodeError, UnicodeDecodeError) as exc:
+ raise ArtifactControlError(f"{label} is not readable JSON: {path}") from exc
+ if not isinstance(parsed, dict):
+ raise ArtifactControlError(f"{label} must contain one JSON object")
+ return parsed
+
+
+def _read_json_object(path: Path, label: str) -> dict[str, Any]:
+ """Read a generic JSON coordinate, retaining accepted read-only alias behavior."""
+
+ try:
+ value = path.read_text(encoding="utf-8")
+ except OSError as exc:
+ raise ArtifactControlError(f"cannot read {label}: {path}") from exc
+ return _parse_json_object(value, label, path)
+
+
+def _write_json(path: Path, value: Mapping[str, Any]) -> None:
+ path.write_text(
+ json.dumps(value, ensure_ascii=False, allow_nan=False, indent=2, sort_keys=True)
+ + "\n",
+ encoding="utf-8",
+ newline="\n",
+ )
+
+
+def _write_json_exclusive(path: Path, value: Mapping[str, Any]) -> None:
+ created = False
+ try:
+ with path.open("x", encoding="utf-8", newline="\n") as stream:
+ created = True
+ stream.write(
+ json.dumps(
+ value, ensure_ascii=False, allow_nan=False, indent=2, sort_keys=True
+ )
+ + "\n"
+ )
+ except Exception:
+ if created:
+ _remove_created_entry(path)
+ raise
+
+
+def _absolute_lexical(path: str | Path) -> Path:
+ """Resolve parent directories but preserve the final filesystem entry."""
+
+ absolute = Path(os.path.abspath(os.fspath(path)))
+ return absolute.parent.resolve() / absolute.name
+
+
+def _lexists(path: Path) -> bool:
+ """Report whether the lexical entry exists, including a dangling symbolic link."""
+
+ return os.path.lexists(path)
+
+
+def _is_link_like(entry: os.stat_result) -> bool:
+ """Identify symbolic links and Windows reparse-point aliases from lexical metadata."""
+
+ if stat.S_ISLNK(entry.st_mode):
+ return True
+ reparse_flag = getattr(stat, "FILE_ATTRIBUTE_REPARSE_POINT", 0)
+ return bool(getattr(entry, "st_file_attributes", 0) & reparse_flag)
+
+
+def _internal_entry_stat(path: Path, label: str, expected: str) -> os.stat_result:
+ """Require one authoritative bundle member to have an ordinary lexical type."""
+
+ try:
+ entry = path.lstat()
+ except OSError as exc:
+ raise ArtifactControlError(f"cannot inspect {label}: {path}") from exc
+ if _is_link_like(entry):
+ raise ArtifactControlError(f"{label} must not be a symbolic link or reparse point")
+ matches = stat.S_ISREG(entry.st_mode) if expected == "file" else stat.S_ISDIR(entry.st_mode)
+ if not matches:
+ raise ArtifactControlError(f"{label} must be an ordinary {expected}: {path}")
+ return entry
+
+
+def _read_internal_regular_bytes(path: Path, label: str) -> bytes:
+ """Capture one ordinary internal file snapshot without following its final entry."""
+
+ before = _internal_entry_stat(path, label, "file")
+ flags = os.O_RDONLY | getattr(os, "O_BINARY", 0) | getattr(os, "O_CLOEXEC", 0)
+ flags |= getattr(os, "O_NOFOLLOW", 0)
+ try:
+ descriptor = os.open(path, flags)
+ except OSError as exc:
+ raise ArtifactControlError(f"cannot open {label}: {path}") from exc
+ try:
+ opened = os.fstat(descriptor)
+ if not stat.S_ISREG(opened.st_mode) or (
+ opened.st_dev,
+ opened.st_ino,
+ ) != (before.st_dev, before.st_ino):
+ raise ArtifactControlError(f"{label} changed during lexical classification")
+ blocks: list[bytes] = []
+ while True:
+ block = os.read(descriptor, 1024 * 1024)
+ if not block:
+ break
+ blocks.append(block)
+ return b"".join(blocks)
+ finally:
+ os.close(descriptor)
+
+
+def _read_internal_json_object(path: Path, label: str) -> tuple[dict[str, Any], bytes]:
+ raw = _read_internal_regular_bytes(path, label)
+ return _parse_json_object(raw, label, path), raw
+
+
+def _require_absent(path: Path, label: str) -> None:
+ if _lexists(path):
+ raise ArtifactControlError(f"{label} already exists: {path}")
+
+
+def _remove_created_entry(path: Path) -> None:
+ """Remove one invocation-owned lexical entry without traversing a replacement link."""
+
+ try:
+ entry = path.lstat()
+ except FileNotFoundError:
+ return
+ if _is_link_like(entry):
+ if stat.S_ISDIR(entry.st_mode) and not stat.S_ISLNK(entry.st_mode):
+ path.rmdir()
+ else:
+ path.unlink()
+ elif stat.S_ISDIR(entry.st_mode):
+ shutil.rmtree(path, ignore_errors=True)
+ else:
+ try:
+ path.chmod(entry.st_mode | stat.S_IWUSR)
+ except OSError:
+ pass
+ path.unlink()
+
+
+def _relative_posix(path: Path, root: Path) -> str:
+ return path.relative_to(root).as_posix()
+
+
+def _source_entries(source: Path) -> list[dict[str, Any]]:
+ try:
+ source_entry = source.lstat()
+ except OSError as exc:
+ raise ArtifactControlError(f"cannot inspect artifact source: {source}") from exc
+ if _is_link_like(source_entry):
+ raise ArtifactControlError(
+ "symbolic links or reparse-point aliases are not accepted as frozen artifacts"
+ )
+ if stat.S_ISREG(source_entry.st_mode):
+ digests, size = _digests_file(source)
+ return [{"kind": "file", "path": ".", "byte_size": size, "digests": digests}]
+ if not stat.S_ISDIR(source_entry.st_mode):
+ raise ArtifactControlError("artifact source must be a regular file or directory")
+
+ entries: list[dict[str, Any]] = []
+ for path in sorted(source.rglob("*"), key=lambda item: item.relative_to(source).as_posix()):
+ try:
+ entry = path.lstat()
+ except OSError as exc:
+ raise ArtifactControlError(
+ f"cannot inspect frozen artifact entry: {_relative_posix(path, source)}"
+ ) from exc
+ if _is_link_like(entry):
+ raise ArtifactControlError(
+ "symbolic links or reparse-point aliases are not accepted as frozen artifacts: "
+ f"{_relative_posix(path, source)}"
+ )
+ relative = _relative_posix(path, source)
+ if stat.S_ISDIR(entry.st_mode):
+ entries.append({"kind": "directory", "path": relative})
+ elif stat.S_ISREG(entry.st_mode):
+ digests, size = _digests_file(path)
+ entries.append(
+ {
+ "kind": "file",
+ "path": relative,
+ "byte_size": size,
+ "digests": digests,
+ }
+ )
+ else:
+ raise ArtifactControlError(f"special filesystem object is not supported: {relative}")
+ return entries
+
+
+def _descriptor(kind: str, media_type: str, entries: list[dict[str, Any]]) -> dict[str, Any]:
+ return {
+ "canonicalization": CANONICALIZATION,
+ "artifact_kind": kind,
+ "media_type": media_type,
+ "entries": entries,
+ }
+
+
+def _content_descriptor(kind: str, entries: list[dict[str, Any]]) -> dict[str, Any]:
+ return {
+ "canonicalization": CANONICALIZATION,
+ "artifact_kind": kind,
+ "entries": entries,
+ }
+
+
+def _make_read_only(path: Path) -> None:
+ path.chmod(path.stat().st_mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH))
+
+
+def _make_writable(path: Path) -> None:
+ path.chmod(path.stat().st_mode | stat.S_IWUSR)
+
+
+def _is_read_only(path: Path) -> bool:
+ return not bool(path.lstat().st_mode & (stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH))
+
+
+def _set_bundle_guard(bundle: Path) -> None:
+ payload = bundle / "payload"
+ if payload.is_file():
+ _make_read_only(payload)
+ else:
+ for path in payload.rglob("*"):
+ if path.is_file():
+ _make_read_only(path)
+ for path in sorted(
+ (item for item in payload.rglob("*") if item.is_dir()),
+ key=lambda item: len(item.parts),
+ reverse=True,
+ ):
+ _make_read_only(path)
+ _make_read_only(payload)
+ _make_read_only(bundle / "freeze.json")
+
+
+def _guard_errors(bundle: Path) -> list[str]:
+ errors: list[str] = []
+ guarded = [bundle / "freeze.json"]
+ payload = bundle / "payload"
+ if payload.is_file():
+ guarded.append(payload)
+ elif payload.is_dir():
+ guarded.append(payload)
+ guarded.extend(
+ path for path in payload.rglob("*") if path.is_file() or path.is_dir()
+ )
+ for path in guarded:
+ if path.exists() and not _is_read_only(path):
+ errors.append(f"write guard is not active: {path.relative_to(bundle).as_posix()}")
+ return errors
+
+
+def _validate_entries(entries: Any) -> list[dict[str, Any]]:
+ if not isinstance(entries, list):
+ raise ArtifactControlError("freeze.entries must be an array")
+ validated: list[dict[str, Any]] = []
+ seen: set[str] = set()
+ for index, entry in enumerate(entries):
+ if not isinstance(entry, dict):
+ raise ArtifactControlError(f"freeze.entries[{index}] must be an object")
+ kind = entry.get("kind")
+ expected = {"kind", "path"} if kind == "directory" else {
+ "kind",
+ "path",
+ "byte_size",
+ "digests",
+ }
+ _strict_object(entry, expected, f"freeze.entries[{index}]")
+ path = entry["path"]
+ if kind not in {"file", "directory"} or not isinstance(path, str):
+ raise ArtifactControlError(f"freeze.entries[{index}] has invalid kind or path")
+ if path in seen:
+ raise ArtifactControlError(f"freeze.entries contains duplicate path {path!r}")
+ seen.add(path)
+ if path != ".":
+ candidate = Path(path)
+ if candidate.is_absolute() or ".." in candidate.parts or candidate.as_posix() != path:
+ raise ArtifactControlError(f"freeze.entries[{index}] path is not portable")
+ if kind == "file":
+ if type(entry["byte_size"]) is not int or entry["byte_size"] < 0:
+ raise ArtifactControlError(f"freeze.entries[{index}].byte_size is invalid")
+ digests = _strict_object(
+ entry["digests"], set(_DIGEST_NAMES), f"freeze.entries[{index}].digests"
+ )
+ for name in _DIGEST_NAMES:
+ value = digests[name]
+ if not isinstance(value, str) or _HEX_256.fullmatch(value) is None:
+ raise ArtifactControlError(
+ f"freeze.entries[{index}].digests.{name} is invalid"
+ )
+ validated.append(dict(entry))
+ order = sorted(validated, key=lambda item: item["path"])
+ if order != validated:
+ raise ArtifactControlError("freeze.entries must be sorted by portable path")
+ return validated
+
+
+def _load_freeze_snapshot(bundle: Path) -> tuple[dict[str, Any], bytes]:
+ freeze, freeze_bytes = _read_internal_json_object(
+ bundle / "freeze.json", "freeze manifest"
+ )
+ _strict_object(
+ freeze,
+ {
+ "schema_version",
+ "artifact_id",
+ "content_id",
+ "artifact_kind",
+ "media_type",
+ "entries",
+ "lineage",
+ "bound_contexts",
+ "mechanism",
+ "freeze_id",
+ },
+ "freeze manifest",
+ )
+ if freeze["schema_version"] != FREEZE_SCHEMA:
+ raise ArtifactControlError(f"unsupported freeze schema {freeze['schema_version']!r}")
+ if freeze["artifact_kind"] not in {"file", "directory"}:
+ raise ArtifactControlError("freeze.artifact_kind is invalid")
+ if not isinstance(freeze["media_type"], str) or not freeze["media_type"]:
+ raise ArtifactControlError("freeze.media_type must be a nonempty string")
+ freeze["entries"] = _validate_entries(freeze["entries"])
+ if freeze["artifact_kind"] == "file" and not (
+ len(freeze["entries"]) == 1
+ and freeze["entries"][0]["kind"] == "file"
+ and freeze["entries"][0]["path"] == "."
+ ):
+ raise ArtifactControlError("a frozen file must have exactly one '.' file entry")
+ if freeze["artifact_kind"] == "directory" and any(
+ entry["path"] == "." for entry in freeze["entries"]
+ ):
+ raise ArtifactControlError("a frozen directory must not contain a '.' entry")
+ for name in ("artifact_id", "content_id", "freeze_id"):
+ if not isinstance(freeze[name], str) or _DUAL_ID.fullmatch(freeze[name]) is None:
+ raise ArtifactControlError(f"freeze.{name} is invalid")
+ if not isinstance(freeze["lineage"], list) or not all(
+ isinstance(item, str) for item in freeze["lineage"]
+ ):
+ raise ArtifactControlError("freeze.lineage must be an array of artifact identifiers")
+ if len(set(freeze["lineage"])) != len(freeze["lineage"]):
+ raise ArtifactControlError("freeze.lineage must not contain duplicates")
+ if any(
+ not item.startswith("vstd-artifact-1:") or _DUAL_ID.fullmatch(item) is None
+ for item in freeze["lineage"]
+ ):
+ raise ArtifactControlError("freeze.lineage contains a non-artifact identifier")
+ contexts = freeze["bound_contexts"]
+ if not isinstance(contexts, list) or not all(isinstance(item, str) for item in contexts):
+ raise ArtifactControlError("freeze.bound_contexts must be an array of artifact identifiers")
+ if len(set(contexts)) != len(contexts):
+ raise ArtifactControlError("freeze.bound_contexts must not contain duplicates")
+ if any(
+ not item.startswith("vstd-artifact-1:") or _DUAL_ID.fullmatch(item) is None
+ for item in contexts
+ ):
+ raise ArtifactControlError("freeze.bound_contexts contains a non-artifact identifier")
+ mechanism = _strict_object(
+ freeze["mechanism"],
+ {"name", "version", "canonicalization", "write_guard"},
+ "freeze.mechanism",
+ )
+ if mechanism != _MECHANISM:
+ raise ArtifactControlError("freeze.mechanism is unsupported")
+ return freeze, freeze_bytes
+
+
+def _load_freeze(bundle: Path) -> dict[str, Any]:
+ return _load_freeze_snapshot(bundle)[0]
+
+
+def _stable_freeze(freeze: Mapping[str, Any]) -> dict[str, Any]:
+ return {key: freeze[key] for key in freeze if key != "freeze_id"}
+
+
+def _observed_bundle_entries(bundle: Path, kind: str) -> list[dict[str, Any]]:
+ payload = bundle / "payload"
+ if kind == "file":
+ _internal_entry_stat(payload, "frozen file payload", "file")
+ else:
+ _internal_entry_stat(payload, "frozen directory payload", "directory")
+ return _source_entries(payload)
+
+
+def _seal_dependencies() -> tuple[Any, Any, Any, Any]:
+ try:
+ from cryptography.exceptions import InvalidSignature
+ from cryptography.hazmat.primitives import serialization
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import (
+ Ed25519PrivateKey,
+ Ed25519PublicKey,
+ )
+ except ImportError as exc:
+ raise ArtifactControlError(
+ "artifact sealing requires verifier-standard[seal]; no substitute was used"
+ ) from exc
+ return InvalidSignature, serialization, Ed25519PrivateKey, Ed25519PublicKey
+
+
+def _seal_projection(envelope: Mapping[str, Any]) -> dict[str, Any]:
+ """Return the finite self-closing projection with both closure holes empty."""
+
+ projected = dict(envelope)
+ projected["signature_base64"] = None
+ projected["seal_id"] = None
+ return projected
+
+
+def _seal_identity_projection(envelope: Mapping[str, Any]) -> dict[str, Any]:
+ projected = dict(envelope)
+ projected["seal_id"] = None
+ return projected
+
+
+def _seal_payload(
+ freeze: Mapping[str, Any], freeze_bytes: bytes, key_id: str
+) -> dict[str, Any]:
+ return {
+ "schema_version": SEAL_PAYLOAD_SCHEMA,
+ "artifact_id": freeze["artifact_id"],
+ "content_id": freeze["content_id"],
+ "freeze_id": freeze["freeze_id"],
+ "freeze_manifest_digests": _digests_bytes(freeze_bytes),
+ "key_id": key_id,
+ "algorithm": SIGNATURE_ALGORITHM,
+ "closure_rule": (
+ "canonicalize the entire envelope with signature_base64 and seal_id set to null; "
+ "verify the signature; then canonicalize with only seal_id null and recompute seal_id"
+ ),
+ }
+
+
+def _seal_file_paths(bundle: Path) -> list[Path]:
+ seals = bundle / "seals"
+ if not _lexists(seals):
+ return []
+ _internal_entry_stat(seals, "seals container", "directory")
+ paths = sorted(seals.iterdir(), key=lambda path: path.name)
+ for path in paths:
+ if path.suffix != ".json":
+ raise ArtifactControlError("seals directory contains an unsupported entry")
+ _internal_entry_stat(path, f"seal member {path.name}", "file")
+ return paths
+
+
+def freeze_artifact(
+ source: str | Path,
+ bundle: str | Path,
+ *,
+ media_type: str = "application/octet-stream",
+ parent_bundles: Iterable[str | Path] = (),
+ context_bundles: Iterable[str | Path] = (),
+) -> dict[str, Any]:
+ """Preserve exact bytes in a new guarded bundle without creating a seal."""
+
+ source_path = _absolute_lexical(source)
+ bundle_path = _absolute_lexical(bundle)
+ if not isinstance(media_type, str) or not media_type:
+ raise ArtifactControlError("media_type must be a nonempty string")
+ _require_absent(bundle_path, "freeze bundle")
+ entries = _source_entries(source_path)
+ if source_path.is_dir():
+ try:
+ bundle_path.relative_to(source_path)
+ except ValueError:
+ pass
+ else:
+ raise ArtifactControlError("freeze bundle cannot be created inside its source directory")
+
+ lineage: list[str] = []
+ for parent in parent_bundles:
+ result = verify_frozen_artifact(parent, require_seal=True)
+ if not result.sealed or result.artifact_id is None:
+ raise ArtifactControlError(f"parent bundle is not cleanly sealed: {parent}")
+ lineage.append(result.artifact_id)
+ contexts: list[str] = []
+ for context in context_bundles:
+ result = verify_frozen_artifact(context, require_seal=True)
+ if not result.sealed or result.artifact_id is None:
+ raise ArtifactControlError(f"context bundle is not cleanly sealed: {context}")
+ contexts.append(result.artifact_id)
+ if len(set(lineage)) != len(lineage) or len(set(contexts)) != len(contexts):
+ raise ArtifactControlError("duplicate parent or context bundles do not add assurance")
+
+ kind = "file" if source_path.is_file() else "directory"
+ descriptor = _descriptor(kind, media_type, entries)
+ artifact_id = _identity("vstd-artifact-1", _canonical_bytes(descriptor))
+ content_id = _identity(
+ "vstd-content-1", _canonical_bytes(_content_descriptor(kind, entries))
+ )
+ freeze: dict[str, Any] = {
+ "schema_version": FREEZE_SCHEMA,
+ "artifact_id": artifact_id,
+ "content_id": content_id,
+ "artifact_kind": kind,
+ "media_type": media_type,
+ "entries": entries,
+ "lineage": sorted(lineage),
+ "bound_contexts": sorted(contexts),
+ "mechanism": dict(_MECHANISM),
+ }
+ freeze["freeze_id"] = _identity("vstd-freeze-1", _canonical_bytes(freeze))
+
+ bundle_path.parent.mkdir(parents=True, exist_ok=True)
+ staging = Path(
+ tempfile.mkdtemp(prefix=f".{bundle_path.name}.freeze-", dir=bundle_path.parent)
+ )
+ try:
+ payload = staging / "payload"
+ if kind == "file":
+ shutil.copyfile(source_path, payload)
+ else:
+ shutil.copytree(source_path, payload)
+ _write_json(staging / "freeze.json", freeze)
+ _set_bundle_guard(staging)
+ observed = _observed_bundle_entries(staging, kind)
+ if observed != entries:
+ raise ArtifactControlError("preserved payload differs from the source inventory")
+ staging.replace(bundle_path)
+ except Exception:
+ if staging.exists():
+ for path in sorted(staging.rglob("*"), key=lambda item: len(item.parts), reverse=True):
+ try:
+ _make_writable(path)
+ except OSError:
+ pass
+ shutil.rmtree(staging, ignore_errors=True)
+ raise
+ return freeze
+
+
+def seal_artifact(bundle: str | Path, private_key: str | Path) -> dict[str, Any]:
+ """Add one deterministic, readable, self-closing Ed25519 seal."""
+
+ bundle_path = Path(bundle).resolve()
+ verification = verify_frozen_artifact(bundle_path, require_seal=False)
+ if verification.state not in {"FROZEN_UNSEALED", "SEALED"}:
+ raise ArtifactControlError("artifact must be cleanly frozen before it can be sealed")
+ freeze, freeze_bytes = _load_freeze_snapshot(bundle_path)
+
+ _, serialization, Ed25519PrivateKey, _ = _seal_dependencies()
+ key_bytes = Path(private_key).read_bytes()
+ try:
+ key = serialization.load_pem_private_key(key_bytes, password=None)
+ except (TypeError, ValueError) as exc:
+ raise ArtifactControlError("seal private key is not a readable unencrypted PEM key") from exc
+ if not isinstance(key, Ed25519PrivateKey):
+ raise ArtifactControlError("seal private key must use Ed25519")
+ public_raw = key.public_key().public_bytes(
+ encoding=serialization.Encoding.Raw,
+ format=serialization.PublicFormat.Raw,
+ )
+ key_id = "vstd-seal-key-1:sha256:" + hashlib.sha256(public_raw).hexdigest()
+ payload = _seal_payload(freeze, freeze_bytes, key_id)
+ envelope: dict[str, Any] = {
+ "schema_version": SEAL_SCHEMA,
+ "seal_payload": payload,
+ "public_key_base64": base64.b64encode(public_raw).decode("ascii"),
+ "signature_base64": None,
+ "seal_id": None,
+ }
+ signature = key.sign(_canonical_bytes(_seal_projection(envelope)))
+ envelope["signature_base64"] = base64.b64encode(signature).decode("ascii")
+ envelope["seal_id"] = _identity(
+ "vstd-seal-1", _canonical_bytes(_seal_identity_projection(envelope))
+ )
+
+ seals = bundle_path / "seals"
+ seals_created = False
+ if _lexists(seals):
+ _internal_entry_stat(seals, "seals container", "directory")
+ else:
+ try:
+ seals.mkdir()
+ seals_created = True
+ except FileExistsError:
+ _internal_entry_stat(seals, "seals container", "directory")
+ filename = hashlib.sha256(_canonical_bytes(envelope)).hexdigest() + ".json"
+ target = seals / filename
+ target_created = False
+ try:
+ if _lexists(target):
+ existing, _ = _read_internal_json_object(target, "seal")
+ if existing != envelope:
+ raise ArtifactControlError("existing seal filename contains different bytes")
+ return envelope
+ _write_json_exclusive(target, envelope)
+ target_created = True
+ _make_read_only(target)
+ final = verify_frozen_artifact(bundle_path, require_seal=True)
+ if not final.sealed or envelope["seal_id"] not in final.valid_seal_ids:
+ raise ArtifactControlError(
+ f"seal did not produce a cleanly sealed artifact; observed {final.state}"
+ )
+ return envelope
+ except Exception:
+ if target_created:
+ _remove_created_entry(target)
+ if seals_created:
+ try:
+ if _is_link_like(seals.lstat()):
+ _remove_created_entry(seals)
+ else:
+ seals.rmdir()
+ except OSError:
+ pass
+ raise
+
+
+def _verify_seal(
+ seal: Mapping[str, Any], freeze: Mapping[str, Any], freeze_bytes: bytes
+) -> tuple[str, str]:
+ InvalidSignature, serialization, _, Ed25519PublicKey = _seal_dependencies()
+ _strict_object(
+ seal,
+ {"schema_version", "seal_payload", "public_key_base64", "signature_base64", "seal_id"},
+ "seal",
+ )
+ if seal["schema_version"] != SEAL_SCHEMA:
+ raise ArtifactControlError(f"unsupported seal schema {seal['schema_version']!r}")
+ public_encoded = seal["public_key_base64"]
+ signature_encoded = seal["signature_base64"]
+ if not isinstance(public_encoded, str) or not isinstance(signature_encoded, str):
+ raise ArtifactControlError("seal public key and signature must be base64 strings")
+ try:
+ public_raw = base64.b64decode(public_encoded, validate=True)
+ signature = base64.b64decode(signature_encoded, validate=True)
+ except ValueError as exc:
+ raise ArtifactControlError("seal public key or signature is not canonical base64") from exc
+ if len(public_raw) != 32 or len(signature) != 64:
+ raise ArtifactControlError("seal public key or signature has the wrong length")
+ if (
+ base64.b64encode(public_raw).decode("ascii") != public_encoded
+ or base64.b64encode(signature).decode("ascii") != signature_encoded
+ ):
+ raise ArtifactControlError("seal public key or signature is not canonical base64")
+ key_id = "vstd-seal-key-1:sha256:" + hashlib.sha256(public_raw).hexdigest()
+ expected_payload = _seal_payload(freeze, freeze_bytes, key_id)
+ if seal["seal_payload"] != expected_payload:
+ raise ArtifactControlError("seal payload does not close the current freeze manifest")
+ seal_id = seal["seal_id"]
+ if not isinstance(seal_id, str) or seal_id != _identity(
+ "vstd-seal-1", _canonical_bytes(_seal_identity_projection(seal))
+ ):
+ raise ArtifactControlError("seal identity does not close the signature-bearing envelope")
+ try:
+ Ed25519PublicKey.from_public_bytes(public_raw).verify(
+ signature, _canonical_bytes(_seal_projection(seal))
+ )
+ except InvalidSignature as exc:
+ raise ArtifactControlError("self-closing seal signature did not verify") from exc
+ return seal_id, key_id
+
+
+def verify_frozen_artifact(
+ bundle: str | Path,
+ *,
+ expected_artifact_id: str | None = None,
+ expected_key_id: str | None = None,
+ require_seal: bool = True,
+) -> ArtifactVerification:
+ """Recompute preserved bytes, write guards, closure, and optional external anchors."""
+
+ bundle_path = Path(bundle).resolve()
+ errors: list[str] = []
+ warnings: list[str] = []
+ artifact_id: str | None = None
+ content_id: str | None = None
+ freeze_id: str | None = None
+ valid_seals: list[str] = []
+ key_ids: list[str] = []
+ invalid_seals = 0
+ freeze_valid = False
+ guard_valid = False
+ freeze_errors: list[str] = []
+ guard_errors: list[str] = []
+ seal_errors: list[str] = []
+ anchor_errors: list[str] = []
+ try:
+ if not bundle_path.is_dir() or bundle_path.is_symlink():
+ raise ArtifactControlError("artifact bundle must be a regular directory")
+ allowed = {"payload", "freeze.json", "seals"}
+ unexpected = sorted(path.name for path in bundle_path.iterdir() if path.name not in allowed)
+ if unexpected:
+ raise ArtifactControlError(
+ "artifact bundle contains unsupported top-level entries: "
+ + ", ".join(unexpected)
+ )
+ freeze, freeze_bytes = _load_freeze_snapshot(bundle_path)
+ artifact_id = freeze["artifact_id"]
+ content_id = freeze["content_id"]
+ freeze_id = freeze["freeze_id"]
+ entries = _observed_bundle_entries(bundle_path, freeze["artifact_kind"])
+ descriptor = _descriptor(freeze["artifact_kind"], freeze["media_type"], entries)
+ observed_artifact_id = _identity("vstd-artifact-1", _canonical_bytes(descriptor))
+ observed_content_id = _identity(
+ "vstd-content-1",
+ _canonical_bytes(_content_descriptor(freeze["artifact_kind"], entries)),
+ )
+ if entries != freeze["entries"]:
+ freeze_errors.append("preserved payload inventory differs from freeze.entries")
+ if observed_artifact_id != artifact_id:
+ freeze_errors.append("preserved payload does not match artifact_id")
+ if observed_content_id != content_id:
+ freeze_errors.append("preserved payload does not match content_id")
+ expected_freeze_id = _identity(
+ "vstd-freeze-1", _canonical_bytes(_stable_freeze(freeze))
+ )
+ if freeze_id != expected_freeze_id:
+ freeze_errors.append("freeze_id does not close the freeze manifest")
+ freeze_valid = not freeze_errors
+ guard_errors.extend(_guard_errors(bundle_path))
+ guard_valid = not guard_errors
+ for seal_path in _seal_file_paths(bundle_path):
+ try:
+ seal, _ = _read_internal_json_object(seal_path, "seal")
+ seal_id, key_id = _verify_seal(seal, freeze, freeze_bytes)
+ valid_seals.append(seal_id)
+ key_ids.append(key_id)
+ if not _is_read_only(seal_path):
+ guard_errors.append(
+ f"write guard is not active: {seal_path.relative_to(bundle_path).as_posix()}"
+ )
+ except ArtifactControlError as exc:
+ invalid_seals += 1
+ seal_errors.append(f"{seal_path.name}: {exc}")
+ guard_valid = not guard_errors
+ except ArtifactControlError as exc:
+ freeze_errors.append(str(exc))
+
+ errors.extend(freeze_errors)
+ errors.extend(guard_errors)
+ errors.extend(seal_errors)
+
+ artifact_anchor = "NOT_CHECKED"
+ if expected_artifact_id is not None:
+ if artifact_id == expected_artifact_id:
+ artifact_anchor = "MATCHED"
+ else:
+ anchor_errors.append("artifact_id does not match the expected external coordinate")
+ artifact_anchor = "MISMATCH"
+ key_anchor = "NOT_CHECKED"
+ if expected_key_id is not None:
+ if expected_key_id in key_ids:
+ key_anchor = "MATCHED"
+ else:
+ anchor_errors.append("no valid seal matches the expected external key coordinate")
+ key_anchor = "MISMATCH"
+
+ if "MISMATCH" in {artifact_anchor, key_anchor}:
+ external_anchor = "MISMATCH"
+ elif artifact_anchor == key_anchor == "MATCHED":
+ external_anchor = "ARTIFACT_AND_KEY_MATCHED"
+ elif artifact_anchor == "MATCHED":
+ external_anchor = "ARTIFACT_ID_MATCHED"
+ elif key_anchor == "MATCHED":
+ external_anchor = "KEY_MATCHED"
+ else:
+ external_anchor = "NOT_CHECKED"
+ errors.extend(anchor_errors)
+
+ if freeze_errors or guard_errors or anchor_errors:
+ state = "FAIL"
+ elif invalid_seals and valid_seals:
+ state = "CONFLICTED"
+ elif errors:
+ state = "FAIL"
+ elif valid_seals:
+ state = "SEALED"
+ else:
+ state = "FROZEN_UNSEALED"
+ warnings.append("artifact identity is not seal-backed")
+ if require_seal:
+ state = "NOT_ESTABLISHED"
+ return ArtifactVerification(
+ state=state,
+ artifact_id=artifact_id,
+ content_id=content_id,
+ freeze_id=freeze_id,
+ freeze_valid=freeze_valid,
+ guard_valid=guard_valid,
+ valid_seal_ids=tuple(sorted(set(valid_seals))),
+ key_ids=tuple(sorted(set(key_ids))),
+ external_anchor=external_anchor,
+ errors=tuple(errors),
+ warnings=tuple(warnings),
+ )
+
+
+def thaw_artifact(
+ bundle: str | Path,
+ destination: str | Path,
+ *,
+ expected_artifact_id: str | None = None,
+ expected_key_id: str | None = None,
+) -> dict[str, Any]:
+ """Create a mutable descendant from a cleanly sealed frozen artifact."""
+
+ bundle_path = Path(bundle).resolve()
+ destination_path = _absolute_lexical(destination)
+ result = verify_frozen_artifact(
+ bundle_path,
+ expected_artifact_id=expected_artifact_id,
+ expected_key_id=expected_key_id,
+ require_seal=True,
+ )
+ if not result.sealed:
+ raise ArtifactControlError(
+ f"thaw requires a cleanly sealed artifact; observed {result.state}"
+ )
+ _require_absent(destination_path, "thaw destination")
+ record_path = destination_path.with_name(destination_path.name + ".vstd-thaw.json")
+ _require_absent(record_path, "thaw record")
+ freeze = _load_freeze(bundle_path)
+ payload = bundle_path / "payload"
+ destination_path.parent.mkdir(parents=True, exist_ok=True)
+ destination_created = False
+ record_created = False
+ record: dict[str, Any] = {
+ "schema_version": THAW_SCHEMA,
+ "parent_artifact_id": result.artifact_id,
+ "parent_content_id": result.content_id,
+ "parent_freeze_id": result.freeze_id,
+ "parent_seal_ids": list(result.valid_seal_ids),
+ "artifact_kind": freeze["artifact_kind"],
+ "media_type": freeze["media_type"],
+ }
+ record["thaw_id"] = _identity("vstd-thaw-1", _canonical_bytes(record))
+ try:
+ if freeze["artifact_kind"] == "file":
+ with (
+ payload.open("rb") as source_stream,
+ destination_path.open("xb") as target_stream,
+ ):
+ destination_created = True
+ shutil.copyfileobj(source_stream, target_stream)
+ _make_writable(destination_path)
+ else:
+ destination_path.mkdir()
+ destination_created = True
+ shutil.copytree(payload, destination_path, dirs_exist_ok=True)
+ for path in destination_path.rglob("*"):
+ if path.is_file() or path.is_dir():
+ _make_writable(path)
+ _make_writable(destination_path)
+ _write_json_exclusive(record_path, record)
+ record_created = True
+ status = thawed_artifact_status(
+ destination_path,
+ record_path,
+ parent_bundle=bundle_path,
+ expected_artifact_id=expected_artifact_id,
+ expected_key_id=expected_key_id,
+ )
+ if status["state"] != "THAWED_CLEAN":
+ raise ArtifactControlError("thawed descendant did not match the sealed parent")
+ except Exception:
+ if record_created:
+ _remove_created_entry(record_path)
+ if destination_created:
+ _remove_created_entry(destination_path)
+ raise
+ return {**record, "record_path": str(record_path)}
+
+
+def thawed_artifact_status(
+ artifact: str | Path,
+ thaw_record: str | Path | None = None,
+ *,
+ parent_bundle: str | Path | None = None,
+ expected_artifact_id: str | None = None,
+ expected_key_id: str | None = None,
+) -> dict[str, Any]:
+ """Assess current descendant equality without authenticating the historical copy.
+
+ A sidecar alone can report only agreement with its own recorded metadata. A
+ ``THAWED_CLEAN`` or ``THAWED_DIRTY`` result requires an actual supplied parent
+ bundle whose freeze and seals verify and whose exact coordinates match the
+ sidecar. Even that result does not prove that the historical copy operation was
+ independently observed.
+ """
+
+ artifact_path = _absolute_lexical(artifact)
+ record_path = (
+ Path(thaw_record).resolve()
+ if thaw_record is not None
+ else artifact_path.with_name(artifact_path.name + ".vstd-thaw.json")
+ )
+ record = _read_json_object(record_path, "thaw record")
+ _strict_object(
+ record,
+ {
+ "schema_version",
+ "parent_artifact_id",
+ "parent_content_id",
+ "parent_freeze_id",
+ "parent_seal_ids",
+ "artifact_kind",
+ "media_type",
+ "thaw_id",
+ },
+ "thaw record",
+ )
+ if record["schema_version"] != THAW_SCHEMA:
+ raise ArtifactControlError(f"unsupported thaw schema {record['schema_version']!r}")
+ identifier_fields = {
+ "parent_artifact_id": "vstd-artifact-1:",
+ "parent_content_id": "vstd-content-1:",
+ "parent_freeze_id": "vstd-freeze-1:",
+ "thaw_id": "vstd-thaw-1:",
+ }
+ for name, prefix in identifier_fields.items():
+ value = record[name]
+ if (
+ not isinstance(value, str)
+ or not value.startswith(prefix)
+ or _DUAL_ID.fullmatch(value) is None
+ ):
+ raise ArtifactControlError(f"thaw record {name} is invalid")
+ seal_ids = record["parent_seal_ids"]
+ if not isinstance(seal_ids, list) or not seal_ids:
+ raise ArtifactControlError("thaw record parent_seal_ids must be a nonempty array")
+ if not all(
+ isinstance(value, str)
+ and value.startswith("vstd-seal-1:")
+ and _DUAL_ID.fullmatch(value) is not None
+ for value in seal_ids
+ ):
+ raise ArtifactControlError("thaw record parent_seal_ids contains an invalid seal ID")
+ if len(set(seal_ids)) != len(seal_ids):
+ raise ArtifactControlError("thaw record parent_seal_ids must not contain duplicates")
+ if record["artifact_kind"] not in {"file", "directory"}:
+ raise ArtifactControlError("thaw record artifact_kind is invalid")
+ if not isinstance(record["media_type"], str) or not record["media_type"]:
+ raise ArtifactControlError("thaw record media_type must be a nonempty string")
+ stable = {key: record[key] for key in record if key != "thaw_id"}
+ if record["thaw_id"] != _identity("vstd-thaw-1", _canonical_bytes(stable)):
+ raise ArtifactControlError("thaw_id does not close the thaw record")
+
+ observed_kind = (
+ "file"
+ if artifact_path.is_file()
+ else "directory"
+ if artifact_path.is_dir()
+ else ""
+ )
+
+ recorded_observed_id: str | None = None
+ try:
+ if observed_kind == record["artifact_kind"]:
+ recorded_observed_id = _identity(
+ "vstd-artifact-1",
+ _canonical_bytes(
+ _descriptor(
+ observed_kind,
+ record["media_type"],
+ _source_entries(artifact_path),
+ )
+ ),
+ )
+ except ArtifactControlError:
+ recorded_observed_id = None
+ recorded_identity_match = recorded_observed_id == record["parent_artifact_id"]
+
+ result: dict[str, Any] = {
+ "state": "NOT_ESTABLISHED",
+ "lineage_state": "NOT_ESTABLISHED",
+ "recorded_identity_match": recorded_identity_match,
+ "verified_parent_identity_match": None,
+ "identity_basis": "SIDECAR_RECORDED_METADATA",
+ "parent_verification_state": "NOT_CHECKED",
+ "external_anchor_state": "NOT_CHECKED",
+ "historical_operation": "NOT_ESTABLISHED",
+ "parent_artifact_id": record["parent_artifact_id"],
+ "observed_artifact_id": recorded_observed_id,
+ "thaw_id": record["thaw_id"],
+ "errors": [],
+ "warnings": [
+ "sidecar agreement does not establish an actual sealed parent or historical thaw operation"
+ ],
+ }
+ if parent_bundle is None:
+ return result
+
+ parent_path = Path(parent_bundle).resolve()
+ parent_verification = verify_frozen_artifact(
+ parent_path,
+ expected_artifact_id=expected_artifact_id,
+ expected_key_id=expected_key_id,
+ require_seal=True,
+ )
+ result["parent_verification_state"] = parent_verification.state
+ result["external_anchor_state"] = parent_verification.external_anchor
+ result["errors"] = list(parent_verification.errors)
+ if not parent_verification.sealed:
+ result["state"] = "FAIL"
+ result["errors"].append(
+ "supplied parent bundle is not cleanly sealed"
+ )
+ return result
+
+ parent_freeze = _load_freeze(parent_path)
+ coordinate_errors: list[str] = []
+ comparisons = (
+ ("parent_artifact_id", parent_verification.artifact_id),
+ ("parent_content_id", parent_verification.content_id),
+ ("parent_freeze_id", parent_verification.freeze_id),
+ ("artifact_kind", parent_freeze["artifact_kind"]),
+ ("media_type", parent_freeze["media_type"]),
+ )
+ for name, expected in comparisons:
+ if record[name] != expected:
+ coordinate_errors.append(
+ f"thaw record {name} does not match the supplied sealed parent"
+ )
+ valid_parent_seals = set(parent_verification.valid_seal_ids)
+ missing_seals = sorted(set(seal_ids) - valid_parent_seals)
+ if missing_seals:
+ coordinate_errors.append(
+ "thaw record names a seal that is not valid on the supplied parent: "
+ + ", ".join(missing_seals)
+ )
+
+ authoritative_observed_id: str | None = None
+ try:
+ if observed_kind == parent_freeze["artifact_kind"]:
+ authoritative_observed_id = _identity(
+ "vstd-artifact-1",
+ _canonical_bytes(
+ _descriptor(
+ observed_kind,
+ parent_freeze["media_type"],
+ _source_entries(artifact_path),
+ )
+ ),
+ )
+ except ArtifactControlError:
+ authoritative_observed_id = None
+
+ result["identity_basis"] = "VERIFIED_PARENT_METADATA"
+ result["observed_artifact_id"] = authoritative_observed_id
+ result["verified_parent_identity_match"] = (
+ authoritative_observed_id == parent_verification.artifact_id
+ )
+ result["warnings"] = [
+ "current equality does not prove that the historical thaw operation was independently observed"
+ ]
+ if parent_verification.external_anchor == "NOT_CHECKED":
+ result["warnings"].append(
+ "supplied parent is internally consistent; external continuity was not checked"
+ )
+ if coordinate_errors:
+ result["state"] = "FAIL"
+ result["errors"].extend(coordinate_errors)
+ return result
+
+ result["lineage_state"] = "PARENT_COORDINATES_ESTABLISHED"
+ result["state"] = (
+ "THAWED_CLEAN"
+ if result["verified_parent_identity_match"]
+ else "THAWED_DIRTY"
+ )
+ return result
+
+
+__all__ = [
+ "ArtifactControlError",
+ "ArtifactVerification",
+ "freeze_artifact",
+ "seal_artifact",
+ "thaw_artifact",
+ "thawed_artifact_status",
+ "verify_frozen_artifact",
+]
diff --git a/src/verifier/artifact_control/artifact-control-1.schema.json b/src/verifier/artifact_control/artifact-control-1.schema.json
new file mode 100644
index 0000000..7812d5c
--- /dev/null
+++ b/src/verifier/artifact_control/artifact-control-1.schema.json
@@ -0,0 +1,123 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://timelordraps.github.io/verifier/schemas/artifact-control-1.schema.json",
+ "title": "Verifier Standard (VSTD) artifact-control mechanism formats",
+ "description": "Strict shapes for freeze manifests, finite self-closing seal envelopes, and copy-on-write thaw lineage records. Shape-valid thaw metadata does not verify a parent or historical copy operation. These objects are mechanism formats, not VSTD receipts or numbered-profile conformance claims.",
+ "oneOf": [
+ {"$ref": "#/$defs/freeze"},
+ {"$ref": "#/$defs/seal"},
+ {"$ref": "#/$defs/thaw"}
+ ],
+ "$defs": {
+ "dualIdentifier": {
+ "type": "string",
+ "pattern": "^vstd-(artifact|content|freeze|seal|thaw)-1:sha256:[0-9a-f]{64}:sha3-256:[0-9a-f]{64}$"
+ },
+ "artifactIdentifier": {
+ "type": "string",
+ "pattern": "^vstd-artifact-1:sha256:[0-9a-f]{64}:sha3-256:[0-9a-f]{64}$"
+ },
+ "digests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["sha256", "sha3-256"],
+ "properties": {
+ "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
+ "sha3-256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}
+ }
+ },
+ "fileEntry": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["kind", "path", "byte_size", "digests"],
+ "properties": {
+ "kind": {"const": "file"},
+ "path": {"type": "string", "minLength": 1},
+ "byte_size": {"type": "integer", "minimum": 0},
+ "digests": {"$ref": "#/$defs/digests"}
+ }
+ },
+ "directoryEntry": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["kind", "path"],
+ "properties": {
+ "kind": {"const": "directory"},
+ "path": {"type": "string", "minLength": 1, "not": {"const": "."}}
+ }
+ },
+ "freeze": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["schema_version", "artifact_id", "content_id", "artifact_kind", "media_type", "entries", "lineage", "bound_contexts", "mechanism", "freeze_id"],
+ "properties": {
+ "schema_version": {"const": "VSTD-ARTIFACT-FREEZE-1"},
+ "artifact_id": {"$ref": "#/$defs/artifactIdentifier"},
+ "content_id": {"$ref": "#/$defs/dualIdentifier"},
+ "artifact_kind": {"enum": ["file", "directory"]},
+ "media_type": {"type": "string", "minLength": 1},
+ "entries": {
+ "type": "array",
+ "items": {"oneOf": [{"$ref": "#/$defs/fileEntry"}, {"$ref": "#/$defs/directoryEntry"}]}
+ },
+ "lineage": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/artifactIdentifier"}},
+ "bound_contexts": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/artifactIdentifier"}},
+ "mechanism": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["name", "version", "canonicalization", "write_guard"],
+ "properties": {
+ "name": {"const": "vstd-reference-freezer"},
+ "version": {"const": "1"},
+ "canonicalization": {"const": "VSTD-ARTIFACT-CANONICAL-1"},
+ "write_guard": {"const": "PORTABLE_READ_ONLY_TREE"}
+ }
+ },
+ "freeze_id": {"$ref": "#/$defs/dualIdentifier"}
+ }
+ },
+ "sealPayload": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["schema_version", "artifact_id", "content_id", "freeze_id", "freeze_manifest_digests", "key_id", "algorithm", "closure_rule"],
+ "properties": {
+ "schema_version": {"const": "VSTD-ARTIFACT-SEAL-CLOSURE-1"},
+ "artifact_id": {"$ref": "#/$defs/artifactIdentifier"},
+ "content_id": {"$ref": "#/$defs/dualIdentifier"},
+ "freeze_id": {"$ref": "#/$defs/dualIdentifier"},
+ "freeze_manifest_digests": {"$ref": "#/$defs/digests"},
+ "key_id": {"type": "string", "pattern": "^vstd-seal-key-1:sha256:[0-9a-f]{64}$"},
+ "algorithm": {"const": "Ed25519"},
+ "closure_rule": {"type": "string", "minLength": 1}
+ }
+ },
+ "seal": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["schema_version", "seal_payload", "public_key_base64", "signature_base64", "seal_id"],
+ "properties": {
+ "schema_version": {"const": "VSTD-ARTIFACT-SEAL-1"},
+ "seal_payload": {"$ref": "#/$defs/sealPayload"},
+ "public_key_base64": {"type": "string", "contentEncoding": "base64"},
+ "signature_base64": {"type": "string", "contentEncoding": "base64"},
+ "seal_id": {"$ref": "#/$defs/dualIdentifier"}
+ }
+ },
+ "thaw": {
+ "type": "object",
+ "description": "Unkeyed lineage metadata whose self-derived thaw_id establishes field agreement only. Established clean or dirty status requires separate verification against the actual supplied sealed parent.",
+ "additionalProperties": false,
+ "required": ["schema_version", "parent_artifact_id", "parent_content_id", "parent_freeze_id", "parent_seal_ids", "artifact_kind", "media_type", "thaw_id"],
+ "properties": {
+ "schema_version": {"const": "VSTD-ARTIFACT-THAW-1"},
+ "parent_artifact_id": {"$ref": "#/$defs/artifactIdentifier"},
+ "parent_content_id": {"$ref": "#/$defs/dualIdentifier"},
+ "parent_freeze_id": {"$ref": "#/$defs/dualIdentifier"},
+ "parent_seal_ids": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/dualIdentifier"}},
+ "artifact_kind": {"enum": ["file", "directory"]},
+ "media_type": {"type": "string", "minLength": 1},
+ "thaw_id": {"$ref": "#/$defs/dualIdentifier"}
+ }
+ }
+ }
+}
diff --git a/src/verifier/constraints/kernel.py b/src/verifier/constraints/kernel.py
index 82842c6..683536c 100644
--- a/src/verifier/constraints/kernel.py
+++ b/src/verifier/constraints/kernel.py
@@ -1,10 +1,14 @@
-"""Small common contract around native constrained-decoding engines.
+"""Terminology: intermediate representation (IR); JavaScript Object Notation (JSON);
+Verifier Standard (VSTD).
+
+Small common contract around native constrained-decoding engines.
This is intentionally not a universal grammar IR. The source constraint remains
in its native language and the selected engine owns compilation. VSTD
standardizes only the adjacent observable seam: source identity, compiled-object
identity, tokenizer identity, per-step token masks, state transitions, and optional
-independent post-validation.
+separately implemented post-validation. This mechanism separation does not establish
+distinct actors.
"""
from __future__ import annotations
diff --git a/src/verifier/constraints/llguidance_backend.py b/src/verifier/constraints/llguidance_backend.py
index cafe1bc..ae93a5c 100644
--- a/src/verifier/constraints/llguidance_backend.py
+++ b/src/verifier/constraints/llguidance_backend.py
@@ -1,4 +1,6 @@
-"""Strict llguidance backend for the VSTD logits constraint seam."""
+"""Terminology: JavaScript Object Notation (JSON); Verifier Standard (VSTD).
+
+Strict llguidance backend for the VSTD logits constraint seam."""
from __future__ import annotations
diff --git a/src/verifier/constraints/postvalidate.py b/src/verifier/constraints/postvalidate.py
index 17beef3..2d62634 100644
--- a/src/verifier/constraints/postvalidate.py
+++ b/src/verifier/constraints/postvalidate.py
@@ -1,4 +1,8 @@
-"""Independent whole-output checks adjacent to logits-time constraints."""
+"""Terminology: JavaScript Object Notation (JSON); Verifier Standard (VSTD).
+
+Whole-output checks implemented separately from logits-time constraints.
+
+This mechanism separation does not establish distinct actors."""
from __future__ import annotations
@@ -17,7 +21,7 @@ def validate_json_schema_output(output_text: str, schema: Mapping[str, Any]) ->
import jsonschema # type: ignore[import-untyped]
from jsonschema import Draft202012Validator
except ImportError as exc: # pragma: no cover - exercised only without test/runtime dependency
- raise RuntimeError("jsonschema is required for independent JSON Schema post-validation") from exc
+ raise RuntimeError("jsonschema is required for separate JSON Schema post-validation") from exc
output_digest = hashlib.sha256(output_text.encode("utf-8")).hexdigest()
constraint_source_digest = canonical_digest(dict(schema))
diff --git a/src/verifier/core/certificate.py b/src/verifier/core/certificate.py
index aadc1a2..dc189bf 100644
--- a/src/verifier/core/certificate.py
+++ b/src/verifier/core/certificate.py
@@ -1,4 +1,12 @@
-"""``VSTD4-GDC-1`` -- Grounded Decision Certificates for VSTD layer 4.
+"""Terminology: American Standard Code for Information Interchange (ASCII);
+conjunctive normal form (CNF); deletion resolution asymmetric tautology (DRAT);
+Boolean satisfiability problem (SAT); flexible SAT proof format (FRAT);
+grounded decision certificate (GDC); GRAT proof format (GRAT); JavaScript Object Notation (JSON);
+linear resolution asymmetric tautology (LRAT); resolution asymmetric tautology (RAT);
+reverse unit propagation (RUP); Unicode Transformation Format, 8-bit (UTF-8);
+Verifier Standard (VSTD).
+
+``VSTD4-GDC-1`` -- Grounded Decision Certificates for the VSTD-4 Refutability profile.
Competition proof formats (DRAT, LRAT, GRAT, FRAT) answer exactly one question:
*is this large formula really unsatisfiable?* They are deliberately
@@ -176,7 +184,7 @@ class VerifierDescriptor:
``format_fragment`` exists so a checker can be honest about what it does not
implement. Silently mis-accepting a construct the checker does not
- understand is the format-level form of semantic mismatch.
+ understand is the serialized-format form of semantic mismatch.
"""
specification_hash: str
@@ -456,6 +464,8 @@ def to_dict(self) -> dict[str, Any]:
@dataclass(frozen=True)
class DecisionCertificate:
+ """Canonical grounded decision certificate (GDC) blocks for the bounded checker."""
+
header: CertificateHeader
formula: tuple[tuple[int, ...], ...]
grounding: Grounding
diff --git a/src/verifier/core/checker.py b/src/verifier/core/checker.py
index ffc2c1d..7a2d4f7 100644
--- a/src/verifier/core/checker.py
+++ b/src/verifier/core/checker.py
@@ -1,13 +1,19 @@
-"""Independent VSTD Checker for SAT, Derivation Graphs, and Grounding.
+"""Terminology: application programming interface (API);
+Boolean satisfiability problem (SAT); Davis-Putnam-Logemann-Loveland (DPLL);
+grounded decision certificate (GDC); Secure Hash Algorithm 256-bit (SHA-256);
+trusted computing base (TCB); Verifier Standard (VSTD).
+
+Bundled VSTD Checker for SAT, Derivation Graphs, and Grounding.
This module provides a minimal, self-contained verification engine with zero
dependencies on external solver libraries or the target repository under test.
-It serves as an independent auditor in the Trusted Computing Base (TCB).
+It is a separate checker implementation in the trusted computing base (TCB), but
+calling it does not itself establish actor, implementation, or runtime independence.
"""
from __future__ import annotations
-from dataclasses import dataclass
+from dataclasses import dataclass, field
from enum import Enum
import hashlib
from pathlib import Path
@@ -26,9 +32,10 @@
def _source_digest(*candidates: Any) -> str:
"""SHA-256 of the first candidate available in source or an installed wheel.
- Several candidates are accepted because specification filenames move as the
- ladder is renumbered, and a descriptor that breaks on a rename would push
- implementers back toward hardcoding.
+ Each named candidate is tried as an absolute path, a repository-relative path,
+ and a current-working-directory-relative path. ``standard/`` coordinates also
+ resolve to the byte-identical installed specification copy. This is location
+ resolution, not semantic substitution; callers must name only equivalent sources.
"""
for candidate in candidates:
path = Path(candidate)
@@ -78,12 +85,67 @@ def unit_propagation_conflict(
class VerificationVerdict(str, Enum):
+ """Outcome vocabulary returned by the VSTD-1 claim-mechanics checker."""
+
VERIFIED = "VERIFIED"
FALSIFIED = "FALSIFIED"
INDETERMINATE = "INDETERMINATE"
UNSUPPORTED = "UNSUPPORTED"
+class IndependenceStatus(str, Enum):
+ """Evidence state for separation between producer and checker."""
+
+ EVIDENCED = "EVIDENCED"
+ DECLARED = "DECLARED"
+ NOT_DEMONSTRATED = "NOT_DEMONSTRATED"
+ CONFLICTED = "CONFLICTED"
+
+
+def independence_is_evidenced(basis: Mapping[str, Any]) -> bool:
+ """Apply the bundled runtime's current independence capability ceiling.
+
+ Serialized status words and evidence references are declarations. VSTD 1.2.0
+ ships no validator that resolves and binds them to distinct actors and execution
+ seams, so no supplied mapping can establish evidenced independence.
+ """
+
+ del basis
+ return False
+
+
+@dataclass(frozen=True)
+class IndependenceBasis:
+ """Actor and execution separation; artifact agreement proves neither."""
+
+ actor_independence: IndependenceStatus = IndependenceStatus.NOT_DEMONSTRATED
+ implementation_separation: IndependenceStatus = IndependenceStatus.NOT_DEMONSTRATED
+ runtime_separation: IndependenceStatus = IndependenceStatus.NOT_DEMONSTRATED
+ evidence: tuple[str, ...] = ()
+
+ @property
+ def independently_verified(self) -> bool:
+ """False until an implemented adapter validates the recorded bindings."""
+
+ return independence_is_evidenced(
+ {
+ "actor_independence": self.actor_independence.value,
+ "implementation_separation": self.implementation_separation.value,
+ "runtime_separation": self.runtime_separation.value,
+ "evidence": self.evidence,
+ }
+ )
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "independently_verified": self.independently_verified,
+ "actor_independence": self.actor_independence.value,
+ "implementation_separation": self.implementation_separation.value,
+ "runtime_separation": self.runtime_separation.value,
+ "evidence": list(self.evidence),
+ }
+
+
class GroundingVerdict(str, Enum):
GROUNDED = "GROUNDED"
ASSUMED = "ASSUMED"
@@ -118,6 +180,8 @@ class IndependentGroundingResult:
@dataclass(frozen=True)
class IndependentAuditReport:
+ """Historical API name for a checker report with explicit separation evidence."""
+
claim_id: str
sat_result: IndependentSatResult
grounding_result: IndependentGroundingResult
@@ -125,11 +189,13 @@ class IndependentAuditReport:
overall_verdict: VerificationVerdict
trusted_computing_base: dict[str, str]
audit_notes: list[str]
+ independence_basis: IndependenceBasis = field(default_factory=IndependenceBasis)
def to_dict(self) -> dict[str, Any]:
return {
"claim_id": self.claim_id,
"overall_verdict": self.overall_verdict.value,
+ "independence_basis": self.independence_basis.to_dict(),
"structural_integrity_passed": self.structural_integrity_passed,
"sat_result": {
"satisfiable": self.sat_result.satisfiable,
@@ -160,7 +226,8 @@ def to_dict(self) -> dict[str, Any]:
class MinimalIndependentDPLL:
"""A self-contained DPLL SAT solver in pure standard-library Python.
- Independent of target solvers, third-party SAT packages, or external binaries.
+ It shares no target-solver, third-party SAT package, or external-binary logic.
+ That implementation separation does not establish actor independence.
"""
def __init__(self, n_vars: int, clauses: Sequence[Sequence[int]]):
@@ -272,7 +339,7 @@ def _dpll(
class IndependentGroundingChecker:
- """Checks grounding, acyclicity, and derivation validity independently."""
+ """Separately implemented grounding, acyclicity, and derivation checks."""
@staticmethod
def audit_derivation(
@@ -382,7 +449,13 @@ def dfs(node: str) -> bool:
class IndependentAuditor:
- """Top-level independent auditor that evaluates claims and derivation artifacts."""
+ """Historical API name for the bundled SAT and grounding checker.
+
+ Calling this class does not establish that separate actors performed the
+ producer and checker runs. Matching results cannot establish that fact. The
+ returned report records actor, implementation, and runtime separation as
+ ``NOT_DEMONSTRATED`` unless a separate integration supplies bound evidence.
+ """
@classmethod
def verifier_descriptor(cls) -> VerifierDescriptor:
@@ -398,13 +471,13 @@ def verifier_descriptor(cls) -> VerifierDescriptor:
``certificate_format`` deliberately does **not** say ``VSTD4-GDC-1``.
This auditor emits an ``IndependentAuditReport``, which is a different
artifact, and claiming a format one does not implement is the
- format-level form of the semantic mismatch rung 4.2 prohibits.
+ serialized-format form of the semantic mismatch rung 4.2 prohibits.
"""
return VerifierDescriptor(
- specification_hash=_source_digest("standard/VSTD-3.md"),
+ specification_hash=_source_digest("standard/VSTD-1.md"),
implementation_hash=_source_digest(_MODULE_PATH),
parser_hash=_source_digest(_MODULE_PATH.with_name("receipt.py")),
- certificate_format="VSTD3-INDEPENDENT-AUDIT",
+ certificate_format="VSTD1-CHECKER-REPORT",
format_fragment="SAT,GROUNDING,ACYCLICITY",
dependencies=("python-stdlib",),
deterministic=True,
@@ -476,9 +549,10 @@ def audit_claim_derivation(
overall = VerificationVerdict.INDETERMINATE
notes = [
- f"SAT formula solved independently: satisfiable={is_sat} (decisions={solver.decisions}, propagations={solver.propagations}).",
- f"Grounding audit status: {grounding_result.grounding_status.value} ({grounding_result.details}).",
+ f"SAT formula solved by the bundled separate implementation: satisfiable={is_sat} (decisions={solver.decisions}, propagations={solver.propagations}).",
+ f"Grounding checker status: {grounding_result.grounding_status.value} ({grounding_result.details}).",
f"Acyclicity verified: cycle_detected={grounding_result.cycle_detected}.",
+ "This same-process call did not demonstrate separate actors, implementation separation, or runtime separation; matching results cannot establish actor independence.",
]
if not is_sat:
notes.append(
diff --git a/src/verifier/core/depth.py b/src/verifier/core/depth.py
index 8de5a10..1eb0ede 100644
--- a/src/verifier/core/depth.py
+++ b/src/verifier/core/depth.py
@@ -1,27 +1,36 @@
-"""``vstd4_depth`` -- how far up the layer-4 ladder a claim actually got.
+"""Terminology: conjunctive normal form (CNF); grounded decision certificate (GDC);
+identifier (ID); unsatisfiable (UNSAT); Verifier Standard (VSTD).
+
+``vstd4_depth`` -- candidate depth over caller-supplied rung references.
VSTD-4 is fourteen rungs, ordered so that each is unstatable without the one
-below it. That ordering is not editorial tidiness. Standing up a genuinely
-external verification node -- VSTD-5 -- must be *computationally costly*,
-because verification is the new scaling, and layer 4 is where the cost is paid.
-The ladder makes the cost curve explicit instead of letting an implementer
-declare the top rung and skip the climb.
+below it. That ordering is not editorial tidiness. Entry to VSTD-5 requires
+separately checkable VSTD-4 obligations rather than a declared top-rung reference.
+The rung sequence makes those dependencies explicit.
-So the depth is **computed, never declared**::
+The structural candidate is **computed, never copied from a declared depth**::
vstd4_depth(claim) = max { k : CNF_4k(claim) is satisfiable }
and the UNSAT certificate at ``k+1`` **is** the explanation of why the claim
-cannot climb higher. The layer certifies its own ceiling using its own
-mechanism, and the conflict clause of that certificate names the missing rung
-outright.
+cannot climb higher. The candidate-depth calculation certifies its own ceiling
+using its own mechanism, and the conflict clause names the missing rung outright.
The encoding is Horn -- assertions ``[j]``, dependencies ``[-k, d]``, absences
``[-j]`` -- so every certificate this module produces is tier ``UP`` and checks
in linear time. The dependency clauses look inert while the numbering is a valid
-topological order, and that is exactly the point: reorder the ladder so a rung
+topological order, and that is exactly the point: reorder the sequence so a rung
depends on one above it and the formula goes unsatisfiable at a low depth,
-loudly, instead of quietly certifying a ladder that is no longer a ladder.
+loudly, instead of quietly certifying a sequence that no longer preserves its
+dependencies.
+
+The compatibility producer checks reference presence and rung dependencies. It does not
+resolve those references, validate the propositions they allegedly establish, or
+check VSTD-1/2/3 preconditions. Its result is therefore a candidate with
+``conformance_status = NOT_ESTABLISHED`` and cannot admit VSTD-5. The separate
+``establish_vstd4`` path reruns exact evidence-bound prerequisite and rung
+mechanisms, then checks the structural witness before it can report established
+conformance.
This module *produces* certificates. It is not part of the trusted computing
base; :mod:`verifier.core.kernel` checks what it emits, and the propagation
@@ -36,6 +45,7 @@
from .certificate import (
CertificateHeader,
ClaimBinding,
+ ClaimCoordinate,
ClauseGrounding,
CostTier,
DecisionBlock,
@@ -44,13 +54,28 @@
GroundedFact,
Grounding,
PropagationStep,
+ ResourceBounds,
UnitPropagationProof,
VariableGrounding,
Verdict,
+ VerifierDescriptor,
+ canonical_digest,
)
+from .evidence import (
+ BoundProposition,
+ EvaluatedProposition,
+ MechanismOutcome,
+ EvidenceStore,
+ VerificationMechanism,
+ VerificationSession,
+)
+from .kernel import KernelOutcome, check as kernel_check
MAX_DEPTH = 14
-"""Entry condition for VSTD-5: ``vstd4_depth(claim) == 14``."""
+"""Highest structural candidate depth; not sufficient for VSTD-5 entry."""
+
+DEPTH_KIND = "CANDIDATE"
+CONFORMANCE_STATUS = "NOT_ESTABLISHED"
class VSTD5EntryError(RuntimeError):
@@ -111,27 +136,29 @@ class Rung:
RULES = (RULE_ASSERTED, RULE_ABSENT, RULE_REQUIRES)
-def _validate_ladder() -> None:
+def _validate_rung_sequence() -> None:
for rung in RUNGS:
for dependency in rung.depends_on:
if dependency >= rung.index:
raise ValueError(
f"rung {rung.id} depends on rung index {dependency}, which is not "
- "below it; the ladder numbering is no longer a topological order"
+ "below it; the rung numbering is no longer a topological order"
)
-_validate_ladder()
+_validate_rung_sequence()
@dataclass(frozen=True)
class DepthResult:
- """A computed depth, with the evidence for both halves of the answer.
+ """A computed candidate depth, with certificates for the structural answer.
- ``witness`` certifies the rungs that were climbed. ``refutation`` certifies
- why the next one was not, and its ``blocking_rungs`` name the reason. A
+ ``witness`` certifies consistency of the caller-supplied rung references.
+ ``refutation`` certifies why the next structural rung was not reached, and
+ its ``blocking_rungs`` name the reason. A
depth reported without ``refutation`` at anything below :data:`MAX_DEPTH`
- would be a declaration, which is the thing this module exists to avoid.
+ would be a declaration, which is the thing this module exists to avoid. The
+ references themselves and prerequisite-profile coordinates are not validated here.
"""
depth: int
@@ -141,11 +168,17 @@ class DepthResult:
@property
def admits_vstd5(self) -> bool:
- return self.depth >= MAX_DEPTH
+ return False
+
+ @property
+ def conformance_status(self) -> str:
+ return CONFORMANCE_STATUS
def to_dict(self) -> dict[str, object]:
return {
"depth": self.depth,
+ "depth_kind": DEPTH_KIND,
+ "conformance_status": self.conformance_status,
"max_depth": MAX_DEPTH,
"admits_vstd5": self.admits_vstd5,
"blocking_rungs": list(self.blocking_rungs),
@@ -154,13 +187,90 @@ def to_dict(self) -> dict[str, object]:
}
-def require_vstd5_entry(result: DepthResult) -> DepthResult:
- """Fail closed unless ``result`` carries the complete layer-4 witness.
+@dataclass(frozen=True)
+class EvidenceBoundDepthResult:
+ """VSTD-4 result obtained by rerunning every bound evidence mechanism.
+
+ The structural candidate is retained as an audit artifact. Normative
+ conformance is established only when the VSTD-1, VSTD-2, and VSTD-3
+ preconditions and all fourteen rung propositions pass under their exact
+ bindings, and the candidate witness itself checks in the independent
+ kernel.
+ """
+
+ candidate: DepthResult
+ prerequisite_evaluations: tuple[tuple[int, EvaluatedProposition], ...]
+ rung_evaluations: tuple[tuple[str, EvaluatedProposition], ...]
+ binding_errors: tuple[str, ...]
+ kernel_outcome: str
+ claim_id: str
+
+ @property
+ def depth(self) -> int:
+ return self.candidate.depth
+
+ @property
+ def witness(self) -> Optional[DecisionCertificate]:
+ return self.candidate.witness
+
+ @property
+ def refutation(self) -> Optional[DecisionCertificate]:
+ return self.candidate.refutation
+
+ @property
+ def blocking_rungs(self) -> tuple[str, ...]:
+ return self.candidate.blocking_rungs
+
+ @property
+ def conformance_status(self) -> str:
+ if self.binding_errors or self.depth != MAX_DEPTH:
+ return CONFORMANCE_STATUS
+ if self.kernel_outcome != KernelOutcome.ACCEPTED.value:
+ return CONFORMANCE_STATUS
+ if any(not result.passed for _, result in self.prerequisite_evaluations):
+ return CONFORMANCE_STATUS
+ if any(not result.passed for _, result in self.rung_evaluations):
+ return CONFORMANCE_STATUS
+ if len(self.prerequisite_evaluations) != 3 or len(self.rung_evaluations) != MAX_DEPTH:
+ return CONFORMANCE_STATUS
+ return "ESTABLISHED"
+
+ @property
+ def admits_vstd5(self) -> bool:
+ return self.conformance_status == "ESTABLISHED"
+
+ def to_dict(self) -> dict[str, object]:
+ payload = self.candidate.to_dict()
+ payload.update(
+ {
+ "depth_kind": "EVIDENCE_BOUND",
+ "conformance_status": self.conformance_status,
+ "admits_vstd5": self.admits_vstd5,
+ "prerequisite_evaluations": {
+ str(profile): result.to_dict()
+ for profile, result in self.prerequisite_evaluations
+ },
+ "rung_evaluations": {
+ rung_id: result.to_dict()
+ for rung_id, result in self.rung_evaluations
+ },
+ "binding_errors": list(self.binding_errors),
+ "kernel_outcome": self.kernel_outcome,
+ "claim_id": self.claim_id,
+ }
+ )
+ return payload
- VSTD-5 is draft, but its entry boundary is not: no future witness transport
- may admit a partial layer-4 claim. Returning the checked result makes this
- function usable as the first line of any later VSTD-5 procedure without
- turning the gate into a second, declarative depth field.
+
+def require_vstd5_entry(
+ result: DepthResult | EvidenceBoundDepthResult,
+) -> EvidenceBoundDepthResult:
+ """Reject the current unbound candidate result at the VSTD-5 boundary.
+
+ A structural candidate over caller-supplied references is not normative
+ VSTD-4 conformance. The evidence-binding implementation uses a distinct
+ result type and gate; it never makes this candidate stronger by setting
+ another declaration field.
"""
if result.depth != MAX_DEPTH or result.witness is None:
raise VSTD5EntryError(
@@ -173,6 +283,17 @@ def require_vstd5_entry(result: DepthResult) -> DepthResult:
raise VSTD5EntryError(
"VSTD-5 entry result carries a ceiling refutation or blocking rung"
)
+ if not isinstance(result, EvidenceBoundDepthResult):
+ raise VSTD5EntryError(
+ "VSTD-5 requires established VSTD-4 conformance; this structural "
+ f"candidate has conformance_status {result.conformance_status}"
+ )
+ if not result.admits_vstd5:
+ detail = "; ".join(result.binding_errors) or "one or more mechanisms did not pass"
+ raise VSTD5EntryError(
+ "VSTD-5 requires established VSTD-4 conformance; evidence-bound "
+ f"result is {result.conformance_status}: {detail}"
+ )
return result
@@ -182,7 +303,7 @@ def require_vstd5_entry(result: DepthResult) -> DepthResult:
def _encode(
- level: int, evidence: Mapping[str, str], claim_id: str
+ candidate_depth: int, evidence: Mapping[str, str], claim_id: str
) -> tuple[tuple[tuple[int, ...], ...], Grounding]:
"""CNF_4k, together with the grounding that says what its variables mean."""
formula: list[tuple[int, ...]] = []
@@ -192,7 +313,7 @@ def emit(literals: Sequence[int], rule: EncodingRule, bindings, subjects) -> Non
clauses.append(ClauseGrounding(len(formula), rule.rule_id, dict(bindings), dict(subjects)))
formula.append(tuple(literals))
- for rung in RUNGS[:level]:
+ for rung in RUNGS[:candidate_depth]:
emit([rung.index], RULE_ASSERTED, {"rung": rung.index}, {"rung": claim_id})
for rung in RUNGS:
@@ -254,9 +375,9 @@ def _propagate(
def _certify(
- level: int, evidence: Mapping[str, str], claim_id: str, binding: ClaimBinding
+ candidate_depth: int, evidence: Mapping[str, str], claim_id: str, binding: ClaimBinding
) -> tuple[DecisionCertificate, tuple[str, ...]]:
- formula, grounding = _encode(level, evidence, claim_id)
+ formula, grounding = _encode(candidate_depth, evidence, claim_id)
steps, conflict, assignment = _propagate(formula)
literals = sum(len(clause) for clause in formula)
@@ -294,29 +415,282 @@ def vstd4_depth(
claim_id: str,
binding: ClaimBinding,
) -> DepthResult:
- """Compute how far up the layer-4 ladder ``evidence`` carries a claim.
+ """Compute a structural candidate depth from caller-supplied references.
``evidence`` maps a rung id (``"4.1"`` .. ``"4.14"``) to the content address
- of the artifact establishing it. An absent or empty entry means the rung is
- not established, and the resulting UNSAT certificate at the next level names
- it.
+ claimed for the artifact establishing it. This function checks only whether
+ each value is nonempty; it does not retrieve the artifact or validate the
+ rung proposition. An absent or empty entry blocks the candidate, and the
+ resulting UNSAT certificate at the next candidate depth names it.
- Descends from :data:`MAX_DEPTH`, so the first satisfiable level found is the
- depth -- the ladder is monotone by construction, but searching downward
+ Descends from :data:`MAX_DEPTH`, so the first satisfiable candidate depth found is the
+ depth -- the rung sequence is monotone by construction, but searching downward
means a fully-conformant claim costs one solve rather than fourteen.
"""
unknown = set(evidence) - set(BY_ID)
if unknown:
raise ValueError(f"evidence names rungs that do not exist: {sorted(unknown)}")
- for level in range(MAX_DEPTH, 0, -1):
- certificate, blocking = _certify(level, evidence, claim_id, binding)
+ for candidate_depth in range(MAX_DEPTH, 0, -1):
+ certificate, blocking = _certify(candidate_depth, evidence, claim_id, binding)
if certificate.header.verdict is Verdict.PASS:
refutation: Optional[DecisionCertificate] = None
blocked: tuple[str, ...] = ()
- if level < MAX_DEPTH:
- refutation, blocked = _certify(level + 1, evidence, claim_id, binding)
- return DepthResult(level, certificate, refutation, blocked)
+ if candidate_depth < MAX_DEPTH:
+ refutation, blocked = _certify(
+ candidate_depth + 1, evidence, claim_id, binding
+ )
+ return DepthResult(candidate_depth, certificate, refutation, blocked)
refutation, blocked = _certify(1, evidence, claim_id, binding)
return DepthResult(0, None, refutation, blocked)
+
+
+def establish_vstd4(
+ rung_evidence: Mapping[str, BoundProposition],
+ *,
+ prerequisite_evidence: Mapping[int, BoundProposition],
+ session: VerificationSession,
+ claim_id: str,
+ binding: ClaimBinding,
+) -> EvidenceBoundDepthResult:
+ """Rerun evidence mechanisms and establish VSTD-4 only if all pass.
+
+ Expected predicates are ``vstd.object_profile.1`` through
+ ``vstd.object_profile.3`` and ``vstd4.rung.4.1`` through
+ ``vstd4.rung.4.14``. Every proposition must target ``claim_id``, expect the
+ Boolean value ``True``, and carry ``claim_binding_digest`` equal to the exact
+ :class:`ClaimBinding` used for the structural certificate. Mismatches are
+ excluded rather than evaluated, so field naming or neighboring evidence
+ cannot earn a rung.
+ """
+
+ errors: list[str] = []
+ prerequisite_results: list[tuple[int, EvaluatedProposition]] = []
+ rung_results: list[tuple[str, EvaluatedProposition]] = []
+ exact_binding = binding.digest()
+
+ unknown_profiles = set(prerequisite_evidence) - {1, 2, 3}
+ if unknown_profiles:
+ errors.append(f"unknown prerequisite profiles: {sorted(unknown_profiles)}")
+ unknown_rungs = set(rung_evidence) - set(BY_ID)
+ if unknown_rungs:
+ errors.append(f"unknown VSTD-4 rungs: {sorted(unknown_rungs)}")
+
+ def binding_error(
+ proposition: BoundProposition, expected_predicate: str, label: str
+ ) -> Optional[str]:
+ if proposition.subject_id != claim_id:
+ return f"{label} targets {proposition.subject_id!r}, not {claim_id!r}"
+ if proposition.predicate != expected_predicate:
+ return (
+ f"{label} binds predicate {proposition.predicate!r}, not "
+ f"{expected_predicate!r}"
+ )
+ if proposition.expected is not True:
+ return f"{label} does not bind the required Boolean true proposition"
+ if proposition.parameters.get("claim_binding_digest") != exact_binding:
+ return f"{label} does not bind the exact VSTD-4 claim commitment"
+ return None
+
+ for profile in (1, 2, 3):
+ proposition = prerequisite_evidence.get(profile)
+ if proposition is None:
+ errors.append(f"missing VSTD-{profile} prerequisite evidence")
+ continue
+ issue = binding_error(proposition, f"vstd.object_profile.{profile}", f"VSTD-{profile}")
+ if issue:
+ errors.append(issue)
+ continue
+ prerequisite_results.append((profile, session.evaluate(proposition)))
+
+ passed_refs: dict[str, str] = {}
+ for rung in RUNGS:
+ proposition = rung_evidence.get(rung.id)
+ if proposition is None:
+ errors.append(f"missing rung {rung.id} evidence")
+ continue
+ issue = binding_error(proposition, f"vstd4.rung.{rung.id}", f"rung {rung.id}")
+ if issue:
+ errors.append(issue)
+ continue
+ result = session.evaluate(proposition)
+ rung_results.append((rung.id, result))
+ if result.outcome is MechanismOutcome.PASS:
+ passed_refs[rung.id] = "sha256:" + proposition.digest()
+
+ candidate = vstd4_depth(passed_refs, claim_id=claim_id, binding=binding)
+ kernel_outcome = KernelOutcome.REJECTED.value
+ if candidate.witness is not None:
+ kernel_outcome = kernel_check(candidate.witness, binding=binding).outcome.value
+
+ return EvidenceBoundDepthResult(
+ candidate,
+ tuple(prerequisite_results),
+ tuple(rung_results),
+ tuple(errors),
+ kernel_outcome,
+ claim_id,
+ )
+
+
+def build_evidence_bound_vstd4_receipt(
+ result: EvidenceBoundDepthResult,
+ *,
+ receipt_id: str,
+ claim_id: str,
+ binding: ClaimBinding,
+ prerequisite_evidence: Mapping[int, BoundProposition],
+ rung_evidence: Mapping[str, BoundProposition],
+ session: VerificationSession,
+ status: str = "VALID",
+) -> dict[str, object]:
+ """Serialize every input needed to rerun an evidence-bound VSTD-4 result."""
+ recomputed = establish_vstd4(
+ rung_evidence,
+ prerequisite_evidence=prerequisite_evidence,
+ session=session,
+ claim_id=claim_id,
+ binding=binding,
+ )
+ if canonical_digest(recomputed.to_dict()) != canonical_digest(result.to_dict()):
+ raise ValueError("VSTD-4 result does not match the supplied replay inputs")
+ all_refs = tuple(
+ sorted(
+ {
+ reference
+ for proposition in (*prerequisite_evidence.values(), *rung_evidence.values())
+ for reference in proposition.evidence_refs
+ }
+ )
+ )
+ return {
+ "schema_version": "VSTD-4",
+ "receipt_id": receipt_id,
+ "claim_id": claim_id,
+ "binding": binding.to_dict(),
+ "vstd4_depth": result.depth,
+ "depth_kind": "EVIDENCE_BOUND",
+ "conformance_status": result.conformance_status,
+ "rung_evidence": {
+ rung_id: "sha256:" + proposition.digest()
+ for rung_id, proposition in sorted(rung_evidence.items())
+ },
+ "witness": None if result.witness is None else result.witness.to_dict(),
+ "ceiling_refutation": (
+ None if result.refutation is None else result.refutation.to_dict()
+ ),
+ "blocking_rungs": list(result.blocking_rungs),
+ "status": status,
+ "kernel_outcome": result.kernel_outcome,
+ "evidence_bindings": {
+ "prerequisites": {
+ str(profile): proposition.to_dict()
+ for profile, proposition in sorted(prerequisite_evidence.items())
+ },
+ "rungs": {
+ rung_id: proposition.to_dict()
+ for rung_id, proposition in sorted(rung_evidence.items())
+ },
+ },
+ "evidence_payloads": session.evidence.export_base64(all_refs),
+ }
+
+
+def claim_binding_from_dict(data: Mapping[str, object]) -> ClaimBinding:
+ """Reconstruct the exact VSTD-4 claim binding carried by a receipt."""
+
+ coordinate = data["coordinate"]
+ verifier = data["verifier"]
+ bounds = data["bounds"]
+ if not isinstance(coordinate, Mapping) or not isinstance(verifier, Mapping) or not isinstance(bounds, Mapping):
+ raise ValueError("receipt ClaimBinding blocks must be objects")
+ return ClaimBinding(
+ str(data["claim"]),
+ ClaimCoordinate(
+ str(coordinate["subject"]),
+ str(coordinate["predicate"]),
+ {str(key): str(value) for key, value in dict(coordinate.get("parameters", {})).items()},
+ ),
+ str(data["policy_root"]),
+ str(data["evidence_root"]),
+ VerifierDescriptor(
+ str(verifier["specification_hash"]),
+ str(verifier["implementation_hash"]),
+ str(verifier["parser_hash"]),
+ str(verifier.get("certificate_format", "VSTD4-GDC-1")),
+ str(verifier.get("format_fragment", "UP,WIDTH-K,RES")),
+ tuple(str(item) for item in verifier.get("dependencies", ())),
+ bool(verifier.get("deterministic", True)),
+ ),
+ ResourceBounds(
+ int(bounds["verification_cost_bound"]),
+ int(bounds["memory_bound"]),
+ int(bounds["certificate_size_bound"]),
+ ),
+ str(data.get("prior_commitment", "")),
+ )
+
+
+def recheck_evidence_bound_vstd4_receipt(
+ receipt: Mapping[str, object],
+ *,
+ mechanisms: Sequence[VerificationMechanism],
+) -> EvidenceBoundDepthResult:
+ """Reconstruct evidence bytes and rerun an evidence-bound VSTD-4 receipt."""
+ if receipt.get("schema_version") != "VSTD-4":
+ raise ValueError("not a VSTD-4 receipt")
+ if receipt.get("depth_kind") != "EVIDENCE_BOUND":
+ raise ValueError("receipt is not evidence-bound")
+ payloads = receipt.get("evidence_payloads")
+ bindings = receipt.get("evidence_bindings")
+ binding_data = receipt.get("binding")
+ if not isinstance(payloads, Mapping) or not isinstance(bindings, Mapping) or not isinstance(binding_data, Mapping):
+ raise ValueError("evidence-bound receipt is missing replay inputs")
+ store = EvidenceStore()
+ store.import_base64({str(key): str(value) for key, value in payloads.items()})
+ session = VerificationSession(store)
+ for mechanism in mechanisms:
+ session.register(mechanism)
+ prerequisites_data = bindings.get("prerequisites")
+ rungs_data = bindings.get("rungs")
+ if not isinstance(prerequisites_data, Mapping) or not isinstance(rungs_data, Mapping):
+ raise ValueError("evidence binding maps are missing")
+ prerequisites = {
+ int(profile): BoundProposition.from_dict(proposition)
+ for profile, proposition in prerequisites_data.items()
+ if isinstance(proposition, Mapping)
+ }
+ rungs = {
+ str(rung_id): BoundProposition.from_dict(proposition)
+ for rung_id, proposition in rungs_data.items()
+ if isinstance(proposition, Mapping)
+ }
+ binding = claim_binding_from_dict(binding_data)
+ result = establish_vstd4(
+ rungs,
+ prerequisite_evidence=prerequisites,
+ session=session,
+ claim_id=str(receipt["claim_id"]),
+ binding=binding,
+ )
+ observed = result.to_dict()
+ comparisons = {
+ "vstd4_depth": observed["depth"],
+ "conformance_status": observed["conformance_status"],
+ "blocking_rungs": observed["blocking_rungs"],
+ "kernel_outcome": observed["kernel_outcome"],
+ }
+ for field, value in comparisons.items():
+ if receipt.get(field) != value:
+ raise ValueError(f"recomputed VSTD-4 field does not match receipt: {field}")
+ for field, certificate in (
+ ("witness", result.witness),
+ ("ceiling_refutation", result.refutation),
+ ):
+ expected = receipt.get(field)
+ expected_digest = None if expected is None else canonical_digest(expected)
+ observed_digest = None if certificate is None else certificate.digest()
+ if expected_digest != observed_digest:
+ raise ValueError(f"recomputed VSTD-4 {field} does not match receipt")
+ return result
diff --git a/src/verifier/core/evidence.py b/src/verifier/core/evidence.py
new file mode 100644
index 0000000..be1cb63
--- /dev/null
+++ b/src/verifier/core/evidence.py
@@ -0,0 +1,506 @@
+"""Terminology: JavaScript Object Notation (JSON); Secure Hash Algorithm 256-bit
+(SHA-256); Verifier Standard (VSTD).
+
+Evidence-bound execution for meta-verifier mechanisms.
+
+Serialized claims are inputs, never verdicts. :class:`VerificationSession`
+resolves every content-addressed evidence item, checks its bytes, selects the
+exact registered mechanism implementation, enforces the declared input bounds,
+and runs that mechanism again. A caller cannot promote a declaration by
+putting ``PASS`` in a field because no such field exists on
+:class:`BoundProposition`.
+
+The session establishes only the exact proposition a mechanism checks under its
+named trust roots and bounds. Registration does not make a mechanism correct,
+independent, or authoritative; it makes the executable coordinate explicit and
+prevents a different declared digest from substituting after the proposition was
+bound. Built-in mechanisms derive that digest from their exact module bytes. An
+external mechanism remains responsible for truthfully deriving its advertised
+implementation digest; the session cannot infer arbitrary plugin source identity.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+from enum import Enum
+import base64
+import hashlib
+from pathlib import Path
+import re
+from typing import Any, Mapping, Protocol, Sequence
+
+from .certificate import canonical_digest
+
+
+_DIGEST = re.compile(r"^(?:sha256:)?([0-9a-f]{64})$")
+
+
+class EvidenceBindingError(ValueError):
+ """An evidence binding is malformed or cannot be resolved exactly."""
+
+
+class MechanismOutcome(str, Enum):
+ """Enumeration of the exported result values."""
+
+ PASS = "PASS"
+ FAIL = "FAIL"
+ UNKNOWN = "UNKNOWN"
+
+
+@dataclass(frozen=True)
+class EvidenceBounds:
+ """Resource ceilings enforced before invoking a domain mechanism."""
+
+ max_evidence_items: int
+ max_evidence_bytes: int
+
+ def __post_init__(self) -> None:
+ if self.max_evidence_items < 0 or self.max_evidence_bytes < 0:
+ raise EvidenceBindingError("evidence bounds cannot be negative")
+
+ def to_dict(self) -> dict[str, int]:
+ return {
+ "max_evidence_items": self.max_evidence_items,
+ "max_evidence_bytes": self.max_evidence_bytes,
+ }
+
+
+@dataclass(frozen=True)
+class BoundProposition:
+ """Exact proposition, evidence, mechanism, trust-root, and bound binding."""
+
+ subject_id: str
+ predicate: str
+ expected: Any
+ mechanism_id: str
+ mechanism_digest: str
+ evidence_refs: tuple[str, ...]
+ trust_roots: tuple[str, ...]
+ bounds: EvidenceBounds
+ parameters: Mapping[str, str] = field(default_factory=dict)
+
+ def __post_init__(self) -> None:
+ if not self.subject_id or not self.predicate or not self.mechanism_id:
+ raise EvidenceBindingError(
+ "subject_id, predicate, and mechanism_id must not be empty"
+ )
+ if not _DIGEST.fullmatch(self.mechanism_digest):
+ raise EvidenceBindingError("mechanism_digest must be a SHA-256 digest")
+ if not self.evidence_refs:
+ raise EvidenceBindingError("a bound proposition needs evidence")
+ normalized = tuple(_normalize_ref(item) for item in self.evidence_refs)
+ if len(set(normalized)) != len(normalized):
+ raise EvidenceBindingError(
+ "duplicate evidence references do not create additional support"
+ )
+ if not self.trust_roots or any(not item for item in self.trust_roots):
+ raise EvidenceBindingError("at least one explicit trust root is required")
+ object.__setattr__(self, "evidence_refs", normalized)
+ object.__setattr__(self, "trust_roots", tuple(sorted(set(self.trust_roots))))
+ object.__setattr__(self, "parameters", dict(sorted(self.parameters.items())))
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "subject_id": self.subject_id,
+ "predicate": self.predicate,
+ "expected": self.expected,
+ "mechanism_id": self.mechanism_id,
+ "mechanism_digest": _normalize_ref(self.mechanism_digest),
+ "evidence_refs": list(self.evidence_refs),
+ "trust_roots": list(self.trust_roots),
+ "bounds": self.bounds.to_dict(),
+ "parameters": dict(self.parameters),
+ }
+
+ def digest(self) -> str:
+ return canonical_digest(self.to_dict())
+
+ @classmethod
+ def from_dict(cls, data: Mapping[str, Any]) -> "BoundProposition":
+ bounds = data["bounds"]
+ return cls(
+ subject_id=str(data["subject_id"]),
+ predicate=str(data["predicate"]),
+ expected=data["expected"],
+ mechanism_id=str(data["mechanism_id"]),
+ mechanism_digest=str(data["mechanism_digest"]),
+ evidence_refs=tuple(str(item) for item in data["evidence_refs"]),
+ trust_roots=tuple(str(item) for item in data["trust_roots"]),
+ bounds=EvidenceBounds(
+ int(bounds["max_evidence_items"]),
+ int(bounds["max_evidence_bytes"]),
+ ),
+ parameters={str(key): str(value) for key, value in data.get("parameters", {}).items()},
+ )
+
+
+@dataclass(frozen=True)
+class MechanismDecision:
+ """One bounded mechanism result plus its exact observations."""
+
+ outcome: MechanismOutcome
+ details: str
+ observations: Mapping[str, Any] = field(default_factory=dict)
+
+
+class VerificationMechanism(Protocol):
+ """Executable domain mechanism selected by an exact implementation digest."""
+
+ mechanism_id: str
+ mechanism_digest: str
+
+ def evaluate(
+ self, binding: BoundProposition, evidence: Sequence[bytes]
+ ) -> MechanismDecision:
+ """Evaluate only ``binding`` using the already digest-checked evidence."""
+
+
+@dataclass(frozen=True)
+class EvaluatedProposition:
+ """Result of executing a mechanism, not a caller-serializable verdict field."""
+
+ binding_digest: str
+ outcome: MechanismOutcome
+ mechanism_id: str
+ mechanism_digest: str
+ evidence_refs: tuple[str, ...]
+ trust_roots: tuple[str, ...]
+ observed_evidence_bytes: int
+ details: str
+ observations: Mapping[str, Any] = field(default_factory=dict)
+
+ @property
+ def passed(self) -> bool:
+ return self.outcome is MechanismOutcome.PASS
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "binding_digest": self.binding_digest,
+ "outcome": self.outcome.value,
+ "mechanism_id": self.mechanism_id,
+ "mechanism_digest": _normalize_ref(self.mechanism_digest),
+ "evidence_refs": list(self.evidence_refs),
+ "trust_roots": list(self.trust_roots),
+ "observed_evidence_bytes": self.observed_evidence_bytes,
+ "details": self.details,
+ "observations": dict(self.observations),
+ }
+
+
+def _normalize_ref(reference: str) -> str:
+ matched = _DIGEST.fullmatch(reference)
+ if matched is None:
+ raise EvidenceBindingError(f"not a SHA-256 evidence reference: {reference!r}")
+ return "sha256:" + matched.group(1)
+
+
+def implementation_file_digest(path: str) -> str:
+ """Return the SHA-256 coordinate of exact mechanism module bytes."""
+ return "sha256:" + hashlib.sha256(Path(path).read_bytes()).hexdigest()
+
+
+class EvidenceStore:
+ """In-memory content-addressed evidence store with collision/fork refusal."""
+
+ def __init__(self) -> None:
+ self._payloads: dict[str, bytes] = {}
+
+ def add(self, payload: bytes) -> str:
+ if not isinstance(payload, bytes):
+ raise TypeError("evidence payload must be bytes")
+ reference = "sha256:" + hashlib.sha256(payload).hexdigest()
+ existing = self._payloads.get(reference)
+ if existing is not None and existing != payload:
+ raise EvidenceBindingError(f"evidence digest collision at {reference}")
+ self._payloads[reference] = payload
+ return reference
+
+ def resolve(self, reference: str) -> bytes:
+ normalized = _normalize_ref(reference)
+ try:
+ payload = self._payloads[normalized]
+ except KeyError as exc:
+ raise EvidenceBindingError(f"evidence is unavailable: {normalized}") from exc
+ observed = "sha256:" + hashlib.sha256(payload).hexdigest()
+ if observed != normalized:
+ raise EvidenceBindingError(
+ f"evidence bytes do not match their reference: {normalized}"
+ )
+ return payload
+
+ def export_base64(self, references: Sequence[str]) -> dict[str, str]:
+ """Export exact evidence bytes for portable, offline mechanism replay."""
+ return {
+ _normalize_ref(reference): base64.b64encode(self.resolve(reference)).decode("ascii")
+ for reference in references
+ }
+
+ def import_base64(self, payloads: Mapping[str, str]) -> None:
+ """Import a portable bundle and refuse every reference/byte mismatch."""
+ for reference, encoded in payloads.items():
+ try:
+ payload = base64.b64decode(encoded, validate=True)
+ except Exception as exc:
+ raise EvidenceBindingError(
+ f"invalid base64 evidence payload for {reference}"
+ ) from exc
+ observed = self.add(payload)
+ if observed != _normalize_ref(reference):
+ raise EvidenceBindingError(
+ f"embedded evidence does not match reference {reference}"
+ )
+
+ def __contains__(self, reference: object) -> bool:
+ if not isinstance(reference, str):
+ return False
+ try:
+ return _normalize_ref(reference) in self._payloads
+ except EvidenceBindingError:
+ return False
+
+
+class VerificationSession:
+ """Resolve evidence and rerun only explicitly registered mechanisms."""
+
+ def __init__(self, evidence: EvidenceStore) -> None:
+ self.evidence = evidence
+ self._mechanisms: dict[str, VerificationMechanism] = {}
+
+ def register(self, mechanism: VerificationMechanism) -> None:
+ if not mechanism.mechanism_id:
+ raise EvidenceBindingError("mechanism_id must not be empty")
+ digest = _normalize_ref(mechanism.mechanism_digest)
+ previous = self._mechanisms.get(mechanism.mechanism_id)
+ if previous is not None and _normalize_ref(previous.mechanism_digest) != digest:
+ raise EvidenceBindingError(
+ f"mechanism substitution refused for {mechanism.mechanism_id}"
+ )
+ self._mechanisms[mechanism.mechanism_id] = mechanism
+
+ def evaluate(self, binding: BoundProposition) -> EvaluatedProposition:
+ mechanism = self._mechanisms.get(binding.mechanism_id)
+ if mechanism is None:
+ return self._unknown(binding, "bound mechanism is not registered", 0)
+ if _normalize_ref(mechanism.mechanism_digest) != _normalize_ref(
+ binding.mechanism_digest
+ ):
+ return self._unknown(binding, "registered mechanism digest does not match", 0)
+
+ if len(binding.evidence_refs) > binding.bounds.max_evidence_items:
+ return self._unknown(binding, "evidence item bound exceeded", 0)
+ try:
+ payloads = tuple(self.evidence.resolve(item) for item in binding.evidence_refs)
+ except EvidenceBindingError as exc:
+ return self._unknown(binding, str(exc), 0)
+ observed_bytes = sum(len(item) for item in payloads)
+ if observed_bytes > binding.bounds.max_evidence_bytes:
+ return self._unknown(binding, "evidence byte bound exceeded", observed_bytes)
+
+ try:
+ decision = mechanism.evaluate(binding, payloads)
+ except Exception as exc: # A mechanism crash is uncertainty, not a pass.
+ return self._unknown(
+ binding,
+ f"mechanism execution failed: {type(exc).__name__}: {exc}",
+ observed_bytes,
+ )
+ if not isinstance(decision, MechanismDecision):
+ return self._unknown(
+ binding, "mechanism returned an invalid decision object", observed_bytes
+ )
+ return EvaluatedProposition(
+ binding.digest(),
+ decision.outcome,
+ binding.mechanism_id,
+ _normalize_ref(binding.mechanism_digest),
+ binding.evidence_refs,
+ binding.trust_roots,
+ observed_bytes,
+ decision.details,
+ dict(decision.observations),
+ )
+
+ def evaluate_compound(
+ self, bindings: Sequence[BoundProposition]
+ ) -> tuple[EvaluatedProposition, ...]:
+ """Run one compound mechanism invocation over separately bound propositions.
+
+ Every returned evaluation retains its own binding digest, evidence
+ references, trust roots, bounds, and outcome. A mechanism without an
+ explicit ``evaluate_compound`` entry point cannot use this path; three
+ ordinary evaluations are not relabeled as one compound invocation.
+ """
+
+ items = tuple(bindings)
+ if not items:
+ raise EvidenceBindingError(
+ "compound evaluation requires at least one bound proposition"
+ )
+ mechanism_id = items[0].mechanism_id
+ mechanism_digest = _normalize_ref(items[0].mechanism_digest)
+ if any(
+ item.mechanism_id != mechanism_id
+ or _normalize_ref(item.mechanism_digest) != mechanism_digest
+ for item in items
+ ):
+ return tuple(
+ self._unknown(
+ item,
+ "compound propositions do not bind one exact mechanism",
+ 0,
+ )
+ for item in items
+ )
+ mechanism = self._mechanisms.get(mechanism_id)
+ if mechanism is None:
+ return tuple(
+ self._unknown(item, "bound mechanism is not registered", 0)
+ for item in items
+ )
+ if _normalize_ref(mechanism.mechanism_digest) != mechanism_digest:
+ return tuple(
+ self._unknown(
+ item, "registered mechanism digest does not match", 0
+ )
+ for item in items
+ )
+ evaluator = getattr(mechanism, "evaluate_compound", None)
+ if not callable(evaluator):
+ return tuple(
+ self._unknown(
+ item,
+ "bound mechanism has no compound evaluation entry point",
+ 0,
+ )
+ for item in items
+ )
+
+ evidence_sets: list[tuple[bytes, ...]] = []
+ observed_sizes: list[int] = []
+ for item in items:
+ if len(item.evidence_refs) > item.bounds.max_evidence_items:
+ return tuple(
+ self._unknown(
+ other,
+ "compound evidence item bound exceeded before invocation",
+ 0,
+ )
+ for other in items
+ )
+ try:
+ payloads = tuple(
+ self.evidence.resolve(reference)
+ for reference in item.evidence_refs
+ )
+ except EvidenceBindingError as exc:
+ return tuple(
+ self._unknown(
+ other,
+ f"compound evidence resolution failed: {exc}",
+ 0,
+ )
+ for other in items
+ )
+ observed_bytes = sum(len(payload) for payload in payloads)
+ if observed_bytes > item.bounds.max_evidence_bytes:
+ return tuple(
+ self._unknown(
+ other,
+ "compound evidence byte bound exceeded before invocation",
+ observed_bytes if other is item else 0,
+ )
+ for other in items
+ )
+ evidence_sets.append(payloads)
+ observed_sizes.append(observed_bytes)
+
+ try:
+ decisions = tuple(evaluator(items, tuple(evidence_sets)))
+ except Exception as exc:
+ return tuple(
+ self._unknown(
+ item,
+ f"compound mechanism execution failed: {type(exc).__name__}: {exc}",
+ observed_sizes[index],
+ )
+ for index, item in enumerate(items)
+ )
+ if len(decisions) != len(items) or any(
+ not isinstance(decision, MechanismDecision) for decision in decisions
+ ):
+ return tuple(
+ self._unknown(
+ item,
+ "compound mechanism did not return one decision per binding",
+ observed_sizes[index],
+ )
+ for index, item in enumerate(items)
+ )
+ return tuple(
+ EvaluatedProposition(
+ item.digest(),
+ decision.outcome,
+ item.mechanism_id,
+ _normalize_ref(item.mechanism_digest),
+ item.evidence_refs,
+ item.trust_roots,
+ observed_sizes[index],
+ decision.details,
+ dict(decision.observations),
+ )
+ for index, (item, decision) in enumerate(zip(items, decisions))
+ )
+
+ @staticmethod
+ def _unknown(
+ binding: BoundProposition, details: str, observed_bytes: int
+ ) -> EvaluatedProposition:
+ return EvaluatedProposition(
+ binding.digest(),
+ MechanismOutcome.UNKNOWN,
+ binding.mechanism_id,
+ _normalize_ref(binding.mechanism_digest),
+ binding.evidence_refs,
+ binding.trust_roots,
+ observed_bytes,
+ details,
+ )
+
+
+class BytesDigestMechanism:
+ """Built-in mechanism for the exact proposition ``bytes.sha256 == expected``."""
+
+ mechanism_id = "vstd.bytes.sha256"
+ mechanism_digest = implementation_file_digest(__file__)
+
+ def evaluate(
+ self, binding: BoundProposition, evidence: Sequence[bytes]
+ ) -> MechanismDecision:
+ if binding.predicate != "bytes.sha256" or len(evidence) != 1:
+ return MechanismDecision(
+ MechanismOutcome.UNKNOWN,
+ "this mechanism checks one bytes.sha256 proposition",
+ )
+ observed = "sha256:" + hashlib.sha256(evidence[0]).hexdigest()
+ expected = _normalize_ref(str(binding.expected))
+ outcome = MechanismOutcome.PASS if observed == expected else MechanismOutcome.FAIL
+ return MechanismDecision(
+ outcome,
+ f"observed {observed}; expected {expected}",
+ {"observed_digest": observed},
+ )
+
+
+__all__ = [
+ "BoundProposition",
+ "BytesDigestMechanism",
+ "EvidenceBindingError",
+ "EvidenceBounds",
+ "EvidenceStore",
+ "EvaluatedProposition",
+ "MechanismDecision",
+ "MechanismOutcome",
+ "VerificationMechanism",
+ "VerificationSession",
+ "implementation_file_digest",
+]
diff --git a/src/verifier/core/geometry.py b/src/verifier/core/geometry.py
index eddaf85..79b8807 100644
--- a/src/verifier/core/geometry.py
+++ b/src/verifier/core/geometry.py
@@ -1,6 +1,7 @@
-"""Typed verification geometry for the additive VSTD-0.2 vertical slice.
+"""Terminology: abstract syntax tree (AST); intermediate representation (IR);
+Verifier Standard (VSTD).
-This module does not alter VSTD-0.1 or VSTD-DATA-0.1 receipts. It supplies a
+Typed verification geometry for the VSTD-2 vertical slice. This module supplies a
small common representation for describing *where* verification attaches,
*in what respect*, and why apparent closure must sometimes be refused.
@@ -18,7 +19,7 @@
from typing import Any, Iterable, Optional
-GEOMETRY_SCHEMA_VERSION = "VSTD-0.2"
+GEOMETRY_SCHEMA_VERSION = "VSTD-2"
class LocusKind(str, Enum):
@@ -259,7 +260,11 @@ class ReconstructionAttempt:
@dataclass(frozen=True)
class VerificationLayer:
- """One bounded order of verification under the adjacent-layer invariant."""
+ """One bounded adjacent verification order.
+
+ The class and its ``*_layer*`` fields retain their published compatibility
+ names. They represent VSTD-2 meta-verification orders, not numbered VSTD profiles.
+ """
layer_id: str
order: int
@@ -568,35 +573,54 @@ def validate(self) -> list[str]:
f"reconstruction {reconstruction.reconstruction_id!r} references unknown residual {residual_id!r}"
)
- layers_by_id = {layer.layer_id: layer for layer in self.verification_layers}
- orders = sorted(layer.order for layer in self.verification_layers)
+ orders_by_id = {
+ verification_order.layer_id: verification_order
+ for verification_order in self.verification_layers
+ }
+ orders = sorted(
+ verification_order.order
+ for verification_order in self.verification_layers
+ )
if orders and orders != list(range(orders[-1] + 1)):
- errors.append("verification layer orders must be contiguous and start at 0")
- for layer in self.verification_layers:
- if layer.order < 0:
- errors.append(f"verification layer {layer.layer_id!r} has negative order")
- if layer.subject_id not in subject_ids:
- errors.append(f"verification layer {layer.layer_id!r} has unknown subject")
- if layer.order == 0 and layer.verifies_layer_id is not None:
- errors.append("verification layer order 0 cannot verify another layer")
- if layer.order > 0:
- target = layers_by_id.get(layer.verifies_layer_id or "")
+ errors.append("verification orders must be contiguous and start at 0")
+ for verification_order in self.verification_layers:
+ if verification_order.order < 0:
+ errors.append(
+ f"verification order {verification_order.layer_id!r} has negative order"
+ )
+ if verification_order.subject_id not in subject_ids:
+ errors.append(
+ f"verification order {verification_order.layer_id!r} has unknown subject"
+ )
+ if verification_order.order == 0 and verification_order.verifies_layer_id is not None:
+ errors.append("verification order 0 cannot verify another order")
+ if verification_order.order > 0:
+ target = orders_by_id.get(verification_order.verifies_layer_id or "")
if target is None:
errors.append(
- f"verification layer {layer.layer_id!r} does not identify a previous layer"
+ f"verification order {verification_order.layer_id!r} does not identify a previous order"
)
- elif target.order != layer.order - 1:
+ elif target.order != verification_order.order - 1:
errors.append(
- f"verification layer {layer.layer_id!r} violates the adjacent-layer invariant"
+ f"verification order {verification_order.layer_id!r} violates the order-adjacency invariant"
)
- for coordinate_id in layer.coordinate_ids:
+ for coordinate_id in verification_order.coordinate_ids:
if coordinate_id not in coordinate_ids:
- errors.append(f"verification layer {layer.layer_id!r} has unknown coordinate")
- for mechanism_id in layer.mechanism_ids:
+ errors.append(
+ f"verification order {verification_order.layer_id!r} has unknown coordinate"
+ )
+ for mechanism_id in verification_order.mechanism_ids:
if mechanism_id not in mechanism_ids:
- errors.append(f"verification layer {layer.layer_id!r} has unknown mechanism")
- if layer.horizon_id and layer.horizon_id not in horizon_ids:
- errors.append(f"verification layer {layer.layer_id!r} has unknown horizon")
+ errors.append(
+ f"verification order {verification_order.layer_id!r} has unknown mechanism"
+ )
+ if (
+ verification_order.horizon_id
+ and verification_order.horizon_id not in horizon_ids
+ ):
+ errors.append(
+ f"verification order {verification_order.layer_id!r} has unknown horizon"
+ )
for novelty in self.novelties:
if novelty.residual_id not in residual_ids:
@@ -611,7 +635,7 @@ def assess_closure(self) -> ClosureAssessment:
coordinates must pass, and every material residual must be resolved or
honestly terminated at a horizon. Self-closure is stronger and refuses
all unresolved horizons, open valence, unverified mechanisms, and
- unaccounted verification layers.
+ unaccounted verification orders.
"""
ordinary: list[str] = list(self.validate())
@@ -659,29 +683,32 @@ def assess_closure(self) -> ClosureAssessment:
self_blockers.append(
f"mechanism {mechanism.mechanism_id!r} is not post-verified"
)
- for layer in self.verification_layers:
- if layer.horizon_id:
+ for verification_order in self.verification_layers:
+ if verification_order.horizon_id:
self_blockers.append(
- f"verification layer {layer.layer_id!r} terminates at a horizon"
+ f"verification order {verification_order.layer_id!r} terminates at a horizon"
)
if self.secondary_subject_id is None:
self_blockers.append("self-closure requires a secondary verification subject")
if not self.meta_focus_coordinate_ids:
self_blockers.append("self-closure requires an explicit meta-focus")
- layer_orders = {layer.order for layer in self.verification_layers}
- if not {0, 1}.issubset(layer_orders):
- self_blockers.append("self-closure requires adjacent V0 and V1 verification layers")
- for layer in self.verification_layers:
- for coordinate_id in layer.coordinate_ids:
+ verification_orders = {
+ verification_order.order
+ for verification_order in self.verification_layers
+ }
+ if not {0, 1}.issubset(verification_orders):
+ self_blockers.append("self-closure requires adjacent V0 and V1 verification orders")
+ for verification_order in self.verification_layers:
+ for coordinate_id in verification_order.coordinate_ids:
judgment = judgments.get(coordinate_id)
if judgment is None or judgment.status is not CoordinateStatus.VERIFIED:
self_blockers.append(
- f"verification layer {layer.layer_id!r} coordinate {coordinate_id!r} is not VERIFIED"
+ f"verification order {verification_order.layer_id!r} coordinate {coordinate_id!r} is not VERIFIED"
)
- if layer.order > 0 and not layer.evidence_ids:
+ if verification_order.order > 0 and not verification_order.evidence_ids:
self_blockers.append(
- f"higher verification layer {layer.layer_id!r} has no sufficiency evidence"
+ f"higher verification order {verification_order.layer_id!r} has no sufficiency evidence"
)
return ClosureAssessment(
diff --git a/src/verifier/core/grounding.py b/src/verifier/core/grounding.py
index 357b7ee..46d4a74 100644
--- a/src/verifier/core/grounding.py
+++ b/src/verifier/core/grounding.py
@@ -1,4 +1,7 @@
-"""Grounding validation for ``VSTD4-GDC-1`` -- rung 4.2, semantic binding.
+"""Terminology: grounded decision certificate (GDC); Boolean satisfiability problem (SAT);
+Verifier Standard (VSTD).
+
+Grounding validation for ``VSTD4-GDC-1`` -- rung 4.2, semantic binding.
A resolution proof establishes a fact about a *formula*. A VSTD claim is about
the *world*. The gap between them is an encoding, and an encoding is exactly
diff --git a/src/verifier/core/kernel.py b/src/verifier/core/kernel.py
index f970d5a..3b9f616 100644
--- a/src/verifier/core/kernel.py
+++ b/src/verifier/core/kernel.py
@@ -1,4 +1,7 @@
-"""The refutability kernel -- the whole trusted computing base of VSTD layer 4.
+"""Terminology: conjunctive normal form (CNF); grounded decision certificate (GDC);
+Boolean satisfiability problem (SAT); Verifier Standard (VSTD).
+
+The refutability kernel -- the whole trusted computing base of VSTD-4 Refutability.
Rung 4.7 is a claim about code, not a slogan: a certificate checker must be
radically simpler than the system that produced the claim, and must share no
@@ -145,7 +148,7 @@ def _tier_admissible(
# --------------------------------------------------------------------------
-# Propagation -- independently re-implemented; see module docstring
+# Propagation -- separately reimplemented from the producer path; see module docstring
# --------------------------------------------------------------------------
@@ -337,7 +340,7 @@ def _decision_shape(certificate: DecisionCertificate) -> Optional[str]:
if not actual and expected == "transcript":
return (
"UNKNOWN carries no indeterminacy transcript; a refusal without evidence "
- "is not a layer-4 verdict"
+ "is not a VSTD-4 verdict"
)
if (
certificate.header.verdict is Verdict.FAIL
@@ -440,7 +443,7 @@ def check(
# Rung 4.7 honesty: a tier this kernel does not implement is UNKNOWN, not
# FAIL. Silently mis-accepting an unimplemented construct would be exactly
- # the semantic mismatch layer 4 prohibits.
+ # the semantic mismatch VSTD-4 Refutability prohibits.
if header.tier is CostTier.SAT_PRESERVING:
return _refuse(
IndeterminacyReason.VERIFIER_UNAVAILABLE,
@@ -583,7 +586,7 @@ def check(
if decision.transcript is None:
return _reject(
"UNKNOWN carries no indeterminacy transcript; a refusal without evidence "
- "is not a layer-4 verdict",
+ "is not a VSTD-4 verdict",
literals=literals,
hints=hints_present,
)
diff --git a/src/verifier/core/provenance.py b/src/verifier/core/provenance.py
index c19f001..2d8b66d 100644
--- a/src/verifier/core/provenance.py
+++ b/src/verifier/core/provenance.py
@@ -1,4 +1,6 @@
-"""Dynamic provenance capture and environment discovery for VSTD."""
+"""Terminology: Verifier Standard (VSTD).
+
+Dynamic provenance capture and environment discovery for VSTD."""
from __future__ import annotations
diff --git a/src/verifier/core/receipt.py b/src/verifier/core/receipt.py
index 0347fad..48bab10 100644
--- a/src/verifier/core/receipt.py
+++ b/src/verifier/core/receipt.py
@@ -1,4 +1,10 @@
-"""Canonical receipt model, canonicalization algorithm, and digest verification for VSTD-0.1."""
+"""Terminology: command-line interface (CLI); identifier (ID); JavaScript Object Notation (JSON);
+Boolean satisfiability problem (SAT); Secure Hash Algorithm 256-bit (SHA-256);
+trusted computing base (TCB); Unicode Transformation Format, 8-bit (UTF-8);
+Verifier Standard (VSTD).
+
+Canonical receipt model, canonicalization algorithm, and digest verification for
+VSTD-1 claim-mechanics receipts."""
from __future__ import annotations
@@ -12,6 +18,10 @@
from .provenance import ProvenanceRecord
+CLAIM_SCHEMA_VERSION = "VSTD-1"
+CLAIM_RECEIPT_KIND = "claim_mechanics"
+
+
def canonical_json_dumps(payload: Any) -> str:
"""Deterministic JSON serialization.
@@ -96,7 +106,10 @@ def to_dict(self) -> dict[str, Any]:
@dataclass
class VstdReceipt:
+ """Mutable in-memory model of a canonically digested VSTD-1 claim receipt."""
+
schema_version: str
+ receipt_kind: str
receipt_id: str
claim: ClaimSpec
evidence: EvidencePayload
@@ -107,10 +120,17 @@ class VstdReceipt:
canonical_digest: str = ""
execution_metadata: Optional[ExecutionMetadata] = None
+ def __post_init__(self) -> None:
+ if self.schema_version != CLAIM_SCHEMA_VERSION:
+ raise ValueError(f"schema_version must be {CLAIM_SCHEMA_VERSION}")
+ if self.receipt_kind != CLAIM_RECEIPT_KIND:
+ raise ValueError(f"receipt_kind must be {CLAIM_RECEIPT_KIND}")
+
def get_stable_payload(self) -> dict[str, Any]:
"""Extract only deterministic, location-independent fields for canonical hashing."""
return {
"schema_version": self.schema_version,
+ "receipt_kind": self.receipt_kind,
"receipt_id": self.receipt_id,
"claim": self.claim.to_dict(),
"evidence": self.evidence.to_dict(),
@@ -144,6 +164,7 @@ def to_dict(self) -> dict[str, Any]:
self.compute_and_set_digest()
return {
"schema_version": self.schema_version,
+ "receipt_kind": self.receipt_kind,
"receipt_id": self.receipt_id,
"canonical_digest": self.canonical_digest,
"claim": self.claim.to_dict(),
@@ -172,6 +193,7 @@ def save_to_directory(self, out_dir: Path) -> Path:
"receipt_id": self.receipt_id,
"canonical_digest": self.canonical_digest,
"schema_version": self.schema_version,
+ "receipt_kind": self.receipt_kind,
"files": {
"receipt.json": hashlib.sha256(receipt_path.read_bytes()).hexdigest(),
"claim.json": hashlib.sha256(claim_path.read_bytes()).hexdigest(),
@@ -201,17 +223,21 @@ def save_to_directory(self, out_dir: Path) -> Path:
def generate_receipt_markdown_report(receipt: VstdReceipt) -> str:
- """Generate human-readable audit report for the receipt."""
+ """Generate a human-readable checker report for the receipt."""
audit = receipt.independent_audit
prov = receipt.provenance
claim = receipt.claim
+ independence = audit.independence_basis
+
return f"""# VSTD Receipt Report — {receipt.receipt_id}
> **Canonical Digest:** `{receipt.canonical_digest}`
> **Schema Version:** `{receipt.schema_version}`
+> **Receipt Kind:** `{receipt.receipt_kind}`
> **Verification Status:** `{claim.status}`
-> **Independent Audit Verdict:** `{audit.overall_verdict.value}`
+> **Checker Verdict:** `{audit.overall_verdict.value}`
+> **Independent Verification:** `{'EVIDENCED' if independence.independently_verified else 'NOT_DEMONSTRATED'}`
---
@@ -228,9 +254,12 @@ def generate_receipt_markdown_report(receipt: VstdReceipt) -> str:
---
-## 2. Independent Audit (VSTD Independent Checker)
+## 2. Bundled Checker Result
-The verification was evaluated by an independent checker with zero shared solver code.
+The bundled checker used its recorded implementation and trusted computing base. Running
+it twice, or obtaining matching results, does not establish that separate independent
+actors performed the runs. Actor, implementation, and runtime separation require their
+own bound evidence.
- **SAT Status:** `{'Satisfiable' if audit.sat_result.satisfiable else 'Unsatisfiable'}` (decisions={audit.sat_result.decisions_count}, propagations={audit.sat_result.propagations_count})
- **Grounding Status:** `{audit.grounding_result.grounding_status.value}`
@@ -246,6 +275,11 @@ def generate_receipt_markdown_report(receipt: VstdReceipt) -> str:
{chr(10).join(f"{k}: {v}" for k, v in audit.trusted_computing_base.items())}
```
+### Independence Basis
+```yaml
+{chr(10).join(f"{k}: {v}" for k, v in independence.to_dict().items())}
+```
+
---
## 3. Provenance & Execution Environment
@@ -265,7 +299,7 @@ def generate_receipt_markdown_report(receipt: VstdReceipt) -> str:
## 4. Reproducibility Instructions
-To reproduce this receipt independently using the VSTD CLI:
+To reproduce the stored checks using the VSTD CLI:
```bash
vstd reproduce receipts/{receipt.receipt_id}
@@ -275,5 +309,5 @@ def generate_receipt_markdown_report(receipt: VstdReceipt) -> str:
---
-*Generated by VSTD Runtime v0.1.0*
+*Generated by the VSTD-1 claim-mechanics reference runtime.*
"""
diff --git a/src/verifier/core/refutation.py b/src/verifier/core/refutation.py
index d26532b..c59e6e5 100644
--- a/src/verifier/core/refutation.py
+++ b/src/verifier/core/refutation.py
@@ -1,6 +1,11 @@
-"""Refutation certificates for VSTD layer 4 (refutability).
+"""Terminology: conjunctive normal form (CNF); Davis-Putnam-Logemann-Loveland (DPLL);
+deletion resolution asymmetric tautology (DRAT); grounded decision certificate (GDC);
+nondeterministic polynomial time (NP); reverse unit propagation (RUP);
+Boolean satisfiability problem (SAT); unsatisfiable (UNSAT); Verifier Standard (VSTD).
-Layer 4 requires that every verdict -- pass **and** fail -- carry an artifact an
+Refutation certificates for VSTD-4 Refutability.
+
+The Refutability coordinate requires that every verdict -- pass **and** fail -- carry an artifact an
independent party can check without the declarant's cooperation.
A satisfiable result already carries such an artifact: the model. Anyone can
@@ -85,7 +90,7 @@ def _is_tautology(clause: Sequence[int]) -> bool:
@dataclass(frozen=True)
class RefutationCertificate:
- """A clausal refutation proof, independently checkable without re-solving."""
+ """A clausal refutation proof that a consumer can check without re-solving."""
proof: list[list[int]]
n_vars: int
diff --git a/src/verifier/core/reproducibility.py b/src/verifier/core/reproducibility.py
index ee193d9..06d6d06 100644
--- a/src/verifier/core/reproducibility.py
+++ b/src/verifier/core/reproducibility.py
@@ -1,4 +1,6 @@
-"""Reproducibility taxonomy and verification comparison levels.
+"""Terminology: Boolean satisfiability problem (SAT); Verifier Standard (VSTD).
+
+Reproduction-fidelity taxonomy and verification comparison states.
Defines the formal gradient of reproducibility for computational and formal claims.
"""
@@ -9,7 +11,7 @@
class ReproducibilityLevel(str, Enum):
- """Monotone levels of reproduction fidelity."""
+ """Monotone reproduction-fidelity states; class name retained for compatibility."""
BITWISE_IDENTICAL = "BITWISE_IDENTICAL"
"""Exact byte-for-byte identity of all generated artifacts, receipts, and hashes."""
@@ -23,11 +25,14 @@ class ReproducibilityLevel(str, Enum):
truth values and proof certificates, though internal trace order or solver step counts may differ."""
RESULT_EQUIVALENT = "RESULT_EQUIVALENT"
- """High-level verification verdict (VERIFIED/FALSIFIED) and primary output metrics agree within
+ """Summary verification verdict (VERIFIED/FALSIFIED) and primary output metrics agree within
declared error tolerance, but internal intermediate proof structures may differ."""
SEMANTIC_REPRODUCTION = "SEMANTIC_REPRODUCTION"
- """The underlying formal proposition is sustained under an independent translation or alternate solver."""
+ """The proposition is sustained under a separately implemented translation or solver.
+
+ This state does not establish distinct actors.
+ """
def compare_reproduction_level(
@@ -39,8 +44,13 @@ def compare_reproduction_level(
reproduced_evidence_hash: str | None = None,
original_raw_bytes: bytes | None = None,
reproduced_raw_bytes: bytes | None = None,
-) -> ReproducibilityLevel:
- """Classify the observed reproduction fidelity between two verification runs."""
+) -> ReproducibilityLevel | None:
+ """Return the strongest reproduction state earned by the supplied comparison evidence.
+
+ ``None`` means that these inputs do not establish a taxonomy state. A
+ matching verdict without matching primary metrics cannot establish result
+ equivalence, and a verdict mismatch cannot establish semantic reproduction.
+ """
if original_raw_bytes is not None and reproduced_raw_bytes is not None:
if original_raw_bytes == reproduced_raw_bytes:
return ReproducibilityLevel.BITWISE_IDENTICAL
@@ -55,7 +65,4 @@ def compare_reproduction_level(
):
return ReproducibilityLevel.EVIDENCE_EQUIVALENT
- if original_verdict == reproduced_verdict:
- return ReproducibilityLevel.RESULT_EQUIVALENT
-
- return ReproducibilityLevel.SEMANTIC_REPRODUCTION
+ return None
diff --git a/src/verifier/core/run.py b/src/verifier/core/run.py
index 9aee4cb..3e40e30 100644
--- a/src/verifier/core/run.py
+++ b/src/verifier/core/run.py
@@ -1,22 +1,22 @@
-"""Generic proof-carrying computational run capture for VSTD.
+"""Terminology: JavaScript Object Notation (JSON); Boolean satisfiability problem (SAT);
+Secure Hash Algorithm 256-bit (SHA-256); Verifier Standard (VSTD);
+YAML Ain't Markup Language (YAML).
+
+Generic computational run receipt capture for VSTD-1.
This module implements the smallest working version of the "wrap any consequential
computation and get a receipt" primitive described in the VSTD program graph.
-It deliberately reuses the existing VSTD-0.1 canonicalization/digest machinery
-(``verifier.core.receipt``), provenance discovery (``verifier.core.provenance``),
-and reproducibility taxonomy (``verifier.core.reproducibility``) rather than
-introducing a parallel schema. No new standard version is declared here — this is
-an implementation living under ``schema_version = "VSTD-0.1"`` with a distinct
-``receipt_kind`` discriminator (``generic_computational_run``) so existing
-``VstdReceipt`` (SAT/derivation-shaped, ``receipt_kind = "claim_verification"``)
-and ``VstdDataReceipt`` (dataset-provenance-shaped) documents are untouched.
+It reuses the VSTD-1 receipt canonicalization, provenance discovery, and
+reproducibility taxonomy rather than introducing a parallel schema. The required
+``receipt_kind`` discriminator ``generic_computational_run`` distinguishes this
+profile from VSTD-1 claim-mechanics receipts.
Design commitments (do not weaken without updating tests + docs):
1. **Claims are not flattened.** "The command exited 0", "the declared output files
exist with these digests", "an evaluator computed this metric", "the run's inputs
trace to a provenance root", and "an external party reported a score" are five
- different, independently falsifiable statements. They are recorded as five
+ different, separately falsifiable statements. They are recorded as five
distinct fields under :class:`RunClaims`, never collapsed into one boolean.
2. **Fail closed.** A missing declared input aborts the run *before* executing the
command (no fabricated "it probably would have worked"). A missing declared
@@ -25,13 +25,15 @@
commands are accepted, closing off the shell-indirection attack class.
3. **External evaluation is never auto-promoted.** If a manifest declares that an
organizer/leaderboard reported a score, that is stored as an
- :class:`ExternalEvaluationEvidence` record with ``attested=False`` unless the
- manifest itself supplies a checkable evidence reference. Its presence never
- flips ``execution_completed`` or any other locally-checked claim to true.
+ :class:`ExternalEvaluationEvidence` record with ``attested=False``. A supplied
+ evidence reference is recorded but not dereferenced or verified by this runtime.
+ Its presence never flips any locally checked claim to true.
4. **Reproduction fidelity is classified, not asserted.** Rehashing on-disk output
artifacts (always available, side-effect free) is distinguished from re-running
- the recorded command (only performed when explicitly requested via ``rerun``),
- and nondeterministic runs are never permitted to claim ``BITWISE_IDENTICAL``.
+ the recorded command (only performed when explicitly requested via ``rerun``).
+ The generic rerun compares the declared output bytes and execution outcome, so
+ it can establish only scoped ``CONTENT_IDENTICAL``; a determinism declaration
+ earns no reproduction-fidelity state.
"""
from __future__ import annotations
@@ -43,7 +45,6 @@
import time
from dataclasses import dataclass
from datetime import datetime, timezone
-from enum import Enum
from pathlib import Path
from typing import Any, Mapping, Optional
@@ -54,42 +55,62 @@
)
from verifier.core.receipt import compute_canonical_digest
from verifier.core.reproducibility import ReproducibilityLevel
-
-RUN_SCHEMA_VERSION = "VSTD-0.1"
-RUN_RECEIPT_KIND = "generic_computational_run"
+from verifier.core.run_support import (
+ RUN_RECEIPT_KIND,
+ RUN_SCHEMA_VERSION,
+ DeterminismDeclaration,
+ RunError,
+ RunOutcome,
+)
+from verifier.core.run_planning import _validated_command, describe_run_plan, load_manifest
_SNIPPET_LIMIT = 4000
_DEFAULT_TIMEOUT_SECONDS = 300
-def _digest_if_available(path: Path, label: str) -> str:
+def _digest_if_available(path: Path, label: str, *alternatives: Path) -> str:
+ for candidate in (path, *alternatives):
+ try:
+ return "sha256:" + hashlib.sha256(candidate.read_bytes()).hexdigest()
+ except OSError:
+ continue
+ return f"UNAVAILABLE:{label}"
+
+
+def _implementation_inventory_digest(paths: tuple[Path, ...]) -> str:
+ """Hash the named module bytes without collapsing producer/checker boundaries."""
+
+ digest = hashlib.sha256()
try:
- return "sha256:" + hashlib.sha256(path.read_bytes()).hexdigest()
+ for path in paths:
+ payload = path.read_bytes()
+ digest.update(path.name.encode("utf-8") + b"\0")
+ digest.update(len(payload).to_bytes(8, "big"))
+ digest.update(payload)
except OSError:
- return f"UNAVAILABLE:{label}"
+ return "UNAVAILABLE:generic-run-module-inventory"
+ return "sha256:" + digest.hexdigest()
-def _run_layer4_binding(
+def _assessment_context(
manifest: Mapping[str, Any],
*,
falsification_condition: str,
) -> dict[str, Any]:
- """Bind the verifier, bounds, precommitment, and refutation surface.
-
- Historical generic-run receipts omit this block and retain their canonical
- digests. New captures always include it, including explicit empty or
- undeclared values; absence is evidence of a missing rung, not permission to
- infer that a bound or precommitment existed.
- """
+ """Serialize bounded generic-run mechanism and refutation context."""
raw_bounds = manifest.get("resource_bounds", {})
if not isinstance(raw_bounds, Mapping):
raise RunError("resource_bounds must be an object")
- bounds: dict[str, int] = {}
- for name in (
+ bound_fields = (
"verification_cost_bound",
"memory_bound",
"certificate_size_bound",
- ):
+ )
+ unknown_bounds = sorted(set(raw_bounds) - set(bound_fields))
+ if unknown_bounds:
+ raise RunError(f"resource_bounds has unknown fields: {', '.join(unknown_bounds)}")
+ bounds: dict[str, int] = {}
+ for name in bound_fields:
value = raw_bounds.get(name, 0)
if type(value) is not int or value < 0:
raise RunError(f"resource_bounds.{name} must be a non-negative integer")
@@ -101,16 +122,38 @@ def _run_layer4_binding(
surface = dict(raw_surface or {})
surface.setdefault("admissible_refutations", [])
surface.setdefault("excluded_claims", ["PHYSICAL_WORLD_COMPLETENESS"])
- surface.setdefault("legacy_falsification_condition", falsification_condition)
+ surface.setdefault("falsification_condition", falsification_condition)
+ for name in ("admissible_refutations", "excluded_claims"):
+ if not isinstance(surface[name], list) or not all(
+ isinstance(item, str) for item in surface[name]
+ ):
+ raise RunError(f"refutation_surface.{name} must be an array of strings")
+ if not isinstance(surface["falsification_condition"], str):
+ raise RunError("refutation_surface.falsification_condition must be a string")
here = Path(__file__).resolve()
+ implementation_modules = tuple(
+ here.with_name(name)
+ for name in (
+ "run.py",
+ "run_support.py",
+ "run_planning.py",
+ "run_validation.py",
+ "run_inspection.py",
+ "run_reproduction.py",
+ "run_impact.py",
+ )
+ )
specification = here.parents[3] / "standard" / "VSTD-1.md"
+ packaged_specification = here.parents[1] / "specifications" / "VSTD-1.md"
verifier = {
"specification_hash": _digest_if_available(
- specification, "standard/VSTD-1.md"
+ specification, "standard/VSTD-1.md", packaged_specification
+ ),
+ "implementation_hash": _implementation_inventory_digest(implementation_modules),
+ "parser_hash": _digest_if_available(
+ here.with_name("run_validation.py"), "core/run_validation.py"
),
- "implementation_hash": _digest_if_available(here, "core/run.py"),
- "parser_hash": _digest_if_available(here, "core/run.py"),
"certificate_format": "VSTD1-GENERIC-RUN",
"format_fragment": "CAPTURE,VALIDATE,REPRODUCE",
"dependencies": ["python-stdlib"],
@@ -124,25 +167,6 @@ def _run_layer4_binding(
}
-class RunError(RuntimeError):
- """Raised for manifest or capture errors that must fail closed."""
-
-
-class RunOutcome(str, Enum):
- COMPLETED = "COMPLETED"
- NONZERO_EXIT = "NONZERO_EXIT"
- MISSING_INPUT = "MISSING_INPUT"
- MISSING_OUTPUT = "MISSING_OUTPUT"
- TIMEOUT = "TIMEOUT"
- EXCEPTION = "EXCEPTION"
-
-
-class DeterminismDeclaration(str, Enum):
- DETERMINISTIC = "DETERMINISTIC"
- NONDETERMINISTIC = "NONDETERMINISTIC"
- UNKNOWN = "UNKNOWN"
-
-
def _now_utc() -> str:
return datetime.now(timezone.utc).isoformat()
@@ -237,7 +261,7 @@ class EvaluatorClaim:
evaluator_name: str
metric_name: str
value: Any
- computed_by: str # "local_reference_evaluator" | "declared_by_manifest_author"
+ computed_by: str # "bound_output_extraction" | "declared_by_manifest_author"
verified_independently: bool
def to_dict(self) -> dict[str, Any]:
@@ -255,10 +279,10 @@ class ExternalEvaluationEvidence:
"""Explicit, bounded slot for organizer/third-party reported results.
Presence of this record NEVER means the runtime cryptographically or
- independently verified the external event described. ``attested``
- distinguishes a claim carrying real checkable evidence (a signature, a
- linked artifact digest) from a bare unverifiable assertion. Default is
- the least trusting classification.
+ verified the external event described through a separate mechanism or actor.
+ ``evidence_kind`` and
+ ``evidence_ref`` preserve what the manifest supplied; ``attested`` remains
+ false because this capture path does not dereference or verify that evidence.
"""
source: str
@@ -281,17 +305,13 @@ def to_dict(self) -> dict[str, Any]:
@classmethod
def from_manifest(cls, d: Mapping[str, Any]) -> "ExternalEvaluationEvidence":
evidence_kind = str(d.get("evidence_kind", "UNVERIFIED_ASSERTION")).upper()
- # Fail closed: only LINKED_ARTIFACT/SIGNED_ATTESTATION with a concrete
- # evidence_ref may claim attested=True. A bare assertion never can,
- # regardless of what the manifest author writes in "attested".
- attested = bool(d.get("attested", False)) and evidence_kind != "UNVERIFIED_ASSERTION" and bool(d.get("evidence_ref"))
return cls(
source=str(d.get("source", "unspecified")),
description=str(d.get("description", "")),
reported_value=d.get("reported_value"),
evidence_kind=evidence_kind,
evidence_ref=d.get("evidence_ref"),
- attested=attested,
+ attested=False,
)
@@ -355,7 +375,7 @@ def _resolve_provenance_linkage(base_dir: Path, root: Mapping[str, Any]) -> Prov
class RunClaims:
"""Distinct, non-flattened claims a run receipt may make.
- Each field is an independently falsifiable statement. They must never be
+ Each field is a separately falsifiable statement. They must never be
collapsed into a single pass/fail boolean — see module docstring.
"""
@@ -392,7 +412,7 @@ class GenericRunReceipt:
claims: RunClaims
provenance_linkage: tuple[ProvenanceLinkage, ...]
reproducibility: dict[str, Any]
- layer4_binding: Optional[dict[str, Any]] = None
+ assessment_context: dict[str, Any]
canonical_digest: str = ""
def get_stable_payload(self) -> dict[str, Any]:
@@ -440,9 +460,8 @@ def get_stable_payload(self) -> dict[str, Any]:
"claims": self.claims.to_dict(),
"provenance_linkage": [p.to_dict() for p in self.provenance_linkage],
"reproducibility": self.reproducibility,
+ "assessment_context": self.assessment_context,
}
- if self.layer4_binding is not None:
- payload["layer4_binding"] = self.layer4_binding
return payload
def compute_and_set_digest(self) -> str:
@@ -472,9 +491,8 @@ def to_dict(self) -> dict[str, Any]:
"claims": self.claims.to_dict(),
"provenance_linkage": [p.to_dict() for p in self.provenance_linkage],
"reproducibility": self.reproducibility,
+ "assessment_context": self.assessment_context,
}
- if self.layer4_binding is not None:
- payload["layer4_binding"] = self.layer4_binding
return payload
def save_to_directory(self, out_dir: Path) -> Path:
@@ -549,8 +567,8 @@ def generate_run_receipt_markdown(receipt: GenericRunReceipt) -> str:
f"- **Reported value:** `{ext.reported_value}`\n"
f"- **Evidence kind:** `{ext.evidence_kind}`\n"
f"- **Evidence reference:** `{ext.evidence_ref}`\n"
- f"- **Attested by the runtime:** `{ext.attested}` "
- f"({'a checkable evidence reference backs this value' if ext.attested else 'this is an UNVERIFIED external assertion — recorded for bookkeeping only, NOT independently checked'})\n"
+ f"- **Verified by this runtime:** `{ext.attested}` "
+ "(the reference is recorded but not checked by a separate mechanism or actor)\n"
)
else:
external_md = "_(no external evaluation evidence declared — this run makes no claim about any external score, leaderboard, or organizer report)_"
@@ -637,94 +655,19 @@ def generate_run_receipt_markdown(receipt: GenericRunReceipt) -> str:
## 8. Reproduction
```bash
-vstd reproduce {receipt.receipt_id if False else ''}
+vstd reproduce
```
Highest demonstrated reproduction fidelity: `{receipt.reproducibility.get("highest_demonstrated_level") or "NOT YET REPRODUCED"}`.
-Declared supported ceiling (determinism-bounded): `{receipt.reproducibility.get("declared_ceiling")}`.
+Declared supported ceiling (bundled mechanism): `{receipt.reproducibility.get("declared_ceiling")}`.
---
-*Generated by VSTD Generic Run Runtime (VSTD-0.1, receipt_kind=generic_computational_run).*
+*Generated by the VSTD-1 generic-run reference runtime
+(`receipt_kind = generic_computational_run`).*
"""
-def load_manifest(manifest_path: Path) -> dict[str, Any]:
- text = manifest_path.read_text(encoding="utf-8")
- if manifest_path.suffix.lower() in (".yaml", ".yml"):
- try:
- import yaml # type: ignore[import-untyped]
- except ImportError as exc:
- raise RunError(
- "YAML manifest support is optional; install verifier-standard[yaml] or use JSON"
- ) from exc
- data = yaml.safe_load(text)
- else:
- data = json.loads(text)
- if not isinstance(data, dict):
- raise RunError(f"Manifest at {manifest_path} must decode to a JSON/YAML object.")
- return data
-
-
-def _validated_command(manifest: Mapping[str, Any]) -> tuple[str, ...]:
- command = manifest.get("command")
- if not isinstance(command, list) or not command or not all(isinstance(c, str) for c in command):
- raise RunError(
- "manifest 'command' must be a non-empty list of strings (argv form). "
- "String/shell commands are rejected to close off shell-indirection attacks."
- )
- return tuple(command)
-
-
-def describe_run_plan(manifest: Mapping[str, Any], manifest_dir: Path) -> dict[str, Any]:
- """Return the observable execution and capture paths without executing them.
-
- This is a review aid, not a sandbox analysis. A subprocess may access resources
- that are not named in a manifest, so the result deliberately says that the
- command's effective access remains outside VSTD's observation boundary.
- """
-
- command = _validated_command(manifest)
- root = manifest_dir.resolve()
-
- def path_record(path_value: Any) -> dict[str, Any]:
- declared = str(path_value)
- resolved = (root / declared).resolve()
- try:
- resolved.relative_to(root)
- outside = False
- except ValueError:
- outside = True
- return {
- "declared": declared,
- "resolved": str(resolved),
- "outside_manifest_directory": outside,
- }
-
- def artifacts(key: str) -> list[dict[str, Any]]:
- result: list[dict[str, Any]] = []
- for entry in manifest.get(key, []):
- if not isinstance(entry, Mapping) or "path" not in entry:
- raise RunError(f"manifest '{key}' entries must be objects with a path")
- record = path_record(entry["path"])
- record["role"] = str(entry.get("role", key[:-1]))
- record["present_before_execution"] = Path(record["resolved"]).is_file()
- result.append(record)
- return result
-
- return {
- "executes_without_sandbox": True,
- "manifest_directory": str(root),
- "command": list(command),
- "cwd": path_record(manifest.get("cwd", ".")),
- "repo_dir": path_record(manifest.get("repo_dir", ".")),
- "inputs": artifacts("inputs"),
- "outputs": artifacts("outputs"),
- "observation_limit": (
- "Declared paths describe receipt capture only; they do not confine the "
- "subprocess or enumerate everything it may access."
- ),
- }
def capture_run(
@@ -732,7 +675,7 @@ def capture_run(
manifest_dir: Path,
receipt_id: Optional[str] = None,
) -> GenericRunReceipt:
- """Execute the manifest-declared command and capture a proof-carrying receipt.
+ """Execute the manifest-declared command and capture a computational run receipt.
Fails closed (raises :class:`RunError`) on manifest shape errors that would
otherwise silently under-specify the claim (non-list command, absent claim
@@ -896,11 +839,11 @@ def capture_run(
for key in [k for k in pointer.split(".") if k]:
node = node[key]
value = node
- computed_by = "local_reference_evaluator"
- verified_independently = True
+ computed_by = "bound_output_extraction"
+ verified_independently = False
except Exception:
value = None
- computed_by = "local_reference_evaluator"
+ computed_by = "bound_output_extraction"
verified_independently = False
evaluator_claims.append(
EvaluatorClaim(
@@ -929,17 +872,8 @@ def capture_run(
key_files=key_files,
)
- supported_levels = [
- ReproducibilityLevel.CONTENT_IDENTICAL.value,
- ReproducibilityLevel.EVIDENCE_EQUIVALENT.value,
- ReproducibilityLevel.RESULT_EQUIVALENT.value,
- ReproducibilityLevel.SEMANTIC_REPRODUCTION.value,
- ]
- if determinism == DeterminismDeclaration.DETERMINISTIC.value:
- supported_levels.insert(0, ReproducibilityLevel.BITWISE_IDENTICAL.value)
- ceiling = ReproducibilityLevel.BITWISE_IDENTICAL.value
- else:
- ceiling = ReproducibilityLevel.CONTENT_IDENTICAL.value
+ supported_levels = [ReproducibilityLevel.CONTENT_IDENTICAL.value]
+ ceiling = ReproducibilityLevel.CONTENT_IDENTICAL.value
receipt = GenericRunReceipt(
schema_version=RUN_SCHEMA_VERSION,
@@ -968,7 +902,7 @@ def capture_run(
"supported_levels": supported_levels,
"reproduction_command": "vstd reproduce ",
},
- layer4_binding=_run_layer4_binding(
+ assessment_context=_assessment_context(
manifest,
falsification_condition=str(
claim_block.get("falsification_condition", "")
@@ -979,234 +913,15 @@ def capture_run(
return receipt
-def _rebuild_stable_payload_from_dict(data: Mapping[str, Any]) -> dict[str, Any]:
- src = data.get("source_state", {})
- payload = {
- "schema_version": data.get("schema_version"),
- "receipt_kind": data.get("receipt_kind"),
- "receipt_id": data.get("receipt_id"),
- "claim_title": data.get("claim_title"),
- "claim_statement": data.get("claim_statement"),
- "claim_scope": data.get("claim_scope"),
- "claim_limitations": data.get("claim_limitations"),
- "falsification_condition": data.get("falsification_condition"),
- "source_state_stable": {
- "target_name": src.get("target_name"),
- "portable_repository_id": src.get("portable_repository_id"),
- "git_commit_sha": src.get("git", {}).get("commit_sha"),
- "git_branch": src.get("git", {}).get("branch"),
- "git_is_dirty": src.get("git", {}).get("is_dirty"),
- "git_dirty_files": src.get("git", {}).get("dirty_files", []),
- "source_file_hashes": src.get("source_file_hashes", {}),
- "runtime_python_version": src.get("runtime", {}).get("python_version"),
- },
- "inputs": data.get("inputs", []),
- "outputs": data.get("outputs", []),
- "execution_stable": {
- "command": data.get("execution", {}).get("command"),
- "cwd": data.get("execution", {}).get("cwd"),
- "exit_code": data.get("execution", {}).get("exit_code"),
- "outcome": data.get("execution", {}).get("outcome"),
- "python_version": data.get("execution", {}).get("python_version"),
- "platform_system": data.get("execution", {}).get("platform_system"),
- "determinism_declared": data.get("execution", {}).get("determinism_declared"),
- "seed_declared": data.get("execution", {}).get("seed_declared"),
- "stdout_sha256": data.get("execution", {}).get("stdout_sha256"),
- "stderr_sha256": data.get("execution", {}).get("stderr_sha256"),
- },
- "claims": data.get("claims", {}),
- "provenance_linkage": data.get("provenance_linkage", []),
- "reproducibility": data.get("reproducibility", {}),
- }
- if "layer4_binding" in data:
- payload["layer4_binding"] = data.get("layer4_binding")
- return payload
-
-
-def is_generic_run_receipt(data: Mapping[str, Any]) -> bool:
- return data.get("receipt_kind") == RUN_RECEIPT_KIND
-
-
-def validate_run_receipt(receipt_path_or_dir: Path) -> int:
- receipt_file = receipt_path_or_dir / "receipt.json" if receipt_path_or_dir.is_dir() else receipt_path_or_dir
- if not receipt_file.exists():
- print(f"[FAIL] Receipt file not found: {receipt_file}")
- return 1
- data = json.loads(receipt_file.read_text(encoding="utf-8"))
- recorded_digest = data.get("canonical_digest", "")
- recomputed = compute_canonical_digest(_rebuild_stable_payload_from_dict(data))
- if recomputed != recorded_digest:
- print(f"[FAIL] Canonical digest mismatch:\n Recorded: {recorded_digest}\n Recomputed: {recomputed}")
- return 1
- print(f"[PASS] Run receipt {data.get('receipt_id')} is valid.")
- print(f" Digest: {recorded_digest}")
- print(f" Outcome: {data.get('execution', {}).get('outcome')}")
- return 0
-
-
-def inspect_run_receipt(receipt_path_or_dir: Path) -> int:
- receipt_file = receipt_path_or_dir / "receipt.json" if receipt_path_or_dir.is_dir() else receipt_path_or_dir
- if not receipt_file.exists():
- print(f"Error: receipt not found at {receipt_file}")
- return 1
- data = json.loads(receipt_file.read_text(encoding="utf-8"))
- print("=" * 70)
- print(f"GENERIC RUN RECEIPT: {data.get('receipt_id')} ({data.get('schema_version')}/{data.get('receipt_kind')})")
- print("=" * 70)
- print(f"Canonical Digest: {data.get('canonical_digest')}")
- print(f"Claim: {data.get('claim_statement')}")
- ex = data.get("execution", {})
- print(f"Command: {' '.join(ex.get('command', []))}")
- print(f"Outcome: {ex.get('outcome')} (exit={ex.get('exit_code')})")
- c = data.get("claims", {})
- print("-" * 70)
- print("CLAIMS (distinct, not flattened):")
- print(f" execution_completed: {c.get('execution_completed')}")
- print(f" output_digests_recorded: {c.get('output_digests_recorded')}")
- print(f" all_declared_artifacts_present: {c.get('all_declared_artifacts_present')}")
- ext = c.get("external_evaluation")
- if ext:
- print(f" external_evaluation: reported={ext.get('reported_value')} attested={ext.get('attested')}")
- else:
- print(" external_evaluation: (none declared)")
- print("=" * 70)
- return 0
-
-
-def reproduce_run_receipt(receipt_path_or_dir: Path, rerun: bool = False) -> int:
- """Assess reproduction fidelity.
-
- By default this rehashes the declared output artifacts as they currently
- exist on disk relative to the receipt directory's manifest base (safe,
- side-effect free, always available). Pass ``rerun=True`` to additionally
- re-execute the recorded command and compare freshly produced outputs —
- this mutates on-disk state at the declared output paths and is therefore
- opt-in only.
- """
- receipt_dir = receipt_path_or_dir if receipt_path_or_dir.is_dir() else receipt_path_or_dir.parent
- receipt_file = receipt_dir / "receipt.json"
- if not receipt_file.exists():
- print(f"Error: receipt not found at {receipt_file}")
- return 1
- data = json.loads(receipt_file.read_text(encoding="utf-8"))
- # Inputs/outputs in the receipt are recorded as paths relative to the manifest's
- # own directory. The convention this runtime uses (see `vstd run`) is that
- # a receipt directory colocates receipt.json with a copy of the originating
- # manifest (manifest.source.json), so that directory is also the correct base
- # for resolving those relative paths during reproduction.
- base_dir = receipt_dir
-
- determinism = data.get("execution", {}).get("determinism_declared")
-
- if rerun:
- manifest_path = base_dir / "manifest.source.json"
- if not manifest_path.exists():
- manifest_path = base_dir / "manifest.json"
- if not manifest_path.exists():
- print(f"[WARN] No source manifest found under {base_dir}; cannot rerun. Falling back to artifact rehash.")
- rerun = False
- else:
- manifest = load_manifest(manifest_path)
- reproduced = capture_run(manifest, manifest_dir=base_dir, receipt_id=data.get("receipt_id"))
- original_outcome = data.get("execution", {}).get("outcome")
- reproduced_outcome = reproduced.execution.outcome
- outputs_match = all(
- o.sha256 == next((x.get("sha256") for x in data.get("outputs", []) if x.get("path") == o.path), None)
- for o in reproduced.outputs
- )
- if determinism == DeterminismDeclaration.DETERMINISTIC.value and outputs_match and original_outcome == reproduced_outcome:
- level = ReproducibilityLevel.BITWISE_IDENTICAL
- elif outputs_match and original_outcome == reproduced_outcome:
- level = ReproducibilityLevel.CONTENT_IDENTICAL
- elif original_outcome == reproduced_outcome:
- level = ReproducibilityLevel.RESULT_EQUIVALENT
- else:
- level = ReproducibilityLevel.SEMANTIC_REPRODUCTION
- print(f"[REPRODUCTION RESULT - RERUN] Level: {level.value}")
- print(f" Original outcome: {original_outcome}")
- print(f" Reproduced outcome: {reproduced_outcome}")
- print(f" Outputs match: {outputs_match}")
- return 0 if outputs_match and original_outcome == reproduced_outcome else 1
-
- # Default path: rehash on-disk artifacts only (no execution).
- mismatches: list[tuple[Any, Any, Optional[str]]] = []
- checked = 0
- for out in data.get("outputs", []):
- recorded_hash = out.get("sha256")
- path = base_dir / out["path"]
- if not path.exists():
- mismatches.append((out["path"], recorded_hash, None))
- continue
- checked += 1
- current_hash = sha256_file(path)
- if current_hash != recorded_hash:
- mismatches.append((out["path"], recorded_hash, current_hash))
-
- if not data.get("outputs"):
- print("[REPRODUCTION RESULT - ARTIFACT REHASH] No outputs were declared; nothing to compare.")
- return 0
-
- if mismatches:
- print(f"[REPRODUCTION RESULT - ARTIFACT REHASH] MISMATCH ({len(mismatches)} of {len(data.get('outputs', []))} outputs)")
- for path, recorded, current in mismatches:
- print(f" {path}: recorded={recorded} current={current}")
- return 1
-
- print(f"[REPRODUCTION RESULT - ARTIFACT REHASH] All {checked} on-disk output artifact(s) match recorded digests.")
- print(f" Reproduction level: {ReproducibilityLevel.CONTENT_IDENTICAL.value} (artifact-level; command was not re-executed - pass --rerun for a full rerun comparison)")
- return 0
-
-
-def compute_blast_radius_impacted_artifacts(dataset_receipt_file: Path, revoked_artifact_id: str) -> set[str]:
- """Forward blast radius of a revoked/invalidated artifact, plus the artifact itself.
-
- Reuses ``ProvenanceHypergraph.blast_radius`` from the existing VSTD-Graph-1
- runtime rather than reimplementing graph traversal here.
- """
- from verifier.data.models import ProvenanceHypergraph
-
- data = json.loads(dataset_receipt_file.read_text(encoding="utf-8"))
- hg = ProvenanceHypergraph.from_dict(data["hypergraph"])
- if revoked_artifact_id not in hg.artifacts:
- raise RunError(f"Artifact '{revoked_artifact_id}' not found in {dataset_receipt_file}.")
- affected = set(hg.blast_radius(revoked_artifact_id))
- affected.add(revoked_artifact_id)
- return affected
-
-
-def find_run_receipts_impacted_by_revocation(
- search_root: Path,
- dataset_receipt_file: Path,
- revoked_artifact_id: str,
-) -> list[dict[str, Any]]:
- """Answer: "which recorded runs need to be reconsidered because this upstream
- dataset-provenance artifact changed or became invalid?"
-
- Scans ``search_root`` recursively for ``receipt.json`` files that are generic
- run receipts (``receipt_kind == generic_computational_run``) and whose
- ``provenance_linkage`` references an artifact inside the forward blast radius
- of ``revoked_artifact_id`` (or the artifact itself). This composes the
- dataset-provenance hypergraph directly into run-receipt impact analysis
- instead of introducing a parallel lineage system.
- """
- impacted_artifacts = compute_blast_radius_impacted_artifacts(dataset_receipt_file, revoked_artifact_id)
- results: list[dict[str, Any]] = []
- for receipt_file in search_root.rglob("receipt.json"):
- try:
- data = json.loads(receipt_file.read_text(encoding="utf-8"))
- except Exception:
- continue
- if not is_generic_run_receipt(data):
- continue
- for link in data.get("provenance_linkage", []):
- if link.get("artifact_id") in impacted_artifacts:
- results.append(
- {
- "receipt_path": str(receipt_file),
- "receipt_id": data.get("receipt_id"),
- "matched_artifact_id": link.get("artifact_id"),
- "claim_statement": data.get("claim_statement"),
- }
- )
- break
- return results
+# Compatibility facade: historical imports continue to resolve from verifier.core.run.
+from verifier.core.run_impact import (
+ compute_blast_radius_impacted_artifacts,
+ find_run_receipts_impacted_by_revocation,
+)
+from verifier.core.run_inspection import inspect_run_receipt
+from verifier.core.run_reproduction import reproduce_run_receipt
+from verifier.core.run_validation import (
+ _rebuild_stable_payload_from_dict,
+ is_generic_run_receipt,
+ validate_run_receipt,
+)
diff --git a/src/verifier/core/run_impact.py b/src/verifier/core/run_impact.py
new file mode 100644
index 0000000..4ec1260
--- /dev/null
+++ b/src/verifier/core/run_impact.py
@@ -0,0 +1,68 @@
+"""Terminology: JavaScript Object Notation (JSON); Verifier Standard (VSTD).
+
+Recorded-ancestry impact analysis for generic-run receipts.
+"""
+
+from __future__ import annotations
+
+import json
+from pathlib import Path
+from typing import Any
+
+from verifier.core.run_support import RunError
+from verifier.core.run_validation import is_generic_run_receipt
+
+
+def compute_blast_radius_impacted_artifacts(dataset_receipt_file: Path, revoked_artifact_id: str) -> set[str]:
+ """Forward blast radius of a revoked/invalidated artifact, plus the artifact itself.
+
+ Reuses ``ProvenanceHypergraph.blast_radius`` from the existing VSTD-Graph-1
+ runtime rather than reimplementing graph traversal here.
+ """
+ from verifier.data.models import ProvenanceHypergraph
+
+ data = json.loads(dataset_receipt_file.read_text(encoding="utf-8"))
+ hg = ProvenanceHypergraph.from_dict(data["hypergraph"])
+ if revoked_artifact_id not in hg.artifacts:
+ raise RunError(f"Artifact '{revoked_artifact_id}' not found in {dataset_receipt_file}.")
+ affected = set(hg.blast_radius(revoked_artifact_id))
+ affected.add(revoked_artifact_id)
+ return affected
+
+
+def find_run_receipts_impacted_by_revocation(
+ search_root: Path,
+ dataset_receipt_file: Path,
+ revoked_artifact_id: str,
+) -> list[dict[str, Any]]:
+ """Answer: "which recorded runs need to be reconsidered because this upstream
+ dataset-provenance artifact changed or became invalid?"
+
+ Scans ``search_root`` recursively for ``receipt.json`` files that are generic
+ run receipts (``receipt_kind == generic_computational_run``) and whose
+ ``provenance_linkage`` references an artifact inside the forward blast radius
+ of ``revoked_artifact_id`` (or the artifact itself). This composes the
+ dataset-provenance hypergraph directly into run-receipt impact analysis
+ instead of introducing a parallel lineage system.
+ """
+ impacted_artifacts = compute_blast_radius_impacted_artifacts(dataset_receipt_file, revoked_artifact_id)
+ results: list[dict[str, Any]] = []
+ for receipt_file in search_root.rglob("receipt.json"):
+ try:
+ data = json.loads(receipt_file.read_text(encoding="utf-8"))
+ except Exception:
+ continue
+ if not is_generic_run_receipt(data):
+ continue
+ for link in data.get("provenance_linkage", []):
+ if link.get("artifact_id") in impacted_artifacts:
+ results.append(
+ {
+ "receipt_path": str(receipt_file),
+ "receipt_id": data.get("receipt_id"),
+ "matched_artifact_id": link.get("artifact_id"),
+ "claim_statement": data.get("claim_statement"),
+ }
+ )
+ break
+ return results
diff --git a/src/verifier/core/run_inspection.py b/src/verifier/core/run_inspection.py
new file mode 100644
index 0000000..110bb74
--- /dev/null
+++ b/src/verifier/core/run_inspection.py
@@ -0,0 +1,57 @@
+"""Terminology: JavaScript Object Notation (JSON); Verifier Standard (VSTD).
+
+Human-readable inspection of structurally valid generic-run receipts.
+"""
+
+from __future__ import annotations
+
+import json
+from pathlib import Path
+from typing import Mapping
+
+from verifier.core.run_validation import _run_payload_errors
+
+
+def inspect_run_receipt(receipt_path_or_dir: Path) -> int:
+ receipt_file = receipt_path_or_dir / "receipt.json" if receipt_path_or_dir.is_dir() else receipt_path_or_dir
+ if not receipt_file.exists():
+ print(f"Error: receipt not found at {receipt_file}")
+ return 1
+ try:
+ data = json.loads(receipt_file.read_text(encoding="utf-8"))
+ except (OSError, json.JSONDecodeError) as exc:
+ print(f"[FAIL] Receipt is not readable JSON: {exc}")
+ return 1
+ if not isinstance(data, Mapping):
+ print("[FAIL] Receipt root must be an object")
+ return 1
+ errors = _run_payload_errors(data)
+ if errors:
+ for error in errors:
+ print(f"[FAIL] {error}")
+ return 1
+ print("=" * 70)
+ print(f"GENERIC RUN RECEIPT: {data.get('receipt_id')} ({data.get('schema_version')}/{data.get('receipt_kind')})")
+ print("=" * 70)
+ print(f"Canonical Digest: {data.get('canonical_digest')}")
+ print(f"Claim: {data.get('claim_statement')}")
+ ex = data.get("execution", {})
+ print(f"Command: {' '.join(ex.get('command', []))}")
+ print(f"Outcome: {ex.get('outcome')} (exit={ex.get('exit_code')})")
+ c = data.get("claims", {})
+ print("-" * 70)
+ print("CLAIMS (distinct, not flattened):")
+ print(f" execution_completed: {c.get('execution_completed')}")
+ print(f" output_digests_recorded: {c.get('output_digests_recorded')}")
+ print(f" all_declared_artifacts_present: {c.get('all_declared_artifacts_present')}")
+ ext = c.get("external_evaluation")
+ if ext:
+ print(
+ " external_evaluation: "
+ f"reported={ext.get('reported_value')} recorded_attested={ext.get('attested')} "
+ "(not verified by inspect)"
+ )
+ else:
+ print(" external_evaluation: (none declared)")
+ print("=" * 70)
+ return 0
diff --git a/src/verifier/core/run_planning.py b/src/verifier/core/run_planning.py
new file mode 100644
index 0000000..44b40c8
--- /dev/null
+++ b/src/verifier/core/run_planning.py
@@ -0,0 +1,90 @@
+"""Terminology: JavaScript Object Notation (JSON); Verifier Standard (VSTD); YAML Ain't Markup Language (YAML).
+
+Side-effect-free generic-run manifest loading and execution-plan description.
+"""
+
+from __future__ import annotations
+
+import json
+from pathlib import Path
+from typing import Any, Mapping
+
+from verifier.core.run_support import RunError
+
+
+def load_manifest(manifest_path: Path) -> dict[str, Any]:
+ text = manifest_path.read_text(encoding="utf-8")
+ if manifest_path.suffix.lower() in (".yaml", ".yml"):
+ try:
+ import yaml # type: ignore[import-untyped]
+ except ImportError as exc:
+ raise RunError(
+ "YAML manifest support is optional; install verifier-standard[yaml] or use JSON"
+ ) from exc
+ data = yaml.safe_load(text)
+ else:
+ data = json.loads(text)
+ if not isinstance(data, dict):
+ raise RunError(f"Manifest at {manifest_path} must decode to a JSON/YAML object.")
+ return data
+
+
+def _validated_command(manifest: Mapping[str, Any]) -> tuple[str, ...]:
+ command = manifest.get("command")
+ if not isinstance(command, list) or not command or not all(isinstance(c, str) for c in command):
+ raise RunError(
+ "manifest 'command' must be a non-empty list of strings (argv form). "
+ "String/shell commands are rejected to close off shell-indirection attacks."
+ )
+ return tuple(command)
+
+
+def describe_run_plan(manifest: Mapping[str, Any], manifest_dir: Path) -> dict[str, Any]:
+ """Return the observable execution and capture paths without executing them.
+
+ This is a review aid, not a sandbox analysis. A subprocess may access resources
+ that are not named in a manifest, so the result deliberately says that the
+ command's effective access remains outside VSTD's observation boundary.
+ """
+
+ command = _validated_command(manifest)
+ root = manifest_dir.resolve()
+
+ def path_record(path_value: Any) -> dict[str, Any]:
+ declared = str(path_value)
+ resolved = (root / declared).resolve()
+ try:
+ resolved.relative_to(root)
+ outside = False
+ except ValueError:
+ outside = True
+ return {
+ "declared": declared,
+ "resolved": str(resolved),
+ "outside_manifest_directory": outside,
+ }
+
+ def artifacts(key: str) -> list[dict[str, Any]]:
+ result: list[dict[str, Any]] = []
+ for entry in manifest.get(key, []):
+ if not isinstance(entry, Mapping) or "path" not in entry:
+ raise RunError(f"manifest '{key}' entries must be objects with a path")
+ record = path_record(entry["path"])
+ record["role"] = str(entry.get("role", key[:-1]))
+ record["present_before_execution"] = Path(record["resolved"]).is_file()
+ result.append(record)
+ return result
+
+ return {
+ "executes_without_sandbox": True,
+ "manifest_directory": str(root),
+ "command": list(command),
+ "cwd": path_record(manifest.get("cwd", ".")),
+ "repo_dir": path_record(manifest.get("repo_dir", ".")),
+ "inputs": artifacts("inputs"),
+ "outputs": artifacts("outputs"),
+ "observation_limit": (
+ "Declared paths describe receipt capture only; they do not confine the "
+ "subprocess or enumerate everything it may access."
+ ),
+ }
diff --git a/src/verifier/core/run_reproduction.py b/src/verifier/core/run_reproduction.py
new file mode 100644
index 0000000..b3ffccd
--- /dev/null
+++ b/src/verifier/core/run_reproduction.py
@@ -0,0 +1,120 @@
+"""Terminology: JavaScript Object Notation (JSON); Verifier Standard (VSTD).
+
+Opt-in rerun and side-effect-free artifact-rehash reproduction assessment.
+"""
+
+from __future__ import annotations
+
+import json
+from pathlib import Path
+from typing import Any, Mapping, Optional
+
+from verifier.core.provenance import sha256_file
+from verifier.core.reproducibility import ReproducibilityLevel
+from verifier.core.run_planning import load_manifest
+from verifier.core.run_validation import _run_payload_errors
+
+
+def reproduce_run_receipt(receipt_path_or_dir: Path, rerun: bool = False) -> int:
+ """Assess reproduction fidelity.
+
+ By default this rehashes the declared output artifacts as they currently
+ exist on disk relative to the receipt directory's manifest base (safe,
+ side-effect free, always available). Pass ``rerun=True`` to additionally
+ re-execute the recorded command and compare freshly produced outputs —
+ this mutates on-disk state at the declared output paths and is therefore
+ opt-in only.
+ """
+ receipt_file = (
+ receipt_path_or_dir / "receipt.json"
+ if receipt_path_or_dir.is_dir()
+ else receipt_path_or_dir
+ )
+ receipt_dir = receipt_file.parent
+ if not receipt_file.exists():
+ print(f"Error: receipt not found at {receipt_file}")
+ return 1
+ try:
+ data = json.loads(receipt_file.read_text(encoding="utf-8"))
+ except (OSError, json.JSONDecodeError) as exc:
+ print(f"[FAIL] Receipt is not readable JSON: {exc}")
+ return 1
+ if not isinstance(data, Mapping):
+ print("[FAIL] Receipt root must be an object")
+ return 1
+ errors = _run_payload_errors(data)
+ if errors:
+ for error in errors:
+ print(f"[FAIL] {error}")
+ return 1
+ # Inputs/outputs in the receipt are recorded as paths relative to the manifest's
+ # own directory. The convention this runtime uses (see `vstd run`) is that
+ # a receipt directory colocates receipt.json with a copy of the originating
+ # manifest (manifest.source.json), so that directory is also the correct base
+ # for resolving those relative paths during reproduction.
+ base_dir = receipt_dir
+
+ if rerun:
+ manifest_path = base_dir / "manifest.source.json"
+ if not manifest_path.exists():
+ manifest_path = base_dir / "manifest.json"
+ if not manifest_path.exists():
+ print(f"[WARN] No source manifest found under {base_dir}; cannot rerun. Falling back to artifact rehash.")
+ rerun = False
+ else:
+ from verifier.core.run import capture_run
+
+ manifest = load_manifest(manifest_path)
+ reproduced = capture_run(manifest, manifest_dir=base_dir, receipt_id=data.get("receipt_id"))
+ original_outcome = data.get("execution", {}).get("outcome")
+ reproduced_outcome = reproduced.execution.outcome
+ original_outputs = {
+ str(item.get("path")): item.get("sha256")
+ for item in data.get("outputs", [])
+ }
+ reproduced_outputs = {item.path: item.sha256 for item in reproduced.outputs}
+ outputs_match = bool(original_outputs) and original_outputs == reproduced_outputs
+ outcomes_match = original_outcome == reproduced_outcome
+ fidelity_state = (
+ ReproducibilityLevel.CONTENT_IDENTICAL.value
+ if outputs_match and outcomes_match
+ else "NOT_DEMONSTRATED"
+ )
+ print(
+ "[REPRODUCTION RESULT - RERUN] "
+ f"Fidelity state: {fidelity_state} (declared-output scope)"
+ )
+ print(f" Original outcome: {original_outcome}")
+ print(f" Reproduced outcome: {reproduced_outcome}")
+ print(f" Outputs match: {outputs_match}")
+ print(" Scope: declared output artifacts and execution outcome")
+ return 0 if outputs_match and outcomes_match else 1
+
+ # Default path: rehash on-disk artifacts only (no execution).
+ mismatches: list[tuple[Any, Any, Optional[str]]] = []
+ checked = 0
+ for out in data.get("outputs", []):
+ recorded_hash = out.get("sha256")
+ path = base_dir / out["path"]
+ if not path.exists():
+ mismatches.append((out["path"], recorded_hash, None))
+ continue
+ checked += 1
+ current_hash = sha256_file(path)
+ if current_hash != recorded_hash:
+ mismatches.append((out["path"], recorded_hash, current_hash))
+
+ if not data.get("outputs"):
+ print("[REPRODUCTION RESULT - ARTIFACT REHASH] NOT_DEMONSTRATED: no outputs were declared.")
+ return 1
+
+ if mismatches:
+ print(f"[REPRODUCTION RESULT - ARTIFACT REHASH] MISMATCH ({len(mismatches)} of {len(data.get('outputs', []))} outputs)")
+ for path, recorded, current in mismatches:
+ print(f" {path}: recorded={recorded} current={current}")
+ return 1
+
+ print(f"[REPRODUCTION RESULT - ARTIFACT REHASH] All {checked} on-disk output artifact(s) match recorded digests.")
+ print(" Declared-output bytes: MATCH")
+ print(" Full-run reproduction: NOT_DEMONSTRATED (command was not re-executed; pass --rerun to assess it)")
+ return 0
diff --git a/src/verifier/core/run_support.py b/src/verifier/core/run_support.py
new file mode 100644
index 0000000..fb6253e
--- /dev/null
+++ b/src/verifier/core/run_support.py
@@ -0,0 +1,28 @@
+"""Shared internal coordinates for the Verifier Standard (VSTD) generic-run profile."""
+
+from __future__ import annotations
+
+from enum import Enum
+
+
+RUN_SCHEMA_VERSION = "VSTD-1"
+RUN_RECEIPT_KIND = "generic_computational_run"
+
+
+class RunError(RuntimeError):
+ """Manifest, capture, or impact error that must fail closed."""
+
+
+class RunOutcome(str, Enum):
+ COMPLETED = "COMPLETED"
+ NONZERO_EXIT = "NONZERO_EXIT"
+ MISSING_INPUT = "MISSING_INPUT"
+ MISSING_OUTPUT = "MISSING_OUTPUT"
+ TIMEOUT = "TIMEOUT"
+ EXCEPTION = "EXCEPTION"
+
+
+class DeterminismDeclaration(str, Enum):
+ DETERMINISTIC = "DETERMINISTIC"
+ NONDETERMINISTIC = "NONDETERMINISTIC"
+ UNKNOWN = "UNKNOWN"
diff --git a/src/verifier/core/run_validation.py b/src/verifier/core/run_validation.py
new file mode 100644
index 0000000..8cb2303
--- /dev/null
+++ b/src/verifier/core/run_validation.py
@@ -0,0 +1,650 @@
+"""Terminology: JavaScript Object Notation (JSON); Secure Hash Algorithm 256-bit (SHA-256); Verifier Standard (VSTD).
+
+Fail-closed structural and canonical-digest validation for generic-run receipts.
+"""
+
+from __future__ import annotations
+
+import json
+from pathlib import Path
+import re
+from typing import Any, Mapping
+
+from verifier.core.receipt import compute_canonical_digest
+from verifier.core.run_support import (
+ RUN_RECEIPT_KIND,
+ RUN_SCHEMA_VERSION,
+ DeterminismDeclaration,
+ RunOutcome,
+)
+
+
+_SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$")
+
+
+def _rebuild_stable_payload_from_dict(data: Mapping[str, Any]) -> dict[str, Any]:
+ src = data.get("source_state", {})
+ payload = {
+ "schema_version": data.get("schema_version"),
+ "receipt_kind": data.get("receipt_kind"),
+ "receipt_id": data.get("receipt_id"),
+ "claim_title": data.get("claim_title"),
+ "claim_statement": data.get("claim_statement"),
+ "claim_scope": data.get("claim_scope"),
+ "claim_limitations": data.get("claim_limitations"),
+ "falsification_condition": data.get("falsification_condition"),
+ "source_state_stable": {
+ "target_name": src.get("target_name"),
+ "portable_repository_id": src.get("portable_repository_id"),
+ "git_commit_sha": src.get("git", {}).get("commit_sha"),
+ "git_branch": src.get("git", {}).get("branch"),
+ "git_is_dirty": src.get("git", {}).get("is_dirty"),
+ "git_dirty_files": src.get("git", {}).get("dirty_files", []),
+ "source_file_hashes": src.get("source_file_hashes", {}),
+ "runtime_python_version": src.get("runtime", {}).get("python_version"),
+ },
+ "inputs": data.get("inputs", []),
+ "outputs": data.get("outputs", []),
+ "execution_stable": {
+ "command": data.get("execution", {}).get("command"),
+ "cwd": data.get("execution", {}).get("cwd"),
+ "exit_code": data.get("execution", {}).get("exit_code"),
+ "outcome": data.get("execution", {}).get("outcome"),
+ "python_version": data.get("execution", {}).get("python_version"),
+ "platform_system": data.get("execution", {}).get("platform_system"),
+ "determinism_declared": data.get("execution", {}).get("determinism_declared"),
+ "seed_declared": data.get("execution", {}).get("seed_declared"),
+ "stdout_sha256": data.get("execution", {}).get("stdout_sha256"),
+ "stderr_sha256": data.get("execution", {}).get("stderr_sha256"),
+ },
+ "claims": data.get("claims", {}),
+ "provenance_linkage": data.get("provenance_linkage", []),
+ "reproducibility": data.get("reproducibility", {}),
+ "assessment_context": data.get("assessment_context", {}),
+ }
+ return payload
+
+
+def _missing_fields(
+ value: object,
+ label: str,
+ required: tuple[str, ...],
+ errors: list[str],
+) -> Mapping[str, Any] | None:
+ if not isinstance(value, Mapping):
+ errors.append(f"{label} must be an object")
+ return None
+ missing = [name for name in required if name not in value]
+ if missing:
+ errors.append(f"{label} missing required fields: {', '.join(missing)}")
+ return value
+
+
+def _unexpected_fields(
+ value: Mapping[str, Any], label: str, allowed: tuple[str, ...], errors: list[str]
+) -> None:
+ unexpected = sorted(set(value) - set(allowed))
+ if unexpected:
+ errors.append(f"{label} has unexpected fields: {', '.join(unexpected)}")
+
+
+def _run_payload_errors(data: Mapping[str, Any]) -> list[str]:
+ """Fail-closed structural checks for the generic-run wire profile."""
+
+ errors: list[str] = []
+ required = (
+ "schema_version",
+ "receipt_kind",
+ "receipt_id",
+ "canonical_digest",
+ "claim_title",
+ "claim_statement",
+ "claim_scope",
+ "claim_limitations",
+ "falsification_condition",
+ "source_state",
+ "inputs",
+ "outputs",
+ "execution",
+ "claims",
+ "provenance_linkage",
+ "reproducibility",
+ "assessment_context",
+ )
+ _missing_fields(data, "receipt", required, errors)
+ _unexpected_fields(data, "receipt", required, errors)
+ if data.get("schema_version") != RUN_SCHEMA_VERSION:
+ errors.append(f"schema_version must be {RUN_SCHEMA_VERSION}")
+ if data.get("receipt_kind") != RUN_RECEIPT_KIND:
+ errors.append(f"receipt_kind must be {RUN_RECEIPT_KIND}")
+ for name in (
+ "receipt_id",
+ "claim_title",
+ "claim_statement",
+ "claim_scope",
+ "falsification_condition",
+ ):
+ if not isinstance(data.get(name), str):
+ errors.append(f"{name} must be a string")
+ digest = data.get("canonical_digest")
+ if not isinstance(digest, str) or not _SHA256_PATTERN.fullmatch(digest):
+ errors.append("canonical_digest must be 64 lowercase hexadecimal characters")
+ if not isinstance(data.get("claim_limitations"), list) or not all(
+ isinstance(item, str) for item in data.get("claim_limitations", [])
+ ):
+ errors.append("claim_limitations must be an array of strings")
+
+ source = _missing_fields(
+ data.get("source_state"),
+ "source_state",
+ (
+ "target_name",
+ "portable_repository_id",
+ "local_repository_path",
+ "git",
+ "runtime",
+ "captured_at_utc",
+ "command_executed",
+ "source_file_hashes",
+ ),
+ errors,
+ )
+ if source is not None:
+ source_fields = (
+ "target_name",
+ "portable_repository_id",
+ "local_repository_path",
+ "git",
+ "runtime",
+ "captured_at_utc",
+ "command_executed",
+ "source_file_hashes",
+ )
+ _unexpected_fields(source, "source_state", source_fields, errors)
+ for name in (
+ "target_name",
+ "portable_repository_id",
+ "local_repository_path",
+ "captured_at_utc",
+ "command_executed",
+ ):
+ if not isinstance(source.get(name), str):
+ errors.append(f"source_state.{name} must be a string")
+ source_hashes = source.get("source_file_hashes")
+ if not isinstance(source_hashes, Mapping) or not all(
+ isinstance(path, str)
+ and isinstance(digest, str)
+ and bool(_SHA256_PATTERN.fullmatch(digest))
+ for path, digest in (
+ source_hashes.items() if isinstance(source_hashes, Mapping) else ()
+ )
+ ):
+ errors.append("source_state.source_file_hashes must map paths to SHA-256 digests")
+ git = _missing_fields(
+ source.get("git"),
+ "source_state.git",
+ ("commit_sha", "branch", "is_dirty"),
+ errors,
+ )
+ if git is not None:
+ git_fields = (
+ "commit_sha",
+ "branch",
+ "is_dirty",
+ "dirty_files",
+ "untracked_files",
+ "remote_origin",
+ )
+ _unexpected_fields(git, "source_state.git", git_fields, errors)
+ if not isinstance(git.get("commit_sha"), str) or not isinstance(
+ git.get("branch"), str
+ ):
+ errors.append("source_state.git commit_sha and branch must be strings")
+ if type(git.get("is_dirty")) is not bool:
+ errors.append("source_state.git.is_dirty must be a boolean")
+ for name in ("dirty_files", "untracked_files"):
+ if name in git and (
+ not isinstance(git.get(name), list)
+ or not all(isinstance(item, str) for item in git.get(name, []))
+ ):
+ errors.append(f"source_state.git.{name} must be an array of strings")
+ if "remote_origin" in git and not isinstance(git.get("remote_origin"), str):
+ errors.append("source_state.git.remote_origin must be a string")
+ runtime = _missing_fields(
+ source.get("runtime"),
+ "source_state.runtime",
+ ("python_version", "platform_system"),
+ errors,
+ )
+ if runtime is not None and any(
+ not isinstance(runtime.get(name), str)
+ for name in ("python_version", "platform_system")
+ ):
+ errors.append("source_state.runtime required fields must be strings")
+ if runtime is not None:
+ runtime_fields = (
+ "python_version",
+ "python_implementation",
+ "platform_system",
+ "platform_release",
+ "platform_machine",
+ "hostname_masked",
+ )
+ _unexpected_fields(runtime, "source_state.runtime", runtime_fields, errors)
+ for name in runtime_fields:
+ if name in runtime and not isinstance(runtime.get(name), str):
+ errors.append(f"source_state.runtime.{name} must be a string")
+
+ for collection_name in ("inputs", "outputs"):
+ collection = data.get(collection_name)
+ if not isinstance(collection, list):
+ errors.append(f"{collection_name} must be an array")
+ continue
+ for index, raw in enumerate(collection):
+ label = f"{collection_name}[{index}]"
+ item = _missing_fields(raw, label, ("path", "role", "present", "sha256", "byte_size"), errors)
+ if item is None:
+ continue
+ _unexpected_fields(
+ item, label, ("path", "role", "present", "sha256", "byte_size"), errors
+ )
+ if not isinstance(item.get("path"), str) or not isinstance(item.get("role"), str):
+ errors.append(f"{label}.path and .role must be strings")
+ if type(item.get("present")) is not bool:
+ errors.append(f"{label}.present must be a boolean")
+ artifact_digest = item.get("sha256")
+ if artifact_digest is not None and (
+ not isinstance(artifact_digest, str)
+ or not _SHA256_PATTERN.fullmatch(artifact_digest)
+ ):
+ errors.append(f"{label}.sha256 must be null or 64 lowercase hexadecimal characters")
+ byte_size = item.get("byte_size")
+ if byte_size is not None and (type(byte_size) is not int or byte_size < 0):
+ errors.append(f"{label}.byte_size must be null or a non-negative integer")
+ if item.get("present") is True and (artifact_digest is None or byte_size is None):
+ errors.append(f"{label} is present but lacks a digest or byte size")
+
+ execution = _missing_fields(
+ data.get("execution"),
+ "execution",
+ (
+ "command",
+ "cwd",
+ "started_at_utc",
+ "ended_at_utc",
+ "elapsed_ms",
+ "exit_code",
+ "outcome",
+ "python_version",
+ "platform_system",
+ "determinism_declared",
+ "seed_declared",
+ "stdout_sha256",
+ "stderr_sha256",
+ "stdout_snippet",
+ "stderr_snippet",
+ ),
+ errors,
+ )
+ if execution is not None:
+ _unexpected_fields(
+ execution,
+ "execution",
+ (
+ "command",
+ "cwd",
+ "started_at_utc",
+ "ended_at_utc",
+ "elapsed_ms",
+ "exit_code",
+ "outcome",
+ "python_version",
+ "platform_system",
+ "determinism_declared",
+ "seed_declared",
+ "stdout_sha256",
+ "stderr_sha256",
+ "stdout_snippet",
+ "stderr_snippet",
+ ),
+ errors,
+ )
+ command = execution.get("command")
+ if not isinstance(command, list) or not command or not all(isinstance(arg, str) for arg in command):
+ errors.append("execution.command must be a non-empty array of strings")
+ if execution.get("outcome") not in {member.value for member in RunOutcome}:
+ errors.append("execution.outcome is not a recognized run outcome")
+ if execution.get("determinism_declared") not in {
+ member.value for member in DeterminismDeclaration
+ }:
+ errors.append("execution.determinism_declared is not recognized")
+ for name in (
+ "cwd",
+ "started_at_utc",
+ "ended_at_utc",
+ "python_version",
+ "platform_system",
+ "stdout_snippet",
+ "stderr_snippet",
+ ):
+ if not isinstance(execution.get(name), str):
+ errors.append(f"execution.{name} must be a string")
+ elapsed = execution.get("elapsed_ms")
+ if isinstance(elapsed, bool) or not isinstance(elapsed, (int, float)) or elapsed < 0:
+ errors.append("execution.elapsed_ms must be a non-negative number")
+ exit_code = execution.get("exit_code")
+ if exit_code is not None and (type(exit_code) is not int):
+ errors.append("execution.exit_code must be an integer or null")
+ seed = execution.get("seed_declared")
+ if seed is not None and not isinstance(seed, str):
+ errors.append("execution.seed_declared must be a string or null")
+ for name in ("stdout_sha256", "stderr_sha256"):
+ value = execution.get(name)
+ if not isinstance(value, str) or not _SHA256_PATTERN.fullmatch(value):
+ errors.append(f"execution.{name} must be 64 lowercase hexadecimal characters")
+
+ claims = _missing_fields(
+ data.get("claims"),
+ "claims",
+ (
+ "execution_completed",
+ "output_digests_recorded",
+ "all_declared_artifacts_present",
+ "evaluator_claims",
+ "external_evaluation",
+ ),
+ errors,
+ )
+ if claims is not None:
+ _unexpected_fields(
+ claims,
+ "claims",
+ (
+ "execution_completed",
+ "output_digests_recorded",
+ "all_declared_artifacts_present",
+ "evaluator_claims",
+ "external_evaluation",
+ ),
+ errors,
+ )
+ for name in ("execution_completed", "output_digests_recorded"):
+ if type(claims.get(name)) is not bool:
+ errors.append(f"claims.{name} must be a boolean")
+ if claims.get("all_declared_artifacts_present") is not None and type(
+ claims.get("all_declared_artifacts_present")
+ ) is not bool:
+ errors.append("claims.all_declared_artifacts_present must be a boolean or null")
+ evaluator_claims = claims.get("evaluator_claims")
+ if not isinstance(evaluator_claims, list):
+ errors.append("claims.evaluator_claims must be an array")
+ else:
+ evaluator_fields = (
+ "evaluator_name",
+ "metric_name",
+ "value",
+ "computed_by",
+ "verified_independently",
+ )
+ for index, raw in enumerate(evaluator_claims):
+ label = f"claims.evaluator_claims[{index}]"
+ evaluator = _missing_fields(raw, label, evaluator_fields, errors)
+ if evaluator is None:
+ continue
+ _unexpected_fields(evaluator, label, evaluator_fields, errors)
+ if not isinstance(evaluator.get("evaluator_name"), str) or not isinstance(
+ evaluator.get("metric_name"), str
+ ):
+ errors.append(f"{label} names must be strings")
+ if evaluator.get("computed_by") not in {
+ "bound_output_extraction",
+ "declared_by_manifest_author",
+ }:
+ errors.append(f"{label}.computed_by is not recognized")
+ if evaluator.get("verified_independently") is not False:
+ errors.append(
+ f"{label}.verified_independently must be false for this runtime"
+ )
+ external = claims.get("external_evaluation")
+ if external is not None and not isinstance(external, Mapping):
+ errors.append("claims.external_evaluation must be an object or null")
+ elif isinstance(external, Mapping):
+ external_fields = (
+ "source",
+ "description",
+ "reported_value",
+ "evidence_kind",
+ "evidence_ref",
+ "attested",
+ )
+ _missing_fields(external, "claims.external_evaluation", external_fields, errors)
+ _unexpected_fields(
+ external, "claims.external_evaluation", external_fields, errors
+ )
+ for name in ("source", "description", "evidence_kind"):
+ if not isinstance(external.get(name), str):
+ errors.append(f"claims.external_evaluation.{name} must be a string")
+ if external.get("evidence_ref") is not None and not isinstance(
+ external.get("evidence_ref"), str
+ ):
+ errors.append("claims.external_evaluation.evidence_ref must be a string or null")
+ if external.get("attested") is not False:
+ errors.append("claims.external_evaluation.attested must be false for this runtime")
+
+ linkage = data.get("provenance_linkage")
+ if not isinstance(linkage, list):
+ errors.append("provenance_linkage must be an array")
+ else:
+ linkage_fields = (
+ "dataset_receipt_path",
+ "artifact_id",
+ "found_in_hypergraph",
+ "ancestor_count",
+ "ancestor_ids",
+ )
+ for index, raw in enumerate(linkage):
+ label = f"provenance_linkage[{index}]"
+ item = _missing_fields(raw, label, linkage_fields, errors)
+ if item is None:
+ continue
+ _unexpected_fields(item, label, linkage_fields, errors)
+ if not isinstance(item.get("dataset_receipt_path"), str) or not isinstance(
+ item.get("artifact_id"), str
+ ):
+ errors.append(f"{label} paths and identifiers must be strings")
+ if type(item.get("found_in_hypergraph")) is not bool:
+ errors.append(f"{label}.found_in_hypergraph must be a boolean")
+ count = item.get("ancestor_count")
+ if count is not None and (type(count) is not int or count < 0):
+ errors.append(f"{label}.ancestor_count must be a non-negative integer or null")
+ if not isinstance(item.get("ancestor_ids"), list) or not all(
+ isinstance(ancestor, str) for ancestor in item.get("ancestor_ids", [])
+ ):
+ errors.append(f"{label}.ancestor_ids must be an array of strings")
+ reproduction = _missing_fields(
+ data.get("reproducibility"),
+ "reproducibility",
+ ("highest_demonstrated_level", "declared_ceiling", "supported_levels", "reproduction_command"),
+ errors,
+ )
+ if reproduction is not None:
+ reproduction_fields = (
+ "highest_demonstrated_level",
+ "declared_ceiling",
+ "supported_levels",
+ "reproduction_command",
+ )
+ _unexpected_fields(reproduction, "reproducibility", reproduction_fields, errors)
+ if reproduction.get("highest_demonstrated_level") is not None and not isinstance(
+ reproduction.get("highest_demonstrated_level"), str
+ ):
+ errors.append("reproducibility.highest_demonstrated_level must be a string or null")
+ if not isinstance(reproduction.get("declared_ceiling"), str) or not isinstance(
+ reproduction.get("reproduction_command"), str
+ ):
+ errors.append("reproducibility ceiling and command must be strings")
+ if not isinstance(reproduction.get("supported_levels"), list) or not all(
+ isinstance(level, str) for level in reproduction.get("supported_levels", [])
+ ):
+ errors.append("reproducibility.supported_levels must be an array of strings")
+ context = _missing_fields(
+ data.get("assessment_context"),
+ "assessment_context",
+ ("verifier", "resource_bounds", "prior_commitment", "refutation_surface"),
+ errors,
+ )
+ if context is not None:
+ context_fields = (
+ "verifier",
+ "resource_bounds",
+ "prior_commitment",
+ "refutation_surface",
+ )
+ _unexpected_fields(context, "assessment_context", context_fields, errors)
+ verifier = _missing_fields(
+ context.get("verifier"),
+ "assessment_context.verifier",
+ (
+ "specification_hash",
+ "implementation_hash",
+ "parser_hash",
+ "certificate_format",
+ "format_fragment",
+ "dependencies",
+ "deterministic",
+ ),
+ errors,
+ )
+ if verifier is not None:
+ verifier_fields = (
+ "specification_hash",
+ "implementation_hash",
+ "parser_hash",
+ "certificate_format",
+ "format_fragment",
+ "dependencies",
+ "deterministic",
+ )
+ _unexpected_fields(
+ verifier, "assessment_context.verifier", verifier_fields, errors
+ )
+ for name in (
+ "specification_hash",
+ "implementation_hash",
+ "parser_hash",
+ ):
+ value = verifier.get(name)
+ unavailable_specification = (
+ name == "specification_hash"
+ and isinstance(value, str)
+ and value.startswith("UNAVAILABLE:")
+ )
+ if (
+ not unavailable_specification
+ and (
+ not isinstance(value, str)
+ or not re.fullmatch(r"sha256:[0-9a-f]{64}", value)
+ )
+ ):
+ errors.append(
+ f"assessment_context.verifier.{name} must be a prefixed SHA-256 digest"
+ )
+ for name in ("certificate_format", "format_fragment"):
+ if not isinstance(verifier.get(name), str):
+ errors.append(f"assessment_context.verifier.{name} must be a string")
+ if not isinstance(verifier.get("dependencies"), list) or not all(
+ isinstance(item, str) for item in verifier.get("dependencies", [])
+ ):
+ errors.append(
+ "assessment_context.verifier.dependencies must be an array of strings"
+ )
+ if type(verifier.get("deterministic")) is not bool:
+ errors.append("assessment_context.verifier.deterministic must be a boolean")
+ bounds = _missing_fields(
+ context.get("resource_bounds"),
+ "assessment_context.resource_bounds",
+ (
+ "verification_cost_bound",
+ "memory_bound",
+ "certificate_size_bound",
+ ),
+ errors,
+ )
+ if bounds is not None:
+ bound_fields = (
+ "verification_cost_bound",
+ "memory_bound",
+ "certificate_size_bound",
+ )
+ _unexpected_fields(
+ bounds, "assessment_context.resource_bounds", bound_fields, errors
+ )
+ for name in bound_fields:
+ value = bounds.get(name)
+ if type(value) is not int or value < 0:
+ errors.append(
+ f"assessment_context.resource_bounds.{name} must be a non-negative integer"
+ )
+ if not isinstance(context.get("prior_commitment"), str):
+ errors.append("assessment_context.prior_commitment must be a string")
+ surface = _missing_fields(
+ context.get("refutation_surface"),
+ "assessment_context.refutation_surface",
+ (
+ "admissible_refutations",
+ "excluded_claims",
+ "falsification_condition",
+ ),
+ errors,
+ )
+ if surface is not None:
+ for name in ("admissible_refutations", "excluded_claims"):
+ if not isinstance(surface.get(name), list) or not all(
+ isinstance(item, str) for item in surface.get(name, [])
+ ):
+ errors.append(
+ f"assessment_context.refutation_surface.{name} must be an array of strings"
+ )
+ if not isinstance(surface.get("falsification_condition"), str):
+ errors.append(
+ "assessment_context.refutation_surface.falsification_condition must be a string"
+ )
+ return errors
+
+
+def is_generic_run_receipt(data: Mapping[str, Any]) -> bool:
+ return (
+ data.get("schema_version") == RUN_SCHEMA_VERSION
+ and data.get("receipt_kind") == RUN_RECEIPT_KIND
+ )
+
+
+def validate_run_receipt(receipt_path_or_dir: Path) -> int:
+ """Validate one generic-run receipt's required fields and stable canonical digest."""
+
+ receipt_file = receipt_path_or_dir / "receipt.json" if receipt_path_or_dir.is_dir() else receipt_path_or_dir
+ if not receipt_file.exists():
+ print(f"[FAIL] Receipt file not found: {receipt_file}")
+ return 1
+ try:
+ data = json.loads(receipt_file.read_text(encoding="utf-8"))
+ except (OSError, json.JSONDecodeError) as exc:
+ print(f"[FAIL] Receipt is not readable JSON: {exc}")
+ return 1
+ if not isinstance(data, Mapping):
+ print("[FAIL] Receipt root must be an object")
+ return 1
+ errors = _run_payload_errors(data)
+ if errors:
+ for error in errors:
+ print(f"[FAIL] {error}")
+ return 1
+ recorded_digest = data.get("canonical_digest", "")
+ recomputed = compute_canonical_digest(_rebuild_stable_payload_from_dict(data))
+ if recomputed != recorded_digest:
+ print(f"[FAIL] Canonical digest mismatch:\n Recorded: {recorded_digest}\n Recomputed: {recomputed}")
+ return 1
+ print(f"[INTEGRITY OK] Run receipt {data.get('receipt_id')} stable digest matches.")
+ print(f" Digest: {recorded_digest}")
+ print(f" Outcome: {data.get('execution', {}).get('outcome')}")
+ return 0
diff --git a/src/verifier/core/translation.py b/src/verifier/core/translation.py
index df30967..9e8ff63 100644
--- a/src/verifier/core/translation.py
+++ b/src/verifier/core/translation.py
@@ -1,4 +1,7 @@
-"""Translation-boundary assurance: the missing dimension between "a formal
+"""Terminology: finite-state machine (FSM); JavaScript Object Notation (JSON);
+Boolean satisfiability problem (SAT); Verifier Standard (VSTD).
+
+Translation-boundary assurance: the missing dimension between "a formal
system said yes" and "the formal system was fed an honest encoding of the
real thing."
@@ -182,8 +185,8 @@ def from_dict(cls, d: dict[str, Any]) -> "TranslationRecord":
return rec
def canonical_digest(self) -> str:
- """Reuses the same canonicalization/digest machinery as VSTD-0.1
- receipts (``verifier.core.receipt.compute_canonical_digest``)
+ """Reuse the VSTD-1 receipt canonicalization and digest machinery
+ (``verifier.core.receipt.compute_canonical_digest``)
rather than inventing a second canonical-JSON scheme."""
return compute_canonical_digest(self.to_dict())
@@ -297,7 +300,7 @@ def canonical_json_digest(obj: Any) -> str:
"""Small helper for hashing an arbitrary source document (e.g. a JSON
Schema) into ``source_digest``, using plain sorted-key JSON -- not the
receipt payload schema, since the source document is not itself a
- receipt. Kept separate from ``compute_canonical_digest`` (VSTD-0.1
+ receipt. Kept separate from ``compute_canonical_digest`` (VSTD-1 receipt
stable-payload canonicalization) to avoid implying the source document
conforms to that schema."""
import hashlib
diff --git a/src/verifier/core/witness.py b/src/verifier/core/witness.py
new file mode 100644
index 0000000..8372d16
--- /dev/null
+++ b/src/verifier/core/witness.py
@@ -0,0 +1,871 @@
+"""Terminology: identifier (ID); Request for Comments (RFC); Secure Hash Algorithm
+256-bit (SHA-256); Verifier Standard (VSTD).
+
+Evidence-bound VSTD-5 Witness Corroboration reference mechanism.
+
+Witness identifiers are coordinates, not trust. Every required separation
+dimension is an exact proposition rerun by a named mechanism, and every
+corroboration reruns a mechanism over content-addressed observations. Matching
+names, repeated evidence, cryptographic identity, or majority count cannot
+manufacture independence or corroboration.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+from datetime import datetime
+from enum import Enum
+import re
+from typing import Any, Mapping
+
+from .depth import EvidenceBoundDepthResult, require_vstd5_entry
+from .evidence import (
+ BoundProposition,
+ EvidenceStore,
+ EvaluatedProposition,
+ MechanismOutcome,
+ VerificationSession,
+ VerificationMechanism,
+)
+from .certificate import canonical_digest
+
+
+class IndependenceDimension(str, Enum):
+ CONTROL = "control"
+ VERDICT_CODE = "verdict_code"
+ TRUST_ROOT = "trust_root"
+ EVIDENCE_SOURCE = "evidence_source"
+ INFRASTRUCTURE = "infrastructure"
+ FINANCIAL_DEPENDENCE = "financial_dependence"
+ JURISDICTIONAL_DEPENDENCE = "jurisdictional_dependence"
+
+
+class RelationshipState(str, Enum):
+ SHARED = "SHARED"
+ SEPARATE = "SEPARATE"
+ UNKNOWN = "UNKNOWN"
+
+
+class CorroborationOutcome(str, Enum):
+ CORROBORATED = "CORROBORATED"
+ REFUTED = "REFUTED"
+ UNKNOWN = "UNKNOWN"
+
+
+class WitnessResultStatus(str, Enum):
+ CORROBORATED = "CORROBORATED"
+ REFUTED = "REFUTED"
+ UNKNOWN = "UNKNOWN"
+ CONFLICTED = "CONFLICTED"
+
+
+@dataclass(frozen=True)
+class WitnessIdentity:
+ witness_id: str
+ identity_evidence_ref: str
+
+ def to_dict(self) -> dict[str, str]:
+ return {
+ "witness_id": self.witness_id,
+ "identity_evidence_ref": self.identity_evidence_ref,
+ }
+
+
+@dataclass(frozen=True)
+class IndependenceAssertion:
+ witness_id: str
+ relationships: Mapping[IndependenceDimension, RelationshipState]
+ evidence: Mapping[IndependenceDimension, BoundProposition]
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "witness_id": self.witness_id,
+ "dimensions": {
+ dimension.value: {
+ "state": self.relationships.get(
+ dimension, RelationshipState.UNKNOWN
+ ).value,
+ "binding": (
+ None
+ if self.evidence.get(dimension) is None
+ else self.evidence[dimension].to_dict()
+ ),
+ }
+ for dimension in IndependenceDimension
+ },
+ }
+
+
+@dataclass(frozen=True)
+class CorroborationRecord:
+ corroboration_id: str
+ witness_id: str
+ claim_binding_digest: str
+ vstd4_certificate_digest: str
+ checker_descriptor_digest: str
+ observed_evidence_refs: tuple[str, ...]
+ result: CorroborationOutcome
+ observed_at: str
+ verification: BoundProposition
+ corroboration_class: str = "GENERAL_COMPUTATIONAL_CHECK"
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "corroboration_id": self.corroboration_id,
+ "witness_id": self.witness_id,
+ "claim_binding_digest": self.claim_binding_digest,
+ "vstd4_certificate_digest": self.vstd4_certificate_digest,
+ "checker_descriptor_digest": self.checker_descriptor_digest,
+ "observed_evidence_refs": list(self.observed_evidence_refs),
+ "result": self.result.value,
+ "observed_at": self.observed_at,
+ "verification": self.verification.to_dict(),
+ "corroboration_class": self.corroboration_class,
+ }
+
+
+@dataclass(frozen=True)
+class WitnessBundle:
+ """Claim-bound identities, ordered separation assertions, and corroborations."""
+
+ claim_id: str
+ declarant_id: str
+ claim_binding_digest: str
+ witnesses: tuple[WitnessIdentity, ...]
+ independence: tuple[IndependenceAssertion, ...]
+ corroborations: tuple[CorroborationRecord, ...]
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "claim_id": self.claim_id,
+ "declarant_id": self.declarant_id,
+ "claim_binding_digest": self.claim_binding_digest,
+ "witnesses": [witness.to_dict() for witness in self.witnesses],
+ "independence_assertions": [
+ item.to_dict() for item in self.independence
+ ],
+ "corroborations": [item.to_dict() for item in self.corroborations],
+ }
+
+ @classmethod
+ def from_dict(cls, data: Mapping[str, Any]) -> "WitnessBundle":
+ witnesses: list[WitnessIdentity] = []
+ assertions: list[IndependenceAssertion] = []
+ for item in data.get("witnesses", ()):
+ witness = WitnessIdentity(
+ str(item["witness_id"]), str(item["identity_evidence_ref"])
+ )
+ witnesses.append(witness)
+ for item in data.get("independence_assertions", ()):
+ dimensions = item.get("dimensions")
+ if not isinstance(dimensions, Mapping):
+ continue
+ relationships: dict[IndependenceDimension, RelationshipState] = {}
+ evidence: dict[IndependenceDimension, BoundProposition] = {}
+ for dimension in IndependenceDimension:
+ value = dimensions.get(dimension.value, {})
+ if not isinstance(value, Mapping):
+ continue
+ relationships[dimension] = RelationshipState(
+ value.get("state", RelationshipState.UNKNOWN.value)
+ )
+ binding = value.get("binding")
+ if isinstance(binding, Mapping):
+ evidence[dimension] = BoundProposition.from_dict(binding)
+ assertions.append(
+ IndependenceAssertion(str(item["witness_id"]), relationships, evidence)
+ )
+ corroborations = tuple(
+ CorroborationRecord(
+ str(item["corroboration_id"]),
+ str(item["witness_id"]),
+ str(item["claim_binding_digest"]),
+ str(item["vstd4_certificate_digest"]),
+ str(item["checker_descriptor_digest"]),
+ tuple(str(ref) for ref in item["observed_evidence_refs"]),
+ CorroborationOutcome(item["result"]),
+ str(item["observed_at"]),
+ BoundProposition.from_dict(item["verification"]),
+ str(item.get("corroboration_class", "GENERAL_COMPUTATIONAL_CHECK")),
+ )
+ for item in data.get("corroborations", ())
+ )
+ return cls(
+ str(data["claim_id"]),
+ str(data["declarant_id"]),
+ str(data["claim_binding_digest"]),
+ tuple(witnesses),
+ tuple(assertions),
+ corroborations,
+ )
+
+
+@dataclass(frozen=True)
+class WitnessCorroborationResult:
+ claim_id: str
+ status: WitnessResultStatus
+ conformance_status: str
+ computed_independence: str
+ independence_evaluations: tuple[
+ tuple[str, str, EvaluatedProposition], ...
+ ]
+ corroboration_evaluations: tuple[tuple[str, EvaluatedProposition], ...]
+ disagreements: tuple[tuple[str, ...], ...]
+ binding_errors: tuple[str, ...]
+ identity_errors: tuple[str, ...]
+ separation_errors: tuple[str, ...]
+ corroboration_errors: tuple[str, ...]
+ limitations: tuple[str, ...] = field(default_factory=tuple)
+
+ @property
+ def established(self) -> bool:
+ return self.conformance_status == "ESTABLISHED"
+
+ @property
+ def errors(self) -> tuple[str, ...]:
+ """Return all errors without using message text as decision input."""
+ return (
+ *self.binding_errors,
+ *self.identity_errors,
+ *self.separation_errors,
+ *self.corroboration_errors,
+ )
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "claim_id": self.claim_id,
+ "status": self.status.value,
+ "conformance_status": self.conformance_status,
+ "computed_independence": self.computed_independence,
+ "independence_evaluations": [
+ {
+ "witness_id": witness_id,
+ "dimension": dimension,
+ "evaluation": evaluation.to_dict(),
+ }
+ for witness_id, dimension, evaluation in self.independence_evaluations
+ ],
+ "corroboration_evaluations": [
+ {
+ "corroboration_id": record_id,
+ "evaluation": evaluation.to_dict(),
+ }
+ for record_id, evaluation in self.corroboration_evaluations
+ ],
+ "disagreements": [list(group) for group in self.disagreements],
+ "binding_errors": list(self.binding_errors),
+ "identity_errors": list(self.identity_errors),
+ "separation_errors": list(self.separation_errors),
+ "corroboration_errors": list(self.corroboration_errors),
+ "errors": list(self.errors),
+ "limitations": list(self.limitations),
+ }
+
+
+_RECEIPT_ID = re.compile(r"^VFY-5-[A-Za-z0-9._:-]+$")
+_RAW_DIGEST = re.compile(r"^[0-9a-f]{64}$")
+_DIGEST_REF = re.compile(r"^(?:sha256:)?[0-9a-f]{64}$")
+_PREFIXED_DIGEST = re.compile(r"^sha256:[0-9a-f]{64}$")
+_DATE_TIME = re.compile(
+ r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$"
+)
+
+
+def _shape_error(path: str, message: str) -> None:
+ raise ValueError(f"invalid VSTD-5 receipt shape at {path}: {message}")
+
+
+def _object(value: Any, path: str, keys: set[str]) -> Mapping[str, Any]:
+ if not isinstance(value, Mapping) or set(value) != keys:
+ _shape_error(path, f"must be an object with exactly {sorted(keys)}")
+ return value
+
+
+def _array(value: Any, path: str, minimum: int = 0) -> list[Any]:
+ if not isinstance(value, list) or len(value) < minimum:
+ _shape_error(path, f"must be an array with at least {minimum} item(s)")
+ return value
+
+
+def _text(value: Any, path: str, nonempty: bool = False) -> str:
+ if not isinstance(value, str) or (nonempty and not value):
+ _shape_error(path, "must be a nonempty string" if nonempty else "must be a string")
+ return value
+
+
+def _sha(value: Any, path: str, form: str = "raw") -> str:
+ value = _text(value, path)
+ pattern = {"raw": _RAW_DIGEST, "ref": _DIGEST_REF, "prefixed": _PREFIXED_DIGEST}[form]
+ if pattern.fullmatch(value) is None:
+ _shape_error(path, f"must be a {form} SHA-256 digest")
+ return value
+
+
+def _strings(
+ value: Any,
+ path: str,
+ minimum: int = 0,
+ *,
+ unique: bool = False,
+ nonempty: bool = False,
+) -> list[str]:
+ values = _array(value, path, minimum)
+ for index, item in enumerate(values):
+ _text(item, f"{path}[{index}]", nonempty)
+ if unique and len(set(values)) != len(values):
+ _shape_error(path, "must not contain duplicates")
+ return values
+
+
+def _evidence_refs(
+ value: Any,
+ path: str,
+ required_payloads: set[str],
+ minimum: int = 0,
+ *,
+ prefixed: bool = False,
+) -> list[str]:
+ references = _array(value, path, minimum)
+ normalized: list[str] = []
+ for index, reference in enumerate(references):
+ reference = _sha(reference, f"{path}[{index}]", "prefixed" if prefixed else "ref")
+ normalized.append("sha256:" + reference.removeprefix("sha256:"))
+ if len(set(normalized)) != len(normalized):
+ _shape_error(path, "must not contain duplicates")
+ required_payloads.update(normalized)
+ return normalized
+
+
+def _binding_shape(value: Any, path: str, required_payloads: set[str]) -> None:
+ binding = _object(
+ value,
+ path,
+ {
+ "subject_id", "predicate", "expected", "mechanism_id", "mechanism_digest",
+ "evidence_refs", "trust_roots", "bounds", "parameters",
+ },
+ )
+ for field_name in ("subject_id", "predicate", "mechanism_id"):
+ _text(binding[field_name], f"{path}.{field_name}", True)
+ _sha(binding["mechanism_digest"], f"{path}.mechanism_digest", "prefixed")
+ _evidence_refs(binding["evidence_refs"], f"{path}.evidence_refs", required_payloads, 1, prefixed=True)
+ _strings(binding["trust_roots"], f"{path}.trust_roots", 1, unique=True, nonempty=True)
+ bounds = _object(
+ binding["bounds"], f"{path}.bounds", {"max_evidence_items", "max_evidence_bytes"}
+ )
+ if any(type(bounds[name]) is not int or bounds[name] < 0 for name in bounds):
+ _shape_error(f"{path}.bounds", "values must be nonnegative integers")
+ parameters = binding["parameters"]
+ if not isinstance(parameters, Mapping) or any(
+ not isinstance(key, str) or not isinstance(item, str) for key, item in parameters.items()
+ ):
+ _shape_error(f"{path}.parameters", "must map strings to strings")
+
+
+def _evaluation_shape(value: Any, path: str) -> None:
+ evaluation = _object(
+ value,
+ path,
+ {
+ "binding_digest", "outcome", "mechanism_id", "mechanism_digest",
+ "evidence_refs", "trust_roots", "observed_evidence_bytes", "details",
+ "observations",
+ },
+ )
+ _sha(evaluation["binding_digest"], f"{path}.binding_digest")
+ if evaluation["outcome"] not in {item.value for item in MechanismOutcome}:
+ _shape_error(f"{path}.outcome", "is not a mechanism outcome")
+ _text(evaluation["mechanism_id"], f"{path}.mechanism_id", True)
+ _sha(evaluation["mechanism_digest"], f"{path}.mechanism_digest", "ref")
+ references: set[str] = set()
+ _evidence_refs(evaluation["evidence_refs"], f"{path}.evidence_refs", references)
+ _strings(evaluation["trust_roots"], f"{path}.trust_roots", 1, unique=True, nonempty=True)
+ if type(evaluation["observed_evidence_bytes"]) is not int or evaluation["observed_evidence_bytes"] < 0:
+ _shape_error(f"{path}.observed_evidence_bytes", "must be a nonnegative integer")
+ _text(evaluation["details"], f"{path}.details")
+ if not isinstance(evaluation["observations"], Mapping):
+ _shape_error(f"{path}.observations", "must be an object")
+
+
+def _result_shape(value: Any, path: str) -> None:
+ result = _object(
+ value,
+ path,
+ {
+ "claim_id", "status", "conformance_status", "computed_independence",
+ "independence_evaluations", "corroboration_evaluations", "disagreements",
+ "binding_errors", "identity_errors", "separation_errors",
+ "corroboration_errors", "errors", "limitations",
+ },
+ )
+ _text(result["claim_id"], f"{path}.claim_id", True)
+ if result["status"] not in {item.value for item in WitnessResultStatus}:
+ _shape_error(f"{path}.status", "is not a witness result")
+ if result["conformance_status"] not in {"ESTABLISHED", "NOT_ESTABLISHED"}:
+ _shape_error(f"{path}.conformance_status", "is not a conformance status")
+ if result["computed_independence"] not in {"INDEPENDENT", "UNKNOWN"}:
+ _shape_error(f"{path}.computed_independence", "is not an independence result")
+ for field_name, id_name in (
+ ("independence_evaluations", "witness_id"),
+ ("corroboration_evaluations", "corroboration_id"),
+ ):
+ for index, item in enumerate(_array(result[field_name], f"{path}.{field_name}")):
+ keys = {id_name, "evaluation"}
+ if field_name == "independence_evaluations":
+ keys.add("dimension")
+ item = _object(item, f"{path}.{field_name}[{index}]", keys)
+ _text(item[id_name], f"{path}.{field_name}[{index}].{id_name}")
+ if "dimension" in item:
+ _text(item["dimension"], f"{path}.{field_name}[{index}].dimension")
+ _evaluation_shape(item["evaluation"], f"{path}.{field_name}[{index}].evaluation")
+ for index, group in enumerate(_array(result["disagreements"], f"{path}.disagreements")):
+ _strings(group, f"{path}.disagreements[{index}]", 2, unique=True)
+ error_fields = (
+ "binding_errors", "identity_errors", "separation_errors",
+ "corroboration_errors", "errors", "limitations",
+ )
+ errors = {name: _strings(result[name], f"{path}.{name}") for name in error_fields}
+ if result["status"] == "CORROBORATED" and (
+ result["conformance_status"] != "ESTABLISHED"
+ or result["computed_independence"] != "INDEPENDENT"
+ ):
+ _shape_error(path, "CORROBORATED requires established independent evidence")
+ if result["computed_independence"] == "INDEPENDENT" and any(
+ errors[name] for name in error_fields[:3]
+ ):
+ _shape_error(path, "INDEPENDENT cannot retain binding, identity, or separation errors")
+ if result["conformance_status"] == "ESTABLISHED" and (
+ result["computed_independence"] != "INDEPENDENT"
+ or any(errors[name] for name in error_fields[:-1])
+ ):
+ _shape_error(path, "ESTABLISHED cannot retain conformance errors")
+
+
+def _validate_vstd5_receipt_shape(receipt: Mapping[str, Any]) -> None:
+ """Enforce the published receipt profile without adding a schema dependency."""
+
+ receipt = _object(
+ receipt,
+ "$",
+ {"schema_version", "receipt_id", "entry_vstd4", "bundle", "evidence_payloads", "result"},
+ )
+ if receipt["schema_version"] != "VSTD-5":
+ _shape_error("$.schema_version", "must equal VSTD-5")
+ if _RECEIPT_ID.fullmatch(_text(receipt["receipt_id"], "$.receipt_id")) is None:
+ _shape_error("$.receipt_id", "must match the VFY-5 identifier grammar")
+ entry = _object(
+ receipt["entry_vstd4"],
+ "$.entry_vstd4",
+ {"result_digest", "depth", "conformance_status", "witness_digest"},
+ )
+ _sha(entry["result_digest"], "$.entry_vstd4.result_digest")
+ _sha(entry["witness_digest"], "$.entry_vstd4.witness_digest")
+ if entry["depth"] != 14 or entry["conformance_status"] != "ESTABLISHED":
+ _shape_error("$.entry_vstd4", "must identify an established depth-14 VSTD-4 result")
+ bundle = _object(
+ receipt["bundle"],
+ "$.bundle",
+ {"claim_id", "declarant_id", "claim_binding_digest", "witnesses", "independence_assertions", "corroborations"},
+ )
+ _text(bundle["claim_id"], "$.bundle.claim_id", True)
+ _text(bundle["declarant_id"], "$.bundle.declarant_id", True)
+ _sha(bundle["claim_binding_digest"], "$.bundle.claim_binding_digest")
+ required_payloads: set[str] = set()
+ for index, item in enumerate(_array(bundle["witnesses"], "$.bundle.witnesses", 1)):
+ witness = _object(item, f"$.bundle.witnesses[{index}]", {"witness_id", "identity_evidence_ref"})
+ _text(witness["witness_id"], f"$.bundle.witnesses[{index}].witness_id", True)
+ _evidence_refs([witness["identity_evidence_ref"]], f"$.bundle.witnesses[{index}].identity_evidence_ref", required_payloads, 1)
+ dimensions = {item.value for item in IndependenceDimension}
+ for index, item in enumerate(_array(bundle["independence_assertions"], "$.bundle.independence_assertions")):
+ path = f"$.bundle.independence_assertions[{index}]"
+ assertion = _object(item, path, {"witness_id", "dimensions"})
+ _text(assertion["witness_id"], f"{path}.witness_id", True)
+ records = _object(assertion["dimensions"], f"{path}.dimensions", dimensions)
+ for dimension, value in records.items():
+ coordinate = f"{path}.dimensions.{dimension}"
+ record = _object(value, coordinate, {"state", "binding"})
+ if record["state"] not in {item.value for item in RelationshipState}:
+ _shape_error(f"{coordinate}.state", "is not a relationship state")
+ if record["binding"] is not None:
+ _binding_shape(record["binding"], f"{coordinate}.binding", required_payloads)
+ for index, item in enumerate(_array(bundle["corroborations"], "$.bundle.corroborations", 1)):
+ path = f"$.bundle.corroborations[{index}]"
+ record = _object(
+ item,
+ path,
+ {
+ "corroboration_id", "witness_id", "claim_binding_digest",
+ "vstd4_certificate_digest", "checker_descriptor_digest",
+ "observed_evidence_refs", "result", "observed_at", "verification",
+ "corroboration_class",
+ },
+ )
+ for name in ("corroboration_id", "witness_id", "corroboration_class"):
+ _text(record[name], f"{path}.{name}", True)
+ _sha(record["claim_binding_digest"], f"{path}.claim_binding_digest")
+ _sha(record["vstd4_certificate_digest"], f"{path}.vstd4_certificate_digest", "ref")
+ _sha(record["checker_descriptor_digest"], f"{path}.checker_descriptor_digest", "ref")
+ _evidence_refs(record["observed_evidence_refs"], f"{path}.observed_evidence_refs", required_payloads, 1)
+ if record["result"] not in {item.value for item in CorroborationOutcome}:
+ _shape_error(f"{path}.result", "is not a corroboration outcome")
+ observed_at = _text(record["observed_at"], f"{path}.observed_at")
+ try:
+ if _DATE_TIME.fullmatch(observed_at) is None:
+ raise ValueError
+ datetime.fromisoformat(observed_at.replace("Z", "+00:00"))
+ except ValueError:
+ _shape_error(f"{path}.observed_at", "must be an RFC 3339 date-time")
+ _binding_shape(record["verification"], f"{path}.verification", required_payloads)
+ payloads = receipt["evidence_payloads"]
+ if not isinstance(payloads, Mapping):
+ _shape_error("$.evidence_payloads", "must be an object")
+ for reference, encoded in payloads.items():
+ _sha(reference, "$.evidence_payloads key", "prefixed")
+ _text(encoded, f"$.evidence_payloads.{reference}")
+ missing = sorted(required_payloads - set(payloads))
+ if missing:
+ _shape_error("$.evidence_payloads", f"is missing verdict-material bytes for {missing}")
+ _result_shape(receipt["result"], "$.result")
+
+def assess_witness_corroboration(
+ entry: EvidenceBoundDepthResult,
+ bundle: WitnessBundle,
+ *,
+ session: VerificationSession,
+) -> WitnessCorroborationResult:
+ """Recheck VSTD-5 entry, separation evidence, and corroboration evidence."""
+
+ require_vstd5_entry(entry)
+ binding_errors: list[str] = []
+ identity_errors: list[str] = []
+ separation_errors: list[str] = []
+ corroboration_errors: list[str] = []
+ independence_results: list[tuple[str, str, EvaluatedProposition]] = []
+ corroboration_results: list[tuple[str, EvaluatedProposition]] = []
+
+ if bundle.claim_id == "":
+ binding_errors.append("claim_id must not be empty")
+ if bundle.claim_id != entry.claim_id:
+ binding_errors.append(
+ "witness bundle claim_id does not match the admitted VSTD-4 claim_id"
+ )
+ if bundle.declarant_id == "":
+ identity_errors.append("declarant_id must not be empty")
+ if bundle.claim_binding_digest != entry.witness.header.binding: # type: ignore[union-attr]
+ binding_errors.append(
+ "witness bundle does not bind the admitted VSTD-4 commitment"
+ )
+
+ identities: dict[str, WitnessIdentity] = {}
+ identity_refs: set[str] = set()
+ for witness in bundle.witnesses:
+ if not witness.witness_id:
+ identity_errors.append("witness_id must not be empty")
+ continue
+ if witness.witness_id == bundle.declarant_id:
+ identity_errors.append(f"witness {witness.witness_id} is the declarant")
+ if witness.witness_id in identities:
+ identity_errors.append(
+ f"duplicate witness identifier: {witness.witness_id}"
+ )
+ identities[witness.witness_id] = witness
+ try:
+ identity_ref = session.evidence.add(
+ session.evidence.resolve(witness.identity_evidence_ref)
+ )
+ except Exception as exc:
+ identity_errors.append(
+ f"witness {witness.witness_id} identity evidence unavailable: {exc}"
+ )
+ continue
+ if identity_ref in identity_refs:
+ identity_errors.append(
+ f"witness {witness.witness_id} repeats another witness identity evidence"
+ )
+ identity_refs.add(identity_ref)
+
+ assertions: dict[str, IndependenceAssertion] = {}
+ for assertion in bundle.independence:
+ if assertion.witness_id in assertions:
+ separation_errors.append(
+ f"duplicate independence assertion: {assertion.witness_id}"
+ )
+ continue
+ assertions[assertion.witness_id] = assertion
+ if assertion.witness_id not in identities:
+ separation_errors.append(
+ f"independence assertion references missing witness {assertion.witness_id}"
+ )
+ continue
+ for dimension in IndependenceDimension:
+ state = assertion.relationships.get(dimension, RelationshipState.UNKNOWN)
+ if state is RelationshipState.SHARED:
+ separation_errors.append(
+ f"witness {assertion.witness_id} shares {dimension.value} with declarant"
+ )
+ continue
+ if state is RelationshipState.UNKNOWN:
+ separation_errors.append(
+ f"witness {assertion.witness_id} has UNKNOWN {dimension.value} separation"
+ )
+ continue
+ proposition = assertion.evidence.get(dimension)
+ if proposition is None:
+ separation_errors.append(
+ f"witness {assertion.witness_id} has no evidence for {dimension.value}"
+ )
+ continue
+ relation_subject = f"{bundle.declarant_id}->{assertion.witness_id}"
+ expected_predicate = f"vstd5.shared.{dimension.value}"
+ if (
+ proposition.subject_id != relation_subject
+ or proposition.predicate != expected_predicate
+ or proposition.expected is not False
+ or proposition.parameters.get("claim_binding_digest")
+ != bundle.claim_binding_digest
+ ):
+ separation_errors.append(
+ f"witness {assertion.witness_id} {dimension.value} evidence is not "
+ "bound to the exact negative separation proposition"
+ )
+ continue
+ result = session.evaluate(proposition)
+ independence_results.append(
+ (assertion.witness_id, dimension.value, result)
+ )
+ if not result.passed:
+ separation_errors.append(
+ f"witness {assertion.witness_id} {dimension.value} separation "
+ f"was not established: {result.outcome.value}"
+ )
+
+ for witness_id in identities:
+ if witness_id not in assertions:
+ separation_errors.append(
+ f"witness {witness_id} has no independence assertion"
+ )
+
+ seen_records: set[str] = set()
+ observed_sets: set[tuple[str, ...]] = set()
+ accepted_outcomes: list[tuple[str, CorroborationOutcome]] = []
+ for record in bundle.corroborations:
+ if record.corroboration_id in seen_records:
+ corroboration_errors.append(
+ f"duplicate corroboration identifier: {record.corroboration_id}"
+ )
+ continue
+ seen_records.add(record.corroboration_id)
+ if record.witness_id not in identities:
+ corroboration_errors.append(
+ f"corroboration {record.corroboration_id} references missing witness"
+ )
+ continue
+ if record.claim_binding_digest != bundle.claim_binding_digest:
+ corroboration_errors.append(
+ f"corroboration {record.corroboration_id} binds a neighboring claim"
+ )
+ continue
+ if record.vstd4_certificate_digest.removeprefix("sha256:") != entry.witness.digest(): # type: ignore[union-attr]
+ corroboration_errors.append(
+ f"corroboration {record.corroboration_id} binds a different VSTD-4 certificate"
+ )
+ continue
+ unique_observations = tuple(sorted(set(record.observed_evidence_refs)))
+ if len(unique_observations) != len(record.observed_evidence_refs):
+ corroboration_errors.append(
+ f"corroboration {record.corroboration_id} repeats evidence references"
+ )
+ continue
+ if unique_observations in observed_sets:
+ corroboration_errors.append(
+ f"corroboration {record.corroboration_id} duplicates another evidence set"
+ )
+ continue
+ observed_sets.add(unique_observations)
+ expected = {
+ "claim_binding_digest": bundle.claim_binding_digest,
+ "vstd4_certificate_digest": record.vstd4_certificate_digest,
+ "checker_descriptor_digest": record.checker_descriptor_digest,
+ "corroboration_class": record.corroboration_class,
+ "result": record.result.value,
+ }
+ proposition = record.verification
+ if (
+ proposition.subject_id != bundle.claim_id
+ or proposition.predicate != "vstd5.corroboration"
+ or proposition.expected != expected
+ or tuple(sorted(proposition.evidence_refs)) != unique_observations
+ or proposition.parameters.get("witness_id") != record.witness_id
+ or proposition.parameters.get("observed_at") != record.observed_at
+ ):
+ corroboration_errors.append(
+ f"corroboration {record.corroboration_id} is not exactly bound"
+ )
+ continue
+ result = session.evaluate(proposition)
+ corroboration_results.append((record.corroboration_id, result))
+ if result.outcome is MechanismOutcome.PASS:
+ accepted_outcomes.append((record.corroboration_id, record.result))
+ else:
+ corroboration_errors.append(
+ f"corroboration {record.corroboration_id} mechanism did not pass: "
+ f"{result.outcome.value}"
+ )
+
+ if not bundle.witnesses:
+ identity_errors.append("at least one witness is required")
+ if not bundle.corroborations:
+ corroboration_errors.append("at least one corroboration is required")
+ corroborating_witnesses = {record.witness_id for record in bundle.corroborations}
+ for witness_id in identities:
+ if witness_id not in corroborating_witnesses:
+ corroboration_errors.append(
+ f"witness {witness_id} has no corroboration record"
+ )
+
+ outcome_groups = {
+ outcome: tuple(record_id for record_id, item in accepted_outcomes if item is outcome)
+ for outcome in CorroborationOutcome
+ }
+ nonempty = [outcome for outcome, ids in outcome_groups.items() if ids]
+ disagreements: tuple[tuple[str, ...], ...] = ()
+ if len(nonempty) > 1:
+ status = WitnessResultStatus.CONFLICTED
+ disagreements = (tuple(sorted(record_id for record_id, _ in accepted_outcomes)),)
+ elif nonempty == [CorroborationOutcome.CORROBORATED]:
+ status = WitnessResultStatus.CORROBORATED
+ elif nonempty == [CorroborationOutcome.REFUTED]:
+ status = WitnessResultStatus.REFUTED
+ else:
+ status = WitnessResultStatus.UNKNOWN
+
+ expected_independence_checks = len(identities) * len(IndependenceDimension)
+ independence_established = (
+ bool(identities)
+ and len(bundle.witnesses) == len(identities)
+ and len(assertions) == len(identities)
+ and len(independence_results) == expected_independence_checks
+ and all(result.passed for _, _, result in independence_results)
+ and not binding_errors
+ and not identity_errors
+ and not separation_errors
+ )
+ computed_independence = "INDEPENDENT" if independence_established else "UNKNOWN"
+ conformance = (
+ "ESTABLISHED"
+ if (
+ independence_established
+ and not corroboration_errors
+ and len(corroboration_results) > 0
+ )
+ else "NOT_ESTABLISHED"
+ )
+ if status is WitnessResultStatus.CORROBORATED and conformance != "ESTABLISHED":
+ status = WitnessResultStatus.UNKNOWN
+ return WitnessCorroborationResult(
+ bundle.claim_id,
+ status,
+ conformance,
+ computed_independence,
+ tuple(independence_results),
+ tuple(corroboration_results),
+ disagreements,
+ tuple(binding_errors),
+ tuple(identity_errors),
+ tuple(separation_errors),
+ tuple(corroboration_errors),
+ (
+ "Identity evidence identifies the witness coordinate; it does not confer trust.",
+ "The result is bounded to the registered mechanisms, trust roots, evidence, and bounds.",
+ ),
+ )
+
+
+def _vstd5_entry_record(entry: EvidenceBoundDepthResult) -> dict[str, Any]:
+ """Return every redundant VSTD-4 coordinate carried by a VSTD-5 receipt."""
+
+ return {
+ "result_digest": canonical_digest(entry.to_dict()),
+ "depth": entry.depth,
+ "conformance_status": entry.conformance_status,
+ "witness_digest": entry.witness.digest(), # type: ignore[union-attr]
+ }
+
+
+def build_vstd5_receipt(
+ entry: EvidenceBoundDepthResult,
+ bundle: WitnessBundle,
+ result: WitnessCorroborationResult,
+ *,
+ receipt_id: str,
+ session: VerificationSession,
+) -> dict[str, Any]:
+ """Serialize a replayable VSTD-5 receipt without treating names as trust."""
+ require_vstd5_entry(entry)
+ recomputed = assess_witness_corroboration(entry, bundle, session=session)
+ if canonical_digest(recomputed.to_dict()) != canonical_digest(result.to_dict()):
+ raise ValueError("VSTD-5 result does not match the supplied replay inputs")
+ references = {
+ witness.identity_evidence_ref for witness in bundle.witnesses
+ }
+ for assertion in bundle.independence:
+ for proposition in assertion.evidence.values():
+ references.update(proposition.evidence_refs)
+ for record in bundle.corroborations:
+ references.update(record.observed_evidence_refs)
+ references.update(record.verification.evidence_refs)
+ receipt = {
+ "schema_version": "VSTD-5",
+ "receipt_id": receipt_id,
+ "entry_vstd4": _vstd5_entry_record(entry),
+ "bundle": bundle.to_dict(),
+ "evidence_payloads": session.evidence.export_base64(tuple(sorted(references))),
+ "result": result.to_dict(),
+ }
+ _validate_vstd5_receipt_shape(receipt)
+ return receipt
+
+
+def recheck_vstd5_receipt(
+ entry: EvidenceBoundDepthResult,
+ receipt: Mapping[str, Any],
+ *,
+ mechanisms: tuple[VerificationMechanism, ...],
+) -> WitnessCorroborationResult:
+ """Import exact bytes, rerun all witness mechanisms, and compare the result."""
+ require_vstd5_entry(entry)
+ _validate_vstd5_receipt_shape(receipt)
+ entry_record = receipt.get("entry_vstd4")
+ bundle_data = receipt.get("bundle")
+ payloads = receipt.get("evidence_payloads")
+ if not isinstance(entry_record, Mapping) or not isinstance(bundle_data, Mapping) or not isinstance(payloads, Mapping):
+ raise ValueError("VSTD-5 receipt is missing replay inputs")
+ if dict(entry_record) != _vstd5_entry_record(entry):
+ raise ValueError("VSTD-5 receipt references an inconsistent VSTD-4 entry")
+ store = EvidenceStore()
+ store.import_base64({str(key): str(value) for key, value in payloads.items()})
+ session = VerificationSession(store)
+ for mechanism in mechanisms:
+ session.register(mechanism)
+ result = assess_witness_corroboration(
+ entry, WitnessBundle.from_dict(bundle_data), session=session
+ )
+ if canonical_digest(result.to_dict()) != canonical_digest(receipt.get("result")):
+ raise ValueError("recomputed VSTD-5 result does not match receipt")
+ return result
+
+
+__all__ = [
+ "CorroborationOutcome",
+ "CorroborationRecord",
+ "IndependenceAssertion",
+ "IndependenceDimension",
+ "RelationshipState",
+ "WitnessBundle",
+ "WitnessCorroborationResult",
+ "WitnessIdentity",
+ "WitnessResultStatus",
+ "assess_witness_corroboration",
+ "build_vstd5_receipt",
+ "recheck_vstd5_receipt",
+]
diff --git a/src/verifier/data/__init__.py b/src/verifier/data/__init__.py
index 602b9ea..cd87778 100644
--- a/src/verifier/data/__init__.py
+++ b/src/verifier/data/__init__.py
@@ -1,16 +1,32 @@
-"""Target-neutral VSTD-Graph reference types and receipt mechanisms."""
+"""Terminology: Verifier Standard (VSTD).
+
+Target-neutral VSTD-Graph reference types and receipt mechanisms."""
from verifier.data.graph_level import (
+ EvidenceBoundGraphLevelResult,
GraphCollection,
GraphLevelResult,
+ establish_graph_level,
+ graph_collection_binding_digest,
graph_level,
)
+from verifier.data.assurance import (
+ AssuranceEvent,
+ AssuranceEventKind,
+ AssuranceLedger,
+ ChallengeProjectionMechanism,
+ DiagnosticAttribution,
+ DiagnosticKind,
+ ObligationCoordinate,
+ recheck_assurance_log,
+)
from verifier.data.models import (
ArtifactNode,
ArtifactStatus,
ArtifactType,
CompletenessMetrics,
+ ConflictRecord,
ContributorSpec,
HyperedgePort,
ProvenanceHypergraph,
@@ -32,6 +48,7 @@
"ArtifactStatus",
"ArtifactType",
"CompletenessMetrics",
+ "ConflictRecord",
"ContributorSpec",
"HyperedgePort",
"ProvenanceHypergraph",
@@ -40,7 +57,18 @@
"TransformationType",
"GraphCollection",
"GraphLevelResult",
+ "EvidenceBoundGraphLevelResult",
+ "establish_graph_level",
+ "graph_collection_binding_digest",
"graph_level",
+ "AssuranceEvent",
+ "AssuranceEventKind",
+ "AssuranceLedger",
+ "ChallengeProjectionMechanism",
+ "DiagnosticAttribution",
+ "DiagnosticKind",
+ "ObligationCoordinate",
+ "recheck_assurance_log",
"PolicyEvaluationResult",
"ProvenancePolicyVerifier",
"DataIndependentAudit",
diff --git a/src/verifier/data/assurance.py b/src/verifier/data/assurance.py
new file mode 100644
index 0000000..1120a70
--- /dev/null
+++ b/src/verifier/data/assurance.py
@@ -0,0 +1,2101 @@
+"""Terminology: Secure Hash Algorithm 256-bit (SHA-256); Verifier Standard (VSTD).
+
+Executable VSTD-Graph artifact-state propagation.
+
+``TRUST`` is mechanism-earned forward artifact support. ``RUST`` is reverse
+diagnostic traversal from a verified descendant deviation toward recorded
+ancestors. ``ROT`` is typed degradation of current admissibility without
+rewriting historical graph bytes. These names are formal semantic terms, not
+acronyms, scalar scores, actor reputation, or references to the Rust language.
+
+The ledger is additive and hash chained. It can project a challenge ledger into
+a current Graph view, preserve conflict resolutions as new records, deduplicate
+support and reachability, compute structural RUST concentration, and perform
+bounded artifact-relative diagnostic attribution. RUST reachability alone
+never establishes falsity, causality, blame, guilt, or responsibility.
+"""
+
+from __future__ import annotations
+
+import json
+import re
+from dataclasses import dataclass, field, replace
+from enum import Enum
+from typing import Any, Iterable, Mapping, Optional
+
+from verifier.core.certificate import canonical_bytes, canonical_digest
+from verifier.core.evidence import (
+ BoundProposition,
+ EvidenceBounds,
+ EvidenceStore,
+ EvaluatedProposition,
+ MechanismDecision,
+ MechanismOutcome,
+ VerificationMechanism,
+ VerificationSession,
+ implementation_file_digest,
+)
+from verifier.layer4.challenge import (
+ ChallengeLedger,
+ ChallengeOutcome,
+ DEGRADATION_ORDER,
+ most_degraded,
+)
+
+from .models import ArtifactStatus, ConflictRecord, ProvenanceHypergraph
+
+
+class AssuranceFlowError(ValueError):
+ """A requested propagation would exceed recorded topology or evidence."""
+
+
+class AssuranceEventKind(str, Enum):
+ TRUST = "TRUST"
+ ROT = "ROT"
+ RUST = "RUST"
+ STATUS_PROJECTION = "STATUS_PROJECTION"
+ CONFLICT_DECLARATION = "CONFLICT_DECLARATION"
+ CONFLICT_RESOLUTION = "CONFLICT_RESOLUTION"
+ CAUSAL_LOCALIZATION = "CAUSAL_LOCALIZATION"
+ RESPONSIBILITY_COMPONENT = "RESPONSIBILITY_COMPONENT"
+ OBLIGATION_APPLICABILITY = "OBLIGATION_APPLICABILITY"
+ OBLIGATION_VIOLATION = "OBLIGATION_VIOLATION"
+ DIAGNOSTIC_ATTRIBUTION = "DIAGNOSTIC_ATTRIBUTION"
+
+
+class DiagnosticKind(str, Enum):
+ BLAME = "BLAME"
+ GUILT = "GUILT"
+
+
+_DIGEST_REF = re.compile(r"^sha256:[0-9a-f]{64}$")
+
+
+@dataclass(frozen=True)
+class ObligationCoordinate:
+ """Exact technical obligation and the declared scope in which it applies.
+
+ An identifier or content digest names the obligation. ``scope`` carries
+ every material time, realm, jurisdiction, contract, policy, or version
+ coordinate. Assumptions and exclusions remain part of the exact coordinate;
+ the evaluating proposition separately binds its evidence, mechanism, trust
+ roots, and resource bounds.
+ """
+
+ obligation_id: str = ""
+ content_digest: str = ""
+ scope: Mapping[str, str] = field(default_factory=dict)
+ assumptions: tuple[str, ...] = ()
+ exclusions: tuple[str, ...] = ()
+
+ def __post_init__(self) -> None:
+ if not self.obligation_id and not self.content_digest:
+ raise AssuranceFlowError(
+ "an obligation coordinate requires an identifier or content digest"
+ )
+ if self.content_digest and not _DIGEST_REF.fullmatch(self.content_digest):
+ raise AssuranceFlowError(
+ "obligation content_digest must be a sha256 content reference"
+ )
+ if not self.scope:
+ raise AssuranceFlowError("an obligation coordinate requires declared scope")
+ normalized_scope: dict[str, str] = {}
+ for key, value in self.scope.items():
+ if not isinstance(key, str) or not key or not isinstance(value, str) or not value:
+ raise AssuranceFlowError(
+ "obligation scope keys and values must be nonempty strings"
+ )
+ normalized_scope[key] = value
+ if any(not isinstance(item, str) or not item for item in self.assumptions):
+ raise AssuranceFlowError("obligation assumptions must be nonempty strings")
+ if any(not isinstance(item, str) or not item for item in self.exclusions):
+ raise AssuranceFlowError("obligation exclusions must be nonempty strings")
+ object.__setattr__(self, "scope", dict(sorted(normalized_scope.items())))
+ object.__setattr__(self, "assumptions", tuple(sorted(set(self.assumptions))))
+ object.__setattr__(self, "exclusions", tuple(sorted(set(self.exclusions))))
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "obligation_id": self.obligation_id,
+ "content_digest": self.content_digest,
+ "scope": dict(self.scope),
+ "assumptions": list(self.assumptions),
+ "exclusions": list(self.exclusions),
+ }
+
+ def digest(self) -> str:
+ return canonical_digest(self.to_dict())
+
+ @classmethod
+ def from_dict(cls, data: Mapping[str, Any]) -> "ObligationCoordinate":
+ scope = data.get("scope")
+ if not isinstance(scope, Mapping):
+ raise AssuranceFlowError("obligation scope is not an object")
+ return cls(
+ obligation_id=str(data.get("obligation_id", "")),
+ content_digest=str(data.get("content_digest", "")),
+ scope={str(key): str(value) for key, value in scope.items()},
+ assumptions=tuple(str(item) for item in data.get("assumptions", ())),
+ exclusions=tuple(str(item) for item in data.get("exclusions", ())),
+ )
+
+
+class ChallengeProjectionMechanism:
+ """Recompute one artifact's status from embedded challenge-ledger records."""
+
+ mechanism_id = "vstd.challenge-ledger.projection"
+ mechanism_digest = implementation_file_digest(__file__)
+
+ def evaluate(
+ self, binding: BoundProposition, evidence: tuple[bytes, ...]
+ ) -> MechanismDecision:
+ if binding.predicate != "vstd.graph.current_status":
+ return MechanismDecision(
+ MechanismOutcome.UNKNOWN,
+ "this mechanism checks one projected challenge-ledger status",
+ )
+ try:
+ records = [json.loads(payload.decode("utf-8")) for payload in evidence]
+ status, details = _status_from_challenge_records(
+ binding.subject_id, records
+ )
+ except (KeyError, TypeError, ValueError, UnicodeError, json.JSONDecodeError) as exc:
+ return MechanismDecision(
+ MechanismOutcome.FAIL,
+ f"challenge-ledger projection evidence is invalid: {exc}",
+ )
+ expected = ArtifactStatus(str(binding.expected))
+ return MechanismDecision(
+ MechanismOutcome.PASS if status is expected else MechanismOutcome.FAIL,
+ details,
+ {"observed_status": status.value},
+ )
+
+
+def _status_from_challenge_records(
+ subject_id: str, records: Iterable[Mapping[str, Any]]
+) -> tuple[ArtifactStatus, str]:
+ """Independent projection over serialized records; no mutable ledger state."""
+
+ ordered = sorted(records, key=lambda item: int(item["sequence"]))
+ if len({int(item["sequence"]) for item in ordered}) != len(ordered):
+ raise ValueError("challenge records repeat a sequence number")
+ filed: dict[str, ArtifactStatus] = {}
+ outcomes: dict[str, ChallengeOutcome] = {}
+ for record in ordered:
+ if str(record["claim_id"]) != subject_id:
+ raise ValueError("challenge record targets a neighboring artifact")
+ kind = str(record["kind"])
+ payload = record["payload"]
+ if not isinstance(payload, Mapping):
+ raise ValueError("challenge record payload is not an object")
+ if kind == "REFUSED":
+ continue
+ if kind == "FILED":
+ challenge = payload["challenge"]
+ admission = payload["admission"]
+ if not isinstance(challenge, Mapping) or not isinstance(admission, Mapping):
+ raise ValueError("filed challenge record is malformed")
+ challenge_id = str(challenge["challenge_id"])
+ if challenge_id in filed:
+ raise ValueError("challenge identifier is repeated")
+ if not bool(admission["admitted"]):
+ raise ValueError("a FILED record carries a refused admission")
+ filed[challenge_id] = ArtifactStatus(
+ str(admission.get("resulting_status", ArtifactStatus.REVOKED.value))
+ )
+ continue
+ if kind == "ADJUDICATED":
+ adjudication = payload["adjudication"]
+ if not isinstance(adjudication, Mapping):
+ raise ValueError("adjudication record is malformed")
+ challenge_id = str(adjudication["challenge_id"])
+ if challenge_id not in filed:
+ raise ValueError("adjudication precedes its filed challenge")
+ if challenge_id in outcomes:
+ raise ValueError("challenge has multiple adjudications")
+ outcomes[challenge_id] = ChallengeOutcome(str(adjudication["outcome"]))
+ continue
+ raise ValueError(f"unknown challenge record kind {kind!r}")
+
+ confirmed = tuple(
+ sorted(
+ challenge_id
+ for challenge_id, outcome in outcomes.items()
+ if outcome is ChallengeOutcome.ACCEPTED
+ )
+ )
+ open_ids = tuple(
+ sorted(
+ challenge_id
+ for challenge_id in filed
+ if outcomes.get(challenge_id)
+ in (None, ChallengeOutcome.UNRESOLVED)
+ )
+ )
+ if confirmed:
+ return (
+ most_degraded(filed[challenge_id] for challenge_id in confirmed),
+ f"{len(confirmed)} confirmed refutation(s); status is terminal",
+ )
+ if open_ids:
+ return (
+ ArtifactStatus.CHALLENGED,
+ f"{len(open_ids)} open credible challenge(s); an unadjudicated "
+ "challenge is not evidence of validity",
+ )
+ if filed:
+ return (
+ ArtifactStatus.VALID,
+ f"all {len(filed)} challenge(s) adjudicated and disproven",
+ )
+ return ArtifactStatus.VALID, "no challenges filed"
+
+
+@dataclass(frozen=True)
+class AssuranceEvent:
+ sequence: int
+ kind: AssuranceEventKind
+ subject_id: str
+ source_ids: tuple[str, ...]
+ proposition: str
+ binding: Mapping[str, Any]
+ recorded_at: str
+ outcome: MechanismOutcome
+ mechanism_id: str
+ mechanism_digest: str
+ evidence_refs: tuple[str, ...]
+ evidence_payloads: Mapping[str, str]
+ trust_roots: tuple[str, ...]
+ details: str
+ previous_event_digest: str = ""
+ attributes: Mapping[str, Any] = field(default_factory=dict)
+
+ def payload(self) -> dict[str, Any]:
+ return {
+ "sequence": self.sequence,
+ "kind": self.kind.value,
+ "subject_id": self.subject_id,
+ "source_ids": list(self.source_ids),
+ "proposition": self.proposition,
+ "binding": dict(self.binding),
+ "recorded_at": self.recorded_at,
+ "outcome": self.outcome.value,
+ "mechanism_id": self.mechanism_id,
+ "mechanism_digest": self.mechanism_digest,
+ "evidence_refs": list(self.evidence_refs),
+ "evidence_payloads": dict(self.evidence_payloads),
+ "trust_roots": list(self.trust_roots),
+ "details": self.details,
+ "previous_event_digest": self.previous_event_digest,
+ "attributes": dict(self.attributes),
+ }
+
+ def digest(self) -> str:
+ return canonical_digest(self.payload())
+
+ def to_dict(self) -> dict[str, Any]:
+ result = self.payload()
+ result["event_digest"] = self.digest()
+ return result
+
+
+@dataclass(frozen=True)
+class ConflictResolution:
+ resolution_id: str
+ conflict_id: str
+ selected_value: str
+ recorded_at: str
+ evaluation: EvaluatedProposition
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "resolution_id": self.resolution_id,
+ "conflict_id": self.conflict_id,
+ "selected_value": self.selected_value,
+ "recorded_at": self.recorded_at,
+ "evaluation": self.evaluation.to_dict(),
+ }
+
+
+@dataclass(frozen=True)
+class StructuralConcentration:
+ ancestor_id: str
+ deviating_descendants: tuple[str, ...]
+
+ @property
+ def count(self) -> int:
+ return len(self.deviating_descendants)
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "ancestor_id": self.ancestor_id,
+ "deviating_descendants": list(self.deviating_descendants),
+ "count": self.count,
+ "meaning": "unique diagnostic reachability roots; not causal strength",
+ }
+
+
+@dataclass(frozen=True)
+class DiagnosticAttribution:
+ kind: DiagnosticKind
+ ancestor_id: str
+ descendant_id: str
+ status: str
+ localization_event_digest: str
+ evaluation: Optional[EvaluatedProposition]
+ details: str
+ obligation_coordinate: Optional[ObligationCoordinate] = None
+ responsibility_component_digest: str = ""
+ applicability_component_digest: str = ""
+ violation_component_digest: str = ""
+
+ def to_dict(self) -> dict[str, Any]:
+ result = {
+ "kind": self.kind.value,
+ "ancestor_id": self.ancestor_id,
+ "descendant_id": self.descendant_id,
+ "status": self.status,
+ "localization_event_digest": self.localization_event_digest,
+ "evaluation": None if self.evaluation is None else self.evaluation.to_dict(),
+ "details": self.details,
+ }
+ if self.obligation_coordinate is not None:
+ result["obligation_coordinate"] = self.obligation_coordinate.to_dict()
+ if self.responsibility_component_digest:
+ result["responsibility_component_digest"] = self.responsibility_component_digest
+ if self.applicability_component_digest:
+ result["applicability_component_digest"] = self.applicability_component_digest
+ if self.violation_component_digest:
+ result["violation_component_digest"] = self.violation_component_digest
+ return result
+
+
+class AssuranceLedger:
+ """Append-only current-state overlay for an immutable provenance graph."""
+
+ FORMAT = "VSTD-GRAPH-ASSURANCE-1"
+
+ def __init__(self, graph: ProvenanceHypergraph) -> None:
+ errors = graph.validate_structure()
+ if errors:
+ raise AssuranceFlowError("invalid source graph: " + "; ".join(errors))
+ if not graph.verify_acyclicity():
+ raise AssuranceFlowError(
+ "cyclic provenance cannot carry recursive assurance propagation"
+ )
+ self.graph = ProvenanceHypergraph.from_dict(graph.to_dict())
+ self._graph_digest = canonical_digest(self.graph.to_dict())
+ self._events: list[AssuranceEvent] = []
+ self._conflicts: dict[str, ConflictRecord] = dict(self.graph.conflicts)
+ self._resolutions: dict[str, ConflictResolution] = {}
+
+ @property
+ def graph_digest(self) -> str:
+ return self._graph_digest
+
+ def events(self) -> tuple[AssuranceEvent, ...]:
+ return tuple(self._events)
+
+ def resolutions(self) -> tuple[ConflictResolution, ...]:
+ return tuple(self._resolutions.values())
+
+ def _append(
+ self,
+ *,
+ kind: AssuranceEventKind,
+ subject_id: str,
+ source_ids: Iterable[str],
+ proposition: str,
+ binding: Mapping[str, Any],
+ recorded_at: str,
+ evaluation: EvaluatedProposition,
+ evidence_payloads: Mapping[str, str],
+ attributes: Optional[Mapping[str, Any]] = None,
+ ) -> AssuranceEvent:
+ sources = tuple(sorted(set(source_ids)))
+ semantic_key = (
+ kind.value,
+ subject_id,
+ sources,
+ proposition,
+ evaluation.binding_digest,
+ tuple(sorted((attributes or {}).items())),
+ )
+ for event in self._events:
+ other_key = (
+ event.kind.value,
+ event.subject_id,
+ event.source_ids,
+ event.proposition,
+ event.attributes.get("binding_digest", ""),
+ tuple(sorted((k, v) for k, v in event.attributes.items() if k != "binding_digest")),
+ )
+ if semantic_key == other_key:
+ return event
+ previous = "" if not self._events else self._events[-1].digest()
+ combined_attributes = dict(attributes or {})
+ combined_attributes["binding_digest"] = evaluation.binding_digest
+ event = AssuranceEvent(
+ len(self._events),
+ kind,
+ subject_id,
+ sources,
+ proposition,
+ dict(binding),
+ recorded_at,
+ evaluation.outcome,
+ evaluation.mechanism_id,
+ evaluation.mechanism_digest,
+ evaluation.evidence_refs,
+ dict(evidence_payloads),
+ evaluation.trust_roots,
+ evaluation.details,
+ previous,
+ combined_attributes,
+ )
+ self._events.append(event)
+ return event
+
+ def current_status(self, artifact_id: str) -> ArtifactStatus:
+ node = self.graph.artifacts.get(artifact_id)
+ if node is None:
+ return ArtifactStatus.UNKNOWN
+ latest_projection: Optional[ArtifactStatus] = None
+ rot_statuses: list[ArtifactStatus] = []
+ for event in self._events:
+ if event.subject_id != artifact_id or event.outcome is not MechanismOutcome.PASS:
+ continue
+ if event.kind is AssuranceEventKind.STATUS_PROJECTION:
+ candidate = event.attributes.get("resulting_status")
+ if candidate is not None:
+ latest_projection = ArtifactStatus(str(candidate))
+ elif event.kind is AssuranceEventKind.ROT:
+ candidate = event.attributes.get("resulting_status")
+ if candidate is not None:
+ rot_statuses.append(ArtifactStatus(str(candidate)))
+ statuses = [node.status, *rot_statuses]
+ if latest_projection is not None:
+ statuses.append(latest_projection)
+ for resolution in self._resolutions.values():
+ conflict = self._conflicts[resolution.conflict_id]
+ if conflict.subject_id == artifact_id and conflict.predicate == "status":
+ statuses.append(ArtifactStatus(resolution.selected_value))
+ return most_degraded(statuses)
+
+ def current_transformation_status(self, transformation_id: str) -> str:
+ """Return a transformation's additive current status projection."""
+ transform = self.graph.transformations.get(transformation_id)
+ if transform is None:
+ return "UNKNOWN"
+ if transform.status != "COMPLETED":
+ return transform.status
+ for resolution in self._resolutions.values():
+ conflict = self._conflicts[resolution.conflict_id]
+ if (
+ conflict.subject_id == transformation_id
+ and conflict.predicate == "status"
+ and resolution.selected_value != "COMPLETED"
+ ):
+ return resolution.selected_value
+ return "COMPLETED"
+
+ def admissibility_blocking_conflicts(self) -> tuple[ConflictRecord, ...]:
+ """Return conflicts whose effect still blocks a clean TRUST route.
+
+ A passing resolution decides which retained value prevailed. It restores
+ admissibility only when the conflict is exactly about ``status`` and the
+ selected current state is itself admissible. Arbitrary resolved fields
+ remain blockers because adjudicating a value does not establish its
+ effect on edge-local support.
+ """
+ resolutions = {
+ item.conflict_id: item for item in self._resolutions.values()
+ }
+ blocking: list[ConflictRecord] = []
+ for conflict_id, conflict in self._conflicts.items():
+ resolution = resolutions.get(conflict_id)
+ if resolution is None:
+ blocking.append(conflict)
+ continue
+ if conflict.predicate != "status":
+ blocking.append(conflict)
+ continue
+ if conflict.subject_id in self.graph.artifacts:
+ if self.current_status(conflict.subject_id) is not ArtifactStatus.VALID:
+ blocking.append(conflict)
+ elif self.current_transformation_status(conflict.subject_id) != "COMPLETED":
+ blocking.append(conflict)
+ return tuple(blocking)
+
+ def impacted_descendants(self, artifact_id: str) -> tuple[str, ...]:
+ """Return the deduplicated recorded forward impact set, not a verdict."""
+ if artifact_id not in self.graph.artifacts:
+ raise AssuranceFlowError(f"unknown impact origin {artifact_id}")
+ return tuple(sorted(self.graph.descendants((artifact_id,)) - {artifact_id}))
+
+ def current_trust_events(self) -> tuple[AssuranceEvent, ...]:
+ """Return recursively current edge-local TRUST records."""
+ if canonical_digest(self.graph.to_dict()) != self.graph_digest:
+ return ()
+ blocked_subjects = {
+ item.subject_id for item in self.admissibility_blocking_conflicts()
+ }
+ trust_events = {
+ event.digest(): event
+ for event in self._events
+ if event.kind is AssuranceEventKind.TRUST
+ }
+ memo: dict[str, bool] = {}
+
+ def is_current(event_digest: str, visiting: set[str]) -> bool:
+ cached = memo.get(event_digest)
+ if cached is not None:
+ return cached
+ if event_digest in visiting:
+ memo[event_digest] = False
+ return False
+ event = trust_events.get(event_digest)
+ if event is None or event.outcome is not MechanismOutcome.PASS:
+ memo[event_digest] = False
+ return False
+ visiting.add(event_digest)
+ try:
+ transformation_id = str(event.attributes["transformation_id"])
+ historical_graph_digest = str(
+ event.attributes["historical_graph_digest"]
+ )
+ attribute_inputs = tuple(str(item) for item in event.attributes["inputs"])
+ attribute_output = str(event.attributes["output"])
+ prerequisite_digests = tuple(
+ str(item)
+ for item in event.attributes["prerequisite_trust_event_digests"]
+ )
+ transform = self.graph.transformations[transformation_id]
+ except (KeyError, TypeError):
+ memo[event_digest] = False
+ return False
+
+ exact_inputs = tuple(sorted({port.artifact_id for port in transform.inputs}))
+ output_ids = {port.artifact_id for port in transform.outputs}
+ expected = {
+ "historical_graph_digest": self.graph_digest,
+ "inputs": list(exact_inputs),
+ "output": event.subject_id,
+ "prerequisite_trust_event_digests": list(prerequisite_digests),
+ "transformation_id": transformation_id,
+ }
+ required_prerequisite_targets = {
+ source
+ for source in exact_inputs
+ if self.graph.incoming_hyperedges(source)
+ }
+ prerequisite_targets: list[str] = []
+ valid = (
+ historical_graph_digest == self.graph_digest
+ and attribute_inputs == exact_inputs
+ and attribute_output == event.subject_id
+ and event.source_ids == exact_inputs
+ and event.subject_id in output_ids
+ and self.current_transformation_status(transformation_id) == "COMPLETED"
+ and event.subject_id not in blocked_subjects
+ and transformation_id not in blocked_subjects
+ and all(source not in blocked_subjects for source in exact_inputs)
+ and self.current_status(event.subject_id) is ArtifactStatus.VALID
+ and all(
+ self.current_status(source) is ArtifactStatus.VALID
+ for source in exact_inputs
+ )
+ and event.binding.get("subject_id") == event.subject_id
+ and event.binding.get("predicate") == "vstd.graph.support"
+ and event.binding.get("expected") == expected
+ and len(set(prerequisite_digests)) == len(prerequisite_digests)
+ )
+ if valid:
+ for prerequisite_digest in prerequisite_digests:
+ prerequisite = trust_events.get(prerequisite_digest)
+ if (
+ prerequisite is None
+ or prerequisite.sequence >= event.sequence
+ or not is_current(prerequisite_digest, visiting)
+ ):
+ valid = False
+ break
+ prerequisite_targets.append(prerequisite.subject_id)
+ if valid:
+ valid = (
+ len(prerequisite_targets) == len(required_prerequisite_targets)
+ and set(prerequisite_targets) == required_prerequisite_targets
+ )
+ memo[event_digest] = valid
+ return valid
+
+ return tuple(
+ event
+ for event_digest, event in trust_events.items()
+ if is_current(event_digest, set())
+ )
+
+ def unresolved_conflicts(self) -> tuple[ConflictRecord, ...]:
+ resolved = {item.conflict_id for item in self._resolutions.values()}
+ return tuple(
+ conflict
+ for conflict_id, conflict in self._conflicts.items()
+ if conflict_id not in resolved
+ )
+
+ def materialize_current_graph(self) -> ProvenanceHypergraph:
+ """Create a derived current view; never mutate the historical graph."""
+ current = ProvenanceHypergraph.from_dict(self.graph.to_dict())
+ for conflict_id, conflict in self._conflicts.items():
+ if conflict_id not in current.conflicts:
+ current.add_conflict(conflict)
+ for artifact_id, node in tuple(current.artifacts.items()):
+ current.artifacts[artifact_id] = replace(
+ node, status=self.current_status(artifact_id)
+ )
+ for transformation_id, transform in tuple(current.transformations.items()):
+ current.transformations[transformation_id] = replace(
+ transform,
+ status=self.current_transformation_status(transformation_id),
+ )
+ for resolution in self._resolutions.values():
+ conflict = self._conflicts[resolution.conflict_id]
+ if conflict.predicate == "status":
+ current.conflicts.pop(resolution.conflict_id, None)
+ return current
+
+ def project_challenges(
+ self,
+ challenges: ChallengeLedger,
+ *,
+ recorded_at: str,
+ ) -> tuple[AssuranceEvent, ...]:
+ """Project challenge state into an additive current Graph overlay."""
+ projected: list[AssuranceEvent] = []
+ mechanism = ChallengeProjectionMechanism()
+ for artifact_id in sorted(self.graph.artifacts):
+ records = challenges.records(artifact_id)
+ if not records:
+ continue
+ claim_status = challenges.status(artifact_id)
+ store = EvidenceStore()
+ references = tuple(
+ store.add(canonical_bytes(record.to_dict())) for record in records
+ )
+ binding = BoundProposition(
+ artifact_id,
+ "vstd.graph.current_status",
+ claim_status.status.value,
+ mechanism.mechanism_id,
+ mechanism.mechanism_digest,
+ references,
+ ("verifier.layer4.challenge.ChallengeLedger",),
+ EvidenceBounds(
+ len(references),
+ sum(len(canonical_bytes(record.to_dict())) for record in records),
+ ),
+ )
+ session = VerificationSession(store)
+ session.register(mechanism)
+ event = self.record_status_projection(
+ artifact_id,
+ binding,
+ session=session,
+ recorded_at=recorded_at,
+ )
+ if event.outcome is not MechanismOutcome.PASS:
+ raise AssuranceFlowError(
+ f"challenge projection failed for {artifact_id}: {event.details}"
+ )
+ projected.append(event)
+ return tuple(projected)
+
+ def record_status_projection(
+ self,
+ artifact_id: str,
+ proposition: BoundProposition,
+ *,
+ session: VerificationSession,
+ recorded_at: str,
+ ) -> AssuranceEvent:
+ """Record a mechanism-checked current-status projection additively."""
+ if artifact_id not in self.graph.artifacts:
+ raise AssuranceFlowError(f"unknown status projection subject {artifact_id}")
+ if (
+ proposition.subject_id != artifact_id
+ or proposition.predicate != "vstd.graph.current_status"
+ or proposition.mechanism_id
+ != ChallengeProjectionMechanism.mechanism_id
+ ):
+ raise AssuranceFlowError(
+ "status projection is not bound to the challenge projection mechanism"
+ )
+ resulting_status = ArtifactStatus(str(proposition.expected))
+ evaluation = session.evaluate(proposition)
+ return self._append(
+ kind=AssuranceEventKind.STATUS_PROJECTION,
+ subject_id=artifact_id,
+ source_ids=(),
+ proposition=proposition.predicate,
+ binding=proposition.to_dict(),
+ recorded_at=recorded_at,
+ evaluation=evaluation,
+ evidence_payloads=session.evidence.export_base64(evaluation.evidence_refs),
+ attributes={"resulting_status": resulting_status.value},
+ )
+
+ def record_conflict(
+ self,
+ conflict: ConflictRecord,
+ proposition: BoundProposition,
+ *,
+ session: VerificationSession,
+ recorded_at: str,
+ ) -> AssuranceEvent:
+ """Add mechanism-established current conflict evidence without rewriting Graph."""
+ if (
+ conflict.subject_id not in self.graph.artifacts
+ and conflict.subject_id not in self.graph.transformations
+ ):
+ raise AssuranceFlowError(
+ f"unknown conflict subject {conflict.subject_id}"
+ )
+ if (
+ not conflict.conflict_id
+ or not conflict.predicate
+ or len(conflict.competing_values) < 2
+ or len(set(conflict.competing_values)) != len(conflict.competing_values)
+ or not conflict.evidence_refs
+ or len(set(conflict.evidence_refs)) != len(conflict.evidence_refs)
+ ):
+ raise AssuranceFlowError(
+ "conflict requires an identifier, predicate, distinct competing values, "
+ "and distinct evidence references"
+ )
+ prior = self._conflicts.get(conflict.conflict_id)
+ if prior is not None:
+ raise AssuranceFlowError(
+ f"conflict identifier {conflict.conflict_id} is already bound"
+ )
+ expected = conflict.to_dict()
+ if (
+ proposition.subject_id != conflict.subject_id
+ or proposition.predicate != "vstd.graph.conflict"
+ or proposition.expected != expected
+ ):
+ raise AssuranceFlowError("conflict evidence is not exactly record-bound")
+ evaluation = session.evaluate(proposition)
+ event = self._append(
+ kind=AssuranceEventKind.CONFLICT_DECLARATION,
+ subject_id=conflict.subject_id,
+ source_ids=conflict.evidence_refs,
+ proposition=proposition.predicate,
+ binding=proposition.to_dict(),
+ recorded_at=recorded_at,
+ evaluation=evaluation,
+ evidence_payloads=session.evidence.export_base64(evaluation.evidence_refs),
+ attributes={"conflict": conflict.to_dict()},
+ )
+ if evaluation.outcome is MechanismOutcome.PASS:
+ self._conflicts[conflict.conflict_id] = conflict
+ return event
+
+ def resolve_conflict(
+ self,
+ conflict_id: str,
+ selected_value: str,
+ proposition: BoundProposition,
+ *,
+ session: VerificationSession,
+ recorded_at: str,
+ ) -> ConflictResolution:
+ """Adjudicate one value without equating selection with admissibility."""
+ conflict = self._conflicts.get(conflict_id)
+ if conflict is None:
+ raise AssuranceFlowError(f"unknown conflict {conflict_id}")
+ if conflict_id in {item.conflict_id for item in self._resolutions.values()}:
+ raise AssuranceFlowError(f"conflict {conflict_id} is already resolved additively")
+ if selected_value not in conflict.competing_values:
+ raise AssuranceFlowError("resolution must select one retained competing value")
+ if conflict.predicate == "status":
+ if conflict.subject_id in self.graph.artifacts:
+ try:
+ ArtifactStatus(selected_value)
+ except ValueError as exc:
+ raise AssuranceFlowError(
+ "artifact status resolution must select a defined status"
+ ) from exc
+ elif not selected_value:
+ raise AssuranceFlowError(
+ "transformation status resolution must not be empty"
+ )
+ if (
+ proposition.subject_id != conflict.subject_id
+ or proposition.predicate != f"vstd.graph.resolve.{conflict.predicate}"
+ or proposition.expected != selected_value
+ or proposition.parameters.get("conflict_id") != conflict_id
+ ):
+ raise AssuranceFlowError("resolution evidence is not exactly conflict-bound")
+ evaluation = session.evaluate(proposition)
+ if not evaluation.passed:
+ raise AssuranceFlowError(
+ f"conflict remains unresolved: mechanism returned {evaluation.outcome.value}"
+ )
+ resolution = ConflictResolution(
+ "resolution:" + canonical_digest(
+ [conflict_id, selected_value, evaluation.binding_digest]
+ ),
+ conflict_id,
+ selected_value,
+ recorded_at,
+ evaluation,
+ )
+ self._resolutions[resolution.resolution_id] = resolution
+ self._append(
+ kind=AssuranceEventKind.CONFLICT_RESOLUTION,
+ subject_id=conflict.subject_id,
+ source_ids=(conflict_id,),
+ proposition=proposition.predicate,
+ binding=proposition.to_dict(),
+ recorded_at=recorded_at,
+ evaluation=evaluation,
+ evidence_payloads=session.evidence.export_base64(evaluation.evidence_refs),
+ attributes={
+ "conflict_id": conflict_id,
+ "selected_value": selected_value,
+ "resolution_id": resolution.resolution_id,
+ },
+ )
+ return resolution
+
+ def record_trust(
+ self,
+ target_id: str,
+ source_ids: Iterable[str],
+ proposition: BoundProposition,
+ *,
+ transformation_id: str,
+ prerequisite_trust_event_digests: Iterable[str] = (),
+ session: VerificationSession,
+ recorded_at: str,
+ ) -> AssuranceEvent:
+ sources = tuple(sorted(set(source_ids)))
+ if not sources:
+ raise AssuranceFlowError("TRUST requires at least one recorded source")
+ if canonical_digest(self.graph.to_dict()) != self.graph_digest:
+ raise AssuranceFlowError("historical Graph changed after ledger creation")
+ if target_id not in self.graph.artifacts:
+ raise AssuranceFlowError(f"unknown TRUST target {target_id}")
+ transform = self.graph.transformations.get(transformation_id)
+ if transform is None:
+ raise AssuranceFlowError(f"unknown TRUST transformation {transformation_id}")
+ exact_inputs = tuple(sorted({port.artifact_id for port in transform.inputs}))
+ if sources != exact_inputs:
+ raise AssuranceFlowError(
+ "TRUST sources must equal the exact transformation input set"
+ )
+ if target_id not in {port.artifact_id for port in transform.outputs}:
+ raise AssuranceFlowError(
+ "TRUST target must be an output of the bound transformation"
+ )
+ if self.current_transformation_status(transformation_id) != "COMPLETED":
+ raise AssuranceFlowError("incomplete transformation cannot provide TRUST")
+ if self.current_status(target_id) is not ArtifactStatus.VALID or any(
+ self.current_status(source) is not ArtifactStatus.VALID
+ for source in sources
+ ):
+ raise AssuranceFlowError(
+ "inadmissible target or transformation input cannot provide current TRUST"
+ )
+ blocked_subjects = {
+ item.subject_id for item in self.admissibility_blocking_conflicts()
+ }
+ if (
+ target_id in blocked_subjects
+ or transformation_id in blocked_subjects
+ or any(source in blocked_subjects for source in sources)
+ ):
+ raise AssuranceFlowError(
+ "conflict without an admissible current-state consequence blocks clean TRUST"
+ )
+
+ prerequisite_digests = tuple(sorted(set(prerequisite_trust_event_digests)))
+ current_by_digest = {
+ event.digest(): event for event in self.current_trust_events()
+ }
+ required_prerequisite_targets = {
+ source for source in sources if self.graph.incoming_hyperedges(source)
+ }
+ prerequisite_targets: list[str] = []
+ for event_digest in prerequisite_digests:
+ prerequisite = current_by_digest.get(event_digest)
+ if prerequisite is None:
+ raise AssuranceFlowError(
+ "TRUST prerequisite is not a current passing TRUST event"
+ )
+ prerequisite_targets.append(prerequisite.subject_id)
+ if (
+ len(prerequisite_targets) != len(required_prerequisite_targets)
+ or set(prerequisite_targets) != required_prerequisite_targets
+ ):
+ raise AssuranceFlowError(
+ "TRUST requires exactly one current prerequisite for each derived input"
+ )
+
+ expected = {
+ "historical_graph_digest": self.graph_digest,
+ "inputs": list(sources),
+ "output": target_id,
+ "prerequisite_trust_event_digests": list(prerequisite_digests),
+ "transformation_id": transformation_id,
+ }
+ if (
+ proposition.subject_id != target_id
+ or proposition.predicate != "vstd.graph.support"
+ or proposition.expected != expected
+ ):
+ raise AssuranceFlowError("TRUST proposition is not exactly topology-bound")
+ evaluation = session.evaluate(proposition)
+ return self._append(
+ kind=AssuranceEventKind.TRUST,
+ subject_id=target_id,
+ source_ids=sources,
+ proposition=proposition.predicate,
+ binding=proposition.to_dict(),
+ recorded_at=recorded_at,
+ evaluation=evaluation,
+ evidence_payloads=session.evidence.export_base64(evaluation.evidence_refs),
+ attributes={
+ "historical_graph_digest": self.graph_digest,
+ "inputs": list(sources),
+ "output": target_id,
+ "prerequisite_trust_event_digests": list(prerequisite_digests),
+ "transformation_id": transformation_id,
+ },
+ )
+
+ def record_rot(
+ self,
+ artifact_id: str,
+ resulting_status: ArtifactStatus,
+ proposition: BoundProposition,
+ *,
+ session: VerificationSession,
+ recorded_at: str,
+ ) -> AssuranceEvent:
+ if artifact_id not in self.graph.artifacts:
+ raise AssuranceFlowError(f"unknown ROT subject {artifact_id}")
+ current = self.current_status(artifact_id)
+ if DEGRADATION_ORDER.index(resulting_status) <= DEGRADATION_ORDER.index(current):
+ raise AssuranceFlowError(
+ "ROT must strictly degrade current admissibility"
+ )
+ if (
+ proposition.subject_id != artifact_id
+ or proposition.predicate != "vstd.graph.current_status"
+ or proposition.expected != resulting_status.value
+ ):
+ raise AssuranceFlowError("ROT evidence is not exactly status-bound")
+ evaluation = session.evaluate(proposition)
+ return self._append(
+ kind=AssuranceEventKind.ROT,
+ subject_id=artifact_id,
+ source_ids=(),
+ proposition=proposition.predicate,
+ binding=proposition.to_dict(),
+ recorded_at=recorded_at,
+ evaluation=evaluation,
+ evidence_payloads=session.evidence.export_base64(evaluation.evidence_refs),
+ attributes={"resulting_status": resulting_status.value},
+ )
+
+ def record_rust(
+ self,
+ descendant_id: str,
+ deviation: BoundProposition,
+ *,
+ session: VerificationSession,
+ recorded_at: str,
+ ) -> AssuranceEvent:
+ if descendant_id not in self.graph.artifacts:
+ raise AssuranceFlowError(f"unknown RUST origin {descendant_id}")
+ if (
+ deviation.subject_id != descendant_id
+ or deviation.predicate != "vstd.graph.descendant_deviation"
+ or deviation.expected is not True
+ ):
+ raise AssuranceFlowError("RUST origin requires an exact deviation proposition")
+ evaluation = session.evaluate(deviation)
+ ancestors = tuple(sorted(self.graph.ancestors((descendant_id,)) - {descendant_id}))
+ return self._append(
+ kind=AssuranceEventKind.RUST,
+ subject_id=descendant_id,
+ source_ids=ancestors,
+ proposition=deviation.predicate,
+ binding=deviation.to_dict(),
+ recorded_at=recorded_at,
+ evaluation=evaluation,
+ evidence_payloads=session.evidence.export_base64(evaluation.evidence_refs),
+ attributes={
+ "meaning": "diagnostic reachability only",
+ "causal_localization": "NOT_ESTABLISHED",
+ },
+ )
+
+ def rust_concentration(self) -> tuple[StructuralConcentration, ...]:
+ reached_by: dict[str, set[str]] = {}
+ for event in self._events:
+ if event.kind is not AssuranceEventKind.RUST or event.outcome is not MechanismOutcome.PASS:
+ continue
+ for ancestor in event.source_ids:
+ reached_by.setdefault(ancestor, set()).add(event.subject_id)
+ return tuple(
+ StructuralConcentration(ancestor, tuple(sorted(descendants)))
+ for ancestor, descendants in sorted(reached_by.items())
+ )
+
+ def localize_cause(
+ self,
+ ancestor_id: str,
+ descendant_id: str,
+ proposition: BoundProposition,
+ *,
+ rust_event_digest: str,
+ session: VerificationSession,
+ recorded_at: str,
+ ) -> AssuranceEvent:
+ """Bind one ancestor to one exact passing descendant-deviation event."""
+ if ancestor_id not in self.graph.ancestors((descendant_id,)) - {descendant_id}:
+ raise AssuranceFlowError("causal candidate is not a recorded ancestor")
+ rust_event = next(
+ (event for event in self._events if event.digest() == rust_event_digest),
+ None,
+ )
+ if (
+ rust_event is None
+ or rust_event.kind is not AssuranceEventKind.RUST
+ or rust_event.outcome is not MechanismOutcome.PASS
+ or rust_event.subject_id != descendant_id
+ or ancestor_id not in rust_event.source_ids
+ ):
+ raise AssuranceFlowError(
+ "localization requires the exact passing RUST event for this "
+ "descendant and ancestor"
+ )
+ deviation_binding_digest = str(rust_event.attributes["binding_digest"])
+ expected = {
+ "ancestor": ancestor_id,
+ "descendant": descendant_id,
+ "rust_event_digest": rust_event_digest,
+ "deviation_binding_digest": deviation_binding_digest,
+ }
+ if (
+ proposition.subject_id != descendant_id
+ or proposition.predicate != "vstd.graph.causal_localization"
+ or proposition.expected != expected
+ ):
+ raise AssuranceFlowError("causal localization is not exactly relation-bound")
+ evaluation = session.evaluate(proposition)
+ return self._append(
+ kind=AssuranceEventKind.CAUSAL_LOCALIZATION,
+ subject_id=descendant_id,
+ source_ids=(ancestor_id,),
+ proposition=proposition.predicate,
+ binding=proposition.to_dict(),
+ recorded_at=recorded_at,
+ evaluation=evaluation,
+ evidence_payloads=session.evidence.export_base64(evaluation.evidence_refs),
+ attributes={
+ "rust_event_digest": rust_event_digest,
+ "deviation_binding_digest": deviation_binding_digest,
+ },
+ )
+
+ def _event_by_digest(self, event_digest: str) -> Optional[AssuranceEvent]:
+ return next(
+ (event for event in self._events if event.digest() == event_digest),
+ None,
+ )
+
+ def _require_localization(
+ self,
+ ancestor_id: str,
+ descendant_id: str,
+ localization_event_digest: str,
+ ) -> AssuranceEvent:
+ localization = self._event_by_digest(localization_event_digest)
+ if (
+ localization is None
+ or localization.kind is not AssuranceEventKind.CAUSAL_LOCALIZATION
+ or localization.outcome is not MechanismOutcome.PASS
+ or localization.subject_id != descendant_id
+ or localization.source_ids != (ancestor_id,)
+ ):
+ raise AssuranceFlowError(
+ "component requires the exact passing localization event for this "
+ "ancestor and descendant"
+ )
+ return localization
+
+ @staticmethod
+ def _unestablished_guilt(
+ ancestor_id: str,
+ descendant_id: str,
+ localization_event_digest: str,
+ details: str,
+ *,
+ obligation: Optional[ObligationCoordinate] = None,
+ responsibility_component_digest: str = "",
+ applicability_component_digest: str = "",
+ violation_component_digest: str = "",
+ ) -> DiagnosticAttribution:
+ return DiagnosticAttribution(
+ DiagnosticKind.GUILT,
+ ancestor_id,
+ descendant_id,
+ "NOT_ESTABLISHED",
+ localization_event_digest,
+ None,
+ details,
+ obligation,
+ responsibility_component_digest,
+ applicability_component_digest,
+ violation_component_digest,
+ )
+
+ def establish_responsibility(
+ self,
+ ancestor_id: str,
+ descendant_id: str,
+ proposition: BoundProposition,
+ *,
+ localization_event_digest: str,
+ session: VerificationSession,
+ recorded_at: str,
+ ) -> AssuranceEvent:
+ """Evaluate one separately bound material-contribution component."""
+
+ localization = self._require_localization(
+ ancestor_id, descendant_id, localization_event_digest
+ )
+ expected = {
+ "ancestor_id": ancestor_id,
+ "descendant_id": descendant_id,
+ "localization_event_digest": localization_event_digest,
+ "rust_event_digest": str(localization.attributes["rust_event_digest"]),
+ "deviation_binding_digest": str(
+ localization.attributes["deviation_binding_digest"]
+ ),
+ }
+ if (
+ proposition.subject_id != ancestor_id
+ or proposition.predicate != "vstd.graph.responsibility"
+ or proposition.expected != expected
+ ):
+ raise AssuranceFlowError(
+ "responsibility component is not exactly artifact/deviation-bound"
+ )
+ evaluation = session.evaluate(proposition)
+ return self._append(
+ kind=AssuranceEventKind.RESPONSIBILITY_COMPONENT,
+ subject_id=ancestor_id,
+ source_ids=(descendant_id,),
+ proposition=proposition.predicate,
+ binding=proposition.to_dict(),
+ recorded_at=recorded_at,
+ evaluation=evaluation,
+ evidence_payloads=session.evidence.export_base64(evaluation.evidence_refs),
+ attributes=expected,
+ )
+
+ def establish_obligation_applicability(
+ self,
+ artifact_id: str,
+ obligation: ObligationCoordinate,
+ proposition: BoundProposition,
+ *,
+ session: VerificationSession,
+ recorded_at: str,
+ ) -> AssuranceEvent:
+ """Evaluate whether one exact obligation applies to one exact artifact."""
+
+ if artifact_id not in self.graph.artifacts:
+ raise AssuranceFlowError(f"unknown obligation subject {artifact_id}")
+ expected = {
+ "artifact_id": artifact_id,
+ "obligation_coordinate": obligation.to_dict(),
+ }
+ if (
+ proposition.subject_id != artifact_id
+ or proposition.predicate != "vstd.graph.obligation_applicability"
+ or proposition.expected != expected
+ ):
+ raise AssuranceFlowError(
+ "obligation applicability is not exactly artifact/scope-bound"
+ )
+ evaluation = session.evaluate(proposition)
+ return self._append(
+ kind=AssuranceEventKind.OBLIGATION_APPLICABILITY,
+ subject_id=artifact_id,
+ source_ids=(),
+ proposition=proposition.predicate,
+ binding=proposition.to_dict(),
+ recorded_at=recorded_at,
+ evaluation=evaluation,
+ evidence_payloads=session.evidence.export_base64(evaluation.evidence_refs),
+ attributes=expected,
+ )
+
+ def establish_obligation_violation(
+ self,
+ ancestor_id: str,
+ descendant_id: str,
+ obligation: ObligationCoordinate,
+ proposition: BoundProposition,
+ *,
+ localization_event_digest: str,
+ applicability_component_digest: str,
+ session: VerificationSession,
+ recorded_at: str,
+ ) -> AssuranceEvent:
+ """Evaluate violation after exact applicability and deviation localization."""
+
+ localization = self._require_localization(
+ ancestor_id, descendant_id, localization_event_digest
+ )
+ applicability = self._event_by_digest(applicability_component_digest)
+ if (
+ applicability is None
+ or applicability.kind is not AssuranceEventKind.OBLIGATION_APPLICABILITY
+ or applicability.outcome is not MechanismOutcome.PASS
+ or applicability.subject_id != ancestor_id
+ or applicability.source_ids
+ or applicability.attributes.get("obligation_coordinate")
+ != obligation.to_dict()
+ ):
+ raise AssuranceFlowError(
+ "violation requires the exact passing applicability component"
+ )
+ expected = {
+ "artifact_id": ancestor_id,
+ "descendant_id": descendant_id,
+ "localization_event_digest": localization_event_digest,
+ "rust_event_digest": str(localization.attributes["rust_event_digest"]),
+ "deviation_binding_digest": str(
+ localization.attributes["deviation_binding_digest"]
+ ),
+ "obligation_coordinate": obligation.to_dict(),
+ "applicability_binding_digest": str(
+ applicability.attributes["binding_digest"]
+ ),
+ }
+ if (
+ proposition.subject_id != ancestor_id
+ or proposition.predicate != "vstd.graph.obligation_violation"
+ or proposition.expected != expected
+ ):
+ raise AssuranceFlowError(
+ "obligation violation is not exactly obligation/deviation-bound"
+ )
+ evaluation = session.evaluate(proposition)
+ return self._append(
+ kind=AssuranceEventKind.OBLIGATION_VIOLATION,
+ subject_id=ancestor_id,
+ source_ids=(descendant_id,),
+ proposition=proposition.predicate,
+ binding=proposition.to_dict(),
+ recorded_at=recorded_at,
+ evaluation=evaluation,
+ evidence_payloads=session.evidence.export_base64(evaluation.evidence_refs),
+ attributes={
+ **expected,
+ "applicability_component_digest": applicability_component_digest,
+ },
+ )
+
+ def establish_guilt_components(
+ self,
+ ancestor_id: str,
+ descendant_id: str,
+ obligation: ObligationCoordinate,
+ responsibility_proposition: BoundProposition,
+ applicability_proposition: BoundProposition,
+ violation_proposition: BoundProposition,
+ *,
+ localization_event_digest: str,
+ session: VerificationSession,
+ recorded_at: str,
+ ) -> tuple[AssuranceEvent, AssuranceEvent, AssuranceEvent]:
+ """Run one compound mechanism and retain three separately bound results."""
+
+ localization = self._require_localization(
+ ancestor_id, descendant_id, localization_event_digest
+ )
+ responsibility_expected = {
+ "ancestor_id": ancestor_id,
+ "descendant_id": descendant_id,
+ "localization_event_digest": localization_event_digest,
+ "rust_event_digest": str(localization.attributes["rust_event_digest"]),
+ "deviation_binding_digest": str(
+ localization.attributes["deviation_binding_digest"]
+ ),
+ }
+ applicability_expected = {
+ "artifact_id": ancestor_id,
+ "obligation_coordinate": obligation.to_dict(),
+ }
+ violation_expected = {
+ "artifact_id": ancestor_id,
+ "descendant_id": descendant_id,
+ "localization_event_digest": localization_event_digest,
+ "rust_event_digest": str(localization.attributes["rust_event_digest"]),
+ "deviation_binding_digest": str(
+ localization.attributes["deviation_binding_digest"]
+ ),
+ "obligation_coordinate": obligation.to_dict(),
+ "applicability_binding_digest": applicability_proposition.digest(),
+ }
+ required = (
+ (
+ responsibility_proposition,
+ "vstd.graph.responsibility",
+ responsibility_expected,
+ ),
+ (
+ applicability_proposition,
+ "vstd.graph.obligation_applicability",
+ applicability_expected,
+ ),
+ (
+ violation_proposition,
+ "vstd.graph.obligation_violation",
+ violation_expected,
+ ),
+ )
+ if any(
+ proposition.subject_id != ancestor_id
+ or proposition.predicate != predicate
+ or proposition.expected != expected
+ for proposition, predicate, expected in required
+ ):
+ raise AssuranceFlowError(
+ "compound GUILT components are not separately and exactly bound"
+ )
+ group_payload = {
+ "component_bindings": [
+ responsibility_proposition.to_dict(),
+ applicability_proposition.to_dict(),
+ violation_proposition.to_dict(),
+ ],
+ "component_roles": [
+ "RESPONSIBILITY",
+ "OBLIGATION_APPLICABILITY",
+ "OBLIGATION_VIOLATION",
+ ],
+ }
+ compound_group_digest = canonical_digest(group_payload)
+ evaluations = session.evaluate_compound(
+ (
+ responsibility_proposition,
+ applicability_proposition,
+ violation_proposition,
+ )
+ )
+ responsibility = self._append(
+ kind=AssuranceEventKind.RESPONSIBILITY_COMPONENT,
+ subject_id=ancestor_id,
+ source_ids=(descendant_id,),
+ proposition=responsibility_proposition.predicate,
+ binding=responsibility_proposition.to_dict(),
+ recorded_at=recorded_at,
+ evaluation=evaluations[0],
+ evidence_payloads=session.evidence.export_base64(
+ evaluations[0].evidence_refs
+ ),
+ attributes={
+ **responsibility_expected,
+ "compound_group_digest": compound_group_digest,
+ "compound_component_index": 0,
+ },
+ )
+ applicability = self._append(
+ kind=AssuranceEventKind.OBLIGATION_APPLICABILITY,
+ subject_id=ancestor_id,
+ source_ids=(),
+ proposition=applicability_proposition.predicate,
+ binding=applicability_proposition.to_dict(),
+ recorded_at=recorded_at,
+ evaluation=evaluations[1],
+ evidence_payloads=session.evidence.export_base64(
+ evaluations[1].evidence_refs
+ ),
+ attributes={
+ **applicability_expected,
+ "compound_group_digest": compound_group_digest,
+ "compound_component_index": 1,
+ },
+ )
+ violation = self._append(
+ kind=AssuranceEventKind.OBLIGATION_VIOLATION,
+ subject_id=ancestor_id,
+ source_ids=(descendant_id,),
+ proposition=violation_proposition.predicate,
+ binding=violation_proposition.to_dict(),
+ recorded_at=recorded_at,
+ evaluation=evaluations[2],
+ evidence_payloads=session.evidence.export_base64(
+ evaluations[2].evidence_refs
+ ),
+ attributes={
+ **violation_expected,
+ "applicability_component_digest": applicability.digest(),
+ "compound_group_digest": compound_group_digest,
+ "compound_component_index": 2,
+ },
+ )
+ return responsibility, applicability, violation
+
+ def compose_guilt(
+ self,
+ ancestor_id: str,
+ descendant_id: str,
+ obligation: ObligationCoordinate,
+ proposition: BoundProposition,
+ *,
+ localization_event_digest: str,
+ responsibility_component_digest: str,
+ applicability_component_digest: str,
+ violation_component_digest: str,
+ session: VerificationSession,
+ recorded_at: str,
+ ) -> DiagnosticAttribution:
+ """Compose technical GUILT from three exact, separately earned components.
+
+ GUILT is artifact-relative. It does not establish moral character, actor
+ reputation, automatic legal liability, innocence, exoneration, obligation
+ satisfaction, or absence of hidden contributors.
+ """
+
+ component_digests = (
+ responsibility_component_digest,
+ applicability_component_digest,
+ violation_component_digest,
+ )
+ if any(not item for item in component_digests) or len(set(component_digests)) != 3:
+ return self._unestablished_guilt(
+ ancestor_id,
+ descendant_id,
+ localization_event_digest,
+ "GUILT requires three distinct component event digests",
+ obligation=obligation,
+ responsibility_component_digest=responsibility_component_digest,
+ applicability_component_digest=applicability_component_digest,
+ violation_component_digest=violation_component_digest,
+ )
+ try:
+ self._require_localization(
+ ancestor_id, descendant_id, localization_event_digest
+ )
+ except AssuranceFlowError as exc:
+ return self._unestablished_guilt(
+ ancestor_id,
+ descendant_id,
+ localization_event_digest,
+ str(exc),
+ obligation=obligation,
+ responsibility_component_digest=responsibility_component_digest,
+ applicability_component_digest=applicability_component_digest,
+ violation_component_digest=violation_component_digest,
+ )
+
+ responsibility = self._event_by_digest(responsibility_component_digest)
+ responsibility_matches = False
+ if responsibility is not None and responsibility.outcome is MechanismOutcome.PASS:
+ responsibility_matches = (
+ responsibility.subject_id == ancestor_id
+ and responsibility.source_ids == (descendant_id,)
+ and responsibility.attributes.get("localization_event_digest")
+ == localization_event_digest
+ and (
+ responsibility.kind is AssuranceEventKind.RESPONSIBILITY_COMPONENT
+ or (
+ responsibility.kind
+ is AssuranceEventKind.DIAGNOSTIC_ATTRIBUTION
+ and responsibility.attributes.get("diagnostic_kind")
+ == DiagnosticKind.BLAME.value
+ )
+ )
+ )
+ if not responsibility_matches:
+ return self._unestablished_guilt(
+ ancestor_id,
+ descendant_id,
+ localization_event_digest,
+ "responsibility component is missing, non-passing, or coordinate-mismatched",
+ obligation=obligation,
+ responsibility_component_digest=responsibility_component_digest,
+ applicability_component_digest=applicability_component_digest,
+ violation_component_digest=violation_component_digest,
+ )
+
+ applicability = self._event_by_digest(applicability_component_digest)
+ if not (
+ applicability is not None
+ and applicability.kind is AssuranceEventKind.OBLIGATION_APPLICABILITY
+ and applicability.outcome is MechanismOutcome.PASS
+ and applicability.subject_id == ancestor_id
+ and not applicability.source_ids
+ and applicability.attributes.get("obligation_coordinate")
+ == obligation.to_dict()
+ ):
+ return self._unestablished_guilt(
+ ancestor_id,
+ descendant_id,
+ localization_event_digest,
+ "applicability component is missing, non-passing, or coordinate-mismatched",
+ obligation=obligation,
+ responsibility_component_digest=responsibility_component_digest,
+ applicability_component_digest=applicability_component_digest,
+ violation_component_digest=violation_component_digest,
+ )
+
+ violation = self._event_by_digest(violation_component_digest)
+ if not (
+ violation is not None
+ and violation.kind is AssuranceEventKind.OBLIGATION_VIOLATION
+ and violation.outcome is MechanismOutcome.PASS
+ and violation.subject_id == ancestor_id
+ and violation.source_ids == (descendant_id,)
+ and violation.attributes.get("localization_event_digest")
+ == localization_event_digest
+ and violation.attributes.get("obligation_coordinate")
+ == obligation.to_dict()
+ and violation.attributes.get("applicability_component_digest")
+ == applicability_component_digest
+ ):
+ return self._unestablished_guilt(
+ ancestor_id,
+ descendant_id,
+ localization_event_digest,
+ "violation component is missing, non-passing, or coordinate-mismatched",
+ obligation=obligation,
+ responsibility_component_digest=responsibility_component_digest,
+ applicability_component_digest=applicability_component_digest,
+ violation_component_digest=violation_component_digest,
+ )
+
+ expected = {
+ "ancestor_id": ancestor_id,
+ "descendant_id": descendant_id,
+ "localization_event_digest": localization_event_digest,
+ "obligation_coordinate": obligation.to_dict(),
+ "responsibility_component_digest": responsibility_component_digest,
+ "applicability_component_digest": applicability_component_digest,
+ "violation_component_digest": violation_component_digest,
+ }
+ if (
+ proposition.subject_id != ancestor_id
+ or proposition.predicate != "vstd.graph.diagnostic.guilt"
+ or proposition.expected != expected
+ ):
+ raise AssuranceFlowError(
+ "GUILT proposition is not exactly component-composition-bound"
+ )
+ evaluation = session.evaluate(proposition)
+ event = self._append(
+ kind=AssuranceEventKind.DIAGNOSTIC_ATTRIBUTION,
+ subject_id=ancestor_id,
+ source_ids=(descendant_id,),
+ proposition=proposition.predicate,
+ binding=proposition.to_dict(),
+ recorded_at=recorded_at,
+ evaluation=evaluation,
+ evidence_payloads=session.evidence.export_base64(evaluation.evidence_refs),
+ attributes={
+ "diagnostic_kind": DiagnosticKind.GUILT.value,
+ "localization_event_digest": localization_event_digest,
+ "obligation_coordinate": obligation.to_dict(),
+ "responsibility_component_digest": responsibility_component_digest,
+ "applicability_component_digest": applicability_component_digest,
+ "violation_component_digest": violation_component_digest,
+ },
+ )
+ return DiagnosticAttribution(
+ DiagnosticKind.GUILT,
+ ancestor_id,
+ descendant_id,
+ "ESTABLISHED" if evaluation.passed else "NOT_ESTABLISHED",
+ localization_event_digest,
+ evaluation,
+ event.details,
+ obligation,
+ responsibility_component_digest,
+ applicability_component_digest,
+ violation_component_digest,
+ )
+
+ def diagnose(
+ self,
+ kind: DiagnosticKind,
+ ancestor_id: str,
+ descendant_id: str,
+ proposition: Optional[BoundProposition],
+ *,
+ session: VerificationSession,
+ recorded_at: str,
+ ) -> DiagnosticAttribution:
+ """Compute BLAME, or fail closed for legacy opaque GUILT calls.
+
+ BLAME means only that the named artifact-relative responsibility
+ proposition passed. GUILT must use :meth:`compose_guilt`; one opaque
+ proposition cannot substitute for separately bound responsibility,
+ applicability, and violation evaluations.
+ """
+ requested_localization_digest = ""
+ if proposition is not None and isinstance(proposition.expected, Mapping):
+ requested_localization_digest = str(
+ proposition.expected.get("localization_event_digest", "")
+ )
+ localization = next(
+ (
+ event
+ for event in reversed(self._events)
+ if event.kind is AssuranceEventKind.CAUSAL_LOCALIZATION
+ and event.subject_id == descendant_id
+ and event.source_ids == (ancestor_id,)
+ and event.outcome is MechanismOutcome.PASS
+ and (
+ not requested_localization_digest
+ or event.digest() == requested_localization_digest
+ )
+ ),
+ None,
+ )
+ if localization is None:
+ return DiagnosticAttribution(
+ kind,
+ ancestor_id,
+ descendant_id,
+ "NOT_ESTABLISHED",
+ "",
+ None,
+ "RUST reachability does not establish causal localization",
+ )
+ if proposition is None:
+ return DiagnosticAttribution(
+ kind,
+ ancestor_id,
+ descendant_id,
+ "NOT_ESTABLISHED",
+ localization.digest(),
+ None,
+ "diagnostic attribution requires a separate exact mechanism",
+ )
+ if kind is DiagnosticKind.GUILT:
+ return self._unestablished_guilt(
+ ancestor_id,
+ descendant_id,
+ localization.digest(),
+ "opaque GUILT evaluation is insufficient; separately establish "
+ "responsibility, obligation applicability, and obligation violation",
+ )
+ expected = {
+ "ancestor": ancestor_id,
+ "descendant": descendant_id,
+ "localization_event_digest": localization.digest(),
+ }
+ if (
+ proposition.subject_id != ancestor_id
+ or proposition.predicate != f"vstd.graph.diagnostic.{kind.value.lower()}"
+ or proposition.expected != expected
+ ):
+ raise AssuranceFlowError("diagnostic proposition is not exactly relation-bound")
+ evaluation = session.evaluate(proposition)
+ event = self._append(
+ kind=AssuranceEventKind.DIAGNOSTIC_ATTRIBUTION,
+ subject_id=ancestor_id,
+ source_ids=(descendant_id,),
+ proposition=proposition.predicate,
+ binding=proposition.to_dict(),
+ recorded_at=recorded_at,
+ evaluation=evaluation,
+ evidence_payloads=session.evidence.export_base64(evaluation.evidence_refs),
+ attributes={
+ "diagnostic_kind": kind.value,
+ "localization_event_digest": localization.digest(),
+ },
+ )
+ return DiagnosticAttribution(
+ kind,
+ ancestor_id,
+ descendant_id,
+ "ESTABLISHED" if evaluation.passed else "NOT_ESTABLISHED",
+ localization.digest(),
+ evaluation,
+ event.details,
+ )
+
+ def verify_hash_chain(self) -> bool:
+ previous = ""
+ for sequence, event in enumerate(self._events):
+ if event.sequence != sequence or event.previous_event_digest != previous:
+ return False
+ previous = event.digest()
+ return True
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "schema_version": self.FORMAT,
+ "historical_graph_digest": self.graph_digest,
+ "historical_graph": self.graph.to_dict(),
+ "events": [event.to_dict() for event in self._events],
+ "conflict_resolutions": [item.to_dict() for item in self._resolutions.values()],
+ "current_view_digest": canonical_digest(self.materialize_current_graph().to_dict()),
+ }
+
+
+def recheck_assurance_log(
+ payload: Mapping[str, Any],
+ *,
+ mechanisms: Iterable[VerificationMechanism],
+) -> AssuranceLedger:
+ """Rebuild and replay a portable assurance log from its embedded bytes."""
+ if payload.get("schema_version") != AssuranceLedger.FORMAT:
+ raise AssuranceFlowError("not a VSTD-Graph assurance log")
+ graph_data = payload.get("historical_graph")
+ events_data = payload.get("events")
+ if not isinstance(graph_data, Mapping) or not isinstance(events_data, list):
+ raise AssuranceFlowError("assurance log is missing its Graph or events")
+ graph = ProvenanceHypergraph.from_dict(graph_data)
+ if canonical_digest(graph.to_dict()) != payload.get("historical_graph_digest"):
+ raise AssuranceFlowError("historical Graph digest does not match embedded bytes")
+ ledger = AssuranceLedger(graph)
+ store = EvidenceStore()
+ for event_data in events_data:
+ if not isinstance(event_data, Mapping):
+ raise AssuranceFlowError("assurance event is not an object")
+ embedded = event_data.get("evidence_payloads")
+ if not isinstance(embedded, Mapping):
+ raise AssuranceFlowError("assurance event has no embedded evidence")
+ store.import_base64(
+ {str(reference): str(encoded) for reference, encoded in embedded.items()}
+ )
+ session = VerificationSession(store)
+ builtin = ChallengeProjectionMechanism()
+ session.register(builtin)
+ for mechanism in mechanisms:
+ if mechanism.mechanism_id == builtin.mechanism_id:
+ raise AssuranceFlowError(
+ "the built-in challenge projection mechanism cannot be replaced"
+ )
+ session.register(mechanism)
+
+ replayed_compound_positions: set[int] = set()
+ for event_position, expected in enumerate(events_data):
+ if event_position in replayed_compound_positions:
+ continue
+ compound_replayed = False
+ try:
+ kind = AssuranceEventKind(str(expected["kind"]))
+ subject_id = str(expected["subject_id"])
+ source_ids = tuple(str(item) for item in expected["source_ids"])
+ binding_data = expected["binding"]
+ if not isinstance(binding_data, Mapping):
+ raise TypeError("event binding is not an object")
+ proposition = BoundProposition.from_dict(binding_data)
+ recorded_at = str(expected["recorded_at"])
+ attributes = expected.get("attributes", {})
+ if not isinstance(attributes, Mapping):
+ raise TypeError("event attributes are not an object")
+
+ if kind is AssuranceEventKind.TRUST:
+ event = ledger.record_trust(
+ subject_id,
+ source_ids,
+ proposition,
+ transformation_id=str(attributes["transformation_id"]),
+ prerequisite_trust_event_digests=tuple(
+ str(item)
+ for item in attributes[
+ "prerequisite_trust_event_digests"
+ ]
+ ),
+ session=session,
+ recorded_at=recorded_at,
+ )
+ elif kind is AssuranceEventKind.ROT:
+ event = ledger.record_rot(
+ subject_id,
+ ArtifactStatus(str(attributes["resulting_status"])),
+ proposition,
+ session=session,
+ recorded_at=recorded_at,
+ )
+ elif kind is AssuranceEventKind.RUST:
+ event = ledger.record_rust(
+ subject_id,
+ proposition,
+ session=session,
+ recorded_at=recorded_at,
+ )
+ elif kind is AssuranceEventKind.STATUS_PROJECTION:
+ event = ledger.record_status_projection(
+ subject_id,
+ proposition,
+ session=session,
+ recorded_at=recorded_at,
+ )
+ elif kind is AssuranceEventKind.CONFLICT_DECLARATION:
+ conflict_data = attributes["conflict"]
+ if not isinstance(conflict_data, Mapping):
+ raise TypeError("conflict declaration is not an object")
+ event = ledger.record_conflict(
+ ConflictRecord(
+ str(conflict_data["conflict_id"]),
+ str(conflict_data["subject_id"]),
+ str(conflict_data["predicate"]),
+ tuple(str(item) for item in conflict_data["competing_values"]),
+ tuple(str(item) for item in conflict_data["evidence_refs"]),
+ ),
+ proposition,
+ session=session,
+ recorded_at=recorded_at,
+ )
+ elif kind is AssuranceEventKind.CONFLICT_RESOLUTION:
+ ledger.resolve_conflict(
+ str(attributes["conflict_id"]),
+ str(attributes["selected_value"]),
+ proposition,
+ session=session,
+ recorded_at=recorded_at,
+ )
+ event = ledger.events()[-1]
+ elif kind is AssuranceEventKind.CAUSAL_LOCALIZATION:
+ if len(source_ids) != 1:
+ raise AssuranceFlowError(
+ "causal localization must name exactly one ancestor"
+ )
+ event = ledger.localize_cause(
+ source_ids[0],
+ subject_id,
+ proposition,
+ rust_event_digest=str(attributes["rust_event_digest"]),
+ session=session,
+ recorded_at=recorded_at,
+ )
+ elif kind is AssuranceEventKind.RESPONSIBILITY_COMPONENT:
+ if len(source_ids) != 1:
+ raise AssuranceFlowError(
+ "responsibility component must name exactly one descendant"
+ )
+ if "compound_group_digest" in attributes:
+ if event_position + 2 >= len(events_data):
+ raise AssuranceFlowError(
+ "compound component group is incomplete"
+ )
+ applicability_expected = events_data[event_position + 1]
+ violation_expected = events_data[event_position + 2]
+ if not isinstance(applicability_expected, Mapping) or not isinstance(
+ violation_expected, Mapping
+ ):
+ raise TypeError("compound component event is not an object")
+ applicability_attributes = applicability_expected.get(
+ "attributes", {}
+ )
+ violation_attributes = violation_expected.get("attributes", {})
+ if not isinstance(applicability_attributes, Mapping) or not isinstance(
+ violation_attributes, Mapping
+ ):
+ raise TypeError("compound component attributes are not objects")
+ compound_group_digest = str(attributes["compound_group_digest"])
+ if (
+ int(attributes.get("compound_component_index", -1)) != 0
+ or applicability_expected.get("kind")
+ != AssuranceEventKind.OBLIGATION_APPLICABILITY.value
+ or violation_expected.get("kind")
+ != AssuranceEventKind.OBLIGATION_VIOLATION.value
+ or applicability_attributes.get("compound_group_digest")
+ != compound_group_digest
+ or violation_attributes.get("compound_group_digest")
+ != compound_group_digest
+ or int(
+ applicability_attributes.get(
+ "compound_component_index", -1
+ )
+ )
+ != 1
+ or int(
+ violation_attributes.get("compound_component_index", -1)
+ )
+ != 2
+ or applicability_expected.get("recorded_at") != recorded_at
+ or violation_expected.get("recorded_at") != recorded_at
+ ):
+ raise AssuranceFlowError(
+ "compound component group order or coordinate is invalid"
+ )
+ applicability_binding = applicability_expected.get("binding")
+ violation_binding = violation_expected.get("binding")
+ obligation_data = applicability_attributes.get(
+ "obligation_coordinate"
+ )
+ if (
+ not isinstance(applicability_binding, Mapping)
+ or not isinstance(violation_binding, Mapping)
+ or not isinstance(obligation_data, Mapping)
+ ):
+ raise TypeError("compound component binding is not an object")
+ compound_events = ledger.establish_guilt_components(
+ subject_id,
+ source_ids[0],
+ ObligationCoordinate.from_dict(obligation_data),
+ proposition,
+ BoundProposition.from_dict(applicability_binding),
+ BoundProposition.from_dict(violation_binding),
+ localization_event_digest=str(
+ attributes["localization_event_digest"]
+ ),
+ session=session,
+ recorded_at=recorded_at,
+ )
+ expected_group = (
+ expected,
+ applicability_expected,
+ violation_expected,
+ )
+ for recomputed, recorded in zip(
+ compound_events, expected_group
+ ):
+ if recomputed.to_dict() != dict(recorded):
+ raise AssuranceFlowError(
+ "recomputed compound component event does not match the log"
+ )
+ replayed_compound_positions.update(
+ (event_position + 1, event_position + 2)
+ )
+ event = compound_events[0]
+ compound_replayed = True
+ else:
+ event = ledger.establish_responsibility(
+ subject_id,
+ source_ids[0],
+ proposition,
+ localization_event_digest=str(
+ attributes["localization_event_digest"]
+ ),
+ session=session,
+ recorded_at=recorded_at,
+ )
+ elif kind is AssuranceEventKind.OBLIGATION_APPLICABILITY:
+ if source_ids:
+ raise AssuranceFlowError(
+ "obligation applicability cannot name a neighboring source"
+ )
+ obligation_data = attributes["obligation_coordinate"]
+ if not isinstance(obligation_data, Mapping):
+ raise TypeError("obligation coordinate is not an object")
+ event = ledger.establish_obligation_applicability(
+ subject_id,
+ ObligationCoordinate.from_dict(obligation_data),
+ proposition,
+ session=session,
+ recorded_at=recorded_at,
+ )
+ elif kind is AssuranceEventKind.OBLIGATION_VIOLATION:
+ if len(source_ids) != 1:
+ raise AssuranceFlowError(
+ "obligation violation must name exactly one descendant"
+ )
+ obligation_data = attributes["obligation_coordinate"]
+ if not isinstance(obligation_data, Mapping):
+ raise TypeError("obligation coordinate is not an object")
+ event = ledger.establish_obligation_violation(
+ subject_id,
+ source_ids[0],
+ ObligationCoordinate.from_dict(obligation_data),
+ proposition,
+ localization_event_digest=str(
+ attributes["localization_event_digest"]
+ ),
+ applicability_component_digest=str(
+ attributes["applicability_component_digest"]
+ ),
+ session=session,
+ recorded_at=recorded_at,
+ )
+ elif kind is AssuranceEventKind.DIAGNOSTIC_ATTRIBUTION:
+ if len(source_ids) != 1:
+ raise AssuranceFlowError(
+ "diagnostic attribution must name exactly one descendant"
+ )
+ diagnostic_kind = DiagnosticKind(str(attributes["diagnostic_kind"]))
+ if diagnostic_kind is DiagnosticKind.GUILT:
+ obligation_data = attributes["obligation_coordinate"]
+ if not isinstance(obligation_data, Mapping):
+ raise TypeError("obligation coordinate is not an object")
+ result = ledger.compose_guilt(
+ subject_id,
+ source_ids[0],
+ ObligationCoordinate.from_dict(obligation_data),
+ proposition,
+ localization_event_digest=str(
+ attributes["localization_event_digest"]
+ ),
+ responsibility_component_digest=str(
+ attributes["responsibility_component_digest"]
+ ),
+ applicability_component_digest=str(
+ attributes["applicability_component_digest"]
+ ),
+ violation_component_digest=str(
+ attributes["violation_component_digest"]
+ ),
+ session=session,
+ recorded_at=recorded_at,
+ )
+ if result.evaluation is None:
+ raise AssuranceFlowError(
+ "recorded GUILT event cannot satisfy component composition"
+ )
+ else:
+ ledger.diagnose(
+ diagnostic_kind,
+ subject_id,
+ source_ids[0],
+ proposition,
+ session=session,
+ recorded_at=recorded_at,
+ )
+ event = ledger.events()[-1]
+ else: # pragma: no cover - exhaustive enum guard
+ raise AssuranceFlowError(f"unsupported assurance event kind {kind.value}")
+ except (KeyError, TypeError, ValueError) as exc:
+ raise AssuranceFlowError(f"cannot replay assurance event: {exc}") from exc
+ if compound_replayed:
+ continue
+ if event.to_dict() != dict(expected):
+ raise AssuranceFlowError(
+ f"recomputed assurance event {event.sequence} does not match the log"
+ )
+
+ if ledger.to_dict() != dict(payload):
+ raise AssuranceFlowError(
+ "recomputed assurance state does not match the serialized log"
+ )
+ return ledger
+
+
+__all__ = [
+ "AssuranceEvent",
+ "AssuranceEventKind",
+ "AssuranceFlowError",
+ "AssuranceLedger",
+ "ChallengeProjectionMechanism",
+ "ConflictResolution",
+ "DiagnosticAttribution",
+ "DiagnosticKind",
+ "ObligationCoordinate",
+ "StructuralConcentration",
+ "recheck_assurance_log",
+]
diff --git a/src/verifier/data/graph_level.py b/src/verifier/data/graph_level.py
index bb523d3..dd9e2e1 100644
--- a/src/verifier/data/graph_level.py
+++ b/src/verifier/data/graph_level.py
@@ -1,19 +1,22 @@
-"""``graph_level`` -- how far up the VSTD-Graph ladder a collection actually got.
+"""Terminology: application programming interface (API); conjunctive normal form (CNF); grounded decision certificate (GDC);
+Boolean satisfiability problem (SAT); unsatisfiable (UNSAT); Verifier Standard (VSTD).
+
+``graph_level`` -- compatibility API for the candidate Graph profile satisfied by supplied collection ratings.
VSTD is verification *mechanics* over one object. VSTD-Graph is verification
-*dynamics* over a collection. The axes remain distinct: a collection holds at
-Graph level ``N`` only when four separately checked conditions over the supplied
-ratings and graph records hold at once.
+*dynamics* over a collection. The axes remain distinct. This module computes a
+candidate Graph profile from caller-supplied ratings; that computation is not
+conformance unless a separate profile validates and binds those ratings.
-1. **Membership floor** -- every member object is at object level >= N.
+1. **Membership floor** -- every member object has an object-profile rating >= N.
2. **Provenance closure** -- every ancestor reachable from any member is also
>= N. A plain minimum-over-members misses this, which is the whole reason a
corpus of well-rated repositories can still be badly rated as a corpus.
3. **Status admissibility** -- no artifact in the closure is ``REVOKED``,
``CHALLENGED``, ``STALE`` or ``UNKNOWN``. Fail-closed, per the ``UNKNOWN``
- principle the data layer already applies elsewhere.
-4. **Edge evidence** -- the transformation hyperedges themselves carry level-N
- evidence. A graph is only as verified as its edges, and this is the condition
+ principle the data package already applies elsewhere.
+4. **Edge evidence** -- the transformation hyperedges themselves carry profile-N
+ ratings. A graph is only as verified as its edges, and this is the condition
that makes the axis dynamics rather than aggregation.
Then, exactly as on the object axis::
@@ -21,12 +24,12 @@
graph_level(C) = max { N : CNF_N(C) is satisfiable }
computed by iterated SAT descending 5 -> 1, and **the UNSAT certificate at N+1
-is the explanation of why the collection cannot rate higher**. That certificate
-is a VSTD4-GDC-1 refutation. That certificate is evidence for the graph-level
-ceiling only. It does not supply, imply, upgrade, or repair evidence for any
-object or graph layer.
+is the explanation of why those supplied ratings do not support a higher candidate**.
+That certificate is a VSTD4-GDC-1 refutation of the encoded candidate only. It is not
+evidence that any object or Graph profile was satisfied, and does not supply, imply,
+upgrade, or repair evidence for one.
-Three independent opinions must agree before this module reports a level: the
+Three separately implemented checks must agree before this module reports a candidate: the
certified Horn encoding, :class:`MinimalIndependentDPLL`, and a direct Python
evaluation of the four conditions. Divergence raises rather than silently
preferring one, because an encoding bug is precisely the failure that makes two
@@ -57,8 +60,18 @@
Grounding,
VariableGrounding,
Verdict,
+ canonical_digest,
)
from verifier.core.checker import MinimalIndependentDPLL
+from verifier.core.evidence import (
+ BoundProposition,
+ EvidenceStore,
+ EvaluatedProposition,
+ MechanismOutcome,
+ VerificationMechanism,
+ VerificationSession,
+)
+from verifier.core.depth import claim_binding_from_dict
from verifier.core.kernel import check as kernel_check
from verifier.core.refutation import build_horn_certificate
from verifier.data.models import ArtifactStatus, ProvenanceHypergraph
@@ -68,23 +81,42 @@
INADMISSIBLE_STATUSES = frozenset(
{
- ArtifactStatus.REVOKED,
- ArtifactStatus.CHALLENGED,
- ArtifactStatus.STALE,
- ArtifactStatus.UNKNOWN,
+ ArtifactStatus.REVOKED.value,
+ ArtifactStatus.CHALLENGED.value,
+ ArtifactStatus.STALE.value,
+ ArtifactStatus.UNKNOWN.value,
+ "CONFLICTED",
}
)
-"""Statuses that disqualify an artifact from any graph level.
+"""Statuses that disqualify an artifact from any candidate Graph profile.
``SUPERSEDED`` is deliberately absent: a superseded artifact was replaced going
forward, but its historical role in a lineage is unchanged and re-rating the
-past every time something is superseded would make levels unstable for reasons
+past every time something is superseded would make candidate profiles unstable for reasons
having nothing to do with evidence. A caller wanting the stricter reading has
:meth:`~verifier.data.policy.ProvenancePolicyVerifier.verify_all_ancestors_valid`,
which admits ``VALID`` and nothing else.
"""
+def graph_collection_binding_digest(
+ graph: ProvenanceHypergraph,
+ *,
+ collection_id: str,
+ members: Sequence[str],
+ binding: ClaimBinding,
+) -> str:
+ """Bind ratings to one Graph, member set, collection, and claim coordinate."""
+ return canonical_digest(
+ {
+ "collection_id": collection_id,
+ "members": sorted(set(members)),
+ "historical_graph_digest": canonical_digest(graph.to_dict()),
+ "claim_binding_digest": binding.digest(),
+ }
+ )
+
+
class GraphEncodingError(RuntimeError):
"""The encoding, the solver and the direct computation do not all agree.
@@ -108,7 +140,7 @@ def __init__(
# --------------------------------------------------------------------------
-# Obligations -- what a level actually asks of a collection
+# Obligations -- what a candidate Graph profile asks of a collection
# --------------------------------------------------------------------------
@@ -131,10 +163,10 @@ class ObligationKind(str, Enum):
@dataclass(frozen=True)
class Obligation:
- """One thing a level requires, and what the graph actually says about it.
+ """One thing a candidate Graph profile requires and what the graph records.
- ``observed`` is level-independent -- it is the ground fact. Whether the
- obligation is *met* is a question asked of that fact once per level, which
+ ``observed`` is profile-independent -- it is the ground fact. Whether the
+ obligation is *met* is a question asked of that fact once per profile, which
is why the variable numbering below is stable across all five encodings and
only the unit clauses move.
"""
@@ -143,7 +175,7 @@ class Obligation:
subject: str
observed: str
level: int = 0
- """The rated level behind ``observed``; unused for status obligations."""
+ """Compatibility field carrying the profile rating; unused for status obligations."""
@property
def predicate(self) -> str:
@@ -151,15 +183,15 @@ def predicate(self) -> str:
def met_at(self, level: int) -> bool:
if self.kind is ObligationKind.STATUS_ADMISSIBILITY:
- return self.observed not in {status.value for status in INADMISSIBLE_STATUSES}
+ return self.observed not in INADMISSIBLE_STATUSES
return self.level >= level
def describe(self, level: int) -> str:
if self.kind is ObligationKind.STATUS_ADMISSIBILITY:
return f"{self.kind.value}: {self.subject} is {self.observed}"
return (
- f"{self.kind.value}: {self.subject} is rated {self.level}, "
- f"which is below {level}"
+ f"{self.kind.value}: {self.subject} has profile rating {self.level}, "
+ f"below required profile {level}"
)
def to_dict(self) -> dict[str, Any]:
@@ -174,12 +206,12 @@ def to_dict(self) -> dict[str, Any]:
@dataclass(frozen=True)
class GraphCollection:
- """A collection under test, with the ratings its level will be computed from.
+ """A collection under test, with ratings for its candidate Graph profile.
- ``object_levels`` and ``edge_levels`` are read as ratings someone else
- established. An artifact or edge with no entry is rated ``0``: unrated is
- not a passing grade, and reading it as one is how a collection of unknowns
- becomes a level-5 corpus.
+ ``object_levels`` and ``edge_levels`` retain compatibility field names and are
+ read as profile ratings someone else established. An artifact or edge with
+ no entry is rated ``0``: unrated is not passing, and reading it as one is how
+ a collection of unknowns becomes a candidate Graph-5 collection.
"""
collection_id: str
@@ -213,7 +245,10 @@ def obligations(graph: ProvenanceHypergraph, collection: GraphCollection) -> tup
)
for artifact_id in sorted(closure):
node = graph.artifacts.get(artifact_id)
- status = ArtifactStatus.UNKNOWN.value if node is None else node.status.value
+ if graph.has_conflict(artifact_id):
+ status = "CONFLICTED"
+ else:
+ status = ArtifactStatus.UNKNOWN.value if node is None else node.status.value
found.append(Obligation(ObligationKind.STATUS_ADMISSIBILITY, artifact_id, status))
edges = sorted(
@@ -224,7 +259,9 @@ def obligations(graph: ProvenanceHypergraph, collection: GraphCollection) -> tup
}
)
for transformation_id in edges:
- level = collection.edge_level(transformation_id)
+ level = 0 if graph.has_conflict(transformation_id) else collection.edge_level(
+ transformation_id
+ )
found.append(
Obligation(ObligationKind.EDGE_EVIDENCE, transformation_id, str(level), level)
)
@@ -268,9 +305,9 @@ def encode(
) -> tuple[tuple[tuple[int, ...], ...], Grounding]:
"""CNF_N, together with the grounding that says what its variables mean.
- Variable 1 is the collection holding at ``level``; variable ``1 + i`` is
- obligation ``i``. The numbering does not move between levels -- only the
- unit clauses do -- so two certificates for adjacent levels are directly
+ Variable 1 is the collection satisfying the profile number stored in ``level``;
+ variable ``1 + i`` is obligation ``i``. The numbering does not move between
+ profiles -- only the unit clauses do -- so two certificates for adjacent profiles are directly
comparable rather than being two unrelated formulas that happen to share a
subject.
"""
@@ -333,7 +370,7 @@ def certify_graph_cnf(
verdict = kernel_check(certificate, binding=binding)
if not verdict.accepted:
raise GraphEncodingError(
- f"{collection_id} at level {level}: the kernel refused this "
+ f"{collection_id} at candidate Graph profile {level}: the kernel refused this "
f"collection's own certificate: {verdict.details}",
certificate=certificate,
)
@@ -346,8 +383,8 @@ def certify_graph_cnf(
satisfiable, _model = solver.solve()
if encoded != satisfiable:
raise GraphEncodingError(
- f"{collection_id} at level {level}: the certified encoding says "
- f"{encoded} but the independent solver said {satisfiable}",
+ f"{collection_id} at candidate Graph profile {level}: the certified encoding says "
+ f"{encoded} but the separately implemented solver said {satisfiable}",
certificate=certificate,
cnf_satisfiable=satisfiable,
direct_result=holds_at(items, level),
@@ -356,7 +393,7 @@ def certify_graph_cnf(
direct = holds_at(items, level)
if encoded != direct:
raise GraphEncodingError(
- f"{collection_id} at level {level}: CNF encoding and direct "
+ f"{collection_id} at candidate Graph profile {level}: CNF encoding and direct "
f"computation disagree -- encoding says {encoded}, direct "
f"computation says {direct}. One of them is wrong and the "
"certificate attached shows what the encoding actually proves.",
@@ -379,17 +416,17 @@ def certify_graph_cnf(
# --------------------------------------------------------------------------
-# The computed level
+# The computed candidate Graph profile
# --------------------------------------------------------------------------
@dataclass(frozen=True)
class GraphLevelResult:
- """A computed level, with the evidence for both halves of the answer.
+ """A candidate Graph profile computed from declared ratings, with its SAT evidence.
- ``witness`` certifies the level reached. ``refutation`` certifies why the
- next one was not, and ``blocking_obligations`` names what stopped it. A
- level reported without a refutation at anything below
+ ``witness`` certifies the profile formula satisfied. ``refutation`` certifies
+ why the next one was not, and ``blocking_obligations`` names what stopped it.
+ A candidate reported without a refutation at anything below
:data:`GRAPH_MAX_LEVEL` would be a declaration, which is the thing this
module exists to avoid.
"""
@@ -399,17 +436,23 @@ class GraphLevelResult:
witness: Optional[DecisionCertificate]
refutation: Optional[DecisionCertificate]
blocking_obligations: tuple[Obligation, ...]
+ rating_basis: str = field(default="CALLER_SUPPLIED", init=False)
+ conformance_status: str = field(default="NOT_ESTABLISHED", init=False)
@property
def explanation(self) -> str:
if self.level >= GRAPH_MAX_LEVEL:
- return f"{self.collection_id} holds at graph level {GRAPH_MAX_LEVEL}."
+ return (
+ f"{self.collection_id} computes to candidate Graph profile "
+ f"{GRAPH_MAX_LEVEL} from caller-supplied ratings; conformance is not established."
+ )
blocked = "; ".join(
item.describe(self.level + 1) for item in self.blocking_obligations
)
return (
- f"{self.collection_id} holds at graph level {self.level}. "
- f"Level {self.level + 1} is refuted by: {blocked or 'no obligation'}."
+ f"{self.collection_id} computes to candidate Graph profile {self.level} "
+ "from caller-supplied ratings; conformance is not established. "
+ f"Graph profile {self.level + 1} is refuted by: {blocked or 'no obligation'}."
)
def to_dict(self) -> dict[str, Any]:
@@ -417,6 +460,8 @@ def to_dict(self) -> dict[str, Any]:
"collection_id": self.collection_id,
"level": self.level,
"max_level": GRAPH_MAX_LEVEL,
+ "rating_basis": self.rating_basis,
+ "conformance_status": self.conformance_status,
"blocking_obligations": [item.to_dict() for item in self.blocking_obligations],
"witness_digest": None if self.witness is None else self.witness.digest(),
"refutation_digest": (
@@ -426,25 +471,84 @@ def to_dict(self) -> dict[str, Any]:
}
+@dataclass(frozen=True)
+class EvidenceBoundGraphLevelResult:
+ """Graph profile whose object and edge ratings were rerun and bound."""
+
+ candidate: GraphLevelResult
+ object_evaluations: tuple[tuple[str, EvaluatedProposition], ...]
+ edge_evaluations: tuple[tuple[str, EvaluatedProposition], ...]
+ binding_errors: tuple[str, ...]
+ kernel_outcome: str
+
+ @property
+ def level(self) -> int:
+ return self.candidate.level
+
+ @property
+ def conformance_status(self) -> str:
+ if (
+ self.level < GRAPH_MIN_LEVEL
+ or self.binding_errors
+ or self.kernel_outcome != "ACCEPTED"
+ ):
+ return "NOT_ESTABLISHED"
+ evaluations = self.object_evaluations + self.edge_evaluations
+ if not evaluations or any(not result.passed for _, result in evaluations):
+ return "NOT_ESTABLISHED"
+ return "ESTABLISHED"
+
+ @property
+ def rating_basis(self) -> str:
+ return "MECHANISM_EVALUATED"
+
+ def to_dict(self) -> dict[str, Any]:
+ payload = self.candidate.to_dict()
+ payload.update(
+ {
+ "rating_basis": self.rating_basis,
+ "conformance_status": self.conformance_status,
+ "object_evaluations": {
+ subject: result.to_dict()
+ for subject, result in self.object_evaluations
+ },
+ "edge_evaluations": {
+ subject: result.to_dict()
+ for subject, result in self.edge_evaluations
+ },
+ "binding_errors": list(self.binding_errors),
+ "kernel_outcome": self.kernel_outcome,
+ }
+ )
+ return payload
+
+
def graph_level(
graph: ProvenanceHypergraph,
collection: GraphCollection,
*,
binding: ClaimBinding,
) -> GraphLevelResult:
- """Compute the graph level of ``collection``, with the proof of its ceiling.
+ """Compute the candidate Graph profile, retaining the compatibility API name.
- Descends from :data:`GRAPH_MAX_LEVEL`, so the first satisfiable level found
- is the answer. The conditions are monotone in the level by construction --
+ Descends from :data:`GRAPH_MAX_LEVEL`, so the first satisfiable profile formula
+ is the answer. The conditions are monotone in the profile number by construction --
an obligation met at ``N`` is met at every ``N' <= N`` -- so descending
- means a fully-conformant collection costs one solve rather than five.
+ means a collection meeting its supplied ratings costs one solve rather than five.
"""
if not collection.members:
raise GraphEncodingError(
f"{collection.collection_id} has no members, so every obligation is "
- "vacuously met and the encoding would hand out level "
+ "vacuously met and the encoding would hand out candidate Graph profile "
f"{GRAPH_MAX_LEVEL} for a collection nobody can refute. An empty "
- "collection has no level."
+ "collection satisfies no Graph profile."
+ )
+
+ closure = graph.ancestors(collection.members)
+ if not graph.verify_acyclicity(closure):
+ raise GraphEncodingError(
+ f"{collection.collection_id} has cyclic recorded ancestry, so recursive "
+ "reachability cannot establish a candidate Graph profile."
)
items = obligations(graph, collection)
@@ -477,3 +581,241 @@ def graph_level(
binding=binding,
)
return GraphLevelResult(collection.collection_id, 0, None, refutation, blocked)
+
+
+def establish_graph_level(
+ graph: ProvenanceHypergraph,
+ *,
+ collection_id: str,
+ members: Sequence[str],
+ object_evidence: Mapping[str, BoundProposition],
+ edge_evidence: Mapping[str, BoundProposition],
+ session: VerificationSession,
+ binding: ClaimBinding,
+) -> EvidenceBoundGraphLevelResult:
+ """Rerun rating mechanisms before computing a conforming Graph profile.
+
+ Each reachable artifact must bind ``vstd.object_profile`` and each reachable
+ transformation must bind ``vstd.graph_edge_profile`` to an integer in
+ ``1..5`` under ``parameters['collection_id']``. Missing, neighboring,
+ duplicate, failed, or uncertain propositions contribute rating zero and
+ prevent conformance; their field placement cannot promote the collection.
+ """
+
+ if not members:
+ raise GraphEncodingError("an evidence-bound Graph collection must have members")
+ identifier_overlap = sorted(
+ set(graph.artifacts) & set(graph.transformations)
+ )
+ if identifier_overlap:
+ raise GraphEncodingError(
+ "evidence-bound Graph establishment requires globally disjoint "
+ "artifact and transformation identifiers: "
+ + ", ".join(identifier_overlap)
+ )
+ normalized_members = tuple(sorted(set(members)))
+ closure = graph.ancestors(normalized_members)
+ edges = {
+ edge.transformation_id
+ for artifact_id in closure
+ for edge in graph.incoming_hyperedges(artifact_id)
+ }
+ errors: list[str] = []
+ object_results: list[tuple[str, EvaluatedProposition]] = []
+ edge_results: list[tuple[str, EvaluatedProposition]] = []
+ object_levels: dict[str, int] = {}
+ edge_levels: dict[str, int] = {}
+ exact_collection_binding = graph_collection_binding_digest(
+ graph,
+ collection_id=collection_id,
+ members=normalized_members,
+ binding=binding,
+ )
+
+ extra_objects = set(object_evidence) - closure
+ extra_edges = set(edge_evidence) - edges
+ if extra_objects:
+ errors.append(f"object ratings outside provenance closure: {sorted(extra_objects)}")
+ if extra_edges:
+ errors.append(f"edge ratings outside provenance closure: {sorted(extra_edges)}")
+
+ def evaluate_rating(
+ subject: str,
+ proposition: Optional[BoundProposition],
+ predicate: str,
+ sink: list[tuple[str, EvaluatedProposition]],
+ ) -> int:
+ if proposition is None:
+ errors.append(f"missing rating evidence for {subject}")
+ return 0
+ if type(proposition.expected) is not int:
+ errors.append(f"rating for {subject} is not an integer")
+ return 0
+ rating = proposition.expected
+ if not GRAPH_MIN_LEVEL <= rating <= GRAPH_MAX_LEVEL:
+ errors.append(f"rating for {subject} is outside 1..5")
+ return 0
+ if (
+ proposition.subject_id != subject
+ or proposition.predicate != predicate
+ or proposition.parameters.get("collection_id") != collection_id
+ or proposition.parameters.get("collection_binding_digest")
+ != exact_collection_binding
+ ):
+ errors.append(f"rating evidence for {subject} is not exactly collection-bound")
+ return 0
+ result = session.evaluate(proposition)
+ sink.append((subject, result))
+ if result.outcome is not MechanismOutcome.PASS:
+ errors.append(
+ f"rating mechanism for {subject} returned {result.outcome.value}"
+ )
+ return 0
+ return rating
+
+ for artifact_id in sorted(closure):
+ object_levels[artifact_id] = evaluate_rating(
+ artifact_id,
+ object_evidence.get(artifact_id),
+ "vstd.object_profile",
+ object_results,
+ )
+ for transformation_id in sorted(edges):
+ edge_levels[transformation_id] = evaluate_rating(
+ transformation_id,
+ edge_evidence.get(transformation_id),
+ "vstd.graph_edge_profile",
+ edge_results,
+ )
+
+ candidate = graph_level(
+ graph,
+ GraphCollection(
+ collection_id,
+ normalized_members,
+ object_levels,
+ edge_levels,
+ ),
+ binding=binding,
+ )
+ kernel_outcome = "REJECTED"
+ certificate = candidate.witness or candidate.refutation
+ if certificate is not None:
+ kernel_outcome = kernel_check(certificate, binding=binding).outcome.value
+ return EvidenceBoundGraphLevelResult(
+ candidate,
+ tuple(object_results),
+ tuple(edge_results),
+ tuple(errors),
+ kernel_outcome,
+ )
+
+
+def build_evidence_bound_graph_level_record(
+ result: EvidenceBoundGraphLevelResult,
+ *,
+ graph: ProvenanceHypergraph,
+ members: Sequence[str],
+ binding: ClaimBinding,
+ object_evidence: Mapping[str, BoundProposition],
+ edge_evidence: Mapping[str, BoundProposition],
+ session: VerificationSession,
+) -> dict[str, Any]:
+ """Serialize exact Graph rating bindings and bytes for offline replay."""
+ recomputed = establish_graph_level(
+ graph,
+ collection_id=result.candidate.collection_id,
+ members=members,
+ object_evidence=object_evidence,
+ edge_evidence=edge_evidence,
+ session=session,
+ binding=binding,
+ )
+ if canonical_digest(recomputed.to_dict()) != canonical_digest(result.to_dict()):
+ raise ValueError("Graph profile result does not match the supplied replay inputs")
+ all_refs = tuple(
+ sorted(
+ {
+ reference
+ for proposition in (*object_evidence.values(), *edge_evidence.values())
+ for reference in proposition.evidence_refs
+ }
+ )
+ )
+ normalized_members = tuple(sorted(set(members)))
+ payload = result.to_dict()
+ payload.update(
+ {
+ "members": list(normalized_members),
+ "binding": binding.to_dict(),
+ "evidence_bindings": {
+ "objects": {
+ subject: proposition.to_dict()
+ for subject, proposition in sorted(object_evidence.items())
+ },
+ "edges": {
+ subject: proposition.to_dict()
+ for subject, proposition in sorted(edge_evidence.items())
+ },
+ },
+ "evidence_payloads": session.evidence.export_base64(all_refs),
+ }
+ )
+ return payload
+
+
+def recheck_evidence_bound_graph_level_record(
+ graph: ProvenanceHypergraph,
+ record: Mapping[str, Any],
+ *,
+ mechanisms: Sequence[VerificationMechanism],
+) -> EvidenceBoundGraphLevelResult:
+ """Rebuild the evidence store, rerun rating mechanisms, and compare result."""
+ if record.get("rating_basis") != "MECHANISM_EVALUATED":
+ raise ValueError("Graph profile record is not mechanism-evaluated")
+ payloads = record.get("evidence_payloads")
+ bindings = record.get("evidence_bindings")
+ binding_data = record.get("binding")
+ members = record.get("members")
+ if (
+ not isinstance(payloads, Mapping)
+ or not isinstance(bindings, Mapping)
+ or not isinstance(binding_data, Mapping)
+ or not isinstance(members, Sequence)
+ or isinstance(members, (str, bytes))
+ ):
+ raise ValueError("evidence-bound Graph record is missing replay inputs")
+ store = EvidenceStore()
+ store.import_base64({str(key): str(value) for key, value in payloads.items()})
+ session = VerificationSession(store)
+ for mechanism in mechanisms:
+ session.register(mechanism)
+ objects_data = bindings.get("objects")
+ edges_data = bindings.get("edges")
+ if not isinstance(objects_data, Mapping) or not isinstance(edges_data, Mapping):
+ raise ValueError("Graph evidence binding maps are missing")
+ objects = {
+ str(subject): BoundProposition.from_dict(proposition)
+ for subject, proposition in objects_data.items()
+ if isinstance(proposition, Mapping)
+ }
+ edges = {
+ str(subject): BoundProposition.from_dict(proposition)
+ for subject, proposition in edges_data.items()
+ if isinstance(proposition, Mapping)
+ }
+ result = establish_graph_level(
+ graph,
+ collection_id=str(record["collection_id"]),
+ members=tuple(str(item) for item in members),
+ object_evidence=objects,
+ edge_evidence=edges,
+ session=session,
+ binding=claim_binding_from_dict(binding_data),
+ )
+ for result_field, value in result.to_dict().items():
+ if record.get(result_field) != value:
+ raise ValueError(
+ f"recomputed Graph field does not match receipt: {result_field}"
+ )
+ return result
diff --git a/src/verifier/data/models.py b/src/verifier/data/models.py
index 279b5a6..5213306 100644
--- a/src/verifier/data/models.py
+++ b/src/verifier/data/models.py
@@ -1,6 +1,8 @@
-"""VSTD-Graph provenance models and algorithms.
+"""Terminology: Secure Hash Algorithm 256-bit (SHA-256); Verifier Standard (VSTD).
-Graph-1 receipts retain the frozen ``VSTD-DATA-0.1`` wire identifier.
+VSTD-Graph provenance models and algorithms.
+
+Graph-1 receipts retain the frozen ``VSTD-DATA-0.1`` serialized receipt identifier.
"""
from __future__ import annotations
@@ -162,6 +164,26 @@ def to_dict(self) -> dict[str, Any]:
}
+@dataclass(frozen=True)
+class ConflictRecord:
+ """Retained incompatible evidence about one artifact or transformation field."""
+
+ conflict_id: str
+ subject_id: str
+ predicate: str
+ competing_values: tuple[str, ...]
+ evidence_refs: tuple[str, ...]
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "conflict_id": self.conflict_id,
+ "subject_id": self.subject_id,
+ "predicate": self.predicate,
+ "competing_values": list(self.competing_values),
+ "evidence_refs": list(self.evidence_refs),
+ }
+
+
@dataclass(frozen=True)
class HyperedgePort:
artifact_id: str
@@ -236,22 +258,46 @@ def __init__(self) -> None:
self.transformations: dict[str, TransformationHyperedge] = {}
self.contributors: dict[str, ContributorSpec] = {}
self.rights: dict[str, RightsSpec] = {}
+ self.conflicts: dict[str, ConflictRecord] = {}
+
+ @staticmethod
+ def _add_unique(collection: dict[str, Any], identifier: str, value: Any) -> str:
+ if identifier in collection:
+ raise ValueError(f"duplicate graph identifier: {identifier}")
+ collection[identifier] = value
+ return identifier
def add_artifact(self, artifact: ArtifactNode) -> str:
- self.artifacts[artifact.artifact_id] = artifact
- return artifact.artifact_id
+ if artifact.artifact_id in self.transformations:
+ raise ValueError(
+ "artifact and transformation identifiers must be disjoint: "
+ f"{artifact.artifact_id}"
+ )
+ return self._add_unique(self.artifacts, artifact.artifact_id, artifact)
def add_transformation(self, transform: TransformationHyperedge) -> str:
- self.transformations[transform.transformation_id] = transform
- return transform.transformation_id
+ if transform.transformation_id in self.artifacts:
+ raise ValueError(
+ "artifact and transformation identifiers must be disjoint: "
+ f"{transform.transformation_id}"
+ )
+ return self._add_unique(
+ self.transformations, transform.transformation_id, transform
+ )
def add_contributor(self, contributor: ContributorSpec) -> str:
- self.contributors[contributor.contributor_id] = contributor
- return contributor.contributor_id
+ return self._add_unique(
+ self.contributors, contributor.contributor_id, contributor
+ )
def add_rights(self, rights: RightsSpec) -> str:
- self.rights[rights.rights_id] = rights
- return rights.rights_id
+ return self._add_unique(self.rights, rights.rights_id, rights)
+
+ def add_conflict(self, conflict: ConflictRecord) -> str:
+ return self._add_unique(self.conflicts, conflict.conflict_id, conflict)
+
+ def has_conflict(self, subject_id: str) -> bool:
+ return any(record.subject_id == subject_id for record in self.conflicts.values())
def incoming_hyperedges(self, artifact_id: str) -> list[TransformationHyperedge]:
"""Hyperedges that produce artifact_id as an output."""
@@ -309,13 +355,23 @@ def root_sources(self) -> set[str]:
roots.add(art_id)
return roots
- def validate_structure(self) -> list[str]:
+ def validate_structure(
+ self, *, allow_legacy_identifier_overlap: bool = False
+ ) -> list[str]:
"""Return deterministic errors for the implemented graph surface.
This validates the stored representation. It does not prove that the graph
- captures every real-world input or transformation.
+ captures every real-world input or transformation. The compatibility flag
+ preserves the two identifier namespaces of frozen ``VSTD-DATA-0.1`` bytes;
+ new construction and assurance mechanisms keep the stricter default.
"""
errors: list[str] = []
+ if not allow_legacy_identifier_overlap:
+ for identifier in sorted(set(self.artifacts) & set(self.transformations)):
+ errors.append(
+ "artifact and transformation identifiers must be disjoint: "
+ f"{identifier}"
+ )
digest_pattern = re.compile(r"^[0-9a-fA-F]{64}$")
for artifact_id, artifact in sorted(self.artifacts.items()):
@@ -352,15 +408,41 @@ def validate_structure(self) -> list[str]:
errors.append(
f"transformation {transformation_id} has an empty role for {port.artifact_id}"
)
+ subjects = set(self.artifacts) | set(self.transformations)
+ for conflict_id, conflict in sorted(self.conflicts.items()):
+ if not conflict_id or conflict.conflict_id != conflict_id:
+ errors.append(f"conflict map key does not match conflict_id: {conflict_id}")
+ if conflict.subject_id not in subjects:
+ errors.append(
+ f"conflict {conflict_id} references missing subject {conflict.subject_id}"
+ )
+ if not conflict.predicate:
+ errors.append(f"conflict {conflict_id} has an empty predicate")
+ if len(set(conflict.competing_values)) < 2:
+ errors.append(f"conflict {conflict_id} must retain at least two competing values")
+ if len(set(conflict.evidence_refs)) < 2:
+ errors.append(f"conflict {conflict_id} must retain at least two evidence references")
return errors
- def verify_acyclicity(self) -> bool:
- """Check whether the bipartite artifact-hyperedge graph contains cycles."""
- adj: dict[str, set[str]] = {a: set() for a in self.artifacts}
+ def verify_acyclicity(self, artifact_ids: Optional[Iterable[str]] = None) -> bool:
+ """Check whether all or a selected artifact-induced subgraph contains cycles.
+
+ Structural reference validation remains the responsibility of
+ :meth:`validate_structure`; missing referenced artifacts are retained as
+ vertices here so the cycle check itself remains total.
+ """
+ if artifact_ids is None:
+ selected = set(self.artifacts)
+ for transform in self.transformations.values():
+ selected.update(port.artifact_id for port in (*transform.inputs, *transform.outputs))
+ else:
+ selected = set(artifact_ids)
+ adj: dict[str, set[str]] = {artifact_id: set() for artifact_id in selected}
for t in self.transformations.values():
for inp in t.inputs:
for out in t.outputs:
- adj[inp.artifact_id].add(out.artifact_id)
+ if inp.artifact_id in selected and out.artifact_id in selected:
+ adj[inp.artifact_id].add(out.artifact_id)
visited: set[str] = set()
rec_stack: set[str] = set()
@@ -377,7 +459,7 @@ def dfs(node: str) -> bool:
rec_stack.remove(node)
return False
- for a in self.artifacts:
+ for a in selected:
if a not in visited:
if dfs(a):
return False
@@ -407,7 +489,7 @@ def compute_completeness(self) -> CompletenessMetrics:
trans_cov = trans_covered / max(total_trans, 1)
# 3. Content-digest declaration coverage. This is syntax coverage, not a
- # physical-byte rehash; see VSTD-DATA-0.1 section 3.
+ # physical-byte rehash; see VSTD-Graph-1 section 3.
digest_pattern = re.compile(r"^[0-9a-fA-F]{64}$")
integ_covered = sum(
1 for art in self.artifacts.values()
@@ -464,10 +546,23 @@ def to_dict(self) -> dict[str, Any]:
"transformations": [t.to_dict() for t in self.transformations.values()],
"contributors": [c.to_dict() for c in self.contributors.values()],
"rights": [r.to_dict() for r in self.rights.values()],
+ "conflicts": [c.to_dict() for c in self.conflicts.values()],
}
@classmethod
- def from_dict(cls, data: Mapping[str, Any]) -> "ProvenanceHypergraph":
+ def from_dict(
+ cls,
+ data: Mapping[str, Any],
+ *,
+ allow_legacy_identifier_overlap: bool = True,
+ ) -> "ProvenanceHypergraph":
+ """Decode stored Graph bytes without rewriting their identifier semantics.
+
+ Frozen ``VSTD-DATA-0.1`` used separate artifact and transformation
+ namespaces. The default therefore retains cross-kind overlap while still
+ refusing duplicates inside either collection. Pass ``False`` for a strict
+ new-mechanism decoder; direct ``add_*`` construction is always strict.
+ """
g = cls()
for c_data in data.get("contributors", []):
g.add_contributor(ContributorSpec(**c_data))
@@ -483,26 +578,40 @@ def from_dict(cls, data: Mapping[str, Any]) -> "ProvenanceHypergraph":
rights_evidence_level=RightsEvidenceLevel(r_data.get("rights_evidence_level", "RIGHTS_DECLARED")),
)
)
- for a_data in data.get("artifacts", []):
- g.add_artifact(
- ArtifactNode(
- artifact_id=a_data["artifact_id"],
- label=a_data["label"],
- artifact_type=ArtifactType(a_data["artifact_type"]),
- content_digest=a_data["content_digest"],
- byte_size=a_data.get("byte_size", 0),
- record_count=a_data.get("record_count"),
- mime_type=a_data.get("mime_type", "application/octet-stream"),
- metadata_digest=a_data.get("metadata_digest", ""),
- provenance_digest=a_data.get("provenance_digest", ""),
- status=ArtifactStatus(a_data.get("status", "UNKNOWN")),
- evidence_class=EvidenceClassification(a_data.get("evidence_class", "DECLARED")),
- rights_id=a_data.get("rights_id"),
- contributor_id=a_data.get("contributor_id"),
- storage_uris=tuple(a_data.get("storage_uris", ())),
- attributes=a_data.get("attributes", {}),
+ for conflict_data in data.get("conflicts", []):
+ g.add_conflict(
+ ConflictRecord(
+ conflict_id=conflict_data["conflict_id"],
+ subject_id=conflict_data["subject_id"],
+ predicate=conflict_data["predicate"],
+ competing_values=tuple(conflict_data.get("competing_values", ())),
+ evidence_refs=tuple(conflict_data.get("evidence_refs", ())),
)
)
+ for a_data in data.get("artifacts", []):
+ artifact = ArtifactNode(
+ artifact_id=a_data["artifact_id"],
+ label=a_data["label"],
+ artifact_type=ArtifactType(a_data["artifact_type"]),
+ content_digest=a_data["content_digest"],
+ byte_size=a_data.get("byte_size", 0),
+ record_count=a_data.get("record_count"),
+ mime_type=a_data.get("mime_type", "application/octet-stream"),
+ metadata_digest=a_data.get("metadata_digest", ""),
+ provenance_digest=a_data.get("provenance_digest", ""),
+ status=ArtifactStatus(a_data.get("status", "UNKNOWN")),
+ evidence_class=EvidenceClassification(
+ a_data.get("evidence_class", "DECLARED")
+ ),
+ rights_id=a_data.get("rights_id"),
+ contributor_id=a_data.get("contributor_id"),
+ storage_uris=tuple(a_data.get("storage_uris", ())),
+ attributes=a_data.get("attributes", {}),
+ )
+ if allow_legacy_identifier_overlap:
+ g._add_unique(g.artifacts, artifact.artifact_id, artifact)
+ else:
+ g.add_artifact(artifact)
for t_data in data.get("transformations", []):
inputs = tuple(
HyperedgePort(artifact_id=p["artifact_id"], role=p["role"])
@@ -512,18 +621,28 @@ def from_dict(cls, data: Mapping[str, Any]) -> "ProvenanceHypergraph":
HyperedgePort(artifact_id=p["artifact_id"], role=p["role"])
for p in t_data.get("outputs", [])
)
- g.add_transformation(
- TransformationHyperedge(
- transformation_id=t_data["transformation_id"],
- label=t_data["label"],
- transformation_type=TransformationType(t_data["transformation_type"]),
- inputs=inputs,
- outputs=outputs,
- software_provenance=t_data.get("software_provenance", {}),
- parameters=t_data.get("parameters", {}),
- execution_environment=t_data.get("execution_environment", {}),
- evidence_class=EvidenceClassification(t_data.get("evidence_class", "DECLARED")),
- status=t_data.get("status", "COMPLETED"),
- )
+ transformation = TransformationHyperedge(
+ transformation_id=t_data["transformation_id"],
+ label=t_data["label"],
+ transformation_type=TransformationType(
+ t_data["transformation_type"]
+ ),
+ inputs=inputs,
+ outputs=outputs,
+ software_provenance=t_data.get("software_provenance", {}),
+ parameters=t_data.get("parameters", {}),
+ execution_environment=t_data.get("execution_environment", {}),
+ evidence_class=EvidenceClassification(
+ t_data.get("evidence_class", "DECLARED")
+ ),
+ status=t_data.get("status", "COMPLETED"),
)
+ if allow_legacy_identifier_overlap:
+ g._add_unique(
+ g.transformations,
+ transformation.transformation_id,
+ transformation,
+ )
+ else:
+ g.add_transformation(transformation)
return g
diff --git a/src/verifier/data/policy.py b/src/verifier/data/policy.py
index 2ba406e..c502cec 100644
--- a/src/verifier/data/policy.py
+++ b/src/verifier/data/policy.py
@@ -1,4 +1,8 @@
-"""Formal Policy Verification Engine for Dataset & Computational Provenance."""
+"""Terminology: conjunctive normal form (CNF); grounded decision certificate (GDC);
+Boolean satisfiability problem (SAT); Software Package Data Exchange (SPDX);
+Verifier Standard (VSTD).
+
+Formal Policy Verification Engine for Dataset & Computational Provenance."""
from __future__ import annotations
diff --git a/src/verifier/data/receipt.py b/src/verifier/data/receipt.py
index bb95ef6..429caf8 100644
--- a/src/verifier/data/receipt.py
+++ b/src/verifier/data/receipt.py
@@ -1,6 +1,9 @@
-"""VSTD-Graph receipt model and canonical serialization.
+"""Terminology: identifier (ID); JavaScript Object Notation (JSON); operating system (OS);
+Boolean satisfiability problem (SAT); trusted computing base (TCB); Verifier Standard (VSTD).
-Graph-1 receipts retain the frozen ``VSTD-DATA-0.1`` wire identifier.
+VSTD-Graph receipt model and canonical serialization.
+
+Graph-1 receipts retain the frozen ``VSTD-DATA-0.1`` serialized receipt identifier.
"""
from __future__ import annotations
@@ -9,11 +12,11 @@
import json
import sys
import time
-from dataclasses import dataclass
+from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Optional
-from verifier.core.checker import VerificationVerdict
+from verifier.core.checker import IndependenceBasis, VerificationVerdict
from verifier.core.provenance import ProvenanceRecord
from verifier.core.receipt import compute_canonical_digest
from verifier.data.models import CompletenessMetrics, ProvenanceHypergraph
@@ -44,6 +47,8 @@ def to_dict(self) -> dict[str, Any]:
@dataclass(frozen=True)
class DataIndependentAudit:
+ """Historical wire field for a checker result plus explicit separation basis."""
+
overall_verdict: VerificationVerdict
acyclic_hypergraph: bool
integrity_passed: bool
@@ -52,6 +57,7 @@ class DataIndependentAudit:
transformations_count: int
trusted_computing_base: dict[str, str]
audit_notes: list[str]
+ independence_basis: IndependenceBasis = field(default_factory=IndependenceBasis)
def to_dict(self) -> dict[str, Any]:
return {
@@ -63,6 +69,7 @@ def to_dict(self) -> dict[str, Any]:
"transformations_count": self.transformations_count,
"trusted_computing_base": self.trusted_computing_base,
"audit_notes": self.audit_notes,
+ "independence_basis": self.independence_basis.to_dict(),
}
@@ -189,7 +196,7 @@ def _duplicate_ids(items: Any, key: str) -> list[str]:
def validate_data_receipt(receipt_path_or_dir: Path) -> int:
- """Validate a VSTD-DATA-0.1 receipt without a target-specific adapter."""
+ """Validate a VSTD-Graph-1 receipt without a target-specific adapter."""
receipt_file = _receipt_file(receipt_path_or_dir)
if not receipt_file.exists():
print(f"[FAIL] Receipt not found at {receipt_file}", file=sys.stderr)
@@ -263,6 +270,7 @@ def validate_data_receipt(receipt_path_or_dir: Path) -> int:
("transformations", "transformation_id"),
("contributors", "contributor_id"),
("rights", "rights_id"),
+ ("conflicts", "conflict_id"),
):
graph_errors.extend(_duplicate_ids(graph_payload.get(collection), key))
try:
@@ -270,7 +278,9 @@ def validate_data_receipt(receipt_path_or_dir: Path) -> int:
except (KeyError, TypeError, ValueError) as exc:
print(f"[FAIL] Cannot parse hypergraph: {exc}", file=sys.stderr)
return 1
- graph_errors.extend(hypergraph.validate_structure())
+ graph_errors.extend(
+ hypergraph.validate_structure(allow_legacy_identifier_overlap=True)
+ )
target_artifact_id = data.get("dataset_spec", {}).get("target_artifact_id")
if target_artifact_id not in hypergraph.artifacts:
@@ -290,6 +300,63 @@ def validate_data_receipt(receipt_path_or_dir: Path) -> int:
if not isinstance(audit, dict):
graph_errors.append("independent_audit must be an object")
audit = {}
+ basis = audit.get("independence_basis")
+ if basis is not None:
+ if not isinstance(basis, dict):
+ graph_errors.append("independent_audit.independence_basis must be an object")
+ else:
+ basis_fields = {
+ "independently_verified",
+ "actor_independence",
+ "implementation_separation",
+ "runtime_separation",
+ "evidence",
+ }
+ missing_basis_fields = sorted(basis_fields - basis.keys())
+ unknown_basis_fields = sorted(basis.keys() - basis_fields)
+ if missing_basis_fields:
+ graph_errors.append(
+ "independent_audit.independence_basis is missing fields: "
+ + ", ".join(missing_basis_fields)
+ )
+ if unknown_basis_fields:
+ graph_errors.append(
+ "independent_audit.independence_basis has unknown fields: "
+ + ", ".join(unknown_basis_fields)
+ )
+ statuses = {
+ "EVIDENCED",
+ "DECLARED",
+ "NOT_DEMONSTRATED",
+ "CONFLICTED",
+ }
+ separation_fields = (
+ "actor_independence",
+ "implementation_separation",
+ "runtime_separation",
+ )
+ for field_name in separation_fields:
+ if basis.get(field_name) not in statuses:
+ graph_errors.append(
+ f"independent_audit.independence_basis.{field_name} is not recognized"
+ )
+ evidence = basis.get("evidence")
+ if not isinstance(evidence, list) or not all(
+ isinstance(item, str) and item for item in evidence
+ ):
+ graph_errors.append(
+ "independent_audit.independence_basis.evidence must be an array of nonempty strings"
+ )
+ if basis.get("independently_verified") is not False:
+ graph_errors.append(
+ "independent_audit.independence_basis cannot be independently verified: "
+ "VSTD 1.2.0 has no actor/execution evidence-binding validator"
+ )
+ if any(basis.get(field_name) == "EVIDENCED" for field_name in separation_fields):
+ graph_errors.append(
+ "independent_audit.independence_basis EVIDENCED assertions are unvalidated; "
+ "the bundled runtime treats externally supplied assertions as no stronger than DECLARED"
+ )
expected_audit_fields = {
"acyclic_hypergraph": acyclic,
"integrity_passed": completeness.content_integrity == 1.0,
@@ -363,10 +430,13 @@ def validate_data_receipt(receipt_path_or_dir: Path) -> int:
print(f"[FAIL] {error}", file=sys.stderr)
return 1
- print(f"[PASS] Dataset Receipt {data.get('receipt_id')} is valid.")
+ print(
+ f"[VALIDATION OK] Dataset Receipt {data.get('receipt_id')} passed "
+ "the implemented stored-receipt checks."
+ )
print(f" Schema: {data.get('schema_version')}")
print(f" Digest: {recorded_digest}")
- print(f" Verdict: {data.get('independent_audit', {}).get('overall_verdict')}")
+ print(f" Stored checker verdict: {data.get('independent_audit', {}).get('overall_verdict')}")
print(" Scope: stored receipt + recorded hypergraph; upstream bytes not rehashed")
return 0
@@ -389,7 +459,7 @@ def reproduce_data_receipt(receipt_path_or_dir: Path) -> int:
completeness = hypergraph.compute_completeness()
elapsed_ms = (time.perf_counter() - start_time) * 1000.0
- print("[REPRODUCTION RESULT] Level: CONTENT_IDENTICAL")
+ print("[REPRODUCTION RESULT] Fidelity state: CONTENT_IDENTICAL")
print(" Replay scope: stored receipt digest + hypergraph mechanisms")
print(" Upstream execution: NOT_RECONSTRUCTED")
print(f" Receipt ID: {data.get('receipt_id')}")
@@ -473,17 +543,23 @@ def generate_data_receipt_markdown(receipt: VstdDataReceipt) -> str:
---
-## 5. Independent Auditor & Trusted Computing Base (TCB)
+## 5. Stored Checker Result & Trusted Computing Base (TCB)
- **Acyclicity Verified:** {'PASSED (No cycles)' if audit.acyclic_hypergraph else 'FAILED (Cycle detected)'}
- **Content-Digest Declaration Check:** {'PASSED' if audit.integrity_passed else 'FAILED'}
-- **Overall Independent Verdict:** {audit.overall_verdict.value}
+- **Overall Checker Verdict:** {audit.overall_verdict.value}
+- **Independent Verification:** {'EVIDENCED' if audit.independence_basis.independently_verified else 'NOT_DEMONSTRATED'}
### TCB Declaration
```yaml
{chr(10).join(f"{k}: {v}" for k, v in audit.trusted_computing_base.items())}
```
+### Independence Basis
+```yaml
+{chr(10).join(f"{k}: {v}" for k, v in audit.independence_basis.to_dict().items())}
+```
+
---
## 6. Upstream Source & Environment Provenance
@@ -496,14 +572,15 @@ def generate_data_receipt_markdown(receipt: VstdDataReceipt) -> str:
---
-## 7. Independent Reproduction
+## 7. Reproduction of Stored Checks
-To independently inspect and reproduce this dataset hypergraph receipt:
+To inspect and reproduce the stored dataset-hypergraph checks:
```bash
vstd data verify receipts/{receipt.receipt_id}
vstd data trace {spec.target_artifact_id} --receipt receipts/{receipt.receipt_id}
```
-*Generated by VSTD Data Runtime v0.1.0 (VSTD-DATA-0.1)*
+*Generated by the VSTD-Graph-1 reference runtime
+(serialized receipt identifier `VSTD-DATA-0.1`).*
"""
diff --git a/src/verifier/experimental_workflow/__init__.py b/src/verifier/experimental_workflow/__init__.py
new file mode 100644
index 0000000..99a3e95
--- /dev/null
+++ b/src/verifier/experimental_workflow/__init__.py
@@ -0,0 +1,36 @@
+"""Terminology: identifier (ID); Verifier Standard (VSTD).
+
+Experimental workflow profile; non-normative and verdict-neutral."""
+
+from __future__ import annotations
+
+from .github import GitHubAdapterError, github_snapshot_to_events
+from .profile import (
+ PROFILE_ID,
+ PROFILE_STATUS,
+ PROFILE_VERSION,
+ WorkflowProfileError,
+ canonical_bytes,
+ load_manifest,
+ manifest_digest,
+ seal_manifest,
+ validate_manifest,
+ verify_repo_artifacts,
+)
+from .schema import workflow_manifest_schema
+
+__all__ = [
+ "GitHubAdapterError",
+ "PROFILE_ID",
+ "PROFILE_STATUS",
+ "PROFILE_VERSION",
+ "WorkflowProfileError",
+ "canonical_bytes",
+ "github_snapshot_to_events",
+ "load_manifest",
+ "manifest_digest",
+ "seal_manifest",
+ "validate_manifest",
+ "verify_repo_artifacts",
+ "workflow_manifest_schema",
+]
diff --git a/src/verifier/experimental_workflow/github.py b/src/verifier/experimental_workflow/github.py
new file mode 100644
index 0000000..c043c31
--- /dev/null
+++ b/src/verifier/experimental_workflow/github.py
@@ -0,0 +1,218 @@
+"""Terminology: application programming interface (API); Verifier Standard (VSTD).
+
+Deterministic GitHub-to-workflow observations with no verification upgrade."""
+
+from __future__ import annotations
+
+import hashlib
+import json
+from typing import Any, Mapping
+
+
+class GitHubAdapterError(ValueError):
+ """Raised when the normalized GitHub snapshot is incomplete or unsupported."""
+
+
+def _expect_object(value: Any, path: str) -> Mapping[str, Any]:
+ if not isinstance(value, Mapping):
+ raise GitHubAdapterError(f"{path} must be an object")
+ return value
+
+
+def _expect_array(value: Any, path: str) -> list[Any]:
+ if not isinstance(value, list):
+ raise GitHubAdapterError(f"{path} must be an array")
+ return value
+
+
+def _exact(value: Mapping[str, Any], path: str, fields: set[str]) -> None:
+ missing = sorted(fields - set(value))
+ unknown = sorted(set(value) - fields)
+ if missing:
+ raise GitHubAdapterError(f"{path} missing fields: {', '.join(missing)}")
+ if unknown:
+ raise GitHubAdapterError(f"{path} unsupported fields: {', '.join(unknown)}")
+
+
+def _text(value: Any, path: str, *, nullable: bool = False) -> str | None:
+ if nullable and value is None:
+ return None
+ if not isinstance(value, str) or not value.strip():
+ raise GitHubAdapterError(f"{path} must be a non-empty string")
+ return value
+
+
+def _integer(value: Any, path: str) -> int:
+ if isinstance(value, bool) or not isinstance(value, int) or value < 0:
+ raise GitHubAdapterError(f"{path} must be a non-negative integer")
+ return value
+
+
+def _event_id(kind: str, repository: str, coordinate: str) -> str:
+ stable = json.dumps(
+ [kind, repository, coordinate], ensure_ascii=True, separators=(",", ":")
+ ).encode("utf-8")
+ return f"github-event-{hashlib.sha256(stable).hexdigest()[:20]}"
+
+
+def _event(
+ *,
+ kind: str,
+ repository: str,
+ coordinate: str,
+ recorded_at: str,
+ native_state: str,
+ details: Mapping[str, Any],
+) -> dict[str, Any]:
+ return {
+ "id": _event_id(kind, repository, coordinate),
+ "kind": kind,
+ "recorded_at": recorded_at,
+ "source": {
+ "platform": "github",
+ "repository": repository,
+ "coordinate": coordinate,
+ },
+ "native_state": native_state,
+ "verification_effect": "NONE",
+ "details": dict(details),
+ }
+
+
+def github_snapshot_to_events(snapshot: Mapping[str, Any]) -> tuple[dict[str, Any], ...]:
+ """Map the documented normalized snapshot to verdict-neutral workflow events.
+
+ The input is not the unconstrained GitHub API response. Rejecting unknown fields
+ prevents a caller from assuming that unparsed platform semantics were preserved.
+ A successful workflow or merged pull request remains a platform fact only.
+ """
+
+ root = _expect_object(snapshot, "$")
+ _exact(
+ root,
+ "$",
+ {"repository", "issues", "commits", "workflow_runs", "pull_requests"},
+ )
+ repository = _text(root["repository"], "$.repository")
+ assert repository is not None
+ events: list[dict[str, Any]] = []
+
+ for index, value in enumerate(_expect_array(root["issues"], "$.issues")):
+ path = f"$.issues[{index}]"
+ item = _expect_object(value, path)
+ _exact(item, path, {"number", "title", "state", "updated_at"})
+ number = _integer(item["number"], f"{path}.number")
+ title = _text(item["title"], f"{path}.title")
+ state = _text(item["state"], f"{path}.state")
+ updated_at = _text(item["updated_at"], f"{path}.updated_at")
+ assert title is not None and state is not None and updated_at is not None
+ events.append(
+ _event(
+ kind="PLATFORM_ISSUE",
+ repository=repository,
+ coordinate=f"issue:{number}",
+ recorded_at=updated_at,
+ native_state=state,
+ details={"number": number, "title": title},
+ )
+ )
+
+ for index, value in enumerate(_expect_array(root["commits"], "$.commits")):
+ path = f"$.commits[{index}]"
+ item = _expect_object(value, path)
+ _exact(item, path, {"sha", "subject", "committed_at"})
+ sha = _text(item["sha"], f"{path}.sha")
+ subject = _text(item["subject"], f"{path}.subject")
+ committed_at = _text(item["committed_at"], f"{path}.committed_at")
+ assert sha is not None and subject is not None and committed_at is not None
+ events.append(
+ _event(
+ kind="PLATFORM_COMMIT",
+ repository=repository,
+ coordinate=f"commit:{sha}",
+ recorded_at=committed_at,
+ native_state="RECORDED",
+ details={"sha": sha, "subject": subject},
+ )
+ )
+
+ for index, value in enumerate(_expect_array(root["workflow_runs"], "$.workflow_runs")):
+ path = f"$.workflow_runs[{index}]"
+ item = _expect_object(value, path)
+ _exact(
+ item,
+ path,
+ {"id", "workflow", "status", "conclusion", "head_sha", "updated_at", "artifacts"},
+ )
+ run_id = _integer(item["id"], f"{path}.id")
+ workflow = _text(item["workflow"], f"{path}.workflow")
+ status = _text(item["status"], f"{path}.status")
+ conclusion = _text(item["conclusion"], f"{path}.conclusion", nullable=True)
+ head_sha = _text(item["head_sha"], f"{path}.head_sha")
+ updated_at = _text(item["updated_at"], f"{path}.updated_at")
+ assert workflow is not None and status is not None and head_sha is not None and updated_at is not None
+ native_state = status if conclusion is None else f"{status}/{conclusion}"
+ events.append(
+ _event(
+ kind="PLATFORM_WORKFLOW_RUN",
+ repository=repository,
+ coordinate=f"workflow-run:{run_id}",
+ recorded_at=updated_at,
+ native_state=native_state,
+ details={"id": run_id, "workflow": workflow, "head_sha": head_sha},
+ )
+ )
+ for artifact_index, artifact_value in enumerate(
+ _expect_array(item["artifacts"], f"{path}.artifacts")
+ ):
+ artifact_path = f"{path}.artifacts[{artifact_index}]"
+ artifact = _expect_object(artifact_value, artifact_path)
+ _exact(artifact, artifact_path, {"id", "name", "digest", "expired"})
+ artifact_id = _integer(artifact["id"], f"{artifact_path}.id")
+ name = _text(artifact["name"], f"{artifact_path}.name")
+ digest = _text(artifact["digest"], f"{artifact_path}.digest", nullable=True)
+ expired = artifact["expired"]
+ if not isinstance(expired, bool):
+ raise GitHubAdapterError(f"{artifact_path}.expired must be boolean")
+ assert name is not None
+ events.append(
+ _event(
+ kind="PLATFORM_ARTIFACT",
+ repository=repository,
+ coordinate=f"workflow-artifact:{artifact_id}",
+ recorded_at=updated_at,
+ native_state="EXPIRED" if expired else "AVAILABLE",
+ details={"id": artifact_id, "name": name, "digest": digest, "run_id": run_id},
+ )
+ )
+
+ for index, value in enumerate(_expect_array(root["pull_requests"], "$.pull_requests")):
+ path = f"$.pull_requests[{index}]"
+ item = _expect_object(value, path)
+ _exact(item, path, {"number", "state", "merged", "head_sha", "base_sha", "updated_at"})
+ number = _integer(item["number"], f"{path}.number")
+ state = _text(item["state"], f"{path}.state")
+ merged = item["merged"]
+ if not isinstance(merged, bool):
+ raise GitHubAdapterError(f"{path}.merged must be boolean")
+ head_sha = _text(item["head_sha"], f"{path}.head_sha")
+ base_sha = _text(item["base_sha"], f"{path}.base_sha")
+ updated_at = _text(item["updated_at"], f"{path}.updated_at")
+ assert state is not None and head_sha is not None and base_sha is not None and updated_at is not None
+ events.append(
+ _event(
+ kind="PLATFORM_PULL_REQUEST",
+ repository=repository,
+ coordinate=f"pull-request:{number}",
+ recorded_at=updated_at,
+ native_state=f"{state}/{'MERGED' if merged else 'NOT_MERGED'}",
+ details={
+ "number": number,
+ "head_sha": head_sha,
+ "base_sha": base_sha,
+ "merged": merged,
+ },
+ )
+ )
+
+ return tuple(sorted(events, key=lambda item: item["id"]))
diff --git a/src/verifier/experimental_workflow/profile.py b/src/verifier/experimental_workflow/profile.py
new file mode 100644
index 0000000..0e4af86
--- /dev/null
+++ b/src/verifier/experimental_workflow/profile.py
@@ -0,0 +1,709 @@
+"""Terminology: identifier (ID); JavaScript Object Notation (JSON);
+Secure Hash Algorithm 256-bit (SHA-256); Unicode Transformation Format, 8-bit (UTF-8);
+Verifier Standard (VSTD).
+
+Validate the non-normative experimental-workflow profile.
+
+This module records allocation and workflow facts. It deliberately does not execute
+domain verifiers, derive VSTD verdicts, or treat repository state as verification.
+"""
+
+from __future__ import annotations
+
+import copy
+import hashlib
+import json
+import re
+from pathlib import Path, PurePosixPath
+from typing import Any, Mapping
+
+
+PROFILE_ID = "vstd.experimental-workflow"
+PROFILE_VERSION = "0.1"
+PROFILE_STATUS = "EXPERIMENTAL_NON_NORMATIVE"
+
+EXPERIMENT_STATES = frozenset(
+ {"DRAFT", "PREREGISTERED", "RUNNING", "BLOCKED", "COMPLETED", "ABANDONED"}
+)
+HYPOTHESIS_STATES = frozenset({"OPEN", "SUPPORTED", "REFUTED", "UNKNOWN", "CONFLICTED"})
+PREREGISTRATION_STATES = frozenset({"NONE", "DRAFT", "FROZEN", "AMENDED"})
+ACTION_STATES = frozenset({"PLANNED", "RUNNING", "BLOCKED", "COMPLETED", "ABANDONED"})
+OBSERVATION_STATES = frozenset({"OBSERVED", "UNKNOWN", "CONFLICTED"})
+MAPPING_STATES = frozenset({"NOT_EVALUATED", "MAPPED"})
+VSTD_VERDICTS = frozenset({"PASS", "FAIL", "UNKNOWN", "CONFLICTED", "REJECTED"})
+CHALLENGE_STATES = frozenset({"OPEN", "RESOLVED", "REJECTED"})
+HORIZON_STATES = frozenset({"UNKNOWN", "CONFLICTED", "BLOCKED", "OUT_OF_SCOPE"})
+PUBLICATION_STATES = frozenset({"PRIVATE", "INTERNAL", "CANDIDATE", "PUBLISHED", "RETRACTED"})
+PLATFORM_EVENT_KINDS = frozenset(
+ {
+ "PLATFORM_ISSUE",
+ "PLATFORM_COMMIT",
+ "PLATFORM_WORKFLOW_RUN",
+ "PLATFORM_ARTIFACT",
+ "PLATFORM_PULL_REQUEST",
+ }
+)
+
+_DIGEST_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
+_PORTABLE_LOCATOR_PREFIXES = (
+ "artifact:",
+ "git:",
+ "https://",
+ "repo:",
+ "urn:",
+)
+
+
+class WorkflowProfileError(ValueError):
+ """Raised when a workflow manifest exceeds or violates the profile boundary."""
+
+
+def _fail(path: str, message: str) -> None:
+ raise WorkflowProfileError(f"{path}: {message}")
+
+
+def _mapping(value: Any, path: str) -> Mapping[str, Any]:
+ if not isinstance(value, Mapping):
+ _fail(path, "must be an object")
+ return value
+
+
+def _sequence(value: Any, path: str) -> list[Any]:
+ if not isinstance(value, list):
+ _fail(path, "must be an array")
+ return value
+
+
+def _string(value: Any, path: str, *, nullable: bool = False) -> str | None:
+ if nullable and value is None:
+ return None
+ if not isinstance(value, str) or not value.strip():
+ _fail(path, "must be a non-empty string")
+ return value
+
+
+def _string_list(value: Any, path: str) -> list[str]:
+ items = _sequence(value, path)
+ for index, item in enumerate(items):
+ _string(item, f"{path}[{index}]")
+ if len(items) != len(set(items)):
+ _fail(path, "must not contain duplicates")
+ return items
+
+
+def _exact_keys(
+ value: Mapping[str, Any],
+ path: str,
+ *,
+ required: set[str],
+ optional: set[str] | None = None,
+) -> None:
+ optional = optional or set()
+ missing = sorted(required - set(value))
+ unknown = sorted(set(value) - required - optional)
+ if missing:
+ _fail(path, f"missing fields: {', '.join(missing)}")
+ if unknown:
+ _fail(path, f"unsupported fields: {', '.join(unknown)}")
+
+
+def _enum(value: Any, allowed: frozenset[str], path: str) -> str:
+ text = _string(value, path)
+ assert text is not None
+ if text not in allowed:
+ _fail(path, f"unsupported value {text!r}")
+ return text
+
+
+def _nonnegative_integer(value: Any, path: str) -> int:
+ if isinstance(value, bool) or not isinstance(value, int) or value < 0:
+ _fail(path, "must be a non-negative integer")
+ return value
+
+
+def _reject_floats(value: Any, path: str = "$", *, seen: set[int] | None = None) -> None:
+ if isinstance(value, float):
+ _fail(path, "floating-point values are not canonical in this profile")
+ if isinstance(value, Mapping):
+ seen = seen or set()
+ identity = id(value)
+ if identity in seen:
+ _fail(path, "cyclic objects cannot be serialized")
+ seen.add(identity)
+ for key, item in value.items():
+ if not isinstance(key, str):
+ _fail(path, "object keys must be strings")
+ _reject_floats(item, f"{path}.{key}", seen=seen)
+ seen.remove(identity)
+ elif isinstance(value, (list, tuple)):
+ seen = seen or set()
+ identity = id(value)
+ if identity in seen:
+ _fail(path, "cyclic arrays cannot be serialized")
+ seen.add(identity)
+ for index, item in enumerate(value):
+ _reject_floats(item, f"{path}[{index}]", seen=seen)
+ seen.remove(identity)
+ elif value is not None and not isinstance(value, (str, int, bool)):
+ _fail(path, f"unsupported canonical type {type(value).__name__}")
+
+
+def canonical_bytes(payload: Any) -> bytes:
+ """Return deterministic UTF-8 JSON bytes after rejecting ambiguous numeric input."""
+
+ _reject_floats(payload)
+ return json.dumps(
+ payload,
+ ensure_ascii=True,
+ separators=(",", ":"),
+ sort_keys=True,
+ ).encode("utf-8")
+
+
+def manifest_digest(payload: Mapping[str, Any]) -> str:
+ """Digest every manifest field except the digest that seals those fields."""
+
+ stable = dict(payload)
+ stable.pop("manifest_digest", None)
+ return "sha256:" + hashlib.sha256(canonical_bytes(stable)).hexdigest()
+
+
+def seal_manifest(payload: Mapping[str, Any]) -> dict[str, Any]:
+ """Deep-copy and seal a manifest without mutating the caller's object."""
+
+ sealed = copy.deepcopy(dict(payload))
+ sealed["manifest_digest"] = manifest_digest(sealed)
+ validate_manifest(sealed)
+ return sealed
+
+
+def _register_id(identifier: Any, path: str, ids: dict[str, str]) -> str:
+ text = _string(identifier, path)
+ assert text is not None
+ if text in ids:
+ _fail(path, f"duplicates {ids[text]}")
+ ids[text] = path
+ return text
+
+
+def _validate_artifact(value: Any, index: int, ids: dict[str, str]) -> str:
+ path = f"$.artifacts[{index}]"
+ item = _mapping(value, path)
+ _exact_keys(
+ item,
+ path,
+ required={"id", "role", "media_type", "digest", "locator"},
+ )
+ identifier = _register_id(item["id"], f"{path}.id", ids)
+ _string(item["role"], f"{path}.role")
+ _string(item["media_type"], f"{path}.media_type")
+ digest = _string(item["digest"], f"{path}.digest")
+ assert digest is not None
+ if not _DIGEST_RE.fullmatch(digest):
+ _fail(f"{path}.digest", "must be lowercase sha256:<64 hex>")
+ locator = _string(item["locator"], f"{path}.locator")
+ assert locator is not None
+ if not locator.startswith(_PORTABLE_LOCATOR_PREFIXES):
+ _fail(
+ f"{path}.locator",
+ "must use artifact:, git:, https://, repo:, or urn: coordinates",
+ )
+ if locator.startswith("repo:"):
+ relative = locator.removeprefix("repo:")
+ candidate = PurePosixPath(relative)
+ if (
+ not relative
+ or "\\" in relative
+ or candidate.is_absolute()
+ or ".." in candidate.parts
+ or "." in candidate.parts
+ ):
+ _fail(f"{path}.locator", "repo: coordinates must be normalized repository-relative paths")
+ return identifier
+
+
+def _validate_substrate(value: Any, path: str) -> None:
+ item = _mapping(value, path)
+ _exact_keys(item, path, required={"kind", "name", "version", "coordinate"})
+ for field in ("kind", "name", "version", "coordinate"):
+ _string(item[field], f"{path}.{field}")
+
+
+def _validate_mapping(value: Any, path: str) -> None:
+ item = _mapping(value, path)
+ _exact_keys(
+ item,
+ path,
+ required={"status", "vstd_verdict", "mapping_profile", "receipt_artifact_id", "reason"},
+ )
+ state = _enum(item["status"], MAPPING_STATES, f"{path}.status")
+ verdict = _string(item["vstd_verdict"], f"{path}.vstd_verdict", nullable=True)
+ mapping_profile = _string(item["mapping_profile"], f"{path}.mapping_profile", nullable=True)
+ receipt_id = _string(item["receipt_artifact_id"], f"{path}.receipt_artifact_id", nullable=True)
+ _string(item["reason"], f"{path}.reason")
+ if state == "NOT_EVALUATED":
+ if any(value is not None for value in (verdict, mapping_profile, receipt_id)):
+ _fail(path, "NOT_EVALUATED cannot carry a VSTD verdict, profile, or receipt")
+ else:
+ if verdict not in VSTD_VERDICTS:
+ _fail(f"{path}.vstd_verdict", "MAPPED requires an explicit VSTD verdict")
+ if mapping_profile is None or receipt_id is None:
+ _fail(path, "MAPPED requires a mapping profile and receipt artifact")
+
+
+def _validate_action_graph(actions: Mapping[str, list[str]]) -> None:
+ visiting: set[str] = set()
+ visited: set[str] = set()
+
+ def visit(identifier: str) -> None:
+ if identifier in visiting:
+ _fail("$.actions", f"dependency cycle includes {identifier!r}")
+ if identifier in visited:
+ return
+ visiting.add(identifier)
+ for dependency in actions[identifier]:
+ if dependency not in actions:
+ _fail("$.actions", f"{identifier!r} depends on unknown action {dependency!r}")
+ visit(dependency)
+ visiting.remove(identifier)
+ visited.add(identifier)
+
+ for identifier in actions:
+ visit(identifier)
+
+
+def _validate_references(references: list[tuple[str, str]], ids: Mapping[str, str]) -> None:
+ for path, target in references:
+ if target not in ids:
+ _fail(path, f"references unknown id {target!r}")
+
+
+def validate_manifest(payload: Mapping[str, Any], *, verify_digest: bool = True) -> None:
+ """Validate syntax, references, bounds, and non-upgrade invariants.
+
+ Validation says that the workflow record is internally well-formed. It does not
+ verify any referenced artifact, native result, hypothesis, or VSTD receipt.
+ """
+
+ root = _mapping(payload, "$")
+ _reject_floats(root)
+ _exact_keys(
+ root,
+ "$",
+ required={
+ "profile",
+ "experiment",
+ "hypotheses",
+ "preregistration",
+ "artifacts",
+ "budgets",
+ "actions",
+ "observations",
+ "interventions",
+ "native_results",
+ "adaptations",
+ "amendments",
+ "challenges",
+ "horizons",
+ "publication",
+ "workflow_events",
+ "manifest_digest",
+ },
+ )
+
+ profile = _mapping(root["profile"], "$.profile")
+ _exact_keys(profile, "$.profile", required={"id", "version", "status"})
+ if profile["id"] != PROFILE_ID or profile["version"] != PROFILE_VERSION:
+ _fail("$.profile", "unsupported profile identifier or version")
+ if profile["status"] != PROFILE_STATUS:
+ _fail("$.profile.status", f"must be {PROFILE_STATUS}")
+
+ ids: dict[str, str] = {}
+ references: list[tuple[str, str]] = []
+
+ experiment = _mapping(root["experiment"], "$.experiment")
+ _exact_keys(
+ experiment,
+ "$.experiment",
+ required={"id", "title", "question", "state", "started_at"},
+ )
+ _register_id(experiment["id"], "$.experiment.id", ids)
+ _string(experiment["title"], "$.experiment.title")
+ _string(experiment["question"], "$.experiment.question")
+ _enum(experiment["state"], EXPERIMENT_STATES, "$.experiment.state")
+ _string(experiment["started_at"], "$.experiment.started_at", nullable=True)
+
+ hypotheses = _sequence(root["hypotheses"], "$.hypotheses")
+ if not hypotheses:
+ _fail("$.hypotheses", "must declare at least one falsifiable hypothesis")
+ for index, value in enumerate(hypotheses):
+ path = f"$.hypotheses[{index}]"
+ item = _mapping(value, path)
+ _exact_keys(
+ item,
+ path,
+ required={"id", "statement", "falsification_condition", "state"},
+ )
+ _register_id(item["id"], f"{path}.id", ids)
+ _string(item["statement"], f"{path}.statement")
+ _string(item["falsification_condition"], f"{path}.falsification_condition")
+ _enum(item["state"], HYPOTHESIS_STATES, f"{path}.state")
+
+ preregistration = _mapping(root["preregistration"], "$.preregistration")
+ _exact_keys(
+ preregistration,
+ "$.preregistration",
+ required={"state", "recorded_at", "artifact_id", "limitations"},
+ )
+ preregistration_state = _enum(
+ preregistration["state"], PREREGISTRATION_STATES, "$.preregistration.state"
+ )
+ _string(preregistration["recorded_at"], "$.preregistration.recorded_at", nullable=True)
+ preregistration_artifact = _string(
+ preregistration["artifact_id"], "$.preregistration.artifact_id", nullable=True
+ )
+ _string_list(preregistration["limitations"], "$.preregistration.limitations")
+ if preregistration_state in {"FROZEN", "AMENDED"} and preregistration_artifact is None:
+ _fail("$.preregistration", "FROZEN or AMENDED requires a bound artifact")
+ if preregistration_artifact is not None:
+ references.append(("$.preregistration.artifact_id", preregistration_artifact))
+
+ artifact_ids = {
+ _validate_artifact(value, index, ids)
+ for index, value in enumerate(_sequence(root["artifacts"], "$.artifacts"))
+ }
+
+ budget_ids: set[str] = set()
+ for index, value in enumerate(_sequence(root["budgets"], "$.budgets")):
+ path = f"$.budgets[{index}]"
+ item = _mapping(value, path)
+ _exact_keys(
+ item,
+ path,
+ required={"id", "resource", "limit", "consumed", "unit", "scope"},
+ )
+ identifier = _register_id(item["id"], f"{path}.id", ids)
+ budget_ids.add(identifier)
+ _string(item["resource"], f"{path}.resource")
+ limit = _nonnegative_integer(item["limit"], f"{path}.limit")
+ consumed = _nonnegative_integer(item["consumed"], f"{path}.consumed")
+ if consumed > limit:
+ _fail(path, "consumed work exceeds the declared limit")
+ _string(item["unit"], f"{path}.unit")
+ _string(item["scope"], f"{path}.scope")
+
+ action_dependencies: dict[str, list[str]] = {}
+ action_ids: set[str] = set()
+ for index, value in enumerate(_sequence(root["actions"], "$.actions")):
+ path = f"$.actions[{index}]"
+ item = _mapping(value, path)
+ _exact_keys(
+ item,
+ path,
+ required={
+ "id",
+ "kind",
+ "target",
+ "state",
+ "priority",
+ "selected_because",
+ "selection_evidence_ids",
+ "alternatives_considered",
+ "budget_ids",
+ "depends_on",
+ "triggered_by",
+ "expected_artifact_effect",
+ "substrate",
+ "native_result_ids",
+ "produced_artifact_ids",
+ },
+ )
+ identifier = _register_id(item["id"], f"{path}.id", ids)
+ action_ids.add(identifier)
+ _string(item["kind"], f"{path}.kind")
+ _string(item["target"], f"{path}.target")
+ _enum(item["state"], ACTION_STATES, f"{path}.state")
+ priority = _nonnegative_integer(item["priority"], f"{path}.priority")
+ if priority == 0:
+ _fail(f"{path}.priority", "must be at least 1")
+ _string(item["selected_because"], f"{path}.selected_because")
+ _string(item["expected_artifact_effect"], f"{path}.expected_artifact_effect")
+ _validate_substrate(item["substrate"], f"{path}.substrate")
+ selection_evidence = _string_list(
+ item["selection_evidence_ids"], f"{path}.selection_evidence_ids"
+ )
+ _string_list(item["alternatives_considered"], f"{path}.alternatives_considered")
+ action_budgets = _string_list(item["budget_ids"], f"{path}.budget_ids")
+ if not action_budgets:
+ _fail(f"{path}.budget_ids", "every selected action must bind at least one budget")
+ unknown_budgets = sorted(set(action_budgets) - budget_ids)
+ if unknown_budgets:
+ _fail(f"{path}.budget_ids", f"unknown budgets: {', '.join(unknown_budgets)}")
+ dependencies = _string_list(item["depends_on"], f"{path}.depends_on")
+ action_dependencies[identifier] = dependencies
+ for field in ("triggered_by", "native_result_ids", "produced_artifact_ids"):
+ values = _string_list(item[field], f"{path}.{field}")
+ references.extend((f"{path}.{field}", target) for target in values)
+ references.extend(
+ (f"{path}.selection_evidence_ids", target) for target in selection_evidence
+ )
+
+ observation_ids: set[str] = set()
+ for index, value in enumerate(_sequence(root["observations"], "$.observations")):
+ path = f"$.observations[{index}]"
+ item = _mapping(value, path)
+ _exact_keys(
+ item,
+ path,
+ required={
+ "id",
+ "action_id",
+ "recorded_at",
+ "statement",
+ "status",
+ "evidence_artifact_ids",
+ "limitations",
+ },
+ )
+ identifier = _register_id(item["id"], f"{path}.id", ids)
+ observation_ids.add(identifier)
+ action_id = _string(item["action_id"], f"{path}.action_id")
+ assert action_id is not None
+ references.append((f"{path}.action_id", action_id))
+ _string(item["recorded_at"], f"{path}.recorded_at")
+ _string(item["statement"], f"{path}.statement")
+ _enum(item["status"], OBSERVATION_STATES, f"{path}.status")
+ evidence_ids = _string_list(
+ item["evidence_artifact_ids"], f"{path}.evidence_artifact_ids"
+ )
+ references.extend((f"{path}.evidence_artifact_ids", target) for target in evidence_ids)
+ _string_list(item["limitations"], f"{path}.limitations")
+
+ for index, value in enumerate(_sequence(root["interventions"], "$.interventions")):
+ path = f"$.interventions[{index}]"
+ item = _mapping(value, path)
+ _exact_keys(
+ item,
+ path,
+ required={
+ "id",
+ "action_id",
+ "description",
+ "applied_at",
+ "target_artifact_ids",
+ "produced_artifact_ids",
+ },
+ )
+ _register_id(item["id"], f"{path}.id", ids)
+ action_id = _string(item["action_id"], f"{path}.action_id")
+ assert action_id is not None
+ references.append((f"{path}.action_id", action_id))
+ _string(item["description"], f"{path}.description")
+ _string(item["applied_at"], f"{path}.applied_at")
+ for field in ("target_artifact_ids", "produced_artifact_ids"):
+ values = _string_list(item[field], f"{path}.{field}")
+ references.extend((f"{path}.{field}", target) for target in values)
+
+ native_result_ids: set[str] = set()
+ for index, value in enumerate(_sequence(root["native_results"], "$.native_results")):
+ path = f"$.native_results[{index}]"
+ item = _mapping(value, path)
+ _exact_keys(
+ item,
+ path,
+ required={"id", "action_id", "verifier", "native_status", "result_artifact_id", "mapping"},
+ )
+ identifier = _register_id(item["id"], f"{path}.id", ids)
+ native_result_ids.add(identifier)
+ action_id = _string(item["action_id"], f"{path}.action_id")
+ assert action_id is not None
+ references.append((f"{path}.action_id", action_id))
+ _validate_substrate(item["verifier"], f"{path}.verifier")
+ _string(item["native_status"], f"{path}.native_status")
+ result_artifact = _string(
+ item["result_artifact_id"], f"{path}.result_artifact_id", nullable=True
+ )
+ if result_artifact is not None:
+ references.append((f"{path}.result_artifact_id", result_artifact))
+ _validate_mapping(item["mapping"], f"{path}.mapping")
+ mapped_receipt = item["mapping"]["receipt_artifact_id"]
+ if mapped_receipt is not None:
+ references.append((f"{path}.mapping.receipt_artifact_id", mapped_receipt))
+
+ for index, value in enumerate(_sequence(root["adaptations"], "$.adaptations")):
+ path = f"$.adaptations[{index}]"
+ item = _mapping(value, path)
+ _exact_keys(
+ item,
+ path,
+ required={"id", "trigger_ids", "decision", "reason", "action_ids", "artifact_ids"},
+ )
+ _register_id(item["id"], f"{path}.id", ids)
+ _string(item["decision"], f"{path}.decision")
+ _string(item["reason"], f"{path}.reason")
+ for field in ("trigger_ids", "action_ids", "artifact_ids"):
+ values = _string_list(item[field], f"{path}.{field}")
+ references.extend((f"{path}.{field}", target) for target in values)
+
+ for collection_name, required, enum_field, allowed in (
+ (
+ "amendments",
+ {"id", "recorded_at", "reason", "supersedes", "artifact_id"},
+ None,
+ None,
+ ),
+ (
+ "challenges",
+ {"id", "target_id", "state", "statement", "evidence_artifact_ids"},
+ "state",
+ CHALLENGE_STATES,
+ ),
+ (
+ "horizons",
+ {"id", "status", "description", "reason"},
+ "status",
+ HORIZON_STATES,
+ ),
+ ):
+ for index, value in enumerate(_sequence(root[collection_name], f"$.{collection_name}")):
+ path = f"$.{collection_name}[{index}]"
+ item = _mapping(value, path)
+ _exact_keys(item, path, required=required)
+ _register_id(item["id"], f"{path}.id", ids)
+ if enum_field is not None and allowed is not None:
+ _enum(item[enum_field], allowed, f"{path}.{enum_field}")
+ if collection_name == "amendments":
+ _string(item["recorded_at"], f"{path}.recorded_at")
+ _string(item["reason"], f"{path}.reason")
+ supersedes = _string_list(item["supersedes"], f"{path}.supersedes")
+ references.extend((f"{path}.supersedes", target) for target in supersedes)
+ artifact_id = _string(item["artifact_id"], f"{path}.artifact_id")
+ assert artifact_id is not None
+ references.append((f"{path}.artifact_id", artifact_id))
+ elif collection_name == "challenges":
+ target_id = _string(item["target_id"], f"{path}.target_id")
+ assert target_id is not None
+ references.append((f"{path}.target_id", target_id))
+ _string(item["statement"], f"{path}.statement")
+ evidence_ids = _string_list(
+ item["evidence_artifact_ids"], f"{path}.evidence_artifact_ids"
+ )
+ references.extend(
+ (f"{path}.evidence_artifact_ids", target) for target in evidence_ids
+ )
+ else:
+ _string(item["description"], f"{path}.description")
+ _string(item["reason"], f"{path}.reason")
+
+ publication = _mapping(root["publication"], "$.publication")
+ _exact_keys(publication, "$.publication", required={"state", "artifact_ids"})
+ _enum(publication["state"], PUBLICATION_STATES, "$.publication.state")
+ publication_artifacts = _string_list(publication["artifact_ids"], "$.publication.artifact_ids")
+ references.extend(("$.publication.artifact_ids", target) for target in publication_artifacts)
+
+ event_ids: set[str] = set()
+ for index, value in enumerate(_sequence(root["workflow_events"], "$.workflow_events")):
+ path = f"$.workflow_events[{index}]"
+ item = _mapping(value, path)
+ _exact_keys(
+ item,
+ path,
+ required={
+ "id",
+ "kind",
+ "recorded_at",
+ "source",
+ "native_state",
+ "verification_effect",
+ "details",
+ },
+ )
+ identifier = _register_id(item["id"], f"{path}.id", ids)
+ event_ids.add(identifier)
+ _enum(item["kind"], PLATFORM_EVENT_KINDS, f"{path}.kind")
+ _string(item["recorded_at"], f"{path}.recorded_at")
+ _string(item["native_state"], f"{path}.native_state")
+ if item["verification_effect"] != "NONE":
+ _fail(f"{path}.verification_effect", "platform events cannot grant a verification verdict")
+ source = _mapping(item["source"], f"{path}.source")
+ _exact_keys(source, f"{path}.source", required={"platform", "repository", "coordinate"})
+ for field in ("platform", "repository", "coordinate"):
+ _string(source[field], f"{path}.source.{field}")
+ details = _mapping(item["details"], f"{path}.details")
+ canonical_bytes(details)
+
+ _validate_action_graph(action_dependencies)
+ _validate_references(references, ids)
+
+ for index, action in enumerate(root["actions"]):
+ unknown_results = sorted(set(action["native_result_ids"]) - native_result_ids)
+ if unknown_results:
+ _fail(
+ f"$.actions[{index}].native_result_ids",
+ f"not native results: {', '.join(unknown_results)}",
+ )
+ unknown_artifacts = sorted(set(action["produced_artifact_ids"]) - artifact_ids)
+ if unknown_artifacts:
+ _fail(
+ f"$.actions[{index}].produced_artifact_ids",
+ f"not artifacts: {', '.join(unknown_artifacts)}",
+ )
+ for index, result in enumerate(root["native_results"]):
+ if result["action_id"] not in action_ids:
+ _fail(f"$.native_results[{index}].action_id", "must reference an action")
+ for index, observation in enumerate(root["observations"]):
+ if observation["action_id"] not in action_ids:
+ _fail(f"$.observations[{index}].action_id", "must reference an action")
+ for index, intervention in enumerate(root["interventions"]):
+ if intervention["action_id"] not in action_ids:
+ _fail(f"$.interventions[{index}].action_id", "must reference an action")
+
+ digest = _string(root["manifest_digest"], "$.manifest_digest")
+ assert digest is not None
+ if not _DIGEST_RE.fullmatch(digest):
+ _fail("$.manifest_digest", "must be lowercase sha256:<64 hex>")
+ if verify_digest and digest != manifest_digest(root):
+ _fail("$.manifest_digest", "does not match the canonical stable payload")
+
+
+def load_manifest(path: Path) -> dict[str, Any]:
+ """Load and validate a UTF-8 JSON workflow manifest."""
+
+ try:
+ payload = json.loads(path.read_text(encoding="utf-8"))
+ except (OSError, UnicodeError, json.JSONDecodeError) as exc:
+ raise WorkflowProfileError(f"cannot load {path.name}: {exc}") from exc
+ if not isinstance(payload, dict):
+ raise WorkflowProfileError("workflow manifest root must be an object")
+ validate_manifest(payload)
+ return payload
+
+
+def verify_repo_artifacts(payload: Mapping[str, Any], repository_root: Path) -> None:
+ """Verify every repository-relative artifact against its bound SHA-256 digest.
+
+ Other locator schemes require their own retriever and trust policy. Skipping those
+ schemes here does not verify them and does not change any recorded result.
+ """
+
+ validate_manifest(payload)
+ root = repository_root.resolve()
+ for index, artifact in enumerate(payload["artifacts"]):
+ locator = artifact["locator"]
+ if not locator.startswith("repo:"):
+ continue
+ relative = PurePosixPath(locator.removeprefix("repo:"))
+ candidate = root.joinpath(*relative.parts).resolve()
+ try:
+ candidate.relative_to(root)
+ except ValueError as exc:
+ _fail(f"$.artifacts[{index}].locator", "resolves outside the repository")
+ raise AssertionError("unreachable") from exc
+ if not candidate.is_file():
+ _fail(f"$.artifacts[{index}].locator", "bound repository artifact is missing")
+ actual = "sha256:" + hashlib.sha256(candidate.read_bytes()).hexdigest()
+ if actual != artifact["digest"]:
+ _fail(
+ f"$.artifacts[{index}].digest",
+ f"does not match {locator}; expected {artifact['digest']}, observed {actual}",
+ )
diff --git a/src/verifier/experimental_workflow/schema.py b/src/verifier/experimental_workflow/schema.py
new file mode 100644
index 0000000..11dd620
--- /dev/null
+++ b/src/verifier/experimental_workflow/schema.py
@@ -0,0 +1,276 @@
+"""Terminology: identifier (ID); JavaScript Object Notation (JSON); Verifier Standard (VSTD).
+
+JSON Schema generator for the experimental workflow interchange profile."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from .profile import PROFILE_ID, PROFILE_STATUS, PROFILE_VERSION
+
+
+def _object(properties: dict[str, Any], required: tuple[str, ...] | None = None) -> dict[str, Any]:
+ return {
+ "type": "object",
+ "additionalProperties": False,
+ "properties": properties,
+ "required": list(required or properties),
+ }
+
+
+def _array(items: dict[str, Any], *, minimum: int = 0) -> dict[str, Any]:
+ schema: dict[str, Any] = {"type": "array", "items": items}
+ if minimum:
+ schema["minItems"] = minimum
+ return schema
+
+
+def workflow_manifest_schema() -> dict[str, Any]:
+ """Return the complete draft-2020-12 interchange schema."""
+
+ nonempty = {"type": "string", "minLength": 1}
+ nullable_nonempty = {"type": ["string", "null"], "minLength": 1}
+ identifier = {"type": "string", "minLength": 1, "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"}
+ identifier_list = _array(identifier)
+ digest = {"type": "string", "pattern": "^sha256:[0-9a-f]{64}$"}
+ substrate = _object(
+ {
+ "kind": nonempty,
+ "name": nonempty,
+ "version": nonempty,
+ "coordinate": nonempty,
+ }
+ )
+ artifact = _object(
+ {
+ "id": identifier,
+ "role": nonempty,
+ "media_type": nonempty,
+ "digest": digest,
+ "locator": {
+ "type": "string",
+ "pattern": "^(artifact:|git:|https://|repo:|urn:).+",
+ },
+ }
+ )
+ mapping = _object(
+ {
+ "status": {"enum": ["NOT_EVALUATED", "MAPPED"]},
+ "vstd_verdict": {
+ "type": ["string", "null"],
+ "enum": ["PASS", "FAIL", "UNKNOWN", "CONFLICTED", "REJECTED", None],
+ },
+ "mapping_profile": nullable_nonempty,
+ "receipt_artifact_id": nullable_nonempty,
+ "reason": nonempty,
+ }
+ )
+ platform_event = _object(
+ {
+ "id": identifier,
+ "kind": {
+ "enum": [
+ "PLATFORM_ISSUE",
+ "PLATFORM_COMMIT",
+ "PLATFORM_WORKFLOW_RUN",
+ "PLATFORM_ARTIFACT",
+ "PLATFORM_PULL_REQUEST",
+ ]
+ },
+ "recorded_at": nonempty,
+ "source": _object(
+ {"platform": nonempty, "repository": nonempty, "coordinate": nonempty}
+ ),
+ "native_state": nonempty,
+ "verification_effect": {"const": "NONE"},
+ "details": {"type": "object"},
+ }
+ )
+
+ return {
+ "$comment": "Terminology: Verifier Standard (VSTD).",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://timelordraps.github.io/verifier/profiles/experimental-workflow.schema.json",
+ "title": "VSTD experimental workflow profile 0.1",
+ "description": (
+ "Non-normative, verdict-neutral interchange for bounded experimental work. "
+ "Schema validity does not verify referenced evidence or native results."
+ ),
+ **_object(
+ {
+ "profile": _object(
+ {
+ "id": {"const": PROFILE_ID},
+ "version": {"const": PROFILE_VERSION},
+ "status": {"const": PROFILE_STATUS},
+ }
+ ),
+ "experiment": _object(
+ {
+ "id": identifier,
+ "title": nonempty,
+ "question": nonempty,
+ "state": {
+ "enum": [
+ "DRAFT",
+ "PREREGISTERED",
+ "RUNNING",
+ "BLOCKED",
+ "COMPLETED",
+ "ABANDONED",
+ ]
+ },
+ "started_at": nullable_nonempty,
+ }
+ ),
+ "hypotheses": _array(
+ _object(
+ {
+ "id": identifier,
+ "statement": nonempty,
+ "falsification_condition": nonempty,
+ "state": {
+ "enum": ["OPEN", "SUPPORTED", "REFUTED", "UNKNOWN", "CONFLICTED"]
+ },
+ }
+ ),
+ minimum=1,
+ ),
+ "preregistration": _object(
+ {
+ "state": {"enum": ["NONE", "DRAFT", "FROZEN", "AMENDED"]},
+ "recorded_at": nullable_nonempty,
+ "artifact_id": nullable_nonempty,
+ "limitations": _array(nonempty),
+ }
+ ),
+ "artifacts": _array(artifact),
+ "budgets": _array(
+ _object(
+ {
+ "id": identifier,
+ "resource": nonempty,
+ "limit": {"type": "integer", "minimum": 0},
+ "consumed": {"type": "integer", "minimum": 0},
+ "unit": nonempty,
+ "scope": nonempty,
+ }
+ )
+ ),
+ "actions": _array(
+ _object(
+ {
+ "id": identifier,
+ "kind": nonempty,
+ "target": nonempty,
+ "state": {
+ "enum": ["PLANNED", "RUNNING", "BLOCKED", "COMPLETED", "ABANDONED"]
+ },
+ "priority": {"type": "integer", "minimum": 1},
+ "selected_because": nonempty,
+ "selection_evidence_ids": identifier_list,
+ "alternatives_considered": _array(nonempty),
+ "budget_ids": _array(identifier, minimum=1),
+ "depends_on": identifier_list,
+ "triggered_by": identifier_list,
+ "expected_artifact_effect": nonempty,
+ "substrate": substrate,
+ "native_result_ids": identifier_list,
+ "produced_artifact_ids": identifier_list,
+ }
+ )
+ ),
+ "observations": _array(
+ _object(
+ {
+ "id": identifier,
+ "action_id": identifier,
+ "recorded_at": nonempty,
+ "statement": nonempty,
+ "status": {"enum": ["OBSERVED", "UNKNOWN", "CONFLICTED"]},
+ "evidence_artifact_ids": identifier_list,
+ "limitations": _array(nonempty),
+ }
+ )
+ ),
+ "interventions": _array(
+ _object(
+ {
+ "id": identifier,
+ "action_id": identifier,
+ "description": nonempty,
+ "applied_at": nonempty,
+ "target_artifact_ids": identifier_list,
+ "produced_artifact_ids": identifier_list,
+ }
+ )
+ ),
+ "native_results": _array(
+ _object(
+ {
+ "id": identifier,
+ "action_id": identifier,
+ "verifier": substrate,
+ "native_status": nonempty,
+ "result_artifact_id": nullable_nonempty,
+ "mapping": mapping,
+ }
+ )
+ ),
+ "adaptations": _array(
+ _object(
+ {
+ "id": identifier,
+ "trigger_ids": identifier_list,
+ "decision": nonempty,
+ "reason": nonempty,
+ "action_ids": identifier_list,
+ "artifact_ids": identifier_list,
+ }
+ )
+ ),
+ "amendments": _array(
+ _object(
+ {
+ "id": identifier,
+ "recorded_at": nonempty,
+ "reason": nonempty,
+ "supersedes": identifier_list,
+ "artifact_id": identifier,
+ }
+ )
+ ),
+ "challenges": _array(
+ _object(
+ {
+ "id": identifier,
+ "target_id": identifier,
+ "state": {"enum": ["OPEN", "RESOLVED", "REJECTED"]},
+ "statement": nonempty,
+ "evidence_artifact_ids": identifier_list,
+ }
+ )
+ ),
+ "horizons": _array(
+ _object(
+ {
+ "id": identifier,
+ "status": {"enum": ["UNKNOWN", "CONFLICTED", "BLOCKED", "OUT_OF_SCOPE"]},
+ "description": nonempty,
+ "reason": nonempty,
+ }
+ )
+ ),
+ "publication": _object(
+ {
+ "state": {
+ "enum": ["PRIVATE", "INTERNAL", "CANDIDATE", "PUBLISHED", "RETRACTED"]
+ },
+ "artifact_ids": identifier_list,
+ }
+ ),
+ "workflow_events": _array(platform_event),
+ "manifest_digest": digest,
+ }
+ ),
+ }
diff --git a/src/verifier/hardware/__init__.py b/src/verifier/hardware/__init__.py
index af906bc..8db00e6 100644
--- a/src/verifier/hardware/__init__.py
+++ b/src/verifier/hardware/__init__.py
@@ -1,4 +1,6 @@
-"""VSTD 3 accelerator-accountability reference implementation."""
+"""Terminology: Verifier Standard (VSTD).
+
+VSTD 3 accelerator-accountability reference implementation."""
from .conformance import ConformanceProfile, evaluate_conformance
from .emulator import VirtualVSTDAccelerator
diff --git a/src/verifier/hardware/adapters/__init__.py b/src/verifier/hardware/adapters/__init__.py
index dffde60..5f0584c 100644
--- a/src/verifier/hardware/adapters/__init__.py
+++ b/src/verifier/hardware/adapters/__init__.py
@@ -1,4 +1,6 @@
-"""Built-in VSTD 3 evidence adapters."""
+"""Terminology: Verifier Standard (VSTD).
+
+Built-in VSTD 3 evidence adapters."""
from .amd import AmdAdapter
from .base import AdapterError, EvidenceAdapter
diff --git a/src/verifier/hardware/adapters/amd.py b/src/verifier/hardware/adapters/amd.py
index 9d4f22a..8175d48 100644
--- a/src/verifier/hardware/adapters/amd.py
+++ b/src/verifier/hardware/adapters/amd.py
@@ -1,4 +1,7 @@
-"""AMD SMI/ROCm discovery and offline evidence normalization."""
+"""Terminology: Advanced Micro Devices (AMD); application-specific integrated circuit (ASIC);
+JavaScript Object Notation (JSON); system management interface (SMI); Verifier Standard (VSTD).
+
+AMD SMI/ROCm discovery and offline evidence normalization."""
from __future__ import annotations
diff --git a/src/verifier/hardware/adapters/generic.py b/src/verifier/hardware/adapters/generic.py
index ea07028..7481430 100644
--- a/src/verifier/hardware/adapters/generic.py
+++ b/src/verifier/hardware/adapters/generic.py
@@ -1,4 +1,6 @@
-"""Registry-driven generic fixture adapter for unknown and future accelerators."""
+"""Terminology: JavaScript Object Notation (JSON); Verifier Standard (VSTD).
+
+Registry-driven generic fixture adapter for unknown and future accelerators."""
from __future__ import annotations
diff --git a/src/verifier/hardware/adapters/nvidia.py b/src/verifier/hardware/adapters/nvidia.py
index 3287b6e..660c0bc 100644
--- a/src/verifier/hardware/adapters/nvidia.py
+++ b/src/verifier/hardware/adapters/nvidia.py
@@ -1,4 +1,8 @@
-"""NVIDIA NVML/nvidia-smi discovery and offline evidence normalization."""
+"""Terminology: JavaScript Object Notation (JSON); NVIDIA Management Library (NVML);
+Reference Integrity Manifest (RIM); Security Protocol and Data Model (SPDM);
+Verifier Standard (VSTD).
+
+NVIDIA NVML/nvidia-smi discovery and offline evidence normalization."""
from __future__ import annotations
diff --git a/src/verifier/hardware/adapters/provider.py b/src/verifier/hardware/adapters/provider.py
index f3e63ce..478db06 100644
--- a/src/verifier/hardware/adapters/provider.py
+++ b/src/verifier/hardware/adapters/provider.py
@@ -1,4 +1,6 @@
-"""Cloud/provider control-plane evidence kept separate from hardware attestation."""
+"""Terminology: JavaScript Object Notation (JSON); Verifier Standard (VSTD).
+
+Cloud/provider control-plane evidence kept separate from hardware attestation."""
from __future__ import annotations
diff --git a/src/verifier/hardware/anchors.py b/src/verifier/hardware/anchors.py
index 157a747..578315e 100644
--- a/src/verifier/hardware/anchors.py
+++ b/src/verifier/hardware/anchors.py
@@ -1,4 +1,6 @@
-"""External continuity-anchor interfaces and deterministic local implementations."""
+"""Terminology: JavaScript Object Notation (JSON); JSON Lines (JSONL); Verifier Standard (VSTD).
+
+External continuity-anchor interfaces and deterministic local implementations."""
from __future__ import annotations
diff --git a/src/verifier/hardware/attestation.py b/src/verifier/hardware/attestation.py
index 57e2029..91a9cdb 100644
--- a/src/verifier/hardware/attestation.py
+++ b/src/verifier/hardware/attestation.py
@@ -1,4 +1,11 @@
-"""Canonical binding and independent verification for VSTD 3 attestations."""
+"""Terminology: hash-based message authentication code (HMAC);
+Security Protocol and Data Model (SPDM); Verifier Standard (VSTD).
+
+Canonical binding and verifier-side recomputation for VSTD 3 attestations.
+
+The legacy public function name ``independently_verify_attestation`` denotes recomputation
+from supplied evidence rather than trust in a collector's status field. It does not
+establish distinct producer and checker actors under VSTD-1."""
from __future__ import annotations
@@ -17,7 +24,7 @@ def attestation_signed_payload(evidence: AttestationEvidence) -> dict[str, objec
"""Return every semantic attestation field covered by its signature.
``signature`` and the collector's ``verification_state`` are deliberately not
- self-authenticating inputs. The latter is independently recomputed by a verifier.
+ self-authenticating inputs. The latter is recomputed by the verifier.
"""
return {
@@ -50,8 +57,9 @@ def independently_verify_attestation(
"""Recompute verification state for algorithms implemented by the core.
The reference package implements only its explicitly test-only HMAC envelope.
- Vendor/SPDM evidence must be verified by an adapter that supplies an independently
- checked result; unknown algorithms remain NOT_VERIFIED rather than being guessed.
+ Vendor/SPDM evidence must be verified by an adapter that supplies a mechanism-checked
+ result; unknown algorithms remain NOT_VERIFIED rather than being guessed. This check
+ does not establish distinct actors.
"""
signature = evidence.signature
diff --git a/src/verifier/hardware/canonical.py b/src/verifier/hardware/canonical.py
index 5458912..82ad336 100644
--- a/src/verifier/hardware/canonical.py
+++ b/src/verifier/hardware/canonical.py
@@ -1,4 +1,7 @@
-"""Strict deterministic serialization primitives for VSTD 3 signed records."""
+"""Terminology: JavaScript Object Notation (JSON); Secure Hash Algorithm 256-bit (SHA-256);
+Verifier Standard (VSTD).
+
+Strict deterministic serialization primitives for VSTD 3 signed records."""
from __future__ import annotations
diff --git a/src/verifier/hardware/claims.py b/src/verifier/hardware/claims.py
index 447ef93..a9e9182 100644
--- a/src/verifier/hardware/claims.py
+++ b/src/verifier/hardware/claims.py
@@ -1,4 +1,6 @@
-"""Evidence-monotone VSTD 3 claim evaluation."""
+"""Terminology: Verifier Standard (VSTD).
+
+Evidence-monotone VSTD 3 claim evaluation."""
from __future__ import annotations
diff --git a/src/verifier/hardware/conformance.py b/src/verifier/hardware/conformance.py
index e514de0..9cde2ed 100644
--- a/src/verifier/hardware/conformance.py
+++ b/src/verifier/hardware/conformance.py
@@ -1,4 +1,6 @@
-"""Incremental, evidence-bounded VSTD 3 conformance profiles."""
+"""Terminology: Verifier Standard (VSTD).
+
+Incremental, evidence-bounded VSTD 3 conformance profiles."""
from __future__ import annotations
diff --git a/src/verifier/hardware/continuity.py b/src/verifier/hardware/continuity.py
index 459c60b..36ac73b 100644
--- a/src/verifier/hardware/continuity.py
+++ b/src/verifier/hardware/continuity.py
@@ -1,4 +1,7 @@
-"""Authenticated event sequencing and reset-epoch verification for VSTD 3."""
+"""Terminology: hash-based message authentication code (HMAC);
+International Organization for Standardization (ISO); Verifier Standard (VSTD).
+
+Authenticated event sequencing and reset-epoch verification for VSTD 3."""
from __future__ import annotations
diff --git a/src/verifier/hardware/emulator.py b/src/verifier/hardware/emulator.py
index 5e7a3f8..1c184a7 100644
--- a/src/verifier/hardware/emulator.py
+++ b/src/verifier/hardware/emulator.py
@@ -1,4 +1,8 @@
-"""Executable reference model for the VSTD 3 firmware-accountability contract."""
+"""Terminology: artificial intelligence (AI); application-specific integrated circuit (ASIC);
+hash-based message authentication code (HMAC); Secure Hash Algorithm 256-bit (SHA-256);
+Verifier Standard (VSTD).
+
+Executable reference model for the VSTD 3 firmware-accountability contract."""
from __future__ import annotations
diff --git a/src/verifier/hardware/fleet.py b/src/verifier/hardware/fleet.py
index 963cdda..92f595d 100644
--- a/src/verifier/hardware/fleet.py
+++ b/src/verifier/hardware/fleet.py
@@ -1,4 +1,6 @@
-"""Fleet-boundary and partition-safe accounting checks for VSTD 3."""
+"""Terminology: Verifier Standard (VSTD).
+
+Fleet-boundary and partition-safe accounting checks for VSTD 3."""
from __future__ import annotations
diff --git a/src/verifier/hardware/models.py b/src/verifier/hardware/models.py
index 4ab5001..3e3110b 100644
--- a/src/verifier/hardware/models.py
+++ b/src/verifier/hardware/models.py
@@ -1,4 +1,8 @@
-"""Accelerator-agnostic records for VSTD 3 hardware accountability."""
+"""Terminology: artificial intelligence (AI); application-specific integrated circuit (ASIC);
+graphics processing unit (GPU); neural processing unit (NPU); tensor processing unit (TPU);
+Verifier Standard (VSTD).
+
+Accelerator-agnostic records for VSTD 3 hardware accountability."""
from __future__ import annotations
diff --git a/src/verifier/hardware/provenance.py b/src/verifier/hardware/provenance.py
index 83fa022..1d45cd1 100644
--- a/src/verifier/hardware/provenance.py
+++ b/src/verifier/hardware/provenance.py
@@ -1,4 +1,6 @@
-"""Composition of VSTD 3 hardware evidence into the existing provenance hypergraph."""
+"""Terminology: Verifier Standard (VSTD).
+
+Composition of VSTD 3 hardware evidence into the existing provenance hypergraph."""
from __future__ import annotations
@@ -60,8 +62,8 @@ def attach_vstd3_receipt(
) -> HardwareProvenanceBinding:
"""Attach a validated receipt so evidence invalidation reaches derived artifacts.
- The function refuses receipts whose passing claims cannot be independently
- reproduced under the supplied key resolver. ``output_artifact_ids`` defaults to
+ The function refuses receipts whose passing claims cannot be recomputed from bound
+ evidence under the supplied key resolver. ``output_artifact_ids`` defaults to
the receipt's declared provenance links and every target must already exist.
"""
diff --git a/src/verifier/hardware/provider_evidence.py b/src/verifier/hardware/provider_evidence.py
index 0790008..3b84297 100644
--- a/src/verifier/hardware/provider_evidence.py
+++ b/src/verifier/hardware/provider_evidence.py
@@ -1,4 +1,9 @@
-"""Canonical binding and independent verification of provider control-plane evidence."""
+"""Canonical binding and verifier-side recomputation of provider control-plane evidence.
+
+The legacy public function name ``independently_verify_provider_evidence`` distinguishes
+recomputation from trusting a provider's status field. It does not establish distinct
+producer and checker actors under Verifier Standard (VSTD) profile 1.
+"""
from __future__ import annotations
diff --git a/src/verifier/hardware/receipt.py b/src/verifier/hardware/receipt.py
index 5e568e5..26ce3ad 100644
--- a/src/verifier/hardware/receipt.py
+++ b/src/verifier/hardware/receipt.py
@@ -1,4 +1,6 @@
-"""Strict persistence helpers for VSTD 3 receipts."""
+"""Terminology: JavaScript Object Notation (JSON); Verifier Standard (VSTD).
+
+Strict persistence helpers for VSTD 3 receipts."""
from __future__ import annotations
diff --git a/src/verifier/hardware/registry.py b/src/verifier/hardware/registry.py
index 6b99da8..af10cb1 100644
--- a/src/verifier/hardware/registry.py
+++ b/src/verifier/hardware/registry.py
@@ -1,4 +1,6 @@
-"""Data-driven accelerator profile registry; profiles do not define claim policy."""
+"""Terminology: JavaScript Object Notation (JSON); Verifier Standard (VSTD).
+
+Data-driven accelerator profile registry; profiles do not define claim policy."""
from __future__ import annotations
diff --git a/src/verifier/hardware/schema.py b/src/verifier/hardware/schema.py
index 1ed1cf6..12033c6 100644
--- a/src/verifier/hardware/schema.py
+++ b/src/verifier/hardware/schema.py
@@ -1,4 +1,8 @@
-"""Deterministic JSON Schema generation for the normative VSTD 3 records."""
+"""Terminology: artificial intelligence (AI); application-specific integrated circuit (ASIC);
+graphics processing unit (GPU); JavaScript Object Notation (JSON); neural processing unit (NPU);
+tensor processing unit (TPU); Verifier Standard (VSTD).
+
+Deterministic JSON Schema generation for the normative VSTD 3 records."""
from __future__ import annotations
@@ -117,6 +121,12 @@ def schema_for(model_type: type, *, schema_id: str, title: str) -> dict[str, obj
builder = _SchemaBuilder()
root = builder.reference(model_type)
return {
+ "$comment": (
+ "Terminology: artificial intelligence (AI); application-specific integrated "
+ "circuit (ASIC); graphics processing unit (GPU); JavaScript Object Notation "
+ "(JSON); neural processing unit (NPU); tensor processing unit (TPU); "
+ "Verifier Standard (VSTD)."
+ ),
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": SCHEMA_BASE + schema_id,
"title": title,
diff --git a/src/verifier/hardware/validation.py b/src/verifier/hardware/validation.py
index 16426ba..eaf68ca 100644
--- a/src/verifier/hardware/validation.py
+++ b/src/verifier/hardware/validation.py
@@ -1,4 +1,6 @@
-"""Fail-closed structural and epistemic validation for VSTD 3 receipts."""
+"""Terminology: International Organization for Standardization (ISO); Verifier Standard (VSTD).
+
+Fail-closed structural and epistemic validation for VSTD 3 receipts."""
from __future__ import annotations
@@ -254,10 +256,10 @@ def validate_vstd3_receipt(
and checked_evidence.verification_state is not VerificationState.VERIFIED
):
warnings.append(
- f"attestation evidence {evidence.evidence_id} could not be independently verified: {verification_detail}"
+ f"attestation evidence {evidence.evidence_id} could not be verified against configured trust material: {verification_detail}"
)
- independently_verified_source_ids = {
+ verified_source_ids = {
evidence.evidence_source_id
for evidence in attestation_by_id.values()
if evidence.verification_state is VerificationState.VERIFIED
@@ -265,7 +267,7 @@ def validate_vstd3_receipt(
claim_source_by_id = {
source_id: (
source
- if source_id in independently_verified_source_ids
+ if source_id in verified_source_ids
or source.verification_state is not VerificationState.VERIFIED
else replace(source, verification_state=VerificationState.NOT_VERIFIED)
)
@@ -466,7 +468,7 @@ def validate_vstd3_receipt(
and checked_provider.verification_state is not VerificationState.VERIFIED
):
warnings.append(
- f"provider evidence {provider_evidence.evidence_id} could not be independently verified: "
+ f"provider evidence {provider_evidence.evidence_id} could not be verified against configured trust material: "
f"{verification_detail}"
)
@@ -600,7 +602,7 @@ def validate_vstd3_receipt(
errors.append("physical-world completeness must remain UNSUPPORTED")
epistemic_key_warning = any(
- "could not be independently verified" in warning
+ "could not be verified against configured trust material" in warning
and ("key unavailable" in warning or "unsupported signature verifier" in warning)
for warning in warnings
)
diff --git a/src/verifier/interoperability/__init__.py b/src/verifier/interoperability/__init__.py
new file mode 100644
index 0000000..6da7ccd
--- /dev/null
+++ b/src/verifier/interoperability/__init__.py
@@ -0,0 +1 @@
+"""Experimental adapters to adjacent verification and transparency standards."""
diff --git a/src/verifier/interoperability/scitt/__init__.py b/src/verifier/interoperability/scitt/__init__.py
new file mode 100644
index 0000000..83f03f0
--- /dev/null
+++ b/src/verifier/interoperability/scitt/__init__.py
@@ -0,0 +1,49 @@
+"""Terminology: Concise Binary Object Representation (CBOR);
+CBOR Object Signing and Encryption (COSE); Supply Chain Integrity, Transparency, and Trust (SCITT);
+Verifier Standard (VSTD).
+
+Experimental, non-normative VSTD/SCITT interoperability surface.
+
+This package does not implement COSE or a SCITT Transparency Service. It
+defines the application payload carried by a SCITT Signed Statement and the
+strict boundary at which a native SCITT verifier's result can become bounded
+VSTD evidence.
+"""
+
+from .adapter import (
+ EXPERIMENTAL_CONTENT_TYPE,
+ EXPERIMENTAL_PROFILE,
+ MAPPING_VERSION,
+ CompositionResult,
+ CompositionStatus,
+ InteropError,
+ ScittEvidenceState,
+ ScittRegistrationTemplate,
+ ScittVerificationEvidence,
+ VstdCoordinates,
+ VstdVerificationEvidence,
+ VstdVerificationState,
+ VstdScittPayload,
+ compose_results,
+ consume_scitt_evidence,
+ create_scitt_registration_template,
+)
+
+__all__ = [
+ "EXPERIMENTAL_CONTENT_TYPE",
+ "EXPERIMENTAL_PROFILE",
+ "MAPPING_VERSION",
+ "CompositionResult",
+ "CompositionStatus",
+ "InteropError",
+ "ScittEvidenceState",
+ "ScittRegistrationTemplate",
+ "ScittVerificationEvidence",
+ "VstdCoordinates",
+ "VstdVerificationEvidence",
+ "VstdVerificationState",
+ "VstdScittPayload",
+ "compose_results",
+ "consume_scitt_evidence",
+ "create_scitt_registration_template",
+]
diff --git a/src/verifier/interoperability/scitt/adapter.py b/src/verifier/interoperability/scitt/adapter.py
new file mode 100644
index 0000000..f0a918f
--- /dev/null
+++ b/src/verifier/interoperability/scitt/adapter.py
@@ -0,0 +1,828 @@
+"""Terminology: American Standard Code for Information Interchange (ASCII);
+Concise Binary Object Representation (CBOR); CBOR Object Signing and Encryption (COSE);
+Internet Engineering Task Force (IETF); JavaScript Object Notation (JSON);
+Request for Comments (RFC); Supply Chain Integrity, Transparency, and Trust (SCITT);
+Secure Hash Algorithm 256-bit (SHA-256); Verifier Standard (VSTD).
+
+Strict experimental mapping between VSTD's interlingua and IETF SCITT.
+
+The emitted registration template is a deterministic *input* to a native
+SCITT/COSE implementation. It is not CBOR, COSE_Sign1, a signature, a COSE
+Receipt, or proof that a Transparency Service registered anything. Likewise,
+the reverse adapter accepts only the normalized output of an external SCITT
+verifier. It never verifies COSE itself.
+
+VSTD does not replace SCITT or the payload's native verifier. It provides the
+portable claim/result language through which those orchestrated substrates are
+composed while their native semantics remain visible.
+
+The central invariant is monotonicity of epistemic strength: registration or
+receipt integrity cannot manufacture a VSTD computational verdict. A composed
+PASS requires both a native VSTD PASS and a current, verified SCITT registration
+for the exact payload. Every other state is preserved or lowers the result.
+"""
+
+from __future__ import annotations
+
+import hashlib
+import json
+import re
+from dataclasses import dataclass
+from enum import Enum
+from types import MappingProxyType
+from typing import Any, Mapping, Sequence
+
+
+MAPPING_VERSION = "0.1"
+EXPERIMENTAL_PROFILE = "vstd-scitt-interop-experimental-0.1"
+EXPERIMENTAL_CONTENT_TYPE = "application/vnd.verifier.vstd-receipt+json"
+
+_SHA256 = re.compile(r"^[0-9a-f]{64}$")
+_VSTD_PASS = frozenset({"PASS"})
+_VSTD_FAIL = frozenset({"FAIL", "FALSIFIED"})
+_VSTD_UNKNOWN = frozenset({"UNKNOWN", "INDETERMINATE", "UNSUPPORTED"})
+
+
+class InteropError(ValueError):
+ """Raised when a mapping is incomplete, ambiguous, or unsupported."""
+
+
+def canonical_json_bytes(value: Any) -> bytes:
+ """Serialize experimental mapping objects deterministically.
+
+ This deliberately matches VSTD's existing sorted, compact, ASCII JSON
+ rules, while remaining a mapping-level serializer rather than a claim that
+ JSON is SCITT's COSE serialized transport format.
+ """
+
+ try:
+ return json.dumps(
+ value,
+ sort_keys=True,
+ separators=(",", ":"),
+ ensure_ascii=True,
+ allow_nan=False,
+ ).encode("utf-8")
+ except (TypeError, ValueError) as exc:
+ raise InteropError(f"value is not canonical-JSON serializable: {exc}") from exc
+
+
+def _sha256(value: bytes) -> str:
+ return hashlib.sha256(value).hexdigest()
+
+
+def _digest(value: str, label: str) -> str:
+ if not isinstance(value, str):
+ raise InteropError(f"{label} must be a lowercase SHA-256 digest")
+ normalized = value.removeprefix("sha256:")
+ if not _SHA256.fullmatch(normalized):
+ raise InteropError(f"{label} must be a lowercase SHA-256 digest")
+ return normalized
+
+
+def _nonempty(value: Any, label: str) -> str:
+ if not isinstance(value, str) or not value:
+ raise InteropError(f"{label} must be a non-empty string")
+ return value
+
+
+def _exact_keys(value: Mapping[str, Any], expected: set[str], label: str) -> None:
+ actual = set(value)
+ if actual != expected:
+ raise InteropError(
+ f"{label} keys mismatch; missing={sorted(expected - actual)}, "
+ f"extra={sorted(actual - expected)}"
+ )
+
+
+def _string_map(value: Mapping[str, Any], label: str) -> dict[str, str]:
+ result: dict[str, str] = {}
+ for key, item in value.items():
+ result[_nonempty(key, f"{label} key")] = _nonempty(
+ item, f"{label}[{key!r}]"
+ )
+ return dict(sorted(result.items()))
+
+
+@dataclass(frozen=True)
+class VstdCoordinates:
+ """Loss-sensitive projection of the VSTD semantics carried in SCITT.
+
+ The full native receipt is embedded as the payload. This projection makes
+ the coordinates a SCITT registration policy or relying-party tool is most
+ likely to inspect explicit without pretending one generic adapter can infer
+ every VSTD receipt family's semantics.
+ """
+
+ receipt_id: str
+ schema_version: str
+ claim_id: str
+ subject: str
+ predicate: str
+ parameters: Mapping[str, str]
+ native_result: str
+ native_canonical_digest: str
+ evidence_bounds: Mapping[str, int]
+ artifact_digests: Mapping[str, str]
+ provenance_references: tuple[str, ...] = ()
+
+ def __post_init__(self) -> None:
+ for name in (
+ "receipt_id",
+ "schema_version",
+ "claim_id",
+ "subject",
+ "predicate",
+ "native_result",
+ ):
+ _nonempty(getattr(self, name), name)
+ object.__setattr__(
+ self,
+ "native_canonical_digest",
+ _digest(self.native_canonical_digest, "native_canonical_digest"),
+ )
+ params = _string_map(self.parameters, "parameters")
+ object.__setattr__(self, "parameters", MappingProxyType(params))
+
+ bounds: dict[str, int] = {}
+ for key, value in self.evidence_bounds.items():
+ key = _nonempty(key, "evidence_bounds key")
+ if type(value) is not int or value < 0:
+ raise InteropError(
+ f"evidence_bounds[{key!r}] must be a non-negative integer"
+ )
+ bounds[key] = value
+ object.__setattr__(
+ self, "evidence_bounds", MappingProxyType(dict(sorted(bounds.items())))
+ )
+
+ artifacts = {
+ _nonempty(key, "artifact_digests key"): _digest(
+ value, f"artifact_digests[{key!r}]"
+ )
+ for key, value in self.artifact_digests.items()
+ }
+ if not artifacts:
+ raise InteropError("at least one artifact digest is required")
+ object.__setattr__(
+ self, "artifact_digests", MappingProxyType(dict(sorted(artifacts.items())))
+ )
+ refs = tuple(_nonempty(item, "provenance reference") for item in self.provenance_references)
+ if len(set(refs)) != len(refs):
+ raise InteropError("provenance_references must be unique")
+ object.__setattr__(self, "provenance_references", refs)
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "receipt_id": self.receipt_id,
+ "schema_version": self.schema_version,
+ "claim_id": self.claim_id,
+ "claim_coordinate": {
+ "subject": self.subject,
+ "predicate": self.predicate,
+ "parameters": dict(self.parameters),
+ },
+ "native_result": self.native_result,
+ "native_canonical_digest": self.native_canonical_digest,
+ "evidence_bounds": dict(self.evidence_bounds),
+ "artifact_digests": dict(self.artifact_digests),
+ "provenance_references": list(self.provenance_references),
+ }
+
+ @classmethod
+ def from_dict(cls, value: Mapping[str, Any]) -> "VstdCoordinates":
+ _exact_keys(
+ value,
+ {
+ "receipt_id",
+ "schema_version",
+ "claim_id",
+ "claim_coordinate",
+ "native_result",
+ "native_canonical_digest",
+ "evidence_bounds",
+ "artifact_digests",
+ "provenance_references",
+ },
+ "vstd_coordinates",
+ )
+ coordinate = value["claim_coordinate"]
+ if not isinstance(coordinate, Mapping):
+ raise InteropError("claim_coordinate must be an object")
+ _exact_keys(
+ coordinate, {"subject", "predicate", "parameters"}, "claim_coordinate"
+ )
+ parameters = coordinate["parameters"]
+ bounds = value["evidence_bounds"]
+ artifacts = value["artifact_digests"]
+ refs = value["provenance_references"]
+ if not isinstance(parameters, Mapping):
+ raise InteropError("claim_coordinate.parameters must be an object")
+ if not isinstance(bounds, Mapping):
+ raise InteropError("evidence_bounds must be an object")
+ if not isinstance(artifacts, Mapping):
+ raise InteropError("artifact_digests must be an object")
+ if not isinstance(refs, list) or not all(isinstance(item, str) for item in refs):
+ raise InteropError("provenance_references must be an array of strings")
+ return cls(
+ receipt_id=value["receipt_id"],
+ schema_version=value["schema_version"],
+ claim_id=value["claim_id"],
+ subject=coordinate["subject"],
+ predicate=coordinate["predicate"],
+ parameters=parameters,
+ native_result=value["native_result"],
+ native_canonical_digest=value["native_canonical_digest"],
+ evidence_bounds=bounds,
+ artifact_digests=artifacts,
+ provenance_references=tuple(refs),
+ )
+
+
+@dataclass(frozen=True)
+class VstdScittPayload:
+ """Experimental application payload for carriage in a SCITT statement."""
+
+ receipt: Mapping[str, Any]
+ coordinates: VstdCoordinates
+ receipt_sha256: str
+ mapping_version: str = MAPPING_VERSION
+ profile: str = EXPERIMENTAL_PROFILE
+ receipt_media_type: str = EXPERIMENTAL_CONTENT_TYPE
+
+ def __post_init__(self) -> None:
+ if self.mapping_version != MAPPING_VERSION:
+ raise InteropError(f"unsupported mapping version {self.mapping_version!r}")
+ if self.profile != EXPERIMENTAL_PROFILE:
+ raise InteropError(f"unsupported profile {self.profile!r}")
+ if self.receipt_media_type != EXPERIMENTAL_CONTENT_TYPE:
+ raise InteropError(
+ f"unsupported receipt media type {self.receipt_media_type!r}"
+ )
+ if not isinstance(self.receipt, Mapping):
+ raise InteropError("receipt must be an object")
+ _digest(self.receipt_sha256, "receipt_sha256")
+ # Break aliases to caller-owned nested dictionaries. ``to_dict`` also
+ # rechecks the digest, so even deliberate mutation through the exposed
+ # nested projection fails closed rather than changing signed bytes.
+ copied = json.loads(canonical_json_bytes(dict(self.receipt)).decode("utf-8"))
+ object.__setattr__(self, "receipt", MappingProxyType(copied))
+ self.verify_integrity()
+
+ @classmethod
+ def create(
+ cls, receipt: Mapping[str, Any], coordinates: VstdCoordinates
+ ) -> "VstdScittPayload":
+ copied = dict(receipt)
+ return cls(
+ receipt=copied,
+ coordinates=coordinates,
+ receipt_sha256=_sha256(canonical_json_bytes(copied)),
+ )
+
+ def verify_integrity(self) -> None:
+ observed = _sha256(canonical_json_bytes(dict(self.receipt)))
+ if observed != self.receipt_sha256:
+ raise InteropError("embedded VSTD receipt does not match receipt_sha256")
+ for field in ("receipt_id", "schema_version"):
+ native = self.receipt.get(field)
+ declared = getattr(self.coordinates, field)
+ if native != declared:
+ raise InteropError(
+ f"embedded receipt {field} {native!r} does not match "
+ f"declared coordinate {declared!r}"
+ )
+ native_digest = self.receipt.get("canonical_digest")
+ if native_digest is not None:
+ if _digest(native_digest, "receipt.canonical_digest") != (
+ self.coordinates.native_canonical_digest
+ ):
+ raise InteropError(
+ "embedded receipt canonical_digest does not match VSTD coordinates"
+ )
+ elif observed != self.coordinates.native_canonical_digest:
+ raise InteropError(
+ "embedded receipt full canonical digest does not match VSTD coordinates"
+ )
+
+ native_claim_id = self.receipt.get("claim_id")
+ if native_claim_id is not None and native_claim_id != self.coordinates.claim_id:
+ raise InteropError(
+ "embedded receipt claim_id does not match VSTD coordinates"
+ )
+
+ binding = self.receipt.get("binding")
+ if isinstance(binding, Mapping):
+ coordinate = binding.get("coordinate")
+ if isinstance(coordinate, Mapping):
+ expected = {
+ "subject": self.coordinates.subject,
+ "predicate": self.coordinates.predicate,
+ "parameters": dict(self.coordinates.parameters),
+ }
+ if dict(coordinate) != expected:
+ raise InteropError(
+ "embedded VSTD binding coordinate does not match mapping coordinate"
+ )
+ bounds = binding.get("bounds")
+ if isinstance(bounds, Mapping) and dict(bounds) != dict(
+ self.coordinates.evidence_bounds
+ ):
+ raise InteropError(
+ "embedded VSTD evidence bounds do not match mapping coordinates"
+ )
+
+ native_result = None
+ witness = self.receipt.get("witness")
+ if isinstance(witness, Mapping):
+ header = witness.get("header")
+ if isinstance(header, Mapping):
+ native_result = header.get("verdict")
+ decision = self.receipt.get("decision")
+ if native_result is None and isinstance(decision, Mapping):
+ native_result = decision.get("verdict")
+ if native_result is not None and native_result != self.coordinates.native_result:
+ raise InteropError(
+ "embedded VSTD native result does not match mapping coordinates"
+ )
+
+ def to_dict(self) -> dict[str, Any]:
+ self.verify_integrity()
+ return {
+ "mapping_version": self.mapping_version,
+ "profile": self.profile,
+ "receipt_media_type": self.receipt_media_type,
+ "receipt_sha256": self.receipt_sha256,
+ "vstd_coordinates": self.coordinates.to_dict(),
+ "vstd_receipt": dict(self.receipt),
+ }
+
+ def to_bytes(self) -> bytes:
+ return canonical_json_bytes(self.to_dict())
+
+ def payload_sha256(self) -> str:
+ return _sha256(self.to_bytes())
+
+ @classmethod
+ def from_bytes(cls, value: bytes) -> "VstdScittPayload":
+ try:
+ decoded = json.loads(value.decode("utf-8"))
+ except (UnicodeDecodeError, json.JSONDecodeError) as exc:
+ raise InteropError(f"SCITT payload is not canonical JSON: {exc}") from exc
+ if not isinstance(decoded, Mapping):
+ raise InteropError("SCITT payload must be an object")
+ if canonical_json_bytes(decoded) != value:
+ raise InteropError("SCITT payload bytes are not in canonical form")
+ _exact_keys(
+ decoded,
+ {
+ "mapping_version",
+ "profile",
+ "receipt_media_type",
+ "receipt_sha256",
+ "vstd_coordinates",
+ "vstd_receipt",
+ },
+ "SCITT payload",
+ )
+ coordinates = decoded["vstd_coordinates"]
+ receipt = decoded["vstd_receipt"]
+ if not isinstance(coordinates, Mapping) or not isinstance(receipt, Mapping):
+ raise InteropError("vstd_coordinates and vstd_receipt must be objects")
+ return cls(
+ mapping_version=decoded["mapping_version"],
+ profile=decoded["profile"],
+ receipt_media_type=decoded["receipt_media_type"],
+ receipt_sha256=decoded["receipt_sha256"],
+ coordinates=VstdCoordinates.from_dict(coordinates),
+ receipt=receipt,
+ )
+
+
+@dataclass(frozen=True)
+class ScittRegistrationTemplate:
+ """Normalized input for a native RFC 9943/COSE statement producer."""
+
+ issuer: str
+ subject: str
+ payload: VstdScittPayload
+
+ def __post_init__(self) -> None:
+ _nonempty(self.issuer, "issuer")
+ _nonempty(self.subject, "subject")
+ if self.subject != self.payload.coordinates.subject:
+ raise InteropError(
+ "SCITT subject must equal the VSTD claim-coordinate subject"
+ )
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "representation": "normalized-registration-input-not-cose",
+ "required_protected_header_projection": {
+ "content_type": EXPERIMENTAL_CONTENT_TYPE,
+ "issuer": self.issuer,
+ "payload_hash_algorithm": "sha-256",
+ "subject": self.subject,
+ "type": EXPERIMENTAL_PROFILE,
+ },
+ "payload_sha256": self.payload.payload_sha256(),
+ "payload": self.payload.to_dict(),
+ }
+
+ def to_bytes(self) -> bytes:
+ return canonical_json_bytes(self.to_dict())
+
+
+class ScittEvidenceState(str, Enum):
+ """Normalized relying-party state; not an IETF registry."""
+
+ REGISTERED = "REGISTERED"
+ MISSING = "MISSING"
+ STALE = "STALE"
+ CONFLICTED = "CONFLICTED"
+ REVOKED = "REVOKED"
+ SUPERSEDED = "SUPERSEDED"
+ UNKNOWN = "UNKNOWN"
+ INVALID = "INVALID"
+
+
+class VstdVerificationState(str, Enum):
+ """Normalized state from a native VSTD checker, not a wire registry."""
+
+ VERIFIED = "VERIFIED"
+ REJECTED = "REJECTED"
+ INDETERMINATE = "INDETERMINATE"
+ NOT_EVALUATED = "NOT_EVALUATED"
+
+
+@dataclass(frozen=True)
+class VstdVerificationEvidence:
+ """Bound output from a native VSTD checker.
+
+ The adapter cannot infer that an embedded receipt was checked merely
+ because the receipt declares ``PASS``. A caller must provide the native
+ check state for the exact embedded receipt and retain the checker trust
+ coordinates. This is deliberately symmetric with
+ :class:`ScittVerificationEvidence`, which is normalized output from a
+ native SCITT verifier rather than a replacement for one. ``state`` says
+ whether that bounded native check ran; it is not numbered VSTD profile conformance.
+ The current adapter therefore emits ``conformance_status`` explicitly and
+ accepts only ``NOT_ESTABLISHED``.
+ """
+
+ state: VstdVerificationState
+ receipt_sha256: str
+ native_result: str
+ checker: str
+ verification_profile: str
+ reason: str
+ conformance_status: str = "NOT_ESTABLISHED"
+
+ def __post_init__(self) -> None:
+ try:
+ state = VstdVerificationState(self.state)
+ except (TypeError, ValueError) as exc:
+ raise InteropError(
+ f"unsupported VSTD verification state {self.state!r}"
+ ) from exc
+ object.__setattr__(self, "state", state)
+ object.__setattr__(
+ self, "receipt_sha256", _digest(self.receipt_sha256, "receipt_sha256")
+ )
+ for name in ("native_result", "checker", "verification_profile", "reason"):
+ _nonempty(getattr(self, name), name)
+ if self.conformance_status != "NOT_ESTABLISHED":
+ raise InteropError(
+ "this experimental adapter cannot establish VSTD conformance; "
+ "conformance_status must be NOT_ESTABLISHED"
+ )
+
+ def to_dict(self) -> dict[str, str]:
+ return {
+ "state": self.state.value,
+ "receipt_sha256": self.receipt_sha256,
+ "native_result": self.native_result,
+ "checker": self.checker,
+ "verification_profile": self.verification_profile,
+ "reason": self.reason,
+ "conformance_status": self.conformance_status,
+ }
+
+ @classmethod
+ def from_dict(cls, value: Mapping[str, Any]) -> "VstdVerificationEvidence":
+ expected = {
+ "state",
+ "receipt_sha256",
+ "native_result",
+ "checker",
+ "verification_profile",
+ "reason",
+ "conformance_status",
+ }
+ # Read the pre-1.2 experimental shape fail-closed: the old object did
+ # not serialize conformance, and absence never established it. New
+ # writers always emit the explicit status.
+ legacy_expected = expected - {"conformance_status"}
+ if set(value) == legacy_expected:
+ value = dict(value)
+ value["conformance_status"] = "NOT_ESTABLISHED"
+ _exact_keys(value, expected, "VSTD verification evidence")
+ try:
+ state = VstdVerificationState(value["state"])
+ except ValueError as exc:
+ raise InteropError(
+ f"unsupported VSTD verification state {value['state']!r}"
+ ) from exc
+ return cls(state=state, **{key: value[key] for key in expected - {"state"}})
+
+
+@dataclass(frozen=True)
+class ScittVerificationEvidence:
+ """Output supplied by a native SCITT verifier under an explicit policy.
+
+ ``state`` is a local normalized policy result. RFC 9943 does not define
+ this enum, and callers must retain ``native_result`` and ``reason`` so that
+ the source verifier's semantics are not erased.
+ """
+
+ state: ScittEvidenceState
+ statement_sha256: str
+ payload_sha256: str
+ issuer: str
+ subject: str
+ signed_statement_verified: bool
+ receipt_verified: bool
+ verification_profile: str
+ registration_policy: str
+ transparency_service: str
+ vds: str
+ native_result: str
+ reason: str
+ registered_at: str | None = None
+
+ def __post_init__(self) -> None:
+ try:
+ state = ScittEvidenceState(self.state)
+ except (TypeError, ValueError) as exc:
+ raise InteropError(f"unsupported SCITT evidence state {self.state!r}") from exc
+ object.__setattr__(self, "state", state)
+ object.__setattr__(
+ self, "statement_sha256", _digest(self.statement_sha256, "statement_sha256")
+ )
+ object.__setattr__(
+ self, "payload_sha256", _digest(self.payload_sha256, "payload_sha256")
+ )
+ for name in (
+ "issuer",
+ "subject",
+ "verification_profile",
+ "registration_policy",
+ "transparency_service",
+ "vds",
+ "native_result",
+ "reason",
+ ):
+ _nonempty(getattr(self, name), name)
+ if type(self.signed_statement_verified) is not bool:
+ raise InteropError("signed_statement_verified must be boolean")
+ if type(self.receipt_verified) is not bool:
+ raise InteropError("receipt_verified must be boolean")
+ if self.registered_at is not None:
+ _nonempty(self.registered_at, "registered_at")
+ if self.state is ScittEvidenceState.REGISTERED and not (
+ self.signed_statement_verified and self.receipt_verified
+ ):
+ raise InteropError(
+ "REGISTERED requires native verification of both statement and receipt"
+ )
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "state": self.state.value,
+ "statement_sha256": self.statement_sha256,
+ "payload_sha256": self.payload_sha256,
+ "issuer": self.issuer,
+ "subject": self.subject,
+ "signed_statement_verified": self.signed_statement_verified,
+ "receipt_verified": self.receipt_verified,
+ "verification_profile": self.verification_profile,
+ "registration_policy": self.registration_policy,
+ "transparency_service": self.transparency_service,
+ "vds": self.vds,
+ "native_result": self.native_result,
+ "reason": self.reason,
+ "registered_at": self.registered_at,
+ }
+
+ @classmethod
+ def from_dict(cls, value: Mapping[str, Any]) -> "ScittVerificationEvidence":
+ expected = {
+ "state",
+ "statement_sha256",
+ "payload_sha256",
+ "issuer",
+ "subject",
+ "signed_statement_verified",
+ "receipt_verified",
+ "verification_profile",
+ "registration_policy",
+ "transparency_service",
+ "vds",
+ "native_result",
+ "reason",
+ "registered_at",
+ }
+ _exact_keys(value, expected, "SCITT verification evidence")
+ try:
+ state = ScittEvidenceState(value["state"])
+ except ValueError as exc:
+ raise InteropError(f"unsupported SCITT evidence state {value['state']!r}") from exc
+ return cls(state=state, **{key: value[key] for key in expected - {"state"}})
+
+
+class CompositionStatus(str, Enum):
+ PASS = "PASS"
+ FAIL = "FAIL"
+ UNKNOWN = "UNKNOWN"
+ CONFLICTED = "CONFLICTED"
+
+
+@dataclass(frozen=True)
+class CompositionResult:
+ """Scoped conjunction of native results, never a conformance certificate."""
+
+ status: CompositionStatus
+ status_scope: str
+ vstd_conformance_status: str
+ native_vstd_result: str
+ native_scitt_result: str
+ reason: str
+ vstd_receipt_sha256: str
+ scitt_statement_sha256: str
+
+ def to_dict(self) -> dict[str, str]:
+ return {
+ "status": self.status.value,
+ "status_scope": self.status_scope,
+ "vstd_conformance_status": self.vstd_conformance_status,
+ "native_vstd_result": self.native_vstd_result,
+ "native_scitt_result": self.native_scitt_result,
+ "reason": self.reason,
+ "vstd_receipt_sha256": self.vstd_receipt_sha256,
+ "scitt_statement_sha256": self.scitt_statement_sha256,
+ }
+
+
+def create_scitt_registration_template(
+ receipt: Mapping[str, Any],
+ coordinates: VstdCoordinates,
+ *,
+ issuer: str,
+ subject: str,
+) -> ScittRegistrationTemplate:
+ """Create deterministic inputs for an external SCITT/COSE producer."""
+
+ return ScittRegistrationTemplate(
+ issuer=issuer,
+ subject=subject,
+ payload=VstdScittPayload.create(receipt, coordinates),
+ )
+
+
+def consume_scitt_evidence(
+ evidence: ScittVerificationEvidence,
+ *,
+ expected_payload_sha256: str,
+ expected_subject: str,
+ accepted_issuers: Sequence[str],
+) -> dict[str, Any]:
+ """Convert a native SCITT verifier result into bounded VSTD evidence.
+
+ The returned object describes transparency evidence only. Its
+ ``computational_verdict`` is always ``NOT_EVALUATED``.
+ """
+
+ expected_digest = _digest(expected_payload_sha256, "expected_payload_sha256")
+ accepted = tuple(_nonempty(item, "accepted issuer") for item in accepted_issuers)
+ if not accepted:
+ raise InteropError("accepted_issuers cannot be empty")
+
+ state = evidence.state
+ reason = evidence.reason
+ if evidence.payload_sha256 != expected_digest:
+ state = ScittEvidenceState.INVALID
+ reason = "SCITT statement payload does not bind the expected VSTD payload"
+ elif evidence.subject != expected_subject:
+ state = ScittEvidenceState.INVALID
+ reason = "SCITT subject does not match the VSTD claim subject"
+ elif evidence.issuer not in accepted:
+ state = ScittEvidenceState.INVALID
+ reason = "SCITT issuer is not accepted by the relying-party policy"
+ elif not evidence.signed_statement_verified or not evidence.receipt_verified:
+ state = ScittEvidenceState.INVALID
+ reason = "native SCITT statement or receipt verification did not succeed"
+
+ return {
+ "evidence_kind": "SCITT_TRANSPARENCY",
+ "normalized_state": state.value,
+ "native_scitt_result": evidence.native_result,
+ "reason": reason,
+ "computational_verdict": "NOT_EVALUATED",
+ "trust_coordinates": {
+ "accepted_issuers": list(accepted),
+ "registration_policy": evidence.registration_policy,
+ "transparency_service": evidence.transparency_service,
+ "verification_profile": evidence.verification_profile,
+ "vds": evidence.vds,
+ },
+ "statement_sha256": evidence.statement_sha256,
+ "payload_sha256": evidence.payload_sha256,
+ "registered_at": evidence.registered_at,
+ }
+
+
+def compose_results(
+ payload: VstdScittPayload,
+ vstd: VstdVerificationEvidence,
+ scitt: ScittVerificationEvidence,
+ *,
+ artifact_digests: Mapping[str, str],
+ accepted_issuers: Sequence[str],
+) -> CompositionResult:
+ """Compose exact VSTD and SCITT results without semantic upgrading."""
+
+ observed_artifacts = {
+ _nonempty(key, "artifact_digests key"): _digest(
+ value, f"artifact_digests[{key!r}]"
+ )
+ for key, value in artifact_digests.items()
+ }
+ transparency = consume_scitt_evidence(
+ scitt,
+ expected_payload_sha256=payload.payload_sha256(),
+ expected_subject=payload.coordinates.subject,
+ accepted_issuers=accepted_issuers,
+ )
+ scitt_state = ScittEvidenceState(transparency["normalized_state"])
+ native_vstd = vstd.native_result
+
+ if observed_artifacts != dict(payload.coordinates.artifact_digests):
+ status = CompositionStatus.FAIL
+ reason = "artifact binding mismatch"
+ elif vstd.receipt_sha256 != payload.receipt_sha256:
+ status = CompositionStatus.FAIL
+ reason = "native VSTD checker result does not bind the embedded receipt"
+ elif (
+ vstd.state is VstdVerificationState.VERIFIED
+ and vstd.native_result != payload.coordinates.native_result
+ ):
+ status = CompositionStatus.FAIL
+ reason = "native VSTD checker result does not match the payload result"
+ elif vstd.state is VstdVerificationState.REJECTED:
+ status = CompositionStatus.FAIL
+ reason = f"native VSTD checker rejected the receipt: {vstd.reason}"
+ elif vstd.state is VstdVerificationState.NOT_EVALUATED:
+ status = CompositionStatus.UNKNOWN
+ reason = "native VSTD receipt was not evaluated"
+ elif vstd.state is VstdVerificationState.INDETERMINATE:
+ status = CompositionStatus.UNKNOWN
+ reason = f"native VSTD checker was unable to decide: {vstd.reason}"
+ elif native_vstd in _VSTD_FAIL:
+ status = CompositionStatus.FAIL
+ reason = "native VSTD verification failed"
+ elif scitt_state is ScittEvidenceState.INVALID:
+ status = CompositionStatus.FAIL
+ reason = transparency["reason"]
+ elif native_vstd == CompositionStatus.CONFLICTED.value:
+ status = CompositionStatus.CONFLICTED
+ reason = "native VSTD evidence is conflicted"
+ elif scitt_state is ScittEvidenceState.CONFLICTED:
+ status = CompositionStatus.CONFLICTED
+ reason = "SCITT evidence graph or relying-party policy reports a conflict"
+ elif native_vstd in _VSTD_UNKNOWN:
+ status = CompositionStatus.UNKNOWN
+ reason = "native VSTD verification is indeterminate or unsupported"
+ elif scitt_state is not ScittEvidenceState.REGISTERED:
+ status = CompositionStatus.UNKNOWN
+ reason = f"SCITT evidence state {scitt_state.value} does not establish a current registration"
+ elif native_vstd in _VSTD_PASS:
+ status = CompositionStatus.PASS
+ reason = (
+ "native candidate-check result PASS (VSTD conformance "
+ "NOT_ESTABLISHED) and exact current SCITT registration both verified"
+ )
+ else:
+ raise InteropError(
+ f"unsupported native VSTD result {native_vstd!r}; refusing to guess"
+ )
+
+ return CompositionResult(
+ status=status,
+ status_scope="NATIVE_VSTD_RESULT_AND_SCITT_REGISTRATION",
+ vstd_conformance_status=vstd.conformance_status,
+ native_vstd_result=native_vstd,
+ native_scitt_result=scitt.native_result,
+ reason=reason,
+ vstd_receipt_sha256=payload.receipt_sha256,
+ scitt_statement_sha256=scitt.statement_sha256,
+ )
diff --git a/src/verifier/layer4/__init__.py b/src/verifier/layer4/__init__.py
index 7dc5779..66d3a51 100644
--- a/src/verifier/layer4/__init__.py
+++ b/src/verifier/layer4/__init__.py
@@ -1,4 +1,6 @@
-"""VSTD-4 refutability records outside the trusted decision kernel."""
+"""Terminology: Verifier Standard (VSTD).
+
+VSTD-4 refutability records outside the trusted decision kernel."""
from .availability import (
ArtifactAvailability,
diff --git a/src/verifier/layer4/availability.py b/src/verifier/layer4/availability.py
index 65c050f..c577941 100644
--- a/src/verifier/layer4/availability.py
+++ b/src/verifier/layer4/availability.py
@@ -1,4 +1,7 @@
-"""Rung 4.8 -- the availability ladder.
+"""Terminology: identifier (ID); International Organization for Standardization (ISO);
+Verifier Standard (VSTD).
+
+Rung 4.8 -- the availability-state sequence.
A hash is not availability. ``proof_sha256 = abc123…`` that nobody can obtain is
cryptographically bound and completely uncheckable, and a verdict resting on it
@@ -6,9 +9,11 @@
IDENTIFIED -> AVAILABLE -> PORTABLE -> SELF_CONTAINED
-The levels are monotone in the same sense as
+The states are monotone in the same sense as
:class:`verifier.core.reproducibility.ReproducibilityLevel`, whose shape this
-mirrors deliberately: an artifact at a level satisfies every level below it.
+mirrors deliberately: an artifact in one state satisfies every prerequisite state.
+The public ``AvailabilityLevel`` name and serialized ``*_level`` fields are retained
+compatibility identifiers; they do not denote numbered VSTD profiles.
> All verdict-critical artifacts MUST either accompany the certificate or be
> retrievable through content-addressed references satisfying a declared
@@ -29,7 +34,7 @@
class AvailabilityLevel(str, Enum):
- """Monotone levels of artifact obtainability."""
+ """Monotone artifact-obtainability states; name retained for compatibility."""
IDENTIFIED = "IDENTIFIED"
"""A content address exists. Nothing asserts that the bytes can be fetched."""
@@ -139,8 +144,9 @@ def to_dict(self) -> dict[str, Any]:
class ArtifactAvailability:
"""One verdict-critical artifact and how obtainable it actually is.
- The level is **derived**, never taken on the declarant's word -- see
- :meth:`assess`. A record may state a level, and if the stated level exceeds
+ The availability state is **derived**, never taken on the declarant's word -- see
+ :meth:`assess`. A record may state a value in its compatibility ``declared_level``
+ field, and if the stated value exceeds
the derived one the record is refused rather than believed.
"""
diff --git a/src/verifier/layer4/challenge.py b/src/verifier/layer4/challenge.py
index 593efa7..d1fcda0 100644
--- a/src/verifier/layer4/challenge.py
+++ b/src/verifier/layer4/challenge.py
@@ -1,11 +1,16 @@
-"""Rung 4.12 -- the challenge protocol.
+"""Terminology: Verifier Standard (VSTD).
-Layer 4 must define what happens when someone says *this verdict is wrong*,
+Rung 4.12 -- the challenge protocol.
+
+The Refutability coordinate must define what happens when someone says *this verdict is wrong*,
even though nobody has yet. A challenge mechanism that exists but does not move
verdict state is item 7 on the challenge-theater list, and until now this
repository was on that list: ``ArtifactStatus.CHALLENGED`` has existed in
-``verifier.data.models`` with **no producer anywhere in the tree**. This
-module is its producer.
+``verifier.data.models`` with **no producer anywhere in the tree**. This module
+produces challenge-ledger claim state. The separate
+``verifier.data.assurance.AssuranceLedger.project_challenges`` mechanism now binds
+the complete serialized record set into an additive VSTD-Graph current-state view;
+this module still never mutates a historical Graph artifact.
The state machine::
@@ -25,7 +30,7 @@
:class:`ChallengeLedger` follows it, and :meth:`ChallengeLedger.status` recomputes
from the records every time.
-The split with layer 5 is clean and worth stating, because it is the whole
+The split with profile 5 is clean and worth stating, because it is the whole
reason this rung sits at 4 and not at 5:
* **VSTD-4:** is the claim structurally challengeable? Testable alone, with a
diff --git a/src/verifier/layer4/closure.py b/src/verifier/layer4/closure.py
index 05c8dc9..08fcf40 100644
--- a/src/verifier/layer4/closure.py
+++ b/src/verifier/layer4/closure.py
@@ -1,4 +1,6 @@
-"""Rung 4.14 -- refutability closure, and the handoff out of layer 4.
+"""Terminology: Verifier Standard (VSTD).
+
+Candidate rung 4.14 -- structural refutability closure.
``A`` is VSTD-4 and ``B`` is VSTD-4 does **not** make ``C = f(A, B)`` VSTD-4.
Refutability is not preserved by arbitrary transformation, and assuming it is
@@ -12,21 +14,23 @@
This rung is simultaneously three things, which is why it sits at the top:
-* the top of layer 4;
-* the precondition for VSTD-Graph condition 4 -- edges carry evidence, not just
- nodes, because a graph is only as verified as its edges;
-* the entry gate to VSTD-5. An external witness can only corroborate a claim
- whose refutability composes, so ``vstd4_depth(claim) == 14`` is the gate.
+* the structural top of the current VSTD-4 candidate;
+* a candidate input to VSTD-Graph condition 4 -- edges need evidence, not just
+ nodes, because a graph is only as verified as its edges.
+
+The depths and certificate references accepted here are caller-supplied and are not
+resolved by this module. Its accepted result is therefore a candidate with conformance
+``NOT_ESTABLISHED``; it is not a VSTD-5 entry gate.
:meth:`RefutabilityClosure.closed_depth` is the load-bearing computation: the
output is capped at the *minimum* depth across its inputs and its transformation.
Not the average, and emphatically not the maximum -- an unevidenced edge between
-two layer-5 artifacts does not yield a layer-5 collection.
+two profile-5 artifacts does not yield a Graph-5 collection.
"""
from __future__ import annotations
-from dataclasses import dataclass
+from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Optional
@@ -53,7 +57,7 @@ class InputBinding:
input_id: str
certificate_digest: str
depth: int
- """``vstd4_depth`` of this input, computed by :mod:`verifier.core.depth`."""
+ """Caller-supplied VSTD-4 candidate depth of this input."""
def to_dict(self) -> dict[str, Any]:
return {
@@ -87,6 +91,7 @@ class ClosureCheck:
closed_depth: int
details: str
unmapped: tuple[str, ...] = ()
+ conformance_status: str = field(default="NOT_ESTABLISHED", init=False)
def to_dict(self) -> dict[str, Any]:
return {
@@ -94,6 +99,7 @@ def to_dict(self) -> dict[str, Any]:
"closed_depth": self.closed_depth,
"details": self.details,
"unmapped": list(self.unmapped),
+ "conformance_status": self.conformance_status,
}
@@ -209,12 +215,12 @@ def validate(self) -> ClosureCheck:
True,
depth,
f"closure over {len(self.inputs)} input(s) is complete; output is capped "
- f"at vstd4_depth {depth}",
+ f"at VSTD-4 candidate depth {depth}; conformance is not established",
)
def cap_output_depth(closure: RefutabilityClosure, claimed_depth: int) -> ClosureCheck:
- """Refuse an output claiming more layer-4 depth than its closure supports.
+ """Refuse an output claiming more candidate depth than its closure supports.
This is rung 4.13 acting across a transformation rather than across time,
and it is the specific check VSTD-Graph condition 4 calls into.
@@ -226,11 +232,12 @@ def cap_output_depth(closure: RefutabilityClosure, claimed_depth: int) -> Closur
return ClosureCheck(
False,
check.closed_depth,
- f"output claims vstd4_depth {claimed_depth} but its closure supports only "
+ f"output claims VSTD-4 candidate depth {claimed_depth} but its closure supports only "
f"{check.closed_depth}; refutability does not increase under composition",
)
return ClosureCheck(
True,
check.closed_depth,
- f"output depth {claimed_depth} is within the closure's ceiling of {check.closed_depth}",
+ f"output candidate depth {claimed_depth} is within the closure's ceiling of "
+ f"{check.closed_depth}; conformance is not established",
)
diff --git a/src/verifier/layer4/precommit.py b/src/verifier/layer4/precommit.py
index 6392159..e17c723 100644
--- a/src/verifier/layer4/precommit.py
+++ b/src/verifier/layer4/precommit.py
@@ -1,4 +1,7 @@
-"""Rung 4.11 -- the precommitment envelope.
+"""Terminology: International Organization for Standardization (ISO);
+Coordinated Universal Time (UTC); Verifier Standard (VSTD).
+
+Rung 4.11 -- the precommitment envelope.
Committing only the claim is not enough, and the gap is not subtle. A declarant
can honestly precommit *"my system achieves X"* and then, after looking at the
@@ -12,7 +15,7 @@
> A declarant MUST NOT select any verdict-material degree of freedom after
> observing the evidence produced by that degree of freedom.
-Two independent checks enforce it, and they catch different cheats.
+Two separate checks enforce it, and they catch different cheats.
:func:`audit_selections` compares what was *used* against what was *committed* --
that catches substitution. The temporal comparison catches the subtler case
where the committed value was left open, or committed late: a choice timestamped
diff --git a/src/verifier/layer4/surface.py b/src/verifier/layer4/surface.py
index b92c90c..f9e80f0 100644
--- a/src/verifier/layer4/surface.py
+++ b/src/verifier/layer4/surface.py
@@ -1,7 +1,9 @@
-"""Rung 4.10 -- the explicit refutation surface.
+"""Terminology: Verifier Standard (VSTD).
+
+Rung 4.10 -- the explicit refutation surface.
VSTD-2 defines the *claim* surface. VSTD-4 defines the *refutation* surface of
-that claim surface. This is where the two layers compose, and it is the rung
+that claim surface. This is where the two profile coordinates compose, and it is the rung
that turns "someone could theoretically challenge this" into a list: here are
the predicates they may challenge, the coordinates on which each applies, and
the evidence that would overturn the verdict.
@@ -16,7 +18,7 @@
which is enforced literally: a surface with an empty ``admissible`` list is
refused. A claim nobody is permitted to refute is not a strong claim, it is an
-unfalsifiable one, and layer 4 exists to say so out loud.
+unfalsifiable one, and the Refutability coordinate exists to say so out loud.
``excluded_claims`` is the other half, and it is not a disclaimer. It gives
``PHYSICAL_WORLD_COMPLETENESS`` a permanent machine-readable home: ordinary
diff --git a/src/verifier/runtime/demo.py b/src/verifier/runtime/demo.py
index 663b929..cda5288 100644
--- a/src/verifier/runtime/demo.py
+++ b/src/verifier/runtime/demo.py
@@ -1,4 +1,7 @@
-"""Deterministic adversarial demonstration of VSTD's refutation boundaries.
+"""Terminology: command-line interface (CLI); JavaScript Object Notation (JSON);
+Verifier Standard (VSTD).
+
+Deterministic adversarial demonstration of VSTD's refutation boundaries.
The demo is intentionally self-contained and side-effect free unless a caller
explicitly asks to emit its JSON specimens. It does not execute manifests,
@@ -328,7 +331,7 @@ def _poisoned_ancestor() -> DemoResult:
{"transform:extract": 5, "transform:collect": 5},
)
binding = ClaimBinding(
- claim="compute the bounded graph level for collection:demo",
+ claim="compute the bounded candidate Graph profile for collection:demo",
coordinate=ClaimCoordinate("collection:demo", "vstd_graph_level"),
policy_root=canonical_digest("flagship-demo-graph-policy"),
evidence_root=canonical_digest(graph.to_dict()),
@@ -353,14 +356,14 @@ def _poisoned_ancestor() -> DemoResult:
and refutation_check.verdict is Verdict.FAIL
)
observed = (
- f"GRAPH-LEVEL-{result.level}; "
+ f"GRAPH-CANDIDATE-{result.level}; "
f"{blockers[0].observed if blockers else 'NO-BLOCKER'}"
)
return DemoResult(
scenario="poisoned-ancestor",
title="Revoked ancestor behind valid descendants",
- question="Does a poisoned transitive ancestor cap the collection's graph level?",
- expected="GRAPH-LEVEL-0; REVOKED blocker; checked refutation",
+ question="Does a poisoned transitive ancestor cap the collection's candidate Graph profile?",
+ expected="GRAPH-CANDIDATE-0; REVOKED blocker; checked refutation",
observed=observed,
ok=ok,
details=result.explanation,
@@ -373,8 +376,8 @@ def _poisoned_ancestor() -> DemoResult:
"edge_levels": dict(sorted(collection.edge_levels.items())),
},
"fixture_boundary": (
- "Object and edge levels are declared scenario inputs. This graph-level "
- "refutation does not establish or upgrade their separate evidence."
+ "Object and edge profile ratings are declared scenario inputs. This "
+ "Graph-profile refutation does not establish or upgrade their separate evidence."
),
"hypergraph": graph.to_dict(),
"graph_result": result.to_dict(),
diff --git a/src/verifier/runtime/experimental_workflow_cli.py b/src/verifier/runtime/experimental_workflow_cli.py
new file mode 100644
index 0000000..b5e3f2c
--- /dev/null
+++ b/src/verifier/runtime/experimental_workflow_cli.py
@@ -0,0 +1,141 @@
+"""Terminology: command-line interface (CLI); JavaScript Object Notation (JSON);
+Verifier Standard (VSTD).
+
+CLI boundary for the experimental, non-normative workflow profile."""
+
+from __future__ import annotations
+
+import argparse
+import json
+from pathlib import Path
+from typing import Any
+
+from verifier.experimental_workflow import (
+ GitHubAdapterError,
+ github_snapshot_to_events,
+ load_manifest,
+ verify_repo_artifacts,
+)
+
+
+def add_experiment_parsers(subparsers: argparse._SubParsersAction) -> None:
+ """Add verdict-neutral experimental-workflow commands to the public parser."""
+
+ parser = subparsers.add_parser(
+ "experiment",
+ help="Validate or adapt experimental, non-normative workflow records.",
+ )
+ commands = parser.add_subparsers(dest="experiment_command", required=True)
+
+ validate_parser = commands.add_parser(
+ "validate",
+ help="Validate a profile manifest without granting a VSTD verdict.",
+ )
+ validate_parser.add_argument("manifest", help="Experimental workflow manifest JSON.")
+ validate_parser.add_argument(
+ "--repo-root",
+ help="Repository root used to verify every repo: artifact locator.",
+ )
+ validate_parser.add_argument("--json", action="store_true")
+
+ github_parser = commands.add_parser(
+ "github-events",
+ help="Map a strict normalized GitHub snapshot to verdict-neutral events.",
+ )
+ github_parser.add_argument("snapshot", help="Normalized GitHub snapshot JSON.")
+ github_parser.add_argument("--json", action="store_true")
+
+
+def _repository_artifact_count(payload: dict[str, Any]) -> int:
+ artifacts = payload.get("artifacts", [])
+ if not isinstance(artifacts, list):
+ return 0
+ return sum(
+ 1
+ for artifact in artifacts
+ if isinstance(artifact, dict)
+ and isinstance(artifact.get("locator"), str)
+ and artifact["locator"].startswith("repo:")
+ )
+
+
+def _validate(args: argparse.Namespace) -> int:
+ manifest_path = Path(args.manifest).resolve()
+ payload = load_manifest(manifest_path)
+ repo_artifact_count = _repository_artifact_count(payload)
+ if not repo_artifact_count:
+ repository_artifacts = "NOT_APPLICABLE"
+ elif args.repo_root:
+ verify_repo_artifacts(payload, Path(args.repo_root).resolve())
+ repository_artifacts = "VERIFIED"
+ else:
+ repository_artifacts = "NOT_CHECKED"
+
+ experiment = payload["experiment"]
+ profile = payload["profile"]
+ assert isinstance(experiment, dict) and isinstance(profile, dict)
+ result = {
+ "status": (
+ "VALID"
+ if repository_artifacts != "NOT_CHECKED"
+ else "VALID_WITH_UNCHECKED_REPOSITORY_ARTIFACTS"
+ ),
+ "profile": {
+ "id": profile["id"],
+ "version": profile["version"],
+ "status": profile["status"],
+ },
+ "experiment": {
+ "id": experiment["id"],
+ "state": experiment["state"],
+ },
+ "manifest_digest": payload["manifest_digest"],
+ "repository_artifact_count": repo_artifact_count,
+ "repository_artifacts": repository_artifacts,
+ "vstd_verdict_granted": False,
+ "claim_boundary": (
+ "Structural validity and bound-byte checks do not establish the hypothesis, "
+ "native verifier, publication, independence, or a VSTD verdict."
+ ),
+ }
+ if args.json:
+ print(json.dumps(result, indent=2, sort_keys=True))
+ else:
+ print(f"[{result['status']}] experimental workflow {experiment['id']}")
+ print(f" Manifest digest: {payload['manifest_digest']}")
+ print(f" Repository artifacts: {repository_artifacts}")
+ print(" VSTD verdict granted: no")
+ print(f" Boundary: {result['claim_boundary']}")
+ return 2 if repository_artifacts == "NOT_CHECKED" else 0
+
+
+def _github_events(args: argparse.Namespace) -> int:
+ snapshot_path = Path(args.snapshot).resolve()
+ snapshot = json.loads(snapshot_path.read_text(encoding="utf-8"))
+ if not isinstance(snapshot, dict):
+ raise GitHubAdapterError("normalized GitHub snapshot must be a JSON object")
+ events = github_snapshot_to_events(snapshot)
+ result = {
+ "adapter": "github-normalized-0.1",
+ "events": list(events),
+ "event_count": len(events),
+ "verification_effects": sorted({event["verification_effect"] for event in events}),
+ "vstd_verdicts_granted": 0,
+ }
+ if args.json:
+ print(json.dumps(result, indent=2, sort_keys=True))
+ else:
+ print(f"[ADAPTED] {len(events)} normalized GitHub events")
+ print(" Verification effects: NONE")
+ print(" VSTD verdicts granted: 0")
+ return 0
+
+
+def handle_experiment_command(args: argparse.Namespace) -> int:
+ """Dispatch one experimental-workflow command without widening its result."""
+
+ if args.experiment_command == "validate":
+ return _validate(args)
+ if args.experiment_command == "github-events":
+ return _github_events(args)
+ return 1
diff --git a/src/verifier/runtime/hardware_cli.py b/src/verifier/runtime/hardware_cli.py
index b98809e..06df9df 100644
--- a/src/verifier/runtime/hardware_cli.py
+++ b/src/verifier/runtime/hardware_cli.py
@@ -1,4 +1,9 @@
-"""Public VSTD 3 accelerator-accountability CLI surfaces."""
+"""Terminology: application programming interface (API); command-line interface (CLI);
+hash-based message authentication code (HMAC); identifier (ID);
+International Organization for Standardization (ISO); JavaScript Object Notation (JSON);
+Verifier Standard (VSTD).
+
+Public VSTD 3 accelerator-accountability CLI surfaces."""
from __future__ import annotations
@@ -499,7 +504,7 @@ def _handle_claims(args: argparse.Namespace) -> int:
"claims": [item.to_dict() for item in receipt.claim_evaluations],
"validation_errors": list(validation.errors),
"validation_warnings": list(validation.warnings),
- "note": "Claim statuses are accepted only when receipt validation independently reproduces every PASS.",
+ "note": "Claim statuses are accepted only when receipt validation recomputes every PASS from bound evidence; this does not establish distinct actors.",
}
_emit(payload, as_json=args.json)
return _status_exit(status)
diff --git a/src/verifier/runtime/public_cli.py b/src/verifier/runtime/public_cli.py
index cd81454..275fb9a 100644
--- a/src/verifier/runtime/public_cli.py
+++ b/src/verifier/runtime/public_cli.py
@@ -1,4 +1,7 @@
-"""Public, target-neutral CLI for the VSTD reference implementation.
+"""Terminology: command-line interface (CLI); identifier (ID); JavaScript Object Notation (JSON);
+Verifier Standard (VSTD); YAML Ain't Markup Language (YAML).
+
+Public, target-neutral CLI for the VSTD reference implementation.
This entry point deliberately excludes repository-specific generators and verifiers.
It operates only on declared generic-run manifests and stored VSTD-Graph receipts.
@@ -7,12 +10,22 @@
from __future__ import annotations
import argparse
+import contextlib
+import io
import json
import shutil
import sys
from pathlib import Path
-from typing import Any
-
+from typing import Any, Callable
+
+from verifier.artifact_control import (
+ freeze_artifact,
+ seal_artifact,
+ thaw_artifact,
+ thawed_artifact_status,
+ verify_frozen_artifact,
+)
+from verifier.core.checker import independence_is_evidenced
from verifier.core.run import (
RunError,
capture_run,
@@ -33,6 +46,10 @@
handle_vstd3_command,
parse_verification_keys,
)
+from verifier.runtime.experimental_workflow_cli import (
+ add_experiment_parsers,
+ handle_experiment_command,
+)
from verifier.runtime.demo import SCENARIOS, demo_report, emit_specimens, run_demo
@@ -59,7 +76,7 @@ def _load_hypergraph(path_or_dir: Path) -> tuple[dict[str, Any], ProvenanceHyper
payload = _read_receipt(path_or_dir)
if payload is None or not _is_data_receipt(payload):
raise ValueError(
- "not a readable VSTD-Graph-1 receipt with frozen wire identifier "
+ "not a readable VSTD-Graph-1 receipt with serialized schema_version identifier "
f"VSTD-DATA-0.1: {_receipt_file(path_or_dir)}"
)
return payload, ProvenanceHypergraph.from_dict(payload["hypergraph"])
@@ -114,13 +131,72 @@ def _inspect_data_receipt(path_or_dir: Path) -> int:
print("=" * 70)
print(f"Canonical Digest: {payload.get('canonical_digest')}")
print(f"Target Artifact: {payload.get('dataset_spec', {}).get('target_artifact_id')}")
- print(f"Audit Verdict: {payload.get('independent_audit', {}).get('overall_verdict')}")
+ print(f"Checker Verdict: {payload.get('independent_audit', {}).get('overall_verdict')}")
+ basis = payload.get("independent_audit", {}).get("independence_basis", {})
+ print(
+ "Independence: "
+ + ("EVIDENCED" if independence_is_evidenced(basis) else "NOT_DEMONSTRATED")
+ )
print(f"Artifacts: {len(graph.artifacts)}")
print(f"Transformations: {len(graph.transformations)}")
print("=" * 70)
return 0
+def _run_receipt_handler_as_json(
+ command: str, receipt_kind: str, handler: Callable[[], int]
+) -> int:
+ """Keep the common receipt commands machine-readable without changing their APIs."""
+
+ stdout = io.StringIO()
+ stderr = io.StringIO()
+ with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
+ exit_code = handler()
+ result = (
+ "COMPLETED"
+ if exit_code == 0
+ else "UNSUPPORTED"
+ if exit_code == 2
+ else "FAILED"
+ )
+ print(
+ json.dumps(
+ {
+ "command": command,
+ "receipt_kind": receipt_kind,
+ "result": result,
+ "exit_code": exit_code,
+ "messages": stdout.getvalue().splitlines(),
+ "errors": stderr.getvalue().splitlines(),
+ },
+ indent=2,
+ sort_keys=True,
+ )
+ )
+ return exit_code
+
+
+def _receipt_command_failure(args: argparse.Namespace, message: str) -> int:
+ if args.json:
+ print(
+ json.dumps(
+ {
+ "command": args.command,
+ "receipt_kind": "UNKNOWN",
+ "result": "FAILED",
+ "exit_code": 1,
+ "messages": [],
+ "errors": [message],
+ },
+ indent=2,
+ sort_keys=True,
+ )
+ )
+ else:
+ print(f"[FAIL] {message}", file=sys.stderr)
+ return 1
+
+
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="vstd",
@@ -160,8 +236,8 @@ def build_parser() -> argparse.ArgumentParser:
plan_parser.add_argument("--json", action="store_true")
for command, help_text in (
- ("validate", "Validate a generic-run or VSTD-Graph receipt."),
- ("inspect", "Inspect a generic-run or VSTD-Graph receipt."),
+ ("validate", "Run implemented receipt checks; Graph candidate validation is not conformance."),
+ ("inspect", "Inspect a generic-run or VSTD-Graph receipt; validate and report VSTD-3."),
("reproduce", "Replay the mechanisms available in a stored receipt."),
):
command_parser = subparsers.add_parser(command, help=help_text)
@@ -200,6 +276,75 @@ def build_parser() -> argparse.ArgumentParser:
export_parser = data_commands.add_parser("export")
export_parser.add_argument("receipt")
+
+ artifact_parser = subparsers.add_parser(
+ "artifact",
+ help="Freeze exact artifact bytes, add or verify a seal, or thaw a descendant.",
+ )
+ artifact_commands = artifact_parser.add_subparsers(
+ dest="artifact_command", required=True
+ )
+ freeze_parser = artifact_commands.add_parser(
+ "freeze",
+ help=(
+ "Copy exact ordinary file or directory bytes into a new guarded bundle; "
+ "symbolic-link sources are refused."
+ ),
+ )
+ freeze_parser.add_argument("source")
+ freeze_parser.add_argument("bundle")
+ freeze_parser.add_argument("--media-type", default="application/octet-stream")
+ freeze_parser.add_argument("--parent", action="append", default=[])
+ freeze_parser.add_argument("--context", action="append", default=[])
+ freeze_parser.add_argument("--json", action="store_true")
+
+ seal_parser = artifact_commands.add_parser(
+ "seal", help="Add a readable finite self-closing Ed25519 seal."
+ )
+ seal_parser.add_argument("bundle")
+ seal_parser.add_argument("--private-key", required=True)
+ seal_parser.add_argument("--json", action="store_true")
+
+ verify_parser = artifact_commands.add_parser(
+ "verify", help="Recompute exact bytes, guards, seals, and optional external anchors."
+ )
+ verify_parser.add_argument("bundle")
+ verify_parser.add_argument("--expected-artifact-id")
+ verify_parser.add_argument("--expected-key-id")
+ verify_parser.add_argument(
+ "--freeze-only",
+ action="store_true",
+ help="Accept a clean freeze without claiming seal-backed identity.",
+ )
+ verify_parser.add_argument("--json", action="store_true")
+
+ thaw_parser = artifact_commands.add_parser(
+ "thaw",
+ help="Copy a clean sealed parent into a new, lexically absent mutable descendant.",
+ )
+ thaw_parser.add_argument("bundle")
+ thaw_parser.add_argument("destination")
+ thaw_parser.add_argument("--expected-artifact-id")
+ thaw_parser.add_argument("--expected-key-id")
+ thaw_parser.add_argument("--json", action="store_true")
+
+ status_parser = artifact_commands.add_parser(
+ "status",
+ help=(
+ "Compare a descendant with recorded sidecar metadata, or verify current "
+ "equality against a supplied sealed parent."
+ ),
+ )
+ status_parser.add_argument("artifact")
+ status_parser.add_argument("--record")
+ status_parser.add_argument(
+ "--parent-bundle",
+ help="Actual frozen parent bundle required to establish THAWED_CLEAN or THAWED_DIRTY.",
+ )
+ status_parser.add_argument("--expected-artifact-id")
+ status_parser.add_argument("--expected-key-id")
+ status_parser.add_argument("--json", action="store_true")
+ add_experiment_parsers(subparsers)
add_vstd3_parsers(subparsers)
return parser
@@ -208,34 +353,55 @@ def _handle_receipt_command(args: argparse.Namespace) -> int:
receipt_path = Path(args.receipt).resolve()
payload = _read_receipt(receipt_path)
if payload is None:
- print(f"[FAIL] Receipt is missing or malformed: {_receipt_file(receipt_path)}", file=sys.stderr)
- return 1
+ return _receipt_command_failure(
+ args, f"Receipt is missing or malformed: {_receipt_file(receipt_path)}"
+ )
if is_generic_run_receipt(payload):
if args.command == "validate":
- return validate_run_receipt(receipt_path)
- if args.command == "inspect":
- return inspect_run_receipt(receipt_path)
- return reproduce_run_receipt(receipt_path, rerun=args.rerun)
+ handler = lambda: validate_run_receipt(receipt_path)
+ elif args.command == "inspect":
+ handler = lambda: inspect_run_receipt(receipt_path)
+ else:
+ handler = lambda: reproduce_run_receipt(receipt_path, rerun=args.rerun)
+ return (
+ _run_receipt_handler_as_json(args.command, "generic_computational_run", handler)
+ if args.json
+ else handler()
+ )
if _is_data_receipt(payload):
if args.command == "validate":
- return validate_data_receipt(receipt_path)
- if args.command == "inspect":
- return _inspect_data_receipt(receipt_path)
- if args.rerun:
- print("[FAIL] --rerun is not defined for stored VSTD-Graph receipts", file=sys.stderr)
- return 1
- return reproduce_data_receipt(receipt_path)
+ handler = lambda: validate_data_receipt(receipt_path)
+ elif args.command == "inspect":
+ handler = lambda: _inspect_data_receipt(receipt_path)
+ elif args.rerun:
+ handler = lambda: _receipt_command_failure(
+ argparse.Namespace(command=args.command, json=False),
+ "--rerun is not defined for stored VSTD-Graph receipts",
+ )
+ else:
+ handler = lambda: reproduce_data_receipt(receipt_path)
+ return (
+ _run_receipt_handler_as_json(args.command, "vstd_graph", handler)
+ if args.json
+ else handler()
+ )
if is_vstd3_receipt(payload):
receipt = load_vstd3_receipt(receipt_path)
if args.command == "reproduce":
- print(
- "[UNSUPPORTED] A stored hardware receipt cannot replay physical execution; "
- "use its declared emulator or vendor collection mechanism.",
- file=sys.stderr,
+ message = (
+ "A stored hardware receipt cannot replay physical execution; use its "
+ "declared emulator or vendor collection mechanism."
)
+ if args.json:
+ return _run_receipt_handler_as_json(
+ args.command,
+ "vstd3_hardware",
+ lambda: (print(f"[UNSUPPORTED] {message}", file=sys.stderr) or 2),
+ )
+ print(f"[UNSUPPORTED] {message}", file=sys.stderr)
return 2
resolver, _ = parse_verification_keys(args.key)
validation = validate_vstd3_receipt(receipt, key_resolver=resolver)
@@ -258,8 +424,7 @@ def _handle_receipt_command(args: argparse.Namespace) -> int:
print(f" - {message}")
return 0 if validation.status.value == "PASS" else (1 if validation.status.value == "FAIL" else 2)
- print("[FAIL] Unsupported receipt kind or schema", file=sys.stderr)
- return 1
+ return _receipt_command_failure(args, "Unsupported receipt kind or schema")
def _handle_data_command(args: argparse.Namespace) -> int:
@@ -308,6 +473,70 @@ def _handle_data_command(args: argparse.Namespace) -> int:
return 0
+def _print_artifact_result(result: dict[str, Any], as_json: bool) -> None:
+ if as_json:
+ print(json.dumps(result, indent=2, sort_keys=True))
+ return
+ print(f"[{result.get('state', 'COMPLETED')}] artifact control")
+ for key, value in result.items():
+ if key != "state":
+ print(f" {key}: {value}")
+
+
+def _handle_artifact_command(args: argparse.Namespace) -> int:
+ if args.artifact_command == "freeze":
+ result = freeze_artifact(
+ args.source,
+ args.bundle,
+ media_type=args.media_type,
+ parent_bundles=args.parent,
+ context_bundles=args.context,
+ )
+ output = {"state": "FROZEN_UNSEALED", **result}
+ _print_artifact_result(output, args.json)
+ return 0
+ if args.artifact_command == "seal":
+ result = seal_artifact(args.bundle, args.private_key)
+ output = {"state": "SEALED", **result}
+ _print_artifact_result(output, args.json)
+ return 0
+ if args.artifact_command == "verify":
+ verification = verify_frozen_artifact(
+ args.bundle,
+ expected_artifact_id=args.expected_artifact_id,
+ expected_key_id=args.expected_key_id,
+ require_seal=not args.freeze_only,
+ )
+ output = verification.to_dict()
+ _print_artifact_result(output, args.json)
+ if verification.state in {"SEALED", "FROZEN_UNSEALED"}:
+ return 0
+ return 2 if verification.state == "NOT_ESTABLISHED" else 1
+ if args.artifact_command == "thaw":
+ result = thaw_artifact(
+ args.bundle,
+ args.destination,
+ expected_artifact_id=args.expected_artifact_id,
+ expected_key_id=args.expected_key_id,
+ )
+ output = {"state": "THAWED_CLEAN", **result}
+ _print_artifact_result(output, args.json)
+ return 0
+ result = thawed_artifact_status(
+ args.artifact,
+ args.record,
+ parent_bundle=args.parent_bundle,
+ expected_artifact_id=args.expected_artifact_id,
+ expected_key_id=args.expected_key_id,
+ )
+ _print_artifact_result(result, args.json)
+ if result["state"] == "THAWED_CLEAN":
+ return 0
+ if result["state"] == "NOT_ESTABLISHED":
+ return 2
+ return 1
+
+
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
try:
@@ -390,6 +619,10 @@ def main(argv: list[str] | None = None) -> int:
if args.command == "data":
return _handle_data_command(args)
+ if args.command == "artifact":
+ return _handle_artifact_command(args)
+ if args.command == "experiment":
+ return handle_experiment_command(args)
if args.command in {"hardware", "continuity", "fleet", "evidence", "claims"}:
return handle_vstd3_command(args)
except (OSError, RunError, ValueError, KeyError) as exc:
diff --git a/src/verifier/specifications/ARTIFACT_CONTROL.md b/src/verifier/specifications/ARTIFACT_CONTROL.md
new file mode 100644
index 0000000..772455a
--- /dev/null
+++ b/src/verifier/specifications/ARTIFACT_CONTROL.md
@@ -0,0 +1,188 @@
+# Verifier Standard (VSTD) artifact freeze, seal, and thaw mechanism
+
+> **Acronyms:** American Standard Code for Information Interchange (ASCII);
+> JavaScript Object Notation (JSON); Privacy-Enhanced Mail (PEM);
+> Secure Hash Algorithm 256-bit (SHA-256); Secure Hash Algorithm 3 256-bit (SHA3-256);
+> Unicode Transformation Format, 8-bit (UTF-8); Verifier Standard (VSTD).
+
+**Status:** normative for artifact-control mechanism version 1
+
+This mechanism preserves exact regular-file bytes, binds them to artifact-derived
+identifiers, optionally closes the freeze with a readable Ed25519 seal, and creates
+mutable descendants by copy-on-write thaw. It is not a numbered VSTD profile, receipt profile,
+encryption format, archival service, correctness proof, or actor reputation system.
+
+## 1. Distinct operations
+
+| Operation | What it establishes when verified | What it does not establish |
+|---|---|---|
+| **Freeze** | The bundle's current regular-file bytes and portable paths match its manifest, and its guarded payload tree is read-only. | Durable external preservation, privileged-write prevention, correctness, freshness, or a cryptographic signer. |
+| **Seal** | A carried public key verifies a signature over the exact freeze closure, and the seal identifier closes the signature-bearing envelope. | Encryption, secrecy, ownership, authorization, trusted time, signer reputation, or protection against whole-bundle substitution. |
+| **Thaw** | The creation operation copied a clean sealed parent into a new mutable descendant and emitted a lineage sidecar. Later `THAWED_CLEAN` status establishes current equality only when the actual supplied parent verifies and every recorded parent coordinate agrees. | Authentication of the historical copy operation, mutation of the parent, continued equality after thaw, or a sealed descendant. |
+
+Sealing and encryption are independent. Version 1 seals are readable and authenticated;
+they do not encrypt any byte. A future encrypted container MUST still identify a separate
+encryption mechanism and MUST NOT treat confidentiality as closure or correctness.
+
+## 2. Bundle and preservation boundary
+
+A bundle contains:
+
+```text
+bundle/
+ payload exact file, or directory of exact files and paths
+ freeze.json VSTD-ARTIFACT-FREEZE-1 manifest
+ seals/*.json zero or more VSTD-ARTIFACT-SEAL-1 envelopes
+```
+
+The mechanism accepts regular files, directories, and empty directories. Symbolic links
+and special filesystem objects fail closed. It preserves file bytes and portable relative
+paths. Permissions, owners, access-control lists, timestamps, extended attributes, sparse
+allocation, and filesystem-specific metadata are outside version 1. The portable
+read-only guard is an observable tripwire, not an access-control boundary against a
+privileged writer.
+
+Freeze classifies the caller-supplied final source entry before dereferencing it, so a
+symbolic link cannot inherit its target's artifact identity. A new bundle, thaw descendant,
+or generated thaw sidecar requires an absent lexical destination: an existing file,
+directory, special object, symbolic link, or dangling symbolic link refuses creation.
+Exclusive file and sidecar creation narrows replacement races, but version 1 does not claim
+universal race-free filesystem security against a concurrent privileged process.
+
+Authoritative entries inside a bundle have a stricter role-specific boundary.
+`freeze.json` and every `seals/*.json` member must be lexical ordinary files; `seals` and a
+directory payload must be lexical ordinary directories; and a file payload must be a
+lexical ordinary file. An internal symbolic link or supported reparse-point alias fails
+structurally even when its target is byte-identical, correctly signed, read-only, or inside
+the same bundle. Manifest parsing and closure use one captured ordinary-file byte snapshot.
+This differs from a caller-supplied outer parent-bundle path or explicit thaw-record path,
+which may be a read-only alias because the resolved bytes and every applicable seal, anchor,
+and binding are subsequently verified. Version 1 identifies accepted ordinary files by
+portable path and bytes, not exclusive inode ownership; hard-link identity remains outside
+its claims. Mount, network-filesystem, and concurrent-replacement behavior remains bounded
+by the host and does not establish universal alias resistance.
+
+“Portable” means slash-normalized relative path representation. Case sensitivity,
+Unicode normalization, reserved names, and path-length limits remain properties of the
+host filesystem; version 1 does not claim that every valid source tree can be materialized
+unchanged on every filesystem.
+
+The manifest inventories every file with its byte size, SHA-256, and SHA3-256 digest. A
+directory entry preserves an empty directory or parent path. Identifier computation uses
+canonical JSON with UTF-8, sorted object keys, no insignificant whitespace, no non-finite
+numbers, and no duplicate keys. Stored objects remain readable; unknown fields fail closed.
+
+## 3. Artifact-derived identifiers
+
+Every version 1 identifier carries independent SHA-256 and SHA3-256 commitments:
+
+```text
+vstd--1:sha256:<64 lowercase hexadecimal characters>:
+ sha3-256:<64 lowercase hexadecimal characters>
+```
+
+`content_id` closes artifact kind, paths, byte sizes, and file digests. `artifact_id`
+closes the same content plus the declared media type. `freeze_id` closes the complete
+freeze manifest except its own field. The artifact therefore carries a self-consistent
+identity while frozen and sealed; the identity is derived from artifact state, not from
+an actor's name or standing.
+
+Hash commitments are indexes and mutation detectors, not preservation. The bundle keeps
+the exact bytes so a verifier can recompute both algorithms. If an algorithm weakens,
+later evidence may add a new external commitment to the preserved historical bytes. It
+MUST NOT rewrite the old manifest or claim that a digest alone retained the bytes.
+
+## 4. Finite self-closing seal
+
+A `VSTD-ARTIFACT-SEAL-1` envelope contains the complete seal payload, raw public key,
+signature, and seal identifier. The seal payload closes the artifact, content, freeze,
+exact freeze-manifest digests, key identifier, signature algorithm, and closure rule.
+
+Let `C(x)` be version 1 canonical JSON, `E` the complete envelope, and `Sign` Ed25519:
+
+```text
+E0 = E with signature_base64 = null and seal_id = null
+signature = Sign(private_key, C(E0))
+E1 = E with signature_base64 = signature and seal_id = null
+seal_id = dual_digest("vstd-seal-1", C(E1))
+```
+
+Verification reconstructs `E0`, verifies the signature with the carried public key,
+reconstructs `E1`, recomputes `seal_id`, and independently recomputes the freeze and
+payload bytes. The two explicit holes terminate the construction: no seal-of-seal chain
+is required, while a change to any closed field, signature, or identifier fails.
+
+The carried key proves only internal signature consistency. An attacker can substitute a
+whole self-consistent bundle and key. A relying party that needs continuity with an
+earlier coordinate MUST supply an expected `artifact_id`, expected key identifier, or
+separately verified external log/manifest entry. External anchoring is not part of
+self-closure and actor identity contributes no verdict weight.
+
+Duplicate copies of one seal deduplicate by `seal_id` and add no strength. A valid and an
+invalid seal remain `CONFLICTED`; placement or multiplicity cannot erase the invalid
+evidence.
+
+## 5. Thaw and lineage
+
+Thaw requires a cleanly verified seal. It copies the parent payload to a new writable
+path and emits a `VSTD-ARTIFACT-THAW-1` sidecar beside the descendant. The sidecar records
+the parent artifact, content, freeze, and seal identifiers. It is lineage metadata, not a
+seal. The requested descendant and sidecar paths must both be lexically absent; thaw never
+uses a preexisting symbolic link as permission to create or label its target. The parent
+remains unchanged.
+
+A sidecar's self-derived `thaw_id` establishes only internal agreement among its fields.
+Sidecar-only status is `NOT_ESTABLISHED`, even when current descendant bytes match the
+recorded artifact identifier. `THAWED_CLEAN` requires the actual supplied parent bundle to
+verify as cleanly `SEALED`; its artifact, content, freeze, artifact-kind, and media-type
+coordinates must equal the sidecar; and every sidecar seal identifier must remain valid on
+that parent. Later additional valid parent seals are permitted. A conflicted parent or any
+coordinate mismatch fails closed. Authoritative parent metadata—not sidecar metadata—is
+used for the established descendant comparison.
+
+`THAWED_CLEAN` means the current descendant matches that supplied, cleanly sealed parent.
+`THAWED_DIRTY` means the verified parent coordinates still agree but the descendant no
+longer does. Neither result proves that a verifier independently observed or authenticated
+the historical copy operation. That claim requires a separately signed, logged, attested,
+or otherwise mechanism-checked event. Without an expected artifact identifier, expected
+key identifier, or separately verified external log coordinate, a supplied parent proves
+internal parent consistency rather than external continuity.
+
+To produce a new frozen artifact, freeze the descendant into a new bundle and bind the
+sealed parent through `lineage`. This is an additive state transition; no operation edits
+or erases the parent.
+
+`bound_contexts` similarly binds the artifact identifiers of clean sealed context
+bundles. It does not interpret or validate their subject matter. A sealed realm descriptor,
+for example, remains only a bound declaration until a named realm or mapping verifier
+checks it.
+
+## 6. Results and artifact-first semantics
+
+| State | Meaning |
+|---|---|
+| `FROZEN_UNSEALED` | Exact bytes, manifest, identifiers, and guards recomputed; no valid seal was required or established. |
+| `NOT_ESTABLISHED` | A seal was required but none was established. |
+| `SEALED` | Freeze, guards, and at least one seal verified with no contradictory seal. |
+| `CONFLICTED` | Valid and invalid seal evidence coexist. |
+| `FAIL` | A checked structural, byte, guard, seal, or external-anchor condition failed. |
+| `THAWED_CLEAN` / `THAWED_DIRTY` | With an actual cleanly verified supplied parent whose exact recorded coordinates agree, a mutable descendant currently matches or differs from that parent. Historical execution of the copy remains `NOT_ESTABLISHED`. |
+
+A clean freeze or seal can earn bounded **TRUST** in integrity and closure. It earns no
+support for semantic correctness. Freezing does not stop **ROT** caused by staleness,
+revocation, supersession, broken dependencies, or changed admissibility. A clean preserved
+ancestor may lower mutation-related diagnostic priority, but **RUST** remains reverse
+diagnostic reachability rather than innocence, guilt, or causal localization.
+
+Realm and temporal claims follow the
+[realm and time-capsule architecture](https://github.com/TimeLordRaps/verifier/blob/main/docs/REALMS_AND_TIME_CAPSULES.md). A structural seal
+is atemporal at its core. It binds a realm descriptor only as context and does not prove
+that realm, its clocks, mappings, physical laws, or continuous closure.
+
+## 7. Public format and implementation
+
+The strict combined schema is published at
+[`artifact-control-1.schema.json`](https://timelordraps.github.io/verifier/schemas/artifact-control-1.schema.json).
+The Python application programming interface and `vstd artifact` commands are generated
+in the public reference. Ed25519 operations require the optional `seal` dependency extra;
+the base package retains zero required third-party runtime dependencies.
diff --git a/src/verifier/specifications/LADDER.md b/src/verifier/specifications/LADDER.md
index be23b3d..202db3d 100644
--- a/src/verifier/specifications/LADDER.md
+++ b/src/verifier/specifications/LADDER.md
@@ -1,41 +1,267 @@
-# The VSTD Ladder — what the numbers mean
+# The Verifier Standard (VSTD) verification complex — what the numbers mean
+
+> **Acronyms:** application programming interface (API); conjunctive normal form (CNF); Certificate Transparency (CT);
+> deletion resolution asymmetric tautology (DRAT); grounded decision certificate (GDC);
+> JavaScript Object Notation (JSON); National Institute of Standards and Technology (NIST);
+> nondeterministic polynomial time (NP); proof-carrying code (PCC);
+> World Wide Web Consortium provenance vocabulary (PROV); PROV data model (PROV-DM); Protect the Software (PS);
+> Request for Comments (RFC); reverse unit propagation (RUP); Boolean satisfiability problem (SAT);
+> Supply-chain Levels for Software Artifacts (SLSA); satisfiability modulo theories (SMT);
+> SMT library standard (SMT-LIB); Secure Software Development Framework (SSDF); The Update Framework (TUF);
+> unsatisfiable (UNSAT); World Wide Web Consortium (W3C).
**Status:** project specification (normative for numbering and composition)
**Editor:** TimeLordRaps
**License:** Apache-2.0
-VSTD specification numbers are **layers of verification depth**, not revisions of a single
-document. VSTD-3 does not supersede VSTD-1 any more than a floor supersedes its
-foundation.
+**Normative language:** The uppercase key words in this series are interpreted as
+described by [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and
+[RFC 8174](https://www.rfc-editor.org/rfc/rfc8174) only when they appear in all capitals;
+lowercase uses are ordinary prose.
+
+**Reader context:** [`Concept guide and intellectual precedents`](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md)
+
+VSTD records separate answers to separate verification questions about an identified
+claim, its evidence, the mechanism that checked it, and the bounds of that check. It does
+not collapse those answers into one universal “verified” label or confidence score. The
+questions, their evidence-bearing relations, and cumulative profiles over them form the
+VSTD **verification complex**.
+
+### Read this first: the number is a checklist position, not a strength score
+
+A numbered profile is a cumulative checklist on one axis. `VSTD-3`, for example, means
+that the required questions for `VSTD-1`, `VSTD-2`, and `VSTD-3` are each established by
+their own applicable evidence. It does **not** mean “assurance strength 3,” software
+version 3, or evidence that is three times stronger than `VSTD-1`.
+
+Here, **established** means that a named mechanism checked the exact required proposition
+against bound evidence under declared limits. A field, document, or actor merely saying
+that the proposition passed does not establish it.
+
+Consider one verification object with these separately recorded results:
+
+| Object-axis question | Current result |
+|---|---|
+| `VSTD-1` Claim Mechanics | established |
+| `VSTD-2` Verification Surface | `UNKNOWN` |
+| `VSTD-3` Substrate Accountability | established |
+
+Its **object profile depth is 1**. The cumulative checklist cannot skip the missing
+`VSTD-2` result. The `VSTD-3` coordinate evidence remains recorded and useful, but it does
+not fill the `VSTD-2` gap or make profile 3 satisfied.
+
+The Graph axis applies a different cumulative checklist to a collection of artifacts and
+their recorded relations. `VSTD-3` and `VSTD-Graph-3` therefore ask different questions;
+the shared number does not make them equivalent.
+
+In one sentence: a **closure coordinate** is one verification question, a **numbered
+profile** is a cumulative checklist of those questions on one axis, and **profile depth**
+is the largest uninterrupted prefix of that checklist that is established.
+
+### Terminology contract
+
+The rest of the Standard uses the following terms precisely:
+
+| Term | Plain meaning | Important boundary |
+|---|---|---|
+| **Closure coordinate** | One named verification question and its failure class, such as Claim Mechanics or Refutability. | Closure is scoped to that question. VSTD-2 surface closure, Graph provenance closure, refutability closure, and artifact-seal structural closure are different results. |
+| **Numbered profile** | A cumulative checklist selected by `VSTD-N` or `VSTD-Graph-N`. Profile `N` requires its named coordinate and every earlier coordinate on the same axis. | A profile number is not a software revision, spatial layer, confidence score, or substitute for the underlying results. |
+| **Profile axis** | One ordered family of cumulative checklists. VSTD has an object axis and a Graph axis. | Equal numbers on different axes do not identify equivalent or interchangeable results. |
+| **Object profile depth** | For one verification object, start at `VSTD-1` and count upward only while every required coordinate remains established. The last uninterrupted number is its depth. | Depth is a compact summary of separately established results, not a new verdict, evidence-strength rating, or permission to ignore a later established coordinate after an earlier gap. |
+| **Candidate Graph profile** | The greatest Graph checklist position satisfied by the current caller-supplied ratings. | The current calculation is `NOT_ESTABLISHED` because those ratings are not evidence-bound. It is not a verified Graph profile. |
+| **Evidence-bound Graph profile** | The greatest Graph checklist position obtained after rerunning exact member, ancestor, and edge rating mechanisms from content-addressed evidence. | It is established only under the named mechanisms, trust roots, evidence, bounds, lifecycle view, and conflict state. |
+| **VSTD-4 rung** | One of the fourteen ordered refutability obligations `4.1` through `4.14`. | “Rung” names only this internal sequence, never a top-level VSTD profile. |
+| **Verification order** | One adjacent meta-verification order in the VSTD-2 geometry model. | The compatibility names `VerificationLayer` and `verification_layers` do not denote numbered VSTD profiles. |
+| **Level** | A retained word in an explicitly named external taxonomy or compatibility identifier, including `ReproducibilityLevel`, `AvailabilityLevel`, `graph_level`, and serialized Graph `level` fields. | In Graph compatibility identifiers, the value is the candidate Graph profile number; “level” is not the governing name for a VSTD profile. |
+| **Layer** | An implementation, protocol, or physical stack whose parts are ordered by containment. | It does not name `VSTD-N` or `VSTD-Graph-N`; historical paths such as `verifier.layer4` remain compatibility identifiers only. |
+| **Tier** | A declared checker-cost class in `VSTD4-GDC-1`. | It is not a VSTD profile, evidence-strength rating, or actor rating. |
+
+“Profile” must also be qualified when confusion is possible: **numbered profile**, **receipt
+profile**, **application profile**, or **geometry profile**. Likewise, “depth” must be
+qualified as object profile depth, VSTD-4 normative or candidate depth, or lineage
+topological depth. The retained `LADDER.md` filename is a stable document path, not the
+governing topology; this document defines a verification complex.
+Current public serialized identifiers, fields, class names, functions, and module paths
+retain their exact compatibility spelling; adjacent prose supplies the precise meaning.
+Profiles are therefore **requirement-set coordinates**, not spatial layers. A profile is
+satisfied only when a named mechanism has bound evidence for every required fact; Boolean
+SAT over caller-supplied assertions establishes only a candidate formula result.
---
## 1. The governing idea
-Each layer names a distinct verification question and a distinct failure class. The
-ordering is a composition rule, not logical entailment between layers.
+Each closure coordinate names a distinct verification question and failure class. Profile
+ordering is a composition rule, not logical entailment between coordinates.
+
+The nearest familiar security analogy is
+[defense in depth](https://en.wikipedia.org/wiki/Defense_in_depth_%28computing%29 "Wikipedia orientation; primary references are mapped below"),
+but the analogy is limited: VSTD closure coordinates are separately evidenced questions, not
+interchangeable controls whose mere quantity establishes assurance. Decomposing assurance
+into named components also has precedent in the Common Criteria, while VSTD deliberately
+uses different coordinates, evidence rules, and conformance semantics.
-**Evidence for one layer never supplies evidence for another layer.** In particular,
-layer-4 evidence does not supply, imply, upgrade, or repair layer 3, 2, or 1. A reported
-depth of `N` is only shorthand for `N` separately checked results, one for each layer
-from 1 through `N`.
+**Evidence for one closure coordinate never supplies evidence for another.** In particular,
+Refutability evidence does not supply, imply, upgrade, or repair Substrate Accountability,
+Verification Surface, or Claim Mechanics evidence. A reported object profile depth of `N`
+is only shorthand for the separately checked results required by profiles 1 through `N`.
-Reflection and metalanguage are useful design analogies for asking what a given
+Reflection and [metalanguage](https://en.wikipedia.org/wiki/Metalogic "Wikipedia orientation; not a proof of the VSTD verification complex")
+are useful design analogies for asking what a given
verification surface leaves unexamined. VSTD does not claim that Tarski's
-undefinability theorem proves this ladder, that adjacent layers form formal
-metalanguages, or that a lower-layer implementation is logically incapable of
-describing another layer's failure. The normative requirement is narrower: an
+[undefinability theorem](https://en.wikipedia.org/wiki/Tarski%27s_undefinability_theorem "Wikipedia orientation; the theorem does not derive this verification complex")
+proves these profile coordinates, that adjacent profiles form formal
+metalanguages, or that an earlier-profile implementation is logically incapable of
+describing another coordinate's failure. The normative requirement is narrower: an
implementation MUST NOT treat success on one question as evidence for a different
question.
+### 1.1 Artifact-first causal provenance orientation
+
+VSTD evaluates bounded propositions about computational processes represented by
+identified software, executions, evidence, and resulting artifacts. It does not evaluate
+whether an actor is good, bad, reputable, or worthy of trust. Standing alone, an actor's
+identity, popularity, repetition, or reputation MUST NOT strengthen an artifact-bound
+result. A named mechanism MAY establish an exact attribution, authorization, or separation
+proposition by checking the required identity evidence; that result remains an adjacent
+proposition and MUST NOT promote an unrelated computational claim.
+
+**Zero identity** means zero identity-derived verdict weight, not anonymity or absence of
+identifiers. **Zero knowledge** means zero unevidenced knowledge is presumed: without a
+mechanism-earned result for the exact proposition, its state remains `UNKNOWN`. This
+architectural zero-knowledge rule MAY be enclosed by cryptographic zero knowledge when a
+witness must remain confidential. That enclosure MUST bind the exact software or program
+coordinate, predicate, public commitments, output, proof parameters, and verification
+mechanism while revealing no more witness information than its declared proof statement.
+Cryptographic zero knowledge MUST be claimed only when a named proof system establishes
+that property under explicit assumptions; a digest or undisclosed input alone is not such
+a proof. The resulting support is bearer- and artifact-bound, never prover-identity-bound.
+**Actor** and **artifact** remain contextual roles, not permanent entity classes: software
+can be an artifact when created, versioned, or evaluated and an actor when it executes a
+transformation.
+
+The capitalized terms **TRUST**, **RUST**, and **ROT** are formal VSTD semantic names, not
+acronyms, numbered-profile receipt verdicts, actor ratings, scalar scores, or references to
+the Rust programming language. They serialize as typed event kinds only in the non-receipt
+`VSTD-GRAPH-ASSURANCE-1` mechanism log. The same bound development graph and its time-indexed lifecycle carry three
+distinct relations:
+
+```text
+development: ancestor artifact --TRUST through a checked transformation--> descendant
+lifecycle: recorded TRUST --ROT under typed current-state evidence--> reassessment
+diagnosis: descendant deviation --RUST memetic causal backtrace--> ancestor candidates
+```
+
+**Memetic propagation** is the transmission of claim and evidence state through recorded
+developmental provenance. The genetic or viral language names this inheritance mechanic:
+TRUST moves forward into descendant claim space; RUST moves backward toward recorded
+ancestor states; ROT changes the current admissibility of previously recorded support. It
+does not claim biological transmission or make identity and reputation sources of
+assurance.
+
+**TRUST** is positive support earned when a named mechanism checks an exact
+artifact-bound process obligation under declared evidence, specification, bounds, and
+trust roots. It moves parent-to-child only across a declared creation or dependency edge
+whose relevant transformation obligations pass. Applicable support composes by
+intersection and is capped by the weakest required parent or edge; it is never added,
+averaged, voted, or converted into actor standing. Every child MUST still discharge its
+new predicates, transformations, boundaries, and evidence obligations. A declared trust
+root is an explicit dependency and stopping boundary, not actor TRUST.
+
+The reference event mechanism realizes that rule edge by edge. Each TRUST event binds the
+historical Graph digest, one exact transformation, its complete input artifact set, one
+output artifact, and the exact prerequisite TRUST event for every derived input. A
+descendant event is current only while every recursively required event, input, output,
+and transformation remains admissible and free of an admissibility-blocking conflict. A
+status-conflict resolution projects its selected state into the current view: `VALID` or
+`COMPLETED` may restore the affected route, while `REVOKED`, `FAILED`, or another
+inadmissible state cannot. Resolving an arbitrary predicate selects a retained value but
+does not establish its admissibility effect, so the route remains blocked. The current
+reference runtime implements no general non-status admissibility-effect mechanism.
+Alternate or duplicate paths remain distinct recorded routes; their count supplies no
+added strength or witness independence.
+
+**ROT** is typed, time-indexed degradation of the current admissibility of recorded TRUST.
+It requires exact lifecycle or dependency evidence, such as expiry under a declared
+freshness bound, `STALE`, `CHALLENGED`, `REVOKED`, `SUPERSEDED`, or an invalidated required
+coordinate. Wall-clock passage, age, or popularity alone MUST NOT create ROT. ROT MUST NOT
+rewrite an immutable historical receipt or imply that its historical result was false. It
+may require reassessment of dependent descendants, but any resulting status change still
+requires its named policy or mechanism.
+
+**RUST** is the inverse-TRUST diagnostic mechanic: a typed trace created by an observed
+descendant deviation from a declared expectation. It moves child-to-parent only through
+historically recorded contributing creation, input, or transformation paths. Current
+revocation, challenge, staleness, or conflict can remove a route from current TRUST without
+erasing it from historical diagnostic ancestry. The inverse is directional and diagnostic,
+not arithmetic: TRUST and RUST never cancel. Distinct comparable backtraces may concentrate
+on a shared ancestor and prioritize it for falsification or diagnostic examination.
+Transferred RUST establishes ancestral reachability, not current admissibility, direct
+observation, falsehood, or causal responsibility; localization requires additional
+intervention, ablation, independently bound execution evidence, or an equivalent declared
+mechanism.
+
+Reference causal localization MUST select one exact passing RUST event, bind that event's
+digest and the exact descendant-deviation proposition digest, confirm the selected artifact
+is among that event's recorded ancestors, and preserve those coordinates through replay.
+
+**BLAME** and **GUILT** are bounded artifact-relative diagnostic results, not opposite
+directions on the Graph. BLAME requires a named mechanism to establish that an exact
+artifact bears responsibility for or materially contributed to an exact localized
+deviation. GUILT requires three separately bound passing components whose coordinates agree:
+(1) responsibility or material contribution by exact artifact A for exact localized
+deviation D; (2) applicability to A of exact obligation O under its declared scope,
+assumptions, exclusions, roots, and bounds; and (3) violation by A of that same O relative to
+that same D and applicable scope. The final GUILT proposition MUST bind the exact obligation
+coordinate, causal-localization event digest, and all three component digests. A single
+compound mechanism MAY check the components in one invocation only when it emits three
+separately bound evaluations; one opaque combined result or nonempty obligation string is
+insufficient. An existing passing BLAME event can supply the responsibility component only
+when its exact event digest and all coordinates match. Neither term concerns actor morality,
+character, identity, reputation, automatic legal liability, or social scoring. Exoneration,
+innocence, obligation satisfaction, absence of hidden contributors, or not-guilty conclusions
+require their own exact propositions and mechanisms; a missing component remains `UNKNOWN`,
+not evidence of the opposite. The localization event transitively binds the selected RUST
+event and exact deviation, so neither result can float across two deviations on the same
+descendant.
+
+The word *causal* is required here for recorded developmental and provenance causality:
+the graph states which artifacts and transformations produced later claim architecture.
+Propagation across those causal-provenance edges does not by itself establish
+intervention-level physical causality, causal localization, responsibility, or guilt.
+
+TRUST, ROT, and RUST MUST remain separate. They do not cancel, form one scalar score, or
+flow in the opposite direction as inherited truth, decay, or guilt. `UNKNOWN` and
+`CONFLICTED` support or lineage MUST remain visible and MUST NOT become a clean signal.
+`VSTD-GRAPH-ASSURANCE-1` now serializes an additive, hash-chained reference event log with
+the complete historical Graph, exact proposition bindings, and embedded evidence bytes.
+`AssuranceLedger` implements mechanism-earned forward TRUST edge by edge, typed ROT,
+challenge-ledger status projection, reverse RUST reachability, unique-descendant structural
+concentration, additive conflict declaration and resolution, explicit causal localization,
+separately bound responsibility/applicability/violation components, and component-composed
+artifact-relative GUILT. Duplicate paths and repeated records remain set-valued and earn no
+strength. `recheck_assurance_log` reconstructs the historical Graph, rehashes the embedded
+evidence, reruns every exact component mechanism—including one-invocation compound groups as
+such—reproduces the event hash chain, and compares the derived current view. A deployment
+still supplies the proposition-specific mechanisms: the event format and dispatcher do not
+establish real-world obligation applicability, legal culpability, a universal support-transfer
+algebra, or causality from topology.
+
+Artifact freezing and sealing are bounded mechanisms under this orientation, specified
+separately in [`ARTIFACT_CONTROL.md`](ARTIFACT_CONTROL.md). A verified freeze preserves
+and recomputes exact bytes; a verified seal earns structural closure for those bytes. It
+does not earn semantic correctness, prevent ROT, localize RUST, create actor TRUST, or
+supply any numbered profile. A sealed realm or temporal descriptor remains a bound input
+until its own mapping, continuity, or transition verifier checks the exact proposition.
+
---
-## 2. The object ladder
+## 2. The object profile axis
VSTD proper governs the verification of **one object**. Call this verification
*mechanics*.
-| Layer | Name | Closes | Does not establish |
+| Numbered profile | Required closure coordinate | Closes | Does not establish |
|---|---|---|---|
| **1** | Claim mechanics | A malformed or tampered statement | Whether the claim applies where it is being applied |
| **2** | Verification surface | A verdict leaking beyond the coordinate actually verified | Whether the evidence behind it is real |
@@ -43,18 +269,23 @@ VSTD proper governs the verification of **one object**. Call this verification
| **4** | Refutability | A claim unfalsifiable in principle by any outside party | Whether the parties who could check are independent |
| **5** | Witness corroboration | Pseudo-independence — witnesses sharing the declarant's trust root | — |
-### 2.1 The self-discernability boundary
+### 2.1 The single-declarant boundary
+
+**A single declarant can in principle produce the evidence required by profiles 1 through
+4.** No second party is required merely to create those bounded inputs and mechanisms.
-**Layers 1 through 4 are self-discernable.** A declarant can establish them alone, with
-no second party in existence.
+**Profile 5 is not.** It requires another party to exist, to act, and to be independent.
-**Layer 5 is not.** It requires another party to exist, to act, and to be independent.
+VSTD-1 records the claim-mechanics status of actor independence but cannot infer it from
+two runs or matching artifacts. VSTD-5 requires the corroborating witness procedure that
+uses such separately evidenced actor participation; recording a field is not witnessing.
-That transition between 4 and 5 is the most important boundary in the ladder. Layer 4
-asks *could a stranger check this?* Layer 5 asks *did one, and were they actually a
-stranger?* The first is a property of the claim. The second is a property of the world.
+That transition between profiles 4 and 5 is the most important object-axis boundary.
+Refutability asks *is a bounded outside check possible?* Witness Corroboration asks *was
+one performed, and are the required separation seams evidence-bound?* The first is a
+property of the claim surface. The second requires additional evidence about an execution.
-An implementation MUST NOT report a layer-5 property on the basis of layer-4 evidence.
+An implementation MUST NOT report a profile-5 property on the basis of Refutability evidence.
Preparing to be checked is not being checked.
---
@@ -65,9 +296,12 @@ VSTD-Graph governs the verification of a **collection** of objects. Call this
verification *dynamics*.
The two axes are parallel but coupled: a collection's dynamics are constrained by its
-members' mechanics, and by the provenance edges between them.
+members' mechanics, and by the
+[provenance](https://en.wikipedia.org/wiki/Data_provenance "Wikipedia orientation; see W3C PROV-DM and supply-chain references below")
+edges between them. The implemented N-ary representation is a
+[hypergraph](https://en.wikipedia.org/wiki/Hypergraph "Wikipedia orientation; not a claim of complete real-world lineage").
-| Layer | Name | Collection-level closure |
+| Numbered profile | Required closure coordinate | Collection proposition |
|---|---|---|
| **Graph-1** | Recorded lineage | members and transformations are represented |
| **Graph-2** | Bounded collection surface | scope does not leak across the collection |
@@ -75,27 +309,42 @@ members' mechanics, and by the provenance edges between them.
| **Graph-4** | Refutable transformation closure | challenges compose across hyperedges |
| **Graph-5** | Corroborated verification network | member and edge witnesses are independently corroborated |
-A collection `C` holds at Graph layer `N` only if all four conditions hold:
+A collection `C` satisfies candidate Graph profile `N` only if all four conditions hold:
-1. **Membership floor** — every member is at object layer ≥ N.
-2. **Provenance closure** — every ancestor reachable from any member is at layer ≥ N.
-3. **Status admissibility** — no ancestor is `REVOKED`, `CHALLENGED`, `STALE`, or
- `UNKNOWN`.
-4. **Edge evidence** — the transformation hyperedges themselves carry layer-N evidence.
+1. **Membership floor** — every member rating is at object profile ≥ N.
+2. **Provenance closure** — every ancestor reachable from any member is rated at object
+ profile ≥ N.
+3. **Status admissibility** — no ancestor is `REVOKED`, `CHALLENGED`, `STALE`,
+ `UNKNOWN`, or subject to an unresolved `CONFLICTED` record.
+4. **Edge evidence** — the transformation hyperedges themselves carry profile-N ratings.
Condition 2 is what a plain minimum over members misses. Condition 4 is what makes this
dynamics rather than aggregation: **a graph is only as verified as its edges**, and an
-unevidenced edge between two layer-5 artifacts does not yield a layer-5 collection.
+unevidenced edge between two profile-5 artifacts does not yield a Graph-5 collection.
-The level is **computed, never declared**:
+The candidate Graph profile number is **computed from object and edge ratings, never
+declared**:
```
-graph_level(C) = max { N : CNF_N(C) is satisfiable }
+candidate_graph_profile(C) = max { N : CNF_N(C) is satisfiable }
```
-The reference implementation searches 5→1. At a result below 5, the grounded
-`FAIL` certificate for `N+1` is the explanation of the ceiling. A level without
-that certificate is a declaration and is non-conforming.
+The compatibility API `graph_level` implements that function and the frozen Graph receipt
+stores its number in a `level` field. The reference implementation searches 5→1 and
+certifies its Boolean encoding. Its current rating inputs are caller-supplied, so it reports
+a **candidate Graph profile** with
+`conformance_status = NOT_ESTABLISHED`; the certificate proves the computation over
+those inputs, not the validity of the ratings. At a result below 5, the grounded `FAIL`
+certificate for profile `N+1` explains that candidate ceiling. Graph conformance additionally
+requires evidence-bound ratings under the applicable object and edge profiles.
+`establish_graph_level` supplies that path: it rehashes embedded evidence, reruns the exact
+registered rating mechanism for every member, ancestor, and reached edge, then recomputes
+and kernel-checks the Graph certificate. Each rating proposition binds a digest over the
+historical Graph bytes, deduplicated member set, collection identifier, and claim binding;
+neighboring collection or topology evidence therefore contributes zero. Missing,
+non-integer, or non-passing bindings also contribute zero and prevent conformance. Profile
+zero never receives `ESTABLISHED`. The record builder recomputes before serialization, and
+the rechecker preserves offline replay.
---
@@ -108,17 +357,22 @@ the third is the load-bearing one.
VSTD does not classify every receipt as an NP certificate. Specific bounded formats,
including `VSTD4-GDC-1`, define a finite decision problem, a certificate language, and
-an independent checker. Complexity claims apply only to such a defined formal problem.
+a checker implemented separately from the producer path. Complexity claims apply only
+to such a defined formal problem; checker separation alone does not establish distinct
+actors.
Other receipt fields may be signed declarations, hashes, measurements, or references
whose meaning depends on explicitly named trust roots.
The useful engineering asymmetry is concrete rather than universal: when a result can
-carry a smaller independently checkable artifact instead of requiring the original
+carry a smaller consumer-checkable artifact instead of requiring the original
computation, VSTD preserves that artifact and its verification bounds.
### 4.2 Bounded admission uses CNF
-The reference admission procedures encode finite, bounded policy questions as CNF.
+The reference admission procedures encode finite, bounded policy questions as
+[conjunctive normal form](https://en.wikipedia.org/wiki/Conjunctive_normal_form "Wikipedia orientation; the implemented format is finite CNF")
+(CNF) for the
+[Boolean satisfiability problem](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem "Wikipedia orientation; SAT success establishes only the encoded formula").
CNF is not identical to 3-SAT. A finite CNF satisfiability instance can be transformed
in polynomial time into an equisatisfiable 3-CNF instance, using auxiliary variables
where required. VSTD does not need that transformation for every checker and does not
@@ -136,7 +390,7 @@ A future claim may cover a finite enumerated world if its observation boundary a
completeness mechanism are declared and checked. It still MUST NOT be widened into a
claim about unobserved physical activity.
-This is why the ladder tops out at corroboration rather than proof of global absence. Layer 5
+This is why the object profile axis tops out at corroboration rather than proof of global absence. Profile 5
does not detect hidden work. It makes the *independence status* of declared work legible,
and leaves the undeclared remainder named and quantified rather than silent.
@@ -145,7 +399,7 @@ and leaves the undeclared remainder named and quantified rather than silent.
## 5. Certificates for refusals
This section applies to the finite propositional decision procedures used by the
-reference layer-4 implementation.
+reference Refutability implementation.
A satisfiable result already carries its certificate: the model. Anyone can evaluate it
against the clause set without a solver.
@@ -154,8 +408,12 @@ An unsatisfiable result, by default, carries nothing but the solver's word.
For a fail-closed standard, **refusals are the most consequential output**. A standard
whose passes are checkable and whose refusals are not has its assurance backwards.
-Layer 4 therefore requires a refutation certificate — a clausal proof, verifiable by
-reverse unit propagation, checkable without re-solving.
+The Refutability coordinate therefore requires a refutation certificate — a clausal proof, verifiable by
+[reverse unit propagation](https://en.wikipedia.org/wiki/Unit_propagation "Wikipedia orientation; VSTD implements a bounded RUP checker"),
+checkable without re-solving. This follows the same producer-certificate/consumer-checker
+engineering asymmetry as
+[proof-carrying code](https://en.wikipedia.org/wiki/Proof-carrying_code "Wikipedia orientation; VSTD does not inherit PCC's safety theorem"),
+while using a narrower certificate language.
Resolution proofs have exponential lower bounds for some formula families. A
conforming implementation therefore MUST declare a bound and MUST answer `UNKNOWN`
@@ -165,46 +423,79 @@ An `UNKNOWN` is never a pass and never an unsatisfiability claim.
Reference implementation: `verifier.core.refutation`.
-### 5.1 The internal VSTD-4 ladder
+### 5.1 The internal VSTD-4 rung sequence
VSTD-4 contains fourteen ordered rungs, from decision certification through
semantic binding, anti-equivocation, bounded portable checking, availability,
-precommitment, challenge handling, degradation, and compositionality. Its depth
+precommitment, challenge handling, degradation, and compositionality. Its normative depth
is computed:
```
vstd4_depth(claim) = max { k : CNF_4k(claim) is satisfiable }
```
-The certificate for rung `k+1` explains a partial depth. Only depth 14 admits a
-claim to any VSTD-5 procedure. See `VSTD-4.md` for the normative rung graph and
-`VSTD4-GDC-1` format.
+The certificate for rung `k+1` explains a partial VSTD-4 normative depth. Only established
+VSTD-4 conformance at normative depth 14 admits a claim to any VSTD-5 procedure. The current
+reference `vstd4_depth` function instead computes a structural candidate from
+caller-supplied rung references, labels conformance `NOT_ESTABLISHED`, and never admits
+VSTD-5. `establish_vstd4` reruns exact VSTD-1/2/3 and rung bindings and may report
+`EVIDENCE_BOUND` / `ESTABLISHED` only when every mechanism and the independent kernel pass.
+See `VSTD-4.md` for the normative rung graph and `VSTD4-GDC-1` format.
---
-## 6. Composition — layers do not supply or substitute
+## 6. Composition — closure coordinates do not supply or substitute
-Layer results may be composed into a depth report. They do not replace, entail, or
-supply one another.
+Closure-coordinate results may be composed into an object profile-depth report. They do
+not replace, entail, or supply one another.
-- Layer 4 without layer 3 certifies a claim whose evidence source is unaccountable.
-- Layer 5 without layer 4 solicits witnesses for a claim no witness could check.
-- Layer 2 without layer 1 scopes a statement whose integrity is unestablished.
+- Refutability without Substrate Accountability certifies a claim whose evidence source is unaccountable.
+- Witness Corroboration without Refutability solicits witnesses for a claim no witness could check.
+- Verification Surface without Claim Mechanics scopes a statement whose integrity is unestablished.
-An implementation reporting aggregate depth *N* MUST present separately checkable
-evidence for every layer from 1 through *N*. Conformance may also be reported for an
-individual layer without claiming aggregate depth. Conformance profiles are declared
-per layer, following VSTD-3 §7.
+An implementation reporting object profile depth *N* MUST present separately checkable
+evidence for every required coordinate in profiles 1 through *N*. Conformance may also be
+reported for one coordinate without claiming cumulative profile depth. Incremental
+Substrate Accountability profiles are declared in VSTD-3 §7.
"Higher is more protected" is true only in the sense that more classes of failure are
-closed. It never means the lower layers became unnecessary.
+closed. It never means the prerequisite coordinates became unnecessary.
---
## 7. Numbering
-- **Specification layers are integers**: VSTD-1 … VSTD-5, VSTD-Graph-1 … VSTD-Graph-5.
-- **Repository releases use semantic versioning** and are independent of layer numbers.
+- **Numbered profiles use integers**: VSTD-1 … VSTD-5, VSTD-Graph-1 … VSTD-Graph-5.
+- **Repository releases use [semantic versioning](https://semver.org/)** and are independent
+ of profile numbers.
-A release version never implies a layer, and a layer never implies a release. See
-`WIRE_IDENTIFIERS.md` for frozen wire identifiers and the historical public filenames.
+A release version never implies a numbered profile, and a numbered profile never implies a
+release. See
+`WIRE_IDENTIFIERS.md` for serialized receipt identifiers and historical public filenames.
+
+---
+
+## 8. Intellectual lineage and adjacent precedents
+
+The verification complex is VSTD project architecture; no cited work proves that these five
+coordinates on either axis are
+necessary, sufficient, complete, or uniquely ordered. The references below show that its
+individual design pressures have established precedents in security engineering,
+provenance, reproducible systems, and proof checking. The
+[`concept guide`](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md) provides definitions, additional
+sources, and explicit non-equivalences.
+
+| VSTD pressure | Adjacent precedent | What the precedent contributes—and does not |
+|---|---|---|
+| Separate failure surfaces and fail-closed defaults | Saltzer and Schroeder, [*The Protection of Information in Computer Systems*](https://web.mit.edu/Saltzer/www/publications/pubs.html) | Classic principles include fail-safe defaults, complete mediation, separation of privilege, and least common mechanism. They motivate separation; they do not derive VSTD's coordinate count. |
+| Named assurance components | Common Criteria, [Part 3: Security assurance components](https://www.commoncriteriaportal.org/files/ccfiles/CC2022PART3R1.pdf) | Demonstrates established componentized assurance and assurance packages. VSTD is not a Common Criteria evaluation or an Evaluation Assurance Level. |
+| Stable cryptographic representations | [RFC 8785: JSON Canonicalization Scheme](https://www.rfc-editor.org/rfc/rfc8785.html) | Shows why JSON used as cryptographic input needs invariant representation. VSTD formats retain their own declared canonicalization rules. |
+| Recorded entities, activities, and agents | W3C [PROV-DM](https://www.w3.org/TR/prov-dm/) | Supplies an interoperable provenance model adjacent to the Graph axis. VSTD-Graph is not a PROV implementation and does not infer complete history. |
+| Software materials, builders, steps, and products | [in-toto specification v1.0](https://in-toto.io/docs/specs/) and [SLSA v1.2](https://slsa.dev/spec/v1.2/) | Establish supply-chain provenance and attestation precedents. VSTD may bind their evidence but cannot manufacture their authorization or assurance level. |
+| Preserved release and provenance evidence | NIST [Special Publication (SP) 800-218 SSDF 1.1](https://doi.org/10.6028/NIST.SP.800-218) | Protect the Software practices PS.3.1 and PS.3.2 call for preserving releases and provenance and enabling integrity verification. They do not certify a VSTD receipt. |
+| Independent recreation | Reproducible Builds, [formal definition](https://reproducible-builds.org/docs/definition/) | Grounds the special case where another party recreates specified artifacts from declared inputs and instructions. Reproducibility does not establish every semantic claim. |
+| Producer-supplied portable certificates | Necula, [*Proof-Carrying Code*](https://doi.org/10.1145/263699.263712) | Establishes the pattern of an untrusted producer supplying a proof checked under a declared policy. VSTD uses the pattern beyond code safety without inheriting PCC's theorem. |
+| Consumer-checked UNSAT results | Wetzler, Heule, and Hunt, [*DRAT-trim*](https://www.cs.cmu.edu/~mheule/publications/drat-trim.pdf) | Establishes practical checking of clausal unsatisfiability proofs rather than trusting solver output. VSTD's implemented RUP format is narrower than DRAT. |
+| A first-class refusal to fabricate a Boolean answer | [SMT-LIB Standard 2.7](https://smt-lib.org/papers/smt-lib-reference-v2.7-r2025-04-09.pdf) | Its response grammar includes `sat`, `unsat`, and `unknown`. VSTD independently defines a richer status system with the same fail-closed pressure. |
+| Append-only public evidence and detectable equivocation | [RFC 9162: Certificate Transparency Version 2.0](https://www.rfc-editor.org/rfc/rfc9162.html) | Merkle proofs make log inclusion and consistency auditable while preserving explicit split-view limitations. VSTD additive receipts are analogous, not a CT implementation. |
+| Freshness, rollback, freeze, and compromise recovery | [The Update Framework specification](https://theupdateframework.github.io/specification/latest/) | Demonstrates that authentic old data is not automatically current data. VSTD does not implement TUF, but likewise keeps freshness and revocation distinct from byte identity. |
diff --git a/src/verifier/specifications/VSTD-1.md b/src/verifier/specifications/VSTD-1.md
new file mode 100644
index 0000000..bb928a7
--- /dev/null
+++ b/src/verifier/specifications/VSTD-1.md
@@ -0,0 +1,192 @@
+# Verifier Standard (VSTD)-1 — Claim Mechanics
+
+> **Acronyms:** artificial intelligence (AI); conjunctive normal form (CNF); directed acyclic graph (DAG);
+> Davis-Putnam-Logemann-Loveland (DPLL); International Organization for Standardization (ISO);
+> JavaScript Object Notation (JSON); Request for Comments (RFC); Boolean satisfiability problem (SAT);
+> Secure Hash Algorithm 256-bit (SHA-256); satisfiability modulo theories (SMT); trusted computing base (TCB);
+> Coordinated Universal Time (UTC); Unicode Transformation Format, 8-bit (UTF-8).
+
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-1 on the object axis; required closure coordinate: Claim Mechanics (see `LADDER.md`)
+**Receipt serialization:** `schema_version = "VSTD-1"`; see `WIRE_IDENTIFIERS.md`
+**Status:** Project Specification with Implemented Reference Subset
+**Maintainer:** TimeLordRaps
+**Date:** 2026-08-21
+
+---
+
+## 1. Purpose & Thesis
+
+VSTD specifies infrastructure for consequential computational claims to carry
+evidence checkable outside its producer. Conformance is defined by this document, not by
+the identity of its maintainer.
+
+Modern AI systems, scientific simulators, and autonomous code generators routinely
+produce complex assertions without an attached, machine-checkable audit trail showing
+what evidence is offered for those claims. **VSTD-1** is a project
+specification for representing claims, capturing runtime provenance, structuring
+machine-readable verification receipts, defining reproduction-fidelity states, and
+separating trusted computing bases from untrusted outputs. It is not a consensus or
+accredited standard.
+
+---
+
+## 2. Scope & Boundaries
+
+### 2.1 What VSTD-1 Covers
+- **Software Artifacts**: Deterministic test execution, static invariant validation, schema conformance.
+- **Formal & Logic Artifacts**: Bounded propositional entailment, derivation graphs,
+ acyclicity checks, and grounding invariants. The current reference subset implements
+ a minimal propositional DPLL path; it does not implement general SMT verification.
+- **AI & Autonomous Agents**: Bounded input/output constraints, zero-trust admission policies, and execution traces.
+- **Scientific Simulation**: Invariant checking, exactness bounds, and deterministic reproduction traces.
+
+### 2.2 What a VSTD Verification Claim Does NOT Imply
+1. **Universal Truth**: Verification is strictly relative to the declared formal system, input formula, and explicit scope.
+2. **Unbounded Safety**: A verified component does not guarantee overall system safety if surrounding orchestration or unmodeled environmental dynamics fail.
+3. **Unchecked Prose**: Non-extracted, unverified natural language outside the formal translation grammar is not certified.
+
+---
+
+## 3. Epistemic Ontology & Claim Statuses
+
+Claims conforming to this specification must carry one of the following explicit status
+labels. Producers and validators MUST downgrade or challenge a claim when applicable
+evidence is missing or falsified. A historical receipt is immutable: correction is an
+additive record rather than an in-place rewrite.
+
+| Status | Definition |
+| :--- | :--- |
+| `DEMONSTRATED` | The claim is backed by executable tests or formal proofs that pass in a reproducible environment with recorded execution coordinates. Actor independence is a separate claim. |
+| `BENCHMARKED` | Quantitative performance or accuracy metrics have been empirically measured against a defined reference baseline. |
+| `SUPPORTED` | Theoretical derivation or empirical evidence is established, but automated end-to-end continuous verification is partial. |
+| `IMPLEMENTED_UNVALIDATED` | Code or logic exists on disk, but automated end-to-end verification has not yet run or passed. |
+| `INDETERMINATE` | Evidence is ambiguous, supporting leaves are unspecified, or solver execution timed out. |
+| `UNSUPPORTED` | No valid empirical or formal evidence is attached to the proposition. |
+| `FALSIFIED` | An executable check, counterexample, or evidence-bound audit refuted the claim. |
+| `HYPOTHESIS` | A stated conjecture intended for experimental falsification. |
+| `LONG_RANGE_OBJECTIVE` | A strategic or architectural aspiration requiring substantial future R&D. |
+
+---
+
+## 4. Claim Representation Schema
+
+A canonical claim record contains:
+- `id`: Unique identifier (e.g., `VFY-000001`).
+- `title`: Short human-readable summary.
+- `statement`: Precise, bounded technical claim.
+- `status`: Verification status from the ontology above.
+- `scope`: Bounded operational domain.
+- `limitations`: Explicit list of assumptions, bounds, and exclusions.
+- `falsification_condition`: Explicit condition under which the claim is considered refuted.
+- `last_verified`: ISO-8601 UTC timestamp of the most recent passing verification.
+
+---
+
+## 5. Independent Verification & Trusted Computing Base (TCB)
+
+To prevent self-referential confirmation bias (systems verifying their own uninspected
+outputs), VSTD-1 defines **independent-verification role separation** as a conformance
+requirement for claims labeled independent:
+
+```text
+Target System (Producer)
+ ↓ (Generates derivation / CNF / artifacts)
+Independent VSTD-Conformant Auditor
+ ↓ (Runs separately implemented DPLL solver + DAG grounding checker in isolated TCB)
+Structured VFY Receipt
+```
+
+Independence in this profile is a claim about distinct actors occupying the producer and
+checker roles. Two executions that return the same result do not prove that separate
+actors performed them; nor do two processes or machines. Those are artifact and runtime
+observations. Actor independence requires separately bound evidence, and it never
+strengthens the checked result merely because an actor is identified or trusted.
+
+### Trusted Computing Base Invariant
+An auditor described as independent must:
+1. Share zero solver state or runtime logic with the producer.
+2. Rely exclusively on a minimal, inspectable codebase (e.g. Python standard library).
+3. Explicitly declare its TCB components in every generated receipt.
+
+Running the bundled reference implementation does not by itself establish
+actor, implementation, or runtime independence. A receipt MUST state the actual
+separation achieved. If distinct actors are not evidenced, actor independence is
+`NOT_DEMONSTRATED` even when two results match. If producer and auditor share relevant
+logic or state, the result is still inspectable but MUST NOT be labeled independent on
+that seam.
+
+Serialized `EVIDENCED` status words and evidence-reference strings are declarations, not
+validated bindings. A runtime MUST derive independent verification only after an
+implemented validator resolves the referenced evidence, binds it to the producer and
+checker executions, and establishes distinct actors plus the claimed implementation and
+runtime seams. The VSTD 1.2.0 reference runtime implements no such adapter; it therefore
+treats externally supplied assertions as no stronger than `DECLARED`, rejects receipts
+that serialize them as `EVIDENCED`, and never emits `EVIDENCED`.
+
+---
+
+## 6. Reproducibility Taxonomy
+
+VSTD-1 defines a five-state reproduction-fidelity taxonomy. The public
+`ReproducibilityLevel` name is a compatibility identifier; it does not denote a numbered
+VSTD profile or assurance strength:
+
+1. `BITWISE_IDENTICAL`: Byte-for-byte exact match across all generated files, logs, and artifacts.
+2. `CONTENT_IDENTICAL`: Canonical JSON representation of stable verification payload matches exactly, ignoring volatile execution fields (timestamps, elapsed wall-clock ms, hostnames).
+3. `EVIDENCE_EQUIVALENT`: All checks, proofs, SAT assignments, and invariant bounds evaluate to the same truth values and proof certificates, though internal trace order or solver step counts may differ.
+4. `RESULT_EQUIVALENT`: Summary verification verdict (`VERIFIED`/`FALSIFIED`) and primary metrics agree within declared tolerance bounds.
+5. `SEMANTIC_REPRODUCTION`: The underlying formal proposition is sustained under a separately implemented translation or alternate solver. This does not establish distinct actors.
+
+---
+
+## 7. Canonical Receipt Specification & Hashing
+
+A VSTD-1 receipt separates **stable verification content** from **volatile execution metadata**.
+The receipt kind is explicit:
+
+```
+receipt.json
+├── schema_version: "VSTD-1"
+├── receipt_kind: "claim_mechanics"
+├── receipt_id: "VFY-XXXXXX"
+├── canonical_digest: SHA256(canonical_json(stable_payload))
+├── claim: {...}
+├── evidence: {...}
+├── target_result: {...}
+├── independent_audit: {...}
+├── provenance: {...}
+├── reproducibility: {...}
+└── execution_metadata: (volatile: timestamps, elapsed_ms, logs)
+```
+
+### Canonicalization Algorithm
+1. Extract stable fields (`schema_version`, `receipt_kind`, `receipt_id`, `claim`, `evidence`, `target_result`, `independent_audit`, `provenance_stable`, `reproducibility`).
+2. Serialize the VSTD-1 JSON subset with alphabetically sorted object keys, compact
+ separators `","` and `":"`, UTF-8 encoding, and no non-finite numbers. This
+ project-specific canonicalization is deterministic for the supported value subset;
+ VSTD-1 does not claim full RFC 8785 conformance.
+3. Compute `SHA-256` digest over the serialized bytes.
+4. The digest remains invariant across directory moves, path changes, and reformatting of human-readable reports.
+
+---
+
+## 8. Challenge & Correction Model
+
+1. Any party may submit a counterexample, failing test, or ungrounded leaf finding.
+2. A validator or reproducer returns failure when the bound content or declared rerun
+ does not match. It does not silently mutate a historical receipt.
+3. The maintainer or integrating system must publish an additive `FALSIFIED`,
+ `INDETERMINATE`, or challenged record, preserving the affected receipt's provenance.
+
+---
+
+## 9. Implementation Roadmap & Extensibility
+
+- **Currently Implemented Reference Subset**: Minimal propositional DPLL entailment,
+ derivation-graph acyclicity and grounding checks, Git/runtime provenance capture,
+ stable-payload digest validation, generic command receipts, and bounded
+ reproducibility comparison.
+- **VSTD-2 — Verification Surface**: verification geometry, residual-driven deconstruction, horizons, valences, and bounded self-closure. Its results remain separate from VSTD-1 claim-mechanics results.
+- **Unassigned Future Work**: Additional proof mechanisms, execution-environment binding, and cross-institutional proof-carrying software gates require separate scoped proposals and evidence. No future version number is reserved here.
diff --git a/src/verifier/specifications/VSTD-2.md b/src/verifier/specifications/VSTD-2.md
new file mode 100644
index 0000000..ebb0fb0
--- /dev/null
+++ b/src/verifier/specifications/VSTD-2.md
@@ -0,0 +1,363 @@
+# Verifier Standard (VSTD)-2 — Verification Surface
+
+> **Acronyms:** abstract syntax tree (AST); continuous delivery or deployment (CD); continuous integration (CI);
+> intermediate representation (IR); trusted computing base (TCB).
+
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-2 on the object axis; required closure coordinate: Verification Surface (see `LADDER.md`)
+**Receipt serialization:** `schema_version = "VSTD-2"`; see `WIRE_IDENTIFIERS.md`
+**Status:** experimental project specification with an implemented vertical slice
+**Maintainer:** TimeLordRaps
+**Date:** 2026-08-20
+
+---
+
+## 1. Relationship to adjacent profiles
+
+VSTD-2 adds a verification-geometry ontology beside VSTD-1 claim mechanics and
+VSTD-Graph collection dynamics. A document conforms to this extension only when it
+declares `schema_version = "VSTD-2"`; a result on one coordinate does not supply a
+result on either adjacent surface.
+
+VSTD-1 answers how a bounded claim carries evidence, provenance, a checker judgment,
+an explicitly evidenced independence basis, and reproducibility information. VSTD-Graph-1 answers how artifacts and
+transformations compose into a provenance hypergraph. VSTD-2 answers a different
+question: **what geometry was selected for verification, what did reconstruction
+expose that the geometry missed, and has the sufficiency of the declared closure
+itself been verified?**
+
+The normative typed slice is implemented by:
+
+- `verifier.core.geometry`;
+- `receipts/schema/vstd2_receipt.json`; and
+- `tests/test_verification_geometry.py`.
+
+---
+
+## 2. Epistemic law
+
+VSTD MUST NOT claim more than the declared verification surface and actual evidence
+establish.
+
+Assumptions MUST NOT manufacture closure. Unknownness, unsupported structure,
+missing evidence, unresolved translation, an unverified mechanism, and an unverified
+root are information. They MUST remain explicit states, residuals, valences, or
+horizons.
+
+A declared trust root is a boundary, not evidence that the root is true. When a
+derivation stops at such a boundary, the geometry MUST record a `TRUST_ROOT` horizon
+and MUST NOT claim self-closure.
+
+---
+
+## 3. Verification geometry
+
+### 3.1 Subject and locus
+
+A **subject** is the overall entity under consideration. A subject may itself become
+an addressable entity inside a larger subject.
+
+A **locus** is a scale-independent, addressable place or entity to which verification
+can attach. A locus may recursively contain other loci. Repositories, functions, AST
+nodes, instructions, dataset rows, models, processes, interfaces, and dependency
+relations are all possible loci.
+
+`LOCUS` answers **where or what**.
+
+### 3.2 Facet
+
+A **facet** is a dimension of assurance applicable to a locus, such as functional or
+semantic correctness, termination, determinism, integrity, provenance,
+reproducibility, translation fidelity, performance, or security.
+
+`FACET` answers **in what respect**.
+
+A facet is not a constituent part of a subject. New facets remain expressible through
+stable identifiers rather than a permanently closed enumeration.
+
+### 3.3 Region, grain, and stratum
+
+A **region** is a meaningful collection of loci considered together, whether or not
+they are syntactically contiguous. The implemented slice represents a region through
+a named surface selection; a separate region object is deferred until distinct region
+semantics are demonstrated.
+
+**Grain** is the resolution at which a subject is decomposed: repository, module,
+function, statement, instruction, row, checkpoint, or another declared resolution.
+
+**Stratum** is the representation layer: requirement, source, AST, IR, assembly,
+execution, output, or verification.
+
+Grain and stratum are orthogonal. Two loci may have function grain while one belongs
+to source stratum and another to execution stratum.
+
+### 3.4 Seam
+
+A **seam** is an interface, transition, dependency, or translation boundary between
+loci. A seam records its source locus, target locus, and relation. A seam can be made
+a locus when assurance must attach to the seam itself.
+
+### 3.5 Coordinate and surface
+
+A **coordinate** is a locus-facet pair:
+
+`coordinate = locus x facet`
+
+A verification claim attaches to a coordinate or an explicitly represented relation
+among coordinates.
+
+A **verification surface** is the declared set of coordinates and relevant seams for
+which verification status is claimed. For subject `S`, loci `L`, and facets `F`:
+
+`surface(S) = (C_selected, E_selected)`
+
+where `C_selected` is a finite subset of `L x F` and `E_selected` is the finite set of
+relevant seams. Coordinates not selected by the surface do not inherit its verdict.
+
+### 3.6 Horizon
+
+A **horizon** is a localized point at which the current verification derivation cannot
+proceed because evidence, representation, mechanism, grain, ontology, or a root ends.
+A horizon proves nothing beyond itself. It records the limit without converting the
+limit into an assumption.
+
+---
+
+## 4. Decomposition, reconstruction, and deconstruction
+
+**Decomposition** resolves or partitions a subject into loci at a declared grain. It
+asks: *what parts can be exposed?*
+
+**Reconstruction** generates, reproduces, simulates, or predicts a subject or its
+relevant behavior from the represented geometry. It asks: *is this representation
+sufficient to regenerate what mattered?*
+
+**Deconstruction** is the iterative inference of a reconstructible verification
+geometry. It combines decomposition, reverse engineering, reconstruction pressure,
+residual analysis, and ontology refinement:
+
+```text
+SUBJECT --deconstruct--> GEOMETRY
+ ^ |
+ | |
+ +----reconstruct--------+
+```
+
+Deconstruction may recurse over the subject by exposing finer loci. It may also
+recurse over the ontology when a residual cannot be expressed by the current
+verification language. Neither recursion licenses invented structure.
+
+Zero residual is not itself a valid objective. A residual eliminated by enlarging an
+unverified TCB, deleting unsupported semantics, overfitting a reconstruction, or
+adding an assumption remains epistemically unresolved. Every material residual MUST
+instead be resolved, localized, represented, or terminated at a horizon.
+
+---
+
+## 5. Residuals and novelty
+
+### 5.1 Residual taxonomy
+
+A **residual** is an evidenced difference between observation and the current
+verification geometry or reconstruction.
+
+- `STRUCTURAL`: observed structure absent from the locus/dependency geometry.
+- `BEHAVIORAL`: observed behavior differs from reconstructed or predicted behavior.
+- `SEMANTIC`: source meaning differs from meaning established by its formalization.
+- `ONTOLOGICAL`: the current verification ontology cannot adequately classify the
+ observed phenomenon.
+
+A residual has a disposition:
+
+- `OPEN`: discovered but not yet adequately localized;
+- `LOCALIZED`: bound to a locus, coordinate, or seam but not discharged;
+- `RESOLVED`: discharged by represented evidence and refinement; or
+- `HORIZON`: localized at an explicit boundary beyond which derivation cannot proceed.
+
+An assumption is not a residual disposition.
+
+### 5.2 Novelty
+
+**Novelty** is residual structure that cannot be discharged using the currently
+declared geometry or mechanism vocabulary. A novelty claim MUST cite its grounding
+residual and classify the insufficiency as grain, locus, facet, seam, stratum,
+mechanism, or ontological novelty. Surprise alone is not novelty.
+
+---
+
+## 6. Closure, valence, and self-closure
+
+### 6.1 Ordinary bounded closure
+
+Ordinary closure asks whether all obligations selected by the declared surface have
+been discharged. The implemented vertical slice permits **bounded closure up to an
+explicit horizon** when:
+
+1. every selected coordinate has a `VERIFIED` judgment backed by evidence and an
+ identified mechanism; and
+2. every material residual is `RESOLVED` or explicitly terminated at a `HORIZON`.
+
+This form of closure is never evidence about what lies beyond a horizon.
+
+### 6.2 Verification valence
+
+**Verification valence** is an open relational or evidentiary capacity licensed by
+the existing geometry. A valence identifies its source, the relation or evidence slot
+that the geometry implies, and whether that slot is `OPEN`, `DISCHARGED`, or terminated
+at a `HORIZON`.
+
+Valence describes the shape of an unresolved obligation. It does not invent the
+entity or evidence that would satisfy it.
+
+### 6.3 Self-closure
+
+**Self-closure is closure that recursively verifies the sufficiency of its own
+declared closure conditions and exposes remaining verification valence rather than
+assuming it away.**
+
+Self-closure requires:
+
+1. structurally valid verification geometry;
+2. ordinary bounded closure;
+3. every material residual `RESOLVED`, not merely stopped at a horizon;
+4. every verification valence `DISCHARGED` by evidence;
+5. every material verification mechanism post-verified by identified evidence;
+6. no unresolved evidence, mechanism, ontology, grain, representation, or trust-root
+ horizon; and
+7. a finite, contiguous sequence of adjacent verification orders.
+
+If any condition fails, the geometry MUST refuse self-closure and enumerate the
+blockers.
+
+### 6.4 Higher verification orders
+
+Higher-order verification is represented as a finite sequence:
+
+- `V0`: verification of the primary subject;
+- `V1`: verification of V0's geometry, evidence, mechanisms, and selected surface;
+- `V2`: verification of V1's sufficiency criteria; and so on only when evidenced.
+
+Each order greater than zero MUST verify exactly the preceding order. Skipped orders
+violate the verification-order adjacency invariant. A finite document never claims that simply
+adding one more self-description would close the sequence; inability to justify the
+next order is a horizon or open valence.
+
+---
+
+## 7. Lifecycle vocabulary
+
+- `PRE_VERIFIED`: the coordinate or surface exists before an applicable verification
+ has had the opportunity to establish a result. It is not a passing status.
+- `VERIFIED`: a bounded coordinate passed an applicable mechanism with bound evidence,
+ declared limitations, freshness, and non-expansion.
+- `POST_VERIFIED`: a passing result is bound to a frozen, content-identified snapshot
+ of the subject, evidence, mechanism state, and relevant environment.
+- `GEOMETRY_INSPECTABLE`: the declared situation has an inspectable geometry that
+ represents covered, unsupported, indeterminate, and horizon-bounded coordinates
+ honestly. This vocabulary is prose-only: it is not a serialized receipt value, and it is not a
+ member of the `CoordinateStatus` enumeration serialized in a VSTD-2 receipt.
+- `COMPLETELY_VERIFIED`: the declared closed surface satisfies self-closure. It never
+ means universal truth, unbounded safety, or permanent validity.
+
+Systems SHOULD minimize pre-verified surface area and dwell time. Post-verified
+snapshots are useful compositional checkpoints, but continuous verification is
+preferred: material changes invalidate dependent judgments and create new
+pre-verified coordinates until checks pass again.
+
+---
+
+## 8. Verifying processes and the common verification language
+
+A **verifying process** has an attached self-verification pipeline that observes its
+operation, translates relevant facts into the common verification geometry, applies
+mechanisms, and emits evidence about both the process and the pipeline.
+
+Self-observation is not self-certification. A pipeline that does not represent its own
+mechanisms, dependencies, translation limits, and horizons is only
+verification-instrumented.
+
+The common **verification language** is the typed graph of subjects, loci, facets,
+coordinates, seams, surfaces, judgments, mechanisms, residuals, horizons, valences,
+and adjacent verification orders. It is not an intermediate programming language for
+every CI/CD system. Native workflows translate observable verification events through
+thin adapters into this graph:
+
+```text
+native process -> adjacent adapter -> verification geometry -> verifier
+```
+
+The adapter and verifier become loci in the next adjacent verification order. This
+keeps verification orders adjacent and finite instead of recursing into infinite
+workflow abstraction.
+
+The language is self-describing only in the bounded sense that its schema, adapter,
+validator, and closure criteria can themselves become subjects. Their description is
+not evidence of their correctness.
+
+### 8.1 Profiles and profiler adapters
+
+A **geometry profile** is a named, reusable constraint on how this geometry is applied;
+it is not a new verdict, numbered VSTD profile, assurance score, or substitute for a
+verification mechanism. A geometry profile
+may identify its subject and grain, expected loci and facets, selected surface and
+exclusions, native observation sources, adapter and mapping identities, applicable
+mechanisms, evidence requirements, bounds, trust roots, horizons, and falsification or
+conformance conditions.
+
+A native profiler or domain tool remains an observation source. Its output enters a
+VSTD-2 surface only through an adjacent adapter that attributes the translated values to
+exact coordinates and exposes omissions, transformations, and information loss. A native
+status word does not transfer into a VSTD judgment without the identified assessment that
+earns that judgment.
+
+Geometry profiles are linked only through explicit shared coordinates, seams, mappings, and
+evidence-bearing transformations. Naming two profiles together, applying them to the same
+subject, or repeating their observations does not compose their verdicts. A composite
+geometry profile must declare and assess the cross-profile seams; unresolved mappings and conflicts
+remain horizons or open valences.
+
+The `VSTD-2` receipt does not currently carry a geometry-profile identifier or a
+geometry-profile-composition object. This section defines the conceptual relationship only.
+A geometry-profile document can bind
+an exact VSTD-2 surface and receipt externally; a new wire representation requires an
+explicit versioned profile boundary.
+
+---
+
+## 9. Reprogramming compatibility
+
+VSTD-2 reserves no universal transformation engine. It remains compatible with the
+following future pattern:
+
+```text
+SUBJECT S0
+ -> deconstruct to GEOMETRY G0
+ -> transform selected verified coordinates into G1
+ -> reconstruct SUBJECT S1
+ -> verify the transformation and resulting behavior
+```
+
+**Reprogramming** is a verified transformation of selected coordinates in a
+deconstructed representation followed by reconstruction into a modified subject.
+Any future implementation MUST receipt the selection, transformation, reconstruction,
+residuals, and resulting verification without silently transferring S0 judgments to
+S1.
+
+---
+
+## 10. Conformance and present limits
+
+A VSTD-2 geometry document conforms to the implemented vertical slice when:
+
+1. it validates against `vstd2_receipt.json`;
+2. `validate_geometry` returns no errors;
+3. every `VERIFIED` judgment cites evidence and a known mechanism;
+4. references and containment are internally consistent;
+5. reconstruction residuals are typed and localized;
+6. verification orders obey the verification-order adjacency invariant; and
+7. closure is reported by `assess_closure` without suppressing its blockers.
+
+The current slice does not infer loci automatically, prove ontology completeness,
+translate arbitrary CI/CD workflow languages, or certify its own Python runtime. Those
+are explicit present limits, not assumed capabilities.
diff --git a/src/verifier/specifications/VSTD-3.md b/src/verifier/specifications/VSTD-3.md
index cd15da2..ecbe454 100644
--- a/src/verifier/specifications/VSTD-3.md
+++ b/src/verifier/specifications/VSTD-3.md
@@ -1,7 +1,20 @@
-# VSTD-3 — Substrate Accountability
-
-**Layer:** 3 of 5 on the object axis (see `LADDER.md`)
-**Receipt wire format:** `schema_version = "VSTD-3.0"` — frozen; see `WIRE_IDENTIFIERS.md`
+# Verifier Standard (VSTD)-3 — Substrate Accountability
+
+> **Acronyms:** Advanced Micro Devices (AMD); Amazon Web Services (AWS); Compute Unified Device Architecture (CUDA);
+> Device Identifier Composition Engine (DICE); DMTF standards organization (DMTF); DICE Protection Environment (DPE);
+> Entity Attestation Token (EAT); floating-point operation (FLOP); hash-based message authentication code (HMAC);
+> integrated development environment (IDE); Internet Engineering Task Force (IETF);
+> International Organization for Standardization (ISO); JavaScript Object Notation (JSON);
+> NVIDIA Management Library (NVML); Peripheral Component Interconnect (PCI); PCI Special Interest Group (PCI-SIG);
+> Remote Attestation Procedures (RATS); Reference Integrity Manifest (RIM); software development kit (SDK);
+> Secure Hash Algorithm 256-bit (SHA-256); system management interface (SMI); Security Protocol and Data Model (SPDM);
+> Trusted Device Interface Security Protocol (TDISP); tensor processing unit (TPU); Coordinated Universal Time (UTC);
+> Unicode Transformation Format, 8-bit (UTF-8); World Wide Web Consortium (W3C).
+
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-3 on the object axis; required closure coordinate: Substrate Accountability (see `LADDER.md`)
+**Receipt serialization:** `schema_version = "VSTD-3.0"` — frozen; see `WIRE_IDENTIFIERS.md`
**Status:** implemented project specification
**Editor:** TimeLordRaps
**License:** Apache-2.0
@@ -126,7 +139,7 @@ The reference implication graph is explicit. In particular:
- fleet-boundary attestation does not imply physical-world completeness.
`VERIFIED` flags inside a receipt are not self-authenticating. A verifier MUST
-independently reproduce signature and continuity checks before using those flags to
+recompute signature and continuity checks from the bound evidence before using those flags to
accept a strong `PASS`.
## 7. Incremental conformance profiles
@@ -350,7 +363,7 @@ hardware or firmware evidence therefore reaches downstream artifacts through the
existing blast-radius algorithm. VSTD-3 does not create a second lineage graph.
Composition is transactional and refuses receipts whose recorded `PASS` claims cannot
-be independently reproduced.
+be recomputed from the bound evidence.
## 20. Verification algorithm
@@ -361,12 +374,12 @@ A verifier MUST, in order:
3. verify identifiers and all references;
4. verify raw evidence byte digests;
5. validate challenge freshness, nonce uniqueness, subject, and certificate binding;
-6. independently verify implemented attestation and provider signatures;
+6. verify implemented attestation and provider signatures against configured trust material;
7. validate topology and partition lineage;
8. bind starts, observations, accounting, ends, and workload identity to events;
9. verify event continuity, resets, and anchors;
10. verify the exact fleet boundary when present;
-11. recompute every recorded passing claim from independently accepted evidence;
+11. recompute every recorded passing claim from mechanism-verified evidence;
12. reject any stronger recorded `PASS`.
Receipt digest integrity alone completes only steps 1–2.
@@ -388,8 +401,8 @@ trust anchors. Merely labeling bytes `SPDM`, `EAT`, or `DICE` is not verificatio
## 23. Compatibility
VSTD-3 adds record and enum values. It does not reinterpret VSTD-1, VSTD-Graph-1,
-VSTD-2, or their historical wire identifiers. Existing readers remain valid
-for their versioned surfaces. VSTD-3 hardware nodes use additive artifact and
+VSTD-2, or their current serialized receipt identifiers. Each reader remains bounded to its
+versioned surface. VSTD-3 hardware nodes use additive artifact and
transformation enum values in the existing hypergraph.
## 24. Falsification conditions
@@ -408,5 +421,5 @@ VSTD-3 conformance is falsified for a claimed surface if any of these occurs:
- global absence of undeclared compute is derived from an ordinary receipt.
Implementation limitations and the complete threat model are in
-`../docs/layers/vstd-3/threat-model.md`; vendor requirements are in
-`../docs/layers/vstd-3/vendor-integration.md`.
+`../docs/profiles/vstd-3/threat-model.md`; vendor requirements are in
+`../docs/profiles/vstd-3/vendor-integration.md`.
diff --git a/src/verifier/specifications/VSTD-4.md b/src/verifier/specifications/VSTD-4.md
index dd4def4..6f90584 100644
--- a/src/verifier/specifications/VSTD-4.md
+++ b/src/verifier/specifications/VSTD-4.md
@@ -1,52 +1,72 @@
-# VSTD-4 — Refutability
+# Verifier Standard (VSTD)-4 — Refutability
-**Layer:** 4 of 5 on the object axis (see `LADDER.md`)
+> **Acronyms:** application programming interface (API); conjunctive normal form (CNF); grounded decision certificate (GDC); JavaScript Object Notation (JSON);
+> resolution asymmetric tautology (RAT); Boolean satisfiability problem (SAT);
+> Unicode Transformation Format, 8-bit (UTF-8).
+
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-4 on the object axis; required closure coordinate: Refutability (see `LADDER.md`)
**Certificate format:** `VSTD4-GDC-1`
-**Status:** implemented project specification
+**Status:** project specification with implemented candidate and evidence-bound reference paths
**Editor:** TimeLordRaps
**License:** Apache-2.0
**Date:** 2026-08-22
-VSTD-4 defines **adversarially portable checkability**. A verdict reaches this
-layer only when its exact meaning, evidence, failure conditions, and checking
-procedure can leave the declarant and survive hostile independent inspection.
+VSTD-4 defines **adversarially portable checkability**. A verdict satisfies this
+profile only when its exact meaning, evidence, failure conditions, and checking
+procedure can leave the declarant and survive hostile inspection outside the declarant.
-VSTD-4 establishes that independent checking is possible. It does not establish
-that an independent party exists or has checked anything; that is VSTD-5.
+VSTD-4 establishes that checking by an outside party is possible. It does not establish
+that such a party exists or has checked anything; that is VSTD-5.
> **No verdict without a portable certificate.**
> **No portable certificate without an explicit falsifier.**
---
-## 1. Conformance and lower-layer preconditions
+## 1. Conformance and prerequisite-profile coordinates
VSTD-4 conformance is incremental. A claim MUST conform to VSTD-1, VSTD-2, and
VSTD-3 before it can conform to VSTD-4. A VSTD-4 certificate over an
unaccountable substrate does not repair the missing VSTD-3 evidence.
-The normative depth is computed:
+The VSTD-4 normative depth is computed:
```
vstd4_depth(claim) = max { k : CNF_4k(claim) is satisfiable }
```
-An implementation MUST NOT accept a declarant-supplied depth as authoritative.
-For a depth below 14, the `FAIL` certificate for rung `k+1` is the normative
-explanation of the ceiling. Entry to any VSTD-5 procedure requires:
+An implementation MUST NOT accept a declarant-supplied VSTD-4 normative depth as authoritative.
+For a normative depth below 14, the `FAIL` certificate for rung `k+1` is the normative
+explanation of the ceiling. Entry to any VSTD-5 procedure requires established
+VSTD-4 conformance and:
```
vstd4_depth(claim) == 14
```
-The reference implementation is `verifier.core.depth`.
+The historical `verifier.core.depth.vstd4_depth` API computes only a structural
+candidate over caller-supplied, nonempty rung references. It does not resolve those
+references, validate their rung propositions, or check VSTD-1/2/3 preconditions. Its
+result is therefore `CANDIDATE` with `conformance_status = NOT_ESTABLISHED`, including
+at candidate depth 14, and the reference VSTD-5 entry gate rejects it.
+
+`verifier.core.depth.establish_vstd4` is the evidence-bound path. It requires
+exact `BoundProposition` records for VSTD-1, VSTD-2, VSTD-3, and all fourteen
+rungs; resolves and rehashes every embedded evidence payload; selects a registered
+mechanism by identifier and implementation digest; enforces evidence byte/item
+bounds; reruns the mechanism; and independently checks the resulting structural
+certificate. Only the complete passing result reports `depth_kind = EVIDENCE_BOUND`,
+`conformance_status = ESTABLISHED`, and admits VSTD-5. The receipt builder embeds
+the bindings and evidence bytes, and the rechecker recomputes the result offline.
---
-## 2. The fourteen-rung ladder
+## 2. The fourteen-rung sequence
-Each rung depends on the evidence named below and on every lower-layer
-precondition. Rung 4.14 depends on the complete ladder.
+Each rung depends on the evidence named below and on every prerequisite-profile
+precondition. Rung 4.14 depends on the complete sequence.
| Rung | Requirement | Direct dependencies |
|---|---|---|
@@ -102,7 +122,7 @@ C = H(claim || coordinate || policy_root || evidence_root || verifier
Canonical serialization MUST use sorted object keys, integer-valued numeric
fields, no floating-point values, UTF-8, and no insignificant whitespace. A
-checker MUST reject a certificate whose binding does not match the independently
+checker MUST reject a certificate whose binding does not match the externally
supplied `ClaimBinding`.
### 2.4 Portable verification
@@ -160,7 +180,7 @@ IDENTIFIED < AVAILABLE < PORTABLE < SELF_CONTAINED
A digest alone establishes only `IDENTIFIED`. VSTD-4 requires at least
`AVAILABLE`, and the claim's bundle is capped by its weakest verdict-critical
-artifact. A declared level that its retrieval and retention evidence cannot
+artifact. A declared availability state that its retrieval and retention evidence cannot
support MUST be rejected.
A locator and retention declaration alone are not retrieval evidence. `AVAILABLE`
@@ -168,7 +188,7 @@ requires a successful retrieval observation bound to the artifact identifier, de
locator, observed bytes, observation time, and observer. The observed bytes MUST match
the content address. `PORTABLE` additionally requires anonymous access and a declared
retrieval procedure. A retrieval observation is scoped to its named trust root; it does
-not by itself establish independent retrieval.
+not by itself establish retrieval by a distinct actor.
### 2.9 Disclosure-safe checkability
@@ -219,7 +239,7 @@ VALID -> CHALLENGED -> REVOKED
A valid challenge mechanism that cannot change claim status is non-conforming.
Synthetic challenges test structural challengeability at VSTD-4. Actual
-independent action belongs to VSTD-5.
+action by a distinct actor belongs to VSTD-5.
### 2.13 Monotonic degradation
@@ -233,7 +253,8 @@ A `RefutabilityClosure` MUST bind input certificates, the transformation
certificate, the output claim, and a total output-refutation mapping. A challenge
to an output must localize to an input, the transformation, or the composition.
-Output depth MUST NOT exceed the weakest required input or transformation depth.
+Output VSTD-4 normative depth MUST NOT exceed the weakest required input or transformation
+VSTD-4 normative depth.
This closure is both the handoff to VSTD-Graph edge evidence and the entry gate to
VSTD-5.
@@ -300,7 +321,7 @@ accepted.
## 4. Normative invariants
> A verdict MUST NOT be recorded at a strength exceeding the strength of the
-> certificate an independent party could check without the declarant's
+> certificate an outside party could check without the declarant's
> cooperation.
> Loss of certificate validity, accessibility, dependency validity, or
@@ -330,17 +351,25 @@ bounded checking.
## 6. Reference implementation boundary
-The reference producer and data structures are in:
+The reference certificate producer, candidate/evidence-bound computations, and data structures are in:
* `src/verifier/core/certificate.py`
* `src/verifier/core/grounding.py`
* `src/verifier/core/depth.py`
+* `src/verifier/core/evidence.py`
* `src/verifier/core/refutation.py`
* `src/verifier/layer4/`
The trusted checker is `src/verifier/core/kernel.py`. Producer modules are not
part of its trusted import boundary.
+The kernel checks the supplied certificate, grounding, and `ClaimBinding` for internal
+consistency. It does not retrieve rung references or establish prerequisite-profile
+results by itself. Kernel acceptance of a candidate certificate is therefore not VSTD-4
+conformance. The evidence-bound path performs those additional checks before it can
+report conformance; its result remains bounded to the registered mechanisms, trust roots,
+evidence, and resource limits.
+
No external implementation, interoperability profile, or third-party attack has
yet been demonstrated for `VSTD4-GDC-1`. This implementation status MUST remain
visible in claims about the format.
diff --git a/src/verifier/specifications/VSTD-5.md b/src/verifier/specifications/VSTD-5.md
new file mode 100644
index 0000000..1d8cbb3
--- /dev/null
+++ b/src/verifier/specifications/VSTD-5.md
@@ -0,0 +1,148 @@
+# Verifier Standard (VSTD)-5 — Witness Corroboration
+
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-5 on the object axis; required closure coordinate: Witness Corroboration (see `LADDER.md`)
+**Status:** project specification with implemented evidence-bound reference mechanism
+**Editor:** TimeLordRaps
+**License:** Apache-2.0
+**Date:** 2026-08-29
+
+VSTD-5 binds a fully refutable claim to an actually checked witness relation. It is
+the first numbered object profile that a declarant acting alone cannot satisfy.
+Witness identity names a coordinate; it never supplies computational trust.
+
+---
+
+## 1. Entry gate
+
+Every VSTD-5 procedure MUST reject a claim unless VSTD-1, VSTD-2, and VSTD-3
+preconditions and all VSTD-4 rung propositions were evidence-bound and checked,
+establishing VSTD-4 conformance at depth 14.
+
+The VSTD-5 bundle `claim_id` MUST equal the exact `claim_id` admitted by that
+evidence-bound VSTD-4 result. A shared claim-binding or certificate digest does not
+establish that a neighboring identifier is an alias. Any future identifier mapping
+would require its own bounded proposition and mechanism; the reference mechanism does
+not implement such aliases.
+
+The compatibility `vstd4_depth` candidate never satisfies this gate. The reference
+`establish_vstd4` path may satisfy it only after rerunning every exact evidence
+binding and checking its depth certificate. `require_vstd5_entry` distinguishes the
+two result types and fails closed.
+
+---
+
+## 2. Required records
+
+The reference receipt contains:
+
+* `WitnessIdentity` — witness coordinate plus content-addressed identity evidence;
+* an ordered `independence_assertions` array — every supplied
+ `IndependenceAssertion`, including duplicates, orphan references, and missing
+ cardinality as an empty or incomplete array, so negative assessment inputs are not
+ collapsed during serialization;
+* `CorroborationRecord` — exact VSTD-4 commitment, certificate, checker descriptor,
+ observations, result, time, class, and executable verification binding;
+* derived disagreements — conflicting checked records retained without voting or
+ averaging; and
+* embedded evidence bytes — enough to rehash and rerun the registered mechanisms
+ offline.
+
+Schema validity establishes only shape. `recheck_vstd5_receipt` imports and hashes
+the embedded bytes, compares every carried VSTD-4 entry coordinate—including the result
+digest and witness digest—with the admitted entry, reruns every registered mechanism, and
+compares the complete derived result. Assessment and receipt construction
+are separate boundaries: `assess_witness_corroboration` may diagnose an arbitrary malformed
+or incomplete bundle as `UNKNOWN` / `NOT_ESTABLISHED`, but that assessment object is not
+thereby a VSTD-5 receipt. `build_vstd5_receipt` MUST fail before returning unless the object
+inhabits the strict receipt schema, contains at least one witness and corroboration, and
+embeds every verdict-material evidence byte. Every receipt it does emit, including a
+representable `UNKNOWN` / `NOT_ESTABLISHED` error receipt, MUST preserve the exact
+error-producing input and recheck identically. `recheck_vstd5_receipt` MUST enforce that
+strict shape and evidence coverage before mechanism replay.
+
+---
+
+## 3. Independence
+
+For every declarant/witness pair, the procedure checks whether they share:
+
+1. ownership or operational control;
+2. verdict-producing code;
+3. a verifier trust root;
+4. an evidence source or telemetry provider;
+5. infrastructure capable of changing the observed result;
+6. financial dependence material to the corroboration; and
+7. jurisdictional or contractual dependence material to compulsion.
+
+Every `SEPARATE` state MUST carry a `BoundProposition` for the exact negative
+relationship, the admitted claim commitment, evidence references, mechanism
+identifier and digest, trust roots, and bounds. `SHARED`, `UNKNOWN`, missing,
+failed, or unevaluable dimensions prevent an `INDEPENDENT` result.
+
+Repeated evidence, duplicate identifiers, identity keys, signatures, reputation,
+and field names MUST NOT manufacture independence. The same identity evidence used
+under multiple witness identifiers is rejected.
+
+---
+
+## 4. Corroboration and disagreement
+
+A corroboration mechanism MUST bind and check the exact:
+
+* claim commitment;
+* VSTD-4 certificate digest;
+* checker descriptor digest;
+* corroboration class;
+* witness coordinate;
+* observation time;
+* observation evidence bytes; and
+* `CORROBORATED`, `REFUTED`, or `UNKNOWN` result.
+
+A record's certificate digest MUST equal the admitted evidence-bound VSTD-4 witness,
+not merely a caller-selected digest repeated in both fields. Every identified witness
+MUST contribute a corroboration record; dangling identities do not create plurality.
+`corroboration_class` is part of the mechanism-checked expected proposition, not declared
+metadata and not an assurance-bearing label by itself.
+
+A mechanism-earned negative result remains negative. Conflicting checked records
+produce `CONFLICTED`; witnesses are not votes, and majority count never cleans the
+conflict. A positive corroboration with any unresolved independence seam is reported as
+overall `UNKNOWN`, not as independently corroborated. Reusing the same evidence set under
+another corroboration identifier is rejected rather than counted twice.
+
+---
+
+## 5. Reference algorithm
+
+`verifier.core.witness.assess_witness_corroboration` performs, in order:
+
+1. evidence-bound VSTD-4 entry and exact claim-identifier validation;
+2. identity-evidence availability and duplicate detection;
+3. exact seven-dimension independence evaluation;
+4. exact corroboration binding and mechanism execution;
+5. duplicate-evidence refusal;
+6. disagreement derivation; and
+7. bounded result emission with all errors and limitations retained.
+
+`build_vstd5_receipt` serializes witness identities and independence assertions as
+separate ordered arrays so representable duplicate, orphan, reused-identity, and missing
+assertion failures survive round trip. It refuses empty witness/corroboration collections,
+empty required identifiers, invalid receipt identifiers, malformed nested records, and
+missing verdict-material bytes rather than naming them receipts. `recheck_vstd5_receipt`
+applies the same zero-dependency structural gate, rejects any inconsistent redundant VSTD-4
+entry coordinate, and mechanism-checks the corroboration class before accepting exact replay.
+Neither function turns an identity coordinate into trust or establishes a fact outside the
+propositions checked by its registered mechanisms.
+
+---
+
+## 6. Current limits
+
+The repository ships the meta-verification mechanism and adversarial fixtures. It
+does not ship or claim a real independent third-party witness, external
+interoperability deployment, accreditation, or a universal way to infer real-world
+separation. A deployment must supply mechanisms that actually check its evidence;
+registering a mechanism names the trust boundary but does not make that mechanism
+correct.
diff --git a/src/verifier/specifications/VSTD-Graph-1.md b/src/verifier/specifications/VSTD-Graph-1.md
new file mode 100644
index 0000000..f7ac14a
--- /dev/null
+++ b/src/verifier/specifications/VSTD-Graph-1.md
@@ -0,0 +1,185 @@
+# Verifier Standard (VSTD)-Graph-1 — Recorded Lineage
+
+> **Acronyms:** application programming interface (API); conjunctive normal form (CNF); Davis-Putnam-Logemann-Loveland (DPLL); operating system (OS);
+> Boolean satisfiability problem (SAT); Secure Hash Algorithm 256-bit (SHA-256); satisfiability modulo theories (SMT);
+> Software Package Data Exchange (SPDX); uniform resource identifier (URI).
+
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-Graph-1; required closure coordinate: Recorded Lineage (see `LADDER.md`)
+**Receipt serialization:** `schema_version = "VSTD-DATA-0.1"` — frozen; see `WIRE_IDENTIFIERS.md`
+**Status:** Project Specification with Implemented Reference Subset
+**Maintainer:** TimeLordRaps
+**Date:** 2026-08-21
+
+---
+
+## 1. Purpose & Core Thesis
+
+> **Dataset and training provenance is the foundational substrate of computational verifiability: data sits directly upstream of training runs, checkpoints, fine-tuned adapters, evaluations, model behavior, downstream software products, licensing, and attribution.**
+
+`VSTD-Graph-1` establishes a content-addressed **Hypergraph Specification** for
+capturing recorded and evidenced lineage of datasets, neural weights, and computational
+outputs within a declared observation boundary. It does not infer unobserved history or
+prove that the recorded graph is complete in the real world. Transformations are
+first-class **N-ary Hyperedges**, which represent many-to-many merges, sharding, and
+multi-input processing without flattening those relationships into ambiguous binary
+links.
+
+This document defines the first numbered profile of the Graph axis. `VSTD-Graph-2.md` through
+`VSTD-Graph-5.md` apply progressively stronger object and transformation-edge
+requirements to the same closed collection. `LADDER.md` defines the computed candidate
+Graph profile and its ceiling certificate; the compatibility API
+`verifier.data.graph_level.graph_level`
+implements that computation.
+
+---
+
+## 2. The Provenance Hypergraph Abstraction
+
+A Dataset Provenance Hypergraph is a 6-tuple:
+$$\mathcal{H} = (\mathcal{A}, \mathcal{T}, \mathcal{C}, \mathcal{R}, \mathcal{P}, \mathcal{X})$$
+
+### 2.1 Artifact Nodes ($\mathcal{A}$)
+Represents any discrete, inspectable data object or model state:
+- `artifact_id`: Unique identifier (e.g. `art:sha256:...`).
+- `artifact_type`: `RAW_SOURCE_FILE`, `CORPUS`, `SHARD`, `DATASET_SPLIT`, `TOKENIZED_CORPUS`, `CHECKPOINT`, `ADAPTER`, `MODEL`, `EVALUATION_REPORT`, `SUBMISSION_ARTIFACT`.
+- **Content-Addressable Cryptographic Digests**:
+ - `content_digest`: a declared `SHA-256` over raw payload bytes. It becomes a verified
+ byte-identity statement only when a named mechanism actually hashes accessible bytes
+ and binds the observation as evidence.
+ - `metadata_digest`: a declared `SHA-256` over explicitly normalized metadata.
+ - `provenance_digest`: a declared `SHA-256` over an explicitly canonicalized ancestor
+ subgraph.
+- `byte_size`, `record_count`, `mime_type`, `storage_uris`.
+- `status`: `VALID`, `CHALLENGED`, `STALE`, `SUPERSEDED`, `REVOKED`, `UNKNOWN`.
+
+### 2.2 Transformation Hyperedges ($\mathcal{T}$)
+Represents a declared N-ary transformation relationship consuming inputs and producing
+outputs. The edge records ancestry; it does not by itself establish causal influence:
+- `transformation_id`: Unique process identifier.
+- `transformation_type`: `COLLECTION`, `EXTRACTION`, `FILTERING`, `DEDUPLICATION`, `NORMALIZATION`, `AUGMENTATION`, `SYNTHETIC_GENERATION`, `TOKENIZATION`, `TRAINING`, `FINE_TUNING`, `DISTILLATION`, `QUANTIZATION`, `EVALUATION`.
+- `inputs`: List of input artifact references with role bindings (e.g. `TRAINING_SPLIT`, `BASE_WEIGHTS`, `CONFIG`).
+- `outputs`: List of produced artifact references with role bindings (e.g. `CHECKPOINT_WEIGHTS`, `METRICS_LOG`).
+- `software_provenance`: Git repository, commit SHA, branch, clean/dirty state, script path, execution command.
+- `parameters`: Exact hyperparameter dictionary, filter criteria, or random seeds.
+- `execution_environment`: Python runtime, host OS, hardware acceleration class, timestamp.
+
+The frozen `VSTD-DATA-0.1` serialization defines artifact and transformation identifiers
+inside separate collections; historical readers therefore retain a payload in which one
+string occurs once in each collection. Direct new construction, evidence-bound Graph
+establishment, and `VSTD-GRAPH-ASSURANCE-1` require the two sets to be globally
+disjoint because current evidence maps and the assurance overlay's `subject_id` do not carry
+an artifact/transformation kind. A historical overlap is readable and reproducible as
+recorded lineage but is inadmissible to those stricter current mechanisms. This compatibility
+rule does not let duplicates within either collection replace recorded evidence.
+
+### 2.3 Contributor Nodes ($\mathcal{C}$)
+- `contributor_id`, `name`, `contributor_type` (`INDIVIDUAL`, `ORGANIZATION`, `MODEL_GENERATOR`, `AUTOMATED_SYSTEM`), `uri`.
+
+### 2.4 Rights & Licensing Nodes ($\mathcal{R}$)
+- `rights_id`, `license_spdx` (e.g. `CC-BY-NC-4.0`, `MIT`, `Apache-2.0`), `commercial_allowed`, `attribution_required`.
+
+### 2.5 Policy & Formal Constraints ($\mathcal{P}$)
+- Machine-checkable Boolean admission rules. The current reference subset evaluates
+ bounded CNF with its minimal DPLL implementation; general SMT is not implemented.
+
+### 2.6 Conflict Records ($\mathcal{X}$)
+- `conflict_id`, `subject_id`, and `predicate` identify the disputed coordinate.
+- `competing_values` retains at least two incompatible values.
+- `evidence_refs` retains at least two evidence records rather than selecting a winner.
+
+A conflict record does not mutate the frozen artifact-status vocabulary. It makes the
+subject inadmissible to a clean candidate Graph profile. The VSTD-Graph-1 receipt has no
+conflict-resolution transition and remains immutable. The separate non-receipt
+`VSTD-GRAPH-ASSURANCE-1` overlay can record additive, mechanism-checked resolution while
+retaining the competing evidence. A selected status is projected into that overlay's current
+view; resolving any other predicate does not by itself establish a clean admissibility effect.
+No general non-status admissibility-effect mechanism is implemented in the current reference
+runtime, so such a conflict remains blocking.
+
+---
+
+## 3. Provenance Completeness Dimensions
+
+`VSTD-Graph-1` rejects treating a monolithic score as proof. The reference subset
+reports six descriptive dimensions plus a disclosed weighted summary:
+
+$$\mathbf{C} = \langle C_{\text{src}}, C_{\text{trans}}, C_{\text{integ}}, C_{\text{lic}}, C_{\text{contrib}}, C_{\text{lineage}} \rangle$$
+
+1. **Source-declaration coverage ($C_{\text{src}}$)**: Share of root artifacts with a
+ non-empty storage URI or `source_repository` declaration $[0.0, 1.0]$.
+2. **Transformation-declaration coverage ($C_{\text{trans}}$)**: Share of hyperedges
+ with a recorded commit identifier or script path $[0.0, 1.0]$.
+3. **Content-digest declaration coverage ($C_{\text{integ}}$)**: Share of artifacts with
+ a syntactically valid 64-hex-character digest $[0.0, 1.0]$. This metric does not by
+ itself show that the referenced physical bytes were rehashed.
+4. **License-metadata coverage ($C_{\text{lic}}$)**: Share of root artifacts linked to
+ an explicit rights record $[0.0, 1.0]$. It is not a legal-validity score.
+5. **Contributor Coverage ($C_{\text{contrib}}$)**: Share of artifacts attributed to identified agents $[0.0, 1.0]$.
+6. **Downstream Lineage Depth ($C_{\text{lineage}}$)**: Integer topological depth from
+ root sources to reachable outputs.
+
+The current weighted summary is
+`0.25*C_src + 0.25*C_trans + 0.25*C_integ + 0.15*C_lic + 0.10*C_contrib`.
+It is a coverage summary, not a probability, trust score, or verification verdict.
+
+---
+
+## 4. Epistemic Incompleteness & Fail-Closed Law
+
+* **The `UNKNOWN` Principle**: If an artifact's status is omitted, or its upstream
+ origin or transformation is not evidenced, the applicable state remains `UNKNOWN` or
+ the applicable coverage dimension remains incomplete. It never silently becomes
+ observed real-world truth.
+* **The `CONFLICTED` Principle**: Incompatible retained evidence remains an explicit
+ conflict record. It is neither averaged nor collapsed into `UNKNOWN`, `VALID`, or a
+ scalar confidence value.
+* **Fail-Closed Policy Admission**: A policy passes only the Boolean condition it
+ actually encodes. For example, "no ancestor is marked `REVOKED`" does not establish
+ that every ancestor is `VALID`; a clean-ancestor policy must explicitly require
+ `VALID` and reject `UNKNOWN`, `CHALLENGED`, `STALE`, and `SUPERSEDED`.
+
+---
+
+## 5. Challenge & Revocation Blast Radius
+
+When an upstream source $S$ is marked `REVOKED` (e.g. due to copyright claim, data poisoning, or corruption):
+1. The hypergraph query engine computes the forward reachability closure:
+ $$\text{BlastRadius}(S) = \{ a \in \mathcal{A} \mid S \rightsquigarrow a \}$$
+2. An integrating lifecycle controller can use that returned set to create additive
+ `CHALLENGED` or `REVOKED` records. The reference query does not silently mutate
+ historical artifact nodes.
+
+---
+
+## 6. Threat Model & Explicit Non-Guarantees
+
+### What the implemented reference subset can establish
+- **Receipt integrity**: Detects changes to stable fields bound by the receipt's
+ canonical digest.
+- **Recorded graph structure**: Checks references, acyclicity, reachability, and the
+ declared coverage metrics of the stored hypergraph.
+- **Declared lineage queries**: Computes ancestors, descendants, and forward blast
+ radius over recorded edges.
+- **Bounded policy evaluation**: Evaluates the recorded CNF condition over its declared
+ graph-to-variable mapping. This does not prove that the mapping captured every
+ real-world fact.
+- **Byte identity when separately observed**: A named adapter that rehashes accessible
+ bytes can establish whether those bytes match a recorded digest at that observation
+ time. Receipt validation alone does not access unbundled upstream files.
+
+### What `VSTD-Graph-1` Does NOT Guarantee
+- **Real-World Ground Truth**: A hash proves byte identity; it does not prove the data is empirically accurate.
+- **Legal Copyright Validity**: A declared SPDX license string records claimed provenance; it is not a judicial copyright ruling.
+- **Authenticity of declarations**: A digest binds bytes or fields; it does not prove
+ that a claimed origin, contributor, execution, or license declaration is authentic.
+- **Complete real-world lineage**: Missing instrumentation, hidden inputs, pre-observation
+ contamination, and out-of-band transformations remain outside the graph unless
+ separately evidenced.
+- **Automatic physical-file checking**: A stored VSTD-Graph receipt validates its own
+ stable content. It flags a physical-file mismatch only when an adapter supplies and
+ rehashes that file.
+- **Translation completeness**: SAT success establishes the encoded formula, not the
+ completeness or correctness of the translation from policy prose or the external
+ world into that formula.
diff --git a/src/verifier/specifications/VSTD-Graph-2.md b/src/verifier/specifications/VSTD-Graph-2.md
new file mode 100644
index 0000000..ec4c50c
--- /dev/null
+++ b/src/verifier/specifications/VSTD-Graph-2.md
@@ -0,0 +1,26 @@
+# Verifier Standard (VSTD)-Graph-2 — Bounded Collection Surface
+
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-Graph-2; required closure coordinate: Bounded Collection Surface (see `LADDER.md`)
+**Status:** project specification with implemented candidate and evidence-bound reference paths
+**License:** Apache-2.0
+
+VSTD-Graph-2 closes collection-scope leakage. A collection satisfies this candidate
+profile only when every member and provenance ancestor is rated at object profile 2 or
+higher, every reachable status is admissible, and every transformation hyperedge
+carries profile-2 edge ratings.
+
+`verifier.data.graph_level.graph_level` computes a candidate from caller-supplied ratings and marks
+conformance `NOT_ESTABLISHED`. `establish_graph_level` instead reruns exact member,
+ancestor, and edge rating propositions from embedded evidence through registered
+mechanisms; only that path may report `MECHANISM_EVALUATED` and `ESTABLISHED`. The
+rating propositions bind one digest over the exact historical Graph, deduplicated member
+set, collection identifier, and Graph claim binding. A neighboring collection, topology,
+or claim therefore contributes rating zero. Profile zero is never established. The
+`FAIL` certificate for Graph profile 2 names the member,
+ancestor, status, or edge obligation that prevents admission under those inputs. It does
+not validate the ratings themselves.
+
+VSTD-Graph-2 does not establish that the evidence sources behind the collection
+are accountable. That is the blind spot closed by VSTD-Graph-3.
diff --git a/src/verifier/specifications/VSTD-Graph-3.md b/src/verifier/specifications/VSTD-Graph-3.md
new file mode 100644
index 0000000..8fc6f5f
--- /dev/null
+++ b/src/verifier/specifications/VSTD-Graph-3.md
@@ -0,0 +1,26 @@
+# Verifier Standard (VSTD)-Graph-3 — Accountable Provenance Closure
+
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-Graph-3; required closure coordinate: Accountable Provenance Closure (see `LADDER.md`)
+**Status:** project specification with implemented candidate and evidence-bound reference paths
+**License:** Apache-2.0
+
+VSTD-Graph-3 closes unaccountable substrate across a collection. A collection
+satisfies this candidate profile only when every member and reachable ancestor is rated at
+object profile 3 or higher, every reachable status is admissible, and every transformation
+hyperedge carries profile-3 edge ratings.
+
+The provenance closure condition is normative: rating only the selected members
+is insufficient. The weakest reachable ancestor or transformation caps the
+collection.
+
+The compatibility computation consumes caller-supplied ratings and therefore reports a
+candidate with conformance `NOT_ESTABLISHED`. `establish_graph_level` reruns a registered
+mechanism over the exact evidence bytes for every member, ancestor, and transformation
+rating; missing, failed, uncertain, neighboring, or out-of-closure bindings contribute
+zero and prevent conformance. Every proposition also binds the exact Graph bytes,
+deduplicated member set, collection identifier, and claim binding.
+
+VSTD-Graph-3 cannot establish that an outside party could refute the composed
+collection. That blind spot is closed by VSTD-Graph-4.
diff --git a/src/verifier/specifications/VSTD-Graph-4.md b/src/verifier/specifications/VSTD-Graph-4.md
new file mode 100644
index 0000000..c4597bc
--- /dev/null
+++ b/src/verifier/specifications/VSTD-Graph-4.md
@@ -0,0 +1,26 @@
+# Verifier Standard (VSTD)-Graph-4 — Refutable Transformation Closure
+
+> **Acronym:** unsatisfiable (UNSAT).
+
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-Graph-4; required closure coordinate: Refutable Transformation Closure (see `LADDER.md`)
+**Status:** project specification with implemented candidate and evidence-bound reference paths
+**License:** Apache-2.0
+
+VSTD-Graph-4 closes non-compositional refutability. A collection satisfies this candidate
+profile only when every member and reachable ancestor is rated at object profile 4 or
+higher, statuses are admissible, and every transformation hyperedge carries
+profile-4 ratings including a valid `RefutabilityClosure`.
+
+Two VSTD-4 nodes connected by an unevidenced edge do not make a VSTD-Graph-4
+collection. A challenge to the collection output must localize to a member,
+ancestor, transformation, or the composition itself.
+
+The unsatisfiable (UNSAT) certificate at the next profile is the computed explanation of the candidate
+ceiling over caller-supplied ratings. It does not establish Graph-4 conformance or
+validate the claimed `RefutabilityClosure` records.
+The evidence-bound path reruns every exact rating mechanism and embeds the proposition
+bindings and evidence bytes for offline replay. Those bindings commit to the exact Graph,
+deduplicated members, collection identifier, and claim binding. A Graph-4 edge rating mechanism must
+actually check the applicable `RefutabilityClosure`; naming one is insufficient.
diff --git a/src/verifier/specifications/VSTD-Graph-5.md b/src/verifier/specifications/VSTD-Graph-5.md
new file mode 100644
index 0000000..6123ef8
--- /dev/null
+++ b/src/verifier/specifications/VSTD-Graph-5.md
@@ -0,0 +1,24 @@
+# Verifier Standard (VSTD)-Graph-5 — Corroborated Verification Network
+
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-Graph-5; required closure coordinate: Corroborated Verification Network (see `LADDER.md`)
+**Status:** project specification with implemented candidate and evidence-bound reference paths
+**License:** Apache-2.0
+
+VSTD-Graph-5 is the collection profile for independently corroborated members,
+ancestors, and transformations. The candidate-profile computation requires
+object and edge ratings of at least 5, provenance closure, and admissible status
+throughout.
+
+The compatibility implementation computes this profile over externally supplied
+profile-5 ratings and reports `NOT_ESTABLISHED`. The evidence-bound path can establish
+it only when registered mechanisms rerun exact VSTD-5 member/ancestor ratings and
+profile-5 transformation ratings from embedded evidence. Every rating is bound to the
+exact Graph bytes, deduplicated member set, collection identifier, and claim binding. A result based on
+self-declared ratings is not VSTD-Graph-5
+conformance.
+
+Conflicting witness records are retained as conflict records and make the relevant
+subject inadmissible to a clean candidate Graph profile. They are never averaged into
+a passing collection.
diff --git a/src/verifier/specifications/WIRE_IDENTIFIERS.md b/src/verifier/specifications/WIRE_IDENTIFIERS.md
index 736365f..b3d7768 100644
--- a/src/verifier/specifications/WIRE_IDENTIFIERS.md
+++ b/src/verifier/specifications/WIRE_IDENTIFIERS.md
@@ -1,106 +1,102 @@
-# VSTD frozen wire identifiers and historical filenames
+# Verifier Standard (VSTD) serialized receipt identifiers
-**Status:** normative for wire-identifier dispatch; filename history is informative
-**Date:** 2026-08-22
+> **Acronyms:** command-line interface (CLI).
-VSTD has no demonstrated external adoption or independent implementation as of this
-release. This document therefore does not prescribe an adopter migration. It records
-identifiers and filenames that appeared in the project's own public releases so that
-those artifacts are not silently reinterpreted.
+**Status:** normative for current serialized-receipt dispatch
+**Date:** 2026-08-29
-Specification numbers now identify verification depth. Repository releases use
-semantic versions independently.
+A **serialized receipt identifier** is the value written into a receipt to select its exact reader and schema, principally `schema_version` plus any required profile discriminator. Standards literature often calls this a *wire identifier* or part of a *wire format*; here it means the stored JavaScript Object Notation (JSON) contract, not a network protocol.
-## 1. Frozen receipt wire identifiers
+Specification numbers identify numbered profiles and their cumulative closure coordinates.
+Repository releases use semantic
+versions independently. Retired partial-profile object identifiers and specification files are
+not current profiles and are absent from this source tree; published tags and Git history
+preserve those earlier project artifacts without making the current reader accept or
+reinterpret them.
-A filename or current layer label does not change the meaning of an issued receipt.
-Readers MUST dispatch a receipt by its wire identifier:
+## 1. Current serialized receipt dispatch
-| Current layer document | Frozen wire identifier |
+Readers MUST dispatch by the exact `schema_version` and any required profile
+discriminator. Unknown identifiers, missing discriminators, and mismatched shapes fail
+closed:
+
+| Numbered-profile document | Current serialized receipt identifier |
|---|---|
-| `VSTD-1.md` | `schema_version = "VSTD-0.1"` |
-| `VSTD-2.md` | `schema_version = "VSTD-0.2"` |
+| `VSTD-1.md` | `schema_version = "VSTD-1"` |
+| `VSTD-2.md` | `schema_version = "VSTD-2"` |
| `VSTD-3.md` | `schema_version = "VSTD-3.0"` |
+| `VSTD-4.md` | `schema_version = "VSTD-4"` |
+| `VSTD-5.md` | `schema_version = "VSTD-5"` |
| `VSTD-Graph-1.md` | `schema_version = "VSTD-DATA-0.1"` |
-New layer-4 and layer-5 documents use their own schemas without changing historical
-canonical digests.
+The frozen `VSTD-DATA-0.1` reader preserves its original separate artifact and
+transformation identifier namespaces. New Graph construction, evidence-bound Graph
+establishment, and the separate `VSTD-GRAPH-ASSURANCE-1` mechanism require global
+cross-kind disjointness; that stricter admission rule does not retroactively narrow which
+historical `VSTD-DATA-0.1` bytes can be decoded and replayed.
-### 1.1 Non-wire vocabulary
+VSTD-1 has two current receipt profiles:
-`VSTD-2.md` section 7 defines a prose lifecycle vocabulary. Only the
-`CoordinateStatus` members serialized in `receipts/schema/vstd2_receipt.json`
-(`PRE_VERIFIED`, `VERIFIED`, `FALSIFIED`, `INDETERMINATE`, `UNSUPPORTED`, `STALE`)
-are wire values. `POST_VERIFIED`, `GEOMETRY_INSPECTABLE`, and `COMPLETELY_VERIFIED`
-are descriptive terms only and have never appeared in an issued receipt; renaming
-them does not affect any canonical digest. `GEOMETRY_INSPECTABLE` was named
-`VERIFIABLE` in unreleased drafts before `v1.1.2`; a status token MUST NOT reuse the
-maintainer's name.
+| `receipt_kind` | Schema | Meaning |
+|---|---|---|
+| `claim_mechanics` | `vstd1_receipt.json` | bounded claim, evidence, checker, provenance, and reproducibility |
+| `generic_computational_run` | `vstd1_generic_run_receipt.json` | planned execution, captured outputs, assessment context, and reproduction surface |
-## 2. Historical names in project releases
+Both discriminators are required. A reader MUST NOT guess the profile from incidental
+field similarity.
-| Historical public name | Current layer label | Meaning |
-|---|---|---|
-| `VSTD-0.1` | `VSTD-1` | claim mechanics |
-| `VSTD-0.2` | `VSTD-2` | verification surface |
-| `VSTD-3.0` | `VSTD-3` | substrate accountability |
-| — | `VSTD-4` | refutability |
-| — | `VSTD-5` | witness corroboration, draft |
-| `VSTD-DATA-0.1` | `VSTD-Graph-1` | recorded lineage over collections |
-
-`VSTD-Graph-2` through `VSTD-Graph-5` first appeared under their current labels.
-
-The current repository does not duplicate old specification paths. Historical tags
-remain the resolver for the bytes published under those paths:
-
-```text
-standard/VSTD-0.1.md -> standard/VSTD-1.md
-standard/VSTD-0.2.md -> standard/VSTD-2.md
-standard/VSTD-3.0.md -> standard/VSTD-3.md
-standard/VSTD-DATA-0.1.md -> standard/VSTD-Graph-1.md
-VSTD3_THREAT_MODEL.md -> docs/layers/vstd-3/threat-model.md
-VSTD3_VENDOR_INTEGRATION.md -> docs/layers/vstd-3/vendor-integration.md
-VSTD3_REFERENCES.md -> docs/layers/vstd-3/references.md
-VSTD3_MIGRATION.md -> docs/layers/vstd-3/compatibility.md
-COMPETITION_EVALUATION_PROFILE.md -> docs/profiles/competition-evaluation.md
-CLAIMS_AND_LIMITS.md -> docs/CLAIMS_AND_LIMITS.md
-```
-
-## 2.1 Import package and distribution rename
-
-From `v1.1.2` the import package is `verifier` and the distribution is
-`verifier-standard`. Both
-were previously `verifiable` / `verifiable-standard`. The rename removes a name that
-collided with the ordinary-English adjective, with a former VSTD-2 status token, and
-with the maintainer's former project name.
-
-| Historical name | Current name | Kind |
-|---|---|---|
-| `verifiable` | `verifier` | import package |
-| `verifiable-standard` | `verifier-standard` | distribution |
-| `verifiable-standard-.zip` | `verifier-standard-.zip` | release source archive |
+The generic-run `assessment_context` is a VSTD-1 container for mechanism identity,
+declared resource bounds, prior commitment, and the refutation surface. It is not a
+VSTD-4 object and carries no VSTD-4 conformance field. The container and its selected
+fields participate in the canonical digest.
+
+### 1.1 Non-wire vocabulary
-No receipt wire identifier, schema `$id`, or canonical digest changes. Specification
-text that cites a reference module (for example `verifier.core.kernel`) is a pointer
-into the reference implementation, not a wire value.
+`VSTD-2.md` section 7 defines prose lifecycle vocabulary. Only the
+`CoordinateStatus` members serialized in `receipts/schema/vstd2_receipt.json`
+(`PRE_VERIFIED`, `VERIFIED`, `FALSIFIED`, `INDETERMINATE`, `UNSUPPORTED`, `STALE`)
+are serialized receipt values. `POST_VERIFIED`, `GEOMETRY_INSPECTABLE`, and `COMPLETELY_VERIFIED`
+are descriptive terms rather than receipt values.
-Release manifests published up to and including `v1.1.1` bind
-`verifiable-standard-.zip` in their `source.archive_prefix`.
-`scripts/release_artifacts.py verify` derives the archive name from the manifest, so
-those releases stay verifiable without republishing.
+## 2. Stored non-receipt mechanism identifiers
-## 3. CLI compatibility
+Artifact-control mechanism objects are stored JSON contracts, not network traffic, VSTD
+receipts, or new numbered profiles. They dispatch independently by:
-`vstd` is the canonical cross-platform CLI name. The `verifier` alias remains
-available, but Windows resolves the unqualified name to its built-in Driver Verifier
-utility on common `PATH` configurations. `verifiable` also remains an alias because
-project release materials and receipt instructions may bind that executable name. It is
-a command name only: since `v1.1.2` it no longer corresponds to any import package.
-Retaining either alias preserves project compatibility; it is not evidence of
-external use.
+| Object | `schema_version` |
+|---|---|
+| Freeze manifest | `VSTD-ARTIFACT-FREEZE-1` |
+| Self-closing seal envelope | `VSTD-ARTIFACT-SEAL-1` |
+| Seal closure payload | `VSTD-ARTIFACT-SEAL-CLOSURE-1` |
+| Thaw lineage sidecar | `VSTD-ARTIFACT-THAW-1` |
+
+Their normative behavior is [`ARTIFACT_CONTROL.md`](ARTIFACT_CONTROL.md); their strict
+combined schema is published as
+[`artifact-control-1.schema.json`](https://timelordraps.github.io/verifier/schemas/artifact-control-1.schema.json).
+These identifiers do not imply a network protocol or VSTD conformance result.
+
+The Graph assurance event log dispatches separately as
+`schema_version = "VSTD-GRAPH-ASSURANCE-1"`. Its governing behavior is
+[`LADDER.md` section 1.1](LADDER.md#11-artifact-first-causal-provenance-orientation),
+and its strict schema is
+[`vstd-graph-assurance-1.schema.json`](https://timelordraps.github.io/verifier/schemas/vstd-graph-assurance-1.schema.json).
+It is not an artifact-control object or a numbered-profile receipt.
+
+## 3. Import package and distribution
+
+The distribution is `verifier-standard`, the import package is `verifier`, and
+`vstd` is the canonical cross-platform CLI name. `verifier` may resolve to Windows Driver
+Verifier on common Windows `PATH` configurations. The `verifiable` command remains a
+compatibility alias for already-published execution instructions; it is not an import
+package or a standard identifier.
+
+Release verification derives archive names and console-script expectations from the
+release manifest being checked. This preserves issued release evidence without carrying
+obsolete standard identifiers into current receipt dispatch.
## 4. Release versioning
-The first repository release using integer layer names is `v1.0.0`. The release
-number does not claim VSTD-5 implementation: VSTD-5 is explicitly draft. Existing
-`v0.1.0` and `v0.2.0` tags and GitHub releases remain untouched.
+A repository release number does not claim conformance to a same-numbered VSTD profile.
+VSTD-5's reference mechanism is implemented. This project-specification status does not
+claim an external witness, independent implementation, standards-body consensus,
+accreditation, or interoperability deployment.
diff --git a/standard/ARTIFACT_CONTROL.md b/standard/ARTIFACT_CONTROL.md
new file mode 100644
index 0000000..772455a
--- /dev/null
+++ b/standard/ARTIFACT_CONTROL.md
@@ -0,0 +1,188 @@
+# Verifier Standard (VSTD) artifact freeze, seal, and thaw mechanism
+
+> **Acronyms:** American Standard Code for Information Interchange (ASCII);
+> JavaScript Object Notation (JSON); Privacy-Enhanced Mail (PEM);
+> Secure Hash Algorithm 256-bit (SHA-256); Secure Hash Algorithm 3 256-bit (SHA3-256);
+> Unicode Transformation Format, 8-bit (UTF-8); Verifier Standard (VSTD).
+
+**Status:** normative for artifact-control mechanism version 1
+
+This mechanism preserves exact regular-file bytes, binds them to artifact-derived
+identifiers, optionally closes the freeze with a readable Ed25519 seal, and creates
+mutable descendants by copy-on-write thaw. It is not a numbered VSTD profile, receipt profile,
+encryption format, archival service, correctness proof, or actor reputation system.
+
+## 1. Distinct operations
+
+| Operation | What it establishes when verified | What it does not establish |
+|---|---|---|
+| **Freeze** | The bundle's current regular-file bytes and portable paths match its manifest, and its guarded payload tree is read-only. | Durable external preservation, privileged-write prevention, correctness, freshness, or a cryptographic signer. |
+| **Seal** | A carried public key verifies a signature over the exact freeze closure, and the seal identifier closes the signature-bearing envelope. | Encryption, secrecy, ownership, authorization, trusted time, signer reputation, or protection against whole-bundle substitution. |
+| **Thaw** | The creation operation copied a clean sealed parent into a new mutable descendant and emitted a lineage sidecar. Later `THAWED_CLEAN` status establishes current equality only when the actual supplied parent verifies and every recorded parent coordinate agrees. | Authentication of the historical copy operation, mutation of the parent, continued equality after thaw, or a sealed descendant. |
+
+Sealing and encryption are independent. Version 1 seals are readable and authenticated;
+they do not encrypt any byte. A future encrypted container MUST still identify a separate
+encryption mechanism and MUST NOT treat confidentiality as closure or correctness.
+
+## 2. Bundle and preservation boundary
+
+A bundle contains:
+
+```text
+bundle/
+ payload exact file, or directory of exact files and paths
+ freeze.json VSTD-ARTIFACT-FREEZE-1 manifest
+ seals/*.json zero or more VSTD-ARTIFACT-SEAL-1 envelopes
+```
+
+The mechanism accepts regular files, directories, and empty directories. Symbolic links
+and special filesystem objects fail closed. It preserves file bytes and portable relative
+paths. Permissions, owners, access-control lists, timestamps, extended attributes, sparse
+allocation, and filesystem-specific metadata are outside version 1. The portable
+read-only guard is an observable tripwire, not an access-control boundary against a
+privileged writer.
+
+Freeze classifies the caller-supplied final source entry before dereferencing it, so a
+symbolic link cannot inherit its target's artifact identity. A new bundle, thaw descendant,
+or generated thaw sidecar requires an absent lexical destination: an existing file,
+directory, special object, symbolic link, or dangling symbolic link refuses creation.
+Exclusive file and sidecar creation narrows replacement races, but version 1 does not claim
+universal race-free filesystem security against a concurrent privileged process.
+
+Authoritative entries inside a bundle have a stricter role-specific boundary.
+`freeze.json` and every `seals/*.json` member must be lexical ordinary files; `seals` and a
+directory payload must be lexical ordinary directories; and a file payload must be a
+lexical ordinary file. An internal symbolic link or supported reparse-point alias fails
+structurally even when its target is byte-identical, correctly signed, read-only, or inside
+the same bundle. Manifest parsing and closure use one captured ordinary-file byte snapshot.
+This differs from a caller-supplied outer parent-bundle path or explicit thaw-record path,
+which may be a read-only alias because the resolved bytes and every applicable seal, anchor,
+and binding are subsequently verified. Version 1 identifies accepted ordinary files by
+portable path and bytes, not exclusive inode ownership; hard-link identity remains outside
+its claims. Mount, network-filesystem, and concurrent-replacement behavior remains bounded
+by the host and does not establish universal alias resistance.
+
+“Portable” means slash-normalized relative path representation. Case sensitivity,
+Unicode normalization, reserved names, and path-length limits remain properties of the
+host filesystem; version 1 does not claim that every valid source tree can be materialized
+unchanged on every filesystem.
+
+The manifest inventories every file with its byte size, SHA-256, and SHA3-256 digest. A
+directory entry preserves an empty directory or parent path. Identifier computation uses
+canonical JSON with UTF-8, sorted object keys, no insignificant whitespace, no non-finite
+numbers, and no duplicate keys. Stored objects remain readable; unknown fields fail closed.
+
+## 3. Artifact-derived identifiers
+
+Every version 1 identifier carries independent SHA-256 and SHA3-256 commitments:
+
+```text
+vstd--1:sha256:<64 lowercase hexadecimal characters>:
+ sha3-256:<64 lowercase hexadecimal characters>
+```
+
+`content_id` closes artifact kind, paths, byte sizes, and file digests. `artifact_id`
+closes the same content plus the declared media type. `freeze_id` closes the complete
+freeze manifest except its own field. The artifact therefore carries a self-consistent
+identity while frozen and sealed; the identity is derived from artifact state, not from
+an actor's name or standing.
+
+Hash commitments are indexes and mutation detectors, not preservation. The bundle keeps
+the exact bytes so a verifier can recompute both algorithms. If an algorithm weakens,
+later evidence may add a new external commitment to the preserved historical bytes. It
+MUST NOT rewrite the old manifest or claim that a digest alone retained the bytes.
+
+## 4. Finite self-closing seal
+
+A `VSTD-ARTIFACT-SEAL-1` envelope contains the complete seal payload, raw public key,
+signature, and seal identifier. The seal payload closes the artifact, content, freeze,
+exact freeze-manifest digests, key identifier, signature algorithm, and closure rule.
+
+Let `C(x)` be version 1 canonical JSON, `E` the complete envelope, and `Sign` Ed25519:
+
+```text
+E0 = E with signature_base64 = null and seal_id = null
+signature = Sign(private_key, C(E0))
+E1 = E with signature_base64 = signature and seal_id = null
+seal_id = dual_digest("vstd-seal-1", C(E1))
+```
+
+Verification reconstructs `E0`, verifies the signature with the carried public key,
+reconstructs `E1`, recomputes `seal_id`, and independently recomputes the freeze and
+payload bytes. The two explicit holes terminate the construction: no seal-of-seal chain
+is required, while a change to any closed field, signature, or identifier fails.
+
+The carried key proves only internal signature consistency. An attacker can substitute a
+whole self-consistent bundle and key. A relying party that needs continuity with an
+earlier coordinate MUST supply an expected `artifact_id`, expected key identifier, or
+separately verified external log/manifest entry. External anchoring is not part of
+self-closure and actor identity contributes no verdict weight.
+
+Duplicate copies of one seal deduplicate by `seal_id` and add no strength. A valid and an
+invalid seal remain `CONFLICTED`; placement or multiplicity cannot erase the invalid
+evidence.
+
+## 5. Thaw and lineage
+
+Thaw requires a cleanly verified seal. It copies the parent payload to a new writable
+path and emits a `VSTD-ARTIFACT-THAW-1` sidecar beside the descendant. The sidecar records
+the parent artifact, content, freeze, and seal identifiers. It is lineage metadata, not a
+seal. The requested descendant and sidecar paths must both be lexically absent; thaw never
+uses a preexisting symbolic link as permission to create or label its target. The parent
+remains unchanged.
+
+A sidecar's self-derived `thaw_id` establishes only internal agreement among its fields.
+Sidecar-only status is `NOT_ESTABLISHED`, even when current descendant bytes match the
+recorded artifact identifier. `THAWED_CLEAN` requires the actual supplied parent bundle to
+verify as cleanly `SEALED`; its artifact, content, freeze, artifact-kind, and media-type
+coordinates must equal the sidecar; and every sidecar seal identifier must remain valid on
+that parent. Later additional valid parent seals are permitted. A conflicted parent or any
+coordinate mismatch fails closed. Authoritative parent metadata—not sidecar metadata—is
+used for the established descendant comparison.
+
+`THAWED_CLEAN` means the current descendant matches that supplied, cleanly sealed parent.
+`THAWED_DIRTY` means the verified parent coordinates still agree but the descendant no
+longer does. Neither result proves that a verifier independently observed or authenticated
+the historical copy operation. That claim requires a separately signed, logged, attested,
+or otherwise mechanism-checked event. Without an expected artifact identifier, expected
+key identifier, or separately verified external log coordinate, a supplied parent proves
+internal parent consistency rather than external continuity.
+
+To produce a new frozen artifact, freeze the descendant into a new bundle and bind the
+sealed parent through `lineage`. This is an additive state transition; no operation edits
+or erases the parent.
+
+`bound_contexts` similarly binds the artifact identifiers of clean sealed context
+bundles. It does not interpret or validate their subject matter. A sealed realm descriptor,
+for example, remains only a bound declaration until a named realm or mapping verifier
+checks it.
+
+## 6. Results and artifact-first semantics
+
+| State | Meaning |
+|---|---|
+| `FROZEN_UNSEALED` | Exact bytes, manifest, identifiers, and guards recomputed; no valid seal was required or established. |
+| `NOT_ESTABLISHED` | A seal was required but none was established. |
+| `SEALED` | Freeze, guards, and at least one seal verified with no contradictory seal. |
+| `CONFLICTED` | Valid and invalid seal evidence coexist. |
+| `FAIL` | A checked structural, byte, guard, seal, or external-anchor condition failed. |
+| `THAWED_CLEAN` / `THAWED_DIRTY` | With an actual cleanly verified supplied parent whose exact recorded coordinates agree, a mutable descendant currently matches or differs from that parent. Historical execution of the copy remains `NOT_ESTABLISHED`. |
+
+A clean freeze or seal can earn bounded **TRUST** in integrity and closure. It earns no
+support for semantic correctness. Freezing does not stop **ROT** caused by staleness,
+revocation, supersession, broken dependencies, or changed admissibility. A clean preserved
+ancestor may lower mutation-related diagnostic priority, but **RUST** remains reverse
+diagnostic reachability rather than innocence, guilt, or causal localization.
+
+Realm and temporal claims follow the
+[realm and time-capsule architecture](https://github.com/TimeLordRaps/verifier/blob/main/docs/REALMS_AND_TIME_CAPSULES.md). A structural seal
+is atemporal at its core. It binds a realm descriptor only as context and does not prove
+that realm, its clocks, mappings, physical laws, or continuous closure.
+
+## 7. Public format and implementation
+
+The strict combined schema is published at
+[`artifact-control-1.schema.json`](https://timelordraps.github.io/verifier/schemas/artifact-control-1.schema.json).
+The Python application programming interface and `vstd artifact` commands are generated
+in the public reference. Ed25519 operations require the optional `seal` dependency extra;
+the base package retains zero required third-party runtime dependencies.
diff --git a/standard/LADDER.md b/standard/LADDER.md
index be23b3d..202db3d 100644
--- a/standard/LADDER.md
+++ b/standard/LADDER.md
@@ -1,41 +1,267 @@
-# The VSTD Ladder — what the numbers mean
+# The Verifier Standard (VSTD) verification complex — what the numbers mean
+
+> **Acronyms:** application programming interface (API); conjunctive normal form (CNF); Certificate Transparency (CT);
+> deletion resolution asymmetric tautology (DRAT); grounded decision certificate (GDC);
+> JavaScript Object Notation (JSON); National Institute of Standards and Technology (NIST);
+> nondeterministic polynomial time (NP); proof-carrying code (PCC);
+> World Wide Web Consortium provenance vocabulary (PROV); PROV data model (PROV-DM); Protect the Software (PS);
+> Request for Comments (RFC); reverse unit propagation (RUP); Boolean satisfiability problem (SAT);
+> Supply-chain Levels for Software Artifacts (SLSA); satisfiability modulo theories (SMT);
+> SMT library standard (SMT-LIB); Secure Software Development Framework (SSDF); The Update Framework (TUF);
+> unsatisfiable (UNSAT); World Wide Web Consortium (W3C).
**Status:** project specification (normative for numbering and composition)
**Editor:** TimeLordRaps
**License:** Apache-2.0
-VSTD specification numbers are **layers of verification depth**, not revisions of a single
-document. VSTD-3 does not supersede VSTD-1 any more than a floor supersedes its
-foundation.
+**Normative language:** The uppercase key words in this series are interpreted as
+described by [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and
+[RFC 8174](https://www.rfc-editor.org/rfc/rfc8174) only when they appear in all capitals;
+lowercase uses are ordinary prose.
+
+**Reader context:** [`Concept guide and intellectual precedents`](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md)
+
+VSTD records separate answers to separate verification questions about an identified
+claim, its evidence, the mechanism that checked it, and the bounds of that check. It does
+not collapse those answers into one universal “verified” label or confidence score. The
+questions, their evidence-bearing relations, and cumulative profiles over them form the
+VSTD **verification complex**.
+
+### Read this first: the number is a checklist position, not a strength score
+
+A numbered profile is a cumulative checklist on one axis. `VSTD-3`, for example, means
+that the required questions for `VSTD-1`, `VSTD-2`, and `VSTD-3` are each established by
+their own applicable evidence. It does **not** mean “assurance strength 3,” software
+version 3, or evidence that is three times stronger than `VSTD-1`.
+
+Here, **established** means that a named mechanism checked the exact required proposition
+against bound evidence under declared limits. A field, document, or actor merely saying
+that the proposition passed does not establish it.
+
+Consider one verification object with these separately recorded results:
+
+| Object-axis question | Current result |
+|---|---|
+| `VSTD-1` Claim Mechanics | established |
+| `VSTD-2` Verification Surface | `UNKNOWN` |
+| `VSTD-3` Substrate Accountability | established |
+
+Its **object profile depth is 1**. The cumulative checklist cannot skip the missing
+`VSTD-2` result. The `VSTD-3` coordinate evidence remains recorded and useful, but it does
+not fill the `VSTD-2` gap or make profile 3 satisfied.
+
+The Graph axis applies a different cumulative checklist to a collection of artifacts and
+their recorded relations. `VSTD-3` and `VSTD-Graph-3` therefore ask different questions;
+the shared number does not make them equivalent.
+
+In one sentence: a **closure coordinate** is one verification question, a **numbered
+profile** is a cumulative checklist of those questions on one axis, and **profile depth**
+is the largest uninterrupted prefix of that checklist that is established.
+
+### Terminology contract
+
+The rest of the Standard uses the following terms precisely:
+
+| Term | Plain meaning | Important boundary |
+|---|---|---|
+| **Closure coordinate** | One named verification question and its failure class, such as Claim Mechanics or Refutability. | Closure is scoped to that question. VSTD-2 surface closure, Graph provenance closure, refutability closure, and artifact-seal structural closure are different results. |
+| **Numbered profile** | A cumulative checklist selected by `VSTD-N` or `VSTD-Graph-N`. Profile `N` requires its named coordinate and every earlier coordinate on the same axis. | A profile number is not a software revision, spatial layer, confidence score, or substitute for the underlying results. |
+| **Profile axis** | One ordered family of cumulative checklists. VSTD has an object axis and a Graph axis. | Equal numbers on different axes do not identify equivalent or interchangeable results. |
+| **Object profile depth** | For one verification object, start at `VSTD-1` and count upward only while every required coordinate remains established. The last uninterrupted number is its depth. | Depth is a compact summary of separately established results, not a new verdict, evidence-strength rating, or permission to ignore a later established coordinate after an earlier gap. |
+| **Candidate Graph profile** | The greatest Graph checklist position satisfied by the current caller-supplied ratings. | The current calculation is `NOT_ESTABLISHED` because those ratings are not evidence-bound. It is not a verified Graph profile. |
+| **Evidence-bound Graph profile** | The greatest Graph checklist position obtained after rerunning exact member, ancestor, and edge rating mechanisms from content-addressed evidence. | It is established only under the named mechanisms, trust roots, evidence, bounds, lifecycle view, and conflict state. |
+| **VSTD-4 rung** | One of the fourteen ordered refutability obligations `4.1` through `4.14`. | “Rung” names only this internal sequence, never a top-level VSTD profile. |
+| **Verification order** | One adjacent meta-verification order in the VSTD-2 geometry model. | The compatibility names `VerificationLayer` and `verification_layers` do not denote numbered VSTD profiles. |
+| **Level** | A retained word in an explicitly named external taxonomy or compatibility identifier, including `ReproducibilityLevel`, `AvailabilityLevel`, `graph_level`, and serialized Graph `level` fields. | In Graph compatibility identifiers, the value is the candidate Graph profile number; “level” is not the governing name for a VSTD profile. |
+| **Layer** | An implementation, protocol, or physical stack whose parts are ordered by containment. | It does not name `VSTD-N` or `VSTD-Graph-N`; historical paths such as `verifier.layer4` remain compatibility identifiers only. |
+| **Tier** | A declared checker-cost class in `VSTD4-GDC-1`. | It is not a VSTD profile, evidence-strength rating, or actor rating. |
+
+“Profile” must also be qualified when confusion is possible: **numbered profile**, **receipt
+profile**, **application profile**, or **geometry profile**. Likewise, “depth” must be
+qualified as object profile depth, VSTD-4 normative or candidate depth, or lineage
+topological depth. The retained `LADDER.md` filename is a stable document path, not the
+governing topology; this document defines a verification complex.
+Current public serialized identifiers, fields, class names, functions, and module paths
+retain their exact compatibility spelling; adjacent prose supplies the precise meaning.
+Profiles are therefore **requirement-set coordinates**, not spatial layers. A profile is
+satisfied only when a named mechanism has bound evidence for every required fact; Boolean
+SAT over caller-supplied assertions establishes only a candidate formula result.
---
## 1. The governing idea
-Each layer names a distinct verification question and a distinct failure class. The
-ordering is a composition rule, not logical entailment between layers.
+Each closure coordinate names a distinct verification question and failure class. Profile
+ordering is a composition rule, not logical entailment between coordinates.
+
+The nearest familiar security analogy is
+[defense in depth](https://en.wikipedia.org/wiki/Defense_in_depth_%28computing%29 "Wikipedia orientation; primary references are mapped below"),
+but the analogy is limited: VSTD closure coordinates are separately evidenced questions, not
+interchangeable controls whose mere quantity establishes assurance. Decomposing assurance
+into named components also has precedent in the Common Criteria, while VSTD deliberately
+uses different coordinates, evidence rules, and conformance semantics.
-**Evidence for one layer never supplies evidence for another layer.** In particular,
-layer-4 evidence does not supply, imply, upgrade, or repair layer 3, 2, or 1. A reported
-depth of `N` is only shorthand for `N` separately checked results, one for each layer
-from 1 through `N`.
+**Evidence for one closure coordinate never supplies evidence for another.** In particular,
+Refutability evidence does not supply, imply, upgrade, or repair Substrate Accountability,
+Verification Surface, or Claim Mechanics evidence. A reported object profile depth of `N`
+is only shorthand for the separately checked results required by profiles 1 through `N`.
-Reflection and metalanguage are useful design analogies for asking what a given
+Reflection and [metalanguage](https://en.wikipedia.org/wiki/Metalogic "Wikipedia orientation; not a proof of the VSTD verification complex")
+are useful design analogies for asking what a given
verification surface leaves unexamined. VSTD does not claim that Tarski's
-undefinability theorem proves this ladder, that adjacent layers form formal
-metalanguages, or that a lower-layer implementation is logically incapable of
-describing another layer's failure. The normative requirement is narrower: an
+[undefinability theorem](https://en.wikipedia.org/wiki/Tarski%27s_undefinability_theorem "Wikipedia orientation; the theorem does not derive this verification complex")
+proves these profile coordinates, that adjacent profiles form formal
+metalanguages, or that an earlier-profile implementation is logically incapable of
+describing another coordinate's failure. The normative requirement is narrower: an
implementation MUST NOT treat success on one question as evidence for a different
question.
+### 1.1 Artifact-first causal provenance orientation
+
+VSTD evaluates bounded propositions about computational processes represented by
+identified software, executions, evidence, and resulting artifacts. It does not evaluate
+whether an actor is good, bad, reputable, or worthy of trust. Standing alone, an actor's
+identity, popularity, repetition, or reputation MUST NOT strengthen an artifact-bound
+result. A named mechanism MAY establish an exact attribution, authorization, or separation
+proposition by checking the required identity evidence; that result remains an adjacent
+proposition and MUST NOT promote an unrelated computational claim.
+
+**Zero identity** means zero identity-derived verdict weight, not anonymity or absence of
+identifiers. **Zero knowledge** means zero unevidenced knowledge is presumed: without a
+mechanism-earned result for the exact proposition, its state remains `UNKNOWN`. This
+architectural zero-knowledge rule MAY be enclosed by cryptographic zero knowledge when a
+witness must remain confidential. That enclosure MUST bind the exact software or program
+coordinate, predicate, public commitments, output, proof parameters, and verification
+mechanism while revealing no more witness information than its declared proof statement.
+Cryptographic zero knowledge MUST be claimed only when a named proof system establishes
+that property under explicit assumptions; a digest or undisclosed input alone is not such
+a proof. The resulting support is bearer- and artifact-bound, never prover-identity-bound.
+**Actor** and **artifact** remain contextual roles, not permanent entity classes: software
+can be an artifact when created, versioned, or evaluated and an actor when it executes a
+transformation.
+
+The capitalized terms **TRUST**, **RUST**, and **ROT** are formal VSTD semantic names, not
+acronyms, numbered-profile receipt verdicts, actor ratings, scalar scores, or references to
+the Rust programming language. They serialize as typed event kinds only in the non-receipt
+`VSTD-GRAPH-ASSURANCE-1` mechanism log. The same bound development graph and its time-indexed lifecycle carry three
+distinct relations:
+
+```text
+development: ancestor artifact --TRUST through a checked transformation--> descendant
+lifecycle: recorded TRUST --ROT under typed current-state evidence--> reassessment
+diagnosis: descendant deviation --RUST memetic causal backtrace--> ancestor candidates
+```
+
+**Memetic propagation** is the transmission of claim and evidence state through recorded
+developmental provenance. The genetic or viral language names this inheritance mechanic:
+TRUST moves forward into descendant claim space; RUST moves backward toward recorded
+ancestor states; ROT changes the current admissibility of previously recorded support. It
+does not claim biological transmission or make identity and reputation sources of
+assurance.
+
+**TRUST** is positive support earned when a named mechanism checks an exact
+artifact-bound process obligation under declared evidence, specification, bounds, and
+trust roots. It moves parent-to-child only across a declared creation or dependency edge
+whose relevant transformation obligations pass. Applicable support composes by
+intersection and is capped by the weakest required parent or edge; it is never added,
+averaged, voted, or converted into actor standing. Every child MUST still discharge its
+new predicates, transformations, boundaries, and evidence obligations. A declared trust
+root is an explicit dependency and stopping boundary, not actor TRUST.
+
+The reference event mechanism realizes that rule edge by edge. Each TRUST event binds the
+historical Graph digest, one exact transformation, its complete input artifact set, one
+output artifact, and the exact prerequisite TRUST event for every derived input. A
+descendant event is current only while every recursively required event, input, output,
+and transformation remains admissible and free of an admissibility-blocking conflict. A
+status-conflict resolution projects its selected state into the current view: `VALID` or
+`COMPLETED` may restore the affected route, while `REVOKED`, `FAILED`, or another
+inadmissible state cannot. Resolving an arbitrary predicate selects a retained value but
+does not establish its admissibility effect, so the route remains blocked. The current
+reference runtime implements no general non-status admissibility-effect mechanism.
+Alternate or duplicate paths remain distinct recorded routes; their count supplies no
+added strength or witness independence.
+
+**ROT** is typed, time-indexed degradation of the current admissibility of recorded TRUST.
+It requires exact lifecycle or dependency evidence, such as expiry under a declared
+freshness bound, `STALE`, `CHALLENGED`, `REVOKED`, `SUPERSEDED`, or an invalidated required
+coordinate. Wall-clock passage, age, or popularity alone MUST NOT create ROT. ROT MUST NOT
+rewrite an immutable historical receipt or imply that its historical result was false. It
+may require reassessment of dependent descendants, but any resulting status change still
+requires its named policy or mechanism.
+
+**RUST** is the inverse-TRUST diagnostic mechanic: a typed trace created by an observed
+descendant deviation from a declared expectation. It moves child-to-parent only through
+historically recorded contributing creation, input, or transformation paths. Current
+revocation, challenge, staleness, or conflict can remove a route from current TRUST without
+erasing it from historical diagnostic ancestry. The inverse is directional and diagnostic,
+not arithmetic: TRUST and RUST never cancel. Distinct comparable backtraces may concentrate
+on a shared ancestor and prioritize it for falsification or diagnostic examination.
+Transferred RUST establishes ancestral reachability, not current admissibility, direct
+observation, falsehood, or causal responsibility; localization requires additional
+intervention, ablation, independently bound execution evidence, or an equivalent declared
+mechanism.
+
+Reference causal localization MUST select one exact passing RUST event, bind that event's
+digest and the exact descendant-deviation proposition digest, confirm the selected artifact
+is among that event's recorded ancestors, and preserve those coordinates through replay.
+
+**BLAME** and **GUILT** are bounded artifact-relative diagnostic results, not opposite
+directions on the Graph. BLAME requires a named mechanism to establish that an exact
+artifact bears responsibility for or materially contributed to an exact localized
+deviation. GUILT requires three separately bound passing components whose coordinates agree:
+(1) responsibility or material contribution by exact artifact A for exact localized
+deviation D; (2) applicability to A of exact obligation O under its declared scope,
+assumptions, exclusions, roots, and bounds; and (3) violation by A of that same O relative to
+that same D and applicable scope. The final GUILT proposition MUST bind the exact obligation
+coordinate, causal-localization event digest, and all three component digests. A single
+compound mechanism MAY check the components in one invocation only when it emits three
+separately bound evaluations; one opaque combined result or nonempty obligation string is
+insufficient. An existing passing BLAME event can supply the responsibility component only
+when its exact event digest and all coordinates match. Neither term concerns actor morality,
+character, identity, reputation, automatic legal liability, or social scoring. Exoneration,
+innocence, obligation satisfaction, absence of hidden contributors, or not-guilty conclusions
+require their own exact propositions and mechanisms; a missing component remains `UNKNOWN`,
+not evidence of the opposite. The localization event transitively binds the selected RUST
+event and exact deviation, so neither result can float across two deviations on the same
+descendant.
+
+The word *causal* is required here for recorded developmental and provenance causality:
+the graph states which artifacts and transformations produced later claim architecture.
+Propagation across those causal-provenance edges does not by itself establish
+intervention-level physical causality, causal localization, responsibility, or guilt.
+
+TRUST, ROT, and RUST MUST remain separate. They do not cancel, form one scalar score, or
+flow in the opposite direction as inherited truth, decay, or guilt. `UNKNOWN` and
+`CONFLICTED` support or lineage MUST remain visible and MUST NOT become a clean signal.
+`VSTD-GRAPH-ASSURANCE-1` now serializes an additive, hash-chained reference event log with
+the complete historical Graph, exact proposition bindings, and embedded evidence bytes.
+`AssuranceLedger` implements mechanism-earned forward TRUST edge by edge, typed ROT,
+challenge-ledger status projection, reverse RUST reachability, unique-descendant structural
+concentration, additive conflict declaration and resolution, explicit causal localization,
+separately bound responsibility/applicability/violation components, and component-composed
+artifact-relative GUILT. Duplicate paths and repeated records remain set-valued and earn no
+strength. `recheck_assurance_log` reconstructs the historical Graph, rehashes the embedded
+evidence, reruns every exact component mechanism—including one-invocation compound groups as
+such—reproduces the event hash chain, and compares the derived current view. A deployment
+still supplies the proposition-specific mechanisms: the event format and dispatcher do not
+establish real-world obligation applicability, legal culpability, a universal support-transfer
+algebra, or causality from topology.
+
+Artifact freezing and sealing are bounded mechanisms under this orientation, specified
+separately in [`ARTIFACT_CONTROL.md`](ARTIFACT_CONTROL.md). A verified freeze preserves
+and recomputes exact bytes; a verified seal earns structural closure for those bytes. It
+does not earn semantic correctness, prevent ROT, localize RUST, create actor TRUST, or
+supply any numbered profile. A sealed realm or temporal descriptor remains a bound input
+until its own mapping, continuity, or transition verifier checks the exact proposition.
+
---
-## 2. The object ladder
+## 2. The object profile axis
VSTD proper governs the verification of **one object**. Call this verification
*mechanics*.
-| Layer | Name | Closes | Does not establish |
+| Numbered profile | Required closure coordinate | Closes | Does not establish |
|---|---|---|---|
| **1** | Claim mechanics | A malformed or tampered statement | Whether the claim applies where it is being applied |
| **2** | Verification surface | A verdict leaking beyond the coordinate actually verified | Whether the evidence behind it is real |
@@ -43,18 +269,23 @@ VSTD proper governs the verification of **one object**. Call this verification
| **4** | Refutability | A claim unfalsifiable in principle by any outside party | Whether the parties who could check are independent |
| **5** | Witness corroboration | Pseudo-independence — witnesses sharing the declarant's trust root | — |
-### 2.1 The self-discernability boundary
+### 2.1 The single-declarant boundary
+
+**A single declarant can in principle produce the evidence required by profiles 1 through
+4.** No second party is required merely to create those bounded inputs and mechanisms.
-**Layers 1 through 4 are self-discernable.** A declarant can establish them alone, with
-no second party in existence.
+**Profile 5 is not.** It requires another party to exist, to act, and to be independent.
-**Layer 5 is not.** It requires another party to exist, to act, and to be independent.
+VSTD-1 records the claim-mechanics status of actor independence but cannot infer it from
+two runs or matching artifacts. VSTD-5 requires the corroborating witness procedure that
+uses such separately evidenced actor participation; recording a field is not witnessing.
-That transition between 4 and 5 is the most important boundary in the ladder. Layer 4
-asks *could a stranger check this?* Layer 5 asks *did one, and were they actually a
-stranger?* The first is a property of the claim. The second is a property of the world.
+That transition between profiles 4 and 5 is the most important object-axis boundary.
+Refutability asks *is a bounded outside check possible?* Witness Corroboration asks *was
+one performed, and are the required separation seams evidence-bound?* The first is a
+property of the claim surface. The second requires additional evidence about an execution.
-An implementation MUST NOT report a layer-5 property on the basis of layer-4 evidence.
+An implementation MUST NOT report a profile-5 property on the basis of Refutability evidence.
Preparing to be checked is not being checked.
---
@@ -65,9 +296,12 @@ VSTD-Graph governs the verification of a **collection** of objects. Call this
verification *dynamics*.
The two axes are parallel but coupled: a collection's dynamics are constrained by its
-members' mechanics, and by the provenance edges between them.
+members' mechanics, and by the
+[provenance](https://en.wikipedia.org/wiki/Data_provenance "Wikipedia orientation; see W3C PROV-DM and supply-chain references below")
+edges between them. The implemented N-ary representation is a
+[hypergraph](https://en.wikipedia.org/wiki/Hypergraph "Wikipedia orientation; not a claim of complete real-world lineage").
-| Layer | Name | Collection-level closure |
+| Numbered profile | Required closure coordinate | Collection proposition |
|---|---|---|
| **Graph-1** | Recorded lineage | members and transformations are represented |
| **Graph-2** | Bounded collection surface | scope does not leak across the collection |
@@ -75,27 +309,42 @@ members' mechanics, and by the provenance edges between them.
| **Graph-4** | Refutable transformation closure | challenges compose across hyperedges |
| **Graph-5** | Corroborated verification network | member and edge witnesses are independently corroborated |
-A collection `C` holds at Graph layer `N` only if all four conditions hold:
+A collection `C` satisfies candidate Graph profile `N` only if all four conditions hold:
-1. **Membership floor** — every member is at object layer ≥ N.
-2. **Provenance closure** — every ancestor reachable from any member is at layer ≥ N.
-3. **Status admissibility** — no ancestor is `REVOKED`, `CHALLENGED`, `STALE`, or
- `UNKNOWN`.
-4. **Edge evidence** — the transformation hyperedges themselves carry layer-N evidence.
+1. **Membership floor** — every member rating is at object profile ≥ N.
+2. **Provenance closure** — every ancestor reachable from any member is rated at object
+ profile ≥ N.
+3. **Status admissibility** — no ancestor is `REVOKED`, `CHALLENGED`, `STALE`,
+ `UNKNOWN`, or subject to an unresolved `CONFLICTED` record.
+4. **Edge evidence** — the transformation hyperedges themselves carry profile-N ratings.
Condition 2 is what a plain minimum over members misses. Condition 4 is what makes this
dynamics rather than aggregation: **a graph is only as verified as its edges**, and an
-unevidenced edge between two layer-5 artifacts does not yield a layer-5 collection.
+unevidenced edge between two profile-5 artifacts does not yield a Graph-5 collection.
-The level is **computed, never declared**:
+The candidate Graph profile number is **computed from object and edge ratings, never
+declared**:
```
-graph_level(C) = max { N : CNF_N(C) is satisfiable }
+candidate_graph_profile(C) = max { N : CNF_N(C) is satisfiable }
```
-The reference implementation searches 5→1. At a result below 5, the grounded
-`FAIL` certificate for `N+1` is the explanation of the ceiling. A level without
-that certificate is a declaration and is non-conforming.
+The compatibility API `graph_level` implements that function and the frozen Graph receipt
+stores its number in a `level` field. The reference implementation searches 5→1 and
+certifies its Boolean encoding. Its current rating inputs are caller-supplied, so it reports
+a **candidate Graph profile** with
+`conformance_status = NOT_ESTABLISHED`; the certificate proves the computation over
+those inputs, not the validity of the ratings. At a result below 5, the grounded `FAIL`
+certificate for profile `N+1` explains that candidate ceiling. Graph conformance additionally
+requires evidence-bound ratings under the applicable object and edge profiles.
+`establish_graph_level` supplies that path: it rehashes embedded evidence, reruns the exact
+registered rating mechanism for every member, ancestor, and reached edge, then recomputes
+and kernel-checks the Graph certificate. Each rating proposition binds a digest over the
+historical Graph bytes, deduplicated member set, collection identifier, and claim binding;
+neighboring collection or topology evidence therefore contributes zero. Missing,
+non-integer, or non-passing bindings also contribute zero and prevent conformance. Profile
+zero never receives `ESTABLISHED`. The record builder recomputes before serialization, and
+the rechecker preserves offline replay.
---
@@ -108,17 +357,22 @@ the third is the load-bearing one.
VSTD does not classify every receipt as an NP certificate. Specific bounded formats,
including `VSTD4-GDC-1`, define a finite decision problem, a certificate language, and
-an independent checker. Complexity claims apply only to such a defined formal problem.
+a checker implemented separately from the producer path. Complexity claims apply only
+to such a defined formal problem; checker separation alone does not establish distinct
+actors.
Other receipt fields may be signed declarations, hashes, measurements, or references
whose meaning depends on explicitly named trust roots.
The useful engineering asymmetry is concrete rather than universal: when a result can
-carry a smaller independently checkable artifact instead of requiring the original
+carry a smaller consumer-checkable artifact instead of requiring the original
computation, VSTD preserves that artifact and its verification bounds.
### 4.2 Bounded admission uses CNF
-The reference admission procedures encode finite, bounded policy questions as CNF.
+The reference admission procedures encode finite, bounded policy questions as
+[conjunctive normal form](https://en.wikipedia.org/wiki/Conjunctive_normal_form "Wikipedia orientation; the implemented format is finite CNF")
+(CNF) for the
+[Boolean satisfiability problem](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem "Wikipedia orientation; SAT success establishes only the encoded formula").
CNF is not identical to 3-SAT. A finite CNF satisfiability instance can be transformed
in polynomial time into an equisatisfiable 3-CNF instance, using auxiliary variables
where required. VSTD does not need that transformation for every checker and does not
@@ -136,7 +390,7 @@ A future claim may cover a finite enumerated world if its observation boundary a
completeness mechanism are declared and checked. It still MUST NOT be widened into a
claim about unobserved physical activity.
-This is why the ladder tops out at corroboration rather than proof of global absence. Layer 5
+This is why the object profile axis tops out at corroboration rather than proof of global absence. Profile 5
does not detect hidden work. It makes the *independence status* of declared work legible,
and leaves the undeclared remainder named and quantified rather than silent.
@@ -145,7 +399,7 @@ and leaves the undeclared remainder named and quantified rather than silent.
## 5. Certificates for refusals
This section applies to the finite propositional decision procedures used by the
-reference layer-4 implementation.
+reference Refutability implementation.
A satisfiable result already carries its certificate: the model. Anyone can evaluate it
against the clause set without a solver.
@@ -154,8 +408,12 @@ An unsatisfiable result, by default, carries nothing but the solver's word.
For a fail-closed standard, **refusals are the most consequential output**. A standard
whose passes are checkable and whose refusals are not has its assurance backwards.
-Layer 4 therefore requires a refutation certificate — a clausal proof, verifiable by
-reverse unit propagation, checkable without re-solving.
+The Refutability coordinate therefore requires a refutation certificate — a clausal proof, verifiable by
+[reverse unit propagation](https://en.wikipedia.org/wiki/Unit_propagation "Wikipedia orientation; VSTD implements a bounded RUP checker"),
+checkable without re-solving. This follows the same producer-certificate/consumer-checker
+engineering asymmetry as
+[proof-carrying code](https://en.wikipedia.org/wiki/Proof-carrying_code "Wikipedia orientation; VSTD does not inherit PCC's safety theorem"),
+while using a narrower certificate language.
Resolution proofs have exponential lower bounds for some formula families. A
conforming implementation therefore MUST declare a bound and MUST answer `UNKNOWN`
@@ -165,46 +423,79 @@ An `UNKNOWN` is never a pass and never an unsatisfiability claim.
Reference implementation: `verifier.core.refutation`.
-### 5.1 The internal VSTD-4 ladder
+### 5.1 The internal VSTD-4 rung sequence
VSTD-4 contains fourteen ordered rungs, from decision certification through
semantic binding, anti-equivocation, bounded portable checking, availability,
-precommitment, challenge handling, degradation, and compositionality. Its depth
+precommitment, challenge handling, degradation, and compositionality. Its normative depth
is computed:
```
vstd4_depth(claim) = max { k : CNF_4k(claim) is satisfiable }
```
-The certificate for rung `k+1` explains a partial depth. Only depth 14 admits a
-claim to any VSTD-5 procedure. See `VSTD-4.md` for the normative rung graph and
-`VSTD4-GDC-1` format.
+The certificate for rung `k+1` explains a partial VSTD-4 normative depth. Only established
+VSTD-4 conformance at normative depth 14 admits a claim to any VSTD-5 procedure. The current
+reference `vstd4_depth` function instead computes a structural candidate from
+caller-supplied rung references, labels conformance `NOT_ESTABLISHED`, and never admits
+VSTD-5. `establish_vstd4` reruns exact VSTD-1/2/3 and rung bindings and may report
+`EVIDENCE_BOUND` / `ESTABLISHED` only when every mechanism and the independent kernel pass.
+See `VSTD-4.md` for the normative rung graph and `VSTD4-GDC-1` format.
---
-## 6. Composition — layers do not supply or substitute
+## 6. Composition — closure coordinates do not supply or substitute
-Layer results may be composed into a depth report. They do not replace, entail, or
-supply one another.
+Closure-coordinate results may be composed into an object profile-depth report. They do
+not replace, entail, or supply one another.
-- Layer 4 without layer 3 certifies a claim whose evidence source is unaccountable.
-- Layer 5 without layer 4 solicits witnesses for a claim no witness could check.
-- Layer 2 without layer 1 scopes a statement whose integrity is unestablished.
+- Refutability without Substrate Accountability certifies a claim whose evidence source is unaccountable.
+- Witness Corroboration without Refutability solicits witnesses for a claim no witness could check.
+- Verification Surface without Claim Mechanics scopes a statement whose integrity is unestablished.
-An implementation reporting aggregate depth *N* MUST present separately checkable
-evidence for every layer from 1 through *N*. Conformance may also be reported for an
-individual layer without claiming aggregate depth. Conformance profiles are declared
-per layer, following VSTD-3 §7.
+An implementation reporting object profile depth *N* MUST present separately checkable
+evidence for every required coordinate in profiles 1 through *N*. Conformance may also be
+reported for one coordinate without claiming cumulative profile depth. Incremental
+Substrate Accountability profiles are declared in VSTD-3 §7.
"Higher is more protected" is true only in the sense that more classes of failure are
-closed. It never means the lower layers became unnecessary.
+closed. It never means the prerequisite coordinates became unnecessary.
---
## 7. Numbering
-- **Specification layers are integers**: VSTD-1 … VSTD-5, VSTD-Graph-1 … VSTD-Graph-5.
-- **Repository releases use semantic versioning** and are independent of layer numbers.
+- **Numbered profiles use integers**: VSTD-1 … VSTD-5, VSTD-Graph-1 … VSTD-Graph-5.
+- **Repository releases use [semantic versioning](https://semver.org/)** and are independent
+ of profile numbers.
-A release version never implies a layer, and a layer never implies a release. See
-`WIRE_IDENTIFIERS.md` for frozen wire identifiers and the historical public filenames.
+A release version never implies a numbered profile, and a numbered profile never implies a
+release. See
+`WIRE_IDENTIFIERS.md` for serialized receipt identifiers and historical public filenames.
+
+---
+
+## 8. Intellectual lineage and adjacent precedents
+
+The verification complex is VSTD project architecture; no cited work proves that these five
+coordinates on either axis are
+necessary, sufficient, complete, or uniquely ordered. The references below show that its
+individual design pressures have established precedents in security engineering,
+provenance, reproducible systems, and proof checking. The
+[`concept guide`](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md) provides definitions, additional
+sources, and explicit non-equivalences.
+
+| VSTD pressure | Adjacent precedent | What the precedent contributes—and does not |
+|---|---|---|
+| Separate failure surfaces and fail-closed defaults | Saltzer and Schroeder, [*The Protection of Information in Computer Systems*](https://web.mit.edu/Saltzer/www/publications/pubs.html) | Classic principles include fail-safe defaults, complete mediation, separation of privilege, and least common mechanism. They motivate separation; they do not derive VSTD's coordinate count. |
+| Named assurance components | Common Criteria, [Part 3: Security assurance components](https://www.commoncriteriaportal.org/files/ccfiles/CC2022PART3R1.pdf) | Demonstrates established componentized assurance and assurance packages. VSTD is not a Common Criteria evaluation or an Evaluation Assurance Level. |
+| Stable cryptographic representations | [RFC 8785: JSON Canonicalization Scheme](https://www.rfc-editor.org/rfc/rfc8785.html) | Shows why JSON used as cryptographic input needs invariant representation. VSTD formats retain their own declared canonicalization rules. |
+| Recorded entities, activities, and agents | W3C [PROV-DM](https://www.w3.org/TR/prov-dm/) | Supplies an interoperable provenance model adjacent to the Graph axis. VSTD-Graph is not a PROV implementation and does not infer complete history. |
+| Software materials, builders, steps, and products | [in-toto specification v1.0](https://in-toto.io/docs/specs/) and [SLSA v1.2](https://slsa.dev/spec/v1.2/) | Establish supply-chain provenance and attestation precedents. VSTD may bind their evidence but cannot manufacture their authorization or assurance level. |
+| Preserved release and provenance evidence | NIST [Special Publication (SP) 800-218 SSDF 1.1](https://doi.org/10.6028/NIST.SP.800-218) | Protect the Software practices PS.3.1 and PS.3.2 call for preserving releases and provenance and enabling integrity verification. They do not certify a VSTD receipt. |
+| Independent recreation | Reproducible Builds, [formal definition](https://reproducible-builds.org/docs/definition/) | Grounds the special case where another party recreates specified artifacts from declared inputs and instructions. Reproducibility does not establish every semantic claim. |
+| Producer-supplied portable certificates | Necula, [*Proof-Carrying Code*](https://doi.org/10.1145/263699.263712) | Establishes the pattern of an untrusted producer supplying a proof checked under a declared policy. VSTD uses the pattern beyond code safety without inheriting PCC's theorem. |
+| Consumer-checked UNSAT results | Wetzler, Heule, and Hunt, [*DRAT-trim*](https://www.cs.cmu.edu/~mheule/publications/drat-trim.pdf) | Establishes practical checking of clausal unsatisfiability proofs rather than trusting solver output. VSTD's implemented RUP format is narrower than DRAT. |
+| A first-class refusal to fabricate a Boolean answer | [SMT-LIB Standard 2.7](https://smt-lib.org/papers/smt-lib-reference-v2.7-r2025-04-09.pdf) | Its response grammar includes `sat`, `unsat`, and `unknown`. VSTD independently defines a richer status system with the same fail-closed pressure. |
+| Append-only public evidence and detectable equivocation | [RFC 9162: Certificate Transparency Version 2.0](https://www.rfc-editor.org/rfc/rfc9162.html) | Merkle proofs make log inclusion and consistency auditable while preserving explicit split-view limitations. VSTD additive receipts are analogous, not a CT implementation. |
+| Freshness, rollback, freeze, and compromise recovery | [The Update Framework specification](https://theupdateframework.github.io/specification/latest/) | Demonstrates that authentic old data is not automatically current data. VSTD does not implement TUF, but likewise keeps freshness and revocation distinct from byte identity. |
diff --git a/standard/VSTD-1.md b/standard/VSTD-1.md
index 86ed48f..bb928a7 100644
--- a/standard/VSTD-1.md
+++ b/standard/VSTD-1.md
@@ -1,7 +1,15 @@
-# VSTD-1 — Claim Mechanics
+# Verifier Standard (VSTD)-1 — Claim Mechanics
-**Layer:** 1 of 5 on the object axis (see `LADDER.md`)
-**Receipt wire format:** `schema_version = "VSTD-0.1"` — frozen; see `WIRE_IDENTIFIERS.md`
+> **Acronyms:** artificial intelligence (AI); conjunctive normal form (CNF); directed acyclic graph (DAG);
+> Davis-Putnam-Logemann-Loveland (DPLL); International Organization for Standardization (ISO);
+> JavaScript Object Notation (JSON); Request for Comments (RFC); Boolean satisfiability problem (SAT);
+> Secure Hash Algorithm 256-bit (SHA-256); satisfiability modulo theories (SMT); trusted computing base (TCB);
+> Coordinated Universal Time (UTC); Unicode Transformation Format, 8-bit (UTF-8).
+
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-1 on the object axis; required closure coordinate: Claim Mechanics (see `LADDER.md`)
+**Receipt serialization:** `schema_version = "VSTD-1"`; see `WIRE_IDENTIFIERS.md`
**Status:** Project Specification with Implemented Reference Subset
**Maintainer:** TimeLordRaps
**Date:** 2026-08-21
@@ -11,14 +19,14 @@
## 1. Purpose & Thesis
VSTD specifies infrastructure for consequential computational claims to carry
-independently checkable evidence. Conformance is defined by this document, not by
+evidence checkable outside its producer. Conformance is defined by this document, not by
the identity of its maintainer.
Modern AI systems, scientific simulators, and autonomous code generators routinely
produce complex assertions without an attached, machine-checkable audit trail showing
what evidence is offered for those claims. **VSTD-1** is a project
specification for representing claims, capturing runtime provenance, structuring
-machine-readable verification receipts, defining reproducibility levels, and
+machine-readable verification receipts, defining reproduction-fidelity states, and
separating trusted computing bases from untrusted outputs. It is not a consensus or
accredited standard.
@@ -37,7 +45,7 @@ accredited standard.
### 2.2 What a VSTD Verification Claim Does NOT Imply
1. **Universal Truth**: Verification is strictly relative to the declared formal system, input formula, and explicit scope.
2. **Unbounded Safety**: A verified component does not guarantee overall system safety if surrounding orchestration or unmodeled environmental dynamics fail.
-3. **Semantic Infallibility of Unchecked Layers**: Non-extracted, unverified natural language outside the formal translation grammar is not certified.
+3. **Unchecked Prose**: Non-extracted, unverified natural language outside the formal translation grammar is not certified.
---
@@ -50,13 +58,13 @@ additive record rather than an in-place rewrite.
| Status | Definition |
| :--- | :--- |
-| `DEMONSTRATED` | The claim is backed by executable tests or formal proofs that pass in an independently reproducible environment. |
+| `DEMONSTRATED` | The claim is backed by executable tests or formal proofs that pass in a reproducible environment with recorded execution coordinates. Actor independence is a separate claim. |
| `BENCHMARKED` | Quantitative performance or accuracy metrics have been empirically measured against a defined reference baseline. |
| `SUPPORTED` | Theoretical derivation or empirical evidence is established, but automated end-to-end continuous verification is partial. |
-| `IMPLEMENTED_UNVALIDATED` | Code or logic exists on disk, but automated independent verification has not yet run or passed. |
+| `IMPLEMENTED_UNVALIDATED` | Code or logic exists on disk, but automated end-to-end verification has not yet run or passed. |
| `INDETERMINATE` | Evidence is ambiguous, supporting leaves are unspecified, or solver execution timed out. |
| `UNSUPPORTED` | No valid empirical or formal evidence is attached to the proposition. |
-| `FALSIFIED` | An executable check, counterexample, or independent audit refuted the claim. |
+| `FALSIFIED` | An executable check, counterexample, or evidence-bound audit refuted the claim. |
| `HYPOTHESIS` | A stated conjecture intended for experimental falsification. |
| `LONG_RANGE_OBJECTIVE` | A strategic or architectural aspiration requiring substantial future R&D. |
@@ -79,17 +87,23 @@ A canonical claim record contains:
## 5. Independent Verification & Trusted Computing Base (TCB)
To prevent self-referential confirmation bias (systems verifying their own uninspected
-outputs), VSTD-1 defines an **Independent Verification Layer** as a conformance
+outputs), VSTD-1 defines **independent-verification role separation** as a conformance
requirement for claims labeled independent:
```text
Target System (Producer)
↓ (Generates derivation / CNF / artifacts)
Independent VSTD-Conformant Auditor
- ↓ (Runs independent DPLL solver + DAG grounding checker in isolated TCB)
+ ↓ (Runs separately implemented DPLL solver + DAG grounding checker in isolated TCB)
Structured VFY Receipt
```
+Independence in this profile is a claim about distinct actors occupying the producer and
+checker roles. Two executions that return the same result do not prove that separate
+actors performed them; nor do two processes or machines. Those are artifact and runtime
+observations. Actor independence requires separately bound evidence, and it never
+strengthens the checked result merely because an actor is identified or trusted.
+
### Trusted Computing Base Invariant
An auditor described as independent must:
1. Share zero solver state or runtime logic with the producer.
@@ -97,32 +111,45 @@ An auditor described as independent must:
3. Explicitly declare its TCB components in every generated receipt.
Running the bundled reference implementation does not by itself establish
-organizational, implementation, or runtime independence. A receipt MUST state the
-actual separation achieved. If producer and auditor share relevant logic or state, the
-result is still inspectable but MUST NOT be labeled independent on that seam.
+actor, implementation, or runtime independence. A receipt MUST state the actual
+separation achieved. If distinct actors are not evidenced, actor independence is
+`NOT_DEMONSTRATED` even when two results match. If producer and auditor share relevant
+logic or state, the result is still inspectable but MUST NOT be labeled independent on
+that seam.
+
+Serialized `EVIDENCED` status words and evidence-reference strings are declarations, not
+validated bindings. A runtime MUST derive independent verification only after an
+implemented validator resolves the referenced evidence, binds it to the producer and
+checker executions, and establishes distinct actors plus the claimed implementation and
+runtime seams. The VSTD 1.2.0 reference runtime implements no such adapter; it therefore
+treats externally supplied assertions as no stronger than `DECLARED`, rejects receipts
+that serialize them as `EVIDENCED`, and never emits `EVIDENCED`.
---
## 6. Reproducibility Taxonomy
-VSTD-1 defines a five-tier reproducibility taxonomy:
+VSTD-1 defines a five-state reproduction-fidelity taxonomy. The public
+`ReproducibilityLevel` name is a compatibility identifier; it does not denote a numbered
+VSTD profile or assurance strength:
1. `BITWISE_IDENTICAL`: Byte-for-byte exact match across all generated files, logs, and artifacts.
2. `CONTENT_IDENTICAL`: Canonical JSON representation of stable verification payload matches exactly, ignoring volatile execution fields (timestamps, elapsed wall-clock ms, hostnames).
3. `EVIDENCE_EQUIVALENT`: All checks, proofs, SAT assignments, and invariant bounds evaluate to the same truth values and proof certificates, though internal trace order or solver step counts may differ.
-4. `RESULT_EQUIVALENT`: High-level verification verdict (`VERIFIED`/`FALSIFIED`) and primary metrics agree within declared tolerance bounds.
-5. `SEMANTIC_REPRODUCTION`: The underlying formal proposition is sustained under an independent translation or alternate solver.
+4. `RESULT_EQUIVALENT`: Summary verification verdict (`VERIFIED`/`FALSIFIED`) and primary metrics agree within declared tolerance bounds.
+5. `SEMANTIC_REPRODUCTION`: The underlying formal proposition is sustained under a separately implemented translation or alternate solver. This does not establish distinct actors.
---
## 7. Canonical Receipt Specification & Hashing
A VSTD-1 receipt separates **stable verification content** from **volatile execution metadata**.
-Its historical wire identifier remains frozen:
+The receipt kind is explicit:
```
receipt.json
-├── schema_version: "VSTD-0.1"
+├── schema_version: "VSTD-1"
+├── receipt_kind: "claim_mechanics"
├── receipt_id: "VFY-XXXXXX"
├── canonical_digest: SHA256(canonical_json(stable_payload))
├── claim: {...}
@@ -135,7 +162,7 @@ receipt.json
```
### Canonicalization Algorithm
-1. Extract stable fields (`schema_version`, `receipt_id`, `claim`, `evidence`, `target_result`, `independent_audit`, `provenance_stable`, `reproducibility`).
+1. Extract stable fields (`schema_version`, `receipt_kind`, `receipt_id`, `claim`, `evidence`, `target_result`, `independent_audit`, `provenance_stable`, `reproducibility`).
2. Serialize the VSTD-1 JSON subset with alphabetically sorted object keys, compact
separators `","` and `":"`, UTF-8 encoding, and no non-finite numbers. This
project-specific canonicalization is deterministic for the supported value subset;
@@ -161,5 +188,5 @@ receipt.json
derivation-graph acyclicity and grounding checks, Git/runtime provenance capture,
stable-payload digest validation, generic command receipts, and bounded
reproducibility comparison.
-- **VSTD-2 — Verification Surface**: verification geometry, residual-driven deconstruction, horizons, valences, and bounded self-closure. VSTD-2 does not reinterpret existing receipts whose wire identifier is `VSTD-0.1`.
+- **VSTD-2 — Verification Surface**: verification geometry, residual-driven deconstruction, horizons, valences, and bounded self-closure. Its results remain separate from VSTD-1 claim-mechanics results.
- **Unassigned Future Work**: Additional proof mechanisms, execution-environment binding, and cross-institutional proof-carrying software gates require separate scoped proposals and evidence. No future version number is reserved here.
diff --git a/standard/VSTD-2.md b/standard/VSTD-2.md
index 32b94ea..ebb0fb0 100644
--- a/standard/VSTD-2.md
+++ b/standard/VSTD-2.md
@@ -1,23 +1,27 @@
-# VSTD-2 — Verification Surface
+# Verifier Standard (VSTD)-2 — Verification Surface
-**Layer:** 2 of 5 on the object axis (see `LADDER.md`)
-**Receipt wire format:** `schema_version = "VSTD-0.2"` — frozen; see `WIRE_IDENTIFIERS.md`
-**Status:** Additive experimental standard with an implemented vertical slice
+> **Acronyms:** abstract syntax tree (AST); continuous delivery or deployment (CD); continuous integration (CI);
+> intermediate representation (IR); trusted computing base (TCB).
+
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-2 on the object axis; required closure coordinate: Verification Surface (see `LADDER.md`)
+**Receipt serialization:** `schema_version = "VSTD-2"`; see `WIRE_IDENTIFIERS.md`
+**Status:** experimental project specification with an implemented vertical slice
**Maintainer:** TimeLordRaps
**Date:** 2026-08-20
---
-## 1. Relationship to earlier standards
+## 1. Relationship to adjacent profiles
-VSTD-2 adds a verification-geometry ontology to VSTD-1. It does not replace or
-reinterpret historical receipts whose wire identifiers are `VSTD-0.1` or
-`VSTD-DATA-0.1`. A document conforms to this
-extension only when it declares `schema_version = "VSTD-0.2"`; older validators may
-continue to process their existing receipt kinds unchanged.
+VSTD-2 adds a verification-geometry ontology beside VSTD-1 claim mechanics and
+VSTD-Graph collection dynamics. A document conforms to this extension only when it
+declares `schema_version = "VSTD-2"`; a result on one coordinate does not supply a
+result on either adjacent surface.
-VSTD-1 answers how a bounded claim carries evidence, provenance, an independent
-judgment, and reproducibility information. VSTD-Graph-1 answers how artifacts and
+VSTD-1 answers how a bounded claim carries evidence, provenance, a checker judgment,
+an explicitly evidenced independence basis, and reproducibility information. VSTD-Graph-1 answers how artifacts and
transformations compose into a provenance hypergraph. VSTD-2 answers a different
question: **what geometry was selected for verification, what did reconstruction
expose that the geometry missed, and has the sufficiency of the declared closure
@@ -234,8 +238,8 @@ Higher-order verification is represented as a finite sequence:
- `V1`: verification of V0's geometry, evidence, mechanisms, and selected surface;
- `V2`: verification of V1's sufficiency criteria; and so on only when evidenced.
-Each order greater than zero MUST verify exactly the preceding order. Skipped layers
-violate the adjacent-layer invariant. A finite document never claims that simply
+Each order greater than zero MUST verify exactly the preceding order. Skipped orders
+violate the verification-order adjacency invariant. A finite document never claims that simply
adding one more self-description would close the sequence; inability to justify the
next order is a horizon or open valence.
@@ -251,7 +255,7 @@ next order is a horizon or open valence.
of the subject, evidence, mechanism state, and relevant environment.
- `GEOMETRY_INSPECTABLE`: the declared situation has an inspectable geometry that
represents covered, unsupported, indeterminate, and horizon-bounded coordinates
- honestly. This vocabulary is prose-only: it is not a wire value, and it is not a
+ honestly. This vocabulary is prose-only: it is not a serialized receipt value, and it is not a
member of the `CoordinateStatus` enumeration serialized in a VSTD-2 receipt.
- `COMPLETELY_VERIFIED`: the declared closed surface satisfies self-closure. It never
means universal truth, unbounded safety, or permanent validity.
@@ -275,7 +279,7 @@ verification-instrumented.
The common **verification language** is the typed graph of subjects, loci, facets,
coordinates, seams, surfaces, judgments, mechanisms, residuals, horizons, valences,
-and adjacent verification layers. It is not an intermediate programming language for
+and adjacent verification orders. It is not an intermediate programming language for
every CI/CD system. Native workflows translate observable verification events through
thin adapters into this graph:
@@ -283,7 +287,7 @@ thin adapters into this graph:
native process -> adjacent adapter -> verification geometry -> verifier
```
-The adapter and verifier become loci in the next adjacent verification layer. This
+The adapter and verifier become loci in the next adjacent verification order. This
keeps verification orders adjacent and finite instead of recursing into infinite
workflow abstraction.
@@ -291,6 +295,34 @@ The language is self-describing only in the bounded sense that its schema, adapt
validator, and closure criteria can themselves become subjects. Their description is
not evidence of their correctness.
+### 8.1 Profiles and profiler adapters
+
+A **geometry profile** is a named, reusable constraint on how this geometry is applied;
+it is not a new verdict, numbered VSTD profile, assurance score, or substitute for a
+verification mechanism. A geometry profile
+may identify its subject and grain, expected loci and facets, selected surface and
+exclusions, native observation sources, adapter and mapping identities, applicable
+mechanisms, evidence requirements, bounds, trust roots, horizons, and falsification or
+conformance conditions.
+
+A native profiler or domain tool remains an observation source. Its output enters a
+VSTD-2 surface only through an adjacent adapter that attributes the translated values to
+exact coordinates and exposes omissions, transformations, and information loss. A native
+status word does not transfer into a VSTD judgment without the identified assessment that
+earns that judgment.
+
+Geometry profiles are linked only through explicit shared coordinates, seams, mappings, and
+evidence-bearing transformations. Naming two profiles together, applying them to the same
+subject, or repeating their observations does not compose their verdicts. A composite
+geometry profile must declare and assess the cross-profile seams; unresolved mappings and conflicts
+remain horizons or open valences.
+
+The `VSTD-2` receipt does not currently carry a geometry-profile identifier or a
+geometry-profile-composition object. This section defines the conceptual relationship only.
+A geometry-profile document can bind
+an exact VSTD-2 surface and receipt externally; a new wire representation requires an
+explicit versioned profile boundary.
+
---
## 9. Reprogramming compatibility
@@ -323,7 +355,7 @@ A VSTD-2 geometry document conforms to the implemented vertical slice when:
3. every `VERIFIED` judgment cites evidence and a known mechanism;
4. references and containment are internally consistent;
5. reconstruction residuals are typed and localized;
-6. verification orders obey the adjacent-layer invariant; and
+6. verification orders obey the verification-order adjacency invariant; and
7. closure is reported by `assess_closure` without suppressing its blockers.
The current slice does not infer loci automatically, prove ontology completeness,
diff --git a/standard/VSTD-3.md b/standard/VSTD-3.md
index cd15da2..ecbe454 100644
--- a/standard/VSTD-3.md
+++ b/standard/VSTD-3.md
@@ -1,7 +1,20 @@
-# VSTD-3 — Substrate Accountability
-
-**Layer:** 3 of 5 on the object axis (see `LADDER.md`)
-**Receipt wire format:** `schema_version = "VSTD-3.0"` — frozen; see `WIRE_IDENTIFIERS.md`
+# Verifier Standard (VSTD)-3 — Substrate Accountability
+
+> **Acronyms:** Advanced Micro Devices (AMD); Amazon Web Services (AWS); Compute Unified Device Architecture (CUDA);
+> Device Identifier Composition Engine (DICE); DMTF standards organization (DMTF); DICE Protection Environment (DPE);
+> Entity Attestation Token (EAT); floating-point operation (FLOP); hash-based message authentication code (HMAC);
+> integrated development environment (IDE); Internet Engineering Task Force (IETF);
+> International Organization for Standardization (ISO); JavaScript Object Notation (JSON);
+> NVIDIA Management Library (NVML); Peripheral Component Interconnect (PCI); PCI Special Interest Group (PCI-SIG);
+> Remote Attestation Procedures (RATS); Reference Integrity Manifest (RIM); software development kit (SDK);
+> Secure Hash Algorithm 256-bit (SHA-256); system management interface (SMI); Security Protocol and Data Model (SPDM);
+> Trusted Device Interface Security Protocol (TDISP); tensor processing unit (TPU); Coordinated Universal Time (UTC);
+> Unicode Transformation Format, 8-bit (UTF-8); World Wide Web Consortium (W3C).
+
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-3 on the object axis; required closure coordinate: Substrate Accountability (see `LADDER.md`)
+**Receipt serialization:** `schema_version = "VSTD-3.0"` — frozen; see `WIRE_IDENTIFIERS.md`
**Status:** implemented project specification
**Editor:** TimeLordRaps
**License:** Apache-2.0
@@ -126,7 +139,7 @@ The reference implication graph is explicit. In particular:
- fleet-boundary attestation does not imply physical-world completeness.
`VERIFIED` flags inside a receipt are not self-authenticating. A verifier MUST
-independently reproduce signature and continuity checks before using those flags to
+recompute signature and continuity checks from the bound evidence before using those flags to
accept a strong `PASS`.
## 7. Incremental conformance profiles
@@ -350,7 +363,7 @@ hardware or firmware evidence therefore reaches downstream artifacts through the
existing blast-radius algorithm. VSTD-3 does not create a second lineage graph.
Composition is transactional and refuses receipts whose recorded `PASS` claims cannot
-be independently reproduced.
+be recomputed from the bound evidence.
## 20. Verification algorithm
@@ -361,12 +374,12 @@ A verifier MUST, in order:
3. verify identifiers and all references;
4. verify raw evidence byte digests;
5. validate challenge freshness, nonce uniqueness, subject, and certificate binding;
-6. independently verify implemented attestation and provider signatures;
+6. verify implemented attestation and provider signatures against configured trust material;
7. validate topology and partition lineage;
8. bind starts, observations, accounting, ends, and workload identity to events;
9. verify event continuity, resets, and anchors;
10. verify the exact fleet boundary when present;
-11. recompute every recorded passing claim from independently accepted evidence;
+11. recompute every recorded passing claim from mechanism-verified evidence;
12. reject any stronger recorded `PASS`.
Receipt digest integrity alone completes only steps 1–2.
@@ -388,8 +401,8 @@ trust anchors. Merely labeling bytes `SPDM`, `EAT`, or `DICE` is not verificatio
## 23. Compatibility
VSTD-3 adds record and enum values. It does not reinterpret VSTD-1, VSTD-Graph-1,
-VSTD-2, or their historical wire identifiers. Existing readers remain valid
-for their versioned surfaces. VSTD-3 hardware nodes use additive artifact and
+VSTD-2, or their current serialized receipt identifiers. Each reader remains bounded to its
+versioned surface. VSTD-3 hardware nodes use additive artifact and
transformation enum values in the existing hypergraph.
## 24. Falsification conditions
@@ -408,5 +421,5 @@ VSTD-3 conformance is falsified for a claimed surface if any of these occurs:
- global absence of undeclared compute is derived from an ordinary receipt.
Implementation limitations and the complete threat model are in
-`../docs/layers/vstd-3/threat-model.md`; vendor requirements are in
-`../docs/layers/vstd-3/vendor-integration.md`.
+`../docs/profiles/vstd-3/threat-model.md`; vendor requirements are in
+`../docs/profiles/vstd-3/vendor-integration.md`.
diff --git a/standard/VSTD-4.md b/standard/VSTD-4.md
index dd4def4..6f90584 100644
--- a/standard/VSTD-4.md
+++ b/standard/VSTD-4.md
@@ -1,52 +1,72 @@
-# VSTD-4 — Refutability
+# Verifier Standard (VSTD)-4 — Refutability
-**Layer:** 4 of 5 on the object axis (see `LADDER.md`)
+> **Acronyms:** application programming interface (API); conjunctive normal form (CNF); grounded decision certificate (GDC); JavaScript Object Notation (JSON);
+> resolution asymmetric tautology (RAT); Boolean satisfiability problem (SAT);
+> Unicode Transformation Format, 8-bit (UTF-8).
+
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-4 on the object axis; required closure coordinate: Refutability (see `LADDER.md`)
**Certificate format:** `VSTD4-GDC-1`
-**Status:** implemented project specification
+**Status:** project specification with implemented candidate and evidence-bound reference paths
**Editor:** TimeLordRaps
**License:** Apache-2.0
**Date:** 2026-08-22
-VSTD-4 defines **adversarially portable checkability**. A verdict reaches this
-layer only when its exact meaning, evidence, failure conditions, and checking
-procedure can leave the declarant and survive hostile independent inspection.
+VSTD-4 defines **adversarially portable checkability**. A verdict satisfies this
+profile only when its exact meaning, evidence, failure conditions, and checking
+procedure can leave the declarant and survive hostile inspection outside the declarant.
-VSTD-4 establishes that independent checking is possible. It does not establish
-that an independent party exists or has checked anything; that is VSTD-5.
+VSTD-4 establishes that checking by an outside party is possible. It does not establish
+that such a party exists or has checked anything; that is VSTD-5.
> **No verdict without a portable certificate.**
> **No portable certificate without an explicit falsifier.**
---
-## 1. Conformance and lower-layer preconditions
+## 1. Conformance and prerequisite-profile coordinates
VSTD-4 conformance is incremental. A claim MUST conform to VSTD-1, VSTD-2, and
VSTD-3 before it can conform to VSTD-4. A VSTD-4 certificate over an
unaccountable substrate does not repair the missing VSTD-3 evidence.
-The normative depth is computed:
+The VSTD-4 normative depth is computed:
```
vstd4_depth(claim) = max { k : CNF_4k(claim) is satisfiable }
```
-An implementation MUST NOT accept a declarant-supplied depth as authoritative.
-For a depth below 14, the `FAIL` certificate for rung `k+1` is the normative
-explanation of the ceiling. Entry to any VSTD-5 procedure requires:
+An implementation MUST NOT accept a declarant-supplied VSTD-4 normative depth as authoritative.
+For a normative depth below 14, the `FAIL` certificate for rung `k+1` is the normative
+explanation of the ceiling. Entry to any VSTD-5 procedure requires established
+VSTD-4 conformance and:
```
vstd4_depth(claim) == 14
```
-The reference implementation is `verifier.core.depth`.
+The historical `verifier.core.depth.vstd4_depth` API computes only a structural
+candidate over caller-supplied, nonempty rung references. It does not resolve those
+references, validate their rung propositions, or check VSTD-1/2/3 preconditions. Its
+result is therefore `CANDIDATE` with `conformance_status = NOT_ESTABLISHED`, including
+at candidate depth 14, and the reference VSTD-5 entry gate rejects it.
+
+`verifier.core.depth.establish_vstd4` is the evidence-bound path. It requires
+exact `BoundProposition` records for VSTD-1, VSTD-2, VSTD-3, and all fourteen
+rungs; resolves and rehashes every embedded evidence payload; selects a registered
+mechanism by identifier and implementation digest; enforces evidence byte/item
+bounds; reruns the mechanism; and independently checks the resulting structural
+certificate. Only the complete passing result reports `depth_kind = EVIDENCE_BOUND`,
+`conformance_status = ESTABLISHED`, and admits VSTD-5. The receipt builder embeds
+the bindings and evidence bytes, and the rechecker recomputes the result offline.
---
-## 2. The fourteen-rung ladder
+## 2. The fourteen-rung sequence
-Each rung depends on the evidence named below and on every lower-layer
-precondition. Rung 4.14 depends on the complete ladder.
+Each rung depends on the evidence named below and on every prerequisite-profile
+precondition. Rung 4.14 depends on the complete sequence.
| Rung | Requirement | Direct dependencies |
|---|---|---|
@@ -102,7 +122,7 @@ C = H(claim || coordinate || policy_root || evidence_root || verifier
Canonical serialization MUST use sorted object keys, integer-valued numeric
fields, no floating-point values, UTF-8, and no insignificant whitespace. A
-checker MUST reject a certificate whose binding does not match the independently
+checker MUST reject a certificate whose binding does not match the externally
supplied `ClaimBinding`.
### 2.4 Portable verification
@@ -160,7 +180,7 @@ IDENTIFIED < AVAILABLE < PORTABLE < SELF_CONTAINED
A digest alone establishes only `IDENTIFIED`. VSTD-4 requires at least
`AVAILABLE`, and the claim's bundle is capped by its weakest verdict-critical
-artifact. A declared level that its retrieval and retention evidence cannot
+artifact. A declared availability state that its retrieval and retention evidence cannot
support MUST be rejected.
A locator and retention declaration alone are not retrieval evidence. `AVAILABLE`
@@ -168,7 +188,7 @@ requires a successful retrieval observation bound to the artifact identifier, de
locator, observed bytes, observation time, and observer. The observed bytes MUST match
the content address. `PORTABLE` additionally requires anonymous access and a declared
retrieval procedure. A retrieval observation is scoped to its named trust root; it does
-not by itself establish independent retrieval.
+not by itself establish retrieval by a distinct actor.
### 2.9 Disclosure-safe checkability
@@ -219,7 +239,7 @@ VALID -> CHALLENGED -> REVOKED
A valid challenge mechanism that cannot change claim status is non-conforming.
Synthetic challenges test structural challengeability at VSTD-4. Actual
-independent action belongs to VSTD-5.
+action by a distinct actor belongs to VSTD-5.
### 2.13 Monotonic degradation
@@ -233,7 +253,8 @@ A `RefutabilityClosure` MUST bind input certificates, the transformation
certificate, the output claim, and a total output-refutation mapping. A challenge
to an output must localize to an input, the transformation, or the composition.
-Output depth MUST NOT exceed the weakest required input or transformation depth.
+Output VSTD-4 normative depth MUST NOT exceed the weakest required input or transformation
+VSTD-4 normative depth.
This closure is both the handoff to VSTD-Graph edge evidence and the entry gate to
VSTD-5.
@@ -300,7 +321,7 @@ accepted.
## 4. Normative invariants
> A verdict MUST NOT be recorded at a strength exceeding the strength of the
-> certificate an independent party could check without the declarant's
+> certificate an outside party could check without the declarant's
> cooperation.
> Loss of certificate validity, accessibility, dependency validity, or
@@ -330,17 +351,25 @@ bounded checking.
## 6. Reference implementation boundary
-The reference producer and data structures are in:
+The reference certificate producer, candidate/evidence-bound computations, and data structures are in:
* `src/verifier/core/certificate.py`
* `src/verifier/core/grounding.py`
* `src/verifier/core/depth.py`
+* `src/verifier/core/evidence.py`
* `src/verifier/core/refutation.py`
* `src/verifier/layer4/`
The trusted checker is `src/verifier/core/kernel.py`. Producer modules are not
part of its trusted import boundary.
+The kernel checks the supplied certificate, grounding, and `ClaimBinding` for internal
+consistency. It does not retrieve rung references or establish prerequisite-profile
+results by itself. Kernel acceptance of a candidate certificate is therefore not VSTD-4
+conformance. The evidence-bound path performs those additional checks before it can
+report conformance; its result remains bounded to the registered mechanisms, trust roots,
+evidence, and resource limits.
+
No external implementation, interoperability profile, or third-party attack has
yet been demonstrated for `VSTD4-GDC-1`. This implementation status MUST remain
visible in claims about the format.
diff --git a/standard/VSTD-5.md b/standard/VSTD-5.md
index 889b93c..1d8cbb3 100644
--- a/standard/VSTD-5.md
+++ b/standard/VSTD-5.md
@@ -1,93 +1,148 @@
-# VSTD-5 — Witness Corroboration
+# Verifier Standard (VSTD)-5 — Witness Corroboration
-**Layer:** 5 of 5 on the object axis (see `LADDER.md`)
-**Status:** DRAFT — not implemented
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-5 on the object axis; required closure coordinate: Witness Corroboration (see `LADDER.md`)
+**Status:** project specification with implemented evidence-bound reference mechanism
**Editor:** TimeLordRaps
**License:** Apache-2.0
-**Date:** 2026-08-22
-
-VSTD-5 binds a fully refutable claim to witnesses that do not share the
-declarant's trust root. It is the first layer that cannot be established by a
-declarant acting alone.
+**Date:** 2026-08-29
-This document is a draft interface, not an implementation or a claim that any
-independent witness exists.
+VSTD-5 binds a fully refutable claim to an actually checked witness relation. It is
+the first numbered object profile that a declarant acting alone cannot satisfy.
+Witness identity names a coordinate; it never supplies computational trust.
---
## 1. Entry gate
-Every VSTD-5 procedure MUST reject a claim unless its computed VSTD-4 depth is
-exactly 14:
+Every VSTD-5 procedure MUST reject a claim unless VSTD-1, VSTD-2, and VSTD-3
+preconditions and all VSTD-4 rung propositions were evidence-bound and checked,
+establishing VSTD-4 conformance at depth 14.
-```
-vstd4_depth(claim) == 14
-```
+The VSTD-5 bundle `claim_id` MUST equal the exact `claim_id` admitted by that
+evidence-bound VSTD-4 result. A shared claim-binding or certificate digest does not
+establish that a neighboring identifier is an alias. Any future identifier mapping
+would require its own bounded proposition and mechanism; the reference mechanism does
+not implement such aliases.
-The gate is structural. A witness cannot corroborate a claim whose refutability
-does not compose.
+The compatibility `vstd4_depth` candidate never satisfies this gate. The reference
+`establish_vstd4` path may satisfy it only after rerunning every exact evidence
+binding and checking its depth certificate. `require_vstd5_entry` distinguishes the
+two result types and fails closed.
---
-## 2. Required record families
-
-A future conforming receipt will contain:
-
-* `WitnessIdentity` — the witness and the method used to bind the record to it;
-* `IndependenceAssertion` — shared control, vendor, jurisdiction, funding,
- infrastructure, and trust-root relationships;
-* `CorroborationRecord` — what the witness independently checked, the VSTD-4
- certificate checked, observable results, time, and bounds;
-* `CorroborationClass` — procurement, power/thermal envelope, network egress,
- vendor telemetry, financial attestation, or physical inspection; and
-* `DisagreementRecord` — conflicting observations and their effect on the claim.
-
-Independence fields MUST be evidence-bearing. A declarant's statement that a
-witness is independent is not independence evidence.
+## 2. Required records
+
+The reference receipt contains:
+
+* `WitnessIdentity` — witness coordinate plus content-addressed identity evidence;
+* an ordered `independence_assertions` array — every supplied
+ `IndependenceAssertion`, including duplicates, orphan references, and missing
+ cardinality as an empty or incomplete array, so negative assessment inputs are not
+ collapsed during serialization;
+* `CorroborationRecord` — exact VSTD-4 commitment, certificate, checker descriptor,
+ observations, result, time, class, and executable verification binding;
+* derived disagreements — conflicting checked records retained without voting or
+ averaging; and
+* embedded evidence bytes — enough to rehash and rerun the registered mechanisms
+ offline.
+
+Schema validity establishes only shape. `recheck_vstd5_receipt` imports and hashes
+the embedded bytes, compares every carried VSTD-4 entry coordinate—including the result
+digest and witness digest—with the admitted entry, reruns every registered mechanism, and
+compares the complete derived result. Assessment and receipt construction
+are separate boundaries: `assess_witness_corroboration` may diagnose an arbitrary malformed
+or incomplete bundle as `UNKNOWN` / `NOT_ESTABLISHED`, but that assessment object is not
+thereby a VSTD-5 receipt. `build_vstd5_receipt` MUST fail before returning unless the object
+inhabits the strict receipt schema, contains at least one witness and corroboration, and
+embeds every verdict-material evidence byte. Every receipt it does emit, including a
+representable `UNKNOWN` / `NOT_ESTABLISHED` error receipt, MUST preserve the exact
+error-producing input and recheck identically. `recheck_vstd5_receipt` MUST enforce that
+strict shape and evidence coverage before mechanism replay.
---
## 3. Independence
-At minimum, an independence assertion MUST name whether declarant and witness
-share:
+For every declarant/witness pair, the procedure checks whether they share:
1. ownership or operational control;
-2. a verdict-producing codebase;
+2. verdict-producing code;
3. a verifier trust root;
4. an evidence source or telemetry provider;
5. infrastructure capable of changing the observed result;
6. financial dependence material to the corroboration; and
-7. a jurisdiction or contractual relationship material to compulsion.
-
-`UNKNOWN` in any required independence dimension MUST cap the independence claim.
+7. jurisdictional or contractual dependence material to compulsion.
-> Claim independence MUST NOT exceed the independence of its weakest binding
-> witness.
+Every `SEPARATE` state MUST carry a `BoundProposition` for the exact negative
+relationship, the admitted claim commitment, evidence references, mechanism
+identifier and digest, trust roots, and bounds. `SHARED`, `UNKNOWN`, missing,
+failed, or unevaluable dimensions prevent an `INDEPENDENT` result.
-Independence is not manufacturable from self-report at any cryptographic
-strength.
+Repeated evidence, duplicate identifiers, identity keys, signatures, reputation,
+and field names MUST NOT manufacture independence. The same identity evidence used
+under multiple witness identifiers is rejected.
---
## 4. Corroboration and disagreement
-A corroboration record MUST bind the exact VSTD-4 commitment `C`, certificate
-digest, checker descriptor, observable evidence, result, and observation time.
-Checking a neighbouring claim or a different commitment is not corroboration.
-
-Witnesses are not votes. Conflicting witnesses MUST degrade the claim and create
-an additive `DisagreementRecord`; their conclusions MUST NOT be averaged into an
-apparently clean result.
+A corroboration mechanism MUST bind and check the exact:
+
+* claim commitment;
+* VSTD-4 certificate digest;
+* checker descriptor digest;
+* corroboration class;
+* witness coordinate;
+* observation time;
+* observation evidence bytes; and
+* `CORROBORATED`, `REFUTED`, or `UNKNOWN` result.
+
+A record's certificate digest MUST equal the admitted evidence-bound VSTD-4 witness,
+not merely a caller-selected digest repeated in both fields. Every identified witness
+MUST contribute a corroboration record; dangling identities do not create plurality.
+`corroboration_class` is part of the mechanism-checked expected proposition, not declared
+metadata and not an assurance-bearing label by itself.
+
+A mechanism-earned negative result remains negative. Conflicting checked records
+produce `CONFLICTED`; witnesses are not votes, and majority count never cleans the
+conflict. A positive corroboration with any unresolved independence seam is reported as
+overall `UNKNOWN`, not as independently corroborated. Reusing the same evidence set under
+another corroboration identifier is rejected rather than counted twice.
---
-## 5. Draft boundary
+## 5. Reference algorithm
+
+`verifier.core.witness.assess_witness_corroboration` performs, in order:
+
+1. evidence-bound VSTD-4 entry and exact claim-identifier validation;
+2. identity-evidence availability and duplicate detection;
+3. exact seven-dimension independence evaluation;
+4. exact corroboration binding and mechanism execution;
+5. duplicate-evidence refusal;
+6. disagreement derivation; and
+7. bounded result emission with all errors and limitations retained.
+
+`build_vstd5_receipt` serializes witness identities and independence assertions as
+separate ordered arrays so representable duplicate, orphan, reused-identity, and missing
+assertion failures survive round trip. It refuses empty witness/corroboration collections,
+empty required identifiers, invalid receipt identifiers, malformed nested records, and
+missing verdict-material bytes rather than naming them receipts. `recheck_vstd5_receipt`
+applies the same zero-dependency structural gate, rejects any inconsistent redundant VSTD-4
+entry coordinate, and mechanism-checks the corroboration class before accepting exact replay.
+Neither function turns an identity coordinate into trust or establishes a fact outside the
+propositions checked by its registered mechanisms.
+
+---
-The schema `receipts/schema/vstd5_receipt.json` records the intended shape for
-review. No reference witness transport, identity scheme, independence scoring
-algorithm, or second-party implementation is shipped in release v1.0.0.
+## 6. Current limits
-The document remains `DRAFT` until VSTD-4 operating experience supplies evidence
-for the final protocol. A draft schema MUST NOT be presented as VSTD-5
-conformance.
+The repository ships the meta-verification mechanism and adversarial fixtures. It
+does not ship or claim a real independent third-party witness, external
+interoperability deployment, accreditation, or a universal way to infer real-world
+separation. A deployment must supply mechanisms that actually check its evidence;
+registering a mechanism names the trust boundary but does not make that mechanism
+correct.
diff --git a/standard/VSTD-Graph-1.md b/standard/VSTD-Graph-1.md
index a06b9cb..f7ac14a 100644
--- a/standard/VSTD-Graph-1.md
+++ b/standard/VSTD-Graph-1.md
@@ -1,7 +1,13 @@
-# VSTD-Graph-1 — Recorded Lineage
+# Verifier Standard (VSTD)-Graph-1 — Recorded Lineage
-**Layer:** 1 of 5 on the graph axis (see `LADDER.md`)
-**Receipt wire format:** `schema_version = "VSTD-DATA-0.1"` — frozen; see `WIRE_IDENTIFIERS.md`
+> **Acronyms:** application programming interface (API); conjunctive normal form (CNF); Davis-Putnam-Logemann-Loveland (DPLL); operating system (OS);
+> Boolean satisfiability problem (SAT); Secure Hash Algorithm 256-bit (SHA-256); satisfiability modulo theories (SMT);
+> Software Package Data Exchange (SPDX); uniform resource identifier (URI).
+
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-Graph-1; required closure coordinate: Recorded Lineage (see `LADDER.md`)
+**Receipt serialization:** `schema_version = "VSTD-DATA-0.1"` — frozen; see `WIRE_IDENTIFIERS.md`
**Status:** Project Specification with Implemented Reference Subset
**Maintainer:** TimeLordRaps
**Date:** 2026-08-21
@@ -20,18 +26,19 @@ first-class **N-ary Hyperedges**, which represent many-to-many merges, sharding,
multi-input processing without flattening those relationships into ambiguous binary
links.
-This document is the first rung of the Graph axis. `VSTD-Graph-2.md` through
+This document defines the first numbered profile of the Graph axis. `VSTD-Graph-2.md` through
`VSTD-Graph-5.md` apply progressively stronger object and transformation-edge
-requirements to the same closed collection. `LADDER.md` defines the computed
-level and its ceiling certificate; `verifier.data.graph_level.graph_level`
+requirements to the same closed collection. `LADDER.md` defines the computed candidate
+Graph profile and its ceiling certificate; the compatibility API
+`verifier.data.graph_level.graph_level`
implements that computation.
---
## 2. The Provenance Hypergraph Abstraction
-A Dataset Provenance Hypergraph is a 5-tuple:
-$$\mathcal{H} = (\mathcal{A}, \mathcal{T}, \mathcal{C}, \mathcal{R}, \mathcal{P})$$
+A Dataset Provenance Hypergraph is a 6-tuple:
+$$\mathcal{H} = (\mathcal{A}, \mathcal{T}, \mathcal{C}, \mathcal{R}, \mathcal{P}, \mathcal{X})$$
### 2.1 Artifact Nodes ($\mathcal{A}$)
Represents any discrete, inspectable data object or model state:
@@ -58,6 +65,15 @@ outputs. The edge records ancestry; it does not by itself establish causal influ
- `parameters`: Exact hyperparameter dictionary, filter criteria, or random seeds.
- `execution_environment`: Python runtime, host OS, hardware acceleration class, timestamp.
+The frozen `VSTD-DATA-0.1` serialization defines artifact and transformation identifiers
+inside separate collections; historical readers therefore retain a payload in which one
+string occurs once in each collection. Direct new construction, evidence-bound Graph
+establishment, and `VSTD-GRAPH-ASSURANCE-1` require the two sets to be globally
+disjoint because current evidence maps and the assurance overlay's `subject_id` do not carry
+an artifact/transformation kind. A historical overlap is readable and reproducible as
+recorded lineage but is inadmissible to those stricter current mechanisms. This compatibility
+rule does not let duplicates within either collection replace recorded evidence.
+
### 2.3 Contributor Nodes ($\mathcal{C}$)
- `contributor_id`, `name`, `contributor_type` (`INDIVIDUAL`, `ORGANIZATION`, `MODEL_GENERATOR`, `AUTOMATED_SYSTEM`), `uri`.
@@ -68,6 +84,20 @@ outputs. The edge records ancestry; it does not by itself establish causal influ
- Machine-checkable Boolean admission rules. The current reference subset evaluates
bounded CNF with its minimal DPLL implementation; general SMT is not implemented.
+### 2.6 Conflict Records ($\mathcal{X}$)
+- `conflict_id`, `subject_id`, and `predicate` identify the disputed coordinate.
+- `competing_values` retains at least two incompatible values.
+- `evidence_refs` retains at least two evidence records rather than selecting a winner.
+
+A conflict record does not mutate the frozen artifact-status vocabulary. It makes the
+subject inadmissible to a clean candidate Graph profile. The VSTD-Graph-1 receipt has no
+conflict-resolution transition and remains immutable. The separate non-receipt
+`VSTD-GRAPH-ASSURANCE-1` overlay can record additive, mechanism-checked resolution while
+retaining the competing evidence. A selected status is projected into that overlay's current
+view; resolving any other predicate does not by itself establish a clean admissibility effect.
+No general non-status admissibility-effect mechanism is implemented in the current reference
+runtime, so such a conflict remains blocking.
+
---
## 3. Provenance Completeness Dimensions
@@ -102,6 +132,9 @@ It is a coverage summary, not a probability, trust score, or verification verdic
origin or transformation is not evidenced, the applicable state remains `UNKNOWN` or
the applicable coverage dimension remains incomplete. It never silently becomes
observed real-world truth.
+* **The `CONFLICTED` Principle**: Incompatible retained evidence remains an explicit
+ conflict record. It is neither averaged nor collapsed into `UNKNOWN`, `VALID`, or a
+ scalar confidence value.
* **Fail-Closed Policy Admission**: A policy passes only the Boolean condition it
actually encodes. For example, "no ancestor is marked `REVOKED`" does not establish
that every ancestor is `VALID`; a clean-ancestor policy must explicitly require
@@ -143,7 +176,7 @@ When an upstream source $S$ is marked `REVOKED` (e.g. due to copyright claim, da
that a claimed origin, contributor, execution, or license declaration is authentic.
- **Complete real-world lineage**: Missing instrumentation, hidden inputs, pre-observation
contamination, and out-of-band transformations remain outside the graph unless
- independently evidenced.
+ separately evidenced.
- **Automatic physical-file checking**: A stored VSTD-Graph receipt validates its own
stable content. It flags a physical-file mismatch only when an adapter supplies and
rehashes that file.
diff --git a/standard/VSTD-Graph-2.md b/standard/VSTD-Graph-2.md
index 9a714cc..ec4c50c 100644
--- a/standard/VSTD-Graph-2.md
+++ b/standard/VSTD-Graph-2.md
@@ -1,17 +1,26 @@
-# VSTD-Graph-2 — Bounded Collection Surface
+# Verifier Standard (VSTD)-Graph-2 — Bounded Collection Surface
-**Layer:** 2 of 5 on the graph axis (see `LADDER.md`)
-**Status:** implemented computed profile
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-Graph-2; required closure coordinate: Bounded Collection Surface (see `LADDER.md`)
+**Status:** project specification with implemented candidate and evidence-bound reference paths
**License:** Apache-2.0
-VSTD-Graph-2 closes collection-level scope leakage. A collection reaches this
-layer only when every member and provenance ancestor is at object layer 2 or
+VSTD-Graph-2 closes collection-scope leakage. A collection satisfies this candidate
+profile only when every member and provenance ancestor is rated at object profile 2 or
higher, every reachable status is admissible, and every transformation hyperedge
-carries layer-2 edge evidence.
+carries profile-2 edge ratings.
-The level is computed by `verifier.data.graph_level`; it is never declared.
-The `FAIL` certificate for Graph layer 2 names the member, ancestor, status, or
-edge obligation that prevents admission.
+`verifier.data.graph_level.graph_level` computes a candidate from caller-supplied ratings and marks
+conformance `NOT_ESTABLISHED`. `establish_graph_level` instead reruns exact member,
+ancestor, and edge rating propositions from embedded evidence through registered
+mechanisms; only that path may report `MECHANISM_EVALUATED` and `ESTABLISHED`. The
+rating propositions bind one digest over the exact historical Graph, deduplicated member
+set, collection identifier, and Graph claim binding. A neighboring collection, topology,
+or claim therefore contributes rating zero. Profile zero is never established. The
+`FAIL` certificate for Graph profile 2 names the member,
+ancestor, status, or edge obligation that prevents admission under those inputs. It does
+not validate the ratings themselves.
VSTD-Graph-2 does not establish that the evidence sources behind the collection
are accountable. That is the blind spot closed by VSTD-Graph-3.
diff --git a/standard/VSTD-Graph-3.md b/standard/VSTD-Graph-3.md
index 710a34f..8fc6f5f 100644
--- a/standard/VSTD-Graph-3.md
+++ b/standard/VSTD-Graph-3.md
@@ -1,17 +1,26 @@
-# VSTD-Graph-3 — Accountable Provenance Closure
+# Verifier Standard (VSTD)-Graph-3 — Accountable Provenance Closure
-**Layer:** 3 of 5 on the graph axis (see `LADDER.md`)
-**Status:** implemented computed profile
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-Graph-3; required closure coordinate: Accountable Provenance Closure (see `LADDER.md`)
+**Status:** project specification with implemented candidate and evidence-bound reference paths
**License:** Apache-2.0
VSTD-Graph-3 closes unaccountable substrate across a collection. A collection
-reaches this layer only when every member and reachable ancestor is at object
-layer 3 or higher, every reachable status is admissible, and every transformation
-hyperedge carries layer-3 edge evidence.
+satisfies this candidate profile only when every member and reachable ancestor is rated at
+object profile 3 or higher, every reachable status is admissible, and every transformation
+hyperedge carries profile-3 edge ratings.
The provenance closure condition is normative: rating only the selected members
is insufficient. The weakest reachable ancestor or transformation caps the
collection.
+The compatibility computation consumes caller-supplied ratings and therefore reports a
+candidate with conformance `NOT_ESTABLISHED`. `establish_graph_level` reruns a registered
+mechanism over the exact evidence bytes for every member, ancestor, and transformation
+rating; missing, failed, uncertain, neighboring, or out-of-closure bindings contribute
+zero and prevent conformance. Every proposition also binds the exact Graph bytes,
+deduplicated member set, collection identifier, and claim binding.
+
VSTD-Graph-3 cannot establish that an outside party could refute the composed
collection. That blind spot is closed by VSTD-Graph-4.
diff --git a/standard/VSTD-Graph-4.md b/standard/VSTD-Graph-4.md
index 7fe4e93..c4597bc 100644
--- a/standard/VSTD-Graph-4.md
+++ b/standard/VSTD-Graph-4.md
@@ -1,17 +1,26 @@
-# VSTD-Graph-4 — Refutable Transformation Closure
+# Verifier Standard (VSTD)-Graph-4 — Refutable Transformation Closure
-**Layer:** 4 of 5 on the graph axis (see `LADDER.md`)
-**Status:** implemented computed profile
+> **Acronym:** unsatisfiable (UNSAT).
+
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-Graph-4; required closure coordinate: Refutable Transformation Closure (see `LADDER.md`)
+**Status:** project specification with implemented candidate and evidence-bound reference paths
**License:** Apache-2.0
-VSTD-Graph-4 closes non-compositional refutability. A collection reaches this
-layer only when every member and reachable ancestor is at object layer 4 or
+VSTD-Graph-4 closes non-compositional refutability. A collection satisfies this candidate
+profile only when every member and reachable ancestor is rated at object profile 4 or
higher, statuses are admissible, and every transformation hyperedge carries
-layer-4 evidence including a valid `RefutabilityClosure`.
+profile-4 ratings including a valid `RefutabilityClosure`.
Two VSTD-4 nodes connected by an unevidenced edge do not make a VSTD-Graph-4
collection. A challenge to the collection output must localize to a member,
ancestor, transformation, or the composition itself.
-The UNSAT certificate at the next level is the computed explanation of the
-collection's ceiling.
+The unsatisfiable (UNSAT) certificate at the next profile is the computed explanation of the candidate
+ceiling over caller-supplied ratings. It does not establish Graph-4 conformance or
+validate the claimed `RefutabilityClosure` records.
+The evidence-bound path reruns every exact rating mechanism and embeds the proposition
+bindings and evidence bytes for offline replay. Those bindings commit to the exact Graph,
+deduplicated members, collection identifier, and claim binding. A Graph-4 edge rating mechanism must
+actually check the applicable `RefutabilityClosure`; naming one is insufficient.
diff --git a/standard/VSTD-Graph-5.md b/standard/VSTD-Graph-5.md
index bdcf96b..6123ef8 100644
--- a/standard/VSTD-Graph-5.md
+++ b/standard/VSTD-Graph-5.md
@@ -1,18 +1,24 @@
-# VSTD-Graph-5 — Corroborated Verification Network
+# Verifier Standard (VSTD)-Graph-5 — Corroborated Verification Network
-**Layer:** 5 of 5 on the graph axis (see `LADDER.md`)
-**Status:** DRAFT profile; computation is implemented, witness protocol is not
+> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md).
+
+**Numbered profile:** VSTD-Graph-5; required closure coordinate: Corroborated Verification Network (see `LADDER.md`)
+**Status:** project specification with implemented candidate and evidence-bound reference paths
**License:** Apache-2.0
VSTD-Graph-5 is the collection profile for independently corroborated members,
-ancestors, and transformations. The computed graph-level mechanism requires
+ancestors, and transformations. The candidate-profile computation requires
object and edge ratings of at least 5, provenance closure, and admissible status
throughout.
-Because VSTD-5 is draft, the reference implementation can compute this profile
-only over externally supplied level-5 ratings; it does not manufacture or verify
-their independence. A result based on self-declared ratings is not VSTD-Graph-5
+The compatibility implementation computes this profile over externally supplied
+profile-5 ratings and reports `NOT_ESTABLISHED`. The evidence-bound path can establish
+it only when registered mechanisms rerun exact VSTD-5 member/ancestor ratings and
+profile-5 transformation ratings from embedded evidence. Every rating is bound to the
+exact Graph bytes, deduplicated member set, collection identifier, and claim binding. A result based on
+self-declared ratings is not VSTD-Graph-5
conformance.
-Conflicting witness records degrade the relevant object status and therefore the
-computed collection level. They are never averaged into a passing collection.
+Conflicting witness records are retained as conflict records and make the relevant
+subject inadmissible to a clean candidate Graph profile. They are never averaged into
+a passing collection.
diff --git a/standard/WIRE_IDENTIFIERS.md b/standard/WIRE_IDENTIFIERS.md
index 736365f..b3d7768 100644
--- a/standard/WIRE_IDENTIFIERS.md
+++ b/standard/WIRE_IDENTIFIERS.md
@@ -1,106 +1,102 @@
-# VSTD frozen wire identifiers and historical filenames
+# Verifier Standard (VSTD) serialized receipt identifiers
-**Status:** normative for wire-identifier dispatch; filename history is informative
-**Date:** 2026-08-22
+> **Acronyms:** command-line interface (CLI).
-VSTD has no demonstrated external adoption or independent implementation as of this
-release. This document therefore does not prescribe an adopter migration. It records
-identifiers and filenames that appeared in the project's own public releases so that
-those artifacts are not silently reinterpreted.
+**Status:** normative for current serialized-receipt dispatch
+**Date:** 2026-08-29
-Specification numbers now identify verification depth. Repository releases use
-semantic versions independently.
+A **serialized receipt identifier** is the value written into a receipt to select its exact reader and schema, principally `schema_version` plus any required profile discriminator. Standards literature often calls this a *wire identifier* or part of a *wire format*; here it means the stored JavaScript Object Notation (JSON) contract, not a network protocol.
-## 1. Frozen receipt wire identifiers
+Specification numbers identify numbered profiles and their cumulative closure coordinates.
+Repository releases use semantic
+versions independently. Retired partial-profile object identifiers and specification files are
+not current profiles and are absent from this source tree; published tags and Git history
+preserve those earlier project artifacts without making the current reader accept or
+reinterpret them.
-A filename or current layer label does not change the meaning of an issued receipt.
-Readers MUST dispatch a receipt by its wire identifier:
+## 1. Current serialized receipt dispatch
-| Current layer document | Frozen wire identifier |
+Readers MUST dispatch by the exact `schema_version` and any required profile
+discriminator. Unknown identifiers, missing discriminators, and mismatched shapes fail
+closed:
+
+| Numbered-profile document | Current serialized receipt identifier |
|---|---|
-| `VSTD-1.md` | `schema_version = "VSTD-0.1"` |
-| `VSTD-2.md` | `schema_version = "VSTD-0.2"` |
+| `VSTD-1.md` | `schema_version = "VSTD-1"` |
+| `VSTD-2.md` | `schema_version = "VSTD-2"` |
| `VSTD-3.md` | `schema_version = "VSTD-3.0"` |
+| `VSTD-4.md` | `schema_version = "VSTD-4"` |
+| `VSTD-5.md` | `schema_version = "VSTD-5"` |
| `VSTD-Graph-1.md` | `schema_version = "VSTD-DATA-0.1"` |
-New layer-4 and layer-5 documents use their own schemas without changing historical
-canonical digests.
+The frozen `VSTD-DATA-0.1` reader preserves its original separate artifact and
+transformation identifier namespaces. New Graph construction, evidence-bound Graph
+establishment, and the separate `VSTD-GRAPH-ASSURANCE-1` mechanism require global
+cross-kind disjointness; that stricter admission rule does not retroactively narrow which
+historical `VSTD-DATA-0.1` bytes can be decoded and replayed.
-### 1.1 Non-wire vocabulary
+VSTD-1 has two current receipt profiles:
-`VSTD-2.md` section 7 defines a prose lifecycle vocabulary. Only the
-`CoordinateStatus` members serialized in `receipts/schema/vstd2_receipt.json`
-(`PRE_VERIFIED`, `VERIFIED`, `FALSIFIED`, `INDETERMINATE`, `UNSUPPORTED`, `STALE`)
-are wire values. `POST_VERIFIED`, `GEOMETRY_INSPECTABLE`, and `COMPLETELY_VERIFIED`
-are descriptive terms only and have never appeared in an issued receipt; renaming
-them does not affect any canonical digest. `GEOMETRY_INSPECTABLE` was named
-`VERIFIABLE` in unreleased drafts before `v1.1.2`; a status token MUST NOT reuse the
-maintainer's name.
+| `receipt_kind` | Schema | Meaning |
+|---|---|---|
+| `claim_mechanics` | `vstd1_receipt.json` | bounded claim, evidence, checker, provenance, and reproducibility |
+| `generic_computational_run` | `vstd1_generic_run_receipt.json` | planned execution, captured outputs, assessment context, and reproduction surface |
-## 2. Historical names in project releases
+Both discriminators are required. A reader MUST NOT guess the profile from incidental
+field similarity.
-| Historical public name | Current layer label | Meaning |
-|---|---|---|
-| `VSTD-0.1` | `VSTD-1` | claim mechanics |
-| `VSTD-0.2` | `VSTD-2` | verification surface |
-| `VSTD-3.0` | `VSTD-3` | substrate accountability |
-| — | `VSTD-4` | refutability |
-| — | `VSTD-5` | witness corroboration, draft |
-| `VSTD-DATA-0.1` | `VSTD-Graph-1` | recorded lineage over collections |
-
-`VSTD-Graph-2` through `VSTD-Graph-5` first appeared under their current labels.
-
-The current repository does not duplicate old specification paths. Historical tags
-remain the resolver for the bytes published under those paths:
-
-```text
-standard/VSTD-0.1.md -> standard/VSTD-1.md
-standard/VSTD-0.2.md -> standard/VSTD-2.md
-standard/VSTD-3.0.md -> standard/VSTD-3.md
-standard/VSTD-DATA-0.1.md -> standard/VSTD-Graph-1.md
-VSTD3_THREAT_MODEL.md -> docs/layers/vstd-3/threat-model.md
-VSTD3_VENDOR_INTEGRATION.md -> docs/layers/vstd-3/vendor-integration.md
-VSTD3_REFERENCES.md -> docs/layers/vstd-3/references.md
-VSTD3_MIGRATION.md -> docs/layers/vstd-3/compatibility.md
-COMPETITION_EVALUATION_PROFILE.md -> docs/profiles/competition-evaluation.md
-CLAIMS_AND_LIMITS.md -> docs/CLAIMS_AND_LIMITS.md
-```
-
-## 2.1 Import package and distribution rename
-
-From `v1.1.2` the import package is `verifier` and the distribution is
-`verifier-standard`. Both
-were previously `verifiable` / `verifiable-standard`. The rename removes a name that
-collided with the ordinary-English adjective, with a former VSTD-2 status token, and
-with the maintainer's former project name.
-
-| Historical name | Current name | Kind |
-|---|---|---|
-| `verifiable` | `verifier` | import package |
-| `verifiable-standard` | `verifier-standard` | distribution |
-| `verifiable-standard-.zip` | `verifier-standard-.zip` | release source archive |
+The generic-run `assessment_context` is a VSTD-1 container for mechanism identity,
+declared resource bounds, prior commitment, and the refutation surface. It is not a
+VSTD-4 object and carries no VSTD-4 conformance field. The container and its selected
+fields participate in the canonical digest.
+
+### 1.1 Non-wire vocabulary
-No receipt wire identifier, schema `$id`, or canonical digest changes. Specification
-text that cites a reference module (for example `verifier.core.kernel`) is a pointer
-into the reference implementation, not a wire value.
+`VSTD-2.md` section 7 defines prose lifecycle vocabulary. Only the
+`CoordinateStatus` members serialized in `receipts/schema/vstd2_receipt.json`
+(`PRE_VERIFIED`, `VERIFIED`, `FALSIFIED`, `INDETERMINATE`, `UNSUPPORTED`, `STALE`)
+are serialized receipt values. `POST_VERIFIED`, `GEOMETRY_INSPECTABLE`, and `COMPLETELY_VERIFIED`
+are descriptive terms rather than receipt values.
-Release manifests published up to and including `v1.1.1` bind
-`verifiable-standard-.zip` in their `source.archive_prefix`.
-`scripts/release_artifacts.py verify` derives the archive name from the manifest, so
-those releases stay verifiable without republishing.
+## 2. Stored non-receipt mechanism identifiers
-## 3. CLI compatibility
+Artifact-control mechanism objects are stored JSON contracts, not network traffic, VSTD
+receipts, or new numbered profiles. They dispatch independently by:
-`vstd` is the canonical cross-platform CLI name. The `verifier` alias remains
-available, but Windows resolves the unqualified name to its built-in Driver Verifier
-utility on common `PATH` configurations. `verifiable` also remains an alias because
-project release materials and receipt instructions may bind that executable name. It is
-a command name only: since `v1.1.2` it no longer corresponds to any import package.
-Retaining either alias preserves project compatibility; it is not evidence of
-external use.
+| Object | `schema_version` |
+|---|---|
+| Freeze manifest | `VSTD-ARTIFACT-FREEZE-1` |
+| Self-closing seal envelope | `VSTD-ARTIFACT-SEAL-1` |
+| Seal closure payload | `VSTD-ARTIFACT-SEAL-CLOSURE-1` |
+| Thaw lineage sidecar | `VSTD-ARTIFACT-THAW-1` |
+
+Their normative behavior is [`ARTIFACT_CONTROL.md`](ARTIFACT_CONTROL.md); their strict
+combined schema is published as
+[`artifact-control-1.schema.json`](https://timelordraps.github.io/verifier/schemas/artifact-control-1.schema.json).
+These identifiers do not imply a network protocol or VSTD conformance result.
+
+The Graph assurance event log dispatches separately as
+`schema_version = "VSTD-GRAPH-ASSURANCE-1"`. Its governing behavior is
+[`LADDER.md` section 1.1](LADDER.md#11-artifact-first-causal-provenance-orientation),
+and its strict schema is
+[`vstd-graph-assurance-1.schema.json`](https://timelordraps.github.io/verifier/schemas/vstd-graph-assurance-1.schema.json).
+It is not an artifact-control object or a numbered-profile receipt.
+
+## 3. Import package and distribution
+
+The distribution is `verifier-standard`, the import package is `verifier`, and
+`vstd` is the canonical cross-platform CLI name. `verifier` may resolve to Windows Driver
+Verifier on common Windows `PATH` configurations. The `verifiable` command remains a
+compatibility alias for already-published execution instructions; it is not an import
+package or a standard identifier.
+
+Release verification derives archive names and console-script expectations from the
+release manifest being checked. This preserves issued release evidence without carrying
+obsolete standard identifiers into current receipt dispatch.
## 4. Release versioning
-The first repository release using integer layer names is `v1.0.0`. The release
-number does not claim VSTD-5 implementation: VSTD-5 is explicitly draft. Existing
-`v0.1.0` and `v0.2.0` tags and GitHub releases remain untouched.
+A repository release number does not claim conformance to a same-numbered VSTD profile.
+VSTD-5's reference mechanism is implemented. This project-specification status does not
+claim an external witness, independent implementation, standards-body consensus,
+accreditation, or interoperability deployment.
diff --git a/standard/schemas/artifact-control-1.schema.json b/standard/schemas/artifact-control-1.schema.json
new file mode 100644
index 0000000..7812d5c
--- /dev/null
+++ b/standard/schemas/artifact-control-1.schema.json
@@ -0,0 +1,123 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://timelordraps.github.io/verifier/schemas/artifact-control-1.schema.json",
+ "title": "Verifier Standard (VSTD) artifact-control mechanism formats",
+ "description": "Strict shapes for freeze manifests, finite self-closing seal envelopes, and copy-on-write thaw lineage records. Shape-valid thaw metadata does not verify a parent or historical copy operation. These objects are mechanism formats, not VSTD receipts or numbered-profile conformance claims.",
+ "oneOf": [
+ {"$ref": "#/$defs/freeze"},
+ {"$ref": "#/$defs/seal"},
+ {"$ref": "#/$defs/thaw"}
+ ],
+ "$defs": {
+ "dualIdentifier": {
+ "type": "string",
+ "pattern": "^vstd-(artifact|content|freeze|seal|thaw)-1:sha256:[0-9a-f]{64}:sha3-256:[0-9a-f]{64}$"
+ },
+ "artifactIdentifier": {
+ "type": "string",
+ "pattern": "^vstd-artifact-1:sha256:[0-9a-f]{64}:sha3-256:[0-9a-f]{64}$"
+ },
+ "digests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["sha256", "sha3-256"],
+ "properties": {
+ "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
+ "sha3-256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}
+ }
+ },
+ "fileEntry": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["kind", "path", "byte_size", "digests"],
+ "properties": {
+ "kind": {"const": "file"},
+ "path": {"type": "string", "minLength": 1},
+ "byte_size": {"type": "integer", "minimum": 0},
+ "digests": {"$ref": "#/$defs/digests"}
+ }
+ },
+ "directoryEntry": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["kind", "path"],
+ "properties": {
+ "kind": {"const": "directory"},
+ "path": {"type": "string", "minLength": 1, "not": {"const": "."}}
+ }
+ },
+ "freeze": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["schema_version", "artifact_id", "content_id", "artifact_kind", "media_type", "entries", "lineage", "bound_contexts", "mechanism", "freeze_id"],
+ "properties": {
+ "schema_version": {"const": "VSTD-ARTIFACT-FREEZE-1"},
+ "artifact_id": {"$ref": "#/$defs/artifactIdentifier"},
+ "content_id": {"$ref": "#/$defs/dualIdentifier"},
+ "artifact_kind": {"enum": ["file", "directory"]},
+ "media_type": {"type": "string", "minLength": 1},
+ "entries": {
+ "type": "array",
+ "items": {"oneOf": [{"$ref": "#/$defs/fileEntry"}, {"$ref": "#/$defs/directoryEntry"}]}
+ },
+ "lineage": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/artifactIdentifier"}},
+ "bound_contexts": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/artifactIdentifier"}},
+ "mechanism": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["name", "version", "canonicalization", "write_guard"],
+ "properties": {
+ "name": {"const": "vstd-reference-freezer"},
+ "version": {"const": "1"},
+ "canonicalization": {"const": "VSTD-ARTIFACT-CANONICAL-1"},
+ "write_guard": {"const": "PORTABLE_READ_ONLY_TREE"}
+ }
+ },
+ "freeze_id": {"$ref": "#/$defs/dualIdentifier"}
+ }
+ },
+ "sealPayload": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["schema_version", "artifact_id", "content_id", "freeze_id", "freeze_manifest_digests", "key_id", "algorithm", "closure_rule"],
+ "properties": {
+ "schema_version": {"const": "VSTD-ARTIFACT-SEAL-CLOSURE-1"},
+ "artifact_id": {"$ref": "#/$defs/artifactIdentifier"},
+ "content_id": {"$ref": "#/$defs/dualIdentifier"},
+ "freeze_id": {"$ref": "#/$defs/dualIdentifier"},
+ "freeze_manifest_digests": {"$ref": "#/$defs/digests"},
+ "key_id": {"type": "string", "pattern": "^vstd-seal-key-1:sha256:[0-9a-f]{64}$"},
+ "algorithm": {"const": "Ed25519"},
+ "closure_rule": {"type": "string", "minLength": 1}
+ }
+ },
+ "seal": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["schema_version", "seal_payload", "public_key_base64", "signature_base64", "seal_id"],
+ "properties": {
+ "schema_version": {"const": "VSTD-ARTIFACT-SEAL-1"},
+ "seal_payload": {"$ref": "#/$defs/sealPayload"},
+ "public_key_base64": {"type": "string", "contentEncoding": "base64"},
+ "signature_base64": {"type": "string", "contentEncoding": "base64"},
+ "seal_id": {"$ref": "#/$defs/dualIdentifier"}
+ }
+ },
+ "thaw": {
+ "type": "object",
+ "description": "Unkeyed lineage metadata whose self-derived thaw_id establishes field agreement only. Established clean or dirty status requires separate verification against the actual supplied sealed parent.",
+ "additionalProperties": false,
+ "required": ["schema_version", "parent_artifact_id", "parent_content_id", "parent_freeze_id", "parent_seal_ids", "artifact_kind", "media_type", "thaw_id"],
+ "properties": {
+ "schema_version": {"const": "VSTD-ARTIFACT-THAW-1"},
+ "parent_artifact_id": {"$ref": "#/$defs/artifactIdentifier"},
+ "parent_content_id": {"$ref": "#/$defs/dualIdentifier"},
+ "parent_freeze_id": {"$ref": "#/$defs/dualIdentifier"},
+ "parent_seal_ids": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/dualIdentifier"}},
+ "artifact_kind": {"enum": ["file", "directory"]},
+ "media_type": {"type": "string", "minLength": 1},
+ "thaw_id": {"$ref": "#/$defs/dualIdentifier"}
+ }
+ }
+ }
+}
diff --git a/standard/schemas/vstd-graph-assurance-1.schema.json b/standard/schemas/vstd-graph-assurance-1.schema.json
new file mode 100644
index 0000000..ab17cce
--- /dev/null
+++ b/standard/schemas/vstd-graph-assurance-1.schema.json
@@ -0,0 +1,278 @@
+{
+ "$comment": "Terminology: Secure Hash Algorithm 256-bit (SHA-256); Verifier Standard (VSTD). TRUST, RUST, and ROT are formal semantic names, not acronyms.",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://timelordraps.github.io/verifier/schemas/vstd-graph-assurance-1.schema.json",
+ "title": "VSTD-Graph Assurance Event Log",
+ "description": "Strict shape for the additive, hash-chained reference event log. Schema validity establishes shape only. Mechanism outcomes must be replayed from the embedded evidence with the exact bound mechanism implementation.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["schema_version", "historical_graph_digest", "historical_graph", "events", "conflict_resolutions", "current_view_digest"],
+ "properties": {
+ "schema_version": {"const": "VSTD-GRAPH-ASSURANCE-1"},
+ "historical_graph_digest": {"$ref": "#/$defs/digest"},
+ "historical_graph": {"$ref": "https://timelordraps.github.io/verifier/schemas/vstd_graph_receipt.json#/properties/hypergraph"},
+ "events": {
+ "type": "array",
+ "items": {"$ref": "#/$defs/event"}
+ },
+ "conflict_resolutions": {
+ "type": "array",
+ "items": {"$ref": "#/$defs/resolution"}
+ },
+ "current_view_digest": {"$ref": "#/$defs/digest"}
+ },
+ "$defs": {
+ "digest": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
+ "digestRef": {"type": "string", "pattern": "^sha256:[0-9a-f]{64}$"},
+ "evaluation": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["binding_digest", "outcome", "mechanism_id", "mechanism_digest", "evidence_refs", "trust_roots", "observed_evidence_bytes", "details", "observations"],
+ "properties": {
+ "binding_digest": {"$ref": "#/$defs/digest"},
+ "outcome": {"enum": ["PASS", "FAIL", "UNKNOWN"]},
+ "mechanism_id": {"type": "string", "minLength": 1},
+ "mechanism_digest": {"$ref": "#/$defs/digestRef"},
+ "evidence_refs": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/digestRef"}},
+ "trust_roots": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
+ "observed_evidence_bytes": {"type": "integer", "minimum": 0},
+ "details": {"type": "string"},
+ "observations": {"type": "object"}
+ }
+ },
+ "event": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["sequence", "kind", "subject_id", "source_ids", "proposition", "binding", "recorded_at", "outcome", "mechanism_id", "mechanism_digest", "evidence_refs", "evidence_payloads", "trust_roots", "details", "previous_event_digest", "attributes", "event_digest"],
+ "properties": {
+ "sequence": {"type": "integer", "minimum": 0},
+ "kind": {"enum": ["TRUST", "ROT", "RUST", "STATUS_PROJECTION", "CONFLICT_DECLARATION", "CONFLICT_RESOLUTION", "CAUSAL_LOCALIZATION", "RESPONSIBILITY_COMPONENT", "OBLIGATION_APPLICABILITY", "OBLIGATION_VIOLATION", "DIAGNOSTIC_ATTRIBUTION"]},
+ "subject_id": {"type": "string", "minLength": 1},
+ "source_ids": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
+ "proposition": {"type": "string", "minLength": 1},
+ "binding": {"$ref": "#/$defs/binding"},
+ "recorded_at": {"type": "string", "minLength": 1},
+ "outcome": {"enum": ["PASS", "FAIL", "UNKNOWN"]},
+ "mechanism_id": {"type": "string", "minLength": 1},
+ "mechanism_digest": {"$ref": "#/$defs/digestRef"},
+ "evidence_refs": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/digestRef"}},
+ "evidence_payloads": {
+ "type": "object",
+ "propertyNames": {"pattern": "^sha256:[0-9a-f]{64}$"},
+ "additionalProperties": {"type": "string", "contentEncoding": "base64"}
+ },
+ "trust_roots": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
+ "details": {"type": "string"},
+ "previous_event_digest": {"type": "string", "pattern": "^(?:[0-9a-f]{64})?$"},
+ "attributes": {"type": "object"},
+ "event_digest": {"$ref": "#/$defs/digest"}
+ },
+ "allOf": [
+ {
+ "if": {"properties": {"kind": {"const": "TRUST"}}, "required": ["kind"]},
+ "then": {"properties": {"attributes": {"$ref": "#/$defs/trustAttributes"}}}
+ },
+ {
+ "if": {"properties": {"kind": {"const": "CONFLICT_DECLARATION"}}, "required": ["kind"]},
+ "then": {"properties": {"attributes": {"$ref": "#/$defs/conflictAttributes"}}}
+ },
+ {
+ "if": {"properties": {"kind": {"const": "CONFLICT_RESOLUTION"}}, "required": ["kind"]},
+ "then": {"properties": {"attributes": {"$ref": "#/$defs/resolutionAttributes"}}}
+ },
+ {
+ "if": {"properties": {"kind": {"const": "CAUSAL_LOCALIZATION"}}, "required": ["kind"]},
+ "then": {"properties": {"attributes": {"$ref": "#/$defs/localizationAttributes"}}}
+ },
+ {
+ "if": {"properties": {"kind": {"const": "RESPONSIBILITY_COMPONENT"}}, "required": ["kind"]},
+ "then": {"properties": {"attributes": {"$ref": "#/$defs/responsibilityAttributes"}}}
+ },
+ {
+ "if": {"properties": {"kind": {"const": "OBLIGATION_APPLICABILITY"}}, "required": ["kind"]},
+ "then": {"properties": {"attributes": {"$ref": "#/$defs/applicabilityAttributes"}}}
+ },
+ {
+ "if": {"properties": {"kind": {"const": "OBLIGATION_VIOLATION"}}, "required": ["kind"]},
+ "then": {"properties": {"attributes": {"$ref": "#/$defs/violationAttributes"}}}
+ },
+ {
+ "if": {"properties": {"kind": {"const": "DIAGNOSTIC_ATTRIBUTION"}}, "required": ["kind"]},
+ "then": {"properties": {"attributes": {"$ref": "#/$defs/diagnosticAttributes"}}}
+ }
+ ]
+ },
+ "trustAttributes": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["binding_digest", "historical_graph_digest", "inputs", "output", "prerequisite_trust_event_digests", "transformation_id"],
+ "properties": {
+ "binding_digest": {"$ref": "#/$defs/digest"},
+ "historical_graph_digest": {"$ref": "#/$defs/digest"},
+ "inputs": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
+ "output": {"type": "string", "minLength": 1},
+ "prerequisite_trust_event_digests": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/digest"}},
+ "transformation_id": {"type": "string", "minLength": 1}
+ }
+ },
+ "conflictAttributes": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["binding_digest", "conflict"],
+ "properties": {
+ "binding_digest": {"$ref": "#/$defs/digest"},
+ "conflict": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["conflict_id", "subject_id", "predicate", "competing_values", "evidence_refs"],
+ "properties": {
+ "conflict_id": {"type": "string", "minLength": 1},
+ "subject_id": {"type": "string", "minLength": 1},
+ "predicate": {"type": "string", "minLength": 1},
+ "competing_values": {"type": "array", "minItems": 2, "uniqueItems": true, "items": {"type": "string"}},
+ "evidence_refs": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string"}}
+ }
+ }
+ }
+ },
+ "resolutionAttributes": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["binding_digest", "conflict_id", "selected_value", "resolution_id"],
+ "properties": {
+ "binding_digest": {"$ref": "#/$defs/digest"},
+ "conflict_id": {"type": "string", "minLength": 1},
+ "selected_value": {"type": "string"},
+ "resolution_id": {"type": "string", "minLength": 1}
+ }
+ },
+ "localizationAttributes": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["binding_digest", "rust_event_digest", "deviation_binding_digest"],
+ "properties": {
+ "binding_digest": {"$ref": "#/$defs/digest"},
+ "rust_event_digest": {"$ref": "#/$defs/digest"},
+ "deviation_binding_digest": {"$ref": "#/$defs/digest"}
+ }
+ },
+ "obligationCoordinate": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["obligation_id", "content_digest", "scope", "assumptions", "exclusions"],
+ "properties": {
+ "obligation_id": {"type": "string"},
+ "content_digest": {"type": "string", "pattern": "^(?:sha256:[0-9a-f]{64})?$"},
+ "scope": {"type": "object", "minProperties": 1, "propertyNames": {"minLength": 1}, "additionalProperties": {"type": "string", "minLength": 1}},
+ "assumptions": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
+ "exclusions": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}}
+ },
+ "anyOf": [
+ {"properties": {"obligation_id": {"type": "string", "minLength": 1}}},
+ {"properties": {"content_digest": {"$ref": "#/$defs/digestRef"}}}
+ ]
+ },
+ "responsibilityAttributes": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["binding_digest", "ancestor_id", "descendant_id", "localization_event_digest", "rust_event_digest", "deviation_binding_digest"],
+ "properties": {
+ "binding_digest": {"$ref": "#/$defs/digest"},
+ "ancestor_id": {"type": "string", "minLength": 1},
+ "descendant_id": {"type": "string", "minLength": 1},
+ "localization_event_digest": {"$ref": "#/$defs/digest"},
+ "rust_event_digest": {"$ref": "#/$defs/digest"},
+ "deviation_binding_digest": {"$ref": "#/$defs/digest"},
+ "compound_group_digest": {"$ref": "#/$defs/digest"},
+ "compound_component_index": {"const": 0}
+ },
+ "dependentRequired": {"compound_group_digest": ["compound_component_index"], "compound_component_index": ["compound_group_digest"]}
+ },
+ "applicabilityAttributes": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["binding_digest", "artifact_id", "obligation_coordinate"],
+ "properties": {
+ "binding_digest": {"$ref": "#/$defs/digest"},
+ "artifact_id": {"type": "string", "minLength": 1},
+ "obligation_coordinate": {"$ref": "#/$defs/obligationCoordinate"},
+ "compound_group_digest": {"$ref": "#/$defs/digest"},
+ "compound_component_index": {"const": 1}
+ },
+ "dependentRequired": {"compound_group_digest": ["compound_component_index"], "compound_component_index": ["compound_group_digest"]}
+ },
+ "violationAttributes": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["binding_digest", "artifact_id", "descendant_id", "localization_event_digest", "rust_event_digest", "deviation_binding_digest", "obligation_coordinate", "applicability_binding_digest", "applicability_component_digest"],
+ "properties": {
+ "binding_digest": {"$ref": "#/$defs/digest"},
+ "artifact_id": {"type": "string", "minLength": 1},
+ "descendant_id": {"type": "string", "minLength": 1},
+ "localization_event_digest": {"$ref": "#/$defs/digest"},
+ "rust_event_digest": {"$ref": "#/$defs/digest"},
+ "deviation_binding_digest": {"$ref": "#/$defs/digest"},
+ "obligation_coordinate": {"$ref": "#/$defs/obligationCoordinate"},
+ "applicability_binding_digest": {"$ref": "#/$defs/digest"},
+ "applicability_component_digest": {"$ref": "#/$defs/digest"},
+ "compound_group_digest": {"$ref": "#/$defs/digest"},
+ "compound_component_index": {"const": 2}
+ },
+ "dependentRequired": {"compound_group_digest": ["compound_component_index"], "compound_component_index": ["compound_group_digest"]}
+ },
+ "diagnosticAttributes": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["binding_digest", "diagnostic_kind", "localization_event_digest"],
+ "properties": {
+ "binding_digest": {"$ref": "#/$defs/digest"},
+ "diagnostic_kind": {"enum": ["BLAME", "GUILT"]},
+ "localization_event_digest": {"$ref": "#/$defs/digest"},
+ "obligation_coordinate": {"$ref": "#/$defs/obligationCoordinate"},
+ "responsibility_component_digest": {"$ref": "#/$defs/digest"},
+ "applicability_component_digest": {"$ref": "#/$defs/digest"},
+ "violation_component_digest": {"$ref": "#/$defs/digest"}
+ },
+ "allOf": [{
+ "if": {"properties": {"diagnostic_kind": {"const": "GUILT"}}, "required": ["diagnostic_kind"]},
+ "then": {"required": ["obligation_coordinate", "responsibility_component_digest", "applicability_component_digest", "violation_component_digest"]}
+ }]
+ },
+ "binding": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["subject_id", "predicate", "expected", "mechanism_id", "mechanism_digest", "evidence_refs", "trust_roots", "bounds", "parameters"],
+ "properties": {
+ "subject_id": {"type": "string", "minLength": 1},
+ "predicate": {"type": "string", "minLength": 1},
+ "expected": {},
+ "mechanism_id": {"type": "string", "minLength": 1},
+ "mechanism_digest": {"$ref": "#/$defs/digestRef"},
+ "evidence_refs": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/digestRef"}},
+ "trust_roots": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
+ "bounds": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["max_evidence_items", "max_evidence_bytes"],
+ "properties": {
+ "max_evidence_items": {"type": "integer", "minimum": 0},
+ "max_evidence_bytes": {"type": "integer", "minimum": 0}
+ }
+ },
+ "parameters": {"type": "object", "additionalProperties": {"type": "string"}}
+ }
+ },
+ "resolution": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["resolution_id", "conflict_id", "selected_value", "recorded_at", "evaluation"],
+ "properties": {
+ "resolution_id": {"type": "string", "minLength": 1},
+ "conflict_id": {"type": "string", "minLength": 1},
+ "selected_value": {"type": "string"},
+ "recorded_at": {"type": "string", "minLength": 1},
+ "evaluation": {"$ref": "#/$defs/evaluation"}
+ }
+ }
+ }
+}
diff --git a/tests/test_artifact_control.py b/tests/test_artifact_control.py
new file mode 100644
index 0000000..0ee4d5c
--- /dev/null
+++ b/tests/test_artifact_control.py
@@ -0,0 +1,1886 @@
+"""Adversarial tests for exact-byte freezing and finite self-closing seals.
+
+Terminology: JavaScript Object Notation (JSON); Privacy-Enhanced Mail (PEM);
+Verifier Standard (VSTD).
+"""
+
+from __future__ import annotations
+
+import base64
+import hashlib
+import json
+import os
+import stat
+from pathlib import Path
+from types import SimpleNamespace
+
+import pytest
+
+import verifier.artifact_control as artifact_control_module
+from verifier.artifact_control import (
+ ArtifactControlError,
+ freeze_artifact,
+ seal_artifact,
+ thaw_artifact,
+ thawed_artifact_status,
+ verify_frozen_artifact,
+)
+from verifier.runtime.public_cli import main
+
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+def _private_key(path: Path) -> Path:
+ cryptography = pytest.importorskip("cryptography.hazmat.primitives.serialization")
+ ed25519 = pytest.importorskip(
+ "cryptography.hazmat.primitives.asymmetric.ed25519"
+ )
+ key = ed25519.Ed25519PrivateKey.generate()
+ path.write_bytes(
+ key.private_bytes(
+ encoding=cryptography.Encoding.PEM,
+ format=cryptography.PrivateFormat.PKCS8,
+ encryption_algorithm=cryptography.NoEncryption(),
+ )
+ )
+ return path
+
+
+def _writable(path: Path) -> None:
+ path.chmod(path.stat().st_mode | stat.S_IWUSR)
+
+
+def _sealed_file(tmp_path: Path, name: str = "case") -> tuple[Path, dict[str, object]]:
+ source = tmp_path / f"{name}.bin"
+ source.write_bytes(b"\x00exact\r\nbytes\xff")
+ bundle = tmp_path / f"{name}.vstd-artifact"
+ freeze_artifact(source, bundle, media_type="application/x-test")
+ seal = seal_artifact(bundle, _private_key(tmp_path / f"{name}.pem"))
+ return bundle, seal
+
+
+def _seal_path(bundle: Path) -> Path:
+ return next((bundle / "seals").glob("*.json"))
+
+
+def _dual_id(kind: str, payload: bytes) -> str:
+ return (
+ f"vstd-{kind}-1:sha256:{hashlib.sha256(payload).hexdigest()}:"
+ f"sha3-256:{hashlib.sha3_256(payload).hexdigest()}"
+ )
+
+
+def _fake_dual_id(kind: str, digit: str = "0") -> str:
+ return f"vstd-{kind}-1:sha256:{digit * 64}:sha3-256:{digit * 64}"
+
+
+def _reclose_thaw_record(path: Path, **changes: object) -> dict[str, object]:
+ record = json.loads(path.read_text(encoding="utf-8"))
+ record.update(changes)
+ stable = {key: record[key] for key in record if key != "thaw_id"}
+ canonical = json.dumps(
+ stable,
+ ensure_ascii=False,
+ allow_nan=False,
+ sort_keys=True,
+ separators=(",", ":"),
+ ).encode("utf-8")
+ record["thaw_id"] = _dual_id("thaw", canonical)
+ path.write_text(
+ json.dumps(record, indent=2, sort_keys=True) + "\n",
+ encoding="utf-8",
+ newline="\n",
+ )
+ return record
+
+
+def _thawed_file(tmp_path: Path, name: str = "thaw") -> tuple[Path, Path, Path]:
+ bundle, _ = _sealed_file(tmp_path, name)
+ descendant = tmp_path / f"{name}-descendant.bin"
+ record = thaw_artifact(bundle, descendant)
+ return bundle, descendant, Path(str(record["record_path"]))
+
+
+def _symlink_or_skip(link: Path, target: Path, *, target_is_directory: bool = False) -> None:
+ try:
+ link.symlink_to(target, target_is_directory=target_is_directory)
+ except OSError as exc:
+ pytest.skip(f"symlink creation is unavailable: {exc}")
+
+
+def _fifo_or_skip(path: Path) -> None:
+ if not hasattr(os, "mkfifo"):
+ pytest.skip("first-in, first-out special objects are unavailable")
+ try:
+ os.mkfifo(path)
+ except OSError as exc:
+ pytest.skip(f"first-in, first-out special-object creation is unavailable: {exc}")
+
+
+def test_freeze_preserves_exact_file_bytes_without_claiming_a_seal(tmp_path: Path) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"\x00\r\n\xff")
+ bundle = tmp_path / "bundle"
+
+ manifest = freeze_artifact(source, bundle, media_type="application/x-bytes")
+
+ assert (bundle / "payload").read_bytes() == source.read_bytes()
+ assert manifest["artifact_id"].startswith("vstd-artifact-1:sha256:")
+ assert manifest["content_id"].startswith("vstd-content-1:sha256:")
+ assert verify_frozen_artifact(bundle).state == "NOT_ESTABLISHED"
+ result = verify_frozen_artifact(bundle, require_seal=False)
+ assert result.state == "FROZEN_UNSEALED"
+ assert result.freeze_valid and result.guard_valid and not result.sealed
+
+
+def test_freeze_preserves_directory_paths_files_and_empty_directories(tmp_path: Path) -> None:
+ source = tmp_path / "tree"
+ (source / "empty").mkdir(parents=True)
+ (source / "nested").mkdir()
+ (source / "nested" / "value.txt").write_bytes(b"value\n")
+
+ bundle = tmp_path / "tree.vstd-artifact"
+ manifest = freeze_artifact(source, bundle)
+
+ assert (bundle / "payload" / "empty").is_dir()
+ assert (bundle / "payload" / "nested" / "value.txt").read_bytes() == b"value\n"
+ assert [entry["path"] for entry in manifest["entries"]] == [
+ "empty",
+ "nested",
+ "nested/value.txt",
+ ]
+ assert verify_frozen_artifact(bundle, require_seal=False).freeze_valid
+
+
+@pytest.mark.parametrize("target_kind", ("file", "directory", "dangling"))
+def test_freeze_refuses_top_level_source_symlink_without_creating_bundle(
+ tmp_path: Path, target_kind: str
+) -> None:
+ target = tmp_path / "target"
+ if target_kind == "file":
+ target.write_bytes(b"target")
+ elif target_kind == "directory":
+ target.mkdir()
+ (target / "value").write_bytes(b"target")
+ link = tmp_path / "source-link"
+ _symlink_or_skip(link, target, target_is_directory=target_kind == "directory")
+ bundle = tmp_path / "bundle"
+
+ with pytest.raises(ArtifactControlError, match="symbolic links"):
+ freeze_artifact(link, bundle)
+
+ assert link.is_symlink()
+ assert not os.path.lexists(bundle)
+
+
+def test_freeze_refuses_nested_symlink_without_leaving_partial_bundle(tmp_path: Path) -> None:
+ source = tmp_path / "source"
+ source.mkdir()
+ (source / "ordinary").write_bytes(b"ordinary")
+ _symlink_or_skip(source / "nested-link", tmp_path / "absent")
+ bundle = tmp_path / "bundle"
+
+ with pytest.raises(ArtifactControlError, match="symbolic links"):
+ freeze_artifact(source, bundle)
+
+ assert not os.path.lexists(bundle)
+
+
+@pytest.mark.parametrize("target_kind", ("file", "directory", "dangling"))
+def test_freeze_refuses_symlink_bundle_destination_without_mutating_target(
+ tmp_path: Path, target_kind: str
+) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"source")
+ target = tmp_path / "target"
+ if target_kind == "file":
+ target.write_bytes(b"unchanged")
+ elif target_kind == "directory":
+ target.mkdir()
+ (target / "unchanged").write_bytes(b"unchanged")
+ bundle = tmp_path / "bundle-link"
+ _symlink_or_skip(bundle, target, target_is_directory=target_kind == "directory")
+
+ with pytest.raises(ArtifactControlError, match="already exists"):
+ freeze_artifact(source, bundle)
+
+ assert bundle.is_symlink()
+ if target_kind == "file":
+ assert target.read_bytes() == b"unchanged"
+ elif target_kind == "directory":
+ assert (target / "unchanged").read_bytes() == b"unchanged"
+ else:
+ assert not target.exists()
+
+
+def test_linked_external_freeze_manifest_is_structural_failure(tmp_path: Path) -> None:
+ bundle, _ = _sealed_file(tmp_path, "linked-external-freeze")
+ freeze_path = bundle / "freeze.json"
+ external = tmp_path / "external-freeze.json"
+ _writable(freeze_path)
+ freeze_path.replace(external)
+ external.chmod(external.stat().st_mode & ~stat.S_IWUSR)
+ _symlink_or_skip(freeze_path, external)
+
+ result = verify_frozen_artifact(bundle)
+
+ assert result.state == "FAIL"
+ assert freeze_path.is_symlink()
+ assert not bool(external.stat().st_mode & stat.S_IWUSR)
+ assert any("freeze manifest must not" in error for error in result.errors)
+
+
+def test_linked_in_bundle_freeze_manifest_fails_even_with_identical_bytes(
+ tmp_path: Path,
+) -> None:
+ bundle, _ = _sealed_file(tmp_path, "linked-in-bundle-freeze")
+ freeze_path = bundle / "freeze.json"
+ target = bundle / "seals" / "freeze-manifest-copy"
+ _writable(freeze_path)
+ freeze_path.replace(target)
+ target.chmod(target.stat().st_mode & ~stat.S_IWUSR)
+ _symlink_or_skip(freeze_path, target)
+
+ result = verify_frozen_artifact(bundle)
+
+ assert result.state == "FAIL"
+ assert any("freeze manifest must not" in error for error in result.errors)
+
+
+def test_dangling_freeze_manifest_link_is_structural_failure(tmp_path: Path) -> None:
+ bundle, _ = _sealed_file(tmp_path, "dangling-freeze")
+ freeze_path = bundle / "freeze.json"
+ _writable(freeze_path)
+ freeze_path.unlink()
+ _symlink_or_skip(freeze_path, tmp_path / "absent-freeze.json")
+
+ result = verify_frozen_artifact(bundle)
+
+ assert result.state == "FAIL"
+ assert any("freeze manifest must not" in error for error in result.errors)
+
+
+@pytest.mark.parametrize("replacement", ("directory", "fifo"))
+def test_nonregular_freeze_manifest_is_structural_failure(
+ tmp_path: Path, replacement: str
+) -> None:
+ bundle, _ = _sealed_file(tmp_path, f"nonregular-freeze-{replacement}")
+ freeze_path = bundle / "freeze.json"
+ _writable(freeze_path)
+ freeze_path.unlink()
+ if replacement == "directory":
+ freeze_path.mkdir()
+ else:
+ _fifo_or_skip(freeze_path)
+
+ result = verify_frozen_artifact(bundle)
+
+ assert result.state == "FAIL"
+ assert any("freeze manifest must be an ordinary file" in error for error in result.errors)
+
+
+def test_missing_and_nonobject_freeze_manifests_fail_closed(tmp_path: Path) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"source")
+ missing_bundle = tmp_path / "missing-bundle"
+ freeze_artifact(source, missing_bundle)
+ freeze_path = missing_bundle / "freeze.json"
+ _writable(freeze_path)
+ freeze_path.unlink()
+ assert verify_frozen_artifact(missing_bundle, require_seal=False).state == "FAIL"
+
+ nonobject_bundle = tmp_path / "nonobject-bundle"
+ freeze_artifact(source, nonobject_bundle)
+ freeze_path = nonobject_bundle / "freeze.json"
+ _writable(freeze_path)
+ freeze_path.write_text("[]\n", encoding="utf-8")
+ result = verify_frozen_artifact(nonobject_bundle, require_seal=False)
+ assert result.state == "FAIL"
+ assert any("one JSON object" in error for error in result.errors)
+
+
+def test_internal_snapshot_fails_if_open_or_identity_changes_after_classification(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"source")
+ bundle = tmp_path / "bundle"
+ freeze_artifact(source, bundle)
+ freeze_path = bundle / "freeze.json"
+ original_open = artifact_control_module.os.open
+
+ def refuse_open(path: object, flags: int) -> int:
+ if Path(path) == freeze_path:
+ raise OSError("simulated no-follow refusal")
+ return original_open(path, flags)
+
+ monkeypatch.setattr(artifact_control_module.os, "open", refuse_open)
+ assert verify_frozen_artifact(bundle, require_seal=False).state == "FAIL"
+ monkeypatch.setattr(artifact_control_module.os, "open", original_open)
+
+ original_fstat = artifact_control_module.os.fstat
+
+ def changed_fstat(descriptor: int) -> SimpleNamespace:
+ observed = original_fstat(descriptor)
+ return SimpleNamespace(
+ st_mode=observed.st_mode,
+ st_dev=observed.st_dev,
+ st_ino=observed.st_ino + 1,
+ )
+
+ monkeypatch.setattr(artifact_control_module.os, "fstat", changed_fstat)
+ result = verify_frozen_artifact(bundle, require_seal=False)
+ assert result.state == "FAIL"
+ assert any("changed during lexical classification" in error for error in result.errors)
+
+
+def test_missing_generic_json_alias_reports_read_failure(tmp_path: Path) -> None:
+ with pytest.raises(ArtifactControlError, match="cannot read"):
+ artifact_control_module._read_json_object(tmp_path / "absent.json", "record")
+
+
+def test_missing_and_special_sources_fail_without_creating_bundle(tmp_path: Path) -> None:
+ missing_bundle = tmp_path / "missing-bundle"
+ with pytest.raises(ArtifactControlError, match="cannot inspect artifact source"):
+ freeze_artifact(tmp_path / "absent", missing_bundle)
+ assert not os.path.lexists(missing_bundle)
+
+ special = tmp_path / "special"
+ _fifo_or_skip(special)
+ special_bundle = tmp_path / "special-bundle"
+ with pytest.raises(ArtifactControlError, match="regular file or directory"):
+ freeze_artifact(special, special_bundle)
+ assert not os.path.lexists(special_bundle)
+
+
+def test_nested_special_or_uninspectable_source_entry_fails_closed(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ source = tmp_path / "source"
+ source.mkdir()
+ special = source / "special"
+ _fifo_or_skip(special)
+ with pytest.raises(ArtifactControlError, match="special filesystem object"):
+ freeze_artifact(source, tmp_path / "special-bundle")
+
+ special.unlink()
+ ordinary = source / "ordinary"
+ ordinary.write_bytes(b"ordinary")
+ original_lstat = Path.lstat
+
+ def refuse_entry(path: Path):
+ if path == ordinary:
+ raise OSError("simulated entry race")
+ return original_lstat(path)
+
+ monkeypatch.setattr(Path, "lstat", refuse_entry)
+ with pytest.raises(ArtifactControlError, match="cannot inspect frozen artifact entry"):
+ freeze_artifact(source, tmp_path / "uninspectable-bundle")
+
+
+def test_payload_mutation_and_guard_removal_fail_closed(tmp_path: Path) -> None:
+ bundle, _ = _sealed_file(tmp_path)
+ payload = bundle / "payload"
+ _writable(payload)
+ payload.write_bytes(b"forged")
+
+ result = verify_frozen_artifact(bundle)
+
+ assert result.state == "FAIL"
+ assert not result.freeze_valid
+ assert not result.guard_valid
+ assert any("payload" in error or "inventory" in error for error in result.errors)
+
+
+def test_linked_payload_member_remains_structural_failure(tmp_path: Path) -> None:
+ bundle, _ = _sealed_file(tmp_path, "linked-payload")
+ payload = bundle / "payload"
+ external = tmp_path / "external-payload"
+ _writable(payload)
+ payload.replace(external)
+ external.chmod(external.stat().st_mode & ~stat.S_IWUSR)
+ _symlink_or_skip(payload, external)
+
+ result = verify_frozen_artifact(bundle)
+
+ assert result.state == "FAIL"
+ assert any("payload must not" in error for error in result.errors)
+
+
+def test_directory_addition_requires_breaking_the_tree_guard_and_fails(tmp_path: Path) -> None:
+ source = tmp_path / "source"
+ source.mkdir()
+ (source / "original").write_bytes(b"original")
+ bundle = tmp_path / "bundle"
+ freeze_artifact(source, bundle)
+ payload = bundle / "payload"
+ _writable(payload)
+ (payload / "added").write_bytes(b"added")
+
+ result = verify_frozen_artifact(bundle, require_seal=False)
+
+ assert result.state == "FAIL"
+ assert not result.freeze_valid
+ assert not result.guard_valid
+
+
+def test_seal_is_finite_self_closing_and_artifact_carried(tmp_path: Path) -> None:
+ bundle, seal = _sealed_file(tmp_path)
+
+ result = verify_frozen_artifact(
+ bundle,
+ expected_artifact_id=seal["seal_payload"]["artifact_id"],
+ expected_key_id=seal["seal_payload"]["key_id"],
+ )
+
+ assert result.state == "SEALED"
+ assert result.sealed and result.freeze_valid and result.guard_valid
+ assert result.external_anchor == "ARTIFACT_AND_KEY_MATCHED"
+ assert result.valid_seal_ids == (seal["seal_id"],)
+ assert seal["signature_base64"] is not None
+ assert seal["seal_id"] is not None
+
+
+@pytest.mark.parametrize(
+ "field",
+ (
+ "artifact_id",
+ "closure_rule",
+ "public_key_base64",
+ "signature_base64",
+ "seal_id",
+ ),
+)
+def test_each_closed_seal_surface_rejects_substitution(tmp_path: Path, field: str) -> None:
+ bundle, _ = _sealed_file(tmp_path, field)
+ path = _seal_path(bundle)
+ envelope = json.loads(path.read_text(encoding="utf-8"))
+ if field in {"artifact_id", "closure_rule"}:
+ envelope["seal_payload"][field] += "-tampered"
+ elif field == "public_key_base64":
+ envelope[field] = base64.b64encode(b"x" * 32).decode("ascii")
+ elif field == "signature_base64":
+ envelope[field] = base64.b64encode(b"x" * 64).decode("ascii")
+ else:
+ envelope[field] += "-tampered"
+ _writable(path)
+ path.write_text(json.dumps(envelope), encoding="utf-8")
+ path.chmod(path.stat().st_mode & ~stat.S_IWUSR)
+
+ result = verify_frozen_artifact(bundle)
+
+ assert result.state == "FAIL"
+ assert not result.valid_seal_ids
+
+
+def test_bundle_substitution_requires_an_external_coordinate(tmp_path: Path) -> None:
+ first, first_seal = _sealed_file(tmp_path, "first")
+ second, _ = _sealed_file(tmp_path, "second")
+
+ self_result = verify_frozen_artifact(second)
+ anchored_result = verify_frozen_artifact(
+ second,
+ expected_artifact_id=first_seal["seal_payload"]["artifact_id"],
+ expected_key_id=first_seal["seal_payload"]["key_id"],
+ )
+
+ assert self_result.state == "SEALED"
+ assert anchored_result.state == "FAIL"
+ assert anchored_result.external_anchor == "MISMATCH"
+ assert first != second
+
+
+def test_one_external_anchor_match_cannot_hide_the_other_mismatch(tmp_path: Path) -> None:
+ first, _ = _sealed_file(tmp_path, "first")
+ second, second_seal = _sealed_file(tmp_path, "second")
+ expected = str(second_seal["seal_payload"]["artifact_id"])
+ mismatched_artifact_id = expected[:-1] + ("0" if expected[-1] != "0" else "1")
+
+ result = verify_frozen_artifact(
+ second,
+ expected_artifact_id=mismatched_artifact_id,
+ expected_key_id=second_seal["seal_payload"]["key_id"],
+ )
+
+ assert result.state == "FAIL"
+ assert result.external_anchor == "MISMATCH"
+ assert first != second
+
+
+def test_duplicate_seal_does_not_multiply_assurance(tmp_path: Path) -> None:
+ bundle, seal = _sealed_file(tmp_path)
+ original = _seal_path(bundle)
+ duplicate = original.with_name("duplicate.json")
+ duplicate.write_bytes(original.read_bytes())
+ duplicate.chmod(duplicate.stat().st_mode & ~stat.S_IWUSR)
+
+ result = verify_frozen_artifact(bundle)
+
+ assert result.state == "SEALED"
+ assert result.valid_seal_ids == (seal["seal_id"],)
+ assert len(result.key_ids) == 1
+
+
+def test_valid_and_invalid_seals_remain_conflicted(tmp_path: Path) -> None:
+ bundle, _ = _sealed_file(tmp_path)
+ invalid = bundle / "seals" / "invalid.json"
+ invalid.write_text("{}\n", encoding="utf-8")
+ invalid.chmod(invalid.stat().st_mode & ~stat.S_IWUSR)
+
+ result = verify_frozen_artifact(bundle)
+
+ assert result.state == "CONFLICTED"
+ assert result.valid_seal_ids
+ assert result.errors
+ with pytest.raises(ArtifactControlError, match="cleanly frozen"):
+ seal_artifact(bundle, _private_key(tmp_path / "other.pem"))
+
+
+@pytest.mark.parametrize("target_location", ("external", "internal"))
+def test_linked_seals_container_is_structural_failure(
+ tmp_path: Path, target_location: str
+) -> None:
+ if target_location == "external":
+ bundle, _ = _sealed_file(tmp_path, "external-seals-container")
+ target = tmp_path / "external-seals"
+ else:
+ source = tmp_path / "directory-source"
+ (source / "seal-store").mkdir(parents=True)
+ (source / "value").write_bytes(b"value")
+ bundle = tmp_path / "internal-seals-container"
+ freeze_artifact(source, bundle)
+ seal_artifact(bundle, _private_key(tmp_path / "internal-seals.pem"))
+ target = bundle / "payload" / "seal-store"
+ _writable(bundle / "payload")
+ _writable(target)
+ target.rmdir()
+ seals = bundle / "seals"
+ if target_location == "external":
+ seals.replace(target)
+ else:
+ target.mkdir()
+ for seal_path in seals.iterdir():
+ seal_path.replace(target / seal_path.name)
+ seals.rmdir()
+ _symlink_or_skip(seals, target, target_is_directory=True)
+
+ result = verify_frozen_artifact(bundle)
+
+ assert result.state == "FAIL"
+ assert any("seals container must not" in error for error in result.errors)
+
+
+def test_dangling_seals_container_link_is_structural_failure(tmp_path: Path) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"source")
+ bundle = tmp_path / "dangling-seals-container"
+ freeze_artifact(source, bundle)
+ _symlink_or_skip(bundle / "seals", tmp_path / "absent-seals", target_is_directory=True)
+
+ result = verify_frozen_artifact(bundle, require_seal=False)
+
+ assert result.state == "FAIL"
+ assert any("seals container must not" in error for error in result.errors)
+
+
+@pytest.mark.parametrize("replacement", ("file", "fifo"))
+def test_non_directory_seals_container_is_structural_failure(
+ tmp_path: Path, replacement: str
+) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"source")
+ bundle = tmp_path / f"non-directory-seals-{replacement}"
+ freeze_artifact(source, bundle)
+ seals = bundle / "seals"
+ if replacement == "file":
+ seals.write_bytes(b"not a directory")
+ else:
+ _fifo_or_skip(seals)
+
+ result = verify_frozen_artifact(bundle, require_seal=False)
+
+ assert result.state == "FAIL"
+ assert any("seals container must be an ordinary directory" in error for error in result.errors)
+
+
+def test_linked_external_seal_member_is_structural_failure(tmp_path: Path) -> None:
+ bundle, _ = _sealed_file(tmp_path, "external-seal-member")
+ seal_path = _seal_path(bundle)
+ external = tmp_path / "external-seal.json"
+ _writable(seal_path)
+ seal_path.replace(external)
+ external.chmod(external.stat().st_mode & ~stat.S_IWUSR)
+ _symlink_or_skip(seal_path, external)
+
+ result = verify_frozen_artifact(bundle)
+
+ assert result.state == "FAIL"
+ assert seal_path.is_symlink()
+ assert not bool(external.stat().st_mode & stat.S_IWUSR)
+ assert any("seal member" in error and "must not" in error for error in result.errors)
+
+
+def test_linked_internal_identical_seal_member_is_structural_failure(tmp_path: Path) -> None:
+ bundle, _ = _sealed_file(tmp_path, "internal-seal-member")
+ seal_path = _seal_path(bundle)
+ target = seal_path.with_name("identical-target.json")
+ target.write_bytes(seal_path.read_bytes())
+ target.chmod(target.stat().st_mode & ~stat.S_IWUSR)
+ _writable(seal_path)
+ seal_path.unlink()
+ _symlink_or_skip(seal_path, target)
+
+ result = verify_frozen_artifact(bundle)
+
+ assert result.state == "FAIL"
+ assert any("seal member" in error and "must not" in error for error in result.errors)
+
+
+def test_dangling_seal_member_link_is_structural_failure(tmp_path: Path) -> None:
+ bundle, _ = _sealed_file(tmp_path, "dangling-seal-member")
+ seal_path = _seal_path(bundle)
+ _writable(seal_path)
+ seal_path.unlink()
+ _symlink_or_skip(seal_path, tmp_path / "absent-seal.json")
+
+ result = verify_frozen_artifact(bundle)
+
+ assert result.state == "FAIL"
+ assert any("seal member" in error and "must not" in error for error in result.errors)
+
+
+@pytest.mark.parametrize("replacement", ("directory", "fifo"))
+def test_nonregular_seal_member_is_structural_failure(
+ tmp_path: Path, replacement: str
+) -> None:
+ bundle, _ = _sealed_file(tmp_path, f"nonregular-seal-{replacement}")
+ seal_path = _seal_path(bundle)
+ _writable(seal_path)
+ seal_path.unlink()
+ if replacement == "directory":
+ seal_path.mkdir()
+ else:
+ _fifo_or_skip(seal_path)
+
+ result = verify_frozen_artifact(bundle)
+
+ assert result.state == "FAIL"
+ assert any("seal member" in error for error in result.errors)
+
+
+def test_unexpected_non_json_seal_member_is_structural_failure(tmp_path: Path) -> None:
+ bundle, _ = _sealed_file(tmp_path, "unexpected-seal-member")
+ (bundle / "seals" / "unexpected.txt").write_bytes(b"unexpected")
+
+ result = verify_frozen_artifact(bundle)
+
+ assert result.state == "FAIL"
+ assert any("unsupported entry" in error for error in result.errors)
+
+
+def test_absent_seals_container_preserves_zero_seal_semantics(tmp_path: Path) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"source")
+ bundle = tmp_path / "unsealed"
+ freeze_artifact(source, bundle)
+
+ assert not os.path.lexists(bundle / "seals")
+ assert verify_frozen_artifact(bundle, require_seal=False).state == "FROZEN_UNSEALED"
+ assert verify_frozen_artifact(bundle, require_seal=True).state == "NOT_ESTABLISHED"
+
+
+def test_seal_creation_refuses_existing_linked_valid_target_without_mutating_it(
+ tmp_path: Path,
+) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"source")
+ bundle = tmp_path / "bundle"
+ key = _private_key(tmp_path / "key.pem")
+ freeze_artifact(source, bundle)
+ seal_artifact(bundle, key)
+ target = _seal_path(bundle)
+ external = tmp_path / "external-envelope.json"
+ _writable(target)
+ target.replace(external)
+ external.chmod(external.stat().st_mode & ~stat.S_IWUSR)
+ before = external.read_bytes()
+ _symlink_or_skip(target, external)
+
+ with pytest.raises(ArtifactControlError, match="cleanly frozen"):
+ seal_artifact(bundle, key)
+
+ assert target.is_symlink()
+ assert external.read_bytes() == before
+
+
+@pytest.mark.parametrize("target_state", ("dangling", "different"))
+def test_seal_creation_refuses_nonordinary_or_different_deterministic_target(
+ tmp_path: Path, target_state: str
+) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"same source")
+ key = _private_key(tmp_path / "key.pem")
+ probe = tmp_path / "probe"
+ freeze_artifact(source, probe)
+ seal_artifact(probe, key)
+ filename = _seal_path(probe).name
+
+ bundle = tmp_path / "bundle"
+ freeze_artifact(source, bundle)
+ seals = bundle / "seals"
+ seals.mkdir()
+ target = seals / filename
+ if target_state == "dangling":
+ external = tmp_path / "absent-envelope.json"
+ _symlink_or_skip(target, external)
+ else:
+ target.write_bytes(b"{}")
+
+ with pytest.raises(ArtifactControlError, match="cleanly frozen"):
+ seal_artifact(bundle, key)
+
+ if target_state == "dangling":
+ assert target.is_symlink()
+ assert not external.exists()
+ else:
+ assert target.read_bytes() == b"{}"
+
+
+@pytest.mark.parametrize("target_state", ("directory", "dangling"))
+def test_seal_creation_refuses_linked_seals_container_without_writing_through_it(
+ tmp_path: Path, target_state: str
+) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"source")
+ bundle = tmp_path / "bundle"
+ key = _private_key(tmp_path / "key.pem")
+ freeze_artifact(source, bundle)
+ target = tmp_path / "seals-target"
+ if target_state == "directory":
+ target.mkdir()
+ seals = bundle / "seals"
+ _symlink_or_skip(seals, target, target_is_directory=True)
+
+ with pytest.raises(ArtifactControlError, match="cleanly frozen"):
+ seal_artifact(bundle, key)
+
+ assert seals.is_symlink()
+ if target_state == "directory":
+ assert list(target.iterdir()) == []
+ else:
+ assert not target.exists()
+
+
+def test_seal_creation_deduplicates_only_an_ordinary_identical_envelope(
+ tmp_path: Path,
+) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"source")
+ bundle = tmp_path / "bundle"
+ key = _private_key(tmp_path / "key.pem")
+ freeze_artifact(source, bundle)
+
+ first = seal_artifact(bundle, key)
+ path = _seal_path(bundle)
+ before = path.read_bytes()
+ second = seal_artifact(bundle, key)
+
+ assert second == first
+ assert path.read_bytes() == before
+ assert not path.is_symlink()
+
+
+def test_seal_creation_recovers_from_directory_creation_race(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"source")
+ bundle = tmp_path / "bundle"
+ key = _private_key(tmp_path / "key.pem")
+ freeze_artifact(source, bundle)
+ seals = bundle / "seals"
+ original_mkdir = Path.mkdir
+
+ def raced_mkdir(path: Path, *args: object, **kwargs: object) -> None:
+ if path == seals:
+ original_mkdir(path)
+ raise FileExistsError("simulated directory race")
+ original_mkdir(path, *args, **kwargs)
+
+ monkeypatch.setattr(Path, "mkdir", raced_mkdir)
+
+ envelope = seal_artifact(bundle, key)
+
+ assert verify_frozen_artifact(bundle).state == "SEALED"
+ assert envelope["seal_id"] in verify_frozen_artifact(bundle).valid_seal_ids
+
+
+def test_seal_creation_compares_existing_ordinary_target_before_reuse(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"same source")
+ key = _private_key(tmp_path / "key.pem")
+ probe = tmp_path / "probe"
+ freeze_artifact(source, probe)
+ seal_artifact(probe, key)
+ filename = _seal_path(probe).name
+
+ bundle = tmp_path / "bundle"
+ freeze_artifact(source, bundle)
+ seals = bundle / "seals"
+ seals.mkdir()
+ target = seals / filename
+ target.write_text("{}\n", encoding="utf-8")
+ original_verify = artifact_control_module.verify_frozen_artifact
+ first = True
+
+ def allow_initial_inspection(*args: object, **kwargs: object):
+ nonlocal first
+ if first:
+ first = False
+ return artifact_control_module.ArtifactVerification(
+ state="FROZEN_UNSEALED",
+ artifact_id=None,
+ content_id=None,
+ freeze_id=None,
+ freeze_valid=True,
+ guard_valid=True,
+ valid_seal_ids=(),
+ key_ids=(),
+ external_anchor="NOT_CHECKED",
+ errors=(),
+ warnings=(),
+ )
+ return original_verify(*args, **kwargs)
+
+ monkeypatch.setattr(
+ artifact_control_module, "verify_frozen_artifact", allow_initial_inspection
+ )
+
+ with pytest.raises(ArtifactControlError, match="different bytes"):
+ seal_artifact(bundle, key)
+
+ assert target.read_text(encoding="utf-8") == "{}\n"
+
+
+def test_seal_creation_failure_before_target_ownership_removes_created_container(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"source")
+ bundle = tmp_path / "bundle"
+ key = _private_key(tmp_path / "key.pem")
+ freeze_artifact(source, bundle)
+
+ def refuse_write(*args: object, **kwargs: object) -> None:
+ raise ArtifactControlError("simulated exclusive-write refusal")
+
+ monkeypatch.setattr(artifact_control_module, "_write_json_exclusive", refuse_write)
+
+ with pytest.raises(ArtifactControlError, match="exclusive-write refusal"):
+ seal_artifact(bundle, key)
+
+ assert not os.path.lexists(bundle / "seals")
+
+
+def test_failed_seal_creation_removes_only_created_target_and_empty_container(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"source")
+ bundle = tmp_path / "bundle"
+ key = _private_key(tmp_path / "key.pem")
+ freeze_artifact(source, bundle)
+ original_verify = artifact_control_module.verify_frozen_artifact
+ calls = 0
+
+ def fail_final_verification(*args: object, **kwargs: object):
+ nonlocal calls
+ calls += 1
+ if calls == 1:
+ return original_verify(*args, **kwargs)
+ return artifact_control_module.ArtifactVerification(
+ state="FAIL",
+ artifact_id=None,
+ content_id=None,
+ freeze_id=None,
+ freeze_valid=False,
+ guard_valid=False,
+ valid_seal_ids=(),
+ key_ids=(),
+ external_anchor="NOT_CHECKED",
+ errors=("simulated final failure",),
+ warnings=(),
+ )
+
+ monkeypatch.setattr(
+ artifact_control_module, "verify_frozen_artifact", fail_final_verification
+ )
+
+ with pytest.raises(ArtifactControlError, match="did not produce"):
+ seal_artifact(bundle, key)
+
+ assert not os.path.lexists(bundle / "seals")
+
+
+def test_seal_cleanup_unlinks_replacement_link_without_touching_target(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"source")
+ bundle = tmp_path / "bundle"
+ key = _private_key(tmp_path / "key.pem")
+ freeze_artifact(source, bundle)
+ external = tmp_path / "replacement-target"
+ external.write_bytes(b"unchanged")
+ original_make_read_only = artifact_control_module._make_read_only
+
+ def replace_then_fail(path: Path) -> None:
+ if path.parent.name == "seals":
+ path.unlink()
+ _symlink_or_skip(path, external)
+ raise ArtifactControlError("simulated seal cleanup")
+ original_make_read_only(path)
+
+ monkeypatch.setattr(artifact_control_module, "_make_read_only", replace_then_fail)
+
+ with pytest.raises(ArtifactControlError, match="simulated seal cleanup"):
+ seal_artifact(bundle, key)
+
+ assert external.read_bytes() == b"unchanged"
+ assert not os.path.lexists(bundle / "seals")
+
+
+def test_seal_cleanup_unlinks_replacement_container_alias_without_traversal(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"source")
+ bundle = tmp_path / "bundle"
+ key = _private_key(tmp_path / "key.pem")
+ freeze_artifact(source, bundle)
+ external = tmp_path / "replacement-container"
+ external.mkdir()
+ marker = external / "unchanged"
+ marker.write_bytes(b"unchanged")
+ original_make_read_only = artifact_control_module._make_read_only
+
+ def replace_container_then_fail(path: Path) -> None:
+ if path.parent.name == "seals":
+ path.unlink()
+ path.parent.rmdir()
+ _symlink_or_skip(path.parent, external, target_is_directory=True)
+ raise ArtifactControlError("simulated container replacement")
+ original_make_read_only(path)
+
+ monkeypatch.setattr(
+ artifact_control_module, "_make_read_only", replace_container_then_fail
+ )
+
+ with pytest.raises(ArtifactControlError, match="container replacement"):
+ seal_artifact(bundle, key)
+
+ assert marker.read_bytes() == b"unchanged"
+ assert not os.path.lexists(bundle / "seals")
+
+
+def test_cleanup_handles_reparse_directory_branch_and_chmod_refusal(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ directory = tmp_path / "junction-shaped"
+ directory.mkdir()
+ monkeypatch.setattr(artifact_control_module, "_is_link_like", lambda entry: True)
+ artifact_control_module._remove_created_entry(directory)
+ assert not directory.exists()
+
+ monkeypatch.undo()
+ file_path = tmp_path / "ordinary"
+ file_path.write_bytes(b"ordinary")
+ original_chmod = Path.chmod
+
+ def refuse_chmod(path: Path, mode: int) -> None:
+ if path == file_path:
+ raise OSError("simulated chmod refusal")
+ original_chmod(path, mode)
+
+ monkeypatch.setattr(Path, "chmod", refuse_chmod)
+ artifact_control_module._remove_created_entry(file_path)
+ assert not file_path.exists()
+
+
+def test_seal_cleanup_preserves_precise_failure_when_empty_container_cannot_be_removed(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"source")
+ bundle = tmp_path / "bundle"
+ key = _private_key(tmp_path / "key.pem")
+ freeze_artifact(source, bundle)
+ original_verify = artifact_control_module.verify_frozen_artifact
+ original_rmdir = Path.rmdir
+ calls = 0
+
+ def fail_final_verification(*args: object, **kwargs: object):
+ nonlocal calls
+ calls += 1
+ if calls == 1:
+ return original_verify(*args, **kwargs)
+ return artifact_control_module.ArtifactVerification(
+ state="FAIL",
+ artifact_id=None,
+ content_id=None,
+ freeze_id=None,
+ freeze_valid=False,
+ guard_valid=False,
+ valid_seal_ids=(),
+ key_ids=(),
+ external_anchor="NOT_CHECKED",
+ errors=("simulated final failure",),
+ warnings=(),
+ )
+
+ def refuse_seals_rmdir(path: Path) -> None:
+ if path == bundle / "seals":
+ raise OSError("simulated inaccessible cleanup path")
+ original_rmdir(path)
+
+ monkeypatch.setattr(
+ artifact_control_module, "verify_frozen_artifact", fail_final_verification
+ )
+ monkeypatch.setattr(Path, "rmdir", refuse_seals_rmdir)
+
+ with pytest.raises(ArtifactControlError, match="did not produce"):
+ seal_artifact(bundle, key)
+
+ assert (bundle / "seals").is_dir()
+ assert list((bundle / "seals").iterdir()) == []
+
+
+def test_thaw_is_copy_on_write_and_dirtying_is_observable(tmp_path: Path) -> None:
+ unsealed_source = tmp_path / "unsealed.bin"
+ unsealed_source.write_bytes(b"unsealed")
+ unsealed = tmp_path / "unsealed"
+ freeze_artifact(unsealed_source, unsealed)
+ with pytest.raises(ArtifactControlError, match="cleanly sealed"):
+ thaw_artifact(unsealed, tmp_path / "blocked")
+
+ bundle, _ = _sealed_file(tmp_path, "sealed")
+ parent_before = (bundle / "payload").read_bytes()
+ descendant = tmp_path / "descendant.bin"
+ record = thaw_artifact(bundle, descendant)
+
+ assert descendant.read_bytes() == parent_before
+ sidecar_only = thawed_artifact_status(descendant)
+ assert sidecar_only["state"] == "NOT_ESTABLISHED"
+ assert sidecar_only["recorded_identity_match"] is True
+ assert sidecar_only["lineage_state"] == "NOT_ESTABLISHED"
+ assert thawed_artifact_status(
+ descendant, parent_bundle=bundle
+ )["state"] == "THAWED_CLEAN"
+ descendant.write_bytes(b"changed")
+ assert thawed_artifact_status(descendant)["state"] == "NOT_ESTABLISHED"
+ assert thawed_artifact_status(
+ descendant, parent_bundle=bundle
+ )["state"] == "THAWED_DIRTY"
+ assert (bundle / "payload").read_bytes() == parent_before
+ assert record["parent_artifact_id"] == verify_frozen_artifact(bundle).artifact_id
+
+
+@pytest.mark.parametrize("target_kind", ("file", "directory", "dangling"))
+def test_thaw_refuses_symlink_destination_without_mutating_target(
+ tmp_path: Path, target_kind: str
+) -> None:
+ bundle, _ = _sealed_file(tmp_path, f"destination-{target_kind}")
+ target = tmp_path / "target"
+ if target_kind == "file":
+ target.write_bytes(b"unchanged")
+ elif target_kind == "directory":
+ target.mkdir()
+ (target / "unchanged").write_bytes(b"unchanged")
+ destination = tmp_path / "descendant-link"
+ _symlink_or_skip(
+ destination, target, target_is_directory=target_kind == "directory"
+ )
+
+ with pytest.raises(ArtifactControlError, match="already exists"):
+ thaw_artifact(bundle, destination)
+
+ assert destination.is_symlink()
+ assert not os.path.lexists(Path(str(destination) + ".vstd-thaw.json"))
+ assert not os.path.lexists(Path(str(target) + ".vstd-thaw.json"))
+ if target_kind == "file":
+ assert target.read_bytes() == b"unchanged"
+ elif target_kind == "directory":
+ assert (target / "unchanged").read_bytes() == b"unchanged"
+ else:
+ assert not target.exists()
+
+
+@pytest.mark.parametrize("destination_kind", ("file", "directory"))
+def test_thaw_refuses_existing_ordinary_destination(
+ tmp_path: Path, destination_kind: str
+) -> None:
+ bundle, _ = _sealed_file(tmp_path, f"existing-{destination_kind}")
+ destination = tmp_path / "existing"
+ if destination_kind == "file":
+ destination.write_bytes(b"unchanged")
+ else:
+ destination.mkdir()
+
+ with pytest.raises(ArtifactControlError, match="already exists"):
+ thaw_artifact(bundle, destination)
+
+ assert destination.is_file() if destination_kind == "file" else destination.is_dir()
+
+
+@pytest.mark.parametrize("target_kind", ("file", "dangling"))
+def test_thaw_refuses_symlink_record_destination_without_mutating_target(
+ tmp_path: Path, target_kind: str
+) -> None:
+ bundle, _ = _sealed_file(tmp_path, f"record-{target_kind}")
+ destination = tmp_path / "descendant"
+ record_path = Path(str(destination) + ".vstd-thaw.json")
+ target = tmp_path / "record-target"
+ if target_kind == "file":
+ target.write_bytes(b"unchanged")
+ _symlink_or_skip(record_path, target)
+
+ with pytest.raises(ArtifactControlError, match="already exists"):
+ thaw_artifact(bundle, destination)
+
+ assert not os.path.lexists(destination)
+ assert record_path.is_symlink()
+ if target_kind == "file":
+ assert target.read_bytes() == b"unchanged"
+ else:
+ assert not target.exists()
+
+
+def test_thaw_directory_succeeds_and_mutation_becomes_dirty(tmp_path: Path) -> None:
+ source = tmp_path / "source"
+ (source / "empty").mkdir(parents=True)
+ (source / "value").write_bytes(b"value")
+ bundle = tmp_path / "bundle"
+ freeze_artifact(source, bundle)
+ seal_artifact(bundle, _private_key(tmp_path / "key.pem"))
+ destination = tmp_path / "descendant"
+
+ record = thaw_artifact(bundle, destination)
+ record_path = Path(str(record["record_path"]))
+ clean = thawed_artifact_status(destination, record_path, parent_bundle=bundle)
+ (destination / "value").write_bytes(b"changed")
+ dirty = thawed_artifact_status(destination, record_path, parent_bundle=bundle)
+
+ assert (destination / "empty").is_dir()
+ assert clean["state"] == "THAWED_CLEAN"
+ assert dirty["state"] == "THAWED_DIRTY"
+
+
+def test_failed_thaw_cleanup_unlinks_replacement_symlink_without_touching_target(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ bundle, _ = _sealed_file(tmp_path, "cleanup")
+ destination = tmp_path / "descendant"
+ record_path = Path(str(destination) + ".vstd-thaw.json")
+ replacement_target = tmp_path / "replacement-target"
+ replacement_target.write_bytes(b"unchanged")
+
+ def fail_after_creation(*args: object, **kwargs: object) -> dict[str, object]:
+ destination.unlink()
+ _symlink_or_skip(destination, replacement_target)
+ raise ArtifactControlError("simulated post-copy refusal")
+
+ monkeypatch.setattr(
+ artifact_control_module, "thawed_artifact_status", fail_after_creation
+ )
+
+ with pytest.raises(ArtifactControlError, match="simulated post-copy refusal"):
+ thaw_artifact(bundle, destination)
+
+ assert not os.path.lexists(destination)
+ assert not os.path.lexists(record_path)
+ assert replacement_target.read_bytes() == b"unchanged"
+
+
+def test_exclusive_sidecar_write_removes_a_partial_file(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ target = tmp_path / "record.json"
+ original = json.dumps
+
+ def fail_serialization(*args: object, **kwargs: object) -> str:
+ if kwargs.get("indent") == 2:
+ raise RuntimeError("simulated serialization failure")
+ return original(*args, **kwargs)
+
+ monkeypatch.setattr(artifact_control_module.json, "dumps", fail_serialization)
+
+ with pytest.raises(RuntimeError, match="simulated serialization failure"):
+ artifact_control_module._write_json_exclusive(target, {"value": 1})
+
+ assert not os.path.lexists(target)
+
+
+def test_exclusive_sidecar_write_never_removes_a_preexisting_file(tmp_path: Path) -> None:
+ target = tmp_path / "record.json"
+ target.write_bytes(b"preexisting")
+
+ with pytest.raises(FileExistsError):
+ artifact_control_module._write_json_exclusive(target, {"value": 1})
+
+ assert target.read_bytes() == b"preexisting"
+
+
+def test_cleanup_tolerates_an_already_absent_invocation_entry(tmp_path: Path) -> None:
+ missing = tmp_path / "already-absent"
+
+ artifact_control_module._remove_created_entry(missing)
+
+ assert not os.path.lexists(missing)
+
+
+def test_failed_sidecar_creation_cleans_the_created_descendant(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ bundle, _ = _sealed_file(tmp_path, "sidecar-cleanup")
+ destination = tmp_path / "descendant"
+
+ def refuse_sidecar(*args: object, **kwargs: object) -> None:
+ raise ArtifactControlError("simulated sidecar refusal")
+
+ monkeypatch.setattr(artifact_control_module, "_write_json_exclusive", refuse_sidecar)
+
+ with pytest.raises(ArtifactControlError, match="simulated sidecar refusal"):
+ thaw_artifact(bundle, destination)
+
+ assert not os.path.lexists(destination)
+ assert not os.path.lexists(Path(str(destination) + ".vstd-thaw.json"))
+
+
+def test_raced_file_destination_is_not_deleted_when_exclusive_creation_refuses_it(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ bundle, _ = _sealed_file(tmp_path, "raced-destination")
+ destination = tmp_path / "descendant"
+ original_open = Path.open
+
+ def raced_open(path: Path, mode: str = "r", *args: object, **kwargs: object):
+ if path == destination and mode == "xb":
+ path.write_bytes(b"raced")
+ raise FileExistsError("simulated concurrent destination")
+ return original_open(path, mode, *args, **kwargs)
+
+ monkeypatch.setattr(Path, "open", raced_open)
+
+ with pytest.raises(FileExistsError, match="concurrent destination"):
+ thaw_artifact(bundle, destination)
+
+ assert destination.read_bytes() == b"raced"
+ assert not os.path.lexists(Path(str(destination) + ".vstd-thaw.json"))
+
+
+def test_post_copy_nonclean_status_removes_created_descendant_and_sidecar(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ bundle, _ = _sealed_file(tmp_path, "nonclean-post-copy")
+ destination = tmp_path / "descendant"
+
+ monkeypatch.setattr(
+ artifact_control_module,
+ "thawed_artifact_status",
+ lambda *args, **kwargs: {"state": "THAWED_DIRTY"},
+ )
+
+ with pytest.raises(ArtifactControlError, match="did not match"):
+ thaw_artifact(bundle, destination)
+
+ assert not os.path.lexists(destination)
+ assert not os.path.lexists(Path(str(destination) + ".vstd-thaw.json"))
+
+
+def test_failed_directory_post_check_cleans_only_created_entries(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ source = tmp_path / "source"
+ (source / "empty").mkdir(parents=True)
+ bundle = tmp_path / "bundle"
+ freeze_artifact(source, bundle)
+ seal_artifact(bundle, _private_key(tmp_path / "directory-cleanup.pem"))
+ destination = tmp_path / "descendant"
+
+ def refuse_status(*args: object, **kwargs: object) -> dict[str, object]:
+ raise ArtifactControlError("simulated directory post-check refusal")
+
+ monkeypatch.setattr(artifact_control_module, "thawed_artifact_status", refuse_status)
+
+ with pytest.raises(ArtifactControlError, match="post-check refusal"):
+ thaw_artifact(bundle, destination)
+
+ assert not os.path.lexists(destination)
+ assert not os.path.lexists(Path(str(destination) + ".vstd-thaw.json"))
+
+
+def test_directory_thaw_rejects_a_link_injected_during_copy(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ source = tmp_path / "source"
+ source.mkdir()
+ (source / "value").write_bytes(b"value")
+ bundle = tmp_path / "bundle"
+ freeze_artifact(source, bundle)
+ seal_artifact(bundle, _private_key(tmp_path / "injected-link.pem"))
+ destination = tmp_path / "descendant"
+ original_copytree = artifact_control_module.shutil.copytree
+
+ def inject_link(*args: object, **kwargs: object) -> Path:
+ copied = original_copytree(*args, **kwargs)
+ _symlink_or_skip(destination / "injected", tmp_path / "absent")
+ return copied
+
+ monkeypatch.setattr(artifact_control_module.shutil, "copytree", inject_link)
+
+ with pytest.raises(ArtifactControlError, match="did not match"):
+ thaw_artifact(bundle, destination)
+
+ assert not os.path.lexists(destination)
+ assert not os.path.lexists(Path(str(destination) + ".vstd-thaw.json"))
+
+
+def test_sealed_parent_and_context_bindings_are_explicit_and_deduplicated(
+ tmp_path: Path,
+) -> None:
+ parent, _ = _sealed_file(tmp_path, "parent")
+ context, _ = _sealed_file(tmp_path, "realm")
+ child_source = tmp_path / "child.bin"
+ child_source.write_bytes(b"child")
+ child = tmp_path / "child"
+
+ manifest = freeze_artifact(
+ child_source,
+ child,
+ parent_bundles=[parent],
+ context_bundles=[context],
+ )
+
+ assert manifest["lineage"] == [verify_frozen_artifact(parent).artifact_id]
+ assert manifest["bound_contexts"] == [verify_frozen_artifact(context).artifact_id]
+ with pytest.raises(ArtifactControlError, match="duplicate"):
+ freeze_artifact(
+ child_source,
+ tmp_path / "duplicate",
+ context_bundles=[context, context],
+ )
+
+
+def test_fabricated_thaw_sidecar_without_parent_never_establishes_lineage(
+ tmp_path: Path,
+) -> None:
+ artifact = tmp_path / "fabricated.bin"
+ artifact.write_bytes(b"fabricated")
+ probe = tmp_path / "probe"
+ manifest = freeze_artifact(artifact, probe, media_type="application/x-fabricated")
+ record_path = tmp_path / "fabricated.bin.vstd-thaw.json"
+ record_path.write_text(
+ json.dumps(
+ {
+ "schema_version": "VSTD-ARTIFACT-THAW-1",
+ "parent_artifact_id": manifest["artifact_id"],
+ "parent_content_id": _fake_dual_id("content"),
+ "parent_freeze_id": _fake_dual_id("freeze"),
+ "parent_seal_ids": [_fake_dual_id("seal")],
+ "artifact_kind": "file",
+ "media_type": "application/x-fabricated",
+ "thaw_id": _fake_dual_id("thaw"),
+ }
+ ),
+ encoding="utf-8",
+ )
+ _reclose_thaw_record(record_path)
+
+ result = thawed_artifact_status(artifact, record_path)
+
+ assert result["state"] == "NOT_ESTABLISHED"
+ assert result["recorded_identity_match"] is True
+ assert result["verified_parent_identity_match"] is None
+ assert result["lineage_state"] == "NOT_ESTABLISHED"
+ assert result["historical_operation"] == "NOT_ESTABLISHED"
+
+
+@pytest.mark.parametrize(
+ ("field", "replacement"),
+ (
+ ("parent_content_id", _fake_dual_id("content")),
+ ("parent_freeze_id", _fake_dual_id("freeze")),
+ ("parent_seal_ids", [_fake_dual_id("seal")]),
+ ),
+)
+def test_reclosed_false_parent_coordinates_do_not_establish_clean_lineage(
+ tmp_path: Path, field: str, replacement: object
+) -> None:
+ bundle, descendant, record_path = _thawed_file(tmp_path, field)
+ _reclose_thaw_record(record_path, **{field: replacement})
+
+ result = thawed_artifact_status(
+ descendant, record_path, parent_bundle=bundle
+ )
+
+ assert result["state"] == "FAIL"
+ assert result["lineage_state"] == "NOT_ESTABLISHED"
+ assert result["historical_operation"] == "NOT_ESTABLISHED"
+
+
+def test_fabricated_parent_artifact_and_matching_descendant_need_actual_parent(
+ tmp_path: Path,
+) -> None:
+ _, descendant, record_path = _thawed_file(tmp_path, "fabricated-parent")
+ descendant.write_bytes(b"neighboring fabricated descendant")
+ probe = tmp_path / "fabricated-parent-probe"
+ manifest = freeze_artifact(
+ descendant, probe, media_type="application/x-test"
+ )
+ _reclose_thaw_record(record_path, parent_artifact_id=manifest["artifact_id"])
+
+ result = thawed_artifact_status(descendant, record_path)
+
+ assert result["recorded_identity_match"] is True
+ assert result["state"] == "NOT_ESTABLISHED"
+ assert result["parent_verification_state"] == "NOT_CHECKED"
+
+
+def test_neighboring_sealed_parent_bundle_is_refused(tmp_path: Path) -> None:
+ _, descendant, record_path = _thawed_file(tmp_path, "original-parent")
+ neighbor, _ = _sealed_file(tmp_path, "neighbor-parent")
+
+ result = thawed_artifact_status(
+ descendant, record_path, parent_bundle=neighbor
+ )
+
+ assert result["state"] == "FAIL"
+ assert any("seal" in error for error in result["errors"])
+
+
+def test_recorded_seal_must_be_valid_on_supplied_parent(tmp_path: Path) -> None:
+ bundle, descendant, record_path = _thawed_file(tmp_path, "invalid-recorded-seal")
+ _reclose_thaw_record(record_path, parent_seal_ids=[_fake_dual_id("seal", "1")])
+
+ result = thawed_artifact_status(
+ descendant, record_path, parent_bundle=bundle
+ )
+
+ assert result["state"] == "FAIL"
+ assert any("not valid" in error for error in result["errors"])
+
+
+def test_later_additional_valid_parent_seal_preserves_thaw_lineage(
+ tmp_path: Path,
+) -> None:
+ bundle, descendant, record_path = _thawed_file(tmp_path, "additional-seal")
+ original = json.loads(record_path.read_text(encoding="utf-8"))["parent_seal_ids"]
+ seal_artifact(bundle, _private_key(tmp_path / "additional-seal-later.pem"))
+
+ result = thawed_artifact_status(
+ descendant, record_path, parent_bundle=bundle
+ )
+
+ assert result["state"] == "THAWED_CLEAN"
+ assert set(original).issubset(verify_frozen_artifact(bundle).valid_seal_ids)
+
+
+def test_unsealed_parent_cannot_establish_thaw_lineage(tmp_path: Path) -> None:
+ source = tmp_path / "unsealed-parent.bin"
+ source.write_bytes(b"unsealed")
+ unsealed = tmp_path / "unsealed-parent"
+ freeze_artifact(source, unsealed, media_type="application/x-test")
+ _, descendant, record_path = _thawed_file(tmp_path, "sealed-origin")
+
+ result = thawed_artifact_status(
+ descendant, record_path, parent_bundle=unsealed
+ )
+
+ assert result["state"] == "FAIL"
+ assert result["parent_verification_state"] == "NOT_ESTABLISHED"
+
+
+def test_conflicted_parent_cannot_establish_thaw_lineage(tmp_path: Path) -> None:
+ bundle, descendant, record_path = _thawed_file(tmp_path, "conflicted-parent")
+ invalid = bundle / "seals" / "invalid.json"
+ invalid.write_text("{}\n", encoding="utf-8")
+ invalid.chmod(invalid.stat().st_mode & ~stat.S_IWUSR)
+
+ result = thawed_artifact_status(
+ descendant, record_path, parent_bundle=bundle
+ )
+
+ assert result["state"] == "FAIL"
+ assert result["parent_verification_state"] == "CONFLICTED"
+
+
+@pytest.mark.parametrize(
+ ("anchor_name", "anchor_value"),
+ (
+ ("expected_artifact_id", _fake_dual_id("artifact")),
+ ("expected_key_id", "vstd-seal-key-1:sha256:" + "0" * 64),
+ ),
+)
+def test_parent_external_anchor_mismatch_refuses_thaw_lineage(
+ tmp_path: Path, anchor_name: str, anchor_value: str
+) -> None:
+ bundle, descendant, record_path = _thawed_file(tmp_path, anchor_name)
+
+ result = thawed_artifact_status(
+ descendant,
+ record_path,
+ parent_bundle=bundle,
+ **{anchor_name: anchor_value},
+ )
+
+ assert result["state"] == "FAIL"
+ assert result["external_anchor_state"] == "MISMATCH"
+
+
+def test_verified_parent_distinguishes_clean_and_dirty_without_claiming_history(
+ tmp_path: Path,
+) -> None:
+ bundle, descendant, record_path = _thawed_file(tmp_path, "verified-parent")
+
+ clean = thawed_artifact_status(
+ descendant, record_path, parent_bundle=bundle
+ )
+ descendant.write_bytes(b"dirty descendant")
+ dirty = thawed_artifact_status(
+ descendant, record_path, parent_bundle=bundle
+ )
+
+ assert clean["state"] == "THAWED_CLEAN"
+ assert clean["lineage_state"] == "PARENT_COORDINATES_ESTABLISHED"
+ assert clean["verified_parent_identity_match"] is True
+ assert dirty["state"] == "THAWED_DIRTY"
+ assert dirty["lineage_state"] == "PARENT_COORDINATES_ESTABLISHED"
+ assert dirty["verified_parent_identity_match"] is False
+ assert clean["historical_operation"] == dirty["historical_operation"] == "NOT_ESTABLISHED"
+ assert any("historical" in warning for warning in clean["warnings"])
+
+
+@pytest.mark.parametrize(
+ ("field", "replacement"),
+ (("artifact_kind", "directory"), ("media_type", "application/x-neighbor")),
+)
+def test_sidecar_kind_and_media_type_must_match_authoritative_parent_metadata(
+ tmp_path: Path, field: str, replacement: str
+) -> None:
+ bundle, descendant, record_path = _thawed_file(tmp_path, field)
+ _reclose_thaw_record(record_path, **{field: replacement})
+
+ result = thawed_artifact_status(
+ descendant, record_path, parent_bundle=bundle
+ )
+
+ assert result["state"] == "FAIL"
+ assert result["identity_basis"] == "VERIFIED_PARENT_METADATA"
+ assert any(field in error for error in result["errors"])
+
+
+def test_status_check_does_not_modify_parent_bytes_seals_or_modes(tmp_path: Path) -> None:
+ bundle, descendant, record_path = _thawed_file(tmp_path, "parent-immutability")
+
+ def snapshot() -> list[tuple[str, bytes | None, int]]:
+ return [
+ (
+ path.relative_to(bundle).as_posix(),
+ path.read_bytes() if path.is_file() else None,
+ stat.S_IMODE(path.stat().st_mode),
+ )
+ for path in sorted(bundle.rglob("*"), key=lambda item: item.as_posix())
+ ]
+
+ before = snapshot()
+ result = thawed_artifact_status(
+ descendant, record_path, parent_bundle=bundle
+ )
+ after = snapshot()
+
+ assert result["state"] == "THAWED_CLEAN"
+ assert after == before
+
+
+@pytest.mark.parametrize(
+ "mutation",
+ (
+ "schema",
+ "identifier",
+ "empty_seals",
+ "invalid_seal",
+ "duplicate_seal",
+ "kind",
+ "media_type",
+ "thaw_id",
+ ),
+)
+def test_malformed_thaw_sidecar_fields_fail_closed(
+ tmp_path: Path, mutation: str
+) -> None:
+ _, descendant, record_path = _thawed_file(tmp_path, f"malformed-{mutation}")
+ record = json.loads(record_path.read_text(encoding="utf-8"))
+ if mutation == "schema":
+ record["schema_version"] = "UNKNOWN"
+ elif mutation == "identifier":
+ record["parent_content_id"] = "not-an-identifier"
+ elif mutation == "empty_seals":
+ record["parent_seal_ids"] = []
+ elif mutation == "invalid_seal":
+ record["parent_seal_ids"] = ["not-a-seal"]
+ elif mutation == "duplicate_seal":
+ record["parent_seal_ids"] = record["parent_seal_ids"] * 2
+ elif mutation == "kind":
+ record["artifact_kind"] = "device"
+ elif mutation == "media_type":
+ record["media_type"] = ""
+ else:
+ record["thaw_id"] = _fake_dual_id("thaw")
+ record_path.write_text(json.dumps(record), encoding="utf-8")
+
+ with pytest.raises(ArtifactControlError):
+ thawed_artifact_status(descendant, record_path)
+
+
+def test_verified_parent_kind_change_is_dirty_not_clean(tmp_path: Path) -> None:
+ bundle, descendant, record_path = _thawed_file(tmp_path, "kind-change")
+ descendant.unlink()
+ descendant.mkdir()
+
+ result = thawed_artifact_status(
+ descendant, record_path, parent_bundle=bundle
+ )
+
+ assert result["state"] == "THAWED_DIRTY"
+ assert result["verified_parent_identity_match"] is False
+
+
+def test_symlink_descendant_cannot_match_recorded_or_verified_parent(
+ tmp_path: Path,
+) -> None:
+ bundle, descendant, record_path = _thawed_file(tmp_path, "symlink-descendant")
+ target = tmp_path / "symlink-target.bin"
+ target.write_bytes(descendant.read_bytes())
+ descendant.unlink()
+ try:
+ descendant.symlink_to(target)
+ except OSError as exc:
+ pytest.skip(f"symlink creation is unavailable: {exc}")
+
+ sidecar_only = thawed_artifact_status(descendant, record_path)
+ verified = thawed_artifact_status(
+ descendant, record_path, parent_bundle=bundle
+ )
+
+ assert sidecar_only["state"] == "NOT_ESTABLISHED"
+ assert sidecar_only["recorded_identity_match"] is False
+ assert verified["state"] == "THAWED_DIRTY"
+ assert verified["verified_parent_identity_match"] is False
+
+
+def test_unreadable_descendant_inventory_cannot_match_any_identity(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ bundle, descendant, record_path = _thawed_file(tmp_path, "unreadable-descendant")
+ original = artifact_control_module._source_entries
+
+ def refuse_descendant(path: Path) -> list[dict[str, object]]:
+ if path.resolve() == descendant.resolve():
+ raise ArtifactControlError("simulated unsupported descendant")
+ return original(path)
+
+ monkeypatch.setattr(artifact_control_module, "_source_entries", refuse_descendant)
+
+ sidecar_only = thawed_artifact_status(descendant, record_path)
+ verified = thawed_artifact_status(
+ descendant, record_path, parent_bundle=bundle
+ )
+
+ assert sidecar_only["recorded_identity_match"] is False
+ assert verified["state"] == "THAWED_DIRTY"
+ assert verified["verified_parent_identity_match"] is False
+
+
+def test_matching_external_parent_anchors_are_reported_without_claiming_history(
+ tmp_path: Path,
+) -> None:
+ bundle, descendant, record_path = _thawed_file(tmp_path, "matching-anchors")
+ parent = verify_frozen_artifact(bundle)
+
+ result = thawed_artifact_status(
+ descendant,
+ record_path,
+ parent_bundle=bundle,
+ expected_artifact_id=parent.artifact_id,
+ expected_key_id=parent.key_ids[0],
+ )
+
+ assert result["state"] == "THAWED_CLEAN"
+ assert result["external_anchor_state"] == "ARTIFACT_AND_KEY_MATCHED"
+ assert not any("external continuity was not checked" in item for item in result["warnings"])
+ assert result["historical_operation"] == "NOT_ESTABLISHED"
+
+
+def test_outer_parent_bundle_alias_remains_an_accepted_read_only_coordinate(
+ tmp_path: Path,
+) -> None:
+ bundle, descendant, record_path = _thawed_file(tmp_path, "outer-parent-alias")
+ alias = tmp_path / "parent-alias"
+ _symlink_or_skip(alias, bundle, target_is_directory=True)
+
+ verification = verify_frozen_artifact(alias)
+ status = thawed_artifact_status(
+ descendant, record_path, parent_bundle=alias
+ )
+
+ assert verification.state == "SEALED"
+ assert status["state"] == "THAWED_CLEAN"
+ assert status["historical_operation"] == "NOT_ESTABLISHED"
+
+
+def test_explicit_thaw_record_alias_remains_readable_without_authenticating_history(
+ tmp_path: Path,
+) -> None:
+ bundle, descendant, record_path = _thawed_file(tmp_path, "record-alias")
+ external = tmp_path / "external-record.json"
+ record_path.replace(external)
+ _symlink_or_skip(record_path, external)
+
+ status = thawed_artifact_status(
+ descendant, record_path, parent_bundle=bundle
+ )
+
+ assert status["state"] == "THAWED_CLEAN"
+ assert status["historical_operation"] == "NOT_ESTABLISHED"
+
+
+def test_hard_linked_internal_json_members_remain_regular_file_semantics(
+ tmp_path: Path,
+) -> None:
+ bundle, _ = _sealed_file(tmp_path, "hard-linked-members")
+ freeze_path = bundle / "freeze.json"
+ seal_path = _seal_path(bundle)
+ external_freeze = tmp_path / "hard-freeze.json"
+ external_seal = tmp_path / "hard-seal.json"
+ _writable(freeze_path)
+ _writable(seal_path)
+ freeze_path.replace(external_freeze)
+ seal_path.replace(external_seal)
+ try:
+ os.link(external_freeze, freeze_path)
+ os.link(external_seal, seal_path)
+ except OSError as exc:
+ pytest.skip(f"hard-link creation is unavailable: {exc}")
+ external_freeze.chmod(external_freeze.stat().st_mode & ~stat.S_IWUSR)
+ external_seal.chmod(external_seal.stat().st_mode & ~stat.S_IWUSR)
+
+ result = verify_frozen_artifact(bundle)
+
+ assert result.state == "SEALED"
+ assert not freeze_path.is_symlink()
+ assert not seal_path.is_symlink()
+ assert freeze_path.stat().st_ino == external_freeze.stat().st_ino
+ assert seal_path.stat().st_ino == external_seal.stat().st_ino
+
+
+def test_parent_mutation_after_thaw_refuses_current_lineage(tmp_path: Path) -> None:
+ bundle, descendant, record_path = _thawed_file(tmp_path, "mutated-parent")
+ payload = bundle / "payload"
+ _writable(payload)
+ payload.write_bytes(b"mutated parent")
+
+ result = thawed_artifact_status(
+ descendant, record_path, parent_bundle=bundle
+ )
+
+ assert result["state"] == "FAIL"
+ assert result["parent_verification_state"] == "FAIL"
+
+
+def test_unknown_bundle_or_manifest_fields_fail_closed(tmp_path: Path) -> None:
+ bundle, _ = _sealed_file(tmp_path)
+ (bundle / "surprise.txt").write_text("not part of the format", encoding="utf-8")
+ assert verify_frozen_artifact(bundle).state == "FAIL"
+
+ (bundle / "surprise.txt").unlink()
+ freeze_path = bundle / "freeze.json"
+ _writable(freeze_path)
+ text = freeze_path.read_text(encoding="utf-8")
+ freeze_path.write_text(text.replace("{", '{"schema_version":"duplicate",', 1))
+ assert verify_frozen_artifact(bundle).state == "FAIL"
+
+
+def test_published_and_packaged_schemas_are_identical_and_accept_emitted_objects(
+ tmp_path: Path,
+) -> None:
+ jsonschema = pytest.importorskip("jsonschema")
+ public_path = ROOT / "standard/schemas/artifact-control-1.schema.json"
+ packaged_path = ROOT / "src/verifier/artifact_control/artifact-control-1.schema.json"
+ assert public_path.read_bytes() == packaged_path.read_bytes()
+ schema = json.loads(public_path.read_text(encoding="utf-8"))
+ jsonschema.Draft202012Validator.check_schema(schema)
+ validator = jsonschema.Draft202012Validator(schema)
+
+ bundle, seal = _sealed_file(tmp_path)
+ freeze = json.loads((bundle / "freeze.json").read_text(encoding="utf-8"))
+ descendant = tmp_path / "descendant"
+ record = thaw_artifact(bundle, descendant)
+ record.pop("record_path")
+
+ validator.validate(freeze)
+ validator.validate(seal)
+ validator.validate(record)
+
+
+def test_public_cli_exposes_the_complete_artifact_lifecycle(
+ tmp_path: Path, capsys: pytest.CaptureFixture[str]
+) -> None:
+ source = tmp_path / "source.bin"
+ source.write_bytes(b"cli")
+ bundle = tmp_path / "bundle"
+ key = _private_key(tmp_path / "key.pem")
+
+ assert main(["artifact", "freeze", str(source), str(bundle), "--json"]) == 0
+ assert json.loads(capsys.readouterr().out)["state"] == "FROZEN_UNSEALED"
+ assert main(["artifact", "verify", str(bundle), "--json"]) == 2
+ assert json.loads(capsys.readouterr().out)["state"] == "NOT_ESTABLISHED"
+ assert main(
+ ["artifact", "seal", str(bundle), "--private-key", str(key), "--json"]
+ ) == 0
+ assert json.loads(capsys.readouterr().out)["state"] == "SEALED"
+ assert main(["artifact", "verify", str(bundle), "--json"]) == 0
+ assert json.loads(capsys.readouterr().out)["state"] == "SEALED"
+
+ descendant = tmp_path / "descendant.bin"
+ assert main(
+ ["artifact", "thaw", str(bundle), str(descendant), "--json"]
+ ) == 0
+ assert json.loads(capsys.readouterr().out)["state"] == "THAWED_CLEAN"
+ assert main(["artifact", "status", str(descendant), "--json"]) == 2
+ assert json.loads(capsys.readouterr().out)["state"] == "NOT_ESTABLISHED"
+ assert main(
+ [
+ "artifact",
+ "status",
+ str(descendant),
+ "--parent-bundle",
+ str(bundle),
+ "--json",
+ ]
+ ) == 0
+ assert json.loads(capsys.readouterr().out)["state"] == "THAWED_CLEAN"
+ descendant.write_bytes(b"dirty")
+ assert main(
+ [
+ "artifact",
+ "status",
+ str(descendant),
+ "--parent-bundle",
+ str(bundle),
+ "--json",
+ ]
+ ) == 1
+ assert json.loads(capsys.readouterr().out)["state"] == "THAWED_DIRTY"
diff --git a/tests/test_assurance_flow_invariants.py b/tests/test_assurance_flow_invariants.py
new file mode 100644
index 0000000..76e6c81
--- /dev/null
+++ b/tests/test_assurance_flow_invariants.py
@@ -0,0 +1,81 @@
+"""Terminology: Verifier Standard (VSTD).
+
+Falsification probes for evidence-strength invariants shared by the five-As
+human traversal and existing VSTD machinery.
+"""
+
+from __future__ import annotations
+
+from verifier.core.reproducibility import (
+ ReproducibilityLevel,
+ compare_reproduction_level,
+)
+from verifier.data.models import (
+ ArtifactNode,
+ ArtifactType,
+ HyperedgePort,
+ ProvenanceHypergraph,
+ TransformationHyperedge,
+ TransformationType,
+)
+
+
+def _artifact(artifact_id: str) -> ArtifactNode:
+ return ArtifactNode(artifact_id, artifact_id, ArtifactType.MODEL, "a" * 64)
+
+
+def _edge(edge_id: str, source: str, target: str) -> TransformationHyperedge:
+ return TransformationHyperedge(
+ edge_id,
+ edge_id,
+ TransformationType.EVALUATION,
+ (HyperedgePort(source, "INPUT"),),
+ (HyperedgePort(target, "OUTPUT"),),
+ {},
+ {},
+ {},
+ )
+
+
+def test_matching_field_or_mismatching_verdict_earns_no_reproduction_level() -> None:
+ assert compare_reproduction_level("a", "b", "PASS", "PASS") is None
+ assert compare_reproduction_level("a", "b", "PASS", "FAIL") is None
+
+
+def test_matching_bound_evidence_can_earn_only_its_checked_level() -> None:
+ assert (
+ compare_reproduction_level(
+ "a",
+ "b",
+ "PASS",
+ "PASS",
+ original_evidence_hash="evidence",
+ reproduced_evidence_hash="evidence",
+ )
+ is ReproducibilityLevel.EVIDENCE_EQUIVALENT
+ )
+
+
+def test_duplicate_paths_do_not_multiply_ancestral_support() -> None:
+ graph = ProvenanceHypergraph()
+ for artifact_id in ("source", "result"):
+ graph.add_artifact(_artifact(artifact_id))
+ graph.add_transformation(_edge("path:one", "source", "result"))
+ graph.add_transformation(_edge("path:two", "source", "result"))
+
+ assert graph.ancestors(["result"]) == {"source", "result"}
+ assert graph.descendants(["source"]) == {"source", "result"}
+
+
+def test_self_consumption_and_two_node_feedback_are_cycles() -> None:
+ self_graph = ProvenanceHypergraph()
+ self_graph.add_artifact(_artifact("a"))
+ self_graph.add_transformation(_edge("self", "a", "a"))
+ assert self_graph.verify_acyclicity() is False
+
+ feedback = ProvenanceHypergraph()
+ feedback.add_artifact(_artifact("a"))
+ feedback.add_artifact(_artifact("b"))
+ feedback.add_transformation(_edge("a-to-b", "a", "b"))
+ feedback.add_transformation(_edge("b-to-a", "b", "a"))
+ assert feedback.verify_acyclicity() is False
diff --git a/tests/test_core_receipt_integrity.py b/tests/test_core_receipt_integrity.py
new file mode 100644
index 0000000..362589a
--- /dev/null
+++ b/tests/test_core_receipt_integrity.py
@@ -0,0 +1,82 @@
+"""Adversarial digest tests for the Verifier Standard (VSTD)-1 receipt model."""
+
+from __future__ import annotations
+
+from dataclasses import replace
+
+import pytest
+
+from verifier.core.checker import IndependentAuditor
+from verifier.core.provenance import GitProvenance, ProvenanceRecord, RuntimeEnvironment
+from verifier.core.receipt import ClaimSpec, EvidencePayload, VstdReceipt
+
+
+def _receipt() -> VstdReceipt:
+ return VstdReceipt(
+ schema_version="VSTD-1",
+ receipt_kind="claim_mechanics",
+ receipt_id="receipt-integrity-test",
+ claim=ClaimSpec(
+ id="claim-1",
+ title="Bounded claim",
+ statement="The recorded formula is satisfiable.",
+ status="PASS",
+ scope="This fixture only.",
+ limitations=("No actor independence is established.",),
+ falsification_condition="The formula is unsatisfiable.",
+ last_verified="2026-08-26",
+ ),
+ evidence=EvidencePayload(
+ domain="Boolean satisfiability problem",
+ input_text_or_formula="x1",
+ n_vars=1,
+ clauses=((1,),),
+ atomic_reasons=(),
+ assumptions=(),
+ source_artifacts={"fixture": "sha256:" + "0" * 64},
+ ),
+ target_result={"satisfiable": True},
+ independent_audit=IndependentAuditor.audit_claim_derivation(
+ claim_id="claim-1",
+ n_vars=1,
+ clauses=((1,),),
+ atomic_reasons=(),
+ ),
+ provenance=ProvenanceRecord(
+ target_name="fixture",
+ portable_repository_id="example/fixture",
+ local_repository_path="excluded-from-stable-payload",
+ git=GitProvenance("a" * 40, "main", False),
+ runtime=RuntimeEnvironment("3.12", "CPython", "test", "test", "test", "masked"),
+ captured_at_utc="2026-08-26T00:00:00Z",
+ command_executed="fixture",
+ ),
+ reproducibility={"highest_demonstrated_level": "CONTENT_IDENTICAL"},
+ )
+
+
+def test_retired_claim_identifier_is_rejected() -> None:
+ retired = "VSTD-" + "0.1"
+ with pytest.raises(ValueError, match="schema_version must be VSTD-1"):
+ replace(_receipt(), schema_version=retired)
+
+
+def test_stable_payload_tampering_invalidates_receipt_digest() -> None:
+ receipt = _receipt()
+ receipt.compute_and_set_digest()
+ assert receipt.verify_digest_integrity()
+
+ receipt.target_result["satisfiable"] = False
+
+ assert not receipt.verify_digest_integrity()
+
+
+def test_recorded_digest_cannot_self_validate_after_claim_replacement() -> None:
+ receipt = _receipt()
+ original_digest = receipt.compute_and_set_digest()
+ receipt.claim = ClaimSpec(
+ **{**receipt.claim.__dict__, "statement": "A substituted proposition."}
+ )
+
+ assert receipt.canonical_digest == original_digest
+ assert not receipt.verify_digest_integrity()
diff --git a/tests/test_evidence_bound_assurance.py b/tests/test_evidence_bound_assurance.py
new file mode 100644
index 0000000..c30d293
--- /dev/null
+++ b/tests/test_evidence_bound_assurance.py
@@ -0,0 +1,2195 @@
+"""Terminology: JavaScript Object Notation (JSON); Secure Hash Algorithm 256-bit
+(SHA-256); Verifier Standard (VSTD).
+
+Adversarial tests for evidence-bound object, Graph, lifecycle, and witness paths.
+"""
+
+from __future__ import annotations
+
+import copy
+from dataclasses import replace
+import hashlib
+import json
+from pathlib import Path
+
+import pytest
+from jsonschema import Draft202012Validator
+from referencing import Registry, Resource
+
+from verifier.core.certificate import (
+ ClaimBinding,
+ ClaimCoordinate,
+ ResourceBounds,
+ canonical_digest,
+)
+from verifier.core.depth import (
+ build_evidence_bound_vstd4_receipt,
+ establish_vstd4,
+ recheck_evidence_bound_vstd4_receipt,
+ require_vstd5_entry,
+)
+from verifier.core.evidence import (
+ BoundProposition,
+ EvidenceBindingError,
+ EvidenceBounds,
+ EvidenceStore,
+ MechanismDecision,
+ MechanismOutcome,
+ VerificationSession,
+)
+from verifier.core.kernel import reference_descriptor
+from verifier.core.witness import (
+ CorroborationOutcome,
+ CorroborationRecord,
+ IndependenceAssertion,
+ IndependenceDimension,
+ RelationshipState,
+ WitnessBundle,
+ WitnessIdentity,
+ WitnessResultStatus,
+ assess_witness_corroboration,
+ build_vstd5_receipt,
+ recheck_vstd5_receipt,
+)
+from verifier.data.assurance import (
+ AssuranceEventKind,
+ AssuranceFlowError,
+ AssuranceLedger,
+ ChallengeProjectionMechanism,
+ DiagnosticKind,
+ recheck_assurance_log,
+)
+from verifier.data.graph_level import (
+ GraphEncodingError,
+ GraphCollection,
+ build_evidence_bound_graph_level_record,
+ establish_graph_level,
+ graph_collection_binding_digest,
+ graph_level,
+ recheck_evidence_bound_graph_level_record,
+)
+from verifier.data.models import (
+ ArtifactNode,
+ ArtifactStatus,
+ ArtifactType,
+ ConflictRecord,
+ HyperedgePort,
+ ProvenanceHypergraph,
+ TransformationHyperedge,
+ TransformationType,
+)
+from verifier.layer4.challenge import (
+ Adjudication,
+ Challenge,
+ ChallengeLedger,
+ ChallengeOutcome,
+)
+from verifier.layer4.surface import RefutationType, surface_from_types
+
+
+class ExactFactMechanism:
+ """Test mechanism: compare exact JSON fact bytes with the bound proposition."""
+
+ mechanism_id = "test.exact-json-fact"
+ mechanism_digest = "sha256:" + hashlib.sha256(
+ b"tests.ExactFactMechanism:v1"
+ ).hexdigest()
+
+ def evaluate(self, binding, evidence):
+ if len(evidence) != 1:
+ return MechanismDecision(MechanismOutcome.UNKNOWN, "one fact required")
+ try:
+ observed = json.loads(evidence[0])
+ except (UnicodeDecodeError, json.JSONDecodeError):
+ return MechanismDecision(MechanismOutcome.FAIL, "fact is not JSON")
+ expected = {
+ "subject_id": binding.subject_id,
+ "predicate": binding.predicate,
+ "expected": binding.expected,
+ }
+ outcome = MechanismOutcome.PASS if observed == expected else MechanismOutcome.FAIL
+ return MechanismDecision(outcome, f"exact fact comparison: {outcome.value}")
+
+
+def _session() -> tuple[EvidenceStore, VerificationSession]:
+ store = EvidenceStore()
+ session = VerificationSession(store)
+ session.register(ExactFactMechanism())
+ return store, session
+
+
+def _proposition(
+ store: EvidenceStore,
+ subject: str,
+ predicate: str,
+ expected,
+ *,
+ parameters=None,
+) -> BoundProposition:
+ payload = json.dumps(
+ {"subject_id": subject, "predicate": predicate, "expected": expected},
+ sort_keys=True,
+ separators=(",", ":"),
+ ).encode()
+ reference = store.add(payload)
+ return BoundProposition(
+ subject,
+ predicate,
+ expected,
+ ExactFactMechanism.mechanism_id,
+ ExactFactMechanism.mechanism_digest,
+ (reference,),
+ ("test:exact-fact-policy",),
+ EvidenceBounds(1, 20_000),
+ parameters or {},
+ )
+
+
+def _binding() -> ClaimBinding:
+ return ClaimBinding(
+ "fixture claim",
+ ClaimCoordinate("claim:fixture", "fixture", {"scope": "test"}),
+ "a" * 64,
+ "b" * 64,
+ reference_descriptor(),
+ ResourceBounds(20_000, 20_000, 200_000),
+ )
+
+
+def _established_vstd4(store, session, *, binding=None):
+ binding = binding or _binding()
+ parameters = {"claim_binding_digest": binding.digest()}
+ prerequisites = {
+ profile: _proposition(
+ store,
+ "claim:fixture",
+ f"vstd.object_profile.{profile}",
+ True,
+ parameters=parameters,
+ )
+ for profile in (1, 2, 3)
+ }
+ rungs = {
+ f"4.{index}": _proposition(
+ store,
+ "claim:fixture",
+ f"vstd4.rung.4.{index}",
+ True,
+ parameters=parameters,
+ )
+ for index in range(1, 15)
+ }
+ return establish_vstd4(
+ rungs,
+ prerequisite_evidence=prerequisites,
+ session=session,
+ claim_id="claim:fixture",
+ binding=binding,
+ )
+
+
+def _graph() -> ProvenanceHypergraph:
+ graph = ProvenanceHypergraph()
+ for artifact_id in ("source", "middle", "result"):
+ graph.add_artifact(
+ ArtifactNode(
+ artifact_id,
+ artifact_id,
+ ArtifactType.MODEL,
+ hashlib.sha256(artifact_id.encode()).hexdigest(),
+ status=ArtifactStatus.VALID,
+ )
+ )
+ graph.add_transformation(
+ TransformationHyperedge(
+ "first",
+ "first",
+ TransformationType.EXTRACTION,
+ (HyperedgePort("source", "INPUT"),),
+ (HyperedgePort("middle", "OUTPUT"),),
+ {},
+ {},
+ {},
+ )
+ )
+ graph.add_transformation(
+ TransformationHyperedge(
+ "second",
+ "second",
+ TransformationType.EVALUATION,
+ (HyperedgePort("middle", "INPUT"),),
+ (HyperedgePort("result", "OUTPUT"),),
+ {},
+ {},
+ {},
+ )
+ )
+ return graph
+
+
+def _graph_rating_evidence(graph, store, binding, *, members=("result",), rating=5):
+ collection_id = "collection:fixture"
+ parameters = {
+ "collection_id": collection_id,
+ "collection_binding_digest": graph_collection_binding_digest(
+ graph,
+ collection_id=collection_id,
+ members=members,
+ binding=binding,
+ ),
+ }
+ objects = {
+ subject: _proposition(
+ store,
+ subject,
+ "vstd.object_profile",
+ rating,
+ parameters=parameters,
+ )
+ for subject in graph.artifacts
+ }
+ edges = {
+ subject: _proposition(
+ store,
+ subject,
+ "vstd.graph_edge_profile",
+ rating,
+ parameters=parameters,
+ )
+ for subject in graph.transformations
+ }
+ return objects, edges
+
+
+def _trust_proposition(
+ store: EvidenceStore,
+ ledger: AssuranceLedger,
+ transformation_id: str,
+ target_id: str,
+ prerequisite_digests=(),
+) -> BoundProposition:
+ transform = ledger.graph.transformations[transformation_id]
+ inputs = sorted({port.artifact_id for port in transform.inputs})
+ prerequisites = sorted(set(prerequisite_digests))
+ return _proposition(
+ store,
+ target_id,
+ "vstd.graph.support",
+ {
+ "historical_graph_digest": ledger.graph_digest,
+ "inputs": inputs,
+ "output": target_id,
+ "prerequisite_trust_event_digests": prerequisites,
+ "transformation_id": transformation_id,
+ },
+ )
+
+
+def _record_trust_chain(
+ ledger: AssuranceLedger,
+ store: EvidenceStore,
+ session: VerificationSession,
+):
+ first_binding = _trust_proposition(store, ledger, "first", "middle")
+ first = ledger.record_trust(
+ "middle",
+ ("source",),
+ first_binding,
+ transformation_id="first",
+ session=session,
+ recorded_at="2026-08-29T00:00:00Z",
+ )
+ second_binding = _trust_proposition(
+ store, ledger, "second", "result", (first.digest(),)
+ )
+ second = ledger.record_trust(
+ "result",
+ ("middle",),
+ second_binding,
+ transformation_id="second",
+ prerequisite_trust_event_digests=(first.digest(),),
+ session=session,
+ recorded_at="2026-08-29T00:00:01Z",
+ )
+ return first, second
+
+
+def _witness_components(
+ store: EvidenceStore,
+ entry,
+ witness_id: str,
+ *,
+ declarant_id: str = "declarant:one",
+ identity_evidence_ref: str | None = None,
+):
+ binding_digest = entry.witness.header.binding
+ identity_ref = identity_evidence_ref or store.add(
+ f"identity coordinate:{witness_id}".encode()
+ )
+ witness = WitnessIdentity(witness_id, identity_ref)
+ relation = f"{declarant_id}->{witness_id}"
+ relationships = {
+ dimension: RelationshipState.SEPARATE for dimension in IndependenceDimension
+ }
+ assertion = IndependenceAssertion(
+ witness_id,
+ relationships,
+ {
+ dimension: _proposition(
+ store,
+ relation,
+ f"vstd5.shared.{dimension.value}",
+ False,
+ parameters={"claim_binding_digest": binding_digest},
+ )
+ for dimension in IndependenceDimension
+ },
+ )
+ checker_digest = hashlib.sha256(witness_id.encode()).hexdigest()
+ certificate_digest = entry.witness.digest()
+ expected = {
+ "claim_binding_digest": binding_digest,
+ "vstd4_certificate_digest": certificate_digest,
+ "checker_descriptor_digest": checker_digest,
+ "corroboration_class": "TEST",
+ "result": CorroborationOutcome.CORROBORATED.value,
+ }
+ verification = _proposition(
+ store,
+ "claim:fixture",
+ "vstd5.corroboration",
+ expected,
+ parameters={
+ "witness_id": witness_id,
+ "observed_at": "2026-08-29T00:00:00Z",
+ },
+ )
+ corroboration = CorroborationRecord(
+ f"corroboration:{witness_id}",
+ witness_id,
+ binding_digest,
+ certificate_digest,
+ checker_digest,
+ verification.evidence_refs,
+ CorroborationOutcome.CORROBORATED,
+ "2026-08-29T00:00:00Z",
+ verification,
+ "TEST",
+ )
+ return witness, assertion, corroboration
+
+
+def _vstd5_schema_validator() -> Draft202012Validator:
+ root = Path(__file__).resolve().parents[1]
+ schema = json.loads((root / "receipts/schema/vstd5_receipt.json").read_text())
+ vstd4_schema = json.loads(
+ (root / "receipts/schema/vstd4_receipt.json").read_text()
+ )
+ assurance_schema = json.loads(
+ (root / "standard/schemas/vstd-graph-assurance-1.schema.json").read_text()
+ )
+ registry = Registry().with_resource(
+ vstd4_schema["$id"], Resource.from_contents(vstd4_schema)
+ ).with_resource(
+ assurance_schema["$id"], Resource.from_contents(assurance_schema)
+ )
+ return Draft202012Validator(schema, registry=registry)
+
+
+def test_serialized_pass_is_not_an_input_to_evidence_evaluation() -> None:
+ assert MechanismOutcome.__doc__ == "Enumeration of the exported result values."
+ store, session = _session()
+ proposition = _proposition(store, "a", "p", True)
+ assert not hasattr(proposition, "outcome")
+ assert session.evaluate(proposition).outcome is MechanismOutcome.PASS
+
+ missing_mechanism = BoundProposition(
+ "a",
+ "p",
+ True,
+ "not.registered",
+ "a" * 64,
+ proposition.evidence_refs,
+ ("test",),
+ EvidenceBounds(1, 1000),
+ )
+ assert session.evaluate(missing_mechanism).outcome is MechanismOutcome.UNKNOWN
+
+
+def test_duplicate_evidence_never_multiplies_support() -> None:
+ store, _session_value = _session()
+ reference = store.add(b"one")
+ with pytest.raises(EvidenceBindingError, match="duplicate evidence"):
+ BoundProposition(
+ "a",
+ "p",
+ True,
+ ExactFactMechanism.mechanism_id,
+ ExactFactMechanism.mechanism_digest,
+ (reference, reference),
+ ("test",),
+ EvidenceBounds(2, 100),
+ )
+
+
+def test_vstd4_can_be_established_only_by_rerunning_every_exact_binding() -> None:
+ store, session = _session()
+ result = _established_vstd4(store, session)
+ assert result.depth == 14
+ assert result.conformance_status == "ESTABLISHED"
+ assert result.admits_vstd5 is True
+ assert require_vstd5_entry(result) is result
+
+
+def test_neighboring_rung_evidence_cannot_establish_vstd4() -> None:
+ store, session = _session()
+ binding = _binding()
+ parameters = {"claim_binding_digest": binding.digest()}
+ prerequisites = {
+ profile: _proposition(
+ store, "claim:fixture", f"vstd.object_profile.{profile}", True,
+ parameters=parameters,
+ )
+ for profile in (1, 2, 3)
+ }
+ rungs = {
+ f"4.{index}": _proposition(
+ store,
+ "claim:fixture",
+ f"vstd4.rung.4.{index}",
+ True,
+ parameters=parameters,
+ )
+ for index in range(1, 15)
+ }
+ rungs["4.7"] = _proposition(
+ store,
+ "claim:neighbor",
+ "vstd4.rung.4.7",
+ True,
+ parameters=parameters,
+ )
+ result = establish_vstd4(
+ rungs,
+ prerequisite_evidence=prerequisites,
+ session=session,
+ claim_id="claim:fixture",
+ binding=binding,
+ )
+ assert result.depth == 6
+ assert result.conformance_status == "NOT_ESTABLISHED"
+ assert any("rung 4.7 targets" in error for error in result.binding_errors)
+
+
+def test_evidence_bound_vstd4_receipt_replays_offline_and_matches_schema() -> None:
+ store, session = _session()
+ binding = _binding()
+ parameters = {"claim_binding_digest": binding.digest()}
+ prerequisites = {
+ profile: _proposition(
+ store, "claim:fixture", f"vstd.object_profile.{profile}", True,
+ parameters=parameters,
+ )
+ for profile in (1, 2, 3)
+ }
+ rungs = {
+ f"4.{index}": _proposition(
+ store,
+ "claim:fixture",
+ f"vstd4.rung.4.{index}",
+ True,
+ parameters=parameters,
+ )
+ for index in range(1, 15)
+ }
+ result = establish_vstd4(
+ rungs,
+ prerequisite_evidence=prerequisites,
+ session=session,
+ claim_id="claim:fixture",
+ binding=binding,
+ )
+ receipt = build_evidence_bound_vstd4_receipt(
+ result,
+ receipt_id="VFY-4-EVIDENCE-TEST",
+ claim_id="claim:fixture",
+ binding=binding,
+ prerequisite_evidence=prerequisites,
+ rung_evidence=rungs,
+ session=session,
+ )
+
+ root = Path(__file__).resolve().parents[1]
+ schema = json.loads((root / "receipts/schema/vstd4_receipt.json").read_text())
+ certificate_schema = json.loads(
+ (root / "receipts/schema/vstd4_certificate.json").read_text()
+ )
+ registry = Registry().with_resource(
+ certificate_schema["$id"], Resource.from_contents(certificate_schema)
+ )
+ Draft202012Validator(schema, registry=registry).validate(receipt)
+ rechecked = recheck_evidence_bound_vstd4_receipt(
+ receipt, mechanisms=(ExactFactMechanism(),)
+ )
+ assert rechecked.claim_id == "claim:fixture"
+ assert rechecked.conformance_status == "ESTABLISHED"
+
+ reference = next(iter(receipt["evidence_payloads"]))
+ receipt["evidence_payloads"][reference] = "bm90LXRoZS1ldmlkZW5jZQ=="
+ with pytest.raises(EvidenceBindingError, match="does not match"):
+ recheck_evidence_bound_vstd4_receipt(
+ receipt, mechanisms=(ExactFactMechanism(),)
+ )
+
+
+def test_graph_profile_can_be_established_from_mechanism_evaluated_ratings() -> None:
+ graph = _graph()
+ store, session = _session()
+ binding = _binding()
+ collection_parameters = {
+ "collection_id": "collection:fixture",
+ "collection_binding_digest": graph_collection_binding_digest(
+ graph,
+ collection_id="collection:fixture",
+ members=("result",),
+ binding=binding,
+ ),
+ }
+ objects = {
+ subject: _proposition(
+ store,
+ subject,
+ "vstd.object_profile",
+ 5,
+ parameters=collection_parameters,
+ )
+ for subject in graph.artifacts
+ }
+ edges = {
+ subject: _proposition(
+ store,
+ subject,
+ "vstd.graph_edge_profile",
+ 5,
+ parameters=collection_parameters,
+ )
+ for subject in graph.transformations
+ }
+ result = establish_graph_level(
+ graph,
+ collection_id="collection:fixture",
+ members=("result",),
+ object_evidence=objects,
+ edge_evidence=edges,
+ session=session,
+ binding=binding,
+ )
+ assert result.level == 5
+ assert result.rating_basis == "MECHANISM_EVALUATED"
+ assert result.conformance_status == "ESTABLISHED"
+
+ caller_only = graph_level(
+ graph,
+ GraphCollection(
+ "collection:fixture", ("result",),
+ {item: 5 for item in graph.artifacts},
+ {item: 5 for item in graph.transformations},
+ ),
+ binding=_binding(),
+ )
+ assert caller_only.conformance_status == "NOT_ESTABLISHED"
+
+ record = build_evidence_bound_graph_level_record(
+ result,
+ graph=graph,
+ members=("result",),
+ binding=binding,
+ object_evidence=objects,
+ edge_evidence=edges,
+ session=session,
+ )
+ root = Path(__file__).resolve().parents[1]
+ graph_schema = json.loads(
+ (root / "receipts/schema/vstd_graph_receipt.json").read_text()
+ )
+ vstd4_schema = json.loads((root / "receipts/schema/vstd4_receipt.json").read_text())
+ assurance_schema = json.loads(
+ (root / "standard/schemas/vstd-graph-assurance-1.schema.json").read_text()
+ )
+ registry = Registry()
+ registry = registry.with_resource(vstd4_schema["$id"], Resource.from_contents(vstd4_schema))
+ registry = registry.with_resource(
+ assurance_schema["$id"], Resource.from_contents(assurance_schema)
+ )
+ Draft202012Validator(
+ graph_schema["properties"]["computed_graph_level"], registry=registry
+ ).validate(record)
+ invalid_zero = copy.deepcopy(record)
+ invalid_zero["level"] = 0
+ assert list(
+ Draft202012Validator(
+ graph_schema["properties"]["computed_graph_level"], registry=registry
+ ).iter_errors(invalid_zero)
+ )
+ rechecked = recheck_evidence_bound_graph_level_record(
+ graph, record, mechanisms=(ExactFactMechanism(),)
+ )
+ assert rechecked.conformance_status == "ESTABLISHED"
+
+
+def test_evidence_bound_graph_refuses_frozen_cross_kind_identifier_overlap() -> None:
+ graph = _graph()
+ graph.transformations["source"] = replace(
+ graph.transformations["first"], transformation_id="source"
+ )
+ with pytest.raises(GraphEncodingError, match="globally disjoint"):
+ establish_graph_level(
+ graph,
+ collection_id="collection:fixture",
+ members=("result",),
+ object_evidence={},
+ edge_evidence={},
+ session=_session()[1],
+ binding=_binding(),
+ )
+
+
+def test_graph_ratings_are_bound_to_exact_collection_and_integer_type() -> None:
+ graph = _graph()
+ store, session = _session()
+ binding = _binding()
+ objects, edges = _graph_rating_evidence(graph, store, binding)
+
+ neighboring = dict(objects)
+ neighboring["source"] = _proposition(
+ store,
+ "source",
+ "vstd.object_profile",
+ 5,
+ parameters={
+ "collection_id": "collection:fixture",
+ "collection_binding_digest": "0" * 64,
+ },
+ )
+ result = establish_graph_level(
+ graph,
+ collection_id="collection:fixture",
+ members=("result",),
+ object_evidence=neighboring,
+ edge_evidence=edges,
+ session=session,
+ binding=binding,
+ )
+ assert result.level == 0
+ assert result.conformance_status == "NOT_ESTABLISHED"
+ assert any("not exactly collection-bound" in item for item in result.binding_errors)
+
+ boolean_rating = dict(objects)
+ boolean_rating["source"] = _proposition(
+ store,
+ "source",
+ "vstd.object_profile",
+ True,
+ parameters=objects["source"].parameters,
+ )
+ result = establish_graph_level(
+ graph,
+ collection_id="collection:fixture",
+ members=("result",),
+ object_evidence=boolean_rating,
+ edge_evidence=edges,
+ session=session,
+ binding=binding,
+ )
+ assert result.level == 0
+ assert result.conformance_status == "NOT_ESTABLISHED"
+ assert any("is not an integer" in item for item in result.binding_errors)
+
+
+def test_profile_zero_never_becomes_established_conformance() -> None:
+ graph = _graph()
+ graph.artifacts["source"] = ArtifactNode(
+ "source",
+ "source",
+ ArtifactType.MODEL,
+ hashlib.sha256(b"source").hexdigest(),
+ status=ArtifactStatus.CHALLENGED,
+ )
+ store, session = _session()
+ binding = _binding()
+ objects, edges = _graph_rating_evidence(graph, store, binding)
+ result = establish_graph_level(
+ graph,
+ collection_id="collection:fixture",
+ members=("result",),
+ object_evidence=objects,
+ edge_evidence=edges,
+ session=session,
+ binding=binding,
+ )
+ assert result.level == 0
+ assert result.conformance_status == "NOT_ESTABLISHED"
+
+
+def test_challenge_projection_changes_current_admissibility_not_history() -> None:
+ graph = _graph()
+ ledger = AssuranceLedger(graph)
+ challenges = ChallengeLedger()
+ surface = surface_from_types(
+ ClaimCoordinate("source", "digest"),
+ (RefutationType.EVIDENCE_HASH_MISMATCH,),
+ overturning_evidence="a mismatching digest",
+ )
+ challenges.file(
+ Challenge(
+ "challenge:1",
+ "source",
+ "certificate:1",
+ "digest",
+ RefutationType.EVIDENCE_HASH_MISMATCH,
+ "sha256:mismatch",
+ "2026-08-29T00:00:00Z",
+ ),
+ surface,
+ )
+ events = ledger.project_challenges(challenges, recorded_at="2026-08-29T00:01:00Z")
+ assert events[0].kind is AssuranceEventKind.STATUS_PROJECTION
+ assert ledger.current_status("source") is ArtifactStatus.CHALLENGED
+ assert graph.artifacts["source"].status is ArtifactStatus.VALID
+ assert ledger.materialize_current_graph().artifacts["source"].status is ArtifactStatus.CHALLENGED
+
+
+def test_upstream_challenge_invalidates_current_trust_and_graph_admission() -> None:
+ graph = _graph()
+ ledger = AssuranceLedger(graph)
+ store, session = _session()
+ first_binding = _trust_proposition(store, ledger, "first", "middle")
+ first = ledger.record_trust(
+ "middle",
+ ("source",),
+ first_binding,
+ transformation_id="first",
+ session=session,
+ recorded_at="2026-08-29T00:00:00Z",
+ )
+ second_binding = _trust_proposition(
+ store, ledger, "second", "result", (first.digest(),)
+ )
+ ledger.record_trust(
+ "result",
+ ("middle",),
+ second_binding,
+ transformation_id="second",
+ prerequisite_trust_event_digests=(first.digest(),),
+ session=session,
+ recorded_at="2026-08-29T00:00:01Z",
+ )
+ assert len(ledger.current_trust_events()) == 2
+ assert ledger.impacted_descendants("source") == ("middle", "result")
+
+ challenges = ChallengeLedger()
+ surface = surface_from_types(
+ ClaimCoordinate("source", "digest"),
+ (RefutationType.EVIDENCE_HASH_MISMATCH,),
+ overturning_evidence="a mismatching digest",
+ )
+ challenges.file(
+ Challenge(
+ "challenge:impact",
+ "source",
+ "certificate:1",
+ "digest",
+ RefutationType.EVIDENCE_HASH_MISMATCH,
+ "sha256:mismatch",
+ "2026-08-29T00:01:00Z",
+ ),
+ surface,
+ )
+ ledger.project_challenges(challenges, recorded_at="2026-08-29T00:02:00Z")
+ assert ledger.current_trust_events() == ()
+ current = ledger.materialize_current_graph()
+ candidate = graph_level(
+ current,
+ GraphCollection(
+ "collection:fixture",
+ ("result",),
+ {item: 5 for item in current.artifacts},
+ {item: 5 for item in current.transformations},
+ ),
+ binding=_binding(),
+ )
+ assert candidate.level == 0
+
+
+def test_challenge_recovery_does_not_undo_independent_rot() -> None:
+ graph = _graph()
+ ledger = AssuranceLedger(graph)
+ store, session = _session()
+ rot = _proposition(
+ store,
+ "source",
+ "vstd.graph.current_status",
+ ArtifactStatus.STALE.value,
+ )
+ ledger.record_rot(
+ "source",
+ ArtifactStatus.STALE,
+ rot,
+ session=session,
+ recorded_at="2026-08-29T00:00:00Z",
+ )
+ challenges = ChallengeLedger()
+ surface = surface_from_types(
+ ClaimCoordinate("source", "digest"),
+ (RefutationType.EVIDENCE_HASH_MISMATCH,),
+ overturning_evidence="a mismatching digest",
+ )
+ challenges.file(
+ Challenge(
+ "challenge:recovered",
+ "source",
+ "certificate:1",
+ "digest",
+ RefutationType.EVIDENCE_HASH_MISMATCH,
+ "sha256:mismatch",
+ "2026-08-29T00:01:00Z",
+ ),
+ surface,
+ )
+ challenges.adjudicate(
+ Adjudication(
+ "challenge:recovered",
+ ChallengeOutcome.REJECTED,
+ "counterevidence disproven",
+ "2026-08-29T00:02:00Z",
+ )
+ )
+ ledger.project_challenges(challenges, recorded_at="2026-08-29T00:03:00Z")
+ assert ledger.current_status("source") is ArtifactStatus.STALE
+ with pytest.raises(AssuranceFlowError, match="strictly degrade"):
+ ledger.record_rot(
+ "source",
+ ArtifactStatus.STALE,
+ rot,
+ session=session,
+ recorded_at="2026-08-29T00:04:00Z",
+ )
+
+
+def test_non_status_conflict_resolution_remains_admissibility_blocking() -> None:
+ graph = _graph()
+ graph.add_conflict(
+ ConflictRecord(
+ "conflict:digest",
+ "source",
+ "content_digest",
+ ("sha256:a", "sha256:b"),
+ ("receipt:a", "receipt:b"),
+ )
+ )
+ ledger = AssuranceLedger(graph)
+ store, session = _session()
+ resolution = _proposition(
+ store,
+ "source",
+ "vstd.graph.resolve.content_digest",
+ "sha256:a",
+ parameters={"conflict_id": "conflict:digest"},
+ )
+ ledger.resolve_conflict(
+ "conflict:digest",
+ "sha256:a",
+ resolution,
+ session=session,
+ recorded_at="2026-08-29T00:00:00Z",
+ )
+ assert "conflict:digest" in graph.conflicts
+ assert ledger.unresolved_conflicts() == ()
+ assert tuple(
+ item.conflict_id for item in ledger.admissibility_blocking_conflicts()
+ ) == ("conflict:digest",)
+ assert "conflict:digest" in ledger.materialize_current_graph().conflicts
+
+
+def test_trust_rot_and_rust_follow_direction_without_recursive_amplification() -> None:
+ graph = _graph()
+ ledger = AssuranceLedger(graph)
+ store, session = _session()
+
+ trust = _trust_proposition(store, ledger, "first", "middle")
+ first = ledger.record_trust(
+ "middle", ("source", "source"), trust,
+ transformation_id="first",
+ session=session, recorded_at="2026-08-29T00:00:00Z",
+ )
+ second = ledger.record_trust(
+ "middle", ("source",), trust,
+ transformation_id="first",
+ session=session, recorded_at="2026-08-29T00:00:00Z",
+ )
+ assert first.digest() == second.digest()
+ assert len(ledger.events()) == 1
+
+ rust = _proposition(store, "result", "vstd.graph.descendant_deviation", True)
+ ledger.record_rust(
+ "result", rust, session=session, recorded_at="2026-08-29T00:01:00Z"
+ )
+ ledger.record_rust(
+ "result", rust, session=session, recorded_at="2026-08-29T00:01:00Z"
+ )
+ concentration = {item.ancestor_id: item for item in ledger.rust_concentration()}
+ assert concentration["source"].count == 1
+
+ rot = _proposition(
+ store, "source", "vstd.graph.current_status", ArtifactStatus.REVOKED.value
+ )
+ ledger.record_rot(
+ "source", ArtifactStatus.REVOKED, rot,
+ session=session, recorded_at="2026-08-29T00:02:00Z",
+ )
+ assert ledger.current_status("source") is ArtifactStatus.REVOKED
+ with pytest.raises(AssuranceFlowError, match="inadmissible target or transformation input"):
+ ledger.record_trust(
+ "middle", ("source",), trust,
+ transformation_id="first",
+ session=session, recorded_at="2026-08-29T00:03:00Z",
+ )
+ assert ledger.verify_hash_chain() is True
+
+
+def test_trust_cannot_jump_over_an_unbound_transformation() -> None:
+ ledger = AssuranceLedger(_graph())
+ store, session = _session()
+ direct = _proposition(
+ store,
+ "result",
+ "vstd.graph.support",
+ {
+ "historical_graph_digest": ledger.graph_digest,
+ "inputs": ["source"],
+ "output": "result",
+ "prerequisite_trust_event_digests": [],
+ "transformation_id": "second",
+ },
+ )
+ with pytest.raises(AssuranceFlowError, match="exact transformation input set"):
+ ledger.record_trust(
+ "result",
+ ("source",),
+ direct,
+ transformation_id="second",
+ session=session,
+ recorded_at="2026-08-29T00:00:00Z",
+ )
+
+ missing_prerequisite = _trust_proposition(
+ store, ledger, "second", "result"
+ )
+ with pytest.raises(AssuranceFlowError, match="exactly one current prerequisite"):
+ ledger.record_trust(
+ "result",
+ ("middle",),
+ missing_prerequisite,
+ transformation_id="second",
+ session=session,
+ recorded_at="2026-08-29T00:00:01Z",
+ )
+
+
+@pytest.mark.parametrize(
+ "status",
+ (
+ ArtifactStatus.CHALLENGED,
+ ArtifactStatus.STALE,
+ ArtifactStatus.REVOKED,
+ ArtifactStatus.SUPERSEDED,
+ ),
+)
+def test_intermediate_degradation_excludes_but_does_not_rewrite_trust(
+ status: ArtifactStatus,
+) -> None:
+ ledger = AssuranceLedger(_graph())
+ store, session = _session()
+ first, second = _record_trust_chain(ledger, store, session)
+ historical_digests = (first.digest(), second.digest())
+ rot = _proposition(
+ store, "middle", "vstd.graph.current_status", status.value
+ )
+ ledger.record_rot(
+ "middle",
+ status,
+ rot,
+ session=session,
+ recorded_at="2026-08-29T00:01:00Z",
+ )
+
+ assert ledger.current_trust_events() == ()
+ assert tuple(event.digest() for event in ledger.events()[:2]) == historical_digests
+ assert all(event.outcome is MechanismOutcome.PASS for event in ledger.events()[:2])
+
+
+@pytest.mark.parametrize("conflict_subject", ("middle", "first"))
+def test_non_status_resolution_does_not_manufacture_current_trust(
+ conflict_subject: str,
+) -> None:
+ ledger = AssuranceLedger(_graph())
+ store, session = _session()
+ first, second = _record_trust_chain(ledger, store, session)
+ assert tuple(event.digest() for event in ledger.current_trust_events()) == (
+ first.digest(),
+ second.digest(),
+ )
+
+ conflict = ConflictRecord(
+ f"conflict:{conflict_subject}",
+ conflict_subject,
+ "current_dependency_state",
+ ("candidate:a", "candidate:b"),
+ ("evidence:a", "evidence:b"),
+ )
+ conflict_binding = _proposition(
+ store,
+ conflict_subject,
+ "vstd.graph.conflict",
+ conflict.to_dict(),
+ )
+ ledger.record_conflict(
+ conflict,
+ conflict_binding,
+ session=session,
+ recorded_at="2026-08-29T00:01:00Z",
+ )
+ assert ledger.current_trust_events() == ()
+ assert len(ledger.events()) == 3
+
+ resolution = _proposition(
+ store,
+ conflict_subject,
+ "vstd.graph.resolve.current_dependency_state",
+ "candidate:a",
+ parameters={"conflict_id": conflict.conflict_id},
+ )
+ ledger.resolve_conflict(
+ conflict.conflict_id,
+ "candidate:a",
+ resolution,
+ session=session,
+ recorded_at="2026-08-29T00:02:00Z",
+ )
+ assert ledger.current_trust_events() == ()
+ assert conflict.conflict_id in ledger.materialize_current_graph().conflicts
+ with pytest.raises(AssuranceFlowError, match="admissible current-state consequence"):
+ ledger.record_trust(
+ "middle",
+ ("source",),
+ _trust_proposition(store, ledger, "first", "middle"),
+ transformation_id="first",
+ session=session,
+ recorded_at="2026-08-29T00:02:01Z",
+ )
+ root = Path(__file__).resolve().parents[1]
+ schema = json.loads(
+ (root / "standard/schemas/vstd-graph-assurance-1.schema.json").read_text()
+ )
+ graph_schema = json.loads(
+ (root / "receipts/schema/vstd_graph_receipt.json").read_text()
+ )
+ registry = Registry().with_resource(
+ graph_schema["$id"], Resource.from_contents(graph_schema)
+ )
+ Draft202012Validator(schema, registry=registry).validate(ledger.to_dict())
+ replayed = recheck_assurance_log(
+ ledger.to_dict(), mechanisms=(ExactFactMechanism(),)
+ )
+ assert replayed.current_trust_events() == ()
+ assert conflict.conflict_id in replayed.materialize_current_graph().conflicts
+
+
+@pytest.mark.parametrize(
+ ("conflict_subject", "selected_value", "expected_current"),
+ (
+ ("middle", "VALID", True),
+ ("middle", "REVOKED", False),
+ ("first", "COMPLETED", True),
+ ("first", "FAILED", False),
+ ),
+)
+def test_status_resolution_projects_current_admissibility_and_replays(
+ conflict_subject: str,
+ selected_value: str,
+ expected_current: bool,
+) -> None:
+ ledger = AssuranceLedger(_graph())
+ store, session = _session()
+ first, second = _record_trust_chain(ledger, store, session)
+ competing = (
+ ("VALID", "REVOKED")
+ if conflict_subject == "middle"
+ else ("COMPLETED", "FAILED")
+ )
+ conflict = ConflictRecord(
+ f"conflict:{conflict_subject}-status",
+ conflict_subject,
+ "status",
+ competing,
+ ("evidence:admissible", "evidence:inadmissible"),
+ )
+ ledger.record_conflict(
+ conflict,
+ _proposition(store, conflict_subject, "vstd.graph.conflict", conflict.to_dict()),
+ session=session,
+ recorded_at="2026-08-29T00:01:00Z",
+ )
+ assert ledger.current_trust_events() == ()
+ ledger.resolve_conflict(
+ conflict.conflict_id,
+ selected_value,
+ _proposition(
+ store,
+ conflict_subject,
+ "vstd.graph.resolve.status",
+ selected_value,
+ parameters={"conflict_id": conflict.conflict_id},
+ ),
+ session=session,
+ recorded_at="2026-08-29T00:02:00Z",
+ )
+
+ expected_digests = (first.digest(), second.digest()) if expected_current else ()
+ assert tuple(event.digest() for event in ledger.current_trust_events()) == expected_digests
+ if conflict_subject == "middle":
+ assert ledger.current_status("middle").value == selected_value
+ else:
+ assert ledger.current_transformation_status("first") == selected_value
+ assert conflict.conflict_id not in ledger.materialize_current_graph().conflicts
+
+ serialized = ledger.to_dict()
+ assert any(
+ event["kind"] == AssuranceEventKind.CONFLICT_DECLARATION.value
+ and event["attributes"]["conflict"]["conflict_id"] == conflict.conflict_id
+ for event in serialized["events"]
+ )
+ assert serialized["conflict_resolutions"][0]["conflict_id"] == conflict.conflict_id
+ replayed = recheck_assurance_log(
+ serialized, mechanisms=(ExactFactMechanism(),)
+ )
+ assert tuple(event.digest() for event in replayed.current_trust_events()) == expected_digests
+ if conflict_subject == "middle":
+ assert replayed.current_status("middle").value == selected_value
+ else:
+ assert replayed.current_transformation_status("first") == selected_value
+
+
+def test_rust_requires_separate_localization_before_blame_or_guilt() -> None:
+ graph = _graph()
+ ledger = AssuranceLedger(graph)
+ store, session = _session()
+ rust = _proposition(store, "result", "vstd.graph.descendant_deviation", True)
+ rust_event = ledger.record_rust(
+ "result", rust, session=session, recorded_at="2026-08-29T00:00:00Z"
+ )
+ refused = ledger.diagnose(
+ DiagnosticKind.BLAME,
+ "source",
+ "result",
+ None,
+ session=session,
+ recorded_at="2026-08-29T00:01:00Z",
+ )
+ assert refused.status == "NOT_ESTABLISHED"
+
+ localization = _proposition(
+ store,
+ "result",
+ "vstd.graph.causal_localization",
+ {
+ "ancestor": "source",
+ "descendant": "result",
+ "rust_event_digest": rust_event.digest(),
+ "deviation_binding_digest": rust.digest(),
+ },
+ )
+ event = ledger.localize_cause(
+ "source", "result", localization,
+ rust_event_digest=rust_event.digest(),
+ session=session, recorded_at="2026-08-29T00:02:00Z",
+ )
+ attribution = _proposition(
+ store,
+ "source",
+ "vstd.graph.diagnostic.blame",
+ {
+ "ancestor": "source",
+ "descendant": "result",
+ "localization_event_digest": event.digest(),
+ },
+ )
+ result = ledger.diagnose(
+ DiagnosticKind.BLAME,
+ "source",
+ "result",
+ attribution,
+ session=session,
+ recorded_at="2026-08-29T00:03:00Z",
+ )
+ assert result.status == "ESTABLISHED"
+ assert "actor" not in result.details.lower()
+
+ opaque_guilt = _proposition(
+ store,
+ "source",
+ "vstd.graph.diagnostic.guilt",
+ {
+ "ancestor": "source",
+ "descendant": "result",
+ "localization_event_digest": event.digest(),
+ "violated_obligation": "obligation:preserve-result-integrity",
+ },
+ parameters={"obligation": "obligation:preserve-result-integrity"},
+ )
+ guilt_result = ledger.diagnose(
+ DiagnosticKind.GUILT,
+ "source",
+ "result",
+ opaque_guilt,
+ session=session,
+ recorded_at="2026-08-29T00:04:00Z",
+ )
+ assert guilt_result.status == "NOT_ESTABLISHED"
+ assert guilt_result.evaluation is None
+
+
+def test_localization_selects_one_exact_passing_deviation() -> None:
+ ledger = AssuranceLedger(_graph())
+ store, session = _session()
+ first_deviation = _proposition(
+ store,
+ "result",
+ "vstd.graph.descendant_deviation",
+ True,
+ parameters={"deviation_id": "D1"},
+ )
+ second_deviation = _proposition(
+ store,
+ "result",
+ "vstd.graph.descendant_deviation",
+ True,
+ parameters={"deviation_id": "D2"},
+ )
+ first_rust = ledger.record_rust(
+ "result",
+ first_deviation,
+ session=session,
+ recorded_at="2026-08-29T00:00:00Z",
+ )
+ second_rust = ledger.record_rust(
+ "result",
+ second_deviation,
+ session=session,
+ recorded_at="2026-08-29T00:01:00Z",
+ )
+
+ with pytest.raises(AssuranceFlowError, match="exact passing RUST event"):
+ ledger.localize_cause(
+ "source",
+ "result",
+ _proposition(
+ store,
+ "result",
+ "vstd.graph.causal_localization",
+ {
+ "ancestor": "source",
+ "descendant": "result",
+ "rust_event_digest": "0" * 64,
+ "deviation_binding_digest": first_deviation.digest(),
+ },
+ ),
+ rust_event_digest="0" * 64,
+ session=session,
+ recorded_at="2026-08-29T00:02:00Z",
+ )
+
+ neighboring_rust = ledger.record_rust(
+ "middle",
+ _proposition(
+ store,
+ "middle",
+ "vstd.graph.descendant_deviation",
+ True,
+ parameters={"deviation_id": "neighbor"},
+ ),
+ session=session,
+ recorded_at="2026-08-29T00:03:00Z",
+ )
+ with pytest.raises(AssuranceFlowError, match="exact passing RUST event"):
+ ledger.localize_cause(
+ "source",
+ "result",
+ _proposition(
+ store,
+ "result",
+ "vstd.graph.causal_localization",
+ {
+ "ancestor": "source",
+ "descendant": "result",
+ "rust_event_digest": neighboring_rust.digest(),
+ "deviation_binding_digest": str(
+ neighboring_rust.attributes["binding_digest"]
+ ),
+ },
+ ),
+ rust_event_digest=neighboring_rust.digest(),
+ session=session,
+ recorded_at="2026-08-29T00:04:00Z",
+ )
+
+ failed_reference = store.add(
+ json.dumps(
+ {
+ "subject_id": "result",
+ "predicate": "vstd.graph.descendant_deviation",
+ "expected": False,
+ },
+ sort_keys=True,
+ separators=(",", ":"),
+ ).encode()
+ )
+ failed_deviation = replace(first_deviation, evidence_refs=(failed_reference,))
+ failed_rust = ledger.record_rust(
+ "result",
+ failed_deviation,
+ session=session,
+ recorded_at="2026-08-29T00:05:00Z",
+ )
+ assert failed_rust.outcome is MechanismOutcome.FAIL
+ with pytest.raises(AssuranceFlowError, match="exact passing RUST event"):
+ ledger.localize_cause(
+ "source",
+ "result",
+ _proposition(
+ store,
+ "result",
+ "vstd.graph.causal_localization",
+ {
+ "ancestor": "source",
+ "descendant": "result",
+ "rust_event_digest": failed_rust.digest(),
+ "deviation_binding_digest": failed_deviation.digest(),
+ },
+ ),
+ rust_event_digest=failed_rust.digest(),
+ session=session,
+ recorded_at="2026-08-29T00:06:00Z",
+ )
+
+ missing_ancestor_ledger = AssuranceLedger(_graph())
+ missing_ancestor_store, missing_ancestor_session = _session()
+ complete_rust = missing_ancestor_ledger.record_rust(
+ "result",
+ _proposition(
+ missing_ancestor_store,
+ "result",
+ "vstd.graph.descendant_deviation",
+ True,
+ ),
+ session=missing_ancestor_session,
+ recorded_at="2026-08-29T00:06:01Z",
+ )
+ missing_ancestor_event = replace(complete_rust, source_ids=("middle",))
+ missing_ancestor_ledger._events[0] = missing_ancestor_event
+ with pytest.raises(AssuranceFlowError, match="exact passing RUST event"):
+ missing_ancestor_ledger.localize_cause(
+ "source",
+ "result",
+ _proposition(
+ missing_ancestor_store,
+ "result",
+ "vstd.graph.causal_localization",
+ {
+ "ancestor": "source",
+ "descendant": "result",
+ "rust_event_digest": missing_ancestor_event.digest(),
+ "deviation_binding_digest": str(
+ missing_ancestor_event.attributes["binding_digest"]
+ ),
+ },
+ ),
+ rust_event_digest=missing_ancestor_event.digest(),
+ session=missing_ancestor_session,
+ recorded_at="2026-08-29T00:06:02Z",
+ )
+
+ localization = ledger.localize_cause(
+ "source",
+ "result",
+ _proposition(
+ store,
+ "result",
+ "vstd.graph.causal_localization",
+ {
+ "ancestor": "source",
+ "descendant": "result",
+ "rust_event_digest": first_rust.digest(),
+ "deviation_binding_digest": first_deviation.digest(),
+ },
+ ),
+ rust_event_digest=first_rust.digest(),
+ session=session,
+ recorded_at="2026-08-29T00:07:00Z",
+ )
+ assert localization.attributes["rust_event_digest"] == first_rust.digest()
+ assert localization.attributes["deviation_binding_digest"] == first_deviation.digest()
+ assert second_rust.digest() not in json.dumps(localization.to_dict())
+
+ blame = _proposition(
+ store,
+ "source",
+ "vstd.graph.diagnostic.blame",
+ {
+ "ancestor": "source",
+ "descendant": "result",
+ "localization_event_digest": localization.digest(),
+ },
+ )
+ assert ledger.diagnose(
+ DiagnosticKind.BLAME,
+ "source",
+ "result",
+ blame,
+ session=session,
+ recorded_at="2026-08-29T00:08:00Z",
+ ).status == "ESTABLISHED"
+
+ tampered = copy.deepcopy(ledger.to_dict())
+ localization_record = next(
+ event
+ for event in tampered["events"]
+ if event["kind"] == AssuranceEventKind.CAUSAL_LOCALIZATION.value
+ )
+ localization_record["attributes"]["rust_event_digest"] = second_rust.digest()
+ with pytest.raises(AssuranceFlowError):
+ recheck_assurance_log(tampered, mechanisms=(ExactFactMechanism(),))
+
+
+def test_assurance_event_log_is_portable_strict_and_evidence_complete() -> None:
+ graph = _graph()
+ ledger = AssuranceLedger(graph)
+ store, session = _session()
+ deviation = _proposition(store, "result", "vstd.graph.descendant_deviation", True)
+ ledger.record_rust(
+ "result",
+ deviation,
+ session=session,
+ recorded_at="2026-08-29T00:00:00Z",
+ )
+ payload = ledger.to_dict()
+ assert payload["historical_graph"] == graph.to_dict()
+ assert set(payload["events"][0]["evidence_payloads"]) == set(
+ payload["events"][0]["evidence_refs"]
+ )
+
+ root = Path(__file__).resolve().parents[1]
+ schema = json.loads(
+ (root / "standard/schemas/vstd-graph-assurance-1.schema.json").read_text()
+ )
+ graph_schema = json.loads(
+ (root / "receipts/schema/vstd_graph_receipt.json").read_text()
+ )
+ registry = Registry().with_resource(
+ graph_schema["$id"], Resource.from_contents(graph_schema)
+ )
+ Draft202012Validator(schema, registry=registry).validate(payload)
+
+ restored = EvidenceStore()
+ restored.import_base64(payload["events"][0]["evidence_payloads"])
+ assert deviation.evidence_refs[0] in restored
+
+ rechecked = recheck_assurance_log(
+ payload,
+ mechanisms=(ExactFactMechanism(),),
+ )
+ assert rechecked.to_dict() == payload
+
+ tampered = copy.deepcopy(payload)
+ tampered["events"][0]["details"] = "caller-rewritten outcome"
+ with pytest.raises(AssuranceFlowError, match="does not match"):
+ recheck_assurance_log(tampered, mechanisms=(ExactFactMechanism(),))
+
+
+def test_assurance_replay_recomputes_challenge_projection() -> None:
+ graph = _graph()
+ ledger = AssuranceLedger(graph)
+ challenges = ChallengeLedger()
+ surface = surface_from_types(
+ ClaimCoordinate("source", "digest"),
+ (RefutationType.EVIDENCE_HASH_MISMATCH,),
+ overturning_evidence="a mismatching digest",
+ )
+ challenges.file(
+ Challenge(
+ "challenge:replay",
+ "source",
+ "certificate:1",
+ "digest",
+ RefutationType.EVIDENCE_HASH_MISMATCH,
+ "sha256:mismatch",
+ "2026-08-29T00:00:00Z",
+ ),
+ surface,
+ )
+ ledger.project_challenges(challenges, recorded_at="2026-08-29T00:01:00Z")
+ replayed = recheck_assurance_log(ledger.to_dict(), mechanisms=())
+ assert replayed.current_status("source") is ArtifactStatus.CHALLENGED
+
+ with pytest.raises(AssuranceFlowError, match="cannot be replaced"):
+ recheck_assurance_log(
+ ledger.to_dict(), mechanisms=(ChallengeProjectionMechanism(),)
+ )
+
+
+def test_assurance_ledger_refuses_recursive_cycle() -> None:
+ graph = _graph()
+ graph.add_transformation(
+ TransformationHyperedge(
+ "cycle",
+ "cycle",
+ TransformationType.EVALUATION,
+ (HyperedgePort("result", "INPUT"),),
+ (HyperedgePort("source", "OUTPUT"),),
+ {},
+ {},
+ {},
+ )
+ )
+ with pytest.raises(AssuranceFlowError, match="cyclic provenance"):
+ AssuranceLedger(graph)
+
+
+def test_vstd5_requires_every_independence_seam_and_preserves_disagreement() -> None:
+ store, session = _session()
+ entry = _established_vstd4(store, session)
+ binding_digest = entry.witness.header.binding # type: ignore[union-attr]
+ witness = WitnessIdentity("witness:one", store.add(b"identity coordinate"))
+ relation = "declarant:one->witness:one"
+ relationships = {
+ dimension: RelationshipState.SEPARATE for dimension in IndependenceDimension
+ }
+ independence = IndependenceAssertion(
+ witness.witness_id,
+ relationships,
+ {
+ dimension: _proposition(
+ store,
+ relation,
+ f"vstd5.shared.{dimension.value}",
+ False,
+ parameters={"claim_binding_digest": binding_digest},
+ )
+ for dimension in IndependenceDimension
+ },
+ )
+
+ def corroboration(record_id, outcome, observation):
+ certificate_digest = entry.witness.digest() # type: ignore[union-attr]
+ expected = {
+ "claim_binding_digest": binding_digest,
+ "vstd4_certificate_digest": certificate_digest,
+ "checker_descriptor_digest": "b" * 64,
+ "corroboration_class": "TEST",
+ "result": outcome.value,
+ }
+ verification = _proposition(
+ store,
+ "claim:fixture",
+ "vstd5.corroboration",
+ expected,
+ parameters={
+ "witness_id": witness.witness_id,
+ "observed_at": "2026-08-29T00:00:00Z",
+ },
+ )
+ # The observation is the exact fact the mechanism reruns.
+ return CorroborationRecord(
+ record_id,
+ witness.witness_id,
+ binding_digest,
+ certificate_digest,
+ "b" * 64,
+ verification.evidence_refs,
+ outcome,
+ "2026-08-29T00:00:00Z",
+ verification,
+ observation,
+ )
+
+ yes = corroboration("corroboration:yes", CorroborationOutcome.CORROBORATED, "TEST")
+ no = corroboration("corroboration:no", CorroborationOutcome.REFUTED, "TEST")
+ bundle = WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ binding_digest,
+ (witness,),
+ (independence,),
+ (yes, no),
+ )
+ result = assess_witness_corroboration(entry, bundle, session=session)
+ assert result.conformance_status == "ESTABLISHED"
+ assert result.computed_independence == "INDEPENDENT"
+ assert result.status is WitnessResultStatus.CONFLICTED
+ assert result.disagreements == (("corroboration:no", "corroboration:yes"),)
+
+ receipt = build_vstd5_receipt(
+ entry,
+ bundle,
+ result,
+ receipt_id="VFY-5-EVIDENCE-TEST",
+ session=session,
+ )
+ validator = _vstd5_schema_validator()
+ validator.validate(receipt)
+ false_positive = copy.deepcopy(receipt)
+ false_positive["result"]["status"] = "CORROBORATED"
+ false_positive["result"]["conformance_status"] = "NOT_ESTABLISHED"
+ false_positive["result"]["computed_independence"] = "UNKNOWN"
+ assert list(
+ validator.iter_errors(false_positive)
+ )
+ false_independence = copy.deepcopy(receipt)
+ false_independence["result"]["conformance_status"] = "NOT_ESTABLISHED"
+ false_independence["result"]["identity_errors"] = [
+ "duplicate witness identifier: witness:one"
+ ]
+ false_independence["result"]["errors"] = list(
+ false_independence["result"]["identity_errors"]
+ )
+ assert list(
+ validator.iter_errors(false_independence)
+ )
+ rechecked = recheck_vstd5_receipt(
+ entry, receipt, mechanisms=(ExactFactMechanism(),)
+ )
+ assert rechecked.status is WitnessResultStatus.CONFLICTED
+
+ uncertain = IndependenceAssertion(
+ witness.witness_id,
+ {**relationships, IndependenceDimension.CONTROL: RelationshipState.UNKNOWN},
+ independence.evidence,
+ )
+ result = assess_witness_corroboration(
+ entry,
+ WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ binding_digest,
+ (witness,),
+ (uncertain,),
+ (yes,),
+ ),
+ session=session,
+ )
+ assert result.conformance_status == "NOT_ESTABLISHED"
+ assert result.status is WitnessResultStatus.UNKNOWN
+ assert result.computed_independence == "UNKNOWN"
+
+
+def test_computed_independence_fails_closed_on_identity_and_assertion_errors() -> None:
+ store, session = _session()
+ entry = _established_vstd4(store, session)
+ binding_digest = entry.witness.header.binding # type: ignore[union-attr]
+ witness, assertion, corroboration = _witness_components(
+ store, entry, "witness:one"
+ )
+
+ duplicate_witness = assess_witness_corroboration(
+ entry,
+ WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ binding_digest,
+ (witness, witness),
+ (assertion,),
+ (corroboration,),
+ ),
+ session=session,
+ )
+ assert duplicate_witness.computed_independence == "UNKNOWN"
+ assert any("duplicate witness identifier" in item for item in duplicate_witness.identity_errors)
+
+ shared_identity = store.add(b"shared identity evidence")
+ first = _witness_components(
+ store, entry, "witness:first", identity_evidence_ref=shared_identity
+ )
+ second = _witness_components(
+ store, entry, "witness:second", identity_evidence_ref=shared_identity
+ )
+ repeated_identity = assess_witness_corroboration(
+ entry,
+ WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ binding_digest,
+ (first[0], second[0]),
+ (first[1], second[1]),
+ (first[2], second[2]),
+ ),
+ session=session,
+ )
+ assert repeated_identity.computed_independence == "UNKNOWN"
+ assert any("repeats another witness identity" in item for item in repeated_identity.identity_errors)
+
+ missing = _witness_components(
+ store,
+ entry,
+ "witness:missing",
+ identity_evidence_ref="sha256:" + "0" * 64,
+ )
+ missing_identity = assess_witness_corroboration(
+ entry,
+ WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ binding_digest,
+ (missing[0],),
+ (missing[1],),
+ (missing[2],),
+ ),
+ session=session,
+ )
+ assert missing_identity.computed_independence == "UNKNOWN"
+ assert any("identity evidence unavailable" in item for item in missing_identity.identity_errors)
+
+ declarant = _witness_components(
+ store, entry, "declarant:one", declarant_id="declarant:one"
+ )
+ reused_declarant = assess_witness_corroboration(
+ entry,
+ WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ binding_digest,
+ (declarant[0],),
+ (declarant[1],),
+ (declarant[2],),
+ ),
+ session=session,
+ )
+ assert reused_declarant.computed_independence == "UNKNOWN"
+ assert any("is the declarant" in item for item in reused_declarant.identity_errors)
+
+ missing_assertion = assess_witness_corroboration(
+ entry,
+ WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ binding_digest,
+ (witness,),
+ (),
+ (corroboration,),
+ ),
+ session=session,
+ )
+ assert missing_assertion.computed_independence == "UNKNOWN"
+ assert any("no independence assertion" in item for item in missing_assertion.separation_errors)
+
+ duplicate_assertion = assess_witness_corroboration(
+ entry,
+ WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ binding_digest,
+ (witness,),
+ (assertion, assertion),
+ (corroboration,),
+ ),
+ session=session,
+ )
+ assert duplicate_assertion.computed_independence == "UNKNOWN"
+ assert any("duplicate independence assertion" in item for item in duplicate_assertion.separation_errors)
+
+
+def test_vstd5_receipts_preserve_noncanonical_replay_inputs() -> None:
+ store, session = _session()
+ entry = _established_vstd4(store, session)
+ binding_digest = entry.witness.header.binding # type: ignore[union-attr]
+ witness, assertion, corroboration = _witness_components(
+ store, entry, "witness:one"
+ )
+ shared_identity = store.add(b"shared identity evidence for replay")
+ first = _witness_components(
+ store,
+ entry,
+ "witness:first",
+ identity_evidence_ref=shared_identity,
+ )
+ second = _witness_components(
+ store,
+ entry,
+ "witness:second",
+ identity_evidence_ref=shared_identity,
+ )
+ orphan = replace(assertion, witness_id="witness:orphan")
+ bundles = {
+ "duplicate-assertion": WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ binding_digest,
+ (witness,),
+ (assertion, assertion),
+ (corroboration,),
+ ),
+ "orphan-assertion": WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ binding_digest,
+ (witness,),
+ (assertion, orphan),
+ (corroboration,),
+ ),
+ "duplicate-witness": WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ binding_digest,
+ (witness, witness),
+ (assertion,),
+ (corroboration,),
+ ),
+ "missing-assertion": WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ binding_digest,
+ (witness,),
+ (),
+ (corroboration,),
+ ),
+ "reused-identity": WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ binding_digest,
+ (first[0], second[0]),
+ (first[1], second[1]),
+ (first[2], second[2]),
+ ),
+ }
+ validator = _vstd5_schema_validator()
+
+ for name, bundle in bundles.items():
+ result = assess_witness_corroboration(entry, bundle, session=session)
+ assert result.conformance_status == "NOT_ESTABLISHED", name
+ receipt = build_vstd5_receipt(
+ entry,
+ bundle,
+ result,
+ receipt_id=f"VFY-5-{name.upper()}",
+ session=session,
+ )
+ validator.validate(receipt)
+ assert len(receipt["bundle"]["independence_assertions"]) == len(
+ bundle.independence
+ )
+ rechecked = recheck_vstd5_receipt(
+ entry, receipt, mechanisms=(ExactFactMechanism(),)
+ )
+ assert rechecked.to_dict() == result.to_dict(), name
+
+
+def test_vstd5_builder_returns_only_strict_replayable_receipts() -> None:
+ store, session = _session()
+ entry = _established_vstd4(store, session)
+ binding_digest = entry.witness.header.binding # type: ignore[union-attr]
+ witness, assertion, corroboration = _witness_components(
+ store, entry, "witness:one"
+ )
+ valid = WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ binding_digest,
+ (witness,),
+ (assertion,),
+ (corroboration,),
+ )
+ result = assess_witness_corroboration(entry, valid, session=session)
+ assert result.status is WitnessResultStatus.CORROBORATED
+ receipt = build_vstd5_receipt(
+ entry, valid, result, receipt_id="VFY-5-STRICT", session=session
+ )
+ _vstd5_schema_validator().validate(receipt)
+ assert recheck_vstd5_receipt(
+ entry, receipt, mechanisms=(ExactFactMechanism(),)
+ ).to_dict() == result.to_dict()
+
+ invalid_bundles = {
+ "no-witnesses": WitnessBundle(
+ "claim:fixture", "declarant:one", binding_digest, (), (), ()
+ ),
+ "no-corroborations": replace(valid, corroborations=()),
+ "empty-claim": replace(valid, claim_id=""),
+ "empty-declarant": replace(valid, declarant_id=""),
+ "empty-witness": replace(
+ valid, witnesses=(replace(witness, witness_id=""),)
+ ),
+ }
+ for name, bundle in invalid_bundles.items():
+ result = assess_witness_corroboration(entry, bundle, session=session)
+ with pytest.raises(ValueError, match="invalid VSTD-5 receipt shape"):
+ build_vstd5_receipt(
+ entry,
+ bundle,
+ result,
+ receipt_id=f"VFY-5-{name.upper()}",
+ session=session,
+ )
+
+ for receipt_id in ("", "invalid id"):
+ with pytest.raises(ValueError, match="receipt_id"):
+ build_vstd5_receipt(
+ entry,
+ valid,
+ assess_witness_corroboration(entry, valid, session=session),
+ receipt_id=receipt_id,
+ session=session,
+ )
+
+
+def test_vstd5_claim_id_must_match_the_admitted_vstd4_claim() -> None:
+ store, session = _session()
+ entry = _established_vstd4(store, session)
+ witness, assertion, corroboration = _witness_components(
+ store, entry, "witness:one"
+ )
+ valid = WitnessBundle(
+ entry.claim_id,
+ "declarant:one",
+ entry.witness.header.binding, # type: ignore[union-attr]
+ (witness,),
+ (assertion,),
+ (corroboration,),
+ )
+ positive = assess_witness_corroboration(entry, valid, session=session)
+ assert positive.status is WitnessResultStatus.CORROBORATED
+ assert positive.conformance_status == "ESTABLISHED"
+
+ name_only = assess_witness_corroboration(
+ entry, replace(valid, claim_id="claim:neighbor"), session=session
+ )
+ assert name_only.status is WitnessResultStatus.UNKNOWN
+ assert name_only.conformance_status == "NOT_ESTABLISHED"
+ assert (
+ "witness bundle claim_id does not match the admitted VSTD-4 claim_id"
+ in name_only.binding_errors
+ )
+
+ neighbor_verification = _proposition(
+ store,
+ "claim:neighbor",
+ "vstd5.corroboration",
+ dict(corroboration.verification.expected),
+ parameters=dict(corroboration.verification.parameters),
+ )
+ neighbor_record = replace(
+ corroboration,
+ observed_evidence_refs=neighbor_verification.evidence_refs,
+ verification=neighbor_verification,
+ )
+ neighbor = replace(
+ valid,
+ claim_id="claim:neighbor",
+ corroborations=(neighbor_record,),
+ )
+ result = assess_witness_corroboration(entry, neighbor, session=session)
+ assert result.status is WitnessResultStatus.UNKNOWN
+ assert result.conformance_status == "NOT_ESTABLISHED"
+ assert result.binding_errors == (
+ "witness bundle claim_id does not match the admitted VSTD-4 claim_id",
+ )
+
+ receipt = build_vstd5_receipt(
+ entry,
+ neighbor,
+ result,
+ receipt_id="VFY-5-NEIGHBOR-CLAIM",
+ session=session,
+ )
+ _vstd5_schema_validator().validate(receipt)
+ assert recheck_vstd5_receipt(
+ entry, receipt, mechanisms=(ExactFactMechanism(),)
+ ).to_dict() == result.to_dict()
+
+ positive_receipt = build_vstd5_receipt(
+ entry,
+ valid,
+ positive,
+ receipt_id="VFY-5-CLAIM-TAMPER",
+ session=session,
+ )
+ positive_receipt["bundle"]["claim_id"] = "claim:neighbor"
+ _vstd5_schema_validator().validate(positive_receipt)
+ with pytest.raises(ValueError, match="recomputed VSTD-5 result"):
+ recheck_vstd5_receipt(
+ entry, positive_receipt, mechanisms=(ExactFactMechanism(),)
+ )
+
+
+def test_vstd5_entry_digest_binds_the_admitted_vstd4_claim_id() -> None:
+ store, session = _session()
+ entry = _established_vstd4(store, session)
+ neighbor = replace(entry, claim_id="claim:neighbor")
+ entry_digest = canonical_digest(entry.to_dict())
+ neighbor_digest = canonical_digest(neighbor.to_dict())
+ assert neighbor_digest != entry_digest
+
+ witness, assertion, corroboration = _witness_components(
+ store, entry, "witness:one"
+ )
+ bundle = WitnessBundle(
+ entry.claim_id,
+ "declarant:one",
+ entry.witness.header.binding, # type: ignore[union-attr]
+ (witness,),
+ (assertion,),
+ (corroboration,),
+ )
+ result = assess_witness_corroboration(entry, bundle, session=session)
+ receipt = build_vstd5_receipt(
+ entry,
+ bundle,
+ result,
+ receipt_id="VFY-5-ENTRY-CLAIM-DIGEST",
+ session=session,
+ )
+ assert receipt["entry_vstd4"]["result_digest"] == entry_digest
+ assert receipt["entry_vstd4"]["result_digest"] != neighbor_digest
+
+
+def test_vstd5_claim_id_is_distinct_from_the_claim_coordinate_subject() -> None:
+ store, session = _session()
+ binding = replace(
+ _binding(),
+ coordinate=ClaimCoordinate(
+ "artifact:coordinate-subject", "fixture", {"scope": "test"}
+ ),
+ )
+ entry = _established_vstd4(store, session, binding=binding)
+ witness, assertion, corroboration = _witness_components(
+ store, entry, "witness:one"
+ )
+ bundle = WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ entry.witness.header.binding, # type: ignore[union-attr]
+ (witness,),
+ (assertion,),
+ (corroboration,),
+ )
+ result = assess_witness_corroboration(entry, bundle, session=session)
+ assert entry.claim_id == "claim:fixture"
+ assert entry.claim_id != binding.coordinate.subject
+ assert result.status is WitnessResultStatus.CORROBORATED
+ assert result.conformance_status == "ESTABLISHED"
+
+
+def test_vstd5_rechecker_refuses_external_shape_and_payload_defects() -> None:
+ store, session = _session()
+ entry = _established_vstd4(store, session)
+ binding_digest = entry.witness.header.binding # type: ignore[union-attr]
+ witness, assertion, corroboration = _witness_components(
+ store, entry, "witness:one"
+ )
+ bundle = WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ binding_digest,
+ (witness,),
+ (assertion,),
+ (corroboration,),
+ )
+ receipt = build_vstd5_receipt(
+ entry,
+ bundle,
+ assess_witness_corroboration(entry, bundle, session=session),
+ receipt_id="VFY-5-EXTERNAL",
+ session=session,
+ )
+ malformed = []
+ for coordinate, value in (
+ (("receipt_id",), "invalid id"),
+ (("bundle", "claim_id"), ""),
+ (("bundle", "witnesses"), []),
+ (("bundle", "corroborations"), []),
+ (("bundle", "witnesses", 0, "witness_id"), ""),
+ ):
+ candidate = copy.deepcopy(receipt)
+ target = candidate
+ for part in coordinate[:-1]:
+ target = target[part]
+ target[coordinate[-1]] = value
+ malformed.append(candidate)
+ extra = copy.deepcopy(receipt)
+ extra["result"]["unexpected"] = True
+ malformed.append(extra)
+
+ validator = _vstd5_schema_validator()
+ for candidate in malformed:
+ assert list(validator.iter_errors(candidate))
+ with pytest.raises(ValueError, match="invalid VSTD-5 receipt shape"):
+ recheck_vstd5_receipt(entry, candidate, mechanisms=())
+
+ missing_payload = copy.deepcopy(receipt)
+ missing_payload["evidence_payloads"].pop(
+ next(iter(missing_payload["evidence_payloads"]))
+ )
+ with pytest.raises(ValueError, match="missing verdict-material bytes"):
+ recheck_vstd5_receipt(entry, missing_payload, mechanisms=())
+
+
+def test_vstd5_rechecker_refuses_schema_valid_cross_field_contradictions() -> None:
+ store, session = _session()
+ entry = _established_vstd4(store, session)
+ witness, assertion, corroboration = _witness_components(
+ store, entry, "witness:one"
+ )
+ bundle = WitnessBundle(
+ "claim:fixture",
+ "declarant:one",
+ entry.witness.header.binding, # type: ignore[union-attr]
+ (witness,),
+ (assertion,),
+ (corroboration,),
+ )
+ result = assess_witness_corroboration(entry, bundle, session=session)
+ receipt = build_vstd5_receipt(
+ entry,
+ bundle,
+ result,
+ receipt_id="VFY-5-CROSS-FIELD",
+ session=session,
+ )
+ validator = _vstd5_schema_validator()
+
+ for field_name in ("result_digest", "witness_digest"):
+ candidate = copy.deepcopy(receipt)
+ candidate["entry_vstd4"][field_name] = "0" * 64
+ validator.validate(candidate)
+ with pytest.raises(ValueError, match="inconsistent VSTD-4 entry"):
+ recheck_vstd5_receipt(
+ entry, candidate, mechanisms=(ExactFactMechanism(),)
+ )
+
+ relabeled = copy.deepcopy(receipt)
+ relabeled["bundle"]["corroborations"][0][
+ "corroboration_class"
+ ] = "UNIVERSAL_FORMAL_PROOF"
+ validator.validate(relabeled)
+ with pytest.raises(ValueError, match="recomputed VSTD-5 result"):
+ recheck_vstd5_receipt(
+ entry, relabeled, mechanisms=(ExactFactMechanism(),)
+ )
+
+ relabeled_bundle = WitnessBundle.from_dict(relabeled["bundle"])
+ relabeled_result = assess_witness_corroboration(
+ entry, relabeled_bundle, session=session
+ )
+ assert relabeled_result.status is WitnessResultStatus.UNKNOWN
+ assert relabeled_result.conformance_status == "NOT_ESTABLISHED"
+ assert relabeled_result.corroboration_errors == (
+ "corroboration corroboration:witness:one is not exactly bound",
+ )
diff --git a/tests/test_experimental_workflow_cli.py b/tests/test_experimental_workflow_cli.py
new file mode 100644
index 0000000..c4184bc
--- /dev/null
+++ b/tests/test_experimental_workflow_cli.py
@@ -0,0 +1,67 @@
+"""Terminology: command-line interface (CLI); Verifier Standard (VSTD).
+
+CLI tests for the verdict-neutral experimental-workflow surface."""
+
+from __future__ import annotations
+
+import json
+import hashlib
+from pathlib import Path
+
+from verifier.experimental_workflow import seal_manifest
+from verifier.runtime.public_cli import main
+
+
+ROOT = Path(__file__).resolve().parents[1]
+MANIFEST = ROOT / "experiments" / "github_verdict_neutrality" / "experiment.json"
+SNAPSHOT = ROOT / "examples" / "experimental_workflow" / "github_snapshot.json"
+
+
+def test_experiment_validate_reports_exact_non_verdict_scope(capsys) -> None:
+ assert main(["experiment", "validate", str(MANIFEST), "--json"]) == 0
+ result = json.loads(capsys.readouterr().out)
+ assert result["status"] == "VALID"
+ assert result["repository_artifacts"] == "NOT_APPLICABLE"
+ assert result["vstd_verdict_granted"] is False
+ assert result["experiment"]["id"] == "experiment-github-verdict-neutrality"
+
+
+def test_experiment_validate_rejects_tampered_digest(tmp_path: Path, capsys) -> None:
+ payload = json.loads(MANIFEST.read_text(encoding="utf-8"))
+ payload["experiment"]["question"] = "Substituted question"
+ path = tmp_path / "tampered.json"
+ path.write_text(json.dumps(payload), encoding="utf-8")
+ assert main(["experiment", "validate", str(path), "--json"]) == 1
+ assert "manifest_digest" in capsys.readouterr().err
+
+
+def test_experiment_validate_does_not_skip_repository_artifacts(
+ tmp_path: Path, capsys
+) -> None:
+ payload = json.loads(MANIFEST.read_text(encoding="utf-8"))
+ payload.pop("manifest_digest")
+ payload["artifacts"].append(
+ {
+ "id": "artifact-repository-evidence",
+ "role": "repository-evidence",
+ "media_type": "text/plain",
+ "digest": "sha256:" + hashlib.sha256(b"evidence").hexdigest(),
+ "locator": "repo:evidence.txt",
+ }
+ )
+ path = tmp_path / "unchecked.json"
+ path.write_text(json.dumps(seal_manifest(payload)), encoding="utf-8")
+
+ assert main(["experiment", "validate", str(path), "--json"]) == 2
+ result = json.loads(capsys.readouterr().out)
+ assert result["status"] == "VALID_WITH_UNCHECKED_REPOSITORY_ARTIFACTS"
+ assert result["repository_artifacts"] == "NOT_CHECKED"
+ assert result["vstd_verdict_granted"] is False
+
+
+def test_experiment_github_events_remain_verdict_neutral(capsys) -> None:
+ assert main(["experiment", "github-events", str(SNAPSHOT), "--json"]) == 0
+ result = json.loads(capsys.readouterr().out)
+ assert result["event_count"] == 5
+ assert result["verification_effects"] == ["NONE"]
+ assert result["vstd_verdicts_granted"] == 0
diff --git a/tests/test_experimental_workflow_profile.py b/tests/test_experimental_workflow_profile.py
new file mode 100644
index 0000000..e55ecb8
--- /dev/null
+++ b/tests/test_experimental_workflow_profile.py
@@ -0,0 +1,328 @@
+"""Terminology: line feed (LF); zero-identity/zero-knowledge (ZIZK).
+
+Adversarial tests for the non-normative experimental-workflow profile."""
+
+from __future__ import annotations
+
+import copy
+import hashlib
+import importlib.util
+import json
+from pathlib import Path
+
+import jsonschema
+import pytest
+
+from verifier.experimental_workflow import (
+ GitHubAdapterError,
+ WorkflowProfileError,
+ github_snapshot_to_events,
+ load_manifest,
+ seal_manifest,
+ validate_manifest,
+ verify_repo_artifacts,
+ workflow_manifest_schema,
+)
+
+
+ROOT = Path(__file__).resolve().parents[1]
+EXAMPLE = ROOT / "examples" / "experimental_workflow"
+EXPERIMENT_MANIFEST = (
+ ROOT / "experiments" / "github_verdict_neutrality" / "experiment.json"
+)
+ARTIFACT_FIRST_MECHANISMS_MANIFEST = (
+ ROOT / "experiments" / "artifact_first_mechanisms" / "experiment.json"
+)
+
+
+def _example_payload() -> dict[str, object]:
+ return json.loads(EXPERIMENT_MANIFEST.read_text(encoding="utf-8"))
+
+
+def _github_snapshot() -> dict[str, object]:
+ return json.loads((EXAMPLE / "github_snapshot.json").read_text(encoding="utf-8"))
+
+
+def _add_mapped_result(payload: dict[str, object], verdict: str) -> None:
+ artifacts = payload["artifacts"]
+ actions = payload["actions"]
+ native_results = payload["native_results"]
+ assert isinstance(artifacts, list)
+ assert isinstance(actions, list) and isinstance(actions[0], dict)
+ assert isinstance(native_results, list)
+ artifacts.append(
+ {
+ "id": "artifact-vstd-receipt",
+ "role": "mapped-vstd-receipt",
+ "media_type": "application/json",
+ "digest": "sha256:" + "3" * 64,
+ "locator": "artifact:vstd-receipt",
+ }
+ )
+ native_results.append(
+ {
+ "id": "result-mapped",
+ "action_id": actions[0]["id"],
+ "verifier": {
+ "kind": "domain-verifier",
+ "name": "bounded-example",
+ "version": "1",
+ "coordinate": "urn:example:bounded-verifier",
+ },
+ "native_status": "INDETERMINATE",
+ "result_artifact_id": None,
+ "mapping": {
+ "status": "MAPPED",
+ "vstd_verdict": verdict,
+ "mapping_profile": "urn:example:vstd-mapping:1",
+ "receipt_artifact_id": "artifact-vstd-receipt",
+ "reason": "A separate receipt records the bounded mapping.",
+ },
+ }
+ )
+ actions[0]["native_result_ids"] = ["result-mapped"]
+
+
+def test_checked_in_manifests_validate_and_match_schema() -> None:
+ schema = workflow_manifest_schema()
+ payload = load_manifest(EXPERIMENT_MANIFEST)
+ jsonschema.Draft202012Validator(schema).validate(payload)
+
+
+def test_artifact_first_mechanism_manifest_preserves_causal_provenance_boundary() -> None:
+ payload = load_manifest(ARTIFACT_FIRST_MECHANISMS_MANIFEST)
+ verify_repo_artifacts(payload, ROOT)
+
+ assert payload["experiment"]["id"] == "experiment-artifact-first-mechanisms"
+ assert "governing" in payload["experiment"]["title"]
+
+ artifacts = {item["id"]: item for item in payload["artifacts"]}
+ assert artifacts["artifact-zk-receipt"]["locator"].endswith(
+ "recorded-proof/receipt.msgpack"
+ )
+ assert artifacts["artifact-zk-public-envelope"]["locator"].endswith(
+ "recorded-proof/public.json"
+ )
+ assert artifacts["artifact-zk-self-test"]["locator"].endswith(
+ "recorded-proof/self-test-results.json"
+ )
+
+ hypotheses = {item["id"]: item for item in payload["hypotheses"]}
+ assert hypotheses["hypothesis-artifact-first-zero-actor-trust"]["state"] == "OPEN"
+ assert hypotheses["hypothesis-contextual-actor-artifact-roles"]["state"] == "OPEN"
+ assert hypotheses["hypothesis-rust-memetic-backtrace"]["state"] == "OPEN"
+ assert hypotheses["hypothesis-rot-current-admissibility"]["state"] == "OPEN"
+ assert hypotheses["hypothesis-dual-causal-propagation"]["state"] == "OPEN"
+
+ adaptation = payload["adaptations"][0]
+ assert "standard/LADDER.md section 1.1" in adaptation["decision"]
+ assert "TRUST transfer" in adaptation["decision"]
+ assert "ROT derivation/propagation" in adaptation["decision"]
+ assert "RUST backtrace/concentration" in adaptation["decision"]
+
+ horizons = {item["id"]: item["status"] for item in payload["horizons"]}
+ assert horizons["horizon-contextual-role-protocol"] == "UNKNOWN"
+ assert horizons["horizon-rot-current-admissibility"] == "UNKNOWN"
+ assert horizons["horizon-rust-memetic-backtrace"] == "UNKNOWN"
+ assert horizons["horizon-forward-artifact-trust"] == "UNKNOWN"
+
+
+def test_manifest_bound_text_artifacts_use_repository_lf_bytes() -> None:
+ payload = load_manifest(ARTIFACT_FIRST_MECHANISMS_MANIFEST)
+ for artifact in payload["artifacts"]:
+ if artifact["media_type"] != "text/markdown":
+ continue
+ locator = artifact["locator"]
+ assert locator.startswith("repo:")
+ data = (ROOT / locator.removeprefix("repo:")).read_bytes()
+ assert b"\r\n" not in data, f"{locator} must match Git's LF-normalized bytes"
+
+
+def test_checked_in_schema_is_generated_from_one_source() -> None:
+ checked_in = json.loads(
+ (ROOT / "docs" / "profiles" / "experimental-workflow.schema.json").read_text(
+ encoding="utf-8"
+ )
+ )
+ assert checked_in == workflow_manifest_schema()
+
+
+def test_manifest_digest_detects_semantic_tampering() -> None:
+ payload = _example_payload()
+ experiment = payload["experiment"]
+ assert isinstance(experiment, dict)
+ experiment["question"] = "A substituted question"
+ with pytest.raises(WorkflowProfileError, match="canonical stable payload"):
+ validate_manifest(payload)
+
+
+def test_seal_manifest_does_not_mutate_caller() -> None:
+ payload = _example_payload()
+ payload.pop("manifest_digest")
+ original = copy.deepcopy(payload)
+ sealed = seal_manifest(payload)
+ assert payload == original
+ assert sealed["manifest_digest"].startswith("sha256:")
+
+
+@pytest.mark.parametrize("value", [-1, 1.5, True])
+def test_budget_rejects_negative_float_and_boolean_limits(value: object) -> None:
+ payload = _example_payload()
+ payload.pop("manifest_digest")
+ budgets = payload["budgets"]
+ assert isinstance(budgets, list) and isinstance(budgets[0], dict)
+ budgets[0]["limit"] = value
+ with pytest.raises(WorkflowProfileError):
+ seal_manifest(payload)
+
+
+def test_consumed_work_cannot_exceed_bound() -> None:
+ payload = _example_payload()
+ payload.pop("manifest_digest")
+ budgets = payload["budgets"]
+ assert isinstance(budgets, list) and isinstance(budgets[0], dict)
+ budgets[0]["consumed"] = budgets[0]["limit"] + 1
+ with pytest.raises(WorkflowProfileError, match="exceeds"):
+ seal_manifest(payload)
+
+
+def test_every_selected_action_requires_a_budget() -> None:
+ payload = _example_payload()
+ payload.pop("manifest_digest")
+ actions = payload["actions"]
+ assert isinstance(actions, list) and isinstance(actions[0], dict)
+ actions[0]["budget_ids"] = []
+ with pytest.raises(WorkflowProfileError, match="bind at least one budget"):
+ seal_manifest(payload)
+
+
+def test_action_dependency_cycles_fail_closed() -> None:
+ payload = _example_payload()
+ payload.pop("manifest_digest")
+ actions = payload["actions"]
+ assert isinstance(actions, list) and isinstance(actions[0], dict)
+ actions[0]["depends_on"] = [actions[0]["id"]]
+ with pytest.raises(WorkflowProfileError, match="dependency cycle"):
+ seal_manifest(payload)
+
+
+@pytest.mark.parametrize(
+ "locator",
+ [
+ "C" + ":\\private\\result.json",
+ "/" + "home/person/result.json",
+ "repo:../private/result.json",
+ "repo:folder\\result.json",
+ ],
+)
+def test_nonportable_or_escaping_artifact_locators_are_rejected(locator: str) -> None:
+ payload = _example_payload()
+ payload.pop("manifest_digest")
+ artifacts = payload["artifacts"]
+ assert isinstance(artifacts, list)
+ artifacts.append(
+ {
+ "id": "artifact-bad-locator",
+ "role": "test",
+ "media_type": "application/json",
+ "digest": "sha256:" + "4" * 64,
+ "locator": locator,
+ }
+ )
+ with pytest.raises(WorkflowProfileError):
+ seal_manifest(payload)
+
+
+def test_not_evaluated_mapping_cannot_smuggle_a_verdict() -> None:
+ payload = _example_payload()
+ payload.pop("manifest_digest")
+ _add_mapped_result(payload, "PASS")
+ native_results = payload["native_results"]
+ assert isinstance(native_results, list) and isinstance(native_results[0], dict)
+ mapping = native_results[0]["mapping"]
+ assert isinstance(mapping, dict)
+ mapping["status"] = "NOT_EVALUATED"
+ with pytest.raises(WorkflowProfileError, match="cannot carry"):
+ seal_manifest(payload)
+
+
+@pytest.mark.parametrize("verdict", ["UNKNOWN", "CONFLICTED"])
+def test_uncertain_mapped_verdicts_remain_representable(verdict: str) -> None:
+ payload = _example_payload()
+ payload.pop("manifest_digest")
+ _add_mapped_result(payload, verdict)
+ sealed = seal_manifest(payload)
+ result = sealed["native_results"][0]
+ assert result["mapping"]["vstd_verdict"] == verdict
+
+
+def test_successful_workflow_and_merge_have_no_verification_effect() -> None:
+ events = github_snapshot_to_events(_github_snapshot())
+ assert len(events) == 5
+ assert {event["verification_effect"] for event in events} == {"NONE"}
+ assert any(event["native_state"] == "completed/success" for event in events)
+ assert any(event["native_state"] == "closed/MERGED" for event in events)
+ assert all("vstd_verdict" not in event for event in events)
+
+
+def test_platform_event_verification_upgrade_is_rejected() -> None:
+ payload = _example_payload()
+ payload.pop("manifest_digest")
+ events = payload["workflow_events"]
+ assert isinstance(events, list) and isinstance(events[0], dict)
+ events[0]["verification_effect"] = "PASS"
+ with pytest.raises(WorkflowProfileError, match="cannot grant"):
+ seal_manifest(payload)
+
+
+def test_github_adapter_rejects_unknown_fields_instead_of_guessing() -> None:
+ snapshot = _github_snapshot()
+ snapshot["deployment_statuses"] = []
+ with pytest.raises(GitHubAdapterError, match="unsupported fields"):
+ github_snapshot_to_events(snapshot)
+
+
+def test_github_adapter_is_deterministic_and_matches_specimen() -> None:
+ events = github_snapshot_to_events(_github_snapshot())
+ manifest = load_manifest(EXPERIMENT_MANIFEST)
+ assert list(events) == manifest["workflow_events"]
+ assert events == github_snapshot_to_events(_github_snapshot())
+
+
+def test_repo_artifact_binding_detects_substitution(tmp_path: Path) -> None:
+ artifact = tmp_path / "evidence.txt"
+ artifact.write_bytes(b"original")
+ payload = _example_payload()
+ payload.pop("manifest_digest")
+ artifacts = payload["artifacts"]
+ assert isinstance(artifacts, list)
+ artifacts.append(
+ {
+ "id": "artifact-repo-test",
+ "role": "test-evidence",
+ "media_type": "text/plain",
+ "digest": "sha256:" + hashlib.sha256(b"original").hexdigest(),
+ "locator": "repo:evidence.txt",
+ }
+ )
+ sealed = seal_manifest(payload)
+ verify_repo_artifacts(sealed, tmp_path)
+ artifact.write_bytes(b"substituted")
+ with pytest.raises(WorkflowProfileError, match="does not match"):
+ verify_repo_artifacts(sealed, tmp_path)
+
+
+def test_indexed_repository_artifacts_match_manifest() -> None:
+ payload = load_manifest(EXPERIMENT_MANIFEST)
+ verify_repo_artifacts(payload, ROOT)
+
+
+def test_experiment_index_is_current() -> None:
+ spec = importlib.util.spec_from_file_location(
+ "build_experiment_index", ROOT / "scripts" / "build_experiment_index.py"
+ )
+ assert spec is not None and spec.loader is not None
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ expected = module.render(module.discover(ROOT))
+ assert (ROOT / "experiments" / "INDEX.md").read_text(encoding="utf-8") == expected
diff --git a/tests/test_external_links.py b/tests/test_external_links.py
new file mode 100644
index 0000000..0d7dcd6
--- /dev/null
+++ b/tests/test_external_links.py
@@ -0,0 +1,64 @@
+"""Tests for the external Hypertext Transfer Protocol (HTTP) link audit."""
+
+from __future__ import annotations
+
+import importlib.util
+from pathlib import Path
+import sys
+
+import pytest
+
+
+ROOT = Path(__file__).resolve().parents[1]
+SCRIPT = ROOT / "scripts/check_external_links.py"
+SPEC = importlib.util.spec_from_file_location("check_external_links", SCRIPT)
+assert SPEC is not None and SPEC.loader is not None
+external_links = importlib.util.module_from_spec(SPEC)
+sys.modules[SPEC.name] = external_links
+SPEC.loader.exec_module(external_links)
+
+
+def test_collect_links_deduplicates_and_removes_fragments(tmp_path: Path) -> None:
+ markdown = tmp_path / "guide.md"
+ markdown.write_text(
+ "[one](https://example.com/path#one) [two](https://example.com/path#two)\n",
+ encoding="utf-8",
+ )
+ html = tmp_path / "guide.html"
+ html.write_text('page ', encoding="utf-8")
+
+ assert external_links.collect_links((markdown, html)) == (
+ "https://example.com/path",
+ "https://example.org/page",
+ )
+
+
+def test_allowlist_requires_a_reviewable_reason(tmp_path: Path) -> None:
+ allowlist = tmp_path / "allowlist.txt"
+ allowlist.write_text("https://example.com/*\n", encoding="utf-8")
+
+ with pytest.raises(ValueError, match="needs a tab and reason"):
+ external_links.read_allowlist(allowlist)
+
+
+def test_allowlist_matches_only_exact_or_declared_prefix() -> None:
+ entries = (("https://example.com/bounded/*", "upstream blocks probes"),)
+
+ assert external_links.allowlist_reason(
+ "https://example.com/bounded/page", entries
+ ) == "upstream blocks probes"
+ assert external_links.allowlist_reason("https://example.com/other", entries) is None
+
+
+def test_external_audit_is_scheduled_and_not_a_pull_request_gate() -> None:
+ workflow = (ROOT / ".github/workflows/external-links.yml").read_text(encoding="utf-8")
+ entries = external_links.read_allowlist(
+ ROOT / ".github/external-links-allowlist.txt"
+ )
+
+ assert "schedule:" in workflow
+ assert "workflow_dispatch:" in workflow
+ assert "pull_request:" not in workflow
+ assert "--retries 2 --workers 8" in workflow
+ assert entries
+ assert all(reason for _, reason in entries)
diff --git a/tests/test_flagship_demo.py b/tests/test_flagship_demo.py
index 7453e93..04b5ab7 100644
--- a/tests/test_flagship_demo.py
+++ b/tests/test_flagship_demo.py
@@ -1,4 +1,6 @@
-"""Conformance tests for the public, adversarial VSTD flagship demo."""
+"""Terminology: Verifier Standard (VSTD).
+
+Conformance tests for the public, adversarial VSTD flagship demo."""
from __future__ import annotations
@@ -12,7 +14,7 @@
"wrong-artifact": "REJECTED",
"honest-unknown": "ACCEPTED/UNKNOWN",
"inflated-tier": "REJECTED",
- "poisoned-ancestor": "GRAPH-LEVEL-0; REVOKED",
+ "poisoned-ancestor": "GRAPH-CANDIDATE-0; REVOKED",
}
diff --git a/tests/test_gdc_certificate.py b/tests/test_gdc_certificate.py
index 4c6945d..f09bef3 100644
--- a/tests/test_gdc_certificate.py
+++ b/tests/test_gdc_certificate.py
@@ -1,4 +1,8 @@
-"""``VSTD4-GDC-1`` conformance, and regressions pinning the three retrofits.
+"""Terminology: conjunctive normal form (CNF); grounded decision certificate (GDC);
+Boolean satisfiability problem (SAT); trusted computing base (TCB); unsatisfiable (UNSAT);
+Verifier Standard (VSTD).
+
+``VSTD4-GDC-1`` conformance, and regressions pinning the three retrofits.
The tests that matter most here are the ones no competition proof format could
express: the keystone test, where a decision block is perfectly valid and the
@@ -6,7 +10,7 @@
linear-time check is dressed in general-resolution machinery to look rigorous.
The retrofit regressions at the bottom assert that the *old* behaviour is gone,
-not merely that the new behaviour works. Three things shipped that layer 4
+not merely that the new behaviour works. Three things shipped that VSTD-4
prohibits, and a test that only exercises the fix would pass again the moment
someone reintroduced the shortcut beside it.
"""
@@ -580,6 +584,7 @@ def test_trusted_computing_base_is_hashes_not_a_literal_dict():
descriptor = IndependentAuditor.verifier_descriptor()
assert descriptor.implementation_hash.startswith("sha256:")
assert descriptor.specification_hash.startswith("sha256:")
+ assert descriptor.certificate_format == "VSTD1-CHECKER-REPORT"
# Computed from the file on disk, not from a string constant.
import hashlib
@@ -589,6 +594,11 @@ def test_trusted_computing_base_is_hashes_not_a_literal_dict():
).hexdigest()
assert descriptor.implementation_hash == expected
+ expected_specification = "sha256:" + hashlib.sha256(
+ (Path(__file__).resolve().parents[1] / "standard" / "VSTD-1.md").read_bytes()
+ ).hexdigest()
+ assert descriptor.specification_hash == expected_specification
+
# And it declares what it actually implements, not VSTD4-GDC-1.
assert descriptor.certificate_format != FORMAT
assert "isolation" not in IndependentAuditor.tcb()
diff --git a/tests/test_generic_run.py b/tests/test_generic_run.py
index 92bd0bb..6a69f2e 100644
--- a/tests/test_generic_run.py
+++ b/tests/test_generic_run.py
@@ -1,22 +1,29 @@
-"""Adversarial and lifecycle tests for the generic proof-carrying computational run
+"""Terminology: Verifier Standard (VSTD).
+
+Adversarial and lifecycle tests for the generic computational run receipt
primitive (`verifier.core.run`).
Covers the acceptance-test flow (capture -> validate -> inspect -> reproduce) plus a
hostile-scrutiny mini-corpus: tampered receipts, tampered outputs, missing declared
inputs/outputs, shell-indirection rejection, non-promotable external evaluation
-claims, and determinism-bounded reproduction ceilings.
+claims, and mechanism-bounded reproduction ceilings.
"""
from __future__ import annotations
+import copy
+import hashlib
import json
+import subprocess
import sys
from pathlib import Path
import pytest
+from jsonschema import Draft202012Validator
from verifier.core.run import (
RunError,
+ _rebuild_stable_payload_from_dict,
capture_run,
find_run_receipts_impacted_by_revocation,
inspect_run_receipt,
@@ -24,9 +31,17 @@
reproduce_run_receipt,
validate_run_receipt,
)
-
-REPO_ROOT = Path(__file__).resolve().parents[1]
-
+from verifier.core.receipt import compute_canonical_digest
+from verifier.data.models import (
+ ArtifactNode,
+ ArtifactStatus,
+ ArtifactType,
+ HyperedgePort,
+ ProvenanceHypergraph,
+ TransformationHyperedge,
+ TransformationType,
+)
+from verifier.runtime.public_cli import _write_reproduction_bundle
def _write_tiny_project(tmp_path: Path) -> Path:
"""A minimal deterministic project: script reads input.txt, writes output.json."""
@@ -43,6 +58,20 @@ def _write_tiny_project(tmp_path: Path) -> Path:
return tmp_path
+def test_digest_consistent_empty_generic_receipt_is_rejected(tmp_path, capsys):
+ receipt = {"receipt_kind": "generic_computational_run"}
+ receipt["canonical_digest"] = compute_canonical_digest(
+ _rebuild_stable_payload_from_dict(receipt)
+ )
+ path = tmp_path / "receipt.json"
+ path.write_text(json.dumps(receipt), encoding="utf-8")
+
+ assert validate_run_receipt(path) == 1
+ output = capsys.readouterr().out
+ assert "[INTEGRITY OK]" not in output
+ assert "missing required fields" in output
+
+
def _base_manifest() -> dict:
return {
"claim": {
@@ -61,6 +90,41 @@ def _base_manifest() -> dict:
}
+def _write_data_receipt(tmp_path: Path) -> tuple[Path, str]:
+ """Write the smallest public graph fixture needed by linkage/blast-radius tests."""
+
+ graph = ProvenanceHypergraph()
+ for artifact_id in ("artifact:source", "artifact:derived"):
+ graph.add_artifact(
+ ArtifactNode(
+ artifact_id,
+ artifact_id,
+ ArtifactType.CORPUS,
+ "a" * 64,
+ status=ArtifactStatus.VALID,
+ )
+ )
+ graph.add_transformation(
+ TransformationHyperedge(
+ "transform:derive",
+ "derive",
+ TransformationType.EXTRACTION,
+ (HyperedgePort("artifact:source", "INPUT"),),
+ (HyperedgePort("artifact:derived", "OUTPUT"),),
+ {},
+ {},
+ {},
+ )
+ )
+ receipt_file = tmp_path / "dataset-receipt" / "receipt.json"
+ receipt_file.parent.mkdir()
+ receipt_file.write_text(
+ json.dumps({"hypergraph": graph.to_dict()}),
+ encoding="utf-8",
+ )
+ return receipt_file, "artifact:source"
+
+
def test_full_lifecycle_capture_validate_inspect_reproduce(tmp_path, capsys):
proj = _write_tiny_project(tmp_path)
manifest = _base_manifest()
@@ -81,29 +145,48 @@ def test_full_lifecycle_capture_validate_inspect_reproduce(tmp_path, capsys):
assert (out_dir / "manifest.json").exists() is False # test manifest was never written to disk
data = json.loads(receipt_file.read_text(encoding="utf-8"))
+ schema = json.loads(
+ (Path(__file__).resolve().parents[1] / "receipts" / "schema" / "vstd1_generic_run_receipt.json").read_text(
+ encoding="utf-8"
+ )
+ )
+ Draft202012Validator(schema).validate(data)
assert is_generic_run_receipt(data)
assert data["canonical_digest"] == receipt.canonical_digest
- layer4 = data["layer4_binding"]
- assert layer4["verifier"]["implementation_hash"].startswith("sha256:")
- assert layer4["verifier"]["parser_hash"].startswith("sha256:")
- assert layer4["resource_bounds"] == {
+ context = data["assessment_context"]
+ assert context["verifier"]["implementation_hash"].startswith("sha256:")
+ assert context["verifier"]["parser_hash"].startswith("sha256:")
+ assert context["resource_bounds"] == {
"verification_cost_bound": 0,
"memory_bound": 0,
"certificate_size_bound": 0,
}
- assert layer4["prior_commitment"] == ""
- assert layer4["refutation_surface"]["admissible_refutations"] == []
- assert "PHYSICAL_WORLD_COMPLETENESS" in layer4["refutation_surface"][
+ assert context["prior_commitment"] == ""
+ assert context["refutation_surface"]["admissible_refutations"] == []
+ assert "PHYSICAL_WORLD_COMPLETENESS" in context["refutation_surface"][
"excluded_claims"
]
assert validate_run_receipt(out_dir) == 0
+ assert "[INTEGRITY OK]" in capsys.readouterr().out
assert inspect_run_receipt(out_dir) == 0
# Default reproduce: artifact rehash only, no side effects.
assert reproduce_run_receipt(out_dir) == 0
+def test_reproduce_honors_an_explicit_receipt_filename(tmp_path):
+ proj = _write_tiny_project(tmp_path)
+ receipt = capture_run(_base_manifest(), manifest_dir=proj)
+ receipt_file = receipt.save_to_directory(proj)
+ renamed_receipt = proj / "renamed-receipt.json"
+ receipt_file.rename(renamed_receipt)
+
+ assert validate_run_receipt(renamed_receipt) == 0
+ assert inspect_run_receipt(renamed_receipt) == 0
+ assert reproduce_run_receipt(renamed_receipt) == 0
+
+
def test_new_run_receipt_binds_precommitment_bounds_and_refutation_surface(tmp_path):
proj = _write_tiny_project(tmp_path)
manifest = _base_manifest()
@@ -119,25 +202,61 @@ def test_new_run_receipt_binds_precommitment_bounds_and_refutation_surface(tmp_p
}
receipt = capture_run(manifest, manifest_dir=proj)
before = receipt.canonical_digest
- layer4 = receipt.get_stable_payload()["layer4_binding"]
- assert layer4["prior_commitment"] == manifest["prior_commitment"]
- assert layer4["resource_bounds"] == manifest["resource_bounds"]
- assert layer4["refutation_surface"]["admissible_refutations"] == [
+ context = receipt.get_stable_payload()["assessment_context"]
+ assert context["prior_commitment"] == manifest["prior_commitment"]
+ assert context["resource_bounds"] == manifest["resource_bounds"]
+ assert context["refutation_surface"]["admissible_refutations"] == [
"evidence_hash_mismatch"
]
- layer4["prior_commitment"] = "sha256:" + "b" * 64
- receipt.layer4_binding = layer4
+ context["prior_commitment"] = "sha256:" + "b" * 64
+ receipt.assessment_context = context
assert receipt.compute_and_set_digest() != before
-def test_historical_generic_run_digest_is_unchanged_by_optional_layer4_block():
- receipt_path = REPO_ROOT / "examples" / "generic_run" / "receipt.json"
- if not receipt_path.exists():
- pytest.skip("historical private-path receipt is intentionally excluded publicly")
- data = json.loads(receipt_path.read_text(encoding="utf-8"))
- assert "layer4_binding" not in data
- assert validate_run_receipt(receipt_path) == 0
+def test_generic_run_requires_assessment_context(tmp_path, capsys):
+ proj = _write_tiny_project(tmp_path)
+ data = capture_run(_base_manifest(), manifest_dir=proj).to_dict()
+ data.pop("assessment_context")
+ data["canonical_digest"] = compute_canonical_digest(
+ _rebuild_stable_payload_from_dict(data)
+ )
+ path = tmp_path / "missing-assessment-context.json"
+ path.write_text(json.dumps(data), encoding="utf-8")
+
+ assert is_generic_run_receipt(data)
+ assert validate_run_receipt(path) == 1
+ assert "missing required fields: assessment_context" in capsys.readouterr().out
+
+
+def test_retired_generic_run_identifier_is_rejected(tmp_path, capsys):
+ proj = _write_tiny_project(tmp_path)
+ data = capture_run(_base_manifest(), manifest_dir=proj).to_dict()
+ data["schema_version"] = "VSTD-" + "0.1"
+ data["canonical_digest"] = compute_canonical_digest(
+ _rebuild_stable_payload_from_dict(data)
+ )
+ path = tmp_path / "retired-identifier.json"
+ path.write_text(json.dumps(data), encoding="utf-8")
+
+ assert not is_generic_run_receipt(data)
+ assert validate_run_receipt(path) == 1
+ assert "schema_version must be VSTD-1" in capsys.readouterr().out
+
+
+def test_assessment_context_rejects_layer_specific_fields(tmp_path, capsys):
+ proj = _write_tiny_project(tmp_path)
+ data = capture_run(_base_manifest(), manifest_dir=proj).to_dict()
+ prohibited_field = "vstd4_" + "conformance"
+ data["assessment_context"][prohibited_field] = "PASS"
+ data["canonical_digest"] = compute_canonical_digest(
+ _rebuild_stable_payload_from_dict(data)
+ )
+ path = tmp_path / "hostile-vstd4-claim.json"
+ path.write_text(json.dumps(data), encoding="utf-8")
+
+ assert validate_run_receipt(path) == 1
+ assert f"unexpected fields: {prohibited_field}" in capsys.readouterr().out
def test_missing_input_fails_closed_without_executing(tmp_path):
@@ -248,7 +367,84 @@ def test_external_evaluation_never_auto_promoted_to_attested(tmp_path):
assert ext.attested is False, "an unverified assertion must never be silently promoted to attested"
-def test_external_evaluation_with_linked_artifact_and_ref_can_be_attested(tmp_path):
+def test_validator_rejects_digest_consistent_independence_and_attestation_upgrades(
+ tmp_path,
+):
+ proj = _write_tiny_project(tmp_path)
+ manifest = _base_manifest()
+ manifest["evaluator_claims"] = [
+ {"evaluator_name": "declared", "metric_name": "score", "value": 1}
+ ]
+ manifest["external_evaluation"] = {
+ "source": "declared",
+ "description": "unverified",
+ "reported_value": 1,
+ }
+ receipt = capture_run(manifest, manifest_dir=proj)
+ original = receipt.to_dict()
+
+ for mutate in (
+ lambda data: data["claims"]["evaluator_claims"][0].update(
+ verified_independently=True
+ ),
+ lambda data: data["claims"]["external_evaluation"].update(attested=True),
+ lambda data: data.update(unbound_claim_upgrade=True),
+ ):
+ data = copy.deepcopy(original)
+ mutate(data)
+ data["canonical_digest"] = compute_canonical_digest(
+ _rebuild_stable_payload_from_dict(data)
+ )
+ path = tmp_path / "hostile-receipt.json"
+ path.write_text(json.dumps(data), encoding="utf-8")
+ assert validate_run_receipt(path) == 1
+
+
+@pytest.mark.parametrize(
+ ("container_path", "field_name"),
+ (
+ (("source_state",), "unknown_source_field"),
+ (("source_state", "git"), "unknown_git_field"),
+ (("source_state", "runtime"), "unknown_runtime_field"),
+ (("assessment_context",), "unknown_binding_field"),
+ (("assessment_context", "verifier"), "unknown_verifier_field"),
+ (("assessment_context", "resource_bounds"), "unknown_bound_field"),
+ ),
+)
+def test_validator_rejects_digest_consistent_unknown_nested_fields(
+ tmp_path, container_path, field_name
+):
+ proj = _write_tiny_project(tmp_path)
+ receipt = capture_run(_base_manifest(), manifest_dir=proj)
+ data = receipt.to_dict()
+ container = data
+ for segment in container_path:
+ container = container[segment]
+ container[field_name] = "attacker-controlled"
+ data["canonical_digest"] = compute_canonical_digest(
+ _rebuild_stable_payload_from_dict(data)
+ )
+ path = tmp_path / "hostile-nested-receipt.json"
+ path.write_text(json.dumps(data), encoding="utf-8")
+
+ assert validate_run_receipt(path) == 1
+
+
+def test_refutation_surface_is_the_explicit_extension_map(tmp_path):
+ proj = _write_tiny_project(tmp_path)
+ manifest = _base_manifest()
+ manifest["refutation_surface"] = {"domain_refutation": "declared extension"}
+ receipt = capture_run(manifest, manifest_dir=proj)
+ path = receipt.save_to_directory(proj)
+
+ assert (
+ receipt.assessment_context["refutation_surface"]["domain_refutation"]
+ == "declared extension"
+ )
+ assert validate_run_receipt(path) == 0
+
+
+def test_external_evaluation_reference_remains_unverified_by_capture_runtime(tmp_path):
proj = _write_tiny_project(tmp_path)
manifest = _base_manifest()
manifest["external_evaluation"] = {
@@ -261,7 +457,7 @@ def test_external_evaluation_with_linked_artifact_and_ref_can_be_attested(tmp_pa
}
receipt = capture_run(manifest, manifest_dir=proj)
ext = receipt.claims.external_evaluation
- assert ext.attested is True
+ assert ext.attested is False
assert ext.evidence_ref == "sha256:deadbeef"
@@ -279,11 +475,11 @@ def test_evaluator_claim_reads_true_value_from_output_not_manifest_assertion(tmp
receipt = capture_run(manifest, manifest_dir=proj)
claim = receipt.claims.evaluator_claims[0]
assert claim.value == 42 # actual value read from the produced artifact, not the bogus 999999
- assert claim.computed_by == "local_reference_evaluator"
- assert claim.verified_independently is True
+ assert claim.computed_by == "bound_output_extraction"
+ assert claim.verified_independently is False
-def test_nondeterministic_run_cannot_declare_bitwise_ceiling(tmp_path):
+def test_determinism_declaration_cannot_raise_reproduction_ceiling(tmp_path):
proj = _write_tiny_project(tmp_path)
script = proj / "rand.py"
script.write_text(
@@ -295,14 +491,15 @@ def test_nondeterministic_run_cannot_declare_bitwise_ceiling(tmp_path):
manifest = _base_manifest()
manifest["command"] = [sys.executable, "rand.py", "output.json"]
manifest["inputs"] = [{"path": "rand.py", "role": "entrypoint_source"}]
- manifest["determinism_declared"] = "NONDETERMINISTIC"
+ manifest["determinism_declared"] = "DETERMINISTIC"
receipt = capture_run(manifest, manifest_dir=proj)
- assert receipt.reproducibility["declared_ceiling"] != "BITWISE_IDENTICAL"
- assert "BITWISE_IDENTICAL" not in receipt.reproducibility["supported_levels"]
+ assert receipt.reproducibility["declared_ceiling"] == "CONTENT_IDENTICAL"
+ assert receipt.reproducibility["supported_levels"] == ["CONTENT_IDENTICAL"]
+ assert receipt.reproducibility["highest_demonstrated_level"] is None
-def test_rerun_reproduction_achieves_bitwise_identical_for_deterministic_example(tmp_path):
+def test_rerun_demonstrates_only_declared_output_content_identity(tmp_path, capsys):
proj = _write_tiny_project(tmp_path)
manifest = _base_manifest()
receipt = capture_run(manifest, manifest_dir=proj)
@@ -310,37 +507,80 @@ def test_rerun_reproduction_achieves_bitwise_identical_for_deterministic_example
(proj / "manifest.source.json").write_text(json.dumps(manifest), encoding="utf-8")
assert reproduce_run_receipt(proj, rerun=True) == 0
+ output = capsys.readouterr().out
+ assert "Fidelity state: CONTENT_IDENTICAL (declared-output scope)" in output
+ assert "BITWISE_IDENTICAL" not in output
+
+
+def test_relocated_bundle_rerun_keeps_declared_output_scope(tmp_path, capsys):
+ source = tmp_path / "source"
+ source.mkdir()
+ _write_tiny_project(source)
+ subprocess.run(["git", "init", "-q"], cwd=source, check=True)
+ subprocess.run(
+ ["git", "config", "user.email", "test" + "@" + "example.invalid"],
+ cwd=source,
+ check=True,
+ )
+ subprocess.run(["git", "config", "user.name", "VSTD Test"], cwd=source, check=True)
+ subprocess.run(["git", "add", "double.py", "input.txt"], cwd=source, check=True)
+ subprocess.run(["git", "commit", "-q", "-m", "fixture"], cwd=source, check=True)
+
+ manifest = _base_manifest()
+ receipt = capture_run(manifest, manifest_dir=source)
+ bundle = tmp_path / "bundle"
+ _write_reproduction_bundle(manifest, source, bundle)
+ receipt.save_to_directory(bundle)
+
+ assert reproduce_run_receipt(bundle, rerun=True) == 0
+ output = capsys.readouterr().out
+ assert "Fidelity state: CONTENT_IDENTICAL (declared-output scope)" in output
+ assert "Scope: declared output artifacts and execution outcome" in output
+
+
+def test_same_outcome_with_changed_output_earns_no_reproduction_level(tmp_path, capsys):
+ proj = _write_tiny_project(tmp_path)
+ manifest = _base_manifest()
+ receipt = capture_run(manifest, manifest_dir=proj)
+ receipt.save_to_directory(proj)
+ (proj / "manifest.source.json").write_text(json.dumps(manifest), encoding="utf-8")
+ (proj / "input.txt").write_text("22", encoding="utf-8")
+
+ assert reproduce_run_receipt(proj, rerun=True) == 1
+ output = capsys.readouterr().out
+ assert "Fidelity state: NOT_DEMONSTRATED" in output
+ assert "RESULT_EQUIVALENT" not in output
+ assert "SEMANTIC_REPRODUCTION" not in output
-def test_provenance_linkage_against_real_vfy_data_receipt():
- """Dogfood check: link a run to the real VFY-DATA-000001 hypergraph in this repo."""
- data_receipt_dir = REPO_ROOT / "receipts" / "VFY-DATA-000001"
- if not (data_receipt_dir / "receipt.json").exists():
- pytest.skip("VFY-DATA-000001 receipt not present in this checkout")
+def test_no_declared_outputs_cannot_vacuously_reproduce(tmp_path, capsys):
+ proj = _write_tiny_project(tmp_path)
+ manifest = _base_manifest()
+ manifest["outputs"] = []
+ receipt = capture_run(manifest, manifest_dir=proj)
+ receipt.save_to_directory(proj)
+ (proj / "manifest.source.json").write_text(json.dumps(manifest), encoding="utf-8")
- data = json.loads((data_receipt_dir / "receipt.json").read_text(encoding="utf-8"))
- arts = data.get("hypergraph", {}).get("artifacts", [])
- # Artifacts are serialized as a list of dicts on disk (see VstdDataReceipt.to_dict
- # -> ProvenanceHypergraph.to_dict); normalize defensively in case that ever changes to a
- # dict keyed by artifact_id.
- if isinstance(arts, dict):
- artifact_ids = list(arts.keys())
- else:
- artifact_ids = [a["artifact_id"] for a in arts]
- assert artifact_ids, "expected at least one artifact in VFY-DATA-000001's hypergraph"
+ assert reproduce_run_receipt(proj, rerun=True) == 1
+ assert "Fidelity state: NOT_DEMONSTRATED" in capsys.readouterr().out
+
+
+def test_provenance_linkage_uses_public_graph_fixture(tmp_path):
+ """Resolve linkage without depending on a receipt absent from the public tree."""
+ data_receipt_file, artifact_id = _write_data_receipt(tmp_path)
from verifier.core.run import _resolve_provenance_linkage
linkage = _resolve_provenance_linkage(
- REPO_ROOT,
- {"dataset_receipt_path": "receipts/VFY-DATA-000001", "artifact_id": artifact_ids[0]},
+ tmp_path,
+ {"dataset_receipt_path": str(data_receipt_file.parent.name), "artifact_id": artifact_id},
)
assert linkage.found_in_hypergraph is True
assert linkage.ancestor_count is not None
missing = _resolve_provenance_linkage(
- REPO_ROOT,
- {"dataset_receipt_path": "receipts/VFY-DATA-000001", "artifact_id": "art:does_not_exist_12345"},
+ tmp_path,
+ {"dataset_receipt_path": str(data_receipt_file.parent.name), "artifact_id": "artifact:missing"},
)
assert missing.found_in_hypergraph is False
assert missing.ancestor_count is None
@@ -351,19 +591,13 @@ def test_blast_radius_revocation_flags_dependent_run_receipts(tmp_path):
consumed it (directly or via a downstream derivative) — composing dataset
provenance into run-receipt impact analysis rather than a parallel system.
"""
- data_receipt_dir = REPO_ROOT / "receipts" / "VFY-DATA-000001"
- data_receipt_file = data_receipt_dir / "receipt.json"
- if not data_receipt_file.exists():
- pytest.skip("VFY-DATA-000001 receipt not present in this checkout")
-
- data = json.loads(data_receipt_file.read_text(encoding="utf-8"))
- artifact_id = data["hypergraph"]["artifacts"][0]["artifact_id"]
+ data_receipt_file, artifact_id = _write_data_receipt(tmp_path)
proj = _write_tiny_project(tmp_path)
manifest = _base_manifest()
manifest["provenance_roots"] = [
{
- "dataset_receipt_path": str(data_receipt_dir),
+ "dataset_receipt_path": str(data_receipt_file.parent),
"artifact_id": artifact_id,
}
]
@@ -399,3 +633,65 @@ def test_blast_radius_revocation_flags_dependent_run_receipts(tmp_path):
matched_ids = {e["receipt_id"] for e in impacted_again}
assert "RUN-TEST-000" in matched_ids
assert "RUN-UNRELATED-000" not in matched_ids
+
+
+def test_repeated_provenance_reference_does_not_duplicate_impact(tmp_path):
+ data_receipt_file, artifact_id = _write_data_receipt(tmp_path)
+ proj = _write_tiny_project(tmp_path)
+ manifest = _base_manifest()
+ repeated = {
+ "dataset_receipt_path": str(data_receipt_file.parent),
+ "artifact_id": artifact_id,
+ }
+ manifest["provenance_roots"] = [repeated, dict(repeated)]
+ receipt = capture_run(manifest, manifest_dir=proj)
+ assert len(receipt.provenance_linkage) == 2
+ receipt.save_to_directory(tmp_path / "receipts_tree" / "RUN-TEST-000")
+
+ impacted = find_run_receipts_impacted_by_revocation(
+ search_root=tmp_path / "receipts_tree",
+ dataset_receipt_file=data_receipt_file,
+ revoked_artifact_id=artifact_id,
+ )
+
+ assert [item["receipt_id"] for item in impacted] == ["RUN-TEST-000"]
+
+
+def test_generic_run_facade_preserves_imports_across_bounded_modules() -> None:
+ from verifier.core import run
+
+ expected_modules = {
+ "load_manifest": "verifier.core.run_planning",
+ "describe_run_plan": "verifier.core.run_planning",
+ "validate_run_receipt": "verifier.core.run_validation",
+ "inspect_run_receipt": "verifier.core.run_inspection",
+ "reproduce_run_receipt": "verifier.core.run_reproduction",
+ "find_run_receipts_impacted_by_revocation": "verifier.core.run_impact",
+ }
+ assert run.capture_run.__module__ == "verifier.core.run"
+ for name, module_name in expected_modules.items():
+ assert getattr(run, name).__module__ == module_name
+
+
+def test_generic_run_mechanism_hash_binds_every_decomposed_module() -> None:
+ from verifier.core import run
+
+ module_names = (
+ "run.py",
+ "run_support.py",
+ "run_planning.py",
+ "run_validation.py",
+ "run_inspection.py",
+ "run_reproduction.py",
+ "run_impact.py",
+ )
+ directory = Path(run.__file__).resolve().parent
+ expected = run._implementation_inventory_digest(
+ tuple(directory / name for name in module_names)
+ )
+ binding = run._assessment_context({}, falsification_condition="fixture")
+
+ assert binding["verifier"]["implementation_hash"] == expected
+ assert binding["verifier"]["parser_hash"] == "sha256:" + hashlib.sha256(
+ (directory / "run_validation.py").read_bytes()
+ ).hexdigest()
diff --git a/tests/test_graph_level.py b/tests/test_graph_level.py
index 30e96b9..fb7e06d 100644
--- a/tests/test_graph_level.py
+++ b/tests/test_graph_level.py
@@ -1,15 +1,18 @@
-"""The VSTD-Graph axis: a computed level, and the proof of its ceiling.
+"""Terminology: Verifier Standard (VSTD).
-The level is never declared. Each test below pins one of the four conditions --
+The VSTD-Graph axis: a candidate profile over supplied ratings, and the proof of its ceiling.
+
+The candidate profile is never declared. Each test below pins one of the four conditions --
membership floor, provenance closure, status admissibility, edge evidence --
-and checks not just that the level dropped but that the certificate at ``N+1``
-*says why*. A level without that certificate would be an assertion, and the
+and checks not just that the profile number dropped but that the certificate at ``N+1``
+*says why*. A candidate profile without that certificate would be an assertion, and the
whole point of this axis is that a collection of well-rated members can still
be badly rated as a collection.
"""
from __future__ import annotations
+from dataclasses import replace
import importlib
import pytest
@@ -26,6 +29,7 @@
GRAPH_MAX_LEVEL,
GraphCollection,
GraphEncodingError,
+ GraphLevelResult,
INADMISSIBLE_STATUSES,
ObligationKind,
certify_graph_cnf,
@@ -38,11 +42,13 @@
ArtifactNode,
ArtifactStatus,
ArtifactType,
+ ConflictRecord,
HyperedgePort,
ProvenanceHypergraph,
TransformationHyperedge,
TransformationType,
)
+from verifier.data.policy import ProvenancePolicyVerifier
graph_module = importlib.import_module("verifier.data.graph_level")
@@ -51,7 +57,7 @@
def _binding() -> ClaimBinding:
return ClaimBinding(
- claim="corpus graph level",
+ claim="corpus candidate Graph profile",
coordinate=ClaimCoordinate("collection:C", "vstd_graph_level"),
policy_root="sha256:policy",
evidence_root="sha256:evidence",
@@ -170,15 +176,34 @@ def test_a_revoked_ancestor_disqualifies_the_collection_entirely():
_assert_certificates_check(result)
-@pytest.mark.parametrize("status", sorted(INADMISSIBLE_STATUSES, key=lambda s: s.value))
+@pytest.mark.parametrize("status", sorted(INADMISSIBLE_STATUSES - {"CONFLICTED"}))
def test_every_inadmissible_status_fails_closed(status):
- assert _level(_graph(mid=status), _collection()).level == 0
+ assert _level(_graph(mid=ArtifactStatus(status)), _collection()).level == 0
def test_superseded_is_admissible_and_documented_as_such():
"""A superseded ancestor was replaced going forward; its history is unchanged."""
- assert ArtifactStatus.SUPERSEDED not in INADMISSIBLE_STATUSES
- assert _level(_graph(src=ArtifactStatus.SUPERSEDED), _collection()).level == GRAPH_MAX_LEVEL
+ graph = _graph(src=ArtifactStatus.SUPERSEDED)
+ assert ArtifactStatus.SUPERSEDED.value not in INADMISSIBLE_STATUSES
+ assert _level(graph, _collection()).level == GRAPH_MAX_LEVEL
+ assert ProvenancePolicyVerifier.verify_all_ancestors_valid(graph, "corpus").passed is False
+
+
+@pytest.mark.parametrize(
+ "current_status",
+ (ArtifactStatus.CHALLENGED, ArtifactStatus.REVOKED, ArtifactStatus.STALE),
+)
+def test_current_admissibility_changes_without_rewriting_historical_graph(current_status):
+ historical = _graph()
+ historical_bytes = historical.to_dict()
+ assert _level(historical, _collection()).level == GRAPH_MAX_LEVEL
+
+ current = ProvenanceHypergraph.from_dict(historical_bytes)
+ current.artifacts["src"] = replace(current.artifacts["src"], status=current_status)
+
+ assert _level(current, _collection()).level == 0
+ assert historical.artifacts["src"].status is ArtifactStatus.VALID
+ assert historical.to_dict() == historical_bytes
def test_an_artifact_missing_from_the_graph_is_unknown_not_absent():
@@ -187,6 +212,19 @@ def test_an_artifact_missing_from_the_graph_is_unknown_not_absent():
assert _level(graph, _collection()).level == 0
+def test_cyclic_ancestry_cannot_receive_a_clean_candidate_level():
+ graph = _graph()
+ graph.add_transformation(
+ TransformationHyperedge(
+ "t3", "feedback", TransformationType.AUGMENTATION,
+ (HyperedgePort("corpus", "IN"),), (HyperedgePort("src", "OUT"),), {}, {}, {},
+ )
+ )
+
+ with pytest.raises(GraphEncodingError, match="cyclic recorded ancestry"):
+ _level(graph, _collection(edges={"t1": 5, "t2": 5, "t3": 5}))
+
+
# --------------------------------------------------------------------------
# The certificate at N+1 is the explanation
# --------------------------------------------------------------------------
@@ -211,10 +249,47 @@ def test_the_witness_and_the_refutation_are_different_certificates():
assert summary["witness_digest"] is not None
assert summary["refutation_digest"] is not None
assert summary["witness_digest"] != summary["refutation_digest"]
+ assert summary["rating_basis"] == "CALLER_SUPPLIED"
+ assert summary["conformance_status"] == "NOT_ESTABLISHED"
+
+
+def test_caller_cannot_promote_a_graph_candidate_to_conformance():
+ with pytest.raises(TypeError, match="conformance_status"):
+ GraphLevelResult(
+ "collection:x",
+ 5,
+ None,
+ None,
+ (),
+ conformance_status="ESTABLISHED", # type: ignore[call-arg]
+ )
+
+
+def test_conflicting_lineage_is_retained_and_blocks_a_clean_level():
+ graph = _graph()
+ graph.add_conflict(
+ ConflictRecord(
+ conflict_id="conflict:src-digest",
+ subject_id="src",
+ predicate="content_digest",
+ competing_values=("sha256:a", "sha256:b"),
+ evidence_refs=("receipt:a", "receipt:b"),
+ )
+ )
+
+ restored = ProvenanceHypergraph.from_dict(graph.to_dict())
+ assert restored.conflicts["conflict:src-digest"].competing_values == (
+ "sha256:a",
+ "sha256:b",
+ )
+ result = _level(restored, _collection())
+ assert result.level == 0
+ assert "caller-supplied ratings" in result.explanation
+ assert "conformance is not established" in result.explanation
def test_variable_numbering_is_stable_across_adjacent_levels():
- """Two levels of the same collection are comparable, not unrelated formulas."""
+ """Two candidate profiles of the same collection are comparable formulas."""
items = obligations(_graph(), _collection({"src": 5, "mid": 3, "corpus": 5}))
low, low_grounding = encode("collection:C", items, 3)
high, high_grounding = encode("collection:C", items, 4)
@@ -226,7 +301,7 @@ def test_variable_numbering_is_stable_across_adjacent_levels():
# --------------------------------------------------------------------------
-# Monotonicity, and the things the level must refuse to say
+# Monotonicity and the claims the candidate-profile computation must refuse
# --------------------------------------------------------------------------
@@ -251,7 +326,7 @@ def test_lowering_any_single_rating_can_only_lower_the_level():
def test_an_empty_collection_has_no_level():
- """Vacuous truth would hand out level 5 for a collection nobody can refute."""
+ """Vacuous truth would hand out candidate Graph-5 for an empty collection."""
with pytest.raises(GraphEncodingError, match="no members"):
_level(_graph(), GraphCollection("collection:empty", ()))
@@ -289,7 +364,7 @@ def test_encoding_divergence_raises_with_a_certificate_attached(monkeypatch):
def test_solver_divergence_is_caught_before_the_direct_check(monkeypatch):
- """The encoding and the independent solver must agree first, or nothing else counts."""
+ """The encoding and separately implemented solver must agree first."""
class ContrarySolver:
def __init__(self, **_kwargs):
@@ -301,7 +376,7 @@ def solve(self):
monkeypatch.setattr(graph_module, "MinimalIndependentDPLL", ContrarySolver)
items = obligations(_graph(), _collection())
- with pytest.raises(GraphEncodingError, match="independent solver said False"):
+ with pytest.raises(GraphEncodingError, match="separately implemented solver said False"):
certify_graph_cnf(
collection_id="collection:C", items=items, level=GRAPH_MAX_LEVEL,
binding=_binding(),
@@ -323,6 +398,6 @@ def test_every_graph_formula_is_horn_and_therefore_tier_up():
graph = _graph()
for rating in range(0, GRAPH_MAX_LEVEL + 1):
items = obligations(graph, _collection({"src": rating, "mid": rating, "corpus": rating}))
- for level in range(1, GRAPH_MAX_LEVEL + 1):
- formula, _grounding = encode("collection:C", items, level)
+ for candidate_profile in range(1, GRAPH_MAX_LEVEL + 1):
+ formula, _grounding = encode("collection:C", items, candidate_profile)
assert is_horn(formula)
diff --git a/tests/test_guilt_composition.py b/tests/test_guilt_composition.py
new file mode 100644
index 0000000..3a5a060
--- /dev/null
+++ b/tests/test_guilt_composition.py
@@ -0,0 +1,568 @@
+"""Adversarial tests for component-earned artifact-relative GUILT."""
+
+from __future__ import annotations
+
+import copy
+from dataclasses import replace
+import hashlib
+import json
+from pathlib import Path
+
+import pytest
+from jsonschema import Draft202012Validator
+from referencing import Registry, Resource
+
+from verifier.core.evidence import (
+ BoundProposition,
+ EvidenceBindingError,
+ EvidenceBounds,
+ EvidenceStore,
+ MechanismDecision,
+ MechanismOutcome,
+ VerificationSession,
+)
+from verifier.data.assurance import (
+ AssuranceFlowError,
+ AssuranceLedger,
+ DiagnosticKind,
+ ObligationCoordinate,
+ recheck_assurance_log,
+)
+from verifier.data.models import (
+ ArtifactNode,
+ ArtifactStatus,
+ ArtifactType,
+ HyperedgePort,
+ ProvenanceHypergraph,
+ TransformationHyperedge,
+ TransformationType,
+)
+
+
+class ExactFactMechanism:
+ mechanism_id = "test.guilt-exact-fact"
+ mechanism_digest = "sha256:" + hashlib.sha256(b"guilt-exact-fact:v1").hexdigest()
+
+ def evaluate(self, binding, evidence):
+ if len(evidence) != 1:
+ return MechanismDecision(MechanismOutcome.UNKNOWN, "one fact required")
+ try:
+ observed = json.loads(evidence[0])
+ except (UnicodeDecodeError, json.JSONDecodeError):
+ return MechanismDecision(MechanismOutcome.FAIL, "invalid fact")
+ expected = {
+ "subject_id": binding.subject_id,
+ "predicate": binding.predicate,
+ "expected": binding.expected,
+ }
+ outcome = MechanismOutcome.PASS if observed == expected else MechanismOutcome.FAIL
+ return MechanismDecision(outcome, f"exact fact: {outcome.value}")
+
+
+class CompoundExactFactMechanism(ExactFactMechanism):
+ mechanism_id = "test.guilt-compound-facts"
+ mechanism_digest = "sha256:" + hashlib.sha256(b"guilt-compound-facts:v1").hexdigest()
+
+ def __init__(self):
+ self.compound_calls = 0
+ self.ordinary_calls = 0
+
+ def evaluate(self, binding, evidence):
+ self.ordinary_calls += 1
+ return MechanismDecision(MechanismOutcome.UNKNOWN, "compound entry point required")
+
+ def evaluate_compound(self, bindings, evidence_sets):
+ self.compound_calls += 1
+ return tuple(
+ super(CompoundExactFactMechanism, self).evaluate(binding, evidence)
+ for binding, evidence in zip(bindings, evidence_sets)
+ )
+
+
+class GuiltRig:
+ def __init__(self):
+ graph = ProvenanceHypergraph()
+ for artifact_id in ("source", "middle", "result"):
+ graph.add_artifact(
+ ArtifactNode(
+ artifact_id,
+ artifact_id,
+ ArtifactType.MODEL,
+ hashlib.sha256(artifact_id.encode()).hexdigest(),
+ status=ArtifactStatus.VALID,
+ )
+ )
+ for identity, source, target in (
+ ("first", "source", "middle"),
+ ("second", "middle", "result"),
+ ):
+ graph.add_transformation(
+ TransformationHyperedge(
+ identity,
+ identity,
+ TransformationType.EVALUATION,
+ (HyperedgePort(source, "INPUT"),),
+ (HyperedgePort(target, "OUTPUT"),),
+ {},
+ {},
+ {},
+ )
+ )
+ self.ledger = AssuranceLedger(graph)
+ self.store = EvidenceStore()
+ self.session = VerificationSession(self.store)
+ self.session.register(ExactFactMechanism())
+ self.obligation = ObligationCoordinate(
+ obligation_id="obligation:integrity",
+ content_digest="sha256:" + "a" * 64,
+ scope={"policy": "fixture-v1", "realm": "test", "version": "1"},
+ assumptions=("fixture policy governs source",),
+ exclusions=("no legal-liability conclusion",),
+ )
+ self.rust, self.localization = self.localize("source", "D1", minute=0)
+
+ def proposition(
+ self,
+ subject,
+ predicate,
+ expected,
+ *,
+ outcome=MechanismOutcome.PASS,
+ mechanism=ExactFactMechanism,
+ parameters=None,
+ ):
+ payload = json.dumps(
+ {"subject_id": subject, "predicate": predicate, "expected": expected},
+ sort_keys=True,
+ separators=(",", ":"),
+ ).encode()
+ reference = self.store.add(payload)
+ proposition = BoundProposition(
+ subject,
+ predicate,
+ expected,
+ mechanism.mechanism_id,
+ mechanism.mechanism_digest,
+ (reference,),
+ ("test:guilt-policy",),
+ EvidenceBounds(1, 20_000),
+ parameters or {},
+ )
+ if outcome is MechanismOutcome.UNKNOWN:
+ return replace(proposition, bounds=EvidenceBounds(0, 20_000))
+ if outcome is MechanismOutcome.FAIL:
+ wrong = self.store.add(
+ json.dumps(
+ {"subject_id": subject, "predicate": predicate, "expected": "neighbor"},
+ sort_keys=True,
+ separators=(",", ":"),
+ ).encode()
+ )
+ return replace(proposition, evidence_refs=(wrong,))
+ return proposition
+
+ def localize(self, ancestor, deviation_id, *, minute):
+ deviation = self.proposition(
+ "result",
+ "vstd.graph.descendant_deviation",
+ True,
+ parameters={"deviation_id": deviation_id},
+ )
+ rust = self.ledger.record_rust(
+ "result",
+ deviation,
+ session=self.session,
+ recorded_at=f"2026-08-29T10:{minute:02d}:00Z",
+ )
+ expected = {
+ "ancestor": ancestor,
+ "descendant": "result",
+ "rust_event_digest": rust.digest(),
+ "deviation_binding_digest": deviation.digest(),
+ }
+ localization = self.ledger.localize_cause(
+ ancestor,
+ "result",
+ self.proposition("result", "vstd.graph.causal_localization", expected),
+ rust_event_digest=rust.digest(),
+ session=self.session,
+ recorded_at=f"2026-08-29T10:{minute + 1:02d}:00Z",
+ )
+ return rust, localization
+
+ def responsibility(self, *, ancestor="source", localization=None, outcome=MechanismOutcome.PASS):
+ localization = localization or self.localization
+ expected = {
+ "ancestor_id": ancestor,
+ "descendant_id": "result",
+ "localization_event_digest": localization.digest(),
+ "rust_event_digest": localization.attributes["rust_event_digest"],
+ "deviation_binding_digest": localization.attributes["deviation_binding_digest"],
+ }
+ return self.ledger.establish_responsibility(
+ ancestor,
+ "result",
+ self.proposition(
+ ancestor, "vstd.graph.responsibility", expected, outcome=outcome
+ ),
+ localization_event_digest=localization.digest(),
+ session=self.session,
+ recorded_at="2026-08-29T10:10:00Z",
+ )
+
+ def applicability(self, *, artifact="source", obligation=None, outcome=MechanismOutcome.PASS):
+ obligation = obligation or self.obligation
+ expected = {
+ "artifact_id": artifact,
+ "obligation_coordinate": obligation.to_dict(),
+ }
+ proposition = self.proposition(
+ artifact,
+ "vstd.graph.obligation_applicability",
+ expected,
+ outcome=outcome,
+ )
+ event = self.ledger.establish_obligation_applicability(
+ artifact,
+ obligation,
+ proposition,
+ session=self.session,
+ recorded_at="2026-08-29T10:11:00Z",
+ )
+ return proposition, event
+
+ def violation(
+ self,
+ applicability,
+ *,
+ artifact="source",
+ obligation=None,
+ localization=None,
+ outcome=MechanismOutcome.PASS,
+ ):
+ obligation = obligation or self.obligation
+ localization = localization or self.localization
+ expected = {
+ "artifact_id": artifact,
+ "descendant_id": "result",
+ "localization_event_digest": localization.digest(),
+ "rust_event_digest": localization.attributes["rust_event_digest"],
+ "deviation_binding_digest": localization.attributes["deviation_binding_digest"],
+ "obligation_coordinate": obligation.to_dict(),
+ "applicability_binding_digest": applicability.attributes["binding_digest"],
+ }
+ return self.ledger.establish_obligation_violation(
+ artifact,
+ "result",
+ obligation,
+ self.proposition(
+ artifact, "vstd.graph.obligation_violation", expected, outcome=outcome
+ ),
+ localization_event_digest=localization.digest(),
+ applicability_component_digest=applicability.digest(),
+ session=self.session,
+ recorded_at="2026-08-29T10:12:00Z",
+ )
+
+ def compose(self, responsibility=None, applicability=None, violation=None, *, digests=None):
+ component_digests = digests or (
+ responsibility.digest() if responsibility else "1" * 64,
+ applicability.digest() if applicability else "2" * 64,
+ violation.digest() if violation else "3" * 64,
+ )
+ expected = {
+ "ancestor_id": "source",
+ "descendant_id": "result",
+ "localization_event_digest": self.localization.digest(),
+ "obligation_coordinate": self.obligation.to_dict(),
+ "responsibility_component_digest": component_digests[0],
+ "applicability_component_digest": component_digests[1],
+ "violation_component_digest": component_digests[2],
+ }
+ return self.ledger.compose_guilt(
+ "source",
+ "result",
+ self.obligation,
+ self.proposition("source", "vstd.graph.diagnostic.guilt", expected),
+ localization_event_digest=self.localization.digest(),
+ responsibility_component_digest=component_digests[0],
+ applicability_component_digest=component_digests[1],
+ violation_component_digest=component_digests[2],
+ session=self.session,
+ recorded_at="2026-08-29T10:13:00Z",
+ )
+
+ def passing_components(self):
+ responsibility = self.responsibility()
+ _, applicability = self.applicability()
+ violation = self.violation(applicability)
+ return responsibility, applicability, violation
+
+
+def test_opaque_or_incomplete_components_never_establish_guilt():
+ rig = GuiltRig()
+ decorative = rig.proposition(
+ "source",
+ "vstd.graph.diagnostic.guilt",
+ {
+ "ancestor": "source",
+ "descendant": "result",
+ "localization_event_digest": rig.localization.digest(),
+ "violated_obligation": "obligation:decorative",
+ },
+ parameters={"obligation": "obligation:decorative"},
+ )
+ opaque = rig.ledger.diagnose(
+ DiagnosticKind.GUILT,
+ "source",
+ "result",
+ decorative,
+ session=rig.session,
+ recorded_at="2026-08-29T10:09:00Z",
+ )
+ assert opaque.status == "NOT_ESTABLISHED" and opaque.evaluation is None
+
+ responsibility = rig.responsibility()
+ assert rig.compose(responsibility).status == "NOT_ESTABLISHED"
+ _, applicability = rig.applicability()
+ assert rig.compose(responsibility, applicability).status == "NOT_ESTABLISHED"
+ assert not any(event.attributes.get("diagnostic_kind") == "GUILT" for event in rig.ledger.events())
+
+
+@pytest.mark.parametrize(
+ ("component", "outcome"),
+ [
+ ("responsibility", MechanismOutcome.FAIL),
+ ("responsibility", MechanismOutcome.UNKNOWN),
+ ("applicability", MechanismOutcome.FAIL),
+ ("applicability", MechanismOutcome.UNKNOWN),
+ ("violation", MechanismOutcome.FAIL),
+ ("violation", MechanismOutcome.UNKNOWN),
+ ],
+)
+def test_fail_or_unknown_component_never_composes(component, outcome):
+ rig = GuiltRig()
+ responsibility = rig.responsibility(
+ outcome=outcome if component == "responsibility" else MechanismOutcome.PASS
+ )
+ _, applicability = rig.applicability(
+ outcome=outcome if component == "applicability" else MechanismOutcome.PASS
+ )
+ violation = None
+ if applicability.outcome is MechanismOutcome.PASS:
+ violation = rig.violation(
+ applicability,
+ outcome=outcome if component == "violation" else MechanismOutcome.PASS,
+ )
+ result = rig.compose(responsibility, applicability, violation)
+ assert result.status == "NOT_ESTABLISHED" and result.evaluation is None
+
+
+def test_neighboring_artifact_obligation_deviation_or_localization_cannot_compose():
+ rig = GuiltRig()
+ responsibility, applicability, _ = rig.passing_components()
+ _, neighbor_app = rig.applicability(artifact="middle")
+ assert rig.compose(responsibility, neighbor_app).status == "NOT_ESTABLISHED"
+
+ other = ObligationCoordinate(
+ obligation_id="obligation:neighbor",
+ scope={"policy": "neighbor", "realm": "test"},
+ )
+ _, other_app = rig.applicability(obligation=other)
+ assert rig.compose(responsibility, other_app).status == "NOT_ESTABLISHED"
+
+ _, middle_localization = rig.localize("middle", "D-middle", minute=20)
+ _, middle_app = rig.applicability(artifact="middle")
+ middle_violation = rig.violation(
+ middle_app, artifact="middle", localization=middle_localization
+ )
+ assert rig.compose(responsibility, applicability, middle_violation).status == "NOT_ESTABLISHED"
+
+ _, second_localization = rig.localize("source", "D2", minute=30)
+ second_violation = rig.violation(applicability, localization=second_localization)
+ assert rig.compose(responsibility, applicability, second_violation).status == "NOT_ESTABLISHED"
+
+ other_violation = rig.violation(other_app, obligation=other)
+ assert rig.compose(responsibility, applicability, other_violation).status == "NOT_ESTABLISHED"
+
+
+def test_exact_components_and_existing_blame_can_each_supply_responsibility():
+ rig = GuiltRig()
+ responsibility, applicability, violation = rig.passing_components()
+ direct = rig.compose(responsibility, applicability, violation)
+ assert direct.status == "ESTABLISHED"
+
+ second = GuiltRig()
+ blame_expected = {
+ "ancestor": "source",
+ "descendant": "result",
+ "localization_event_digest": second.localization.digest(),
+ }
+ blame = second.ledger.diagnose(
+ DiagnosticKind.BLAME,
+ "source",
+ "result",
+ second.proposition("source", "vstd.graph.diagnostic.blame", blame_expected),
+ session=second.session,
+ recorded_at="2026-08-29T10:10:00Z",
+ )
+ assert blame.status == "ESTABLISHED"
+ blame_event = second.ledger.events()[-1]
+ _, second_applicability = second.applicability()
+ second_violation = second.violation(second_applicability)
+ assert second.compose(blame_event, second_applicability, second_violation).status == "ESTABLISHED"
+
+
+def test_one_compound_invocation_emits_three_bound_results_and_replays_once():
+ rig = GuiltRig()
+ compound = CompoundExactFactMechanism()
+ rig.session.register(compound)
+ responsibility_expected = {
+ "ancestor_id": "source",
+ "descendant_id": "result",
+ "localization_event_digest": rig.localization.digest(),
+ "rust_event_digest": rig.localization.attributes["rust_event_digest"],
+ "deviation_binding_digest": rig.localization.attributes["deviation_binding_digest"],
+ }
+ applicability_expected = {
+ "artifact_id": "source",
+ "obligation_coordinate": rig.obligation.to_dict(),
+ }
+ responsibility_proposition = rig.proposition(
+ "source", "vstd.graph.responsibility", responsibility_expected,
+ mechanism=CompoundExactFactMechanism,
+ )
+ applicability_proposition = rig.proposition(
+ "source", "vstd.graph.obligation_applicability", applicability_expected,
+ mechanism=CompoundExactFactMechanism,
+ )
+ violation_expected = {
+ "artifact_id": "source",
+ "descendant_id": "result",
+ "localization_event_digest": rig.localization.digest(),
+ "rust_event_digest": rig.localization.attributes["rust_event_digest"],
+ "deviation_binding_digest": rig.localization.attributes["deviation_binding_digest"],
+ "obligation_coordinate": rig.obligation.to_dict(),
+ "applicability_binding_digest": applicability_proposition.digest(),
+ }
+ violation_proposition = rig.proposition(
+ "source", "vstd.graph.obligation_violation", violation_expected,
+ mechanism=CompoundExactFactMechanism,
+ )
+ components = rig.ledger.establish_guilt_components(
+ "source",
+ "result",
+ rig.obligation,
+ responsibility_proposition,
+ applicability_proposition,
+ violation_proposition,
+ localization_event_digest=rig.localization.digest(),
+ session=rig.session,
+ recorded_at="2026-08-29T10:10:00Z",
+ )
+ assert compound.compound_calls == 1 and compound.ordinary_calls == 0
+ assert all(event.outcome is MechanismOutcome.PASS for event in components)
+ assert len({event.attributes["binding_digest"] for event in components}) == 3
+ assert rig.compose(*components).status == "ESTABLISHED"
+
+ replay_mechanism = CompoundExactFactMechanism()
+ payload = rig.ledger.to_dict()
+ replayed = recheck_assurance_log(
+ payload, mechanisms=(ExactFactMechanism(), replay_mechanism)
+ )
+ assert replayed.to_dict() == payload
+ assert replay_mechanism.compound_calls == 1 and replay_mechanism.ordinary_calls == 0
+
+
+def _passing_payload():
+ rig = GuiltRig()
+ components = rig.passing_components()
+ result = rig.compose(*components)
+ assert result.status == "ESTABLISHED"
+ return rig, components, result, rig.ledger.to_dict()
+
+
+def test_schema_and_replay_accept_exact_component_log():
+ _, _, _, payload = _passing_payload()
+ root = Path(__file__).resolve().parents[1]
+ schema = json.loads((root / "standard/schemas/vstd-graph-assurance-1.schema.json").read_text())
+ graph_schema = json.loads((root / "receipts/schema/vstd_graph_receipt.json").read_text())
+ registry = Registry().with_resource(graph_schema["$id"], Resource.from_contents(graph_schema))
+ Draft202012Validator(schema, registry=registry).validate(payload)
+ assert recheck_assurance_log(payload, mechanisms=(ExactFactMechanism(),)).to_dict() == payload
+
+
+def test_duplicate_component_references_do_not_manufacture_strength():
+ rig = GuiltRig()
+ responsibility = rig.responsibility()
+ duplicate = responsibility.digest()
+ result = rig.compose(digests=(duplicate, duplicate, duplicate))
+ assert result.status == "NOT_ESTABLISHED" and "distinct" in result.details
+
+
+@pytest.mark.parametrize(
+ "field",
+ ["responsibility_component_digest", "applicability_component_digest", "violation_component_digest"],
+)
+def test_replay_refuses_changed_component_digest(field):
+ _, _, _, payload = _passing_payload()
+ guilt = next(event for event in payload["events"] if event["attributes"].get("diagnostic_kind") == "GUILT")
+ guilt["attributes"][field] = "f" * 64
+ with pytest.raises(AssuranceFlowError):
+ recheck_assurance_log(payload, mechanisms=(ExactFactMechanism(),))
+
+
+@pytest.mark.parametrize(
+ ("kind", "path", "value"),
+ [
+ ("OBLIGATION_APPLICABILITY", ("artifact_id",), "middle"),
+ ("OBLIGATION_APPLICABILITY", ("obligation_coordinate", "obligation_id"), "obligation:neighbor"),
+ ("OBLIGATION_APPLICABILITY", ("obligation_coordinate", "scope", "policy"), "neighbor"),
+ ("OBLIGATION_VIOLATION", ("descendant_id",), "middle"),
+ ("OBLIGATION_VIOLATION", ("localization_event_digest",), "e" * 64),
+ ],
+)
+def test_replay_refuses_changed_artifact_obligation_scope_deviation_or_localization(kind, path, value):
+ _, _, _, payload = _passing_payload()
+ event = next(item for item in payload["events"] if item["kind"] == kind)
+ target = event["attributes"]
+ for key in path[:-1]:
+ target = target[key]
+ target[path[-1]] = value
+ with pytest.raises(AssuranceFlowError):
+ recheck_assurance_log(payload, mechanisms=(ExactFactMechanism(),))
+
+
+def test_replay_refuses_changed_evidence_outcome_or_event_order():
+ _, _, _, original = _passing_payload()
+ evidence = copy.deepcopy(original)
+ applicability = next(event for event in evidence["events"] if event["kind"] == "OBLIGATION_APPLICABILITY")
+ reference = next(iter(applicability["evidence_payloads"]))
+ applicability["evidence_payloads"][reference] = "bmVpZ2hib3I="
+ with pytest.raises(EvidenceBindingError):
+ recheck_assurance_log(evidence, mechanisms=(ExactFactMechanism(),))
+
+ outcome = copy.deepcopy(original)
+ next(event for event in outcome["events"] if event["kind"] == "OBLIGATION_VIOLATION")["outcome"] = "FAIL"
+ with pytest.raises(AssuranceFlowError):
+ recheck_assurance_log(outcome, mechanisms=(ExactFactMechanism(),))
+
+ order = copy.deepcopy(original)
+ first = next(index for index, event in enumerate(order["events"]) if event["kind"] == "RESPONSIBILITY_COMPONENT")
+ second = next(index for index, event in enumerate(order["events"]) if event["kind"] == "OBLIGATION_APPLICABILITY")
+ order["events"][first], order["events"][second] = order["events"][second], order["events"][first]
+ with pytest.raises(AssuranceFlowError):
+ recheck_assurance_log(order, mechanisms=(ExactFactMechanism(),))
+
+
+def test_result_never_renders_innocence_morality_reputation_or_legal_liability():
+ _, _, result, _ = _passing_payload()
+ rendered = json.dumps(result.to_dict(), sort_keys=True).lower()
+ for prohibited in (
+ "innocence",
+ "moral culpability",
+ "actor reputation",
+ "general actor trust",
+ "legal liability",
+ ):
+ assert prohibited not in rendered
diff --git a/tests/test_independent_checker.py b/tests/test_independent_checker.py
index 638ddab..e70de01 100644
--- a/tests/test_independent_checker.py
+++ b/tests/test_independent_checker.py
@@ -1,13 +1,19 @@
-"""Unit tests for the independent VSTD SAT solver and Grounding checker."""
+"""Terminology: Boolean satisfiability problem (SAT); unsatisfiable (UNSAT);
+Verifier Standard (VSTD).
+
+Unit tests for the bundled VSTD SAT solver and grounding checker."""
from __future__ import annotations
from verifier.core.checker import (
GroundingVerdict,
+ IndependenceBasis,
+ IndependenceStatus,
IndependentGroundingChecker,
IndependentAuditor,
MinimalIndependentDPLL,
VerificationVerdict,
+ independence_is_evidenced,
)
@@ -117,6 +123,40 @@ def test_independent_auditor_end_to_end() -> None:
expected_satisfiable=True,
)
assert audit.overall_verdict == VerificationVerdict.VERIFIED
+ assert audit.independence_basis.independently_verified is False
+ assert audit.to_dict()["independence_basis"]["actor_independence"] == (
+ "NOT_DEMONSTRATED"
+ )
+ assert audit.to_dict()["independence_basis"]["runtime_separation"] == (
+ "NOT_DEMONSTRATED"
+ )
assert audit.sat_result.satisfiable is True
assert audit.grounding_result.grounding_status == GroundingVerdict.GROUNDED
assert "MinimalIndependentDPLL" in audit.trusted_computing_base["solver"]
+
+
+def test_matching_checker_runs_do_not_establish_actor_independence() -> None:
+ arguments = {
+ "claim_id": "TEST-REPEAT",
+ "n_vars": 1,
+ "clauses": [[1]],
+ "atomic_reasons": [],
+ "expected_satisfiable": True,
+ }
+ first = IndependentAuditor.audit_claim_derivation(**arguments)
+ second = IndependentAuditor.audit_claim_derivation(**arguments)
+ assert first.overall_verdict == second.overall_verdict
+ assert not first.independence_basis.independently_verified
+ assert not second.independence_basis.independently_verified
+
+
+def test_serialized_evidence_references_cannot_self_promote_independence() -> None:
+ basis = IndependenceBasis(
+ actor_independence=IndependenceStatus.EVIDENCED,
+ implementation_separation=IndependenceStatus.EVIDENCED,
+ runtime_separation=IndependenceStatus.EVIDENCED,
+ evidence=("receipt:producer", "receipt:checker"),
+ )
+ assert not basis.independently_verified
+ raw = basis.to_dict()
+ assert not independence_is_evidenced(raw)
diff --git a/tests/test_layer4.py b/tests/test_layer4.py
index f4963ad..9e632d1 100644
--- a/tests/test_layer4.py
+++ b/tests/test_layer4.py
@@ -1,4 +1,6 @@
-"""Rungs 4.8 through 4.14 -- the parts of layer 4 that are not the kernel.
+"""Terminology: Verifier Standard (VSTD).
+
+Rungs 4.8 through 4.14 -- the parts of VSTD-4 outside the kernel.
Each test here pins one of the challenge-theater prohibitions:
@@ -18,7 +20,7 @@
import pytest
from verifier.core.certificate import ClaimCoordinate
-from verifier.data.models import ArtifactStatus
+from verifier.data.models import ArtifactNode, ArtifactStatus, ArtifactType, ProvenanceHypergraph
from verifier.hardware.anchors import AnchorError, LocalAnchorProvider
from verifier.layer4.availability import (
ArtifactAvailability,
@@ -426,6 +428,27 @@ def test_a_credible_challenge_actually_moves_verdict_state():
assert ledger.status("claim:1").status is ArtifactStatus.REVOKED
+def test_challenge_records_do_not_silently_mutate_graph_state():
+ graph = ProvenanceHypergraph()
+ graph.add_artifact(
+ ArtifactNode(
+ "claim:1",
+ "claim",
+ ArtifactType.MODEL,
+ "a" * 64,
+ status=ArtifactStatus.VALID,
+ )
+ )
+ ledger = ChallengeLedger()
+ ledger.file(_challenge(), _surface())
+ ledger.adjudicate(
+ Adjudication("ch:1", ChallengeOutcome.ACCEPTED, "confirmed", "2026-02-02T00:00:00Z")
+ )
+
+ assert ledger.status("claim:1").status is ArtifactStatus.REVOKED
+ assert graph.artifacts["claim:1"].status is ArtifactStatus.VALID
+
+
def test_a_disproven_challenge_returns_the_claim_to_valid():
ledger = ChallengeLedger()
ledger.file(_challenge(), _surface())
@@ -567,6 +590,7 @@ def test_the_output_is_capped_by_its_weakest_link():
check = closure.validate()
assert check.accepted is True
assert check.closed_depth == 9 # not 14, not the average, not the transformation
+ assert check.conformance_status == "NOT_ESTABLISHED"
def test_refutability_does_not_increase_under_composition():
diff --git a/tests/test_logits_constraint_kernel.py b/tests/test_logits_constraint_kernel.py
index 706f821..33a9372 100644
--- a/tests/test_logits_constraint_kernel.py
+++ b/tests/test_logits_constraint_kernel.py
@@ -1,4 +1,6 @@
-"""Real logits-level constraint tests against llguidance, not an engine simulation."""
+"""Terminology: JavaScript Object Notation (JSON); Verifier Standard (VSTD).
+
+Real logits-level constraint tests against llguidance, not an engine simulation."""
from __future__ import annotations
diff --git a/tests/test_packaged_specifications.py b/tests/test_packaged_specifications.py
index b3e03ac..2765048 100644
--- a/tests/test_packaged_specifications.py
+++ b/tests/test_packaged_specifications.py
@@ -1,4 +1,6 @@
-"""Installed specification resources must match the public normative files exactly."""
+"""Terminology: Request for Comments (RFC); Verifier Standard (VSTD).
+
+Installed specification resources must match the public normative files exactly."""
from __future__ import annotations
@@ -9,7 +11,30 @@
def test_packaged_specification_bytes_match_normative_sources() -> None:
- for name in ("LADDER.md", "VSTD-3.md", "VSTD-4.md", "WIRE_IDENTIFIERS.md"):
- normative = REPO_ROOT / "standard" / name
- packaged = REPO_ROOT / "src" / "verifier" / "specifications" / name
- assert packaged.read_bytes() == normative.read_bytes(), name
+ normative_files = sorted((REPO_ROOT / "standard").glob("*.md"))
+ packaged_dir = REPO_ROOT / "src" / "verifier" / "specifications"
+ assert {path.name for path in packaged_dir.glob("*.md")} == {
+ path.name for path in normative_files
+ }
+ for normative in normative_files:
+ assert (packaged_dir / normative.name).read_bytes() == normative.read_bytes(), (
+ normative.name
+ )
+
+
+def test_ladder_fixes_causal_provenance_directions_without_actor_trust() -> None:
+ ladder = (REPO_ROOT / "standard" / "LADDER.md").read_text(encoding="utf-8")
+ assert "ancestor artifact --TRUST through a checked transformation--> descendant" in ladder
+ assert "recorded TRUST --ROT under typed current-state evidence--> reassessment" in ladder
+ assert "descendant deviation --RUST memetic causal backtrace--> ancestor candidates" in ladder
+ assert "Memetic propagation" in ladder
+ assert "RFC 2119" in ladder
+ assert "RFC 8174" in ladder
+ assert "serialize as typed event kinds only in the non-receipt\n`VSTD-GRAPH-ASSURANCE-1` mechanism log" in ladder
+ assert "`AssuranceLedger` implements mechanism-earned forward TRUST" in ladder
+ assert "`recheck_assurance_log` reconstructs the historical Graph" in ladder
+ assert "MUST NOT strengthen an artifact-bound\nresult" in ladder
+ assert "TRUST and RUST never cancel" in ladder
+ assert "whether an actor is good, bad, reputable, or worthy of trust" in ladder
+ assert "zero unevidenced knowledge is presumed" in ladder
+ assert "cryptographic zero knowledge" in ladder
diff --git a/tests/test_presentation_surface.py b/tests/test_presentation_surface.py
index eb670b9..01c54ae 100644
--- a/tests/test_presentation_surface.py
+++ b/tests/test_presentation_surface.py
@@ -1,15 +1,37 @@
-"""The public first impression is a checked repository surface."""
+"""Terminology: application programming interface (API); Concise Binary Object Representation (CBOR);
+CBOR Object Signing and Encryption (COSE); continuous integration (CI); Hypertext Markup Language (HTML);
+Supply Chain Integrity, Transparency, and Trust (SCITT); uniform resource locator (URL);
+Verifier Standard (VSTD).
+
+The public first impression is a checked repository surface."""
from __future__ import annotations
+from html.parser import HTMLParser
import importlib.util
import json
from pathlib import Path
+import sys
+from urllib.parse import unquote
+
+import yaml
ROOT = Path(__file__).resolve().parents[1]
+class _BuiltPageLinks(HTMLParser):
+ def __init__(self) -> None:
+ super().__init__()
+ self.links: list[str] = []
+
+ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
+ attributes = dict(attrs)
+ for name in ("href", "src"):
+ if attributes.get(name):
+ self.links.append(attributes[name])
+
+
def test_professional_presentation_surface_has_no_drift() -> None:
path = ROOT / "scripts/check_presentation.py"
spec = importlib.util.spec_from_file_location("check_presentation", path)
@@ -19,6 +41,55 @@ def test_professional_presentation_surface_has_no_drift() -> None:
assert module.run() == []
+def test_acronym_gate_rejects_missing_and_late_first_use(tmp_path: Path) -> None:
+ path = ROOT / "scripts/check_acronyms.py"
+ spec = importlib.util.spec_from_file_location("check_acronyms_fixture", path)
+ assert spec is not None and spec.loader is not None
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+
+ docs = tmp_path / "docs"
+ docs.mkdir()
+ glossary = docs / "ACRONYMS.md"
+ glossary.write_text(
+ "| Term | Expansion | Note |\n"
+ "|---|---|---|\n"
+ "| `API` | application programming interface | interface |\n"
+ "| `VSTD` | Verifier Standard | standard |\n",
+ encoding="utf-8",
+ )
+ readme = tmp_path / "README.md"
+ readme.write_text("# VSTD API\n\nVerifier Standard (VSTD).\n", encoding="utf-8")
+ module.ROOT = tmp_path
+ module.GLOSSARY = glossary
+
+ errors = module.validate_repo()
+ assert any("VSTD appears before its expansion" in error for error in errors)
+ assert any("API is not expanded" in error for error in errors)
+
+ readme.write_text(
+ "# Verifier Standard (VSTD) application programming interface (API)\n",
+ encoding="utf-8",
+ )
+ assert module.validate_repo() == []
+
+
+def test_terminology_gate_rejects_interchangeable_structural_terms() -> None:
+ path = ROOT / "scripts/check_terminology.py"
+ spec = importlib.util.spec_from_file_location("check_terminology_fixture", path)
+ assert spec is not None and spec.loader is not None
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+
+ ambiguous = "Graph level 3 depends on lower-layer conformance."
+ labels = [label for label, _line in module.terminology_violations(ambiguous)]
+ assert "Graph profile called a level" in labels
+ assert "profile dependency called lower-layer" in labels
+ assert module.terminology_violations(
+ "Candidate Graph profile 3 depends on prerequisite-profile conformance."
+ ) == []
+
+
def test_public_boundary_catches_private_coordinates_without_naming_them() -> None:
path = ROOT / "scripts" / "check_presentation.py"
spec = importlib.util.spec_from_file_location("check_presentation_boundaries", path)
@@ -36,6 +107,85 @@ def test_public_boundary_catches_private_coordinates_without_naming_them() -> No
assert "private deployment field" in module.public_boundary_violations(deployment_field)
+def test_maturity_table_requires_each_major_surface_and_explicit_conformance() -> None:
+ path = ROOT / "scripts" / "check_presentation.py"
+ spec = importlib.util.spec_from_file_location("check_presentation_maturity", path)
+ assert spec is not None and spec.loader is not None
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+
+ readme = (ROOT / "README.md").read_text(encoding="utf-8")
+ assert module.maturity_table_violations(readme) == []
+
+ combined = readme.replace("| VSTD-Graph-3 |", "| VSTD-Graph-2 |", 1)
+ errors = module.maturity_table_violations(combined)
+ assert any("VSTD-Graph-2" in error and "observed 2" in error for error in errors)
+ assert any("VSTD-Graph-3" in error and "observed 0" in error for error in errors)
+
+
+def test_artifact_state_vocabulary_is_process_bound_and_unambiguous() -> None:
+ ladder = (ROOT / "standard" / "LADDER.md").read_text(encoding="utf-8")
+ humans = (ROOT / "HUMANS.md").read_text(encoding="utf-8")
+ agents = (ROOT / "AGENTS.md").read_text(encoding="utf-8")
+ architecture = (ROOT / "docs" / "ARCHITECTURE.md").read_text(encoding="utf-8")
+
+ for text in (ladder, humans, agents, architecture):
+ assert "TRUST" in text
+ assert "ROT" in text
+ assert "RUST" in text
+ assert "whether an actor is good, bad" in ladder
+ assert "zero unevidenced knowledge is presumed" in ladder
+ assert "bearer- and artifact-bound, never prover-identity-bound" in ladder
+ assert "inverse-TRUST diagnostic mechanic" in ladder
+ assert "historical receipt" in ladder
+ assert "actor ratings" in architecture
+
+
+def test_standard_orients_readers_before_formal_terminology() -> None:
+ ladder = (ROOT / "standard" / "LADDER.md").read_text(encoding="utf-8")
+ orientation = ladder.index("### Read this first:")
+ terminology = ladder.index("### Terminology contract")
+ assert orientation < terminology
+ assert "A field, document, or actor merely saying" in ladder
+ assert "Its **object profile depth is 1**" in ladder
+ assert "The cumulative checklist cannot skip the missing" in ladder
+ assert "not a new verdict, evidence-strength rating" in ladder
+
+
+def test_object_receipts_use_full_ladder_identifiers() -> None:
+ vstd1 = json.loads(
+ (ROOT / "receipts" / "schema" / "vstd1_receipt.json").read_text(encoding="utf-8")
+ )
+ generic = json.loads(
+ (ROOT / "receipts" / "schema" / "vstd1_generic_run_receipt.json").read_text(
+ encoding="utf-8"
+ )
+ )
+ vstd2 = json.loads(
+ (ROOT / "receipts" / "schema" / "vstd2_receipt.json").read_text(encoding="utf-8")
+ )
+
+ assert vstd1["properties"]["schema_version"]["enum"] == ["VSTD-1"]
+ assert vstd1["properties"]["receipt_kind"]["const"] == "claim_mechanics"
+ assert generic["properties"]["schema_version"]["const"] == "VSTD-1"
+ assert generic["properties"]["receipt_kind"]["const"] == "generic_computational_run"
+ assert vstd2["properties"]["schema_version"]["const"] == "VSTD-2"
+
+
+def test_long_lived_docs_reject_transient_time_state() -> None:
+ path = ROOT / "scripts" / "check_presentation.py"
+ spec = importlib.util.spec_from_file_location("check_presentation_time", path)
+ assert spec is not None and spec.loader is not None
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+
+ assert module.transient_time_status_violations("TIME.md is CLEAR today")
+ assert module.transient_time_status_violations("TIME == OPEN")
+ assert module.transient_time_status_violations(
+ "TIME.md is a contradiction annunciator"
+ ) == []
+
+
def test_lineage_claim_gate_rejects_causal_upgrades_without_blocking_boundaries() -> None:
path = ROOT / "scripts" / "check_presentation.py"
spec = importlib.util.spec_from_file_location("check_presentation_lineage", path)
@@ -69,9 +219,34 @@ def test_pages_artifact_serves_every_canonical_schema_id(tmp_path: Path) -> None
spec.loader.exec_module(module)
output = tmp_path / "site"
- copied = module.build(output)
+ copied = module.build(output, source_ref="test-commit")
assert (output / "index.html").is_file()
- sources = sorted((ROOT / "receipts/schema").glob("*.json"))
+ assert (output / "guides.html").is_file()
+ assert (output / "reference.html").is_file()
+ assert (output / "docs/QUICKSTART.html").is_file()
+ assert (output / "standard/index.html").is_file()
+ assert (output / "standard/ARTIFACT_CONTROL.html").is_file()
+ assert (output / "experiments/index.html").is_file()
+ assert (output / "project/ROADMAP.html").is_file()
+ assert (output / "assets/orientation-previews.js").is_file()
+ coordinate = json.loads(
+ (output / "documentation-coordinate.json").read_text(encoding="utf-8")
+ )
+ assert coordinate == {
+ "canonical_base_url": "https://timelordraps.github.io/verifier/",
+ "documentation_version": "1.2.0",
+ "normative_source": "standard/",
+ "release_state": "UNRELEASED_CANDIDATE",
+ "schema_version": 1,
+ "source_ref": "test-commit",
+ }
+ sources = sorted(
+ (
+ *ROOT.joinpath("receipts/schema").glob("*.json"),
+ *ROOT.joinpath("standard/schemas").glob("*.json"),
+ ),
+ key=lambda path: path.name,
+ )
assert [path.name for path in copied] == [path.name for path in sources]
for source, deployed in zip(sources, copied):
assert deployed.read_bytes() == source.read_bytes()
@@ -80,6 +255,262 @@ def test_pages_artifact_serves_every_canonical_schema_id(tmp_path: Path) -> None
)
+def test_every_declared_document_is_rendered_with_source_aware_navigation(
+ tmp_path: Path,
+) -> None:
+ path = ROOT / "scripts/build_docs.py"
+ spec = importlib.util.spec_from_file_location("build_docs_coverage", path)
+ assert spec is not None and spec.loader is not None
+ module = importlib.util.module_from_spec(spec)
+ sys.modules[spec.name] = module
+ try:
+ spec.loader.exec_module(module)
+ finally:
+ sys.modules.pop(spec.name, None)
+
+ output = tmp_path / "site"
+ output.mkdir()
+ written = module.build(output, source_ref="test-commit")
+ declared = module.documents()
+ assert len(written) == len(declared)
+ for document in declared:
+ target = output / Path(document.route.as_posix())
+ assert target.is_file(), f"documentation omits {document.source.relative_to(ROOT)}"
+ for target in written:
+ assert "\x00" not in target.read_text(encoding="utf-8")
+
+ ladder = (output / "standard/index.html").read_text(encoding="utf-8")
+ assert 'class="doc-sidebar"' in ladder
+ assert "On this page" in ladder
+ assert '>Standard' in ladder
+ assert '>Specifications' not in ladder
+ assert 'href="VSTD-1.html"' in ladder
+ assert 'href="../docs/CONCEPTS_AND_PRECEDENTS.html"' in ladder
+ assert (
+ 'Concept guide and '
+ 'intellectual precedents ' in ladder
+ )
+ releasing = (output / "project/RELEASING.html").read_text(encoding="utf-8")
+ assert 'Run ' in releasing
+ assert ' Confirm python scripts/check_time_status.py passes' in releasing
+ assert 'Push the tag ' in releasing
+ assert 'Let Zenodo ' in releasing
+ assert (
+ 'concept guide '
+ in ladder
+ )
+ assert (
+ "github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md"
+ not in ladder
+ )
+ assert "/blob/test-commit/standard/LADDER.md" in ladder
+ assert "without changing its status" in ladder
+ assert "Evidence for one closure coordinate never supplies evidence for another." in ladder
+ assert "An UNKNOWN is never a pass" in ladder
+ assert 'data-orientation-preview="repository"' in ladder
+ assert 'data-orientation-concept="Defense in depth"' in ladder
+ assert 'src="../assets/orientation-previews.js" defer' in ladder
+
+ readme = (output / "project/README.html").read_text(encoding="utf-8")
+ assert 'href="../docs/QUICKSTART.html"' in readme
+ assert 'href="../standard/index.html"' in readme
+
+ concepts = (output / "docs/CONCEPTS_AND_PRECEDENTS.html").read_text(
+ encoding="utf-8"
+ )
+ assert 'class="orientation-link"' in concepts
+ assert 'data-orientation-preview="repository"' in concepts
+ assert 'data-orientation-concept="Assurance"' in concepts
+ assert (
+ 'data-orientation-definition="VSTD reports evidence-bounded results, '
+ 'not universal confidence or institutional accreditation."' in concepts
+ )
+ assert (
+ 'data-orientation-boundary="Wikipedia orientation; not a VSTD authority"'
+ in concepts
+ )
+ assert 'rel="noreferrer"' in concepts
+ assert 'src="../assets/orientation-previews.js" defer' in concepts
+ assert 'href="../reference.html#api-compute_canonical_digest"' in concepts
+ assert 'href="../reference.html#api-ReproducibilityLevel"' in concepts
+ assert 'href="../reference.html#api-DecisionCertificate"' in concepts
+
+ quickstart = (output / "docs/QUICKSTART.html").read_text(encoding="utf-8")
+ assert "orientation-previews.js" not in quickstart
+
+
+def test_orientation_previews_are_bounded_and_fail_to_ordinary_links() -> None:
+ source = (ROOT / "docs/CONCEPTS_AND_PRECEDENTS.md").read_text(encoding="utf-8")
+ script = (ROOT / "docs/assets/orientation-previews.js").read_text(encoding="utf-8")
+
+ assert "short definition is versioned in" in source
+ assert "popup performs no" in source
+ assert "data-orientation-preview=\"repository\"" in script
+ assert "dataset.orientationDefinition" in script
+ assert "Repository definition · versioned with VSTD" in script
+ assert "optional external background" in script
+ assert "fetch(" not in script
+ assert "w/api.php" not in script
+ assert ".textContent = text" in script
+
+
+def test_generated_api_reference_has_documented_supported_exports() -> None:
+ reference = (ROOT / "docs/reference.html").read_text(encoding="utf-8")
+
+ assert "No docstring is declared for this export" not in reference
+ assert "Canonical grounded decision certificate (GDC) blocks" in reference
+ assert "canonically digested VSTD-1 claim receipt" in reference
+ assert "Validate one generic-run receipt" in reference
+ assert "Outcome vocabulary returned by the VSTD-1 claim-mechanics checker" in reference
+
+
+def test_assembled_site_has_no_broken_internal_navigation(tmp_path: Path) -> None:
+ path = ROOT / "scripts/build_pages.py"
+ spec = importlib.util.spec_from_file_location("build_pages_links", path)
+ assert spec is not None and spec.loader is not None
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+
+ output = tmp_path / "site"
+ module.build(output, source_ref="test-commit")
+ canonical = "https://timelordraps.github.io/verifier/"
+ for source in output.rglob("*.html"):
+ parser = _BuiltPageLinks()
+ parser.feed(source.read_text(encoding="utf-8"))
+ for raw in parser.links:
+ if raw.startswith(canonical):
+ relative = unquote(raw.removeprefix(canonical).split("#", 1)[0])
+ target = output / relative
+ if not relative or relative.endswith("/"):
+ target /= "index.html"
+ elif raw.startswith(("#", "http://", "https://", "mailto:", "data:")):
+ continue
+ else:
+ relative = unquote(raw.split("#", 1)[0])
+ target = source.parent / relative
+ if relative.endswith("/"):
+ target /= "index.html"
+ assert target.exists(), f"{source.relative_to(output)} links missing {raw}"
+
+
+def test_guides_keep_repository_documentation_inside_the_site() -> None:
+ guides = (ROOT / "docs/guides.html").read_text(encoding="utf-8")
+ assert 'Standard ' in guides
+ assert '>Specifications' not in guides
+ assert 'href="docs/QUICKSTART.html"' in guides
+ assert 'href="standard/"' in guides
+ assert 'href="experiments/"' in guides
+ assert 'href="project/ROADMAP.html"' in guides
+ assert "github.com/TimeLordRaps/verifier/blob/main/docs/" not in guides
+ assert "github.com/TimeLordRaps/verifier/blob/main/standard/" not in guides
+
+
+def test_pages_explains_artifact_first_state_without_actor_ratings() -> None:
+ page = (ROOT / "docs" / "index.html").read_text(encoding="utf-8")
+ assert "Verify the process, not the actor." in page
+ assert "TRUST · FORWARD" in page
+ assert "ROT · CURRENT STATE" in page
+ assert "RUST · BACKWARD" in page
+ assert "cryptographic zero knowledge can enclose" in page
+ assert "not acronyms or actor ratings" in page
+
+
+def test_architecture_map_names_every_published_schema() -> None:
+ architecture = (ROOT / "docs" / "ARCHITECTURE.md").read_text(encoding="utf-8")
+ for schema in (ROOT / "receipts" / "schema").glob("*.json"):
+ assert schema.name in architecture, schema.name
+
+
+def test_conformance_gate_requires_real_scitt_cose_integration() -> None:
+ workflow = yaml.safe_load(
+ (ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8")
+ )
+ jobs = workflow["jobs"]
+ scitt_job = jobs["scitt-crypto"]
+ steps = "\n".join(str(step.get("run", "")) for step in scitt_job["steps"])
+ assert 'pip install ".[test,scitt]"' in steps
+ assert "import cbor2, cryptography, scitt_cose" in steps
+ assert "tests/test_scitt_crypto_example.py" in steps
+ assert "scitt-crypto" in jobs["conformance-gate"]["needs"]
+
+
+def test_repository_checks_do_not_self_certify_conformance() -> None:
+ readme = (ROOT / "README.md").read_text(encoding="utf-8")
+ guides = (ROOT / "docs" / "guides.html").read_text(encoding="utf-8")
+ workflow_text = (ROOT / ".github" / "workflows" / "ci.yml").read_text(
+ encoding="utf-8"
+ )
+ workflow = yaml.safe_load(workflow_text)
+
+ assert "[![Conformance]" not in readme
+ assert "[![Repository checks]" in readme
+ assert workflow["name"] == "repository-checks"
+ assert "trace poisoned ancestry" not in guides
+ assert "examples/zizk_artifact_first" in guides
+
+
+def test_pull_requests_retain_a_commit_addressed_pages_preview() -> None:
+ workflow = yaml.safe_load(
+ (ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8")
+ )
+ presentation = workflow["jobs"]["presentation"]
+ commands = "\n".join(str(step.get("run", "")) for step in presentation["steps"])
+ uploads = [
+ step
+ for step in presentation["steps"]
+ if "upload-artifact@" in step.get("uses", "")
+ ]
+
+ assert '--source-ref "$GITHUB_SHA"' in commands
+ assert uploads[0]["with"]["name"] == "pages-preview-${{ github.sha }}"
+ assert uploads[0]["with"]["path"] == "_site"
+
+
+def test_codeql_is_pinned_and_required_by_the_protected_gate() -> None:
+ workflow = yaml.safe_load(
+ (ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8")
+ )
+ jobs = workflow["jobs"]
+ codeql = jobs["codeql"]
+ uses = [str(step.get("uses", "")) for step in codeql["steps"]]
+
+ assert any(
+ item
+ == "github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938"
+ for item in uses
+ )
+ assert any(
+ item
+ == "github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938"
+ for item in uses
+ )
+ assert "codeql" in jobs["conformance-gate"]["needs"]
+
+
+def test_branch_coverage_is_retained_without_a_global_threshold() -> None:
+ workflow = yaml.safe_load(
+ (ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8")
+ )
+ jobs = workflow["jobs"]
+ coverage = jobs["coverage"]
+ commands = "\n".join(str(step.get("run", "")) for step in coverage["steps"])
+ uploads = [
+ step for step in coverage["steps"] if "upload-artifact@" in step.get("uses", "")
+ ]
+
+ assert "coverage run --branch --source=src/verifier" in commands
+ assert "coverage report --show-missing" in commands
+ assert "coverage json --pretty-print -o coverage.json" in commands
+ assert "coverage xml -o coverage.xml" in commands
+ assert "--fail-under" not in commands
+ assert uploads[0]["with"]["name"] == "branch-coverage-python-3.12"
+ assert set(uploads[0]["with"]["path"].splitlines()) == {
+ "coverage.json",
+ "coverage.xml",
+ }
+ assert "coverage" in jobs["conformance-gate"]["needs"]
+
+
def test_pages_builder_refuses_to_merge_into_existing_content(tmp_path: Path) -> None:
path = ROOT / "scripts/build_pages.py"
spec = importlib.util.spec_from_file_location("build_pages_safety", path)
@@ -98,3 +529,52 @@ def test_pages_builder_refuses_to_merge_into_existing_content(tmp_path: Path) ->
else:
raise AssertionError("Pages builder merged into non-empty output")
assert marker.read_text(encoding="utf-8") == "keep\n"
+
+
+def test_generated_reference_covers_commands_and_top_level_exports() -> None:
+ """The docs tab is generated and must list its declared live surface."""
+
+ path = ROOT / "scripts/build_reference.py"
+ spec = importlib.util.spec_from_file_location("build_reference_coverage", path)
+ assert spec is not None and spec.loader is not None
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+
+ page = (ROOT / "docs/reference.html").read_text(encoding="utf-8")
+ assert page == module.render()
+
+ import verifier
+ from verifier.runtime.public_cli import build_parser
+
+ for command in module._walk(build_parser()):
+ anchor = 'id="cli-' + str(command["prog"]).replace(" ", "-") + '"'
+ assert anchor in page, f"reference page omits {command['prog']}"
+ for name in verifier.__all__:
+ assert f'id="api-{name}"' in page, f"reference page omits export {name}"
+ assert verifier.__standard__ == "VSTD-5"
+ assert (
+ verifier.__standard_status__
+ == "PROJECT SPECIFICATION; EVIDENCE-BOUND REFERENCE MECHANISM"
+ )
+ assert "VSTD-5 PROJECT SPECIFICATION; EVIDENCE-BOUND REFERENCE MECHANISM" in page
+ assert "Monotone reproduction-fidelity states" in page
+
+
+def test_generated_reference_detects_drift() -> None:
+ path = ROOT / "scripts/build_reference.py"
+ spec = importlib.util.spec_from_file_location("build_reference_drift", path)
+ assert spec is not None and spec.loader is not None
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+
+ module.PIPELINE = ((
+ "vstd ghost",
+ "A command that no longer exists.",
+ ("verifier.core.run:not_a_real_entry_point",),
+ ),)
+ try:
+ module.render()
+ except module.ReferenceBuildError:
+ pass
+ else:
+ raise AssertionError("reference build published a missing pipeline entry point")
diff --git a/tests/test_public_api.py b/tests/test_public_api.py
new file mode 100644
index 0000000..da8ccfd
--- /dev/null
+++ b/tests/test_public_api.py
@@ -0,0 +1,97 @@
+"""Supported Python application programming interface (API) characterization tests."""
+
+from __future__ import annotations
+
+import inspect
+from pathlib import Path
+
+import pytest
+
+import verifier
+
+
+ROOT = Path(__file__).resolve().parents[1]
+EXPECTED_EXPORTS = {
+ "AssuranceLedger",
+ "ArtifactControlError",
+ "ArtifactVerification",
+ "BoundProposition",
+ "EvidenceBindingError",
+ "DecisionCertificate",
+ "EvidenceBounds",
+ "EvidenceStore",
+ "MechanismDecision",
+ "MechanismOutcome",
+ "ObligationCoordinate",
+ "ProvenanceHypergraph",
+ "ReproducibilityLevel",
+ "VerificationSession",
+ "VerificationGeometry",
+ "VerificationVerdict",
+ "VstdReceipt",
+ "WitnessBundle",
+ "assess_witness_corroboration",
+ "build_evidence_bound_graph_level_record",
+ "build_evidence_bound_vstd4_receipt",
+ "build_vstd5_receipt",
+ "capture_run",
+ "certificate_from_canonical_bytes",
+ "compute_canonical_digest",
+ "claim_binding_from_dict",
+ "establish_graph_level",
+ "establish_vstd4",
+ "freeze_artifact",
+ "graph_collection_binding_digest",
+ "recheck_assurance_log",
+ "recheck_evidence_bound_graph_level_record",
+ "recheck_evidence_bound_vstd4_receipt",
+ "recheck_vstd5_receipt",
+ "require_vstd5_entry",
+ "seal_artifact",
+ "thaw_artifact",
+ "thawed_artifact_status",
+ "validate_run_receipt",
+ "vstd4_depth",
+ "verify_frozen_artifact",
+}
+
+
+def test_supported_top_level_exports_are_explicit_and_resolvable() -> None:
+ assert set(verifier.__all__) == EXPECTED_EXPORTS
+ assert set(verifier._LAZY_EXPORTS) == EXPECTED_EXPORTS
+ for name in verifier.__all__:
+ assert getattr(verifier, name) is not None
+
+
+def test_thaw_status_public_api_requires_explicit_parent_evidence_for_establishment() -> None:
+ parameters = inspect.signature(verifier.thawed_artifact_status).parameters
+ assert tuple(parameters) == (
+ "artifact",
+ "thaw_record",
+ "parent_bundle",
+ "expected_artifact_id",
+ "expected_key_id",
+ )
+ assert parameters["parent_bundle"].kind is inspect.Parameter.KEYWORD_ONLY
+
+
+def test_deprecation_registry_warns_without_replacing_the_export(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ name = "capture_run"
+ monkeypatch.delattr(verifier, name, raising=False)
+ monkeypatch.setitem(verifier._API_DEPRECATIONS, name, ("1.3.0", "replacement_name"))
+
+ with pytest.warns(DeprecationWarning, match="deprecated since 1.3.0"):
+ resolved = getattr(verifier, name)
+
+ assert resolved is verifier._LAZY_EXPORTS[name] or callable(resolved)
+
+
+def test_every_declared_deprecation_is_supported_and_release_noted() -> None:
+ changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
+ for name, (since, replacement) in verifier._API_DEPRECATIONS.items():
+ assert name in verifier.__all__
+ assert since in changelog
+ assert name in changelog
+ assert replacement in changelog
diff --git a/tests/test_public_cli.py b/tests/test_public_cli.py
index b0fa70d..d9a3a31 100644
--- a/tests/test_public_cli.py
+++ b/tests/test_public_cli.py
@@ -1,4 +1,6 @@
-"""Tests for the target-neutral public CLI surface."""
+"""Terminology: command-line interface (CLI); Verifier Standard (VSTD).
+
+Tests for the target-neutral public CLI surface."""
from __future__ import annotations
@@ -46,6 +48,14 @@ def test_public_parser_has_no_target_specific_generation_commands() -> None:
assert parser.parse_args(["data", "export", "receipt.json"]).data_command == "export"
assert parser.parse_args(["plan", "manifest.json"]).command == "plan"
assert parser.parse_args(["demo"]).command == "demo"
+ assert (
+ parser.parse_args(["artifact", "verify", "bundle"]).artifact_command
+ == "verify"
+ )
+ assert (
+ parser.parse_args(["experiment", "validate", "experiment.json"]).experiment_command
+ == "validate"
+ )
def test_public_cli_flagship_demo_is_side_effect_free_and_machine_readable(
@@ -115,6 +125,31 @@ def test_public_cli_generic_run_lifecycle(tmp_path: Path, capsys) -> None:
assert "[UNSANDBOXED EXECUTION]" in capsys.readouterr().err
+def test_generic_receipt_validate_and_inspect_honor_json(tmp_path: Path, capsys) -> None:
+ manifest = _manifest(tmp_path)
+ receipt_dir = tmp_path / "receipt"
+ assert main(["run", str(manifest), "--output", str(receipt_dir)]) == 0
+ capsys.readouterr()
+
+ for command in ("validate", "inspect"):
+ assert main([command, str(receipt_dir), "--json"]) == 0
+ result = json.loads(capsys.readouterr().out)
+ assert result["command"] == command
+ assert result["receipt_kind"] == "generic_computational_run"
+ assert result["result"] == "COMPLETED"
+ assert result["exit_code"] == 0
+
+
+def test_unknown_receipt_failure_honors_json(tmp_path: Path, capsys) -> None:
+ path = tmp_path / "receipt.json"
+ path.write_text('{"schema_version": "UNKNOWN"}', encoding="utf-8")
+
+ assert main(["validate", str(path), "--json"]) == 1
+ result = json.loads(capsys.readouterr().out)
+ assert result["result"] == "FAILED"
+ assert result["errors"] == ["Unsupported receipt kind or schema"]
+
+
def test_public_cli_rejects_unknown_receipt(tmp_path: Path) -> None:
path = tmp_path / "receipt.json"
path.write_text('{"schema_version": "UNKNOWN"}', encoding="utf-8")
diff --git a/tests/test_public_data.py b/tests/test_public_data.py
index be8e56e..267c336 100644
--- a/tests/test_public_data.py
+++ b/tests/test_public_data.py
@@ -1,11 +1,19 @@
-"""Target-neutral VSTD-DATA receipt validation and mechanism replay."""
+"""Terminology: Verifier Standard (VSTD).
+
+Target-neutral VSTD-DATA receipt validation and mechanism replay."""
from __future__ import annotations
+import json
+from dataclasses import replace
from pathlib import Path
+import pytest
+from jsonschema import Draft202012Validator
+
from verifier.core.checker import VerificationVerdict
from verifier.core.provenance import GitProvenance, ProvenanceRecord, RuntimeEnvironment
+from verifier.core.receipt import compute_canonical_digest
from verifier.data.models import (
ArtifactNode,
ArtifactStatus,
@@ -16,6 +24,7 @@
TransformationHyperedge,
TransformationType,
)
+from verifier.data.assurance import AssuranceFlowError, AssuranceLedger
from verifier.data.policy import ProvenancePolicyVerifier
from verifier.data.receipt import (
DataIndependentAudit,
@@ -24,6 +33,7 @@
reproduce_data_receipt,
validate_data_receipt,
)
+from verifier.runtime.public_cli import _inspect_data_receipt, main
def _receipt() -> VstdDataReceipt:
@@ -105,12 +115,94 @@ def _receipt() -> VstdDataReceipt:
)
-def test_public_data_receipt_round_trip(tmp_path: Path) -> None:
+def _rehash(payload: dict) -> None:
+ provenance = payload["provenance"]
+ payload["canonical_digest"] = compute_canonical_digest(
+ {
+ "schema_version": payload["schema_version"],
+ "receipt_id": payload["receipt_id"],
+ "dataset_spec": payload["dataset_spec"],
+ "hypergraph": payload["hypergraph"],
+ "completeness_metrics": payload["completeness_metrics"],
+ "policy_evaluations": payload["policy_evaluations"],
+ "independent_audit": payload["independent_audit"],
+ "provenance_stable": {
+ "target_name": provenance["target_name"],
+ "portable_repository_id": provenance["portable_repository_id"],
+ "git_commit_sha": provenance["git"]["commit_sha"],
+ "git_branch": provenance["git"]["branch"],
+ "git_is_dirty": provenance["git"]["is_dirty"],
+ "runtime_python_version": provenance["runtime"]["python_version"],
+ },
+ "reproducibility": payload["reproducibility"],
+ }
+ )
+
+
+def test_public_data_receipt_round_trip(tmp_path: Path, capsys) -> None:
_receipt().save_to_directory(tmp_path)
assert validate_data_receipt(tmp_path) == 0
+ assert "[VALIDATION OK]" in capsys.readouterr().out
assert reproduce_data_receipt(tmp_path) == 0
+def test_graph_validate_and_inspect_honor_json(tmp_path: Path, capsys) -> None:
+ _receipt().save_to_directory(tmp_path)
+
+ for command in ("validate", "inspect"):
+ assert main([command, str(tmp_path), "--json"]) == 0
+ result = json.loads(capsys.readouterr().out)
+ assert result["command"] == command
+ assert result["receipt_kind"] == "vstd_graph"
+ assert result["result"] == "COMPLETED"
+ assert result["exit_code"] == 0
+
+
+def test_actorless_independence_upgrade_is_rejected_and_never_displayed(
+ tmp_path: Path, capsys
+) -> None:
+ receipt_path = _receipt().save_to_directory(tmp_path)
+ payload = json.loads(receipt_path.read_text(encoding="utf-8"))
+ payload["independent_audit"]["independence_basis"][
+ "independently_verified"
+ ] = True
+ _rehash(payload)
+ receipt_path.write_text(json.dumps(payload), encoding="utf-8")
+
+ assert _inspect_data_receipt(tmp_path) == 0
+ assert "Independence: NOT_DEMONSTRATED" in capsys.readouterr().out
+ assert main(["validate", str(tmp_path)]) == 1
+ assert "no actor/execution evidence-binding validator" in capsys.readouterr().err
+
+
+def test_self_promoted_independence_with_arbitrary_references_is_rejected(
+ tmp_path: Path, capsys
+) -> None:
+ receipt_path = _receipt().save_to_directory(tmp_path)
+ payload = json.loads(receipt_path.read_text(encoding="utf-8"))
+ basis = payload["independent_audit"]["independence_basis"]
+ basis.update(
+ {
+ "actor_independence": "EVIDENCED",
+ "implementation_separation": "EVIDENCED",
+ "runtime_separation": "EVIDENCED",
+ "evidence": ["receipt:producer", "receipt:checker"],
+ "independently_verified": True,
+ }
+ )
+ _rehash(payload)
+ receipt_path.write_text(json.dumps(payload), encoding="utf-8")
+
+ assert main(["validate", str(tmp_path)]) == 1
+ errors = capsys.readouterr().err
+ assert "no actor/execution evidence-binding validator" in errors
+ assert "no stronger than DECLARED" in errors
+ assert main(["inspect", str(tmp_path)]) == 0
+ inspection = capsys.readouterr().out
+ assert "Independence: NOT_DEMONSTRATED" in inspection
+ assert "Independence: EVIDENCED" not in inspection
+
+
def test_public_data_receipt_tamper_fails(tmp_path: Path) -> None:
receipt_path = _receipt().save_to_directory(tmp_path)
receipt_path.write_text(receipt_path.read_text(encoding="utf-8") + " ", encoding="utf-8")
@@ -130,6 +222,141 @@ def test_missing_artifact_status_defaults_to_unknown() -> None:
assert artifact.status == ArtifactStatus.UNKNOWN
+def test_duplicate_graph_identifier_cannot_replace_recorded_evidence() -> None:
+ graph = ProvenanceHypergraph()
+ original = ArtifactNode(
+ artifact_id="artifact:duplicate",
+ label="original",
+ artifact_type=ArtifactType.RAW_SOURCE_FILE,
+ content_digest="a" * 64,
+ )
+ graph.add_artifact(original)
+
+ with pytest.raises(ValueError, match="duplicate graph identifier"):
+ graph.add_artifact(
+ ArtifactNode(
+ artifact_id="artifact:duplicate",
+ label="replacement",
+ artifact_type=ArtifactType.RAW_SOURCE_FILE,
+ content_digest="b" * 64,
+ )
+ )
+
+ assert graph.artifacts["artifact:duplicate"] is original
+
+
+def test_artifact_and_transformation_identifiers_are_globally_disjoint() -> None:
+ graph = ProvenanceHypergraph()
+ graph.add_artifact(
+ ArtifactNode(
+ artifact_id="shared:id",
+ label="artifact",
+ artifact_type=ArtifactType.RAW_SOURCE_FILE,
+ content_digest="a" * 64,
+ )
+ )
+ collision = TransformationHyperedge(
+ transformation_id="shared:id",
+ label="transformation",
+ transformation_type=TransformationType.EVALUATION,
+ inputs=(HyperedgePort("shared:id", "INPUT"),),
+ outputs=(HyperedgePort("shared:id", "OUTPUT"),),
+ software_provenance={},
+ parameters={},
+ execution_environment={},
+ )
+ with pytest.raises(ValueError, match="identifiers must be disjoint"):
+ graph.add_transformation(collision)
+
+ reverse = ProvenanceHypergraph()
+ reverse.add_transformation(collision)
+ with pytest.raises(ValueError, match="identifiers must be disjoint"):
+ reverse.add_artifact(graph.artifacts["shared:id"])
+
+ graph.transformations[collision.transformation_id] = collision
+ assert graph.validate_structure()[0] == (
+ "artifact and transformation identifiers must be disjoint: shared:id"
+ )
+
+
+def test_frozen_graph_reader_preserves_separate_identifier_namespaces(
+ tmp_path: Path, capsys
+) -> None:
+ artifact = ArtifactNode(
+ artifact_id="artifact:input",
+ label="input",
+ artifact_type=ArtifactType.RAW_SOURCE_FILE,
+ content_digest="a" * 64,
+ )
+ output = ArtifactNode(
+ artifact_id="artifact:output",
+ label="output",
+ artifact_type=ArtifactType.EVALUATION_REPORT,
+ content_digest="b" * 64,
+ )
+ transformation = TransformationHyperedge(
+ transformation_id="artifact:input",
+ label="historical overlapping identifier",
+ transformation_type=TransformationType.EVALUATION,
+ inputs=(HyperedgePort("artifact:input", "INPUT"),),
+ outputs=(HyperedgePort("artifact:output", "OUTPUT"),),
+ software_provenance={},
+ parameters={},
+ execution_environment={},
+ )
+ payload = {
+ "artifacts": [artifact.to_dict(), output.to_dict()],
+ "transformations": [transformation.to_dict()],
+ "contributors": [],
+ "rights": [],
+ "conflicts": [],
+ }
+ graph_schema = json.loads(
+ (
+ Path(__file__).resolve().parents[1]
+ / "receipts/schema/vstd_graph_receipt.json"
+ ).read_text()
+ )["properties"]["hypergraph"]
+ Draft202012Validator(graph_schema).validate(payload)
+
+ restored = ProvenanceHypergraph.from_dict(payload)
+ assert restored.to_dict() == payload
+ assert restored.validate_structure(
+ allow_legacy_identifier_overlap=True
+ ) == []
+ assert restored.validate_structure()[0] == (
+ "artifact and transformation identifiers must be disjoint: artifact:input"
+ )
+ with pytest.raises(ValueError, match="identifiers must be disjoint"):
+ ProvenanceHypergraph.from_dict(
+ payload, allow_legacy_identifier_overlap=False
+ )
+ duplicate_artifact = {
+ **payload,
+ "artifacts": [*payload["artifacts"], artifact.to_dict()],
+ }
+ with pytest.raises(ValueError, match="duplicate graph identifier"):
+ ProvenanceHypergraph.from_dict(duplicate_artifact)
+ with pytest.raises(AssuranceFlowError, match="invalid source graph"):
+ AssuranceLedger(restored)
+
+ receipt = _receipt()
+ receipt.hypergraph = restored
+ receipt.completeness_metrics = restored.compute_completeness()
+ receipt.independent_audit = replace(
+ receipt.independent_audit,
+ acyclic_hypergraph=True,
+ integrity_passed=True,
+ root_sources_count=1,
+ terminal_outputs_count=1,
+ transformations_count=1,
+ )
+ receipt.save_to_directory(tmp_path)
+ assert validate_data_receipt(tmp_path) == 0
+ assert "[VALIDATION OK]" in capsys.readouterr().out
+ assert reproduce_data_receipt(tmp_path) == 0
+
+
def test_completeness_rejects_non_hex_digest() -> None:
graph = ProvenanceHypergraph()
graph.add_artifact(
diff --git a/tests/test_refutation_certificate.py b/tests/test_refutation_certificate.py
index 21ce6d3..b4fe77e 100644
--- a/tests/test_refutation_certificate.py
+++ b/tests/test_refutation_certificate.py
@@ -1,4 +1,7 @@
-"""VSTD layer 4: refusals must carry certificates a stranger can check.
+"""Terminology: conjunctive normal form (CNF); Boolean satisfiability problem (SAT);
+unsatisfiable (UNSAT); Verifier Standard (VSTD).
+
+VSTD-4 Refutability: refusals must carry certificates a stranger can check.
The property under test is not merely that the solver is correct. It is that an
UNSAT verdict ships an artifact an independent party validates *without*
@@ -153,7 +156,7 @@ def _random_cnf(rng: random.Random, n_vars: int, n_clauses: int) -> list[list[in
def test_agrees_with_the_existing_solver_and_every_refusal_is_certified():
"""Cross-check against MinimalIndependentDPLL over many random 3-CNF instances.
- Fixed seed: this must be reproducible, per VSTD layer 1.
+ Fixed seed: this must be reproducible under VSTD-1 Claim Mechanics.
"""
rng = random.Random(20260822)
unsat_seen = 0
diff --git a/tests/test_release_artifacts.py b/tests/test_release_artifacts.py
index 98f0cf7..7b54aca 100644
--- a/tests/test_release_artifacts.py
+++ b/tests/test_release_artifacts.py
@@ -1,4 +1,6 @@
-"""The public source archive must bind exact, publicly resolvable Git bytes."""
+"""Terminology: Verifier Standard (VSTD); ZIP archive format (ZIP).
+
+The public source archive must bind exact, publicly resolvable Git bytes."""
from __future__ import annotations
@@ -19,12 +21,21 @@
REPO_ROOT = Path(__file__).resolve().parents[1]
SCRIPT = REPO_ROOT / "scripts" / "release_artifacts.py"
RELEASE_WORKFLOW = REPO_ROOT / ".github" / "workflows" / "release.yml"
+TIME_GATE = REPO_ROOT / "scripts" / "check_time_status.py"
+RELEASE_METADATA_GATE = REPO_ROOT / "scripts" / "check_release_metadata.py"
SPEC = importlib.util.spec_from_file_location("vstd_release_artifacts", SCRIPT)
assert SPEC is not None and SPEC.loader is not None
release_artifacts = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(release_artifacts)
+METADATA_SPEC = importlib.util.spec_from_file_location(
+ "vstd_release_metadata", RELEASE_METADATA_GATE
+)
+assert METADATA_SPEC is not None and METADATA_SPEC.loader is not None
+release_metadata = importlib.util.module_from_spec(METADATA_SPEC)
+METADATA_SPEC.loader.exec_module(release_metadata)
+
def test_source_release_manifest_binds_head_and_exact_archive_bytes(tmp_path: Path) -> None:
result = subprocess.run(
@@ -130,16 +141,16 @@ def test_repository_url_spellings_are_canonical(raw: str, expected: str) -> None
def _write_raw_wheel(path: Path, *, newline: bytes, reverse: bool) -> None:
- dist_info = "verifier_standard-1.1.3.dist-info"
+ dist_info = "verifier_standard-1.2.0.dist-info"
members = [
- ("verifier/__init__.py", b'__version__ = "1.1.3"\n'),
+ ("verifier/__init__.py", b'__version__ = "1.2.0"\n'),
(
f"{dist_info}/METADATA",
newline.join(
[
b"Metadata-Version: 2.4",
b"Name: verifier-standard",
- b"Version: 1.1.3",
+ b"Version: 1.2.0",
b"",
b"Canonical metadata.",
b"",
@@ -200,9 +211,9 @@ def test_wheel_normalization_removes_host_newlines_and_zip_metadata(tmp_path: Pa
infos = bundle.infolist()
assert all(info.create_system == 3 for info in infos)
assert all(info.compress_type == zipfile.ZIP_STORED for info in infos)
- metadata_name = "verifier_standard-1.1.3.dist-info/METADATA"
+ metadata_name = "verifier_standard-1.2.0.dist-info/METADATA"
assert b"\r" not in bundle.read(metadata_name)
- record_name = "verifier_standard-1.1.3.dist-info/RECORD"
+ record_name = "verifier_standard-1.2.0.dist-info/RECORD"
rows = list(csv.reader(io.StringIO(bundle.read(record_name).decode("utf-8"))))
records = {row[0]: row[1:] for row in rows}
for info in infos:
@@ -217,7 +228,7 @@ def test_wheel_normalization_removes_host_newlines_and_zip_metadata(tmp_path: Pa
def _write_raw_sdist(path: Path, *, newline: bytes, reverse: bool) -> None:
- root = "verifier_standard-1.1.3"
+ root = "verifier_standard-1.2.0"
members = [
(
f"{root}/PKG-INFO",
@@ -225,7 +236,7 @@ def _write_raw_sdist(path: Path, *, newline: bytes, reverse: bool) -> None:
[
b"Metadata-Version: 2.4",
b"Name: verifier-standard",
- b"Version: 1.1.3",
+ b"Version: 1.2.0",
b"",
]
),
@@ -237,7 +248,7 @@ def _write_raw_sdist(path: Path, *, newline: bytes, reverse: bool) -> None:
[
b"Metadata-Version: 2.4",
b"Name: verifier-standard",
- b"Version: 1.1.3",
+ b"Version: 1.2.0",
b"",
]
),
@@ -272,7 +283,7 @@ def test_sdist_normalization_removes_host_newlines_and_tar_metadata(tmp_path: Pa
with tarfile.open(first, "r:gz") as bundle:
files = {member.name: member for member in bundle.getmembers()}
- root = "verifier_standard-1.1.3"
+ root = "verifier_standard-1.2.0"
metadata = bundle.extractfile(files[f"{root}/PKG-INFO"])
assert metadata is not None and b"\r" not in metadata.read()
readme = bundle.extractfile(files[f"{root}/README.md"])
@@ -295,6 +306,44 @@ def test_artifact_directory_comparison_fails_closed(tmp_path: Path) -> None:
release_artifacts.compare_artifact_directories(first, second)
+def test_cyclonedx_sbom_is_deterministic_bound_and_non_self_referential(
+ tmp_path: Path,
+) -> None:
+ artifacts = {
+ "verifier-standard-1.2.0.zip": {
+ "byte_size": 3,
+ "sha256": release_artifacts._sha256(b"zip"),
+ },
+ "verifier_standard-1.2.0-py3-none-any.whl": {
+ "byte_size": 5,
+ "sha256": release_artifacts._sha256(b"wheel"),
+ },
+ }
+ arguments = {
+ "commit": "a" * 40,
+ "epoch": "1787446816",
+ "release": "1.2.0",
+ "artifacts": artifacts,
+ }
+ first = tmp_path / "first.cdx.json"
+ second = tmp_path / "second.cdx.json"
+ release_artifacts._write_cyclonedx_sbom(first, **arguments)
+ release_artifacts._write_cyclonedx_sbom(second, **arguments)
+
+ assert first.read_bytes() == second.read_bytes()
+ payload = json.loads(first.read_text(encoding="utf-8"))
+ assert payload["bomFormat"] == "CycloneDX"
+ assert payload["specVersion"] == "1.6"
+ assert {component["name"] for component in payload["components"]} == set(artifacts)
+ assert first.name not in {component["name"] for component in payload["components"]}
+ release_artifacts._verify_cyclonedx_sbom(first, **arguments)
+
+ payload["components"][0]["hashes"][0]["content"] = "0" * 64
+ first.write_text(json.dumps(payload), encoding="utf-8")
+ with pytest.raises(release_artifacts.ReleaseError, match="bound release subjects"):
+ release_artifacts._verify_cyclonedx_sbom(first, **arguments)
+
+
def test_release_notes_use_the_github_tag_object_verification() -> None:
workflow = RELEASE_WORKFLOW.read_text(encoding="utf-8")
assert 'git/tags/$TAG_OBJECT' in workflow
@@ -302,3 +351,135 @@ def test_release_notes_use_the_github_tag_object_verification() -> None:
assert ".verification.reason" in workflow
assert "SIGNED_AND_GITHUB_VERIFIED" in workflow
assert 'git verify-tag "$GITHUB_REF_NAME"' not in workflow
+
+
+def test_release_is_drafted_with_attested_sbom_before_publication() -> None:
+ workflow = RELEASE_WORKFLOW.read_text(encoding="utf-8")
+ create = workflow.index('gh release create "$GITHUB_REF_NAME"')
+ publish = workflow.index('gh release edit "$GITHUB_REF_NAME"')
+
+ assert "dist/*.cdx.json" in workflow
+ assert "--draft" in workflow[create:publish]
+ assert "--draft=false" in workflow[publish:]
+ assert create < publish
+
+
+@pytest.mark.parametrize(
+ "status", ["OPEN", "CONFLICTED", "", "CLEAR\nStatus: CLEAR", "CLEAR\nStatus: open"]
+)
+def test_release_time_gate_rejects_every_non_exact_clear_state(
+ tmp_path: Path, status: str
+) -> None:
+ time_file = tmp_path / "TIME.md"
+ time_file.write_text(f"# TIME\n\nStatus: {status}\n", encoding="utf-8")
+ result = subprocess.run(
+ [sys.executable, str(TIME_GATE), str(time_file)],
+ text=True,
+ capture_output=True,
+ check=False,
+ )
+ assert result.returncode == 1
+ assert "[TIME BLOCKED]" in result.stderr
+
+
+def test_tag_release_requires_clear_time_from_the_exact_checkout(tmp_path: Path) -> None:
+ time_file = tmp_path / "TIME.md"
+ time_file.write_text("# TIME\n\nStatus: CLEAR\n", encoding="utf-8")
+ result = subprocess.run(
+ [sys.executable, str(TIME_GATE), str(time_file)],
+ text=True,
+ capture_output=True,
+ check=False,
+ )
+ assert result.returncode == 0
+ assert "[TIME CLEAR]" in result.stdout
+
+ workflow = RELEASE_WORKFLOW.read_text(encoding="utf-8")
+ assert "Require TIME CLEAR in the exact tagged checkout" in workflow
+ assert "python scripts/check_time_status.py" in workflow
+ assert workflow.index("python scripts/check_time_status.py") < workflow.index(
+ "python -m pytest -q"
+ )
+
+
+def _write_final_release_metadata(root: Path) -> None:
+ (root / "pyproject.toml").write_text(
+ '[project]\nname = "verifier-standard"\nversion = "1.2.0"\n',
+ encoding="utf-8",
+ )
+ (root / "CHANGELOG.md").write_text(
+ "# Changelog\n\n## 1.2.0 - 2026-08-26\n", encoding="utf-8"
+ )
+ (root / "CITATION.cff").write_text(
+ 'cff-version: 1.2.0\nmessage: "Cite this published release."\n'
+ "version: 1.2.0\ndate-released: 2026-08-26\n",
+ encoding="utf-8",
+ )
+ (root / ".zenodo.json").write_text(
+ json.dumps({"version": "1.2.0", "description": "Final publication metadata."}),
+ encoding="utf-8",
+ )
+
+
+@pytest.mark.parametrize(
+ "fault",
+ (
+ "unreleased_changelog",
+ "missing_citation_date",
+ "mismatched_citation_date",
+ "candidate_citation",
+ "candidate_zenodo",
+ "package_version",
+ ),
+)
+def test_release_metadata_gate_rejects_unfinalized_or_inconsistent_state(
+ tmp_path: Path, fault: str
+) -> None:
+ _write_final_release_metadata(tmp_path)
+ if fault == "unreleased_changelog":
+ path = tmp_path / "CHANGELOG.md"
+ path.write_text(path.read_text().replace("2026-08-26", "UNRELEASED"))
+ elif fault == "missing_citation_date":
+ path = tmp_path / "CITATION.cff"
+ path.write_text(path.read_text().replace("date-released: 2026-08-26\n", ""))
+ elif fault == "mismatched_citation_date":
+ path = tmp_path / "CITATION.cff"
+ path.write_text(path.read_text().replace("2026-08-26", "2026-08-25"))
+ elif fault == "candidate_citation":
+ path = tmp_path / "CITATION.cff"
+ path.write_text(path.read_text().replace("published release", "release candidate"))
+ elif fault == "candidate_zenodo":
+ path = tmp_path / ".zenodo.json"
+ path.write_text(
+ json.dumps({"version": "1.2.0", "description": "Release-candidate metadata."})
+ )
+ else:
+ path = tmp_path / "pyproject.toml"
+ path.write_text(path.read_text().replace("1.2.0", "1.1.3"))
+
+ with pytest.raises(ValueError):
+ release_metadata.require_finalized(tmp_path, "1.2.0")
+
+
+def test_release_metadata_gate_accepts_one_final_consistent_coordinate(tmp_path: Path) -> None:
+ _write_final_release_metadata(tmp_path)
+ release_metadata.require_finalized(tmp_path, "1.2.0")
+
+
+def test_tag_release_contract_binds_main_version_gate_and_final_metadata() -> None:
+ workflow = RELEASE_WORKFLOW.read_text(encoding="utf-8")
+ required = (
+ 'git merge-base --is-ancestor "$GITHUB_SHA" origin/main',
+ 'git rev-parse "${GITHUB_REF}^{commit}"',
+ 'test "$VERSION" = "$PACKAGE_VERSION"',
+ 'commits/$GITHUB_SHA/check-runs',
+ 'select(.name == "conformance-gate" and .conclusion == "success")',
+ 'repos/$GITHUB_REPOSITORY/immutable-releases',
+ "--jq '.enabled')\" = true",
+ 'python scripts/check_release_metadata.py --version "${GITHUB_REF_NAME#v}"',
+ )
+ for fragment in required:
+ assert fragment in workflow
+ assert workflow.index('repos/$GITHUB_REPOSITORY/immutable-releases') < workflow.index(
+ 'gh release create "$GITHUB_REF_NAME"'
+ )
diff --git a/tests/test_scitt_crypto_example.py b/tests/test_scitt_crypto_example.py
new file mode 100644
index 0000000..a367b42
--- /dev/null
+++ b/tests/test_scitt_crypto_example.py
@@ -0,0 +1,121 @@
+"""Terminology: Concise Binary Object Representation (CBOR);
+CBOR Object Signing and Encryption (COSE); Supply Chain Integrity, Transparency, and Trust (SCITT);
+Verifier Standard (VSTD).
+
+Optional real-COSE integration test for the self-contained example."""
+
+from __future__ import annotations
+
+import importlib.util
+import json
+from pathlib import Path
+
+import pytest
+from jsonschema import Draft202012Validator
+from referencing import Registry, Resource
+
+
+pytest.importorskip("scitt_cose")
+pytest.importorskip("cryptography")
+
+REPO_ROOT = Path(__file__).resolve().parents[1]
+DEMO = REPO_ROOT / "examples" / "scitt_interop" / "demo.py"
+
+
+def _load_demo():
+ spec = importlib.util.spec_from_file_location("vstd_scitt_demo", DEMO)
+ assert spec is not None and spec.loader is not None
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module
+
+
+def test_real_signed_statement_receipt_and_independent_consumption(tmp_path):
+ result = _load_demo().produce(tmp_path)
+ assert result["vstd_kernel"]["outcome"] == "ACCEPTED"
+ assert result["vstd_kernel"]["verdict"] == "PASS"
+ assert result["scitt_observation"]["signed_statement_verified"] is True
+ assert result["scitt_observation"]["receipt_verified"] is True
+ assert result["composition"]["status"] == "PASS"
+ assert result["vstd_observation"]["conformance_status"] == "NOT_ESTABLISHED"
+ assert result["composition"]["vstd_conformance_status"] == "NOT_ESTABLISHED"
+ assert result["composition"]["status_scope"] == (
+ "NATIVE_VSTD_RESULT_AND_SCITT_REGISTRATION"
+ )
+ assert "conformance NOT_ESTABLISHED" in result["composition"]["reason"]
+
+ schema_dir = REPO_ROOT / "receipts" / "schema"
+ receipt_schema = json.loads((schema_dir / "vstd4_receipt.json").read_text())
+ certificate_schema = json.loads(
+ (schema_dir / "vstd4_certificate.json").read_text()
+ )
+ registry = Registry().with_resource(
+ certificate_schema["$id"], Resource.from_contents(certificate_schema)
+ )
+ receipt = json.loads((tmp_path / "vstd_receipt.json").read_text())
+ Draft202012Validator(receipt_schema, registry=registry).validate(receipt)
+ assert receipt["conformance_status"] == "NOT_ESTABLISHED"
+
+
+def test_application_payload_is_deterministic_but_ephemeral_cose_keys_are_not(
+ tmp_path,
+):
+ demo = _load_demo()
+ first = tmp_path / "first"
+ second = tmp_path / "second"
+ demo.produce(first)
+ demo.produce(second)
+
+ assert (first / "vstd_scitt_payload.json").read_bytes() == (
+ second / "vstd_scitt_payload.json"
+ ).read_bytes()
+ assert (first / "signed_statement.cose").read_bytes() != (
+ second / "signed_statement.cose"
+ ).read_bytes()
+
+
+def test_real_statement_and_receipt_tampering_are_rejected(tmp_path):
+ demo = _load_demo()
+ demo.produce(tmp_path)
+
+ statement = tmp_path / "signed_statement.cose"
+ statement_bytes = statement.read_bytes()
+ statement.write_bytes(statement_bytes[:-1] + bytes([statement_bytes[-1] ^ 1]))
+ with pytest.raises(RuntimeError, match="signature did not verify"):
+ demo.verify(tmp_path)
+
+ demo.produce(tmp_path)
+ receipt = tmp_path / "receipt.cose"
+ receipt_bytes = receipt.read_bytes()
+ receipt.write_bytes(receipt_bytes[:-1] + bytes([receipt_bytes[-1] ^ 1]))
+ with pytest.raises(RuntimeError, match="COSE Receipt failed"):
+ demo.verify(tmp_path)
+
+
+def test_real_malformed_scitt_statement_is_rejected_before_composition(tmp_path):
+ demo = _load_demo()
+ demo.produce(tmp_path)
+ (tmp_path / "signed_statement.cose").write_bytes(b"\x80")
+
+ with pytest.raises(RuntimeError, match="malformed SCITT Signed Statement"):
+ demo.verify(tmp_path)
+
+
+def test_real_scitt_registration_does_not_upgrade_vstd_budget_exhaustion(tmp_path):
+ demo = _load_demo()
+ demo.produce(tmp_path)
+ result = demo.verify(tmp_path, vstd_budget=0)
+ assert result["scitt_observation"]["signed_statement_verified"] is True
+ assert result["scitt_observation"]["receipt_verified"] is True
+ assert result["vstd_kernel"]["outcome"] == "REFUSED"
+ assert result["vstd_kernel"]["verdict"] == "UNKNOWN"
+ assert result["composition"]["status"] == "UNKNOWN"
+
+
+def test_real_valid_scitt_registration_does_not_repair_rejected_vstd_claim(tmp_path):
+ result = _load_demo().produce(tmp_path, vstd_binding_tamper=True)
+ assert result["scitt_observation"]["signed_statement_verified"] is True
+ assert result["scitt_observation"]["receipt_verified"] is True
+ assert result["vstd_kernel"]["outcome"] == "REJECTED"
+ assert result["vstd_observation"]["state"] == "REJECTED"
+ assert result["composition"]["status"] == "FAIL"
diff --git a/tests/test_scitt_interop.py b/tests/test_scitt_interop.py
new file mode 100644
index 0000000..6f73df4
--- /dev/null
+++ b/tests/test_scitt_interop.py
@@ -0,0 +1,437 @@
+"""Terminology: grounded decision certificate (GDC);
+Supply Chain Integrity, Transparency, and Trust (SCITT); Verifier Standard (VSTD).
+
+Adversarial tests for the experimental VSTD/SCITT composition boundary."""
+
+from __future__ import annotations
+
+import json
+
+import pytest
+
+from verifier.interoperability.scitt import (
+ CompositionStatus,
+ InteropError,
+ ScittEvidenceState,
+ ScittVerificationEvidence,
+ VstdCoordinates,
+ VstdScittPayload,
+ VstdVerificationEvidence,
+ VstdVerificationState,
+ compose_results,
+ consume_scitt_evidence,
+ create_scitt_registration_template,
+)
+
+
+DIGEST_A = "a" * 64
+DIGEST_B = "b" * 64
+DIGEST_C = "c" * 64
+ISSUER = "https://issuer.example"
+SUBJECT = "artifact:sha256:" + DIGEST_A
+
+
+def _receipt(*, result: str = "PASS") -> dict:
+ return {
+ "schema_version": "VSTD-4",
+ "receipt_id": "VFY-4-scitt-interop-test",
+ "canonical_digest": DIGEST_B,
+ "claim_id": "SCITT-INTEROP-TEST",
+ "binding": {
+ "claim": "the bounded predicate holds for the named artifact",
+ "coordinate": {
+ "subject": SUBJECT,
+ "predicate": "bounded_predicate",
+ "parameters": {"policy": "test-policy-v1"},
+ },
+ "bounds": {
+ "verification_cost_bound": 100,
+ "memory_bound": 10,
+ "certificate_size_bound": 10000,
+ },
+ },
+ "decision": {"verdict": result, "certificate": "fixture-only"},
+ }
+
+
+def _coordinates(*, result: str = "PASS") -> VstdCoordinates:
+ return VstdCoordinates(
+ receipt_id="VFY-4-scitt-interop-test",
+ schema_version="VSTD-4",
+ claim_id="SCITT-INTEROP-TEST",
+ subject=SUBJECT,
+ predicate="bounded_predicate",
+ parameters={"policy": "test-policy-v1"},
+ native_result=result,
+ native_canonical_digest=DIGEST_B,
+ evidence_bounds={
+ "verification_cost_bound": 100,
+ "memory_bound": 10,
+ "certificate_size_bound": 10000,
+ },
+ artifact_digests={"primary": DIGEST_A},
+ provenance_references=("urn:example:provenance:1",),
+ )
+
+
+def _payload(*, result: str = "PASS") -> VstdScittPayload:
+ return VstdScittPayload.create(_receipt(result=result), _coordinates(result=result))
+
+
+def _scitt(
+ payload: VstdScittPayload,
+ *,
+ state: ScittEvidenceState = ScittEvidenceState.REGISTERED,
+ signed: bool = True,
+ receipt: bool = True,
+ payload_digest: str | None = None,
+ issuer: str = ISSUER,
+ subject: str = SUBJECT,
+) -> ScittVerificationEvidence:
+ return ScittVerificationEvidence(
+ state=state,
+ statement_sha256=DIGEST_C,
+ payload_sha256=payload_digest or payload.payload_sha256(),
+ issuer=issuer,
+ subject=subject,
+ signed_statement_verified=signed,
+ receipt_verified=receipt,
+ verification_profile="RFC9943+RFC9942",
+ registration_policy="urn:example:registration-policy:v1",
+ transparency_service="https://transparency.example",
+ vds="RFC9162_SHA256",
+ native_result=state.value.lower(),
+ reason="native verifier fixture result",
+ registered_at="2026-08-23T00:00:00Z",
+ )
+
+
+def _vstd(
+ payload: VstdScittPayload,
+ *,
+ state: VstdVerificationState = VstdVerificationState.VERIFIED,
+ result: str | None = None,
+ receipt_digest: str | None = None,
+) -> VstdVerificationEvidence:
+ return VstdVerificationEvidence(
+ state=state,
+ receipt_sha256=receipt_digest or payload.receipt_sha256,
+ native_result=result or payload.coordinates.native_result,
+ checker="verifier.core.kernel.check",
+ verification_profile="VSTD4-GDC-1/reference-kernel",
+ reason="native checker fixture result",
+ )
+
+
+def _compose(
+ payload: VstdScittPayload,
+ scitt: ScittVerificationEvidence,
+ *,
+ artifacts: dict[str, str] | None = None,
+):
+ return compose_results(
+ payload,
+ _vstd(payload),
+ scitt,
+ artifact_digests=artifacts or {"primary": DIGEST_A},
+ accepted_issuers=[ISSUER],
+ )
+
+
+def test_deterministic_serialization_and_round_trip_preserve_coordinates():
+ payload = _payload()
+ encoded = payload.to_bytes()
+ assert encoded == payload.to_bytes()
+ assert b'": ' not in encoded
+ assert b", " not in encoded
+
+ decoded = VstdScittPayload.from_bytes(encoded)
+ assert decoded.to_bytes() == encoded
+ assert decoded.coordinates.to_dict() == payload.coordinates.to_dict()
+ assert decoded.receipt_sha256 == payload.receipt_sha256
+ assert decoded.coordinates.evidence_bounds["memory_bound"] == 10
+ assert decoded.coordinates.provenance_references == (
+ "urn:example:provenance:1",
+ )
+
+
+def test_native_vstd_payload_does_not_require_scitt_identity_or_log_coordinates():
+ payload = _payload().to_dict()
+ serialized = json.dumps(payload, sort_keys=True)
+ for scitt_coordinate in (
+ "issuer",
+ "transparency_service",
+ "registration_policy",
+ "registered_at",
+ ):
+ assert scitt_coordinate not in payload
+ assert f'"{scitt_coordinate}"' not in serialized
+
+ template = create_scitt_registration_template(
+ _receipt(), _coordinates(), issuer=ISSUER, subject=SUBJECT
+ ).to_dict()
+ assert template["required_protected_header_projection"]["issuer"] == ISSUER
+
+
+def test_noncanonical_or_extra_payload_fields_are_rejected():
+ payload = _payload().to_dict()
+ payload["unexpected"] = True
+ with pytest.raises(InteropError, match="not in canonical form"):
+ VstdScittPayload.from_bytes(json.dumps(payload).encode())
+
+ canonical_with_extra = json.dumps(
+ payload, sort_keys=True, separators=(",", ":")
+ ).encode()
+ with pytest.raises(InteropError, match="keys mismatch"):
+ VstdScittPayload.from_bytes(canonical_with_extra)
+
+
+def test_version_mismatch_and_unsupported_profile_fail_closed():
+ payload = _payload().to_dict()
+ payload["mapping_version"] = "9.9"
+ encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
+ with pytest.raises(InteropError, match="unsupported mapping version"):
+ VstdScittPayload.from_bytes(encoded)
+
+ payload["mapping_version"] = "0.1"
+ payload["profile"] = "unknown-profile"
+ encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
+ with pytest.raises(InteropError, match="unsupported profile"):
+ VstdScittPayload.from_bytes(encoded)
+
+
+def test_receipt_identity_and_claim_coordinate_mismatch_are_rejected():
+ receipt = _receipt()
+ receipt["receipt_id"] = "VFY-4-other"
+ with pytest.raises(InteropError, match="receipt_id"):
+ VstdScittPayload.create(receipt, _coordinates())
+
+ receipt = _receipt()
+ receipt["binding"]["coordinate"]["predicate"] = "other_predicate"
+ with pytest.raises(InteropError, match="binding coordinate"):
+ VstdScittPayload.create(receipt, _coordinates())
+
+
+def test_mutating_nested_receipt_after_creation_does_not_change_payload():
+ receipt = _receipt()
+ payload = VstdScittPayload.create(receipt, _coordinates())
+ before = payload.to_bytes()
+ receipt["binding"]["claim"] = "mutated by caller"
+ assert payload.to_bytes() == before
+
+
+def test_registration_template_is_explicitly_not_cose_and_binds_subject():
+ template = create_scitt_registration_template(
+ _receipt(), _coordinates(), issuer=ISSUER, subject=SUBJECT
+ )
+ data = template.to_dict()
+ assert data["representation"] == "normalized-registration-input-not-cose"
+ assert data["payload_sha256"] == template.payload.payload_sha256()
+ assert data["required_protected_header_projection"]["issuer"] == ISSUER
+ assert data["required_protected_header_projection"]["subject"] == SUBJECT
+
+ with pytest.raises(InteropError, match="subject must equal"):
+ create_scitt_registration_template(
+ _receipt(), _coordinates(), issuer=ISSUER, subject="artifact:other"
+ )
+
+
+def test_registered_vstd_pass_composes_to_pass_only_for_exact_artifact():
+ payload = _payload()
+ result = _compose(payload, _scitt(payload))
+ assert result.status is CompositionStatus.PASS
+ assert result.status_scope == "NATIVE_VSTD_RESULT_AND_SCITT_REGISTRATION"
+ assert result.vstd_conformance_status == "NOT_ESTABLISHED"
+ assert result.native_vstd_result == "PASS"
+ assert result.native_scitt_result == "registered"
+ assert "conformance NOT_ESTABLISHED" in result.reason
+
+
+def test_registered_scitt_cannot_create_pass_without_bound_vstd_verification():
+ payload = _payload()
+ result = compose_results(
+ payload,
+ _vstd(payload, state=VstdVerificationState.NOT_EVALUATED),
+ _scitt(payload),
+ artifact_digests={"primary": DIGEST_A},
+ accepted_issuers=[ISSUER],
+ )
+ assert result.status is CompositionStatus.UNKNOWN
+ assert result.reason == "native VSTD receipt was not evaluated"
+
+
+def test_vstd_checker_result_must_bind_exact_receipt_and_native_result():
+ payload = _payload()
+ wrong_receipt = compose_results(
+ payload,
+ _vstd(payload, receipt_digest=DIGEST_C),
+ _scitt(payload),
+ artifact_digests={"primary": DIGEST_A},
+ accepted_issuers=[ISSUER],
+ )
+ assert wrong_receipt.status is CompositionStatus.FAIL
+ assert "embedded receipt" in wrong_receipt.reason
+
+ wrong_result = compose_results(
+ payload,
+ _vstd(payload, result="UNKNOWN"),
+ _scitt(payload),
+ artifact_digests={"primary": DIGEST_A},
+ accepted_issuers=[ISSUER],
+ )
+ assert wrong_result.status is CompositionStatus.FAIL
+ assert "payload result" in wrong_result.reason
+
+
+def test_rejected_vstd_receipt_cannot_be_repaired_by_scitt_registration():
+ payload = _payload()
+ result = compose_results(
+ payload,
+ _vstd(payload, state=VstdVerificationState.REJECTED),
+ _scitt(payload),
+ artifact_digests={"primary": DIGEST_A},
+ accepted_issuers=[ISSUER],
+ )
+ assert result.status is CompositionStatus.FAIL
+ assert "checker rejected" in result.reason
+
+
+def test_registered_scitt_preserves_vstd_resource_indeterminacy():
+ payload = _payload()
+ result = compose_results(
+ payload,
+ _vstd(
+ payload,
+ state=VstdVerificationState.INDETERMINATE,
+ result="UNKNOWN",
+ ),
+ _scitt(payload),
+ artifact_digests={"primary": DIGEST_A},
+ accepted_issuers=[ISSUER],
+ )
+ assert result.status is CompositionStatus.UNKNOWN
+ assert result.native_vstd_result == "UNKNOWN"
+ assert "unable to decide" in result.reason
+
+
+def test_artifact_substitution_fails_even_when_scitt_registration_is_valid():
+ payload = _payload()
+ result = _compose(payload, _scitt(payload), artifacts={"primary": DIGEST_B})
+ assert result.status is CompositionStatus.FAIL
+ assert result.reason == "artifact binding mismatch"
+
+
+def test_valid_registration_does_not_upgrade_failed_vstd_claim():
+ payload = _payload(result="FAIL")
+ result = _compose(payload, _scitt(payload))
+ assert result.status is CompositionStatus.FAIL
+ assert result.native_vstd_result == "FAIL"
+
+
+@pytest.mark.parametrize("native", ["UNKNOWN", "INDETERMINATE", "UNSUPPORTED"])
+def test_registered_statement_preserves_vstd_indeterminacy(native):
+ payload = _payload(result=native)
+ result = _compose(payload, _scitt(payload))
+ assert result.status is CompositionStatus.UNKNOWN
+ assert result.native_vstd_result == native
+
+
+@pytest.mark.parametrize(
+ "state",
+ [
+ ScittEvidenceState.MISSING,
+ ScittEvidenceState.STALE,
+ ScittEvidenceState.REVOKED,
+ ScittEvidenceState.SUPERSEDED,
+ ScittEvidenceState.UNKNOWN,
+ ],
+)
+def test_noncurrent_scitt_evidence_caps_vstd_pass_at_unknown(state):
+ payload = _payload()
+ result = _compose(payload, _scitt(payload, state=state))
+ assert result.status is CompositionStatus.UNKNOWN
+ assert state.value in result.reason
+
+
+def test_conflicted_evidence_is_not_collapsed_to_unknown_or_pass():
+ payload = _payload()
+ result = _compose(
+ payload, _scitt(payload, state=ScittEvidenceState.CONFLICTED)
+ )
+ assert result.status is CompositionStatus.CONFLICTED
+
+
+def test_payload_transplant_is_detected_despite_verified_scitt_receipt():
+ payload = _payload()
+ evidence = _scitt(payload, payload_digest=DIGEST_B)
+ result = _compose(payload, evidence)
+ assert result.status is CompositionStatus.FAIL
+ assert "payload" in result.reason
+
+
+def test_wrong_issuer_and_subject_fail_relying_party_policy():
+ payload = _payload()
+ wrong_issuer = _scitt(payload, issuer="https://other.example")
+ assert _compose(payload, wrong_issuer).status is CompositionStatus.FAIL
+
+ wrong_subject = _scitt(payload, subject="artifact:other")
+ assert _compose(payload, wrong_subject).status is CompositionStatus.FAIL
+
+
+def test_unverified_statement_or_receipt_cannot_be_called_registered():
+ payload = _payload()
+ with pytest.raises(InteropError, match="REGISTERED requires"):
+ _scitt(payload, signed=False)
+ with pytest.raises(InteropError, match="REGISTERED requires"):
+ _scitt(payload, receipt=False)
+
+
+def test_scitt_evidence_adapter_never_emits_computational_verdict():
+ payload = _payload()
+ evidence = consume_scitt_evidence(
+ _scitt(payload),
+ expected_payload_sha256=payload.payload_sha256(),
+ expected_subject=SUBJECT,
+ accepted_issuers=[ISSUER],
+ )
+ assert evidence["normalized_state"] == "REGISTERED"
+ assert evidence["computational_verdict"] == "NOT_EVALUATED"
+
+
+def test_malformed_evidence_and_unknown_vstd_result_are_rejected():
+ payload = _payload()
+ malformed = _scitt(payload).to_dict()
+ malformed["extra"] = "guess me"
+ with pytest.raises(InteropError, match="keys mismatch"):
+ ScittVerificationEvidence.from_dict(malformed)
+
+ unsupported = _payload(result="VALID")
+ with pytest.raises(InteropError, match="refusing to guess"):
+ _compose(unsupported, _scitt(unsupported))
+
+
+def test_scitt_verification_evidence_round_trip():
+ payload = _payload()
+ evidence = _scitt(payload)
+ decoded = ScittVerificationEvidence.from_dict(evidence.to_dict())
+ assert decoded == evidence
+
+
+def test_vstd_verification_evidence_round_trip_and_closed_shape():
+ evidence = _vstd(_payload())
+ assert evidence.to_dict()["conformance_status"] == "NOT_ESTABLISHED"
+ assert VstdVerificationEvidence.from_dict(evidence.to_dict()) == evidence
+
+ legacy = evidence.to_dict()
+ del legacy["conformance_status"]
+ assert VstdVerificationEvidence.from_dict(legacy) == evidence
+
+ promoted = evidence.to_dict()
+ promoted["conformance_status"] = "ESTABLISHED"
+ with pytest.raises(InteropError, match="cannot establish VSTD conformance"):
+ VstdVerificationEvidence.from_dict(promoted)
+
+ malformed = evidence.to_dict()
+ malformed["extra"] = "guess me"
+ with pytest.raises(InteropError, match="keys mismatch"):
+ VstdVerificationEvidence.from_dict(malformed)
diff --git a/tests/test_simulacrabench_packet.py b/tests/test_simulacrabench_packet.py
deleted file mode 100644
index 9910999..0000000
--- a/tests/test_simulacrabench_packet.py
+++ /dev/null
@@ -1,123 +0,0 @@
-"""Adversarial checks for the synthetic closed-evaluation profile specimen."""
-
-from __future__ import annotations
-
-import copy
-import importlib.util
-import json
-from pathlib import Path
-
-import pytest
-
-from verifier.core.certificate import canonical_digest
-
-
-REPO_ROOT = Path(__file__).resolve().parents[1]
-EXAMPLE = REPO_ROOT / "examples" / "simulacrabench_synthetic"
-SPEC = importlib.util.spec_from_file_location(
- "simulacrabench_packet_verifier", EXAMPLE / "verify_packet.py"
-)
-assert SPEC is not None and SPEC.loader is not None
-MODULE = importlib.util.module_from_spec(SPEC)
-SPEC.loader.exec_module(MODULE)
-
-
-def _load(name: str) -> dict:
- value = json.loads((EXAMPLE / name).read_text(encoding="utf-8"))
- assert isinstance(value, dict)
- return value
-
-
-def _reseal(document: dict, field: str) -> None:
- document.pop(field, None)
- document[field] = f"sha256:{canonical_digest(document)}"
-
-
-def test_public_packet_and_non_disclosing_challenge_verify() -> None:
- result = MODULE.verify_all()
- assert result["packet"] == {
- "packet_id": "VSTD-SB-SYNTH-002",
- "packet_digest": "sha256:6f64a1bfa97a83e10b3a3c034c7d397b853e8dba9baa2db256be0abcfd299296",
- "availability_floor": "IDENTIFIED",
- "public_reproduction": "UNAVAILABLE",
- "claim_status": "RECORDED_UNDER_DECLARED_SYNTHETIC_EVALUATOR",
- }
- assert result["challenge"]["after_public_filing"] == "CHALLENGED"
- assert result["challenge"]["adjudicated"] is False
- assert result["challenge"]["records_disclosed"] == 0
-
-
-def test_public_packet_excludes_private_score_detail_and_local_locations() -> None:
- packet = _load("public_packet.json")
- public_text = json.dumps(packet, sort_keys=True).lower()
- def keys(value):
- if isinstance(value, dict):
- return set(value).union(*(keys(item) for item in value.values()))
- if isinstance(value, list):
- return set().union(*(keys(item) for item in value)) if value else set()
- return set()
-
- assert {"raw_skill", "log_score", "by_item", "std_error"}.isdisjoint(keys(packet))
- for prohibited in ("e:\\\\", "c:\\\\users"):
- assert prohibited not in public_text
- for item in packet["evidence_inventory"]:
- if item["disclosure"] == "access-controlled":
- assert item["locator"] == ""
- assert item["assessed_level"] == "IDENTIFIED"
- assert packet["reported_result"]["privacy_policy"]["raw_skill_disclosed"] is False
- assert packet["availability_summary"]["public_reproduction"] == "UNAVAILABLE"
- assert packet["availability_summary"]["accepted"] is False
-
-
-def test_locator_declaration_without_retrieval_observation_cannot_be_available() -> None:
- packet = _load("public_packet.json")
- mutant = copy.deepcopy(packet)
- hidden = next(
- item
- for item in mutant["evidence_inventory"]
- if item["artifact_id"] == "hidden-synthetic-fixture"
- )
- hidden["locator"] = "https://example.invalid/private-artifact"
- hidden["declared_level"] = "AVAILABLE"
- hidden["assessed_level"] = "AVAILABLE"
- _reseal(mutant, "packet_digest")
- with pytest.raises(MODULE.PacketError, match="evidence policy"):
- MODULE.verify_packet(mutant)
-
-
-def test_private_retention_and_packet_staleness_cannot_diverge() -> None:
- packet = _load("public_packet.json")
- mutant = copy.deepcopy(packet)
- mutant["limits"]["retention_declaration_horizon"] = "2026-10-01T00:00:00Z"
- _reseal(mutant, "packet_digest")
- with pytest.raises(MODULE.PacketError, match="retention_declaration_horizon"):
- MODULE.verify_packet(mutant)
-
-
-def test_public_challenge_contains_no_private_transcript_or_adjudication() -> None:
- challenge = _load("challenge_demo.json")
- assert "authorized_transcript" not in challenge
- assert challenge["transitions"] == {"after_public_filing": "CHALLENGED"}
- assert challenge["trust"]["adjudicated"] is False
-
-
-def test_challenge_cannot_disclose_a_hidden_record() -> None:
- packet = _load("public_packet.json")
- challenge = _load("challenge_demo.json")
- mutant = copy.deepcopy(challenge)
- mutant["leak_check"]["individual_records"] = 1
- _reseal(mutant, "challenge_digest")
- with pytest.raises(MODULE.PacketError, match="leaks"):
- MODULE.verify_challenge(packet, mutant)
-
-
-def test_challenge_cannot_substitute_a_different_refutation_surface() -> None:
- packet = _load("public_packet.json")
- challenge = _load("challenge_demo.json")
- mutant = copy.deepcopy(challenge)
- mutant["refutation_surface"]["admissible_refutations"][0][
- "overturning_evidence"
- ] = "A weaker post-hoc condition."
- _reseal(mutant, "challenge_digest")
- with pytest.raises(MODULE.PacketError, match="differs"):
- MODULE.verify_challenge(packet, mutant)
diff --git a/tests/test_verification_geometry.py b/tests/test_verification_geometry.py
index 920f228..4a8fc93 100644
--- a/tests/test_verification_geometry.py
+++ b/tests/test_verification_geometry.py
@@ -1,4 +1,6 @@
-"""Semantic tests for the additive VSTD-0.2 verification geometry slice."""
+"""Terminology: Verifier Standard (VSTD).
+
+Semantic tests for the additive VSTD-2 verification geometry slice."""
import json
from dataclasses import replace
@@ -254,6 +256,13 @@ def test_locus_and_facet_are_distinct_and_grain_is_orthogonal_to_stratum() -> No
assert geometry.validate() == []
+def test_retired_geometry_identifier_is_rejected() -> None:
+ geometry = replace(
+ geometry_with_reconstruction_horizon(), schema_version="VSTD-" + "0.2"
+ )
+ assert any("schema_version must be 'VSTD-2'" in error for error in geometry.validate())
+
+
def test_assumptions_cannot_manufacture_a_verified_judgment() -> None:
geometry = geometry_with_reconstruction_horizon()
geometry.judgments[0] = CoordinateJudgment(
@@ -335,7 +344,7 @@ def test_verification_orders_must_be_adjacent_not_infinitely_abstracted() -> Non
errors = geometry.validate()
assert any("contiguous and start at 0" in error for error in errors)
- assert any("adjacent-layer invariant" in error for error in errors)
+ assert any("order-adjacency invariant" in error for error in errors)
def test_geometry_digest_is_deterministic() -> None:
diff --git a/tests/test_vstd3_capabilities.py b/tests/test_vstd3_capabilities.py
index 324ce7d..bbe3fe6 100644
--- a/tests/test_vstd3_capabilities.py
+++ b/tests/test_vstd3_capabilities.py
@@ -1,3 +1,6 @@
+"""Terminology: Advanced Micro Devices (AMD); application-specific integrated circuit (ASIC);
+Verifier Standard (VSTD)."""
+
from __future__ import annotations
import base64
diff --git a/tests/test_vstd3_cli.py b/tests/test_vstd3_cli.py
index 374175b..5e8393a 100644
--- a/tests/test_vstd3_cli.py
+++ b/tests/test_vstd3_cli.py
@@ -1,3 +1,5 @@
+"""Terminology: identifier (ID); Verifier Standard (VSTD)."""
+
from __future__ import annotations
import json
diff --git a/tests/test_vstd3_emulator.py b/tests/test_vstd3_emulator.py
index 9d4ce2e..f40cf02 100644
--- a/tests/test_vstd3_emulator.py
+++ b/tests/test_vstd3_emulator.py
@@ -1,3 +1,5 @@
+"""Terminology: floating-point operation (FLOP); Verifier Standard (VSTD)."""
+
from __future__ import annotations
from dataclasses import replace
@@ -127,7 +129,10 @@ def test_verified_flags_without_keys_cannot_bootstrap_strong_claims() -> None:
validation = validate_vstd3_receipt(receipt)
assert not validation.valid
assert validation.status is ClaimStatus.UNKNOWN
- assert any("could not be independently verified" in warning for warning in validation.warnings)
+ assert any(
+ "could not be verified against configured trust material" in warning
+ for warning in validation.warnings
+ )
overclaims = "\n".join(validation.errors)
assert "overclaims DEVICE_IDENTITY" in overclaims
assert "overclaims FIRMWARE_INTEGRITY" in overclaims
diff --git a/tests/test_vstd3_provenance.py b/tests/test_vstd3_provenance.py
index 7d00a87..a6ceaf1 100644
--- a/tests/test_vstd3_provenance.py
+++ b/tests/test_vstd3_provenance.py
@@ -131,6 +131,7 @@ def test_missing_declared_output_is_rejected_without_partial_mutation() -> None:
"transformations": [],
"contributors": [],
"rights": [],
+ "conflicts": [],
}
diff --git a/tests/test_vstd3_schema.py b/tests/test_vstd3_schema.py
index 1f1b92c..4feeb74 100644
--- a/tests/test_vstd3_schema.py
+++ b/tests/test_vstd3_schema.py
@@ -1,3 +1,5 @@
+"""Terminology: Verifier Standard (VSTD)."""
+
from __future__ import annotations
import json
diff --git a/tests/test_vstd4_depth.py b/tests/test_vstd4_depth.py
index 208d4a6..65dd245 100644
--- a/tests/test_vstd4_depth.py
+++ b/tests/test_vstd4_depth.py
@@ -1,14 +1,15 @@
-"""The ladder internal to VSTD-4, and the gate it guards.
+"""Terminology: identifier (ID); unsatisfiable (UNSAT); Verifier Standard (VSTD).
-``vstd4_depth`` is computed, never declared. That is the whole point: standing
-up an external verification node is VSTD-5, and reaching it must be
-*computationally costly*, because verification is the new scaling. A rung that
-could be declared would let an implementer skip the climb.
+The structural candidate rung sequence internal to VSTD-4, and the gate it cannot cross.
+
+``vstd4_depth`` computes consistency over caller-supplied rung references. The
+references are not resolved and prerequisite-profile coordinates are not checked, so
+the result remains ``NOT_ESTABLISHED`` even when its candidate depth is 14.
The tests below check the two halves of an honest answer. The witness certifies
the rungs that were climbed; the refutation certifies why the next one was not,
and its conflict clause names the missing rung. Both are checked by the same
-kernel that checks any other VSTD-4 claim -- the layer certifies its own ceiling
+kernel that checks any other VSTD-4 claim -- the candidate-depth computation certifies its own ceiling
using its own mechanism.
"""
@@ -74,7 +75,7 @@ def _assert_certificates_check(result: DepthResult) -> None:
# --------------------------------------------------------------------------
-# The ladder itself
+# The rung sequence itself
# --------------------------------------------------------------------------
@@ -97,10 +98,11 @@ def test_the_top_rung_depends_on_every_other():
# --------------------------------------------------------------------------
-def test_full_evidence_reaches_the_top_and_admits_vstd5():
+def test_full_reference_set_reaches_only_the_candidate_top():
result = _depth(_evidence())
assert result.depth == MAX_DEPTH
- assert result.admits_vstd5 is True
+ assert result.conformance_status == "NOT_ESTABLISHED"
+ assert result.admits_vstd5 is False
assert result.refutation is None
assert result.blocking_rungs == ()
assert result.witness is not None
@@ -132,7 +134,7 @@ def test_the_refutation_is_the_explanation_not_a_separate_report():
def test_removing_evidence_can_only_lower_the_depth():
- """Rung 4.13, applied to the ladder itself: weakening never strengthens."""
+ """Rung 4.13, applied to the sequence itself: weakening never strengthens."""
baseline = _depth(_evidence()).depth
previous = baseline
for rung in reversed(RUNGS):
@@ -144,9 +146,9 @@ def test_removing_evidence_can_only_lower_the_depth():
def test_depth_is_monotone_in_the_prefix():
- for level in range(0, MAX_DEPTH + 1):
- result = _depth(_evidence(only=level))
- assert result.depth == level
+ for candidate_depth in range(0, MAX_DEPTH + 1):
+ result = _depth(_evidence(only=candidate_depth))
+ assert result.depth == candidate_depth
_assert_certificates_check(result)
@@ -159,16 +161,26 @@ def test_no_evidence_is_depth_zero_with_a_refutation_and_no_witness():
_assert_certificates_check(result)
-def test_the_vstd5_gate_refuses_anything_below_fourteen():
- """Layer 4 asks *could a stranger check this?*; layer 5 asks *did one?*"""
- for level in range(0, MAX_DEPTH):
- result = _depth(_evidence(only=level))
+def test_the_vstd5_gate_refuses_every_unbound_candidate():
+ for candidate_depth in range(0, MAX_DEPTH + 1):
+ result = _depth(_evidence(only=candidate_depth))
assert result.admits_vstd5 is False
- with pytest.raises(VSTD5EntryError, match="requires computed vstd4_depth"):
+ expected = (
+ "requires computed vstd4_depth"
+ if candidate_depth < MAX_DEPTH
+ else "requires established VSTD-4 conformance"
+ )
+ with pytest.raises(VSTD5EntryError, match=expected):
require_vstd5_entry(result)
- complete = _depth(_evidence())
- assert complete.admits_vstd5 is True
- assert require_vstd5_entry(complete) is complete
+
+
+def test_fourteen_arbitrary_strings_cannot_establish_vstd4_or_vstd5_readiness():
+ result = _depth({rung.id: "arbitrary-nonempty-text" for rung in RUNGS})
+ assert result.depth == MAX_DEPTH
+ assert result.conformance_status == "NOT_ESTABLISHED"
+ assert result.admits_vstd5 is False
+ with pytest.raises(VSTD5EntryError, match="requires established VSTD-4 conformance"):
+ require_vstd5_entry(result)
def test_unknown_rung_ids_are_refused():
@@ -179,6 +191,8 @@ def test_unknown_rung_ids_are_refused():
def test_depth_summary_carries_both_certificate_digests():
summary = _depth(_evidence(without=("4.5",))).to_dict()
assert summary["depth"] == 4
+ assert summary["depth_kind"] == "CANDIDATE"
+ assert summary["conformance_status"] == "NOT_ESTABLISHED"
assert summary["admits_vstd5"] is False
assert summary["blocking_rungs"] == ["4.5"]
assert summary["witness_digest"] is not None
diff --git a/tests/test_vstd_schemas.py b/tests/test_vstd_schemas.py
index 114a267..9bf403f 100644
--- a/tests/test_vstd_schemas.py
+++ b/tests/test_vstd_schemas.py
@@ -1,6 +1,8 @@
-"""Published JSON Schema coverage for the integer-layer release.
+"""Terminology: JavaScript Object Notation (JSON); Verifier Standard (VSTD).
-JSON Schema checks document shape. The independent kernel remains authoritative
+Published JavaScript Object Notation (JSON) Schema coverage for the numbered-profile release.
+
+JSON Schema checks document shape. The separately implemented kernel remains authoritative
for grounding, tier, count, binding, and proof semantics.
"""
@@ -25,11 +27,19 @@
)
from verifier.core.kernel import check, reference_descriptor
from verifier.core.refutation import build_horn_certificate
+from verifier.data.models import (
+ ArtifactNode,
+ ArtifactStatus,
+ ArtifactType,
+ ConflictRecord,
+ ProvenanceHypergraph,
+)
SCHEMA_DIR = Path(__file__).resolve().parents[1] / "receipts" / "schema"
PUBLISHED_SCHEMAS = (
"vstd1_receipt.json",
+ "vstd1_generic_run_receipt.json",
"vstd2_receipt.json",
"vstd3_receipt.json",
"vstd4_receipt.json",
@@ -93,6 +103,57 @@ def test_every_published_schema_is_valid_draft_2020_12() -> None:
Draft202012Validator.check_schema(_load(name))
+def test_graph_schema_and_runtime_share_status_and_conflict_shapes() -> None:
+ schema = _load("vstd_graph_receipt.json")["properties"]["hypergraph"]
+ graph = ProvenanceHypergraph()
+ graph.add_artifact(
+ ArtifactNode("artifact:a", "a", ArtifactType.CORPUS, "a" * 64, status=ArtifactStatus.VALID)
+ )
+ graph.add_conflict(
+ ConflictRecord(
+ "conflict:a",
+ "artifact:a",
+ "content_digest",
+ ("sha256:a", "sha256:b"),
+ ("receipt:a", "receipt:b"),
+ )
+ )
+ payload = graph.to_dict()
+ Draft202012Validator(schema).validate(payload)
+ assert ProvenanceHypergraph.from_dict(payload).to_dict() == payload
+
+
+def test_graph_schema_keeps_legacy_candidate_blocks_additively_valid() -> None:
+ candidate_schema = _load("vstd_graph_receipt.json")["properties"][
+ "computed_graph_level"
+ ]
+ legacy = {
+ "collection_id": "collection:legacy",
+ "level": 2,
+ "max_level": 5,
+ "blocking_obligations": [],
+ "witness_digest": HEX,
+ "refutation_digest": HEX,
+ }
+ Draft202012Validator(candidate_schema).validate(legacy)
+ assert "rating_basis" not in candidate_schema["required"]
+ assert "conformance_status" not in candidate_schema["required"]
+
+
+def test_independence_schema_rejects_actorless_independence_claim() -> None:
+ basis_schema = _load("vstd1_receipt.json")["properties"]["independent_audit"][
+ "properties"
+ ]["independence_basis"]
+ basis = {
+ "independently_verified": True,
+ "actor_independence": "NOT_DEMONSTRATED",
+ "implementation_separation": "EVIDENCED",
+ "runtime_separation": "EVIDENCED",
+ "evidence": ["receipt:checker"],
+ }
+ assert list(Draft202012Validator(basis_schema).iter_errors(basis))
+
+
def test_vstd4_gdc_certificate_matches_its_published_schema() -> None:
certificate, _binding = _certificate()
Draft202012Validator(_load("vstd4_certificate.json")).validate(
@@ -100,7 +161,7 @@ def test_vstd4_gdc_certificate_matches_its_published_schema() -> None:
)
-def test_vstd4_receipt_requires_the_computed_ceiling_certificate() -> None:
+def test_vstd4_candidate_receipt_is_explicit_and_keeps_legacy_shape_valid() -> None:
certificate, binding = _certificate()
schema = _load("vstd4_receipt.json")
validator = Draft202012Validator(schema, registry=_registry())
@@ -110,6 +171,7 @@ def test_vstd4_receipt_requires_the_computed_ceiling_certificate() -> None:
"claim_id": "claim:schema-test",
"binding": binding.to_dict(),
"vstd4_depth": 13,
+ "conformance_status": "NOT_ESTABLISHED",
"rung_evidence": {f"4.{index}": f"sha256:{HEX}" for index in range(1, 14)},
"witness": certificate.to_dict(),
"ceiling_refutation": certificate.to_dict(),
@@ -117,6 +179,15 @@ def test_vstd4_receipt_requires_the_computed_ceiling_certificate() -> None:
"status": "VALID",
}
validator.validate(receipt)
+ assert "does not establish VSTD-4 conformance" in schema["properties"]["status"]["description"]
+
+ legacy = dict(receipt)
+ del legacy["conformance_status"]
+ validator.validate(legacy)
+
+ receipt["conformance_status"] = "ESTABLISHED"
+ assert list(validator.iter_errors(receipt))
+ receipt["conformance_status"] = "NOT_ESTABLISHED"
receipt["ceiling_refutation"] = None
errors = list(validator.iter_errors(receipt))
@@ -124,62 +195,30 @@ def test_vstd4_receipt_requires_the_computed_ceiling_certificate() -> None:
assert any("not of type 'object'" in error.message for error in errors)
-def test_vstd5_draft_schema_enforces_the_vstd4_entry_gate() -> None:
+def test_vstd5_schema_requires_replayable_evidence_bound_inputs() -> None:
schema = _load("vstd5_receipt.json")
- validator = Draft202012Validator(schema, format_checker=FormatChecker())
- receipt = {
+ assert schema["properties"]["schema_version"]["const"] == "VSTD-5"
+ assert "must recheck" in schema["description"]
+ legacy_draft = {
"schema_version": "VSTD-5-DRAFT",
"status": "DRAFT",
"receipt_id": "VFY-5-SCHEMA-TEST",
- "claim_id": "claim:schema-test",
- "claim_binding": HEX,
- "entry_vstd4_depth": 14,
- "witnesses": [
- {
- "witness_id": "witness:test",
- "identity_evidence": "sha256:" + HEX,
- "independence": {
- "shared_control": "UNKNOWN",
- "shared_code": "UNKNOWN",
- "shared_trust_root": "UNKNOWN",
- "shared_evidence_source": "UNKNOWN",
- "shared_infrastructure": "UNKNOWN",
- "financial_dependence": "UNKNOWN",
- "jurisdictional_dependence": "UNKNOWN",
- "evidence": [],
- },
- }
- ],
- "corroborations": [
- {
- "corroboration_id": "corroboration:test",
- "witness_id": "witness:test",
- "class": "PHYSICAL_INSPECTION",
- "vstd4_certificate_digest": HEX,
- "checker_descriptor_digest": HEX,
- "observed_evidence": [HEX],
- "result": "UNKNOWN",
- "observed_at": "2026-08-22T12:00:00Z",
- }
- ],
- "disagreements": [],
- "computed_independence": "UNKNOWN",
}
- validator.validate(receipt)
+ assert list(Draft202012Validator(schema).iter_errors(legacy_draft))
- receipt["entry_vstd4_depth"] = 13
- errors = list(validator.iter_errors(receipt))
- assert errors
- assert any("14 was expected" in error.message for error in errors)
+ dimension = schema["$defs"]["dimension"]
+ assert dimension["required"] == ["state", "binding"]
+ assert {"type": "null"} in dimension["properties"]["binding"]["anyOf"]
-def test_layer_filenames_do_not_change_historical_wire_identifiers() -> None:
- assert _load("vstd1_receipt.json")["properties"]["schema_version"]["enum"] == [
- "VSTD-0.1"
- ]
- assert _load("vstd2_receipt.json")["properties"]["schema_version"]["const"] == (
- "VSTD-0.2"
- )
+def test_current_wire_identifiers_and_profile_discriminators() -> None:
+ claim = _load("vstd1_receipt.json")["properties"]
+ generic = _load("vstd1_generic_run_receipt.json")["properties"]
+ assert claim["schema_version"]["enum"] == ["VSTD-1"]
+ assert claim["receipt_kind"]["const"] == "claim_mechanics"
+ assert generic["schema_version"]["const"] == "VSTD-1"
+ assert generic["receipt_kind"]["const"] == "generic_computational_run"
+ assert _load("vstd2_receipt.json")["properties"]["schema_version"]["const"] == "VSTD-2"
assert _load("vstd_graph_receipt.json")["properties"]["schema_version"][
"enum"
] == ["VSTD-DATA-0.1"]
diff --git a/tests/test_zizk_artifact_first.py b/tests/test_zizk_artifact_first.py
new file mode 100644
index 0000000..cc2ae72
--- /dev/null
+++ b/tests/test_zizk_artifact_first.py
@@ -0,0 +1,132 @@
+"""Terminology: identifier (ID); Verifier Standard (VSTD)."""
+
+from __future__ import annotations
+
+import hashlib
+import json
+from pathlib import Path
+import subprocess
+
+
+ROOT = Path(__file__).resolve().parents[1]
+MECHANISM = ROOT / "examples" / "zizk_artifact_first" / "risc0"
+
+
+def test_zero_knowledge_mechanism_is_optional_and_pinned() -> None:
+ host_manifest = (MECHANISM / "host" / "Cargo.toml").read_text(encoding="utf-8")
+ guest_manifest = (
+ MECHANISM / "methods" / "guest" / "Cargo.toml"
+ ).read_text(encoding="utf-8")
+ methods_manifest = (MECHANISM / "methods" / "Cargo.toml").read_text(
+ encoding="utf-8"
+ )
+
+ assert 'version = "=3.0.6"' in host_manifest
+ assert 'features = ["disable-dev-mode"]' in host_manifest
+ assert 'version = "=3.0.6"' in guest_manifest
+ assert 'version = "=3.0.6"' in methods_manifest
+ assert "zizk" not in (ROOT / "pyproject.toml").read_text(encoding="utf-8").lower()
+
+
+def test_zero_knowledge_claim_boundary_is_explicit() -> None:
+ boundary = (MECHANISM / "CLAIM_BOUNDARY.md").read_text(encoding="utf-8")
+ assert "does not prove" in boundary
+ assert "bounded reference mechanism" in boundary
+ assert "UNKNOWN" in boundary
+ assert "CONFLICTED" in boundary
+
+
+def test_experimental_scope_does_not_absorb_the_governing_architecture() -> None:
+ readme = (ROOT / "README.md").read_text(encoding="utf-8")
+ architecture = (ROOT / "docs" / "ARCHITECTURE.md").read_text(encoding="utf-8")
+ experiment_index = (
+ ROOT / "experiments" / "artifact_first_mechanisms" / "README.md"
+ ).read_text(encoding="utf-8")
+ design = (
+ ROOT
+ / "experiments"
+ / "artifact_first_mechanisms"
+ / "reverification"
+ / "ROUND2_DESIGN_NOTE.md"
+ ).read_text(encoding="utf-8")
+
+ assert "Governing VSTD architecture" in readme
+ assert "not an optional research" in readme
+ assert "architecture, not a side experiment" in architecture
+ for phrase in (
+ "event serialization",
+ "TRUST-transfer algebra",
+ "ROT derivation and propagation",
+ "RUST concentration and localization",
+ "complete hidden-witness trichotomy derivation",
+ "specific optional proof backends",
+ ):
+ assert phrase in experiment_index
+ assert "semantic experiment for bounded identity disclosure" not in design
+ assert "Zero Identity experiment" not in design
+ assert "bounded identity-disclosure reference" in design
+
+
+def test_zizk_preserves_memetic_causality_without_localization_overclaim() -> None:
+ readme = (ROOT / "README.md").read_text(encoding="utf-8")
+ ladder = (ROOT / "standard" / "LADDER.md").read_text(encoding="utf-8")
+ mechanism = (MECHANISM / "README.md").read_text(encoding="utf-8")
+
+ assert "Zero identity means zero identity-derived verdict weight" in readme
+ assert "zero unevidenced knowledge is presumed" in readme
+ assert "cryptographic zero knowledge can enclose" in readme
+ assert "without attaching TRUST to the prover's identity" in readme
+ assert "TRUST is mechanism-earned artifact support" in readme
+ assert "ROT is typed, time-indexed degradation" in readme
+ assert "RUST is the inverse-TRUST diagnostic mechanic" in readme
+ assert "cryptographic zero-knowledge\nenclosure" in mechanism
+ assert "without importing prover identity into TRUST" in mechanism
+ assert "memetic causal backtrace" in ladder
+ assert "genetic or viral language names this inheritance mechanic" in ladder
+ assert "does not by itself establish\nintervention-level physical causality" in ladder
+
+
+def test_private_inputs_are_excluded_and_public_proof_artifacts_are_versioned() -> None:
+ ignore = (MECHANISM / ".gitignore").read_text(encoding="utf-8")
+ assert "private-*.json" in ignore
+ assert "local-artifacts/" in ignore
+ tracked = subprocess.run(
+ ["git", "ls-files", "--", "examples/zizk_artifact_first/risc0"],
+ cwd=ROOT,
+ check=True,
+ capture_output=True,
+ text=True,
+ ).stdout.splitlines()
+ assert any(path.endswith("recorded-proof/receipt.msgpack") for path in tracked)
+ assert any(path.endswith("recorded-proof/public.json") for path in tracked)
+ assert any(path.endswith("recorded-proof/self-test-results.json") for path in tracked)
+ assert not any("private-" in path and path.endswith(".json") for path in tracked)
+
+
+def test_recorded_public_proof_artifact_hashes_match_the_reported_run() -> None:
+ expected = {
+ "receipt.msgpack": "04813c4757ba4efbdad9d51d50d7402f3a98f6c23e53b9b58cce8af12ef9caa2",
+ "public.json": "188098e6ba1ac940475f15e0a4304ff08d678d98a9ed708dbe41dc6dde596b76",
+ "self-test-results.json": "e4c1bff21fb6161221276157fa96af6661af8635da35970ba12e462881f2c6fe",
+ }
+ for name, digest in expected.items():
+ artifact = MECHANISM / "recorded-proof" / name
+ assert hashlib.sha256(artifact.read_bytes()).hexdigest() == digest
+
+
+def test_recorded_verification_binds_the_tracked_guest_to_the_proof() -> None:
+ public = json.loads(
+ (MECHANISM / "recorded-proof" / "public.json").read_text(encoding="utf-8")
+ )
+ expected_image_id = public["image_id"]
+ script = (MECHANISM / "scripts" / "verify_recorded_proof.sh").read_text(
+ encoding="utf-8"
+ )
+ host = (MECHANISM / "host" / "src" / "main.rs").read_text(encoding="utf-8")
+
+ assert expected_image_id in script
+ assert "cargo run --locked --release -q -p vstd-zk-host -- image-id" in script
+ assert 'if [[ "${ACTUAL_IMAGE_ID}" != "${EXPECTED_IMAGE_ID}" ]]; then' in script
+ assert "verify recorded-proof/receipt.msgpack recorded-proof/public.json" in script
+ assert "let trusted_id = expected_id.unwrap_or_else(method_id);" in host
+ assert "trusted_id != method_id()" not in host