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..034d015 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: diff --git a/.github/ISSUE_TEMPLATE/specification-ambiguity.yml b/.github/ISSUE_TEMPLATE/specification-ambiguity.yml index 1a4f478..9b0ec07 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 + 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..c19f940 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,7 @@ ## Coordinate +> **Acronyms:** Verifier Standard (VSTD). + - VSTD layer/profile: - Repository release or target commit: - Claim, schema, or implementation seam: @@ -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/workflows/ci.yml b/.github/workflows/ci.yml index 8cc961f..e14efa3 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: @@ -35,6 +37,19 @@ 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 + release-integrity: runs-on: ${{ matrix.os }} strategy: @@ -96,12 +111,16 @@ 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["layer4_binding"]["verifier"]["specification_hash"], IndependentAuditor.verifier_descriptor().specification_hash); assert all(value.startswith("sha256:") for value in hashes), hashes' presentation: runs-on: ubuntu-latest @@ -113,23 +132,42 @@ jobs: - run: python scripts/check_presentation.py - run: python scripts/build_pages.py --output _site + 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, stdlib-smoke, scitt-crypto, 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 }} STDLIB: ${{ needs.stdlib-smoke.result }} + SCITT: ${{ needs.scitt-crypto.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 "$STDLIB" = success + test "$SCITT" = success test "$RELEASE" = success test "$REPRODUCIBLE" = success test "$WHEEL" = success test "$PRESENTATION" = success + test "$CODEQL" = success diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1113b76..9d931da 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,9 @@ 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 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]" @@ -55,12 +61,14 @@ jobs: python scripts/check_release_boundary.py dist/*.zip dist/*.whl dist/*.tar.gz 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["layer4_binding"]["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 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..c1d6473 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,24 +1,57 @@ # 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. +dynamics). Implementation status is layer-specific: the current VSTD-4 and Graph 2-4 +depth mechanisms compute candidates over caller-supplied references or ratings with +conformance `NOT_ESTABLISHED`; **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, +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 +68,52 @@ 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). +### 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 frozen wire 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 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: @@ -80,7 +152,13 @@ If that path is not inside this repository, prefix commands with `PYTHONPATH=src - `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`. +- `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_release_metadata.py`, and `release_artifacts.py`. - `tests/` — flat `tests/test_*.py`, no `conftest.py`. ## 5. Invariants that must not be refactored away @@ -103,18 +181,18 @@ keeps import cost near zero. Do not convert these into eager imports. **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. +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. **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. +**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` must carry `"$id": "https://timelordraps.github.io/verifier/schemas/"`. `scripts/build_pages.py` @@ -140,9 +218,13 @@ CRLF/LF equivalence as byte identity. This matters when working on Windows. 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`, +`stdlib-smoke`, `scitt-crypto`, `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. ## 7. Conventions @@ -167,8 +249,11 @@ assertion to make a suite green. 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. +frozen-wire 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..b9123cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,118 @@ # 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); +> 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 wire semantics. +- 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. +- State artifact support as forward causal-provenance flow and Rust as a memetic causal + backtrace toward recorded ancestor states, without inferring guilt, responsibility, or + intervention-level causal localization from diagnostic reachability alone. + +### Claim boundaries and validation + +- 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. +- 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 + the frozen `VSTD-0.1` wire identifier by 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; + version 1.2.0 has no actor/execution evidence-binding adapter and therefore never + derives `EVIDENCED` from serialized references. +- 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 + +- Preserve incompatible Graph assertions as evidence-linked conflict records and label + rating-derived levels as `CALLER_SUPPLIED` candidates with conformance `NOT_ESTABLISHED`. +- Classify the current VSTD-4 depth calculation as a structural candidate 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 Graph 2-4 candidates consistently on first-view, documentation, command, schema, + and SCITT surfaces. Keep challenge-ledger state, degradation from status already + recorded in a Graph, and the missing challenge-to-Graph adapter distinct. + +### 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`. +- Make package/reference status explicitly say VSTD-4 candidate conformance is + `NOT_ESTABLISHED`, 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 wire format; reject duplicate Graph + identifiers and reproduction levels 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. +- Classify generic-run `layer4_binding` as a legacy wire container rather than a layer + abstraction. Preserve pre-version-1.0 and version-1.x reads, keep current writes lossless + under the frozen profile, and require an explicit later profile/schema boundary before + replacing it; only `vstd4_conformance = NOT_EVALUATED` is accepted. + ## 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 @@ -52,7 +158,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 @@ -116,16 +222,18 @@ 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. +- Add the fourteen-rung VSTD-4 structural calculation and compute its candidate depth by + iterated satisfiability rather than copying a declared level. 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. +- Add the historical VSTD-Graph level calculation from membership, provenance closure, + status, and caller-supplied edge ratings, with a certificate explaining the next + unreachable candidate level. 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 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..618fc93 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,31 +1,118 @@ -# Contributing to VSTD +# Contributing to Verifier Standard (VSTD) + +> **Acronym:** GNU Privacy Guard (GPG). 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 layer 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 layer document; +2. frozen wire identifier 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 layer, repository release, and exact coordinate or seam; +- state compatibility effects, including frozen wire 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 scripts/check_presentation.py +python scripts/check_acronyms.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 +``` + +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; +- compatibility and frozen-wire 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..ef0c5de 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,10 +13,12 @@ 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 depth runtime +computes only a structural candidate over caller-supplied references with conformance +`NOT_ESTABLISHED`; no mechanism binds all rung propositions and VSTD-1/2/3 preconditions +into VSTD-4 conformance. `VSTD4-GDC-1` has no demonstrated independent implementation +or external interoperability. VSTD-5 remains draft and has no shipped witness procedure. ## Layer and release states @@ -50,7 +54,7 @@ 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. diff --git a/HUMANS.md b/HUMANS.md new file mode 100644 index 0000000..7564ad2 --- /dev/null +++ b/HUMANS.md @@ -0,0 +1,89 @@ +# 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 wire 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. + +## 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 layer document, frozen wire/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..1c557bd 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,44 @@
-VSTD object and graph verification layers, each requiring its own separate evidence - -# VSTD +# Verifier Standard (VSTD) **Portable, bounded, refutable evidence for computational claims.** -[![Conformance](https://github.com/TimeLordRaps/verifier/actions/workflows/ci.yml/badge.svg)](https://github.com/TimeLordRaps/verifier/actions/workflows/ci.yml) +[![Repository checks](https://github.com/TimeLordRaps/verifier/actions/workflows/ci.yml/badge.svg)](https://github.com/TimeLordRaps/verifier/actions/workflows/ci.yml) [![Latest release](https://img.shields.io/github/v/release/TimeLordRaps/verifier?display_name=tag&sort=semver)](https://github.com/TimeLordRaps/verifier/releases/latest) [![Python 3.10–3.13](https://img.shields.io/badge/python-3.10%E2%80%933.13-3776AB.svg)](https://www.python.org/) [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-2f7d6d.svg)](LICENSE) -[![Status: alpha](https://img.shields.io/badge/status-founder--maintained%20alpha-d97706.svg)](#project-status) +[![Status: alpha](https://img.shields.io/badge/status-alpha-d97706.svg)](#current-maturity) -*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) +> **Acronym used below:** 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. + +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, reproduction, and a flagship adversarial demo. VSTD-4 depth and +Graph layers 2–5 are candidate computations with conformance `NOT_ESTABLISHED`; +VSTD-5 is not implemented. 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) -## See it fail correctly +## 30–60 second demonstration ```bash git clone https://github.com/TimeLordRaps/verifier.git @@ -32,7 +47,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 +55,85 @@ 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 repository computes a candidate, but a required evidence-binding or conformance mechanism is absent. | 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 compatibility 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 | A grounded decision certificate (GDC) parser/kernel plus structural depth candidate | The certificate binds formula, grounding, claim, roots, and bounds; rung references and VSTD-1/2/3 preconditions are not evidence-bound by the depth runtime | `NOT_ESTABLISHED` | Rung-by-rung evidence validation, lower-layer composition, and an independent checker implementation | +| VSTD-5 | Draft | Fail-closed rejection of current VSTD-4 candidates only | No witness-corroboration binding is implemented | Not implemented | Witness protocol, qualifying VSTD-4 input, distinct actors, independence evidence, and operational experience | +| 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 layer specification | Candidate bounded-collection level and ceiling-certificate computation | Uses caller-supplied object and edge ratings; the ratings are not validated against layer-2 evidence | `NOT_ESTABLISHED` | Rating-to-evidence validators for members, ancestors, statuses, and transformation edges | +| VSTD-Graph-3 | Project layer specification | Candidate accountable-provenance level and ceiling-certificate computation | Uses caller-supplied object and edge ratings; no mechanism establishes that VSTD-3 produced them | `NOT_ESTABLISHED` | VSTD-3 rating evidence for every member, reachable ancestor, and transformation edge | +| VSTD-Graph-4 | Project layer specification | Candidate refutable-transformation level and ceiling-certificate computation | Uses caller-supplied object and edge ratings; claimed refutability-closure records are not validated | `NOT_ESTABLISHED` | VSTD-4 rating evidence and validation of every reached refutability closure | +| VSTD-Graph-5 | Draft profile | Candidate level 5 can be computed from caller-supplied ratings | No independent-witness or rating-evidence binding | `NOT_ESTABLISHED` | Graph-2–4 evidence binding plus a corroborated verification-network protocol | +| Generic run | Frozen `VSTD-0.1` compatibility profile under VSTD-1 | 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 | `vstd4_conformance = NOT_EVALUATED` | Sandbox, generic external-evidence resolver, and actor/execution binder | +| 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 layer or profile | Artifact-bound claim/evidence/mechanism semantics, contextual actor/artifact roles, forward support, and reverse diagnostic Rust constraints | Existing mechanism-specific evidence only; identity or reputation alone, repetition, and topology add no assurance | Governing architectural invariant; not a separate VSTD conformance result | Event serialization, support-transfer algebra, Rust concentration/localization, complete trichotomy derivation, and maturation of specific 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 verifier command that can run network-offline after setup | 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 | Complete VSTD trichotomy predicate, second build, external audit, 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: + +> 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. -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. +## Architecture -## Two axes; evidence never substitutes +Verifier Standard object and graph verification layers, each requiring separate evidence -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. +Specification numbers identify verification depth, not software revisions. The object +axis evaluates one computational claim; the Graph axis evaluates a bounded collection +and its recorded transformations. -| Depth | VSTD object mechanics | VSTD-Graph collection dynamics | +| Depth | Object axis | Graph axis | |---:|---|---| | 1 | Claim mechanics | Recorded lineage | | 2 | Verification surface | Bounded collection surface | @@ -76,136 +141,190 @@ 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 higher-layer result does **not** supply, imply, upgrade, or repair a lower-layer +result. Aggregate depth requires separate passing evidence for every preceding layer. -Start with [`standard/LADDER.md`](standard/LADDER.md). Wire identifiers are frozen -separately in [`standard/WIRE_IDENTIFIERS.md`](standard/WIRE_IDENTIFIERS.md). +The same recorded causal-provenance graph can carry bounded artifact support forward and +diagnostic Rust backward. This memetic propagation is how provenance state moves through +developmental claim space. Rust identifies ancestor states worth examining; traversal +alone does not prove guilt, responsibility, causal localization, or automatic ancestor +falsification. See [the governing +architecture](standard/LADDER.md#11-artifact-first-causal-provenance-orientation). -## Choose a path +This is VSTD's **ZIZK artifact-first trust architecture**, not an optional research +profile. Zero identity is not anonymity or absence of identifiers: it means identity or +reputation alone cannot strengthen an artifact-bound result. A mechanism may still earn +an exact attribution, authorization, or separation claim by checking the required identity +evidence. Zero knowledge applies only when a named proof system establishes its formal +privacy property for the exact predicate and parameters under explicit assumptions; it is +not a property of VSTD generally and does not make disclosure mandatory. 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. -| 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. +## Install and use + +The distribution name is `verifier-standard`. The published base package has no +required third-party runtime dependencies. + +```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 ".[scitt]" # optional SCITT/COSE experiment +``` + +`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. + +An unrelated PyPI distribution named `verifier` exports the same top-level Python +import. Do not co-install it with `verifier-standard`. + +### 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. +- [`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 + +Read authoritative material in this order: -## Verify a release +1. [Ladder and composition](standard/LADDER.md) +2. [Object and Graph layer documents](standard/) +3. [Frozen wire 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) -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: +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..7516977 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,12 +1,31 @@ # Release procedure +> **Acronyms:** 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); +> 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,11 @@ 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`, + distributions declare `verifier-standard`, version `1.2.0`, import package `verifier`, and the frozen three console scripts. - 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 @@ -45,7 +64,9 @@ exact, publicly resolvable commit. distribution 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. 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 @@ -53,7 +74,7 @@ exact, publicly resolvable commit. 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 @@ -67,7 +88,7 @@ exact, publicly resolvable commit. 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,8 +97,9 @@ 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. + rechecks protected-main ancestry, package version, the successful protected + repository-check aggregate (the `conformance-gate` status context), 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 access only the wheel and source distribution, requires approval in the protected @@ -92,7 +114,7 @@ exact, publicly resolvable commit. An attestation complements but does not replace the release manifest, and it does not turn 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..3cd8d36 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,9 +1,17 @@ -# 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). **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 +26,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 +86,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 ``` @@ -45,6 +101,20 @@ claim → evidence → bounded check → publish → challenge → adjudicate 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. +## Current experimental development tracks + +This dated register records substantive work as of **2026-08-25**. 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 | [`experiments/artifact_first_mechanisms/experiment.json`](experiments/artifact_first_mechanisms/experiment.json) | Experimental event serialization, transfer algebra, Rust concentration/localization, complete trichotomy derivation, and specific unfinished optional proof backends under the already-governing ZIZK architecture. The bounded identity evaluator and tracked RISC Zero reference mechanism are under `examples/`. | Implement and falsify each mechanism without treating the governing orientation as contingent. | +| 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 **Exit evidence** @@ -57,7 +127,7 @@ 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** @@ -73,11 +143,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. + +**Still build** -- a non-normative profile for observable user, agent, and tool messages; +- 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,7 +169,10 @@ 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. diff --git a/SECURITY.md b/SECURITY.md index cf97653..7c279bd 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,5 +1,8 @@ # Security policy +> **Acronyms:** application programming interface (API); hash-based message authentication code (HMAC); +> Verifier Standard (VSTD). + ## Supported release Only the latest tagged public release is supported. Historical receipts and standards @@ -13,10 +16,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 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..703d591 --- /dev/null +++ b/docs/ACRONYMS.md @@ -0,0 +1,123 @@ +# 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. + +| 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/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..ffb4a18 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,230 @@ +# Verifier Standard (VSTD) conformance architecture + +> **Acronyms:** Boolean satisfiability problem (SAT); command-line interface (CLI); +> 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/` + +Use this order when two surfaces appear to disagree: + +1. normative layer document; +2. frozen wire identifier 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. + +## Layer 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` | `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 unbound candidate depth in `verifier.core.depth` | `vstd4_certificate.json`, `vstd4_receipt.json` | `test_gdc_certificate.py`, `test_vstd4_depth.py` | +| VSTD-5 | `standard/VSTD-5.md` | fail-closed candidate rejection only | `vstd5_receipt.json` | `test_vstd4_depth.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` | `computed_graph_level` within `vstd_graph_receipt.json` | `test_graph_level.py` | +| ZIZK artifact-first trust | `standard/LADDER.md` section 1.1 | Governs every mechanism; bounded RISC Zero example under `examples/zizk_artifact_first/` | No separate wire identifier or profile | presentation, experiment-manifest, and ZIZK mechanism tests | + +VSTD-5 is draft. The VSTD-4 depth runtime and Graph-2 through Graph-5 compute candidates +from caller-supplied references or ratings and return +`conformance_status = NOT_ESTABLISHED`; evidence binding is not implemented. Neither +candidate is layer conformance. + +## Governing ZIZK architecture and mechanism ownership + +Zero-identity/zero-knowledge (ZIZK) artifact-first trust is a governing VSTD +architecture, not a side experiment, layer, profile, or scalar trust system. Its +normative source is `standard/LADDER.md` section 1.1: + +- identity or reputation alone, popularity, and repetition supply no assurance; +- actor and artifact are contextual roles rather than permanent entity classes; +- established artifact support may move forward only across admissible bound + transformations, while every child discharges its new obligations; +- diagnostic Rust may move backward only as recorded ancestral reachability; and +- forward support and backward Rust never cancel, reverse direction, or manufacture a + clean signal from `UNKNOWN` or `CONFLICTED` inputs. + +Zero identity is the no-identity-derived-trust rule above, not anonymity or absence of +identifiers. Checked identity evidence may establish only its exact attribution, +authorization, or separation proposition. Zero knowledge is mechanism-specific: it +applies only where a named proof system establishes the formal property for the exact +predicate and parameters under explicit assumptions. + +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 | `examples/zizk_artifact_first/risc0/`; native verification only, no VSTD receipt mapping | +| Bounded identity-disclosure evaluator | Bounded non-normative reference mechanism | `examples/zizk_artifact_first/zero_identity/`; no identity-derived trust | +| Event serialization and support-transfer algebra | Experimental and unimplemented | May implement the governing direction but cannot redefine it | +| Rust concentration and localization | Experimental and unimplemented | Diagnostic reachability only until a separately specified mechanism earns more | +| 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 | + +## Wire dispatch + +Dispatch first by `schema_version`, then by a required profile discriminator when the +frozen identifier carries multiple profiles. `VSTD-0.1` generic-run receipts require +`receipt_kind = "generic_computational_run"`. Legacy SAT/derivation receipts have no +discriminator and must match the claim-receipt required fields. Unknown combinations fail +closed. + +## 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. + +## 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 `layer4_binding.refutation_surface` are explicit data or +extension surfaces. The refutation surface remains open because versions 1.1.2 and 1.1.3 +preserved caller-defined domain refutations under the frozen `VSTD-0.1` wire identifier. +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. + +### Historical generic-run binding container + +`layer4_binding` is a legacy `VSTD-0.1` generic-run wire container, not a VSTD-4 object. +Versions 0.1.0 and 0.2.0 omitted it; released writers from 1.0.0 through 1.1.3 emitted it. +It is optional for historical reads and participates in the canonical digest when present. +The version 1.2.0 writer keeps emitting it because the current profile has no other lossless +location for its manifest-declared context. Dropping it would discard evidence; moving it +would define a new wire shape. + +| 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`. | +| `vstd4_conformance` | VSTD-4-specific Assessment coordinate | Only `NOT_EVALUATED` is permitted; it earns no VSTD-4 state. | + +Layers are assessment coordinates, not containers for generic verification context. The +legacy name must not generate `layer1_binding`, `layer2_binding`, or similar structures. +Replacing this writer requires an explicit later generic-run profile discriminator and +matching schema coordinate; a package version alone cannot redefine the frozen profile. +No future identifier is reserved here. + +## Five-As human traversal + +**Status:** non-wire architecture guide. The five As are roles in a human traversal of +existing VSTD records, not five new object types, levels, 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 actor/execution evidence binding is not implemented. 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-level 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, and prior commitments can preserve and reference the output. | There is no universal recursive-loop envelope; any future wire representation requires a separate proposal. | + +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 depth 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. +Artifact trust is bounded forward support across an admissible recorded transformation; +the child still discharges its new obligations. Rust is reverse diagnostic reachability +from a downstream deviation toward recorded ancestors. Together they form memetic +causal-provenance propagation over one development graph: support moves forward through +descendant claim space and Rust backtraces toward ancestor states. This propagation is +not guilt, responsibility, causal localization, or automatic ancestor falsification, and +neither direction is currently an emitted or validated transfer result. + +### 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 candidate level. | +| Repeated identical reruns | At most the same bounded equivalence result; repetition does not prove independence or a stronger tier. | +| 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 level. | +| Stale, revoked, challenged, or unknown evidence reused as clean current support | Inadmissible to a clean current Graph candidate; the historical record remains. | +| 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. Because version 1.2.0 has no +actor/execution evidence-binding adapter, the bundled runtime treats supplied assertions +as no stronger than `DECLARED`, rejects receipts that label them `EVIDENCED`, and never +derives `EVIDENCED`. + +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 level. + +### 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 Graph recomputation follows the full ancestor closure and returns level 0. It does not rewrite the historical receipt. | +| 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` blocks a clean candidate. Conflict resolution is not implemented; any future resolution must be additive and retain the competing evidence. | +| 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 | Existing ancestor queries establish recorded reverse reachability only. No runtime emits Rust, measures independent concentration, or attributes causal responsibility. | +| A challenge ledger changes a claim's current status | The append-only ledger derives `CHALLENGED` or `REVOKED`, but no adapter binds that claim status into a Graph artifact. Cross-surface propagation is `NOT_ESTABLISHED`, not silently clean. | +| Later evidence is intended to clear a conflict | No conflict-resolution transition exists in version 1.2.0. Removing the old record would violate additive correction; a future mechanism must preserve it and identify what resolved it. | +| Candidate calculation encounters cyclic ancestry | Rejected before candidate calculation; recursive topology cannot manufacture assurance. | + +The implemented forward blast-radius query discovers recorded downstream artifacts and +generic-run receipts that require reconsideration when given an invalidated artifact. It is +a discovery mechanism, not automatic status mutation, current-admissibility adjudication, +or proof of causal influence. Automatic propagation for challenge, staleness, supersession, +conflict resolution, Artifact support, and Rust remains `NOT_ESTABLISHED` until a distinct +mechanism binds the lifecycle event to the exact Graph artifact and proposition. diff --git a/docs/CLAIMS_AND_LIMITS.md b/docs/CLAIMS_AND_LIMITS.md index 5533a9e..c32bcdf 100644 --- a/docs/CLAIMS_AND_LIMITS.md +++ b/docs/CLAIMS_AND_LIMITS.md @@ -1,5 +1,16 @@ # 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); 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,13 +20,31 @@ 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. | VSTD-4 depth conformance is `NOT_ESTABLISHED`; rung evidence and lower-layer preconditions are not bound by the depth runtime. | +| What can VSTD-Graph establish? | Stored objects, transformations, conflicts, recorded reachability, and policy results over that graph. | Content digests, Graph receipt, hyperedges, statuses, and named query or policy. | Graph levels 2–5 are candidates from caller-supplied ratings; completeness, causality, and rating-to-evidence binding are absent. | +| 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 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 actor independence, automatic challenge-to-Graph propagation, VSTD-5, and unqualified provenance truth. | No current ordinary VSTD mechanism observes or validates those propositions. | 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 | @@ -30,14 +59,39 @@ 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. | +| “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, field-level 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.** | 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. | +| “The reference implementation computed VSTD-Graph depth `N`.” | **Not yet as a conformance claim.** | The current implementation computes a candidate level from caller-supplied artifact and edge ratings. 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 each reached layer. | The supplied ratings were independently derived, each layer's evidence passed, or Graph conformance was established. | + +## 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 +100,13 @@ 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 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.” | **Not established by the current reference implementation.** | VSTD-5 requires evidence-bound VSTD-1/2/3 preconditions and normative VSTD-4 conformance at depth 14. `require_vstd5_entry` rejects the current unbound candidate. | 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 +158,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 +189,13 @@ VSTD-4 safe template: > establish evidence authenticity, complete policy coverage, independent > witnessing, or truth outside the coordinate. -VSTD-5 draft boundary template: +VSTD-5 draft boundary template for the current implementation: -> 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. +> The artifact reached VSTD-4 structural candidate depth 14 with an accepted +> certificate over caller-supplied references. Conformance remains +> `NOT_ESTABLISHED`; the VSTD-5 entry gate rejected the candidate. VSTD-5 remains +> a draft specification, and no VSTD-5 readiness, conformance, or +> independent-witness claim is made. ## Prohibited shortcuts diff --git a/docs/CONCEPTS_AND_PRECEDENTS.md b/docs/CONCEPTS_AND_PRECEDENTS.md new file mode 100644 index 0000000..0955c8f --- /dev/null +++ b/docs/CONCEPTS_AND_PRECEDENTS.md @@ -0,0 +1,95 @@ +# 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 kinds of link: + +1. **Orientation links** point to Wikipedia for a quick definition. The links use + ordinary Markdown title text, which some browsers expose as a small hover tooltip. + GitHub does not run Wikipedia's Page Previews code, so a full infobox-style hover card + is not portable in repository Markdown. +2. **Primary references** point to standards, specifications, or original papers. These + establish the neighboring precedent described here. They do not prove that VSTD is + correct, adopted, interoperable, accredited, or conformant to the referenced system. + +When an orientation summary and a primary source differ, use the primary source. When a +primary source and a VSTD requirement differ, the VSTD document controls VSTD conformance +and the difference must remain explicit. + +## Orientation glossary + +| Concept | Quick orientation | How VSTD uses or bounds it | +|---|---|---| +| 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. | +| Layered controls | [Defense in depth](https://en.wikipedia.org/wiki/Defense_in_depth_%28computing%29 "Wikipedia orientation; not a VSTD authority") | The analogy is multiple failure classes. VSTD adds the stricter rule that evidence for one layer never supplies another. | +| 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. | +| 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. | +| 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. | +| 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 layer 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 layer 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 ladder 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 layers. | +| 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 a cryptographic wire 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 ladder." +- "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..99f653c 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,15 +38,23 @@ 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: @@ -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..a319c54 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) — numbering, separate evidence per layer, 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/assets/site.css b/docs/assets/site.css index 68d667a..8101834 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,6 +81,30 @@ 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); } footer { border-top: 1px solid var(--line); margin-top: 54px; padding: 28px 0 44px; color: var(--muted); font-size: .9rem; } @@ -82,9 +116,13 @@ footer { border-top: 1px solid var(--line); margin-top: 54px; padding: 28px 0 44 @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; } } + +@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..8a792bb 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..21af4d5 100644 --- a/docs/assets/vstd-overview.svg +++ b/docs/assets/vstd-overview.svg @@ -1,5 +1,5 @@ - VSTD two-axis verification overview + Verifier Standard (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. @@ -28,7 +28,7 @@ portable · bounded · refutable - FOUNDER-MAINTAINED ALPHA + ALPHA PROJECT SPECIFICATION OBJECT MECHANICS COLLECTION DYNAMICS @@ -68,9 +68,9 @@ REF. SUBSETREF. SUBSET - EXPERIMENTALIMPLEMENTED - IMPLEMENTEDIMPLEMENTED - IMPLEMENTEDIMPLEMENTED + EXPERIMENTALCANDIDATE + IMPLEMENTEDCANDIDATE + CANDIDATECANDIDATE DRAFTDRAFT diff --git a/docs/guides.html b/docs/guides.html new file mode 100644 index 0000000..bb8cd2e --- /dev/null +++ b/docs/guides.html @@ -0,0 +1,96 @@ + + + + + + + + + + + + VSTD guides and standards + + + + + + +
+ +
+ +
+
+
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).

+
+ +
+
+

Start here

+ +
+ + + + + +
+

Project and contribution

+ +
+
+
+ +
VSTD · Apache-2.0 · Maintainer-led alpha · No standards-body endorsement claimed.
+ + diff --git a/docs/index.html b/docs/index.html index 8c24c75..538f0b1 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,22 +3,29 @@ - + + VSTD — portable, bounded, refutable + + +
@@ -29,11 +36,24 @@
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 +

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.

The VSTD object and graph axes, where every layer requires its own separate evidence @@ -66,7 +86,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 +95,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,11 +104,15 @@

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. VSTD-4 depth and Graph layers 2-5 are candidate computations with conformance NOT_ESTABLISHED; VSTD-5 is draft and not implemented. The project has no demonstrated external adoption, independent implementation, interoperability deployment, or third-party security review.

+

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/layers/vstd-3/compatibility.md b/docs/layers/vstd-3/compatibility.md index 01f9ba8..cfddd9f 100644 --- a/docs/layers/vstd-3/compatibility.md +++ b/docs/layers/vstd-3/compatibility.md @@ -1,9 +1,16 @@ -# VSTD-3 implementation compatibility +# Verifier Standard (VSTD)-3 implementation compatibility + +> Reader aid: [concept glossary and primary precedents](../../CONCEPTS_AND_PRECEDENTS.md). VSTD-3 is additive. It does not reinterpret earlier receipt wire formats. For the historical filename and 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. diff --git a/docs/layers/vstd-3/references.md b/docs/layers/vstd-3/references.md index f59b215..b223f41 100644 --- a/docs/layers/vstd-3/references.md +++ b/docs/layers/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-layer 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/layers/vstd-3/threat-model.md index b697e40..81c6a17 100644 --- a/docs/layers/vstd-3/threat-model.md +++ b/docs/layers/vstd-3/threat-model.md @@ -1,4 +1,10 @@ -# VSTD-3 threat model +# Verifier Standard (VSTD)-3 threat model + +> **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). **Layer:** VSTD-3; historical receipt wire identifier `VSTD-3.0` **Purpose:** defensive verification and conformance; not offensive exploit guidance diff --git a/docs/layers/vstd-3/vendor-integration.md b/docs/layers/vstd-3/vendor-integration.md index 91d9671..dba3255 100644 --- a/docs/layers/vstd-3/vendor-integration.md +++ b/docs/layers/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/profiles/competition-evaluation.md b/docs/profiles/competition-evaluation.md index 44f63cd..e405987 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,10 @@ 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. + ## 1. Evaluation surface An integration declares the exact surface before it reports a verified result: @@ -43,7 +51,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 +75,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..c68ab74 --- /dev/null +++ b/docs/profiles/experimental-workflow.md @@ -0,0 +1,172 @@ +# 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 profile is not a new VSTD layer or verdict. It does not change any receipt wire +identifier, canonical digest, schema `$id`, conformance behavior, or normative VSTD +semantics. + +## 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/reference.html b/docs/reference.html new file mode 100644 index 0000000..89201a9 --- /dev/null +++ b/docs/reference.html @@ -0,0 +1,467 @@ + + + + + + + + + + + + VSTD docs — command-line interface (CLI) and application programming interface (API) reference + + + + + + +
+ +
+ +
+
+
Reference · verifier-standard 1.2.0 · VSTD-4 CANDIDATE; CONFORMANCE NOT_ESTABLISHED
+

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.

+
+ + + + + + + + + + +
CommandWhat it doesImplementation entry points
vstd demoRuns 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 planResolves a manifest's command and declared paths without executing anything.verifier.core.run:load_manifest
verifier.core.run:describe_run_plan
vstd runExecutes a trusted manifest without sandboxing, captures the observed execution, and writes a canonically digested receipt.verifier.core.run:load_manifest
verifier.core.run:capture_run
verifier.core.receipt:compute_canonical_digest
vstd validateDispatches on the receipt's frozen wire 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:validate_run_receipt
verifier.data.receipt:validate_data_receipt
verifier.hardware.validation:validate_vstd3_receipt
vstd inspectPrints the claim coordinate, digest, and verdict surface of a stored receipt.verifier.core.run:inspect_run_receipt
verifier.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:reproduce_run_receipt
verifier.data.receipt:reproduce_data_receipt
vstd impactFinds stored run receipts whose recorded ancestry reaches a revoked provenance artifact.verifier.core.run: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 experimentValidates 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 / claimsEvaluates 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
+
+
+ +
+
+
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.

+ + + + +
ArgumentKindMeaning
--scenariooptionalRun all scenarios or one named scenario. (one of: all, wrong-artifact, honest-unknown, inflated-tier, poisoned-ancestor) [default: all]
--jsonoptional
--emit-specimensoptionalWrite deterministic JSON specimens and observations to DIR.
+
+
+

vstd run

+

Execute a trusted manifest without sandboxing and capture a VSTD receipt.

+ + + + +
ArgumentKindMeaning
manifestpositionalJSON or YAML run manifest.
--outputoptionalReceipt output directory.
--receipt-idoptionalOverride the manifest claim id.
+
+
+

vstd plan

+

Show a manifest's declared command and paths without executing it.

+ + + +
ArgumentKindMeaning
manifestpositionalJSON or YAML run manifest.
--jsonoptional
+
+
+

vstd validate

+

Run implemented receipt checks; Graph candidate validation is not conformance.

+ + + + +
ArgumentKindMeaning
receiptpositionalReceipt directory or receipt.json.
--jsonoptional
--keyoptional
+
+
+

vstd inspect

+

Inspect a generic-run or VSTD-Graph receipt; validate and report VSTD-3.

+ + + + +
ArgumentKindMeaning
receiptpositionalReceipt directory or receipt.json.
--jsonoptional
--keyoptional
+
+
+

vstd reproduce

+

Replay the mechanisms available in a stored receipt.

+ + + + +
ArgumentKindMeaning
receiptpositionalReceipt directory or receipt.json.
--jsonoptional
--rerunoptionalGeneric-run receipts only: execute the recorded command again.
+
+
+

vstd impact

+

Find run receipts affected by a provenance-artifact revocation.

+ + + + +
ArgumentKindMeaning
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.

+ + + + +
ArgumentKindMeaning
artifact_idpositional
--receiptoptional
--directionoptional(one of: ancestors, descendants, blast_radius) [default: ancestors]
+
+
+

vstd data graph

+

Subcommand group.

+ + +
ArgumentKindMeaning
receiptpositional
+
+
+

vstd data export

+

Subcommand group.

+ + +
ArgumentKindMeaning
receiptpositional
+
+
+

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.

+ + + + +
ArgumentKindMeaning
manifestpositionalExperimental workflow manifest JSON.
--repo-rootoptionalRepository root used to verify every repo: artifact locator.
--jsonoptional
+
+
+

vstd experiment github-events

+

Map a strict normalized GitHub snapshot to verdict-neutral events.

+ + + +
ArgumentKindMeaning
snapshotpositionalNormalized GitHub snapshot JSON.
--jsonoptional
+
+
+

vstd hardware

+

Discover or emulate accelerator evidence.

+

No arguments; this command only groups subcommands.

+
+
+

vstd hardware list

+

List accelerator profiles.

+ + + +
ArgumentKindMeaning
--vendoroptional
--jsonoptionalEmit stable machine-readable JSON.
+
+
+

vstd hardware inspect

+

Inspect one accelerator profile.

+ + + +
ArgumentKindMeaning
profile_idpositional
--jsonoptionalEmit stable machine-readable JSON.
+
+
+

vstd hardware discover

+

Run a vendor or generic adapter.

+ + + + + +
ArgumentKindMeaning
--adapteroptional(one of: generic, nvidia, amd, intel)
--fixtureoptional
--outputoptionalWrite the normalized adapter result JSON.
--jsonoptionalEmit stable machine-readable JSON.
+
+
+

vstd hardware emulate

+

Run the deterministic virtual firmware contract probe.

+ + + + + + + + +
ArgumentKindMeaning
--outputoptional
--created-atoptionalFinal ISO-8601 receipt timestamp.
--device-idoptional[default: vstd3-virtual-0]
--firmware-versionoptional[default: 1.0.0]
--key-idoptional[default: vstd3-virtual-device-key]
--key-hexoptionalTest-only emulator HMAC key in hex.
--jsonoptionalEmit stable machine-readable JSON.
+
+
+

vstd hardware attest

+

Run an explicitly virtual attestation probe; no commodity claim is made.

+ + + + + + + + + +
ArgumentKindMeaning
--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
--jsonoptionalEmit stable machine-readable JSON.
+
+
+

vstd hardware capabilities

+

Evaluate incremental VSTD 3 conformance profiles.

+ + + + +
ArgumentKindMeaning
receiptpositional
--jsonoptionalEmit stable machine-readable JSON.
--keyoptionalTest-only HMAC verification key; repeat for multiple key ids.
+
+
+

vstd hardware verify

+

Verify a VSTD 3 receipt and all recorded passing claims.

+ + + + +
ArgumentKindMeaning
receiptpositional
--jsonoptionalEmit stable machine-readable JSON.
--keyoptionalTest-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.

+ + + + +
ArgumentKindMeaning
receiptpositional
--jsonoptionalEmit stable machine-readable JSON.
--keyoptionalTest-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.

+ + + +
ArgumentKindMeaning
receiptpositional
--jsonoptionalEmit stable machine-readable JSON.
+
+
+

vstd evidence

+

Inspect VSTD 3 evidence strength.

+

No arguments; this command only groups subcommands.

+
+
+

vstd evidence inspect

+

Subcommand group.

+ + + + +
ArgumentKindMeaning
receiptpositional
--jsonoptionalEmit stable machine-readable JSON.
--keyoptionalTest-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.

+ + + + +
ArgumentKindMeaning
receiptpositional
--jsonoptionalEmit stable machine-readable JSON.
--keyoptionalTest-only HMAC verification key; repeat for multiple key ids.
+
+
+

vstd claims explain

+

Subcommand group.

+ + + +
ArgumentKindMeaning
kindpositional(one of: DEVICE_IDENTITY, FIRMWARE_INTEGRITY, EXECUTION_OBSERVED, EXECUTION_ATTESTATION, EXECUTION_ACCOUNTING, ACCOUNTING_CONTINUITY, COMPLETE_MEDIATION, FLEET_COMPLETENESS, PHYSICAL_WORLD_COMPLETENESS)
--jsonoptionalEmit stable machine-readable JSON.
+
+
+
+ +
+
+
API
+

Top-level Python exports.

+

The names in verifier.__all__, with their live + signatures and declared docstrings. Public subpackage surfaces are not exhaustively + listed here; use the architecture map + to reach their owning modules, schemas, and tests.

+
+

DecisionCertificate class

+
DecisionCertificate(header: 'CertificateHeader', formula: 'tuple[tuple[int, ...], ...]', grounding: 'Grounding', decision: 'DecisionBlock', hints: 'dict[str, Any]' = <factory>) -> None
+

No docstring is declared for this export; the signature above is its whole declared surface.

+

Defined in verifier.core.certificate

+ + + + +
MethodSummary
digest(self) -> 'str'
to_dict(self) -> 'dict[str, Any]'
without_hints(self) -> "'DecisionCertificate'"Hint-stripped form.
+
+
+

ReproducibilityLevel enum

+

Enumeration of the exported result values.

+

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-0.2') -> None
+

A finite verification geometry and its higher-order audit surface.

+

Defined in verifier.core.geometry

+ + + + + +
MethodSummary
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.
+
+
+

VerificationVerdict enum

+

Enumeration of the exported result values.

+

Defined in verifier.core.checker

+

Members: VERIFIED, FALSIFIED, INDETERMINATE, UNSUPPORTED

+
+
+

VstdReceipt class

+
VstdReceipt(schema_version: '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
+

No docstring is declared for this export; the signature above is its whole declared surface.

+

Defined in verifier.core.receipt

+ + + + + + +
MethodSummary
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'
+
+
+

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

+ +
+
+

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

+ +
+
+

require_vstd5_entry function

+
require_vstd5_entry(result: 'DepthResult') -> 'DepthResult'
+

Reject the current unbound candidate result at the VSTD-5 boundary.

+

Defined in verifier.core.depth

+ +
+
+

validate_run_receipt function

+
validate_run_receipt(receipt_path_or_dir: 'Path') -> 'int'
+

No docstring is declared for this export; the signature above is its whole declared surface.

+

Defined in verifier.core.run

+ +
+
+

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 the frozen wire identifiers they belong to are listed in the + standard.

+ +
+
+
+ +
VSTD · Apache-2.0 · Reference generated from the implementation by scripts/build_reference.py.
+ + diff --git a/docs/standards/SCITT_SEMANTIC_BOUNDARY.md b/docs/standards/SCITT_SEMANTIC_BOUNDARY.md new file mode 100644 index 0000000..21ba78a --- /dev/null +++ b/docs/standards/SCITT_SEMANTIC_BOUNDARY.md @@ -0,0 +1,203 @@ +# 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 layers 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 layer 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. Rating evidence and challenge-to-Graph propagation remain + `NOT_ESTABLISHED`. + +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 remain draft. A higher VSTD layer does not supply a missing +lower layer. + +## 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 layers and 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 a zero-knowledge proof +protocol. Full-disclosure receipts remain valid. Zero-knowledge and zero-identity +work belongs in separately reviewed experimental profiles, and no receipt may be +called zero knowledge 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. A future reputation or “rust” view can be derived from that recorded history +only as a separate, time-indexed policy result. It must never overwrite a native +verdict or turn repeated registrations, signatures, or observations into truth. + +## 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 wire 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..b230d46 --- /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 layers 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; layer-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 reputation / trust | Graph history can record challenges, staleness, supersession, revocation, and refutation; 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 and age do not increase epistemic strength by themselves. | A future reputation/rust view must be separately derived, policy-bound, and unable to upgrade native results. | +| 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 at different layers. | 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 layer. | 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-level 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 can derive claim state. Graph candidate computation degrades when an ancestor already records `REVOKED`. No adapter binds the first result into the second, so challenge-to-Graph propagation is `NOT_ESTABLISHED`. | 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. | Neither SCITT core nor current VSTD supplies the missing cross-surface propagation mechanism. | Preserve each native state. A future adapter must bind the exact claim, artifact, event, and policy before a relying party changes Graph state. | +| 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 at higher graph levels. | 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 layers 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 levels 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 layers. | Preserve native reason codes and identify which layer 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 layer 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 levels 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 layer. | 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 layer, 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..0f982f8 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:94e4f7d4cb771f76d3e856ad93f0e7c3d151d47d862abef00fbcda23d7975e1f", + "parser_hash": "sha256:7d05d3810219b1ef8400accd5735fbde494c4fb310c4d86f08381d4979dcde5c", + "specification_hash": "sha256:9648fee5c94a8c41a581ec003226dd87eca59bc6e2356ed0383fcabbf02a1d5f" } }, "certificate": { @@ -157,7 +157,7 @@ ] }, "header": { - "binding": "aee206fd7bc450c01bda6c54a4e26ba228ebf752b4085df56f1d250055a7c9cd", + "binding": "45fb1d24853ba1532e28ebaa31b73eedcff96552fc130cfbbfe9dc4262358808", "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..e7798db 100644 --- a/examples/flagship_demo/specimens/index.json +++ b/examples/flagship_demo/specimens/index.json @@ -37,9 +37,9 @@ "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 level 0 from caller-supplied ratings; conformance is not established. Level 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?", "scenario": "poisoned-ancestor", diff --git a/examples/flagship_demo/specimens/inflated-tier.json b/examples/flagship_demo/specimens/inflated-tier.json index 481fe4d..abc963d 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:94e4f7d4cb771f76d3e856ad93f0e7c3d151d47d862abef00fbcda23d7975e1f", + "parser_hash": "sha256:7d05d3810219b1ef8400accd5735fbde494c4fb310c4d86f08381d4979dcde5c", + "specification_hash": "sha256:9648fee5c94a8c41a581ec003226dd87eca59bc6e2356ed0383fcabbf02a1d5f" } }, "certificate": { @@ -154,7 +154,7 @@ ] }, "header": { - "binding": "aee206fd7bc450c01bda6c54a4e26ba228ebf752b4085df56f1d250055a7c9cd", + "binding": "45fb1d24853ba1532e28ebaa31b73eedcff96552fc130cfbbfe9dc4262358808", "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..50f47eb 100644 --- a/examples/flagship_demo/specimens/poisoned-ancestor.json +++ b/examples/flagship_demo/specimens/poisoned-ancestor.json @@ -1,7 +1,7 @@ { - "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 level 0 from caller-supplied ratings; conformance is not established. Level 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?", "scenario": "poisoned-ancestor", @@ -18,7 +18,7 @@ "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:94e4f7d4cb771f76d3e856ad93f0e7c3d151d47d862abef00fbcda23d7975e1f", + "parser_hash": "sha256:7d05d3810219b1ef8400accd5735fbde494c4fb310c4d86f08381d4979dcde5c", + "specification_hash": "sha256:9648fee5c94a8c41a581ec003226dd87eca59bc6e2356ed0383fcabbf02a1d5f" } }, "collection": { @@ -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 level 0 from caller-supplied ratings; conformance is not established. Level 1 is refuted by: STATUS_ADMISSIBILITY: artifact:source is REVOKED.", "level": 0, "max_level": 5, - "refutation_digest": "87e9b1889e745c83c4a2dfc3968eadc9a4015146884a99f892e70b2d04d27eed", + "rating_basis": "CALLER_SUPPLIED", + "refutation_digest": "25e9bec3b78e32a249e5f4eadfd0515f4670c2c65f29b5e64268eaa02f7e9cb2", "witness_digest": null }, "hypergraph": { @@ -120,6 +122,7 @@ "storage_uris": [] } ], + "conflicts": [], "contributors": [], "rights": [], "transformations": [ @@ -589,7 +592,7 @@ ] }, "header": { - "binding": "23c087d7ef52a1995c2f54f51940f82faef60dc5262627fcee68dd9c9d78eb8e", + "binding": "465fbae913606519d06914c7d1929c3f710f75ca5471074425af1bd721e4ed3d", "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..be65ca4 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:94e4f7d4cb771f76d3e856ad93f0e7c3d151d47d862abef00fbcda23d7975e1f", + "parser_hash": "sha256:7d05d3810219b1ef8400accd5735fbde494c4fb310c4d86f08381d4979dcde5c", + "specification_hash": "sha256:9648fee5c94a8c41a581ec003226dd87eca59bc6e2356ed0383fcabbf02a1d5f" } }, "certificate": { @@ -162,7 +162,7 @@ ] }, "header": { - "binding": "aee206fd7bc450c01bda6c54a4e26ba228ebf752b4085df56f1d250055a7c9cd", + "binding": "45fb1d24853ba1532e28ebaa31b73eedcff96552fc130cfbbfe9dc4262358808", "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..f54846b --- /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 depth is a structural candidate 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[9Hw|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..89879f2 100644 --- a/examples/verification_geometry_residual/README.md +++ b/examples/verification_geometry_residual/README.md @@ -1,5 +1,7 @@ # Reconstruction residual and bounded closure +> **Acronym:** Verifier Standard (VSTD). + This example is the smallest VSTD-0.2 verification-geometry vertical slice. Its machine-readable form is [`geometry.json`](geometry.json). diff --git a/examples/zizk_artifact_first/README.md b/examples/zizk_artifact_first/README.md new file mode 100644 index 0000000..f9c5174 --- /dev/null +++ b/examples/zizk_artifact_first/README.md @@ -0,0 +1,33 @@ +# 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, treats actor and artifact as +contextual roles, carries bounded Artifact support forward, and carries diagnostic Rust +backward through memetic causal-provenance propagation without scalar cancellation or +inherited guilt. + +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 wire 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..53028f2 --- /dev/null +++ b/examples/zizk_artifact_first/risc0/CLAIM_BOUNDARY.md @@ -0,0 +1,50 @@ +# 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 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. It neither creates that architecture nor makes its +specific proof system mandatory. No frozen wire 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..0d03041 --- /dev/null +++ b/examples/zizk_artifact_first/risc0/README.md @@ -0,0 +1,203 @@ +# 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 VSTD layer, wire +identifier, conformance level, 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 does not make all VSTD receipts +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` | 301811 | `5fd33b0fbf6b54e34d4dd19c5ff068a8f82bacacc21881b5fa2cc5c0a90090df` | +| `public.json` | 2575 | `6324c3c5d77ea4df4034f61131059289d5228f190d69e34c59bd7416fa9ac823` | +| `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 executes this direct verifier command: + +```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 -- \ + verify recorded-proof/receipt.msgpack recorded-proof/public.json \ + e1e9bf4f68ef60ff9af6b50e144082bc475cc20cab47e8187201153da597dcd8 +``` + +The final argument is the exact RISC Zero guest image identifier recorded by the +public envelope and independently pinned by this repository. It is an explicit +program trust coordinate, not actor identity or reputation. Omitting it verifies +newly produced artifacts against the guest image built by the current checkout; +supplying it permits offline verification of this immutable historical receipt +without silently substituting the current build's image identifier. 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..e22f01a --- /dev/null +++ b/examples/zizk_artifact_first/risc0/ROUND1_ZERO_KNOWLEDGE_REPORT.md @@ -0,0 +1,219 @@ +# 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). + +**Date:** 2026-08-23 +**Status:** completed recorded run; non-secret proof artifacts tracked as a bounded +reference mechanism; no VSTD receipt mapping + +## Repository coordinates + +- Repository: `TimeLordRaps/verifier` +- Immutable base: `598c545be3833d6d81bb7e252ca5837f3bb2a449` +- Branch: `codex/zizk-zero-knowledge` +- Worktree: isolated worktree named `zizk-zk-codex`; its machine-specific absolute + path is intentionally excluded from this public report +- Primary worktree modified: no +- Existing frozen wire identifiers modified: no +- Push, pull request, merge, tag, release, or publication performed: no + +## 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 | `e1e9bf4f68ef60ff9af6b50e144082bc475cc20cab47e8187201153da597dcd8` | +| 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 +``` + +Real proof plus automated negative cases: + +```text +RISC0_DEV_MODE=0 vstd-zk-host self-test local-artifacts/recorded-final +PASS +elapsed wall time: 6.10 seconds +maximum resident set: 1,214,664 KiB +``` + +Offline verifier invocation without the witness: + +```text +RISC0_DEV_MODE=0 vstd-zk-host verify receipt.msgpack public.json +PASS +elapsed wall time: 0.10 seconds +maximum resident set: 5,632 KiB +``` + +Repository validation: + +```text +python -m pytest -q +258 passed, 3 skipped + +python scripts/check_presentation.py +[PRESENTATION OK] links, versions, boundaries, paths, and visual assets + +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 ephemeral private witness and salt remain excluded. + +| Artifact | Bytes | SHA-256 | +|---|---:|---| +| `receipt.msgpack` | 301811 | `5fd33b0fbf6b54e34d4dd19c5ff068a8f82bacacc21881b5fa2cc5c0a90090df` | +| `public.json` | 2575 | `6324c3c5d77ea4df4034f61131059289d5228f190d69e34c59bd7416fa9ac823` | +| `self-test-results.json` | 377 | `e4c1bff21fb6161221276157fa96af6661af8635da35970ba12e462881f2c6fe` | +| `corrupted-receipt.msgpack` | 301811 | `389117e63a429e55c3f3616b9cbf2339fb1c99b48712ef7a3e5f5f16b32b6d81` | +| `tampered-journal.msgpack` | 301811 | `4e443f5084b8665a7185e6e4e62fd72eed5130673b3d7bfde488cdc6c405555c` | +| `mutated-public.json` | 2575 | `c2a056b71b2019daa8ac9f3aefcb4c2cc28a1b56ef97f8c61947bf37c5f2b7b9` | +| `transplanted-public.json` | 2575 | `95cf0d97e20777e2ad49234b7b27074dc8931ee7de215632a7d22a6b5466f2b6` | + +## Unresolved assumptions + +- The selected cryptographic implementation and transitive dependencies were not + independently audited in this work. +- The image ID was produced once in this environment; a second independent build has not + yet corroborated it. +- 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 within the reference program a real composite STARK receipt for one bounded +hidden-witness predicate, with the recorded negative cases rejected. It does not establish +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..b59a1f1 --- /dev/null +++ b/examples/zizk_artifact_first/risc0/THREAT_MODEL.md @@ -0,0 +1,96 @@ +# 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 obtained from the reviewed guest ELF; +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. a 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. Reproducing an +image ID on another trusted build host is useful evidence, not supplied here as an +independent implementation. + +### 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..3e845cc --- /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": "e1e9bf4f68ef60ff9af6b50e144082bc475cc20cab47e8187201153da597dcd8", + "receipt_sha256": "5fd33b0fbf6b54e34d4dd19c5ff068a8f82bacacc21881b5fa2cc5c0a90090df", + "receipt_size": 301811, + "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": [ + 22, + 122, + 99, + 204, + 97, + 34, + 89, + 53, + 32, + 92, + 242, + 247, + 10, + 210, + 88, + 172, + 220, + 231, + 224, + 30, + 26, + 196, + 131, + 46, + 214, + 182, + 32, + 56, + 55, + 236, + 22, + 203 + ], + "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": [ + 70, + 25, + 227, + 207, + 4, + 53, + 122, + 247, + 196, + 116, + 55, + 94, + 192, + 15, + 67, + 94, + 13, + 20, + 211, + 145, + 35, + 230, + 197, + 120, + 234, + 38, + 157, + 44, + 156, + 99, + 136, + 154 + ], + "threshold": 70, + "evidence_commitment": [ + 197, + 74, + 14, + 209, + 241, + 162, + 135, + 66, + 59, + 124, + 229, + 70, + 93, + 53, + 243, + 147, + 120, + 47, + 41, + 34, + 80, + 125, + 63, + 61, + 137, + 137, + 80, + 249, + 27, + 95, + 80, + 95 + ], + "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..e058835 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..a75ab97 --- /dev/null +++ b/examples/zizk_artifact_first/risc0/scripts/verify_recorded_proof.sh @@ -0,0 +1,16 @@ +#!/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 -- \ + verify recorded-proof/receipt.msgpack recorded-proof/public.json \ + e1e9bf4f68ef60ff9af6b50e144082bc475cc20cab47e8187201153da597dcd8 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..a93dd3f --- /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 VSTD layer or +profile, not implemented by +the `verifier` package, and not referenced by any receipt. Nothing here carries a wire +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-level 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 frozen wire 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..d3873bc --- /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 frozen wire 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 frozen wire 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 frozen wire 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..e134b31 --- /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 wire 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 property-level 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 frozen wire 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..c49f44b --- /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 nothing on the VSTD ladder. 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..fcbc9f7 --- /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, + "wire_identifier": null, + "notes": [ + "This bounded reference model carries no wire identifier, no schema $id route, and no receipt digest.", + "It does not alter, extend, or reinterpret any frozen VSTD wire 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-level 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..6cf9fc6 --- /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["wire_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_wire_identifier() -> None: + for path in (REFERENCE_SURFACE / "fixtures").glob("*.json"): + text = path.read_text(encoding="utf-8") + for frozen in ("VSTD-0.1", "VSTD-0.2", "VSTD-3.0", "VSTD-DATA-0.1"): + assert frozen not in text, f"{path.name} must not bind a frozen wire identifier" diff --git a/experiments/INDEX.md b/experiments/INDEX.md new file mode 100644 index 0000000..3581eec --- /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 bounded event serialization, support-transfer algebra, Rust concentration and localization rules, and hidden-witness trichotomy mechanisms can implement the governing artifact-first causal-provenance orientation without actor reputation, scalar cancellation, causal-localization overclaim, or making that orientation contingent on the study? | CANDIDATE | 5 | [`experiments/artifact_first_mechanisms/experiment.json`](artifact_first_mechanisms/experiment.json)
`sha256:20a9060d8244ed1af59d0ea2e058dc412b08c5b4851c4abcbf7994059e2b093f` | +| 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..cfd740f --- /dev/null +++ b/experiments/artifact_first_mechanisms/README.md @@ -0,0 +1,22 @@ +# 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). + +Only the following unfinished mechanisms are experimental here: + +- event serialization; +- bounded support-transfer algebra; +- 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..d925ce7 --- /dev/null +++ b/experiments/artifact_first_mechanisms/experiment.json @@ -0,0 +1,414 @@ +{ + "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 bounded event serialization, support-transfer algebra, Rust concentration and localization rules, and hidden-witness trichotomy mechanisms can implement the governing artifact-first causal-provenance orientation without actor reputation, scalar cancellation, causal-localization overclaim, or making that 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-dual-causal-propagation", + "statement": "An operational substrate can carry scoped positive artifact support from parent to child and diagnostic Rust from child to parent without collapsing them into one scalar or allowing either direction to bypass claim-local evidence.", + "falsification_condition": "Positive support flows backward, Rust flows forward as inherited guilt, parent trust automatically proves a child, missing or conflicted support becomes clean, or actor identity changes either signal.", + "state": "OPEN" + } + ], + "preregistration": { + "state": "AMENDED", + "recorded_at": "2026-08-24T00:00:00Z", + "artifact_id": "artifact-round2-design", + "limitations": [ + "This experimental workflow manifest records only unfinished mechanisms and their evidence; it does not classify VSTD's governing ZIZK artifact-first architecture as experimental.", + "Round 2 is a design synthesis rather than a completed trustless protocol.", + "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:3275ec038c0c6ddc39bc26e5fc6d68853fff4ce6014aea20fa4fadbfaca4cbd2", + "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:5fd33b0fbf6b54e34d4dd19c5ff068a8f82bacacc21881b5fa2cc5c0a90090df", + "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:6324c3c5d77ea4df4034f61131059289d5228f190d69e34c59bd7416fa9ac823", + "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:09da65d7f87236344632df73821c177de27abbbb94f496e17ab8c409ce6a3398", + "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:557089fd0cb082aafb6a7a1eeecce7ac253828c4930197c5cbf995f6d5914aff", + "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 the normative dual-direction causal-provenance propagation: forward bounded artifact support and backward memetic Rust across contextual actor/artifact roles", + "state": "COMPLETED", + "priority": 3, + "selected_because": "The normative ladder 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" + ], + "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, forward-support, backward-Rust, and concentration mechanics while leaving the event schema, transfer algebra, and localization protocol 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 as the controlling semantic invariant and confine Round 2 to candidate operational mechanics for its parent-to-child artifact support and child-to-parent Rust directions.", + "reason": "The architecture is normative; only event serialization, support-transfer algebra, Rust concentration and localization, complete trichotomy derivation, and specific unfinished optional proof backends remain experimental. The experiment must not make the core causal-provenance orientation appear contingent on its outcome.", + "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 from its experimental event, transfer, Rust, trichotomy, and optional proof-backend mechanisms: 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" + } + ], + "challenges": [], + "horizons": [ + { + "id": "horizon-operational-protocol", + "status": "UNKNOWN", + "description": "End-to-end trustless reverification protocol", + "reason": "Round 2 records a design substrate; no complete operational protocol or independent implementation 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-rust-memetic-backtrace", + "status": "UNKNOWN", + "description": "Typed backward Rust transfer, concentration, and localization over bound ancestry", + "reason": "The child-to-parent transfer states and concentration boundary are design-only; no event ledger, propagation implementation, or localization experiment is included." + }, + { + "id": "horizon-forward-artifact-trust", + "status": "UNKNOWN", + "description": "Scoped forward artifact-trust transfer through developmental claim space", + "reason": "The design requires meet-like bounded support and child-local obligations, but no transfer algebra or implementation is included." + } + ], + "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:20a9060d8244ed1af59d0ea2e058dc412b08c5b4851c4abcbf7994059e2b093f" +} 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..ec326f6 --- /dev/null +++ b/experiments/artifact_first_mechanisms/reverification/ROUND2_DESIGN_NOTE.md @@ -0,0 +1,619 @@ +# 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. + +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. + +> The same bound causal-provenance graph carries two memetic propagation directions: +> bounded artifact trust moves forward through developmental claim space, while observed +> Rust genetically backtraces from descendant deviations toward recorded ancestor states. +> This causal-provenance transmission does not itself establish causal localization. + +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 | +| Artifact-first 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 | +| 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 | this Round 2 design | A proposed viral backtrace of measured deviation through bound creation ancestry, never trust or a verdict | Reframed as relation-bound memetic transmission toward recorded ancestor states | + +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 wire 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 artifact trust without actor-trust accumulation + +Tier 0 records both bounded positive artifact support 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. + +Artifact trust is a positive signal bound to an exact artifact, 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 --bounded positive support--> descendant claim or artifact +diagnosis: descendant Rust --memetic causal backtrace--> recorded ancestor states +``` + +`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 positive artifact support 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 working name for a Tier 1 view of past measured deviation. It is not +trust, inverse trust, a verdict, or a prediction. + +### 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. + +The same coding agent may therefore have one rust history as an evaluated artifact, other +histories for specific creation or transformation relations in which it acted, and no +valid global score. These histories 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, +reproduction by a distinct actor, or other declared mechanism 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 artifact 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, proof verification is the repeatable public interface. It proves +only the program execution and journal bound by the selected proof system. It does not +reveal or independently observe the witness and does not establish that the witness was +truthful. + +## 6. Proposed `STALE` entry and successor semantics + +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 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 artifact 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 frozen wire + 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 wire 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..ef524da 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", @@ -26,12 +34,20 @@ dependencies = [] Homepage = "https://github.com/TimeLordRaps/verifier" Documentation = "https://timelordraps.github.io/verifier/" 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"] +scitt = [ + "scitt-cose==0.2.2", + "cbor2==6.1.4", + "cryptography==50.0.0", +] test = ["pytest>=8.0", "pyyaml>=6.0", "jsonschema>=4.18"] release = ["build==1.5.0", "twine==7.0.0"] @@ -41,16 +57,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"] [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..9d3b18a --- /dev/null +++ b/receipts/schema/vstd1_generic_run_receipt.json @@ -0,0 +1,314 @@ +{ + "$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 generic_computational_run profile carried under the frozen VSTD-0.1 wire identifier.", + "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" + ], + "properties": { + "schema_version": { "const": "VSTD-0.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" } + } + }, + "layer4_binding": { + "type": "object", + "description": "Legacy VSTD-0.1 generic-run container introduced by the version 1.0.0 writer. Its contents are generic assessment context, not VSTD-4 conformance; pre-version-1.0 receipts omit it.", + "additionalProperties": false, + "required": ["verifier", "resource_bounds", "prior_commitment", "refutation_surface"], + "properties": { + "vstd4_conformance": { + "const": "NOT_EVALUATED", + "description": "Negative routing guard added by the version 1.2.0 writer and absent from earlier receipts. The historical container records generic assessment context; it is not a VSTD-4 grounded decision certificate." + }, + "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": "Deliberately open domain-refutation map retained for compatibility with issued VSTD-0.1 receipts; the three named fields have fixed meanings and additional fields remain declarations.", + "required": ["admissible_refutations", "excluded_claims", "legacy_falsification_condition"], + "properties": { + "admissible_refutations": { "type": "array", "items": { "type": "string" } }, + "excluded_claims": { "type": "array", "items": { "type": "string" } }, + "legacy_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..252257e 100644 --- a/receipts/schema/vstd1_receipt.json +++ b/receipts/schema/vstd1_receipt.json @@ -1,4 +1,5 @@ { + "$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", @@ -92,7 +93,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..61d8a2e 100644 --- a/receipts/schema/vstd2_receipt.json +++ b/receipts/schema/vstd2_receipt.json @@ -1,4 +1,5 @@ { + "$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", 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..5c12461 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", + "title": "VSTD-4 Structural Candidate Receipt", + "description": "Legacy-compatible VSTD-4 shape. vstd4_depth is a candidate over caller-supplied rung references; conformance is NOT_ESTABLISHED. Historical receipts without conformance_status have the same non-upgrading interpretation.", "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,8 @@ "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": "Structural candidate depth, not normative VSTD-4 conformance."}, + "conformance_status": {"const": "NOT_ESTABLISHED"}, "rung_evidence": { "type": "object", "additionalProperties": false, @@ -27,7 +30,7 @@ "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"}}, @@ -43,7 +46,7 @@ ], "$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 lower-layer conformance."}, "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..368d3e1 100644 --- a/receipts/schema/vstd5_receipt.json +++ b/receipts/schema/vstd5_receipt.json @@ -1,8 +1,9 @@ { + "$comment": "Terminology: Verifier Standard (VSTD). Draft interface only; matching this schema is not VSTD-5 readiness or proof of independent corroboration.", "$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.", + "description": "Shape for review only. entry_vstd4_depth is a declaration of the future normative precondition; the current VSTD-4 candidate cannot satisfy it, and no reference VSTD-5 acceptance path is implemented.", "type": "object", "additionalProperties": false, "required": ["schema_version", "status", "receipt_id", "claim_id", "claim_binding", "entry_vstd4_depth", "witnesses", "corroborations", "disagreements", "computed_independence"], @@ -12,7 +13,7 @@ "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}, + "entry_vstd4_depth": {"const": 14, "description": "Future normative VSTD-4 conformance depth. Structural candidate depth 14 is insufficient."}, "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"}}, diff --git a/receipts/schema/vstd_graph_receipt.json b/receipts/schema/vstd_graph_receipt.json index dad3e63..d668dae 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 wire identifier is frozen for historical Graph-1 receipts; computed_graph_level records a candidate level over caller-supplied ratings and does 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,6 +165,8 @@ "collection_id": {"type": "string", "minLength": 1}, "level": {"type": "integer", "minimum": 0, "maximum": 5}, "max_level": {"const": 5}, + "rating_basis": {"const": "CALLER_SUPPLIED"}, + "conformance_status": {"const": "NOT_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}$"}, 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_reference.py b/scripts/build_reference.py new file mode 100644 index 0000000..797458a --- /dev/null +++ b/scripts/build_reference.py @@ -0,0 +1,462 @@ +#!/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:load_manifest", "verifier.core.run:describe_run_plan"), + ), + ( + "vstd run", + "Executes a trusted manifest without sandboxing, captures the observed " + "execution, and writes a canonically digested receipt.", + ( + "verifier.core.run:load_manifest", + "verifier.core.run:capture_run", + "verifier.core.receipt:compute_canonical_digest", + ), + ), + ( + "vstd validate", + "Dispatches on the receipt's frozen wire 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: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: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: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: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 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(): + raise ReferenceBuildError(f"cannot locate source file for {module_name}") + 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 = ( + "" + f"\n{rows}
ArgumentKindMeaning
" + 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 = ( + "" + f"\n{rows}
MethodSummary
" + ) + # ``str, Enum`` inherits a version-specific builtin ``str`` docstring on + # Python 3.10, while later interpreters expose Enum's generic docstring. + # Neither describes the public VSTD surface, so emit one stable summary. + summary = "Enumeration of the exported result values." if kind == "enum" else _summary(value) + 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 ( + "" + f"\n{rows}
CommandWhat it doesImplementation entry points
" + ) + + +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 + + + + + + +
+ +
+ +
+
+
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. Public subpackage surfaces are not exhaustively + listed here; use the architecture map + to reach their owning modules, schemas, and tests.

+
{_api_section()}
+
+
+ +
+
+
Wire
+

Canonical schemas and identifiers.

+

Receipt schemas are served from this site at their canonical + $id routes, and the frozen wire identifiers they belong to are listed in the + standard.

+ +
+
+
+ +
VSTD · Apache-2.0 · Reference generated from the implementation by scripts/build_reference.py.
+ + +""" + + +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_presentation.py b/scripts/check_presentation.py index 8c70c8c..518559a 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 @@ -79,18 +85,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": "`NOT_ESTABLISHED`", + "VSTD-5": "Not implemented", + "VSTD-Graph-1": "Implemented reference subset", + "VSTD-Graph-2": "`NOT_ESTABLISHED`", + "VSTD-Graph-3": "`NOT_ESTABLISHED`", + "VSTD-Graph-4": "`NOT_ESTABLISHED`", + "VSTD-Graph-5": "`NOT_ESTABLISHED`", + "Generic run": "`vstd4_conformance = NOT_EVALUATED`", + "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": ( + "Governing architectural invariant; not a separate VSTD conformance result" + ), + "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( @@ -131,6 +202,29 @@ def check_local_links(errors: list[str]) -> None: ) +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 +236,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,28 +252,153 @@ 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", + "It cannot prove general AI", + "[Normative specifications](standard/LADDER.md)", + "[Report an ambiguity or counterexample]", + "[Report a vulnerability privately]", + "SCITT registration proves neither payload", + "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-4 CANDIDATE; CONFORMANCE NOT_ESTABLISHED" not in reference: + errors.append("generated reference does not bound its VSTD-4 implementation 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(): @@ -239,12 +459,12 @@ def check_visual_assets(errors: list[str]) -> None: "vstd-1": "REF. SUBSET", "vstd-2": "EXPERIMENTAL", "vstd-3": "IMPLEMENTED", - "vstd-4": "IMPLEMENTED", + "vstd-4": "CANDIDATE", "vstd-5": "DRAFT", "graph-1": "REF. SUBSET", - "graph-2": "IMPLEMENTED", - "graph-3": "IMPLEMENTED", - "graph-4": "IMPLEMENTED", + "graph-2": "CANDIDATE", + "graph-3": "CANDIDATE", + "graph-4": "CANDIDATE", "graph-5": "DRAFT", } observed_status = { @@ -272,14 +492,87 @@ 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_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 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_visual_assets(errors) + check_generated_reference(errors) + check_experiment_index(errors) + check_acronyms(errors) return errors @@ -289,7 +582,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, and acronym expansion" + ) return 0 diff --git a/scripts/check_release_boundary.py b/scripts/check_release_boundary.py index ef5908b..75cd954 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 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_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..b7d4e5a 100644 --- a/scripts/release_artifacts.py +++ b/scripts/release_artifacts.py @@ -1,4 +1,6 @@ -"""Build and verify public release artifacts from an exact public Git ref. +"""Terminology: 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`` diff --git a/src/verifier/__init__.py b/src/verifier/__init__.py index 83a7232..dc9ce3c 100644 --- a/src/verifier/__init__.py +++ b/src/verifier/__init__.py @@ -1,12 +1,17 @@ -"""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 from typing import TYPE_CHECKING, Any -__version__ = "1.1.3" +__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-4" +__standard_status__ = "CANDIDATE; CONFORMANCE NOT_ESTABLISHED" _LAZY_EXPORTS = { "VerificationVerdict": ("verifier.core.checker", "VerificationVerdict"), 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..8f7e2ac 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 VSTD layer 4. Competition proof formats (DRAT, LRAT, GRAT, FRAT) answer exactly one question: *is this large formula really unsatisfiable?* They are deliberately diff --git a/src/verifier/core/checker.py b/src/verifier/core/checker.py index ffc2c1d..37055bd 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 @@ -84,6 +90,59 @@ class VerificationVerdict(str, Enum): 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 +177,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 +186,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 +223,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 +336,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 +446,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: @@ -401,10 +471,10 @@ def verifier_descriptor(cls) -> VerifierDescriptor: format-level 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 +546,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..449615f 100644 --- a/src/verifier/core/depth.py +++ b/src/verifier/core/depth.py @@ -1,4 +1,7 @@ -"""``vstd4_depth`` -- how far up the layer-4 ladder a claim actually got. +"""Terminology: conjunctive normal form (CNF); 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 @@ -7,7 +10,7 @@ The ladder makes the cost curve explicit instead of letting an implementer declare the top rung and skip the climb. -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 } @@ -23,6 +26,11 @@ 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. +The current 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. + This module *produces* certificates. It is not part of the trusted computing base; :mod:`verifier.core.kernel` checks what it emits, and the propagation routine here is deliberately a separate implementation from the kernel's. @@ -50,7 +58,10 @@ ) 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): @@ -126,12 +137,14 @@ def _validate_ladder() -> None: @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 lower-layer preconditions are not validated here. """ depth: int @@ -141,11 +154,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), @@ -155,12 +174,12 @@ def to_dict(self) -> dict[str, object]: def require_vstd5_entry(result: DepthResult) -> DepthResult: - """Fail closed unless ``result`` carries the complete layer-4 witness. + """Reject the current unbound candidate result at the VSTD-5 boundary. - 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. + VSTD-5 is draft, but its entry boundary is not: a structural candidate over + caller-supplied references is not normative VSTD-4 conformance. A future + evidence-binding implementation needs a distinct result type and gate; it + must not make this candidate stronger by setting another declaration field. """ if result.depth != MAX_DEPTH or result.witness is None: raise VSTD5EntryError( @@ -173,7 +192,10 @@ def require_vstd5_entry(result: DepthResult) -> DepthResult: raise VSTD5EntryError( "VSTD-5 entry result carries a ceiling refutation or blocking rung" ) - return result + raise VSTD5EntryError( + "VSTD-5 requires established VSTD-4 conformance; this structural " + f"candidate has conformance_status {result.conformance_status}" + ) # -------------------------------------------------------------------------- @@ -294,12 +316,13 @@ 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 level 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 diff --git a/src/verifier/core/geometry.py b/src/verifier/core/geometry.py index eddaf85..523f0d7 100644 --- a/src/verifier/core/geometry.py +++ b/src/verifier/core/geometry.py @@ -1,4 +1,7 @@ -"""Typed verification geometry for the additive VSTD-0.2 vertical slice. +"""Terminology: abstract syntax tree (AST); intermediate representation (IR); +Verifier Standard (VSTD). + +Typed verification geometry for the additive VSTD-0.2 vertical slice. This module does not alter VSTD-0.1 or VSTD-DATA-0.1 receipts. It supplies a small common representation for describing *where* verification attaches, 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..76a0468 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 layer 4. 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 # -------------------------------------------------------------------------- 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..f8ce899 100644 --- a/src/verifier/core/receipt.py +++ b/src/verifier/core/receipt.py @@ -1,4 +1,9 @@ -"""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-0.1.""" from __future__ import annotations @@ -201,17 +206,20 @@ 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}` > **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 +236,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 +257,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 +281,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} diff --git a/src/verifier/core/refutation.py b/src/verifier/core/refutation.py index d26532b..d8edc8c 100644 --- a/src/verifier/core/refutation.py +++ b/src/verifier/core/refutation.py @@ -1,4 +1,9 @@ -"""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). + +Refutation certificates for VSTD layer 4 (refutability). Layer 4 requires that every verdict -- pass **and** fail -- carry an artifact an independent party can check without the declarant's cooperation. @@ -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..b5fcae2 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). + +Reproducibility taxonomy and verification comparison levels. Defines the formal gradient of reproducibility for computational and formal claims. """ @@ -27,7 +29,10 @@ class ReproducibilityLevel(str, Enum): 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 level 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 level earned by the supplied comparison evidence. + + ``None`` means that these inputs do not establish a taxonomy level. 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..82df481 100644 --- a/src/verifier/core/run.py +++ b/src/verifier/core/run.py @@ -1,4 +1,8 @@ -"""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. This module implements the smallest working version of the "wrap any consequential computation and get a receipt" primitive described in the VSTD program graph. @@ -8,7 +12,7 @@ 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"``) +legacy ``VstdReceipt`` documents (SAT/derivation-shaped, without this discriminator) and ``VstdDataReceipt`` (dataset-provenance-shaped) documents are untouched. Design commitments (do not weaken without updating tests + docs): @@ -16,7 +20,7 @@ 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 +29,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 level. """ from __future__ import annotations @@ -39,6 +45,7 @@ import hashlib import json import platform as _platform +import re import subprocess import time from dataclasses import dataclass @@ -60,36 +67,44 @@ _SNIPPET_LIMIT = 4000 _DEFAULT_TIMEOUT_SECONDS = 300 +_SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$") -def _digest_if_available(path: Path, label: str) -> str: - try: - return "sha256:" + hashlib.sha256(path.read_bytes()).hexdigest() - except OSError: - return f"UNAVAILABLE:{label}" +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 _run_layer4_binding( +def _legacy_generic_run_binding( manifest: Mapping[str, Any], *, falsification_condition: str, ) -> dict[str, Any]: - """Bind the verifier, bounds, precommitment, and refutation surface. + """Serialize generic assessment context into the historical wire container. - 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. + ``layer4_binding`` was introduced by the version 1.0.0 generic-run writer, + but none of its positive content establishes VSTD-4. Older receipts omit the + block and retain their canonical digests. Version 1.2.0 continues to emit the + legacy shape so declared context is not silently dropped under the frozen + profile; a clean replacement requires an explicit later profile boundary. """ 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") @@ -102,12 +117,20 @@ def _run_layer4_binding( surface.setdefault("admissible_refutations", []) surface.setdefault("excluded_claims", ["PHYSICAL_WORLD_COMPLETENESS"]) surface.setdefault("legacy_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["legacy_falsification_condition"], str): + raise RunError("refutation_surface.legacy_falsification_condition must be a string") here = Path(__file__).resolve() 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": _digest_if_available(here, "core/run.py"), "parser_hash": _digest_if_available(here, "core/run.py"), @@ -118,6 +141,7 @@ def _run_layer4_binding( } return { "verifier": verifier, + "vstd4_conformance": "NOT_EVALUATED", "resource_bounds": bounds, "prior_commitment": str(manifest.get("prior_commitment", "")), "refutation_surface": surface, @@ -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,6 +412,7 @@ class GenericRunReceipt: claims: RunClaims provenance_linkage: tuple[ProvenanceLinkage, ...] reproducibility: dict[str, Any] + # Historical VSTD-0.1 wire name; this is generic assessment context, not VSTD-4. layer4_binding: Optional[dict[str, Any]] = None canonical_digest: str = "" @@ -549,8 +570,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,11 +658,11 @@ 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")}`. --- @@ -732,7 +753,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 +917,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 +950,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 +980,7 @@ def capture_run( "supported_levels": supported_levels, "reproduction_command": "vstd reproduce ", }, - layer4_binding=_run_layer4_binding( + layer4_binding=_legacy_generic_run_binding( manifest, falsification_condition=str( claim_block.get("falsification_condition", "") @@ -1023,8 +1035,563 @@ def _rebuild_stable_payload_from_dict(data: Mapping[str, Any]) -> dict[str, Any] 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", + ) + _missing_fields(data, "receipt", required, errors) + _unexpected_fields(data, "receipt", required + ("layer4_binding",), 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") + if "layer4_binding" in data: + layer4 = _missing_fields( + data.get("layer4_binding"), + "layer4_binding", + ("verifier", "resource_bounds", "prior_commitment", "refutation_surface"), + errors, + ) + if layer4 is not None: + layer4_fields = ( + "verifier", + "vstd4_conformance", + "resource_bounds", + "prior_commitment", + "refutation_surface", + ) + _unexpected_fields(layer4, "layer4_binding", layer4_fields, errors) + if "vstd4_conformance" in layer4 and layer4.get( + "vstd4_conformance" + ) != "NOT_EVALUATED": + errors.append("layer4_binding.vstd4_conformance must be NOT_EVALUATED") + verifier = _missing_fields( + layer4.get("verifier"), + "layer4_binding.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, "layer4_binding.verifier", verifier_fields, errors + ) + for name in ( + "specification_hash", + "implementation_hash", + "parser_hash", + ): + value = verifier.get(name) + unavailable_legacy_specification = ( + name == "specification_hash" + and isinstance(value, str) + and value.startswith("UNAVAILABLE:") + ) + if ( + not unavailable_legacy_specification + and ( + not isinstance(value, str) + or not re.fullmatch(r"sha256:[0-9a-f]{64}", value) + ) + ): + errors.append( + f"layer4_binding.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"layer4_binding.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( + "layer4_binding.verifier.dependencies must be an array of strings" + ) + if type(verifier.get("deterministic")) is not bool: + errors.append("layer4_binding.verifier.deterministic must be a boolean") + bounds = _missing_fields( + layer4.get("resource_bounds"), + "layer4_binding.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, "layer4_binding.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"layer4_binding.resource_bounds.{name} must be a non-negative integer" + ) + if not isinstance(layer4.get("prior_commitment"), str): + errors.append("layer4_binding.prior_commitment must be a string") + surface = _missing_fields( + layer4.get("refutation_surface"), + "layer4_binding.refutation_surface", + ( + "admissible_refutations", + "excluded_claims", + "legacy_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"layer4_binding.refutation_surface.{name} must be an array of strings" + ) + if not isinstance(surface.get("legacy_falsification_condition"), str): + errors.append( + "layer4_binding.refutation_surface.legacy_falsification_condition must be a string" + ) + return errors + + def is_generic_run_receipt(data: Mapping[str, Any]) -> bool: - return data.get("receipt_kind") == RUN_RECEIPT_KIND + 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: @@ -1032,13 +1599,25 @@ def validate_run_receipt(receipt_path_or_dir: Path) -> int: 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")) + 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"[PASS] Run receipt {data.get('receipt_id')} is valid.") + 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 @@ -1049,7 +1628,19 @@ def inspect_run_receipt(receipt_path_or_dir: Path) -> int: 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")) + 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) @@ -1066,7 +1657,11 @@ def inspect_run_receipt(receipt_path_or_dir: Path) -> int: 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')}") + 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) @@ -1083,12 +1678,28 @@ def reproduce_run_receipt(receipt_path_or_dir: Path, rerun: bool = False) -> int 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" + 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 - data = json.loads(receipt_file.read_text(encoding="utf-8")) + 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 @@ -1096,8 +1707,6 @@ def reproduce_run_receipt(receipt_path_or_dir: Path, rerun: bool = False) -> int # 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(): @@ -1110,23 +1719,24 @@ def reproduce_run_receipt(receipt_path_or_dir: Path, rerun: bool = False) -> int 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 + 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 + level = ( + ReproducibilityLevel.CONTENT_IDENTICAL.value + if outputs_match and outcomes_match + else "NOT_DEMONSTRATED" ) - 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"[REPRODUCTION RESULT - RERUN] Level: {level} (declared-output scope)") 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 + 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]]] = [] @@ -1143,8 +1753,8 @@ def reproduce_run_receipt(receipt_path_or_dir: Path, rerun: bool = False) -> int 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 + 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)") @@ -1153,7 +1763,8 @@ def reproduce_run_receipt(receipt_path_or_dir: Path, rerun: bool = False) -> int 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)") + print(" Declared-output bytes: MATCH") + print(" Run-level reproduction: NOT_DEMONSTRATED (command was not re-executed; pass --rerun to assess it)") return 0 diff --git a/src/verifier/core/translation.py b/src/verifier/core/translation.py index df30967..e257b28 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." diff --git a/src/verifier/data/__init__.py b/src/verifier/data/__init__.py index 602b9ea..cc51803 100644 --- a/src/verifier/data/__init__.py +++ b/src/verifier/data/__init__.py @@ -1,4 +1,6 @@ -"""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 ( GraphCollection, @@ -11,6 +13,7 @@ ArtifactStatus, ArtifactType, CompletenessMetrics, + ConflictRecord, ContributorSpec, HyperedgePort, ProvenanceHypergraph, @@ -32,6 +35,7 @@ "ArtifactStatus", "ArtifactType", "CompletenessMetrics", + "ConflictRecord", "ContributorSpec", "HyperedgePort", "ProvenanceHypergraph", diff --git a/src/verifier/data/graph_level.py b/src/verifier/data/graph_level.py index bb523d3..c7f5f93 100644 --- a/src/verifier/data/graph_level.py +++ b/src/verifier/data/graph_level.py @@ -1,9 +1,12 @@ -"""``graph_level`` -- how far up the VSTD-Graph ladder a collection actually got. +"""Terminology: conjunctive normal form (CNF); grounded decision certificate (GDC); +Boolean satisfiability problem (SAT); unsatisfiable (UNSAT); Verifier Standard (VSTD). + +``graph_level`` -- the candidate level 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 level 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. 2. **Provenance closure** -- every ancestor reachable from any member is also @@ -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 layer was reached, 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 @@ -68,10 +71,11 @@ 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. @@ -151,7 +155,7 @@ 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: @@ -213,7 +217,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 +231,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) ) @@ -347,7 +356,7 @@ def certify_graph_cnf( if encoded != satisfiable: raise GraphEncodingError( f"{collection_id} at level {level}: the certified encoding says " - f"{encoded} but the independent solver said {satisfiable}", + f"{encoded} but the separately implemented solver said {satisfiable}", certificate=certificate, cnf_satisfiable=satisfiable, direct_result=holds_at(items, level), @@ -385,7 +394,7 @@ def certify_graph_cnf( @dataclass(frozen=True) class GraphLevelResult: - """A computed level, with the evidence for both halves of the answer. + """A candidate level 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 @@ -399,16 +408,22 @@ 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 level " + 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"{self.collection_id} computes to candidate graph level {self.level} " + "from caller-supplied ratings; conformance is not established. " f"Level {self.level + 1} is refuted by: {blocked or 'no obligation'}." ) @@ -417,6 +432,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": ( @@ -437,7 +454,7 @@ def graph_level( Descends from :data:`GRAPH_MAX_LEVEL`, so the first satisfiable level found is the answer. The conditions are monotone in the level 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( @@ -447,6 +464,13 @@ def graph_level( "collection has no level." ) + 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 level." + ) + items = obligations(graph, collection) for level in range(GRAPH_MAX_LEVEL, GRAPH_MIN_LEVEL - 1, -1): diff --git a/src/verifier/data/models.py b/src/verifier/data/models.py index 279b5a6..fbe0563 100644 --- a/src/verifier/data/models.py +++ b/src/verifier/data/models.py @@ -1,4 +1,6 @@ -"""VSTD-Graph provenance models and algorithms. +"""Terminology: Secure Hash Algorithm 256-bit (SHA-256); Verifier Standard (VSTD). + +VSTD-Graph provenance models and algorithms. Graph-1 receipts retain the frozen ``VSTD-DATA-0.1`` wire identifier. """ @@ -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,36 @@ 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 + 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 + 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.""" @@ -352,15 +388,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 +439,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 @@ -464,6 +526,7 @@ 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 @@ -483,6 +546,16 @@ def from_dict(cls, data: Mapping[str, Any]) -> "ProvenanceHypergraph": rights_evidence_level=RightsEvidenceLevel(r_data.get("rights_evidence_level", "RIGHTS_DECLARED")), ) ) + 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", []): g.add_artifact( ArtifactNode( 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..fff329d 100644 --- a/src/verifier/data/receipt.py +++ b/src/verifier/data/receipt.py @@ -1,4 +1,7 @@ -"""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). + +VSTD-Graph receipt model and canonical serialization. Graph-1 receipts retain the frozen ``VSTD-DATA-0.1`` wire identifier. """ @@ -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(), } @@ -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: @@ -290,6 +298,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 +428,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 @@ -473,17 +541,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,9 +570,9 @@ 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} 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..658daeb 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) level 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..695650d --- /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 wire 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 VSTD layer 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..423b681 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 ladder. A hash is not availability. ``proof_sha256 = abc123…`` that nobody can obtain is cryptographically bound and completely uncheckable, and a verdict resting on it diff --git a/src/verifier/layer4/challenge.py b/src/verifier/layer4/challenge.py index 593efa7..ed0544f 100644 --- a/src/verifier/layer4/challenge.py +++ b/src/verifier/layer4/challenge.py @@ -1,11 +1,14 @@ -"""Rung 4.12 -- the challenge protocol. +"""Terminology: Verifier Standard (VSTD). + +Rung 4.12 -- the challenge protocol. Layer 4 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 only; it is not an adapter that mutates or +binds that state into a VSTD-Graph artifact. The state machine:: diff --git a/src/verifier/layer4/closure.py b/src/verifier/layer4/closure.py index 05c8dc9..f882078 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,11 +14,13 @@ 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 layer-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. @@ -26,7 +30,7 @@ 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..e721010 100644 --- a/src/verifier/layer4/surface.py +++ b/src/verifier/layer4/surface.py @@ -1,4 +1,6 @@ -"""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 diff --git a/src/verifier/runtime/demo.py b/src/verifier/runtime/demo.py index 663b929..eef32ee 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, @@ -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", + expected="GRAPH-CANDIDATE-0; REVOKED blocker; checked refutation", observed=observed, ok=ok, details=result.explanation, 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..68365c1 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,15 @@ 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.core.checker import independence_is_evidenced from verifier.core.run import ( RunError, capture_run, @@ -33,6 +39,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 @@ -114,13 +124,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 +229,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 +269,7 @@ def build_parser() -> argparse.ArgumentParser: export_parser = data_commands.add_parser("export") export_parser.add_argument("receipt") + add_experiment_parsers(subparsers) add_vstd3_parsers(subparsers) return parser @@ -208,34 +278,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 +349,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: @@ -390,6 +480,8 @@ def main(argv: list[str] | None = None) -> int: if args.command == "data": return _handle_data_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/LADDER.md b/src/verifier/specifications/LADDER.md index be23b3d..8cf8b38 100644 --- a/src/verifier/specifications/LADDER.md +++ b/src/verifier/specifications/LADDER.md @@ -1,9 +1,26 @@ -# The VSTD Ladder — what the numbers mean +# The Verifier Standard (VSTD) Ladder — what the numbers mean + +> **Acronyms:** 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 +**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 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. @@ -15,19 +32,82 @@ foundation. Each layer names a distinct verification question and a distinct failure class. The ordering is a composition rule, not logical entailment between layers. +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 layers 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 layers, 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`. -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 ladder") +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 +[undefinability theorem](https://en.wikipedia.org/wiki/Tarski%27s_undefinability_theorem "Wikipedia orientation; the theorem does not derive this ladder") +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 implementation MUST NOT treat success on one question as evidence for a different question. +### 1.1 Artifact-first causal provenance orientation + +VSTD evaluates artifact-bound claims, evidence, predicates, mechanisms, and declared +trust roots. 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 does not promote an unrelated computational claim. **Actor** and +**artifact** are contextual roles rather than permanent entity classes: a coding agent +may be an artifact when it is created, versioned, or evaluated and an actor when it +creates or transforms another artifact. + +The same bound development graph carries two typed causal-provenance propagation +directions: + +```text +development: ancestor artifact --bounded positive support--> descendant claim or artifact +diagnosis: descendant Rust --memetic causal backtrace--> recorded ancestor states +``` + +**Memetic propagation** is the transmission of claim and evidence state through recorded +developmental provenance. The genetic or viral language names this inheritance mechanic: +positive Artifact support propagates forward into descendant claim space, while Rust +propagates backward toward ancestor states as a provenance backtrace. It does not claim +biological transmission or make identity and reputation sources of assurance. + +**Artifact trust** is positive support already established for an exact artifact-bound +obligation. 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. + +**Rust** is a typed diagnostic trace created by an observed descendant deviation from a +declared expectation. It moves child-to-parent only through recorded admissible creation, +input, or transformation paths. Distinct comparable backtraces may concentrate on a +shared ancestor and prioritize it for diagnostic examination. Transferred Rust establishes +ancestral reachability, not direct observation or causal responsibility; localization +requires additional intervention, ablation, reproduction by a distinct actor, or equivalent +declared evidence. + +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. + +Forward support and backward Rust MUST remain separate. They do not cancel, form one +scalar score, or flow in the opposite direction as inherited truth or guilt. `UNKNOWN` +and `CONFLICTED` support or lineage MUST remain visible and MUST NOT become a clean +signal. This section fixes the semantic orientation and prohibited inferences; an event +format, transfer algebra, concentration-independence rule, and localization protocol each +require their own specification and evidence. Until those exist, Artifact trust and Rust +are causal-provenance propagation constraints, not computable conformance results; no +current VSTD runtime emits or validates either transfer. + --- ## 2. The object ladder @@ -50,6 +130,10 @@ no second party in existence. **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. @@ -65,7 +149,10 @@ 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 | |---|---|---| @@ -79,23 +166,26 @@ A collection `C` holds at Graph layer `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`. +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 layer-N evidence. 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. -The level is **computed, never declared**: +The level is **computed from validated object and edge ratings, never declared**: ``` graph_level(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 reference implementation searches 5→1 and certifies its Boolean encoding. Its +current rating inputs are caller-supplied, so it reports a **candidate level** 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 `N+1` explains that candidate ceiling. Graph conformance additionally +requires evidence-bound ratings under the applicable object and edge profiles. --- @@ -108,17 +198,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 @@ -155,7 +250,11 @@ 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. +[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` @@ -176,9 +275,11 @@ 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 normative depth. Only established +VSTD-4 conformance at 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. See `VSTD-4.md` for the normative rung graph and `VSTD4-GDC-1` format. --- @@ -204,7 +305,34 @@ closed. It never means the lower layers 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. +- **Repository releases use [semantic versioning](https://semver.org/)** and are independent + of layer 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. + +--- + +## 8. Intellectual lineage and adjacent precedents + +The ladder is VSTD project architecture; no cited work proves that these five layers 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 layer 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..e93f7d6 --- /dev/null +++ b/src/verifier/specifications/VSTD-1.md @@ -0,0 +1,189 @@ +# 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). + +**Layer:** 1 of 5 on the object axis (see `LADDER.md`) +**Receipt wire format:** `schema_version = "VSTD-0.1"` — frozen; 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 reproducibility levels, 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. **Semantic Infallibility of Unchecked Layers**: 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 an **Independent Verification Layer** 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 at this layer 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-tier reproducibility taxonomy: + +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 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: + +``` +receipt.json +├── schema_version: "VSTD-0.1" +├── 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_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. VSTD-2 does not reinterpret existing receipts whose wire identifier is `VSTD-0.1`. +- **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..c1d095d --- /dev/null +++ b/src/verifier/specifications/VSTD-2.md @@ -0,0 +1,336 @@ +# 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). + +**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:** experimental project specification with an implemented vertical slice +**Maintainer:** TimeLordRaps +**Date:** 2026-08-20 + +--- + +## 1. Relationship to earlier standards + +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-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 layers +violate the adjacent-layer 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 wire 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 layers. 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 layer. 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. + +--- + +## 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 adjacent-layer 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..f137cce 100644 --- a/src/verifier/specifications/VSTD-3.md +++ b/src/verifier/specifications/VSTD-3.md @@ -1,4 +1,17 @@ -# VSTD-3 — Substrate Accountability +# 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). **Layer:** 3 of 5 on the object axis (see `LADDER.md`) **Receipt wire format:** `schema_version = "VSTD-3.0"` — frozen; see `WIRE_IDENTIFIERS.md` @@ -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. diff --git a/src/verifier/specifications/VSTD-4.md b/src/verifier/specifications/VSTD-4.md index dd4def4..466e07f 100644 --- a/src/verifier/specifications/VSTD-4.md +++ b/src/verifier/specifications/VSTD-4.md @@ -1,18 +1,24 @@ -# VSTD-4 — Refutability +# Verifier Standard (VSTD)-4 — Refutability + +> **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). **Layer:** 4 of 5 on the object axis (see `LADDER.md`) **Certificate format:** `VSTD4-GDC-1` -**Status:** implemented project specification +**Status:** project specification; candidate computation implemented; evidence binding and conformance not implemented **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. +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.** @@ -33,13 +39,18 @@ 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: +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. --- @@ -102,7 +113,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 @@ -168,7 +179,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 +230,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 @@ -300,7 +311,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,7 +341,7 @@ bounded checking. ## 6. Reference implementation boundary -The reference producer and data structures are in: +The reference certificate producer, candidate-depth computation, and data structures are in: * `src/verifier/core/certificate.py` * `src/verifier/core/grounding.py` @@ -341,6 +352,11 @@ The reference producer and data structures are in: 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 the required lower-layer +results. Kernel acceptance of a candidate certificate is therefore not VSTD-4 +conformance. + 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..53cf5bd --- /dev/null +++ b/src/verifier/specifications/VSTD-5.md @@ -0,0 +1,98 @@ +# 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). + +**Layer:** 5 of 5 on the object axis (see `LADDER.md`) +**Status:** DRAFT — not implemented +**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. + +This document is a draft interface, not an implementation or a claim that any +independent witness exists. + +--- + +## 1. Entry gate + +Every VSTD-5 procedure MUST reject a claim unless VSTD-1/2/3 preconditions and all +VSTD-4 rung propositions have been evidence-bound and checked, establishing normative +VSTD-4 conformance at depth 14: + +``` +vstd4_depth(claim) == 14 +``` + +The gate is not satisfied by a structural candidate over caller-supplied references. +The current reference candidate reports `conformance_status = NOT_ESTABLISHED`, and +`require_vstd5_entry` rejects it even at candidate depth 14. A witness cannot +corroborate a claim whose refutability does not compose. + +--- + +## 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. + +--- + +## 3. Independence + +At minimum, an independence assertion MUST name whether declarant and witness +share: + +1. ownership or operational control; +2. a verdict-producing codebase; +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. + +> Claim independence MUST NOT exceed the independence of its weakest binding +> witness. + +Independence is not manufacturable from self-report at any cryptographic +strength. + +--- + +## 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. + +--- + +## 5. Draft boundary + +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. + +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. diff --git a/src/verifier/specifications/VSTD-Graph-1.md b/src/verifier/specifications/VSTD-Graph-1.md new file mode 100644 index 0000000..2ed486b --- /dev/null +++ b/src/verifier/specifications/VSTD-Graph-1.md @@ -0,0 +1,171 @@ +# Verifier Standard (VSTD)-Graph-1 — Recorded Lineage + +> **Acronyms:** 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). + +**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` +**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 is the first rung 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` +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. + +### 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 computed Graph level. The current reference implementation +has no conflict-resolution transition; later resolution must be additive and must retain the +competing evidence. + +--- + +## 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..57769dc --- /dev/null +++ b/src/verifier/specifications/VSTD-Graph-2.md @@ -0,0 +1,20 @@ +# 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). + +**Layer:** 2 of 5 on the graph axis (see `LADDER.md`) +**Status:** implemented candidate computation; rating-evidence binding not implemented +**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 +higher, every reachable status is admissible, and every transformation hyperedge +carries layer-2 edge evidence. + +`verifier.data.graph_level` computes a candidate from caller-supplied ratings and marks +conformance `NOT_ESTABLISHED`. The `FAIL` certificate for Graph layer 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..9707895 --- /dev/null +++ b/src/verifier/specifications/VSTD-Graph-3.md @@ -0,0 +1,23 @@ +# 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). + +**Layer:** 3 of 5 on the graph axis (see `LADDER.md`) +**Status:** implemented candidate computation; rating-evidence binding not implemented +**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. + +The provenance closure condition is normative: rating only the selected members +is insufficient. The weakest reachable ancestor or transformation caps the +collection. + +The reference computation consumes caller-supplied ratings and therefore reports a +candidate with conformance `NOT_ESTABLISHED`. Its certificate does not establish that a +VSTD-3 mechanism produced any supplied rating. + +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..6e0bcae --- /dev/null +++ b/src/verifier/specifications/VSTD-Graph-4.md @@ -0,0 +1,22 @@ +# 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). + +**Layer:** 4 of 5 on the graph axis (see `LADDER.md`) +**Status:** implemented candidate computation; rating-evidence binding not implemented +**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 +higher, statuses are admissible, and every transformation hyperedge carries +layer-4 evidence 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 candidate +ceiling over caller-supplied ratings. It does not establish Graph-4 conformance or +validate the claimed `RefutabilityClosure` records. diff --git a/src/verifier/specifications/VSTD-Graph-5.md b/src/verifier/specifications/VSTD-Graph-5.md new file mode 100644 index 0000000..8bb95ba --- /dev/null +++ b/src/verifier/specifications/VSTD-Graph-5.md @@ -0,0 +1,21 @@ +# 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). + +**Layer:** 5 of 5 on the graph axis (see `LADDER.md`) +**Status:** DRAFT profile; computation is implemented, witness protocol is not +**License:** Apache-2.0 + +VSTD-Graph-5 is the collection profile for independently corroborated members, +ancestors, and transformations. The computed graph-level mechanism 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 +conformance. + +Conflicting witness records are retained as conflict records and make the relevant +subject inadmissible to a clean computed collection level. 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..a88b809 100644 --- a/src/verifier/specifications/WIRE_IDENTIFIERS.md +++ b/src/verifier/specifications/WIRE_IDENTIFIERS.md @@ -1,10 +1,12 @@ -# VSTD frozen wire identifiers and historical filenames +# Verifier Standard (VSTD) frozen wire identifiers and historical filenames + +> **Acronyms:** Boolean satisfiability problem (SAT); command-line interface (CLI). **Status:** normative for wire-identifier dispatch; filename history is informative **Date:** 2026-08-22 -VSTD has no demonstrated external adoption or independent implementation as of this -release. This document therefore does not prescribe an adopter migration. It records +VSTD has no demonstrated external adoption or independent implementation at this source +coordinate. 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. @@ -14,7 +16,9 @@ semantic versions independently. ## 1. Frozen receipt wire identifiers A filename or current layer label does not change the meaning of an issued receipt. -Readers MUST dispatch a receipt by its wire identifier: +Readers MUST first dispatch by wire identifier. Where a frozen identifier carries more +than one released profile, they MUST then dispatch by the profile discriminator and MUST +NOT validate one profile against another profile's shape: | Current layer document | Frozen wire identifier | |---|---| @@ -26,6 +30,37 @@ Readers MUST dispatch a receipt by its wire identifier: New layer-4 and layer-5 documents use their own schemas without changing historical canonical digests. +`VSTD-0.1` has two claim-mechanics profiles. A receipt with +`receipt_kind = "generic_computational_run"` uses +`vstd1_generic_run_receipt.json`. Historical SAT/derivation receipts predate the +discriminator and use `vstd1_receipt.json` only when their required `claim`, `evidence`, +`target_result`, and `independent_audit` fields are present. Missing or unknown profile +information fails closed; it is not permission to guess a shape. + +The bundled checker descriptor used `certificate_format = "VSTD3-INDEPENDENT-AUDIT"` +through release `1.1.3` even though it checked VSTD-1 claim mechanics. That historical +value remains attributable to those receipts but does not establish VSTD-3 conformance. +New `1.2.0` receipts use `VSTD1-CHECKER-REPORT`, bind `VSTD-1.md`, and record actor, +implementation, and runtime separation explicitly. Neither descriptor name proves that +separate actors performed producer and checker runs. + +The generic-run field name `layer4_binding` is also historical. Version 0.1.0 and 0.2.0 +writers omitted it; writers from version 1.0.0 through 1.1.3 emitted it under the same +`VSTD-0.1` generic-run discriminator. Readers MUST accept both forms. When present, the +exact block participates in the canonical digest and remains attributable to its writer. + +The block carries generic assessment context and VSTD-1 refutation metadata, not a +VSTD-4 grounded decision certificate. Version 1.2.0 continues to emit the legacy block so +manifest-declared verifier coordinates, resource-bound declarations, prior commitment, +and refutation surface are not silently discarded. It adds +`vstd4_conformance = "NOT_EVALUATED"`; neither the container name nor its presence +dispatches the receipt to VSTD-4 or establishes that a declared bound was enforced. + +This historical container is not a pattern for `layer1_binding`, `layer2_binding`, or +other layer-named context objects. A clean replacement requires an explicit new generic-run +profile discriminator and matching schema coordinate. A package semantic-version change +alone MUST NOT reinterpret the existing profile. No replacement identifier is reserved. + ### 1.1 Non-wire vocabulary `VSTD-2.md` section 7 defines a prose lifecycle vocabulary. Only the diff --git a/standard/LADDER.md b/standard/LADDER.md index be23b3d..8cf8b38 100644 --- a/standard/LADDER.md +++ b/standard/LADDER.md @@ -1,9 +1,26 @@ -# The VSTD Ladder — what the numbers mean +# The Verifier Standard (VSTD) Ladder — what the numbers mean + +> **Acronyms:** 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 +**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 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. @@ -15,19 +32,82 @@ foundation. Each layer names a distinct verification question and a distinct failure class. The ordering is a composition rule, not logical entailment between layers. +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 layers 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 layers, 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`. -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 ladder") +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 +[undefinability theorem](https://en.wikipedia.org/wiki/Tarski%27s_undefinability_theorem "Wikipedia orientation; the theorem does not derive this ladder") +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 implementation MUST NOT treat success on one question as evidence for a different question. +### 1.1 Artifact-first causal provenance orientation + +VSTD evaluates artifact-bound claims, evidence, predicates, mechanisms, and declared +trust roots. 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 does not promote an unrelated computational claim. **Actor** and +**artifact** are contextual roles rather than permanent entity classes: a coding agent +may be an artifact when it is created, versioned, or evaluated and an actor when it +creates or transforms another artifact. + +The same bound development graph carries two typed causal-provenance propagation +directions: + +```text +development: ancestor artifact --bounded positive support--> descendant claim or artifact +diagnosis: descendant Rust --memetic causal backtrace--> recorded ancestor states +``` + +**Memetic propagation** is the transmission of claim and evidence state through recorded +developmental provenance. The genetic or viral language names this inheritance mechanic: +positive Artifact support propagates forward into descendant claim space, while Rust +propagates backward toward ancestor states as a provenance backtrace. It does not claim +biological transmission or make identity and reputation sources of assurance. + +**Artifact trust** is positive support already established for an exact artifact-bound +obligation. 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. + +**Rust** is a typed diagnostic trace created by an observed descendant deviation from a +declared expectation. It moves child-to-parent only through recorded admissible creation, +input, or transformation paths. Distinct comparable backtraces may concentrate on a +shared ancestor and prioritize it for diagnostic examination. Transferred Rust establishes +ancestral reachability, not direct observation or causal responsibility; localization +requires additional intervention, ablation, reproduction by a distinct actor, or equivalent +declared evidence. + +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. + +Forward support and backward Rust MUST remain separate. They do not cancel, form one +scalar score, or flow in the opposite direction as inherited truth or guilt. `UNKNOWN` +and `CONFLICTED` support or lineage MUST remain visible and MUST NOT become a clean +signal. This section fixes the semantic orientation and prohibited inferences; an event +format, transfer algebra, concentration-independence rule, and localization protocol each +require their own specification and evidence. Until those exist, Artifact trust and Rust +are causal-provenance propagation constraints, not computable conformance results; no +current VSTD runtime emits or validates either transfer. + --- ## 2. The object ladder @@ -50,6 +130,10 @@ no second party in existence. **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. @@ -65,7 +149,10 @@ 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 | |---|---|---| @@ -79,23 +166,26 @@ A collection `C` holds at Graph layer `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`. +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 layer-N evidence. 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. -The level is **computed, never declared**: +The level is **computed from validated object and edge ratings, never declared**: ``` graph_level(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 reference implementation searches 5→1 and certifies its Boolean encoding. Its +current rating inputs are caller-supplied, so it reports a **candidate level** 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 `N+1` explains that candidate ceiling. Graph conformance additionally +requires evidence-bound ratings under the applicable object and edge profiles. --- @@ -108,17 +198,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 @@ -155,7 +250,11 @@ 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. +[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` @@ -176,9 +275,11 @@ 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 normative depth. Only established +VSTD-4 conformance at 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. See `VSTD-4.md` for the normative rung graph and `VSTD4-GDC-1` format. --- @@ -204,7 +305,34 @@ closed. It never means the lower layers 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. +- **Repository releases use [semantic versioning](https://semver.org/)** and are independent + of layer 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. + +--- + +## 8. Intellectual lineage and adjacent precedents + +The ladder is VSTD project architecture; no cited work proves that these five layers 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 layer 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..e93f7d6 100644 --- a/standard/VSTD-1.md +++ b/standard/VSTD-1.md @@ -1,4 +1,12 @@ -# VSTD-1 — Claim Mechanics +# 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). **Layer:** 1 of 5 on the object axis (see `LADDER.md`) **Receipt wire format:** `schema_version = "VSTD-0.1"` — frozen; see `WIRE_IDENTIFIERS.md` @@ -11,7 +19,7 @@ ## 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 @@ -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. | @@ -86,10 +94,16 @@ requirement for claims labeled independent: 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 at this layer 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,9 +111,19 @@ 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`. --- @@ -111,7 +135,7 @@ VSTD-1 defines a five-tier reproducibility taxonomy: 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. +5. `SEMANTIC_REPRODUCTION`: The underlying formal proposition is sustained under a separately implemented translation or alternate solver. This does not establish distinct actors. --- diff --git a/standard/VSTD-2.md b/standard/VSTD-2.md index 32b94ea..c1d095d 100644 --- a/standard/VSTD-2.md +++ b/standard/VSTD-2.md @@ -1,8 +1,13 @@ -# VSTD-2 — Verification Surface +# 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). **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 +**Status:** experimental project specification with an implemented vertical slice **Maintainer:** TimeLordRaps **Date:** 2026-08-20 @@ -16,8 +21,8 @@ reinterpret historical receipts whose wire identifiers are `VSTD-0.1` or extension only when it declares `schema_version = "VSTD-0.2"`; older validators may continue to process their existing receipt kinds unchanged. -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 diff --git a/standard/VSTD-3.md b/standard/VSTD-3.md index cd15da2..f137cce 100644 --- a/standard/VSTD-3.md +++ b/standard/VSTD-3.md @@ -1,4 +1,17 @@ -# VSTD-3 — Substrate Accountability +# 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). **Layer:** 3 of 5 on the object axis (see `LADDER.md`) **Receipt wire format:** `schema_version = "VSTD-3.0"` — frozen; see `WIRE_IDENTIFIERS.md` @@ -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. diff --git a/standard/VSTD-4.md b/standard/VSTD-4.md index dd4def4..466e07f 100644 --- a/standard/VSTD-4.md +++ b/standard/VSTD-4.md @@ -1,18 +1,24 @@ -# VSTD-4 — Refutability +# Verifier Standard (VSTD)-4 — Refutability + +> **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). **Layer:** 4 of 5 on the object axis (see `LADDER.md`) **Certificate format:** `VSTD4-GDC-1` -**Status:** implemented project specification +**Status:** project specification; candidate computation implemented; evidence binding and conformance not implemented **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. +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.** @@ -33,13 +39,18 @@ 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: +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. --- @@ -102,7 +113,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 @@ -168,7 +179,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 +230,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 @@ -300,7 +311,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,7 +341,7 @@ bounded checking. ## 6. Reference implementation boundary -The reference producer and data structures are in: +The reference certificate producer, candidate-depth computation, and data structures are in: * `src/verifier/core/certificate.py` * `src/verifier/core/grounding.py` @@ -341,6 +352,11 @@ The reference producer and data structures are in: 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 the required lower-layer +results. Kernel acceptance of a candidate certificate is therefore not VSTD-4 +conformance. + 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..53cf5bd 100644 --- a/standard/VSTD-5.md +++ b/standard/VSTD-5.md @@ -1,4 +1,6 @@ -# VSTD-5 — Witness Corroboration +# 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). **Layer:** 5 of 5 on the object axis (see `LADDER.md`) **Status:** DRAFT — not implemented @@ -17,15 +19,18 @@ independent witness exists. ## 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/2/3 preconditions and all +VSTD-4 rung propositions have been evidence-bound and checked, establishing normative +VSTD-4 conformance at depth 14: ``` vstd4_depth(claim) == 14 ``` -The gate is structural. A witness cannot corroborate a claim whose refutability -does not compose. +The gate is not satisfied by a structural candidate over caller-supplied references. +The current reference candidate reports `conformance_status = NOT_ESTABLISHED`, and +`require_vstd5_entry` rejects it even at candidate depth 14. A witness cannot +corroborate a claim whose refutability does not compose. --- diff --git a/standard/VSTD-Graph-1.md b/standard/VSTD-Graph-1.md index a06b9cb..2ed486b 100644 --- a/standard/VSTD-Graph-1.md +++ b/standard/VSTD-Graph-1.md @@ -1,4 +1,10 @@ -# VSTD-Graph-1 — Recorded Lineage +# Verifier Standard (VSTD)-Graph-1 — Recorded Lineage + +> **Acronyms:** 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). **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` @@ -30,8 +36,8 @@ 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: @@ -68,6 +74,16 @@ 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 computed Graph level. The current reference implementation +has no conflict-resolution transition; later resolution must be additive and must retain the +competing evidence. + --- ## 3. Provenance Completeness Dimensions @@ -102,6 +118,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 +162,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..57769dc 100644 --- a/standard/VSTD-Graph-2.md +++ b/standard/VSTD-Graph-2.md @@ -1,7 +1,9 @@ -# VSTD-Graph-2 — Bounded Collection Surface +# 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). **Layer:** 2 of 5 on the graph axis (see `LADDER.md`) -**Status:** implemented computed profile +**Status:** implemented candidate computation; rating-evidence binding not implemented **License:** Apache-2.0 VSTD-Graph-2 closes collection-level scope leakage. A collection reaches this @@ -9,9 +11,10 @@ layer only when every member and provenance ancestor is at object layer 2 or higher, every reachable status is admissible, and every transformation hyperedge carries layer-2 edge evidence. -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` computes a candidate from caller-supplied ratings and marks +conformance `NOT_ESTABLISHED`. The `FAIL` certificate for Graph layer 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..9707895 100644 --- a/standard/VSTD-Graph-3.md +++ b/standard/VSTD-Graph-3.md @@ -1,7 +1,9 @@ -# VSTD-Graph-3 — Accountable Provenance Closure +# 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). **Layer:** 3 of 5 on the graph axis (see `LADDER.md`) -**Status:** implemented computed profile +**Status:** implemented candidate computation; rating-evidence binding not implemented **License:** Apache-2.0 VSTD-Graph-3 closes unaccountable substrate across a collection. A collection @@ -13,5 +15,9 @@ The provenance closure condition is normative: rating only the selected members is insufficient. The weakest reachable ancestor or transformation caps the collection. +The reference computation consumes caller-supplied ratings and therefore reports a +candidate with conformance `NOT_ESTABLISHED`. Its certificate does not establish that a +VSTD-3 mechanism produced any supplied rating. + 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..6e0bcae 100644 --- a/standard/VSTD-Graph-4.md +++ b/standard/VSTD-Graph-4.md @@ -1,7 +1,11 @@ -# VSTD-Graph-4 — Refutable Transformation Closure +# 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). **Layer:** 4 of 5 on the graph axis (see `LADDER.md`) -**Status:** implemented computed profile +**Status:** implemented candidate computation; rating-evidence binding not implemented **License:** Apache-2.0 VSTD-Graph-4 closes non-compositional refutability. A collection reaches this @@ -13,5 +17,6 @@ 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 UNSAT certificate at the next level 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. diff --git a/standard/VSTD-Graph-5.md b/standard/VSTD-Graph-5.md index bdcf96b..8bb95ba 100644 --- a/standard/VSTD-Graph-5.md +++ b/standard/VSTD-Graph-5.md @@ -1,4 +1,6 @@ -# VSTD-Graph-5 — Corroborated Verification Network +# 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). **Layer:** 5 of 5 on the graph axis (see `LADDER.md`) **Status:** DRAFT profile; computation is implemented, witness protocol is not @@ -14,5 +16,6 @@ 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 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 computed collection level. They are never averaged into +a passing collection. diff --git a/standard/WIRE_IDENTIFIERS.md b/standard/WIRE_IDENTIFIERS.md index 736365f..a88b809 100644 --- a/standard/WIRE_IDENTIFIERS.md +++ b/standard/WIRE_IDENTIFIERS.md @@ -1,10 +1,12 @@ -# VSTD frozen wire identifiers and historical filenames +# Verifier Standard (VSTD) frozen wire identifiers and historical filenames + +> **Acronyms:** Boolean satisfiability problem (SAT); command-line interface (CLI). **Status:** normative for wire-identifier dispatch; filename history is informative **Date:** 2026-08-22 -VSTD has no demonstrated external adoption or independent implementation as of this -release. This document therefore does not prescribe an adopter migration. It records +VSTD has no demonstrated external adoption or independent implementation at this source +coordinate. 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. @@ -14,7 +16,9 @@ semantic versions independently. ## 1. Frozen receipt wire identifiers A filename or current layer label does not change the meaning of an issued receipt. -Readers MUST dispatch a receipt by its wire identifier: +Readers MUST first dispatch by wire identifier. Where a frozen identifier carries more +than one released profile, they MUST then dispatch by the profile discriminator and MUST +NOT validate one profile against another profile's shape: | Current layer document | Frozen wire identifier | |---|---| @@ -26,6 +30,37 @@ Readers MUST dispatch a receipt by its wire identifier: New layer-4 and layer-5 documents use their own schemas without changing historical canonical digests. +`VSTD-0.1` has two claim-mechanics profiles. A receipt with +`receipt_kind = "generic_computational_run"` uses +`vstd1_generic_run_receipt.json`. Historical SAT/derivation receipts predate the +discriminator and use `vstd1_receipt.json` only when their required `claim`, `evidence`, +`target_result`, and `independent_audit` fields are present. Missing or unknown profile +information fails closed; it is not permission to guess a shape. + +The bundled checker descriptor used `certificate_format = "VSTD3-INDEPENDENT-AUDIT"` +through release `1.1.3` even though it checked VSTD-1 claim mechanics. That historical +value remains attributable to those receipts but does not establish VSTD-3 conformance. +New `1.2.0` receipts use `VSTD1-CHECKER-REPORT`, bind `VSTD-1.md`, and record actor, +implementation, and runtime separation explicitly. Neither descriptor name proves that +separate actors performed producer and checker runs. + +The generic-run field name `layer4_binding` is also historical. Version 0.1.0 and 0.2.0 +writers omitted it; writers from version 1.0.0 through 1.1.3 emitted it under the same +`VSTD-0.1` generic-run discriminator. Readers MUST accept both forms. When present, the +exact block participates in the canonical digest and remains attributable to its writer. + +The block carries generic assessment context and VSTD-1 refutation metadata, not a +VSTD-4 grounded decision certificate. Version 1.2.0 continues to emit the legacy block so +manifest-declared verifier coordinates, resource-bound declarations, prior commitment, +and refutation surface are not silently discarded. It adds +`vstd4_conformance = "NOT_EVALUATED"`; neither the container name nor its presence +dispatches the receipt to VSTD-4 or establishes that a declared bound was enforced. + +This historical container is not a pattern for `layer1_binding`, `layer2_binding`, or +other layer-named context objects. A clean replacement requires an explicit new generic-run +profile discriminator and matching schema coordinate. A package semantic-version change +alone MUST NOT reinterpret the existing profile. No replacement identifier is reserved. + ### 1.1 Non-wire vocabulary `VSTD-2.md` section 7 defines a prose lifecycle vocabulary. Only the 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_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..d72ba5f --- /dev/null +++ b/tests/test_experimental_workflow_profile.py @@ -0,0 +1,325 @@ +"""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-dual-causal-propagation"]["state"] == "OPEN" + + adaptation = payload["adaptations"][0] + assert "standard/LADDER.md section 1.1" in adaptation["decision"] + assert "parent-to-child artifact support" in adaptation["decision"] + assert "child-to-parent Rust" in adaptation["decision"] + + horizons = {item["id"]: item["status"] for item in payload["horizons"]} + assert horizons["horizon-contextual-role-protocol"] == "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_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..fc69fc5 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 @@ -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..7c8e11e 100644 --- a/tests/test_generic_run.py +++ b/tests/test_generic_run.py @@ -1,22 +1,28 @@ -"""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 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 +30,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 +57,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 +89,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 +144,49 @@ 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"] == { + legacy_context = data["layer4_binding"] + assert legacy_context["vstd4_conformance"] == "NOT_EVALUATED" + assert legacy_context["verifier"]["implementation_hash"].startswith("sha256:") + assert legacy_context["verifier"]["parser_hash"].startswith("sha256:") + assert legacy_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 legacy_context["prior_commitment"] == "" + assert legacy_context["refutation_surface"]["admissible_refutations"] == [] + assert "PHYSICAL_WORLD_COMPLETENESS" in legacy_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,51 @@ 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"] == [ + legacy_context = receipt.get_stable_payload()["layer4_binding"] + assert legacy_context["prior_commitment"] == manifest["prior_commitment"] + assert legacy_context["resource_bounds"] == manifest["resource_bounds"] + assert legacy_context["refutation_surface"]["admissible_refutations"] == [ "evidence_hash_mismatch" ] - layer4["prior_commitment"] = "sha256:" + "b" * 64 - receipt.layer4_binding = layer4 + legacy_context["prior_commitment"] = "sha256:" + "b" * 64 + receipt.layer4_binding = legacy_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 +@pytest.mark.parametrize("historical_shape", ("pre_v1", "v1_without_marker")) +def test_historical_generic_run_binding_shapes_remain_readable( + tmp_path, capsys, historical_shape +): + proj = _write_tiny_project(tmp_path) + data = capture_run(_base_manifest(), manifest_dir=proj).to_dict() + if historical_shape == "pre_v1": + data.pop("layer4_binding") + else: + data["layer4_binding"].pop("vstd4_conformance") + data["canonical_digest"] = compute_canonical_digest( + _rebuild_stable_payload_from_dict(data) + ) + path = tmp_path / f"{historical_shape}.json" + path.write_text(json.dumps(data), encoding="utf-8") + + assert is_generic_run_receipt(data) + assert validate_run_receipt(path) == 0 + assert "[INTEGRITY OK]" in capsys.readouterr().out + + +def test_legacy_container_cannot_claim_vstd4_conformance(tmp_path, capsys): + proj = _write_tiny_project(tmp_path) + data = capture_run(_base_manifest(), manifest_dir=proj).to_dict() + data["layer4_binding"]["vstd4_conformance"] = "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 "vstd4_conformance must be NOT_EVALUATED" in capsys.readouterr().out def test_missing_input_fails_closed_without_executing(tmp_path): @@ -248,7 +357,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"), + (("layer4_binding",), "unknown_binding_field"), + (("layer4_binding", "verifier"), "unknown_verifier_field"), + (("layer4_binding", "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_compatible_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.layer4_binding["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 +447,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 +465,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 +481,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 +497,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 "Level: 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) -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") + assert reproduce_run_receipt(bundle, rerun=True) == 0 + output = capsys.readouterr().out + assert "Level: 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 "Level: NOT_DEMONSTRATED" in output + assert "RESULT_EQUIVALENT" not in output + assert "SEMANTIC_REPRODUCTION" not in output + + +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") + + assert reproduce_run_receipt(proj, rerun=True) == 1 + assert "Level: NOT_DEMONSTRATED" in capsys.readouterr().out - 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" + +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 +581,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 +623,25 @@ 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"] diff --git a/tests/test_graph_level.py b/tests/test_graph_level.py index 30e96b9..b9e397a 100644 --- a/tests/test_graph_level.py +++ b/tests/test_graph_level.py @@ -1,4 +1,6 @@ -"""The VSTD-Graph axis: a computed level, and the proof of its ceiling. +"""Terminology: Verifier Standard (VSTD). + +The VSTD-Graph axis: a candidate level over supplied ratings, and the proof of its ceiling. The level is never declared. Each test below pins one of the four conditions -- membership floor, provenance closure, status admissibility, edge evidence -- @@ -10,6 +12,7 @@ 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") @@ -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,6 +249,43 @@ 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(): @@ -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(), 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..5770d1c 100644 --- a/tests/test_layer4.py +++ b/tests/test_layer4.py @@ -18,7 +18,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 +426,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 +588,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..c38086a 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,25 @@ 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 --bounded positive support--> descendant" in ladder + assert "descendant Rust --memetic causal backtrace--> recorded ancestor states" in ladder + assert "Memetic propagation" in ladder + assert "RFC 2119" in ladder + assert "RFC 8174" in ladder + assert "not computable conformance results" in ladder + assert "current VSTD runtime emits or validates either transfer" in ladder + assert "MUST NOT strengthen an artifact-bound result" in ladder + assert "They do not cancel, form one\nscalar score" in ladder diff --git a/tests/test_presentation_surface.py b/tests/test_presentation_surface.py index eb670b9..d80835c 100644 --- a/tests/test_presentation_surface.py +++ b/tests/test_presentation_surface.py @@ -1,4 +1,8 @@ -"""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); +Supply Chain Integrity, Transparency, and Trust (SCITT); Verifier Standard (VSTD). + +The public first impression is a checked repository surface.""" from __future__ import annotations @@ -6,6 +10,8 @@ import json from pathlib import Path +import yaml + ROOT = Path(__file__).resolve().parents[1] @@ -19,6 +25,39 @@ 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_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 +75,36 @@ 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_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) @@ -71,6 +140,8 @@ def test_pages_artifact_serves_every_canonical_schema_id(tmp_path: Path) -> None output = tmp_path / "site" copied = module.build(output) assert (output / "index.html").is_file() + assert (output / "guides.html").is_file() + assert (output / "reference.html").is_file() sources = sorted((ROOT / "receipts/schema").glob("*.json")) assert [path.name for path in copied] == [path.name for path in sources] for source, deployed in zip(sources, copied): @@ -80,6 +151,61 @@ def test_pages_artifact_serves_every_canonical_schema_id(tmp_path: Path) -> None ) +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_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_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 +224,49 @@ 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-4" + assert verifier.__standard_status__ == "CANDIDATE; CONFORMANCE NOT_ESTABLISHED" + assert "VSTD-4 CANDIDATE; CONFORMANCE NOT_ESTABLISHED" in page + assert "Enumeration of the exported result values." 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_cli.py b/tests/test_public_cli.py index b0fa70d..f8f6138 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,10 @@ 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(["experiment", "validate", "experiment.json"]).experiment_command + == "validate" + ) def test_public_cli_flagship_demo_is_side_effect_free_and_machine_readable( @@ -115,6 +121,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..15e918a 100644 --- a/tests/test_public_data.py +++ b/tests/test_public_data.py @@ -1,11 +1,17 @@ -"""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 pathlib import Path +import pytest + 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, @@ -24,6 +30,7 @@ reproduce_data_receipt, validate_data_receipt, ) +from verifier.runtime.public_cli import _inspect_data_receipt, main def _receipt() -> VstdDataReceipt: @@ -105,12 +112,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 +219,29 @@ 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_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..2c0a508 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 layer 4: 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* diff --git a/tests/test_release_artifacts.py b/tests/test_release_artifacts.py index 98f0cf7..7e6519e 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"]) @@ -302,3 +313,119 @@ 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 + + +@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")', + 'python scripts/check_release_metadata.py --version "${GITHUB_REF_NAME#v}"', + ) + for fragment in required: + assert fragment in workflow 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..d67108f 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-0.2 verification geometry slice.""" import json from dataclasses import replace 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..14b7a3a 100644 --- a/tests/test_vstd4_depth.py +++ b/tests/test_vstd4_depth.py @@ -1,9 +1,10 @@ -"""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 ladder 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 lower-layer preconditions 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, @@ -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 @@ -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): +def test_the_vstd5_gate_refuses_every_unbound_candidate(): + for level in range(0, MAX_DEPTH + 1): result = _depth(_evidence(only=level)) assert result.admits_vstd5 is False - with pytest.raises(VSTD5EntryError, match="requires computed vstd4_depth"): + expected = ( + "requires computed vstd4_depth" + if level < 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..b301c26 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 JSON Schema coverage for the integer-layer 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,9 +195,10 @@ 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_draft_schema_records_shape_without_establishing_entry() -> None: schema = _load("vstd5_receipt.json") validator = Draft202012Validator(schema, format_checker=FormatChecker()) + assert "current VSTD-4 candidate cannot satisfy" in schema["description"] receipt = { "schema_version": "VSTD-5-DRAFT", "status": "DRAFT", diff --git a/tests/test_zizk_artifact_first.py b/tests/test_zizk_artifact_first.py new file mode 100644 index 0000000..de8bdd8 --- /dev/null +++ b/tests/test_zizk_artifact_first.py @@ -0,0 +1,122 @@ +"""Terminology: 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", + "support-transfer algebra", + "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") + + assert "Zero identity is not anonymity or absence of identifiers" in readme + assert "identity or\nreputation alone cannot strengthen" in readme + assert "formal\nprivacy property for the exact predicate" in readme + 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": "5fd33b0fbf6b54e34d4dd19c5ff068a8f82bacacc21881b5fa2cc5c0a90090df", + "public.json": "6324c3c5d77ea4df4034f61131059289d5228f190d69e34c59bd7416fa9ac823", + "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_pins_the_historical_program_trust_coordinate() -> 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 "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