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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ All notable changes follow [Keep a Changelog](https://keepachangelog.com/en/1.1.

## [Unreleased]

### Added

- The unreleased package identity is now `0.2.0` for the claim/evidence export contract.
- `demo` and `run` can export a complete claim/evidence recurrence artifact with package
attribution, packet hashes, the full trace, and a named stopping decision.
- `verify-evidence` validates the unsigned outer digest, every evidence-packet hash, and derived
claim/decision summaries offline; conflicting artifact writes are refused.
- Bounded untrusted evidence to 1 MiB, 32 JSON levels, 50,000 nodes, and 1,024 packets;
malformed UTF-8, duplicate keys, conflicting summaries, and recomputed trace tampering fail
closed.
- Remote OpenAI-compatible credentials now require HTTPS, with a loopback-only HTTP exception;
credential-bearing URLs and unredacted transport errors are rejected.

## [0.1.0] - 2026-08-06

### Added
Expand Down
3 changes: 1 addition & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ cff-version: 1.2.0
message: "If you use VerifAxis, please cite the software."
title: "VerifAxis"
type: software
version: 0.1.0
date-released: 2026-08-06
version: 0.2.0
authors:
- name: Ali
repository-code: "https://github.com/aliengineering-byte/verifaxis"
Expand Down
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,26 @@ $ verifaxis demo

This is `smoke/demo` output, not a benchmark result.

Persist the complete claim, evidence chain, and named stopping decision, then validate it offline:

```console
$ verifaxis demo --evidence-output demo-evidence.json
$ verifaxis verify-evidence demo-evidence.json
{
"decision": "VERIFIED",
"evidence_packets": 2,
"status": "EVIDENCE ARTIFACT VERIFIED"
}
```

The artifact contains the final explicit claim, both the failing and passing verifier packets,
candidate and packet hashes, the full recurrence trace, `VERIFIED` stopping reason, package
attribution, and limitations. Its outer hash detects accidental or unrecomputed changes; packet
hashes and derived summaries are checked separately. This is self-consistency, not authentication:
an editor can recompute the unsigned hash, and hash validity does not make a verifier correct or
complete. The CLI accepts an identical existing artifact but refuses to overwrite different
content.

## Architecture

VerifAxis implements **Verifier-Conditioned External Recurrence (VCER)**:
Expand All @@ -66,7 +86,7 @@ flowchart LR

The loop persists candidates, concise structured state, evidence hashes, residuals, budgets, and termination decisions. It neither requests nor stores private chain-of-thought. LLM-generated criticism is always marked as LLM-produced and never silently treated as independent evidence.

The black-box adapter supports OpenAI-compatible HTTP endpoints, including compatible local servers. Open-weight latent recurrence is an interface-level future direction only; v0.1 makes no claim that it works.
The black-box adapter supports OpenAI-compatible HTTP endpoints, including compatible local servers. It rejects API keys and credential-like headers over plain HTTP unless the endpoint is explicitly loopback (`localhost` or a loopback IP), rejects credentials embedded in URLs, bounds and strictly parses responses, and does not include endpoint error details that may contain secrets. Open-weight latent recurrence is an interface-level future direction only; VerifAxis makes no claim that it works.

## What VerifAxis does not guarantee

Expand All @@ -82,11 +102,21 @@ See the [threat model](docs/threat-model.md) and [novelty decision](docs/novelty

```bash
verifaxis demo
verifaxis run examples/arithmetic.yaml
verifaxis run examples/arithmetic.yaml --evidence-output claim-evidence.json
verifaxis verify-evidence claim-evidence.json
verifaxis bench --config configs/smoke.yaml
verifaxis report runs/latest --format html
```

Evidence output is complete by design: it contains the task, candidates, verifier packets,
counterexamples, and timestamps. Choose a sanitized input or protect the destination as sensitive
data. Output is no-clobber; because timestamps make a fresh run different, use a new filename (or
deliberately remove the old local artifact) when repeating a demo. Validate only artifacts from
trusted sources: `verify-evidence` treats files as untrusted strict UTF-8 JSON, rejects duplicate
keys, and limits the document to 1 MiB, 32 JSON levels, 50,000 JSON nodes, and 1,024 evidence
packets before deriving the claim, trace chain, and decision. These bounds mitigate local resource
exhaustion; they do not authenticate an unsigned artifact.

Run all offline checks:

```bash
Expand All @@ -113,6 +143,7 @@ class MyVerifier:
```

Start with `src/verifaxis/verifiers/` and the security boundaries in [CONTRIBUTING.md](CONTRIBUTING.md).
Report a sanitized defect with the [bug form](https://github.com/aliengineering-byte/verifaxis/issues/new?template=bug.yml), or discuss a verifier/research question with the [research form](https://github.com/aliengineering-byte/verifaxis/issues/new?template=research.yml). Never attach private prompts, credentials, or unredacted evidence.

## Research status

Expand Down
6 changes: 3 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

## Supported versions

Security fixes target the latest `0.1.x` revision on `main` while the project is pre-release.
Security fixes target the latest pre-release revision on `main`.

## Reporting

Use GitHub's private vulnerability reporting for `aliengineering-byte/verifaxis`. Do not disclose suspected vulnerabilities in public issues. Include affected version, reproduction, impact, and any suggested mitigation. Expect an acknowledgement within seven days; remediation timing depends on severity and maintainer availability.

## Security model

Model candidates, prompts, endpoint responses, verifier output, counterexamples, artifacts, and configuration files are untrusted. Default verifiers never run arbitrary candidate code. The math and restricted-function paths interpret allowlisted syntax only. The OpenAI-compatible adapter sends data only to the endpoint explicitly configured by the caller.
Model candidates, prompts, endpoint responses, verifier output, counterexamples, artifacts, and configuration files are untrusted. Default verifiers never run arbitrary candidate code. The math and restricted-function paths interpret allowlisted syntax only. The OpenAI-compatible adapter sends data only to the endpoint explicitly configured by the caller, requires HTTPS for remote credentials, allows credentialed HTTP only for loopback testing, and redacts transport error details.

Out of scope for v0.1: arbitrary-code sandboxes, multi-tenant hosting, authentication, secret storage, and network retrieval. See `docs/threat-model.md`.
Out of scope: arbitrary-code sandboxes, multi-tenant hosting, authentication, secret storage, and network retrieval. See `docs/threat-model.md`.
1 change: 1 addition & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ p_{z+1} = A_phi(p_z, h_z, Encode(e_z))
| `EvidenceResidual` | Explicit unresolved/failed/conflicting constraints | Data, not free-form hidden reasoning |
| `VerificationController` | Continue, verify, abstain, or stop on a named failure mode | Cannot promote LLM criticism to independent proof |
| `RunTrace` | Persist steps, accounting, evidence, residuals, termination | JSON-safe and auditable |
| Claim/evidence artifact | Bind an explicit final claim to the recurrence, packet hashes, and named stopping decision | Tamper evidence is not proof that the verifier is correct |

Provider, verifier, controller, trace storage, and reporting boundaries remain separate so experiments can change one factor at a time.

Expand Down
6 changes: 3 additions & 3 deletions docs/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ Protect the host, secrets, local files, network, trace integrity, experimental v
| False verifier pass | Independence/reliability metadata, conflict checks, fault testing, false-verification metric | A single trusted verifier can be wrong or incomplete |
| LLM critique laundering | `llm_generated` is explicit; LLM-only evidence cannot verify | Incorrect integration metadata |
| Replay/stale evidence | Candidate/claim binding and stale-evidence faults | Weak semantic claim binding |
| Resource exhaustion | Bounded model/verifier calls, iterations, tokens, and runtime accounting | HTTP endpoints enforce their own hard limits |
| Secret leakage | No keys in config/examples/traces; environment-based endpoint credentials; secret scans | Provider request logs and user-supplied prompts |
| Unsafe deserialization | JSON only for public config/trace paths | JSON size/depth denial of service without caller limits |
| Resource exhaustion | Bounded model/verifier calls, iterations, tokens, response bytes/JSON shape, and evidence file/JSON/packet counts | HTTP endpoints enforce their own server-side limits |
| Secret leakage | No keys in config/examples/traces; remote credentials require HTTPS; loopback-only HTTP credential exception; endpoint errors are redacted; secret scans | Provider request logs, caller-supplied custom header names, and user-supplied prompts |
| Unsafe deserialization | JSON only; evidence rejects malformed UTF-8, duplicate keys, files over 1 MiB, depth over 32, over 50,000 nodes, and over 1,024 packets | Callers that bypass the bounded file loader must impose equivalent transport limits |
| Misleading research claim | Frozen contract, raw paired results, explicit smoke labels | Human interpretation and selective reporting |

## Arbitrary code
Expand Down
1 change: 1 addition & 0 deletions paper/claims.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Default arithmetic and restricted-function verifiers do not execute arbitrary candidate Python.
- The controller exposes named success, budget, plateau, oscillation, conflict, unverifiable, model-error, and verifier-error outcomes.
- The smoke harness can exercise named baselines and controlled fault types offline.
- The CLI can export and independently validate a tamper-evident claim/evidence recurrence artifact.

These are engineering claims, not model-quality findings.

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ build-backend = "hatchling.build"

[project]
name = "verifaxis"
version = "0.1.0"
version = "0.2.0"
description = "Verifier-conditioned recurrence for frozen language models, with executable evidence, adaptive stopping, and reproducible evaluation."
readme = "README.md"
requires-python = ">=3.11"
license = { text = "Apache-2.0" }
license = "Apache-2.0"
authors = [{ name = "Ali" }]
keywords = ["llm", "verification", "test-time-compute", "reproducible-research"]
classifiers = [
Expand Down
15 changes: 15 additions & 0 deletions src/verifaxis/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
"""VerifAxis public API."""

from importlib.metadata import version as package_version

__version__ = package_version("verifaxis")

from .controller import VerificationController
from .evidence import (
build_claim_evidence_artifact,
load_and_validate_claim_evidence_artifact,
validate_claim_evidence_artifact,
write_claim_evidence_artifact,
)
from .interfaces import ModelAdapter, Verifier
from .runtime import verify
from .types import (
Expand Down Expand Up @@ -32,5 +42,10 @@
"VerificationController",
"VerificationResult",
"Verifier",
"__version__",
"build_claim_evidence_artifact",
"load_and_validate_claim_evidence_artifact",
"validate_claim_evidence_artifact",
"verify",
"write_claim_evidence_artifact",
]
52 changes: 46 additions & 6 deletions src/verifaxis/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@
from pathlib import Path
from typing import Any

from . import __version__
from .bench import load_config, run_benchmark
from .evidence import (
load_and_validate_claim_evidence_artifact,
write_claim_evidence_artifact,
)
from .models import ReplayModel
from .reporting import canonical_json, load_run, write_report
from .runtime import verify
from .types import VerificationResult
from .verifiers import SafeMathVerifier


Expand All @@ -37,7 +43,7 @@ def _name(value: Any, *, default: str) -> str:
return default


def _run_spec(path: str | Path) -> dict[str, Any]:
def _run_spec(path: str | Path) -> VerificationResult:
spec = _json_file(path)
allowed = {"schema_version", "task", "model", "verifiers", "max_iterations"}
unknown = set(spec) - allowed
Expand All @@ -62,10 +68,10 @@ def _run_spec(path: str | Path) -> dict[str, Any]:
[SafeMathVerifier() for _ in verifier_names],
max_iterations=max_iterations,
)
return result.to_dict()
return result


def _demo() -> int:
def _demo(args: argparse.Namespace) -> int:
task = "What is 197 * 83?"
result = verify(task, ReplayModel(), [SafeMathVerifier()], max_iterations=4)
payload = {
Expand All @@ -78,13 +84,28 @@ def _demo() -> int:
"model_calls": result.trace.model_calls,
"verifier_calls": result.trace.verifier_calls,
}
if args.evidence_output is not None:
evidence_path = write_claim_evidence_artifact(
result,
args.evidence_output,
producer_version=__version__,
)
payload["evidence_artifact"] = str(evidence_path)
sys.stdout.write(canonical_json(payload))
return 0 if result.verified else 1


def _run(args: argparse.Namespace) -> int:
result = _run_spec(args.config)
rendered = canonical_json(result)
payload = result.to_dict()
if args.evidence_output is not None:
evidence_path = write_claim_evidence_artifact(
result,
args.evidence_output,
producer_version=__version__,
)
payload["evidence_artifact"] = str(evidence_path)
rendered = canonical_json(payload)
if args.output is None:
sys.stdout.write(rendered)
else:
Expand All @@ -95,6 +116,12 @@ def _run(args: argparse.Namespace) -> int:
return 0


def _verify_evidence(args: argparse.Namespace) -> int:
validation = load_and_validate_claim_evidence_artifact(args.artifact)
sys.stdout.write(canonical_json(validation))
return 0


def _bench(args: argparse.Namespace) -> int:
config = load_config(args.config)
result = run_benchmark(config, args.output)
Expand All @@ -121,11 +148,22 @@ def build_parser() -> argparse.ArgumentParser:
)
subparsers = parser.add_subparsers(dest="command", required=True)

subparsers.add_parser("demo", help="run the deterministic arithmetic smoke demo")
demo_parser = subparsers.add_parser("demo", help="run the deterministic arithmetic smoke demo")
demo_parser.add_argument(
"--evidence-output", help="optional complete claim/evidence artifact destination"
)

run_parser = subparsers.add_parser("run", help="run a JSON-valid YAML task config")
run_parser.add_argument("config", help="path to the run configuration")
run_parser.add_argument("--output", help="optional JSON trace destination")
run_parser.add_argument(
"--evidence-output", help="optional complete claim/evidence artifact destination"
)

verify_evidence_parser = subparsers.add_parser(
"verify-evidence", help="validate a claim/evidence artifact offline"
)
verify_evidence_parser.add_argument("artifact", help="artifact JSON path")

bench_parser = subparsers.add_parser("bench", help="run deterministic smoke benchmarks")
bench_parser.add_argument("--config", required=True, help="benchmark configuration path")
Expand All @@ -145,13 +183,15 @@ def main(argv: Sequence[str] | None = None) -> int:
args = parser.parse_args(argv)
try:
if args.command == "demo":
return _demo()
return _demo(args)
if args.command == "run":
return _run(args)
if args.command == "bench":
return _bench(args)
if args.command == "report":
return _report(args)
if args.command == "verify-evidence":
return _verify_evidence(args)
except (OSError, ValueError) as error:
parser.error(str(error))
parser.error(f"unknown command: {args.command}")
Expand Down
Loading
Loading