ledgerwash
Flags weakened evidence in agent work ledgers — handoff receipts, task records, handoff summaries — at a pinned git revision. 標出 agent 工作帳本裡被弱化的證據。
checkwash guards the diff; ledgerwash guards the ledger. Local-first. Deterministic. No LLM. No network. Read-only: does not execute the subject, does not enforce anything.
Engine 0.5.0, contract spec 5. The GitHub v0.5.0 source release
records the published SHA and final CI; the verification report
records the hosted nine-cell comparison and EC locked-install evidence. No PyPI package or
1.0 claim. Read SPEC.md (contract) and ARCHITECTURE.md
(layers; SPEC wins on conflict). Frozen acceptance is tests/test_gates.py (A1–A9).
Try it
Python 3.11+, git, uv. From this checkout:
uv sync --locked --extra dev
uv run ledgerwash --version
uv run ledgerwash qualify
uv run python -m pytest
uv run ledgerwash scan <repo> --at <pinned-sha> --out findings.json
scan with no --at pins to the target's HEAD; recording that sha is then the caller's
duty. Exit 0 is not proof the ledger is honest; it means no in-scope finding was at or above
fail_on (default high). A repo with no ledger at all is reported through residuals —
no-ledger is not a clean-ledger.
Consumer recipe: judging a ledger
Pin the commit first (write the sha down), then one invocation:
uv run ledgerwash scan <repo> --at <pinned-sha>
Record the judge's identity with the verdict: block @ ledgerwash 0.5.0, spec 5, epoch b8761e95…, adapter ec-ledger, exit 1. A pass that cannot name the judge is not
reproducible (SPEC §8).
Spec 4 reports mechanical verification coverage separately from findings. To require both the selected finding threshold and complete supported verification inputs:
uv run ledgerwash scan <repo> --at <pinned-sha> --require-complete
Default verdict follows finding_verdict; strict additionally requires
coverage.complete. A strict block may mean missing data without a high finding.
The initial complete profile covers EC task-store operation/repair receipts;
legacy/migration history stays explicitly unverified. See SPEC §3.1 and the
runner evidence design for the limits.
Spec 5 (v0.5) additionally promotes the negative-space escapes surfaced by the
2026-10-01 red-team round to findings — a DONE task whose closure time is missing,
empty, unparseable, redacted or contradicted by the file's own git history, a
ledger whose receipts carry no parseable recorded_at, receipts bound to no
loaded task, and expected_head pins that are not commits (SPEC §4, §10; see
spec 5 PREREG).
DONE_TIME_BACKDATED compares the task's claimed closure time with its birth
commit's claimed committer date. It does not establish ancestry order or trusted
time: a new child commit with a backdated committer date can avoid that finding
without rewriting any earlier history. The supplementary adversarial case in
PREREG Amendment 2 records this default-mode escape; --require-complete still
blocks its missing operation history. A narrow migration-continuity check also
prevents treating a newly introduced JSON representation as a newly created
task: the unchanged task, archive bytes and exact source row must bind to the
birth commit's actual first-parent ledger. A migration label alone is insufficient.
That parent ledger can still be fabricated; this is continuity, not historical
truth or complete migration verification. Strict migration coverage stays
incomplete, and unusable closure times retain DONE_TIME_UNESTABLISHED.
Three new rules are warn, below the
default fail_on=high threshold; surfacing a finding does not always block.
The spec 5 validation report records a passed
technical pool run but a failed real-ledger prediction: each of two pinned EC
epochs gained 170 DONE_TIME_BACKDATED findings tied to structured-task
migration. A newly introduced JSON path is not necessarily a newly created task.
The migration follow-up passed its
preregistered controls and pool revalidation: exactly those 170 identities are
removed per epoch, while 102 time-unestablished findings and all other findings,
coverage and residuals remain. A self-consistent forged parent still escapes
default mode; strict blocks its incomplete coverage. The earlier failed
prediction is preserved, and a workload PASS
does not establish that findings identify fabricated completion.
The S4 sample also demonstrates that a completely fabricated artifact can pass when its receipts, hashes and timestamps agree internally. Record independent execution evidence (for example, the CI run and exact source SHA) alongside a ledgerwash verdict when claiming that verification actually happened. A LedgerWash pass alone does not establish artifact authenticity; see S4 report.
Writing receipts that verify
raw-git-blob-bytes fingerprints are sha256 over the bytes git stores — the blob — never
the worktree file. In a repo with core.autocrlf=true, hashing the worktree file bakes
CRLF into the digest while the blob stores LF, and every receipt becomes unverifiable at
its anchor (the S4 round reproduced this live; the tool now hints at it):
import hashlib, subprocess
blob = subprocess.run(["git", "cat-file", "blob", f"{commit}:{path}"],
capture_output=True).stdout
digest = hashlib.sha256(blob).hexdigest()
At write time the receipt's birth commit does not exist yet, so hash the exact bytes you
are about to commit, and pin line endings (* -text in .gitattributes) to remove the
ambiguity entirely.
Round-0 pedigree
Thirteen of the fourteen rules formalize a preregistered human triage of a real 462-row
agent task ledger (91 real weakening instances, 4 categories, 205 raw signals → ~98% naive
false-positive rate): era-aware fingerprint anchors (birth / expected_head instead of
current HEAD), a closed fingerprint contract table, routed pins that never claim death from
local unreachability, timestamp quality as a first-class check instead of a silent skip,
and the legacy digit-masked redaction convention (TIMESTAMP_REDACTED) kept apart from
genuinely malformed timestamps. On that same corpus, excluding the timestamp-quality classes
(112 TIMESTAMP_MALFORMED in spec 1; 107 TIMESTAMP_REDACTED + 5 prose TIMESTAMP_MALFORMED
in spec 2; 203 findings in total either way), the spec 1 and spec 2 outputs reproduce the
human triage exactly: 91 findings (docs/recon/RECON.md). The
fourteenth rule, NO_OPERATION_HISTORY (spec 3), comes from the S4 escape sample
(docs/s4/REPORT.md), not from round 0; on the same corpus it flags
three more DONE tasks, closed after the receipt system existed yet with zero receipts, that
the human triage missed (T-291, T-293, T-297; docs/s4/REPORT-2.md),
so the same subset is 91 + 3 = 94 under spec 3.
Provenance
Incubated 2026-09-27→28 inside taipei49314/agent-rd — round-0 corpus validation (PASS: 4 categories, 91 instances), S3 prototype, adversarial review pass — and graduated to this independent repository on 2026-09-28 by human instruction. The preregistered acceptance reconciliation ships with the repository in docs/recon/ (PREREG, RECON report, raw finding envelopes); the incubation record (proposal, round-0 triage) remains in agent-rd.
Metadata
Release files for ledgerwash 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| ledgerwash-0.5.0.tar.gz | 243.5 kB | With provenance | Details for ledgerwash-0.5.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ledgerwash-0.5.0-py3-none-any.whl | Python 3 | none | any | Details for ledgerwash-0.5.0-py3-none-any.whl |
Total release size: 288.0 kB
Release files / ledgerwash-0.5.0.tar.gz
| Download URL | ledgerwash-0.5.0.tar.gz |
|---|---|
| Size | 243.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c555f688b229dc52fa5c4940d35442afd5f0f5696709ee6d1f8faf872e10ca83
|
|
BLAKE2b-256 checksum How to use checksums |
a33d5f38e005ad0e4e51a04bdf22b7aa9d9af65a81d57245b23feaa02e74f8e7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / ledgerwash-0.5.0-py3-none-any.whl
| Download URL | ledgerwash-0.5.0-py3-none-any.whl |
|---|---|
| Size | 44.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5a38efe79c38f8d4e2ea25dae231d174e597ffbc5ee2e3df4d312fdb7bc128e9
|
|
BLAKE2b-256 checksum How to use checksums |
c7671bad4203ba82543429216c8e74b91bca8de468d445e97ccf8cacca62db4f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logSecurity for ledgerwash 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for ledgerwash-0.5.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.