Skip to content

Releases: docket-dev-org/docket

Docket 0.2.3

Choose a tag to compare

@shivanshjain456 shivanshjain456 released this 20 Oct 14:47

2026-10-20

Fixed

  • The uv install path asks the index rather than its own cache. uv keeps an index
    cache with a different lifetime from PyPI's, and the packaging guard reads PyPI's. Measured
    on 0.2.2: the simple index served the file immediately while uv tool install docket-ai==0.2.2 resolved no version of docket-ai==0.2.2 until --refresh was passed.
    The test now passes it. This fix is not provable by mutation here: removing both flags
    leaves the test green, because the cache has since populated and the failure window has
    closed. The evidence is the direct measurement taken in the window.

No change to the shipped code: git diff 0.2.2..0.2.3 -- src/ is empty.

Docket 0.2.1

Choose a tag to compare

@shivanshjain456 shivanshjain456 released this 20 Oct 14:19

2026-10-20

Fixed

  • The release gate installs the package under test. .github/workflows/release.yml ran
    the whole suite against a tree whose package was never installed, so tests/test_action.py
    and tests/test_packaging.py failed with docket: command not found — exit 127, which is
    not a verdict about the tag and reads like one. 0.2.0 was published with a release check
    that had never passed. This is the same omission the CI tests job had, fixed there first
    and missed here.

  • The packaging tests measure the version the tree declares. tests/test_packaging.py
    pinned the version as a literal, and its pipx test resolved the binary with
    shutil.which("docket") after a successful install — which finds whichever docket is
    already on PATH, not the one pipx just made. It passed on every clean machine and failed
    on the release candidate. The install is now directed into a throwaway PIPX_HOME and
    PIPX_BIN_DIR, and the binary is taken from that directory.

Added

  • tests/test_release_workflow.py, which holds the release gate to the same standard as
    the push gate: the package is installed and installed before the suite runs, the release
    job's test dependencies are the CI tests job's, the gate runs the tests directory
    rather than a subset, it prints its counts, a tag with no changelog section fails the job,
    the workflow uploads nothing to PyPI and carries no PyPI credential, and the GitHub
    release uses the run-scoped token. Eight mutations, all caught.

No change to the shipped code: git diff 0.2.0..0.2.1 -- src/ is empty, which is why 0.2.1
exists — so that the newest tag, the newest release and a green gate name the same commit.

Docket 0.2.0

Choose a tag to compare

@shivanshjain456 shivanshjain456 released this 20 Oct 14:11

2026-10-20

  • Retention of the provider responses the boundary saw. Every model.response event now
    carries response_sha256 over the exact bytes returned to the agent, and
    [evidence] record_responses additionally keeps a bounded copy of the body in a responses
    sub-directory of the run directory, named by its own digest. docket verify re-hashes every
    retained body under the new retained-responses check, so one edited or deleted after the run
    fails. The reason this exists is a provider Docket cannot read: its usage block is in a fourth
    shape, the token counts are estimated from the response text, and that text used to be
    discarded, so the estimate could never be checked. A truncated body carries both digests and
    says which is which; the verifier checks the bytes that were written and never claims to have
    checked bytes it was not given. docket report --redact drops the retention fields rather
    than leaving a pointer to a body the copy does not carry, and records how many under
    retained_responses_not_carried. Retention is off by default: a response body can contain
    the prompt, a tool result, or a credential, so the digest is the default and the body is the
    opt-in. See docs/evidence-retention.md.

  • Published to PyPI as docket-ai 0.1.0. pip install docket-ai installs the same
    package, and the import, the console script and the command are all still docket. The
    distribution name differs because PyPI administratively prohibits the bare name docket:
    the JSON API answers 404 for it, and twine upload still refuses it with "The name
    'docket' isn't allowed"
    . PyPI keeps that list to prevent dependency-confusion attacks, and
    the only way off it is a PEP 541 request reviewed by the PyPI administrators. PUBLISHING.md
    records the reason, the one place the name is written, and the request process.

  • PUBLISHING.md, the maintainer runbook for shipping a release. It records what the
    release gate does, why the PyPI upload stays a manual twine upload rather than a step in
    the workflow (a PyPI token can publish under the project's name forever and cannot be
    scoped to a branch, so a repository secret hands it to everyone who ever gains admin here,
    while github.token is scoped to one run and expires with it), the prohibited-name
    evidence, and how to cut and re-cut a tag.

Changed

  • The README leads with the install path that works. It told a reader that docket was
    not on PyPI, that the name had been unregistered on 2026-10-17, and that
    pip install docket would start working after a future 0.1.0 publication. The release
    exists, so the apology was the only thing left. The install section now opens with
    python3 -m pip install docket-ai, and pipx install docket-ai replaces the wheel URL.
    The clone path stays, because it is what a contributor wants rather than what a user wants.

  • The version note states what is guaranteed instead of hedging. It said the CLI "may
    change before 1.0", which read as an unfinished project. It now says the format is frozen by
    the specification and its 44 vectors across three implementations, and that exit codes,
    flags and the JSON shape of every command are part of the interface and stable across 0.1.x.

  • The exit-code table gains 1, and documents exit-code propagation. docket run hands
    back the exit code of the command it wrapped, so docket run --policy docket.toml -- ./build.sh is safe in a pipeline. That was true and undocumented.

  • The quickstart verifies .docket/latest.receipt.json instead of a DKT-*.receipt.json
    glob.
    The glob worked on the first run and exited 2 on the second, because verify takes
    one receipt and argparse rejects two. The path every run rewrites has no such edge.

  • pyproject.toml uses the SPDX license expression and license-files, so PyPI renders
    the licence rather than a free-text string.

Fixed

  • The doc-link guard now asserts the opposite of what it used to. It used to fail any
    document that instructed a reader to install an unpublished package, and its own docstring
    said to relax it by removing the instruction once published. It now fails any document that
    still carries the apology, and reads the distribution name from pyproject.toml rather than
    repeating it, so a rename cannot leave the guard and the guide disagreeing.

  • docket verify --impl node: a second opinion from an installation. The claim that the
    format is not this tool needed the other tool to be reachable without a clone, so the wheel now
    carries verify-js/verify.mjs and the conformance corpus under docket/_vendor/, and
    docket verify --impl node runs that program against the same receipt and the same ledger and
    prints its own verdict and its own exit codes (0/1/2). A pipeline that already gates on
    docket verify needs no change when it switches. docket doctor now reports whether this
    installation has both the verifier and a node on PATH, and two tests read the packaging
    configuration rather than a built artifact, so a dropped force-include line fails here
    instead of in a reader's terminal.

  • docket verify finds the ledger beside the receipt instead of checking less. It used to
    need --ledger or it skipped the chain, the roots, the file digest and the re-binding of the
    usage rows. It now looks in the .docket/runs/ directory beside the receipt it was handed and
    selects by the ledger.file_sha256 the receipt carries, so the file used is provably the one
    the receipt was built from — including for a receipt that arrived in an archive with its
    ledger alongside. The line naming the file is printed, --ledger still overrides, and a
    receipt with no ledger anywhere still degrades to SKIP rather than to a red result.

  • A third implementation, written from the specification alone. verify-go/ is a
    dependency-free Go verifier built from spec/ and the vectors without reading the Python or
    Node one, with a CLEANROOM.md that declares the constraint before any code existed. It agrees
    with the other two on the whole corpus — 132 correct, 0 wrong, 0 not judged over 44 vectors —
    and it produced the artifact the claim needed: twelve questions the specification does not
    answer
    , five of which are worth amending (rounding of the sixth decimal, price-table matching,
    the shape of limits_disclosure, blank lines in a ledger, and the marker for a reached cache
    ceiling). make verify-go runs vet, gofmt and 16 table-driven test functions; CI has its own
    job; make conformance includes it whenever the binary exists.

  • Three more members of a receipt were stated and never checked, and now are.
    totals.by_model[], the usage-source counts and totals.cost_basis are derived from
    appendix.usage, so they can be recomputed — but §8 only ever named the total, the token
    totals and the per-row costs. A sweep that rewrote each of them left ok: true every time: a
    breakdown could name a model nobody called (with the total still reconciling), or present four
    estimated calls as reported. by-model-totals and usage-source-counts are new checks;
    cost-basis is a contradiction check that compares only the class word and reports quota mode as SKIP, because the meter's mode is stated nowhere else in the receipt and believing
    it is not checking it. Four new corpus vectors (39–42), eleven new tests in the Python
    suite, eleven in the Node one, and ADR 0007 records why the other designs were rejected.

  • ledger_reject: the corpus can now say "this ledger must be refused". Every vector was a
    receipt that must fail; a malformed ledger — the file an interrupted append or a hand edit
    actually damages — had no way to be expressed, so the two implementations' refusal to repair one
    was asserted by their tests and by nothing a stranger could run. Two vectors (37, 38: a
    duplicated key inside an event, a half-written last line) and a fourth expectation kind close it.
    The rule is the same as for receipts: a reason must be stated, and the vector may only require a
    token that appears in the ledger.

  • The corpus grew to 36 vectors and found four more defects — including one in the same file twice.
    spec/vectors/invalid/ now covers the parsing family (a UTF-8 BOM, trailing bytes, invalid UTF-8 —
    all refused before any check runs) and, for the first time, documents that must be accepted:
    4e0 where an integer is expected, -0, and a decomposed Unicode form. Those three exist because a
    corpus that only says what to refuse lets two implementations disagree about what to read, and the
    first one settled a real disagreement: spec §3.2 says an integral float MUST be normalised, Python
    refused every float token and Node accepted every one. The spec's sentence decided it; both now
    normalise an integral float and refuse a fractional one. Probing the totals member by member also
    found that totals.calls, totals.input_tokens and totals.output_tokens were never compared with
    the appendix in either implementation — a receipt could say "calls": 0 beside four usage rows and
    verify — and that Node's readFileSync(path, "utf8") silently replaced invalid bytes with U+FFFD, so
    it verified a repaired copy of a corrupted document. All four are fixed, with vectors and tests.

  • reason_any_of, because a corpus may not demand one implementation's prose. The first version
    of the BOM vector required the word BOM; Node said Unexpected token '\ufeff', refused correctly,
    and was scored wrong-reason. An expectation now names tokens that must appear in the document
    (NaN, Infinity, a member name), or nothing at all when the damage has no nameable token. Writing
    a corpus before the third implementation exists is an exercise in not encoding the first two's
    vocabulary.

  • **`docs/adr/0006-th...

Read more

Docket 0.1.8

Choose a tag to compare

@github-actions github-actions released this 19 Oct 23:39

[0.1.8] - 2026-10-19

Fixed

  • PUBLISHING.md told the reader to run a command PyPI now refuses. The guide shipped
    twine upload dist/*.whl dist/*.tar.gz with no username, and PyPI has withdrawn username and
    password authentication from the upload endpoint entirely. Setting TWINE_USERNAME to the
    account name, which is the obvious thing to do, is refused with
    403 Username/Password authentication is no longer supported — an error that names
    authentication rather than the username, so a correct token and a correct account both look
    like the problem. The guide now exports TWINE_USERNAME=__token__ beside the token and says
    why the literal string is required. This cost one failed upload while shipping 0.1.7.

  • The release build left the previous release's artifacts in dist/, so the documented upload
    command was one release away from breaking.
    tools/build_dist.py copied the new wheel and
    sdist beside the old ones and stopped there, and the command PUBLISHING.md gives the reader
    names both extensions rather than dist/*. Shipping a second release therefore handed PyPI a
    filename it already holds, which fails after the current release's files have been accepted.
    A rebuild now clears the distributions it supersedes and prints each removal, and --check
    refuses a dist/ holding a distribution the provenance record does not name. Only the two
    extensions the upload command reaches are touched.

  • The suite size quoted in four documents was two tests behind. Adding the guards above moved
    the collected count from 1236 to 1238, and the README, CONTRIBUTING.md, docs/testing.md and
    docs/sre-coverage.md each promise the reader a total. The per-file row for
    test_build_dist.py moved from 16 to 19.

Docket 0.1.7

Choose a tag to compare

@github-actions github-actions released this 19 Oct 22:27

[0.1.7] - 2026-10-19

Fixed

  • The race the 0.1.6 release gate found, shipped under a number that carries the fix. The
    0.1.6 wheel and sdist were uploaded before the race was closed, so the published 0.1.6 is the
    one whose receipt could claim 62 events over a ledger holding 64. PyPI reserves a filename once
    it is published, so the only way to ship the fix is a release built from the commit that holds
    it — which is what this is. The remote v0.1.6 tag pointed at that same commit, its release
    workflow had failed, and no release existed for it; the tag was deleted rather than left
    naming a version whose artifacts did not match it.

    Nothing else changed. The tree is the one d8992ca describes: the drain that stops the receipt
    racing the boundary's last recording call, the build-from-ref tooling that keeps an artifact
    from predating its tag, and the publishing guide corrected to name the two distributions
    rather than the whole of dist/.

Docket 0.1.5

Choose a tag to compare

@github-actions github-actions released this 19 Oct 19:56

[0.1.5] - 2026-10-19

Fixed

  • The published long description carries the mark. The 0.1.4 wheel and sdist were built from
    the commit before the README logo was committed, so the package page rendered the version and
    the badges but no artwork. README.md is the long description, and the two distributions of
    0.1.4 froze it at that commit; re-uploading them under the same filenames is not possible,
    because PyPI reserves a filename once it is published. 0.1.5 is built from the commit that
    holds the logo, and the package page and the repository now agree.

Added

  • The README carries the horizontal lockup, in both schemes. A <picture> with a
    dark-mode <source> and a light <img>, 250x80, sitting above ## Why Docket?. Both
    targets are absolute and point at the documentation site, which is where the rest of the
    README already points: README.md is the PyPI long description, and PyPI resolves a relative
    target against https://pypi.org/project/docket-ai/, so a repo-relative logo would 404 on
    the package page while resolving correctly on GitHub. That is the same defect the 29
    markdown links already carried, and it took three releases to notice.

  • The PyPI link guard now sees raw HTML. INLINE_LINK matches ](target) only, so a raw
    src or srcset was invisible to both halves of tests/test_doc_links.py; a logo pointed
    at a relative path would have 404'd on PyPI unreported. HTML_ASSET reads the src,
    srcset and href attributes of any img, source or link tag, and both guards use it.
    Both halves were proven to fire on the defect they now name by injecting a relative logo
    path and a dead absolute one into the README and watching the right guard fail.

    Measured on the published description rather than on the source: rendered through PyPI's own
    readme_renderer and nh3 sanitiser, the 0.1.5 description carries 52 href and src
    attributes, 36 of them absolute, 0 relative, and all 30 distinct URLs answer 200.

Docket 0.1.3

Choose a tag to compare

@github-actions github-actions released this 19 Oct 16:51

[0.1.3] - 2026-10-19

Fixed

  • Every receipt since 0.1.0 named a tool version that was not running. The version was written
    three times — src/docket/__init__.py's __version__, src/docket/receipt.py's
    TOOL_VERSION, and src/docket/supervisor.py's DOCKET_TOOL_VERSION — and each had to be
    edited by hand on every release. Two of the three were still 0.1.0 when the distribution
    installed 0.1.2, so docket --version said one thing, pip show said another, and the
    tool.version field in every receipt named a release that was not the one that wrote it. A
    receipt that misreports its author makes a false claim about provenance, which is the one thing
    a receipt exists to make checkable. All three now import from a new src/docket/_version.py,
    which derives the version from the installed distribution, falls back to the nearest
    pyproject.toml, and then to unknown. FORMAT_VERSION moved there for the same reason. The
    guard that had checked only __init__.py now scans every module under src/ for a version
    literal, in both the assignment and the dict-entry form, because scanning one file is what let
    the other two literals survive the first fix.

  • examples/custom-agent/agent_sdk.py crashed on the command its own README advertised. Under
    docket run, the session attaches to the supervised run and session.ledger is None by
    design — the supervisor is the only writer to that file, because two writers would produce two
    competing hash chains. The example printed session.ledger.path unconditionally, so the
    supervised command raised AttributeError while the standalone one worked. Only the standalone
    path was tested. The example now branches on session.attached and says what is true in each
    case, and tests/test_examples.py drives the supervised path and asserts the agent's own events
    reached the run's ledger labelled reported_by: "agent" and that the receipt verifies.

  • The Typing :: Typed classifier was a claim with nothing behind it. pyproject.toml
    declared the classifier and the wheel shipped no py.typed, so no consumer's type checker
    would read the package's annotations at all. The package was annotated all along — and 65 of
    those annotations were wrong, which nobody could see because nothing asked. py.typed now
    ships, mypy runs in the lint job, and the 65 errors are fixed: narrowing that did not survive
    a second method call, __exit__ annotated bool where it always returns False, a signing
    key accepted and then used as any key type, a metrics dict annotated dict[str, int] that
    holds a decimal string, and the rest. A guard test fails if the marker disappears while the
    classifier stays.

  • docket keygen did not exist, so docket run --sign had no way to produce a key.
    src/docket/signing.py could generate an Ed25519 keypair from the first release and nothing
    called it: no command, no documentation. The only way to sign a receipt was to already know
    the library function. docket keygen [PATH] [--force] writes the keypair at mode 0600, prints
    the public key, refuses to replace an existing key without --force (replacing a signing key
    invalidates every receipt signed with the old one), and exits 70 naming the install when the
    optional cryptography package is absent. Documented in the CLI reference, and tested end to
    end: keygen, sign, verify, and a tampered receipt that fails both the totals and the signature.

  • Two asserts were added to narrow a type, and bandit refused them. An assert is
    removed under python -O, and a tool whose whole claim is that it does not skip checks cannot
    rest one on a statement the interpreter is entitled to delete. verify.py now tests
    chain_problem directly — which is the narrowing that was wanted anyway, and drops the
    chain_ok alias that made the tuple unreachable — and policy.py raises for the case that
    cannot happen rather than asserting it. The line citations in docs/testing.md moved with the
    code and were re-checked against the harness's site list again.

  • Eight definitions in the package were dead code. generate_keypair was the visible one —
    docket run --sign was a flag with no way to produce a key, and the function that could have
    produced one was invisible. Seven more had the same shape: short_hash, mb,
    money_or_zero, stable_seed, is_known, default_price_table, price_document_digest
    and _gzip_bytes, each a one- or two-line helper with no caller anywhere in the tree. Dead
    code is not harmless in a project whose pitch is that a receipt says what the tool did: a
    package that carries functions nothing runs is making the same kind of claim about itself. All
    eight are deleted, and tests/test_supply_chain.py now fails when a top-level definition in
    src/ appears nowhere else in src/, tests/, tools/, examples/ or verify-js/.

  • A signing key of the wrong type produced an AttributeError from inside cryptography.
    load_pem_private_key returns a union of every key type the library supports, and most of
    them have no .sign(data). The call now refuses a non-Ed25519 key with a message naming the
    file and the key it holds.

  • Every link in the README was broken on the PyPI page. README.md is the package's long
    description, and PyPI renders it with no repository around it, resolving a relative target
    against https://pypi.org/project/docket-ai/. All 29 relative links therefore 404ed there while
    resolving perfectly on GitHub — invisible from the repository, which is why it survived three
    releases. They are now absolute: a target the documentation site publishes points at the site,
    so a reader arriving from PyPI lands in the documentation rather than in raw markdown, and
    LICENSE points at the repository. Two guards were added: one fails on any relative target, one
    fetches every absolute URL and fails on a non-200.

Changed

  • Documentation in [project.urls] pointed at a GitHub directory listing. It now points at the
    live documentation site, which is what a reader arriving from PyPI is looking for.

  • Two public properties replace a private attribute an example reached into:
    Session.docket_number and Session.ledger_path, the latter returning None when a
    supervisor owns the ledger rather than a path this session does not write to.

  • The line citations in docs/testing.md were re-based onto the code as it now stands. Each one
    was re-checked against tools/mutate.py's site list, so every number the page quotes is a
    line the mutation harness would actually mutate.

  • The conformance vectors were regenerated, because they carry tool.version and the spec states
    that vectors move with the implementation in the same commit. Only the version string and the
    two receipt_core_sha256 digests that cover it changed; all three implementations still agree
    (Python and Node: 88 correct, 0 wrong; Node's own suite: 100/100).

Docket 0.1.2

Choose a tag to compare

@github-actions github-actions released this 18 Oct 18:31

[0.1.2] - 2026-10-18

Fixed

  • docket --version reported a version the package was not. src/docket/__init__.py carried a
    literal __version__ = "0.1.0" while pyproject.toml moved to 0.1.1, and nothing compared the
    two. Every receipt's tool.version field and the output of docket --version, docket selftest and docket doctor said 0.1.0 while pip show, the PyPI page and
    importlib.metadata said 0.1.1. A receipt that names the wrong tool makes a false claim about
    provenance, which is the one thing a receipt exists to make checkable.

    The version is now read from the installed distribution, falling back to the pyproject.toml
    of a source tree and then to unknown. The distribution name is not written into the module,
    so renaming it cannot desynchronise the two again. tests/test_supply_chain.py asserts the
    derivation and fails if the literal returns.

Docket 0.1.1

Choose a tag to compare

@github-actions github-actions released this 18 Oct 17:53

[0.1.1] - 2026-10-18

Fixed

  • The quickstart linked a wheel filename that does not exist. It told a reader to install
    docket-0.1.0-py3-none-any.whl, which the rename to the docket-ai distribution had turned
    into docket_ai-0.1.0-py3-none-any.whl. The quickstart now installs from PyPI like the
    README does, keeps the clone path for a contributor, and links to PUBLISHING.md for why the
    distribution name differs from the command.

  • The PyPI long description quoted a stale test count. The 0.1.0 wheel was built before the
    supply-chain guard was added, so its rendered page said 1196 tests where the repository said
    1197. The wheels are otherwise byte-identical.

Docket 0.1.0

Choose a tag to compare

@github-actions github-actions released this 18 Oct 17:29

0.1.0 — 2026-10-13

First release: a bounded, legible, provable agent run.

Added

  • Boundary (docket run -- <command>): a loopback recording proxy that injects provider
    base URLs (or HTTP_PROXY with --http-proxy), enforces policy before forwarding a call,
    accrues cost from provider-reported usage, and terminates the supervised process group
    (SIGTERM, then SIGKILL after a grace period) when a cap is exceeded. Exit codes:
    0 clean, 42 cap exceeded, 43 policy error, 70 internal, 130 interrupted.
  • Ledger: append-only JSONL, each event carrying seq, ts, type, data, prev and
    hash; the first event binds to sha256("docket/genesis/v1"). Canonical JSON with integers
    only (fractional values are decimal strings) so that the same bytes verify in any runtime.
  • Receipts: docket/receipt/v1 with an RFC 6962 Merkle root over event hashes, per-call
    usage rows carrying inclusion proofs and re-bindable event hashes, the exact price table
    digest, deterministic findings with evidence sequence numbers, and a limits_disclosure
    block that verification fails if absent.
  • Verification: offline docket verify (Python, zero dependencies) and an independent
    verifier in verify-js/verify.mjs (Node standard library only), both bound to the committed
    conformance vectors in spec/vectors/. Checks report SKIP with a reason rather than
    passing when they cannot be performed, and the two implementations agree on the shape of a
    failure as well as the verdict: both recompute every event hash, both decline to check the
    head when the chain is broken, and both re-bind each usage row to the recomputed event at its
    sequence number. The Node verifier additionally checks the receipt-core digest against a
    committed vector.
  • Findings: runaway loops (byte-identical repeated requests), repeated context billed
    uncached, oversized context calls, policy-blocked calls, deny-write violations, premium-model
    leakage and retry storms — each with severity, confidence (deterministic / heuristic),
    attributed impact and a cited evidence trail.
  • Recording surfaces: SDK (docket.Session, record_model_call, record_tool,
    record_note) for Python agents, and /docket/tool/call + /docket/note HTTP endpoints so
    agents in any language can write to the ledger.
  • Policy: TOML policy with caps, model allow/deny globs with first-match ordering, watched
    and ignore globs, deny_write scopes, egress modes (tunnel, allow, deny, allowlist),
    price-table override, and on_breach = "kill" | "deny".
  • Demo: docket demo runs a scripted runaway agent against an in-process fake provider —
    offline, no API key, deterministic clock — prints findings, verifies the receipt, and with
    --tamper edits one byte of the ledger to show verification failing at the exact event.
  • Specification: spec/docket-receipt-1.md, including the canonical-form rules, the
    verification procedure, and §9 what a valid receipt does and does not establish.
  • Conformance vectors: spec/vectors/{canonical,merkle,ledger,receipt,receipt-digest}.json
    plus a generator, consumed as the cross-implementation contract by both test suites.

Notes

  • Attached sessions. An SDK Session started inside docket run writes through the
    boundary's /docket/event endpoint instead of opening the ledger file itself: one writer per
    chain, and every agent-asserted event carries reported_by: "agent" so a reader can tell
    claims from boundary observations. Outside a supervised run it keeps its own ledger, as before.
  • Totals are derived from the usage appendix, so a receipt can never disagree with the rows
    a verifier recomputes (the failure mode being $0.00 claimed against $0.0042 recomputed).
    Rows asserted by the agent are counted and labelled separately (asserted_calls).
  • docket verify --seal-key accepts the key file written by docket run --seal or the hex key.
  • Zero runtime dependencies. docket[sign] is the only extra that installs anything
    (cryptography, for Ed25519 signatures).
  • Prices in src/docket/prices.json are as of 2026-10-13 with sources recorded in the file;
    they are estimates for budgeting, and every receipt states which table it used and flags
    any model priced by fallback as assumed.