Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4fea8bf
feat(evidence): add deterministic exact snapshot manifests
wolfiesch Aug 31, 2026
a8aaac5
fix(evidence): use collections ABC imports
wolfiesch Aug 31, 2026
e819e4f
fix(evidence): report empty snapshots as exact mismatches
wolfiesch Aug 31, 2026
b44b7d8
fix(evidence): make manifest helper type explicit
wolfiesch Aug 31, 2026
6636682
fix(evidence): harden manifest replacement boundaries
wolfiesch Aug 31, 2026
621f003
test(evidence): cover destination and schema attacks
wolfiesch Aug 31, 2026
4842924
fix(evidence): enforce portable artifact paths
wolfiesch Aug 31, 2026
d9b77c6
test(evidence): cover path aliases and decoder limits
wolfiesch Aug 31, 2026
7ad5311
docs(evidence): render the CLI example as a code block
wolfiesch Aug 31, 2026
e6f2c9d
fix(evidence): require portable manifest filenames
wolfiesch Aug 31, 2026
9420e6c
test(evidence): reject nonportable manifest names
wolfiesch Aug 31, 2026
1d54474
fix(evidence): reject unstable and manifest-aliased artifacts
wolfiesch Aug 31, 2026
adfb5bf
test(evidence): cover unstable files and name aliases
wolfiesch Aug 31, 2026
95d5c41
fix(evidence): require a stable portable inventory
wolfiesch Aug 31, 2026
bbc5c3e
fix(evidence): bind hashes to the initial walk
wolfiesch Aug 31, 2026
a1c8859
fix(evidence): align manifest generation and read limits
wolfiesch Aug 31, 2026
05a75a7
fix(evidence): verify snapshot after publication
wolfiesch Aug 31, 2026
f5735a2
test(evidence): cover publish races and size bounds
wolfiesch Aug 31, 2026
dbce556
fix(evidence): harden manifest validation and reads
wolfiesch Aug 31, 2026
4ee48cb
fix(evidence): close verification race windows
wolfiesch Aug 31, 2026
a16160a
test(evidence): keep deep refusal cross-version
wolfiesch Aug 31, 2026
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
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,37 @@ Run the dedicated pivot capability artifact with:
uv run excelbench cross-language-pivot-context --fixture fixtures/excel/tier2/15_pivot_tables.xlsx --output results-cross-language-pivots
```


## Exact Evidence Manifests

A benchmark directory can be bound to its exact source and artifact identities with
a deterministic, path-free manifest:

```bash
uv run excelbench evidence-manifest \
--root results-release-2026-08-31 \
--snapshot-id wolfxl-2.1-linux-x86_64 \
--source-sha 0123456789abcdef0123456789abcdef01234567 \
--observed-at 2026-08-31T00:00:00Z \
--subject wolfxl-wheel@2.1.0=<wheel-sha256>

uv run excelbench verify-evidence \
--root results-release-2026-08-31 \
--expected-source-sha 0123456789abcdef0123456789abcdef01234567
```

The v1 contract inventories every regular file, hashes a canonical sorted file set,
rejects symlinks and cross-platform path collisions, and refuses undeclared, missing,
or changed files. It excludes only the manifest itself. The observation timestamp is
explicit so identical inputs produce identical manifest bytes.

The manifest is the subject to sign or attest in release CI. Successful verification
does not make an evidence lane current by itself: public claims must still name the
snapshot date, source commit, tested package subjects, platform, and workload.

Schema: [\`schemas/evidence-manifest-v1.schema.json\`](schemas/evidence-manifest-v1.schema.json)


## How It Works

1. **Generate reference files** -- [xlwings](https://www.xlwings.org/) drives real Excel to produce canonical `.xlsx`/`.xls` test files with known features.
Expand Down
118 changes: 118 additions & 0 deletions schemas/evidence-manifest-v1.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
{
"$id": "https://excelbench.dev/schemas/evidence-manifest/v1",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"properties": {
"artifact_count": {
"minimum": 1,
"type": "integer"
},
"artifact_set_sha256": {
"pattern": "^[0-9a-f]{64}$",
"type": "string"
},
"artifacts": {
"items": {
"additionalProperties": false,
"properties": {
"path": {
"minLength": 1,
"type": "string"
},
"sha256": {
"pattern": "^[0-9a-f]{64}$",
"type": "string"
},
"size_bytes": {
"minimum": 0,
"type": "integer"
}
},
"required": [
"path",
"sha256",
"size_bytes"
],
"type": "object"
},
"minItems": 1,
"type": "array"
},
"observed_at": {
"format": "date-time",
"pattern": "Z$",
"type": "string"
},
"schema": {
"const": "https://excelbench.dev/schemas/evidence-manifest/v1"
},
"schema_version": {
"const": 1
},
"snapshot_id": {
"minLength": 1,
"type": "string"
},
"source": {
"additionalProperties": false,
"properties": {
"commit": {
"pattern": "^[0-9a-f]{40}$",
"type": "string"
},
"repository": {
"minLength": 1,
"type": "string"
}
},
"required": [
"repository",
"commit"
],
"type": "object"
},
"subjects": {
"items": {
"additionalProperties": false,
"properties": {
"name": {
"minLength": 1,
"type": "string"
},
"sha256": {
"pattern": "^[0-9a-f]{64}$",
"type": "string"
},
"version": {
"minLength": 1,
"type": "string"
}
},
"required": [
"name",
"sha256"
],
"type": "object"
},
"type": "array"
},
"total_size_bytes": {
"minimum": 0,
"type": "integer"
}
},
"required": [
"schema",
"schema_version",
"snapshot_id",
"observed_at",
"source",
"subjects",
"artifacts",
"artifact_count",
"total_size_bytes",
"artifact_set_sha256"
],
"title": "ExcelBench evidence manifest v1",
"type": "object"
}
115 changes: 115 additions & 0 deletions src/excelbench/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2001,5 +2001,120 @@ def show_summary(results: "BenchmarkResults") -> None:
console.print(table)


@app.command("evidence-manifest")
def evidence_manifest(
root: Path = typer.Option(
Path("results"),
"--root",
help="Evidence directory to inventory exactly.",
),
snapshot_id: str = typer.Option(
...,
"--snapshot-id",
help="Stable identity for this evidence snapshot.",
),
source_sha: str = typer.Option(
...,
"--source-sha",
help="Exact 40-character source commit represented by the evidence.",
),
observed_at: str = typer.Option(
...,
"--observed-at",
help="Explicit timezone-aware observation timestamp.",
),
repository: str = typer.Option(
"SynthGL/ExcelBench",
"--repository",
help="Source repository identity.",
),
subject: list[str] | None = typer.Option(
None,
"--subject",
help="Bound artifact as NAME=SHA256 or NAME@VERSION=SHA256; repeatable.",
),
manifest_name: str = typer.Option(
"excelbench-evidence.json",
"--manifest-name",
help="Manifest filename created directly below the evidence root.",
),
replace: bool = typer.Option(
False,
"--replace",
help="Atomically replace an existing manifest.",
),
) -> None:
"""Create a deterministic exact-file evidence manifest."""
from excelbench.evidence_manifest import (
EvidenceManifestError,
build_evidence_manifest,
parse_subject,
verify_evidence_manifest,
write_evidence_manifest,
)

try:
subjects = [parse_subject(value) for value in subject or []]
manifest = build_evidence_manifest(
root,
snapshot_id=snapshot_id,
repository=repository,
source_sha=source_sha,
observed_at=observed_at,
subjects=subjects,
manifest_name=manifest_name,
)
output = root.resolve() / manifest_name
write_evidence_manifest(output, manifest, replace=replace)
Comment thread
wolfiesch marked this conversation as resolved.
verify_evidence_manifest(root, manifest, manifest_name=manifest_name)
except (EvidenceManifestError, OSError) as exc:
console.print(f"[red]Evidence manifest refused: {exc}[/red]")
raise typer.Exit(1) from exc
console.print(f"[green]✓ Evidence manifest: {output}[/green]")
console.print(f" Artifacts: {manifest['artifact_count']}")
console.print(f" Artifact set: {manifest['artifact_set_sha256']}")


@app.command("verify-evidence")
def verify_evidence(
root: Path = typer.Option(
Path("results"),
"--root",
help="Evidence directory covered by the manifest.",
),
manifest_name: str = typer.Option(
"excelbench-evidence.json",
"--manifest-name",
help="Manifest filename directly below the evidence root.",
),
expected_source_sha: str | None = typer.Option(
None,
"--expected-source-sha",
help="Optional exact source commit required by the caller.",
),
) -> None:
"""Fail unless a manifest exactly covers the current evidence directory."""
from excelbench.evidence_manifest import (
EvidenceManifestError,
read_evidence_manifest,
verify_evidence_manifest,
)

path = root.resolve() / manifest_name
try:
manifest = read_evidence_manifest(path)
verify_evidence_manifest(
root,
manifest,
expected_source_sha=expected_source_sha,
manifest_name=manifest_name,
)
except (EvidenceManifestError, OSError) as exc:
console.print(f"[red]Evidence verification failed: {exc}[/red]")
raise typer.Exit(1) from exc
console.print(f"[green]✓ Evidence verified: {path}[/green]")
console.print(f" Artifact set: {manifest['artifact_set_sha256']}")


if __name__ == "__main__":
app()
Loading
Loading