Skip to content

Releases: yizhao95/prov_ledger

0.4.8 — a task records why once, evidence follows the task, a graph refresh takes seconds

Choose a tag to compare

@yizhao95 yizhao95 released this 15 Oct 17:05
f7460b1

Changed

  • A plan's close records why the task was done, once. Close-time reasons
    were one per changed function or column; they are one per task now — why it
    was done, who asked, what was not chosen — as a quote of the user's words
    (stated), a reading (asserted) or unstated. A plan that did several
    things records one reason per task, assigns each changed node to one of them,
    and says in one sentence why they share the plan. reason-fill takes tasks
    (and bundle); the per-node reasons list exits 2 and names the new shape.
    reason-slots lists the changed nodes, the plan's root and the task reasons
    already written. review_run.py --reasons <file> takes the same
    {"tasks": […], "bundle"?}.

  • Rules R0–R5 no longer write reasons at close. They wrote one reason per
    node (a sentence that named it, a test that failed and then passed, a pure
    refactor, an anchored constraint, …); the task's reason carries the why now.
    R6 still turns a failed step that a deviation recovered into a rejected path,
    and the external trigger still logs one verdict per candidate. Rows written
    before are kept and read as before.

  • A plan nobody explained gets one unstated task reason, not one per
    changed node. The session close of the hooks-only mode does the same.

  • The reads show the task reasons of the plans that changed a node.
    provledger why, the /ledger fact table, the /receipts timeline, the
    context pack, the plan pre-flight and the evidence slots: for each node, the
    task reasons of the plans whose runs changed it (the state graph's history),
    marked task reason of <plan>. With several tasks, only the task the node
    was assigned to. A node a plan only matched does not get its reason.

  • Evidence is looked for once per task. provledger review evidence-slots
    lists one slot per task reason of the plan — the task, the nodes it covers,
    its root and the root's words, the plan's window, the identifiers to search on
    — instead of one per changed node. What the host agent looks for is the source
    of the task's root, and the slot says where a found source hangs: the root's
    words (reference add --utterance) when the plan's root names them, otherwise
    a task reason. A task whose root already has a linked source is not a slot.
    Slots are no longer ranked by significance; PROVLEDGER_EVIDENCE_SLOTS still
    caps them. review evidence-log takes --reason <id>; --node still records
    a slot from before.

  • A source keeps who said it and the passage they said. reference add --said-by … --excerpt … (or --excerpt-file): the passage, word for word, at
    most 2000 characters. The /ledger fact table and the /receipts timeline
    print it under the source as said by <who>: "…", and say when a source backs
    the task's root rather than the node. A passage on a personal source is not
    shown.

  • Per-node close records say what they are. A reason the close wrote about
    one node before task reasons (rules R0–R5, per-node answers, the per-node
    unstated backstop) is marked per-node record, before task reasons in
    provledger why, the /ledger fact table and the /receipts timeline.

Migration

  • 036 rebuilds change_reason so a reason may have no node (a constraint still
    names one), keeping every row, id and hash; it adds the append-only tables
    reason_node (which task a node was assigned to) and plan_bundle (why
    several tasks share a plan).
  • 037 adds reference.said_by and reference.excerpt (both inside the hash
    chain for new rows; older rows verify in their own form) and rebuilds
    evidence_log so a row may name a task reason (reason_id) instead of a
    node, keeping every row.

Fixed

  • A plan's close no longer waits 20–40 minutes for the graph refresh. The
    analyzers committed after every row they wrote, and the graph is a
    rollback-journal database, so each commit waited on the disk twice. On this
    repository a refresh writes about 100,000 rows; on WSL it took 21.6 minutes,
    almost all of it in those waits. The analyzers now commit every 2,000 row
    changes and once before the history layer, whose providers read the graph on
    a connection of their own. On a copy of the same graph the refresh took
    25 seconds and wrote the same nodes, edges, snapshots, events and cards; a
    reader polling the graph during it waited at most 1.4 seconds (its timeout is
    5). sql_refs, api_refs and data_model committed their own updates per
    row too, and go through the same batching.
  • A question asked in Chinese finds what was recorded (FL-152). /ledger
    and /receipts matched only ASCII words, so a question in Chinese found no
    candidate and the fact table came back empty, which the ledger skill reads as
    "nothing recorded". Han text is now matched by pairs of adjacent characters,
    with question words and particles left out; a record counts once, and needs
    two shared pairs when the question has three or more. provledger ask and
    receipts candidates take --also "<English words>", and both skills add the
    subject in English when the question is in another language, because most
    reasons and every code name are recorded in English. On a copy of this
    repository's ledger, a Chinese question that found no candidate finds 12, and
    with --also the three nodes about recovered steps rank 4th, 12th and 13th.
  • A question reaches the nodes of a task reason (FL-242). Since task
    reasons (migration 036) the reason a question matches most often has no node
    of its own, and the matchers dropped it; a source hung on one was not found by
    its label either. A matched task reason now leads to the nodes it answers for:
    the nodes assigned to it, or every node its plan changed for a plan's only task.
  • A session that loads the plugin with /reload-plugins can run
    provledger.
    It never ran the SessionStart bootstrap, so provledger said
    there was no venv; the first call now runs the same bootstrap. INSTALL and the
    README say to run /reload-plugins after installing inside a session.
  • Graph archives no longer pile up. Every refresh copies the whole graph to
    provledger.<sha>.db beside it, and nothing removed the copies (19 GB for this
    repository). archive_db.sh keeps the newest five, and its own checks now run
    in the psg suite.

Documents

  • docs/KNOWN-ISSUES.md: the refresh-cost entry has the new timing and says
    each refresh keeps a full copy of the previous graph, the newest five of them;
    the entry about projects registered outside the default directory is gone
    (fixed in 0.4.5); the install-session entry says what /reload-plugins
    changes; the claude.ai / Cowork entry says what each refuses or ignores; a new
    entry says a Chinese question reaches English records only through --also.

Development

  • The tests of the close rules R0-R5 are skipped, not deleted (FL-243). Since
    task reasons no product path runs the rules, and nothing reads what they filled
    (the reason-slots checklist). The code stays for now; 32 backend tests carry the
    marker r0_r5 and tests/conftest.py skips them, and the orchestrator/triggers.py
    docstring says what in the module is still live.

0.4.7 — a plan says which root cause it serves

Choose a tag to compare

@yizhao95 yizhao95 released this 10 Oct 03:31
f96870f

Added

  • A plan says which root cause it serves. A plan is one task; why it exists
    usually started earlier, in the user's own words, and several plans may carry
    the same root forward. The plan-input takes an optional root:
    {"kind": "new"} when this task starts one (optionally pointing at the user's
    recorded sentence), or {"kind": "continues", "plan_id": …} when it carries an
    earlier plan's root on. Without it the root is recorded as unknown, and
    publish lists the project's recent roots after the headline. Every judgement is
    a new row in an append-only table, recorded as asserted; continuing a plan
    resolves to where its root started.
  • The headline shows the earlier tasks under the same root. A plan that
    continues a root gets a third layer: each earlier task under it, and each
    rejected path those tasks recorded, which can be answered (and so adopted)
    like any other finding.

0.4.6 — a stated reason quotes only the user, a rejected path hangs where it is named, a correction reads as one

Choose a tag to compare

@yizhao95 yizhao95 released this 09 Oct 23:34
54ebd20

Fixed

  • A stated reason quotes only what the user typed. Claude Code sends some of
    its own text through the prompt hook: a finished subagent's report, a task
    notification, a system reminder. Older hooks recorded some of it as the user's
    words. Those rows stay in the ledger, but the close-time rule that quotes the
    user's words still read them, so a sentence from a subagent's report could
    become a node's stated reason. The rule now leaves that text out, and the
    ledger refuses to quote it as stated whoever asks. The hook and the rule use
    one definition of injected text.
  • A rejected path hangs on the node its failure names, or on the plan. The
    close-time rule that records a failed step as a rejected path used to pick the
    first node whose name was any word of the failure text, so "run the suites"
    landed on a function called run, and why told that function's story with
    someone else's failure. It now anchors only on a node the plan changed and the
    text names as code (Module.func, func(, `func`, or a snake_case or
    camelCase name), and otherwise on the plan. Ordinary words never anchor.
  • A corrected record reads as its correction. A correction is appended and
    supersedes the row it corrects; the old row keeps its words. why, the
    /ledger and /receipts fact table, the plan-time context, the export and
    the dashboard now show the newest row of each chain and say which row it
    corrects (corrects #N); provledger record #N still prints the old one. The
    node badge counts a chain once.
  • provledger reasons recheck lists the rows the two rules above wrote
    before this release: stated reasons quoting injected text, and rejected
    paths on a node their text does not name. --apply appends a correction for
    each (an unstated row, or the same rejected path re-anchored) in one
    transaction. Run it once after upgrading; without --apply it writes nothing.

Development

  • A test that opens the real ledger fails its suite even when it adds no row.
    One backend test ran provledger trigger eval without its own ORCH_DB, so it
    opened ~/skill-workspace/orchestrator.db with the working tree's code: that
    switched the ledger to WAL and ran the branch's migrations on it before any
    release carried them, while every row count stayed the same. The test now uses
    a ledger of its own, and scripts/home_guard.py also compares the real ledger's
    applied migrations and journal mode before and after each suite.

0.4.5 — hooks keep the user's words, closes finish, nothing is left stuck

Choose a tag to compare

@yizhao95 yizhao95 released this 08 Oct 01:29
bd246df

Fixed

  • A failed publish leaves nothing behind, and a plan nobody started can be put
    down.
    A step type the orchestrator does not know, expectations on a plan
    without a tracked project, or a headline note citing a record that does not
    exist were found only after the plan row was written, leaving a plan
    IN_PROGRESS with no steps that nothing could close. Every check now runs
    before the first write, and the plan, its steps and its skills are written
    together. abandon-plan.sh (plan id, reason) turns a plan whose steps never
    started into the new status ABANDONED, with the reason on the plan's record.
  • A COMMAND step is never left stuck. start-step refuses a COMMAND step —
    only run-step.sh starts one, because only its exit code completes it — and
    run-step stopped by a timeout or Ctrl-C fails its step and says it was
    stopped, instead of dying with the step IN_PROGRESS.
  • A project registered outside the default directory keeps its graph. The
    review's refresh and the session refresh wrote a new graph, with no history,
    into the default directory, and the registry followed it; both now refresh the
    graph where the project is registered.
  • A plan whose failed retry was retried in turn closes. A plan that failed
    through a regular step is judged again once every failure is recovered. When a
    retry had itself failed and been retried (the retry a child of the attempt, as
    the executing-plans skill says), that judging stopped with
    'sqlite3.Row' object has no attribute 'get' and the plan stayed FAILED.
  • A plan close takes seconds, not minutes, and finishes. On a large project the
    close's rules read the state graph without an index — every lookup of a node's
    latest snapshot scanned the whole table — and recomputed the plan's changed
    nodes once per file a sentence named. On provLedger's own graph a close took
    1 to 10 minutes; the review's 60 s ceiling cut every one, rolled it back, and
    the plan was closed by hand without its rules ever writing a reason. The graph
    now has the two indexes (created by the review's refresh, before the close),
    the changed nodes are computed once, and the rule that matches node names
    against the user's words looks them up instead of running one regex per name:
    the same closes now take 1-2 s with identical results. When a write still
    outlives its ceiling, the review driver reads the step and the plan back and
    says what they show.
  • The hooks stop losing the user's words to a locked ledger. The ledger now
    runs in WAL, so the dashboard's poll, a long read or a plan close no longer
    holds a hook's write up, and a prompt or a tool call that cannot get the write
    lock within two seconds goes to a spool file beside the ledger and is written,
    with the time it happened, by the next hook. Before, it was logged to
    hook-errors.log and gone. (The Stop hook's row is not spooled yet.)
  • The provenance hash chains cannot fork under two writers. A chained insert
    read the chain head before taking the write lock, so a second writer could slip
    in between and both rows would point at the same predecessor — which verify
    reports as tampering. The head is now read under the write lock, and compound
    writes take the lock when they open.
  • Before the first plan, the dashboard shows the empty state, not an error.
    With no ledger yet the page rendered a red "orchestrator.db not found" box,
    although the launcher says the dashboard is running; it now shows the empty
    state naming the command that publishes a plan. Found by the release check.
  • ensure-dashboard.sh probes the port the dashboard starts on. Setting
    PROVLEDGER_DASH_PORT alone started the dashboard there and then waited on
    8765 — or, with another dashboard already on 8765, reported that one as up.

Changed

  • /ledger answers as a handoff, and /receipts says what to check before you
    send.
    Both skills are rewritten from Anthropic's public guidance on skills and
    prompts: the intent first, every rule with its reason, a template and complete
    examples. /ledger leads with the answer, then where it came from (who asked,
    when, in their own words), what was done about it, and what holds now, in date
    order. It checks every sentence against what this session read before
    submitting, never shows a sentence the check deleted, and may resubmit once
    with an id it found. /receipts writes a complete reply told as a timeline and
    lists, apart from it, what the record does not settle — for you to confirm or
    add before sending. /ledger is for you, so it may go into detail: ask submit now keeps up to 15 sentences, not 8. /receipts speaks for you to
    someone else, so its reply is official, keeps to their question and holds
    nothing uncertain. Both copy every number and identifier from a read, and the
    scope line as each read printed it.
  • The fact table says what an agent used to guess. A node whose reason was
    closed unstated now shows a cited line ("nobody said why this changed") and
    the absence "No reason was recorded for X." — before, it printed reasons (0),
    which reads like "never touched", and an agent supplied the goal of the task
    that changed it. Every source says it is a link only, that the body is not in
    the ledger, and whether anyone checked it. The table, and the /receipts
    material, open with a legend: what each tier and cite token is, and how to put
    it into words.

Development

  • The release check walks a plugin user's path (scripts/release-e2e.sh,
    INSTALL.md §5): a new stage 0 installs the plugin into a sandbox and runs a cold
    first session and a first task the agent plans itself; stage 2 also asks every
    question as a real /ledger or /receipts slash command; stage 3 marks those
    real answers, and a disagreement is a finding a person decides.
    Its model calls copy the developer's current login right before each call, and
    a call that could outlive the login is not made and reads as BLOCKED: a sandbox
    that refreshed the login would leave the developer's own unable to refresh. The
    judge is handed the whole material an answer was written from; it used to be cut
    at 24000 characters without a word, and a quoted line past the cut was marked as
    resting on no record.
  • Measured on real sessions before release: the dummy project's twelve questions,
    asked twice of the old skills and of the new ones and marked by a model judge.
    Answers told in date order went from 9 to 21 of 24, questions whose two runs
    disagreed from 5 to 1, answers with a claim resting on no record from 5 to 3;
    key points hit stayed at 43 of 48. (scripts/release_e2e/skill_ab.py runs that
    comparison for any two refs.)

0.4.4 — provledger on the session's PATH, and what the release check found

Choose a tag to compare

@yizhao95 yizhao95 released this 06 Oct 03:46
05b0168

0.4.4 — 2026-10-05

Fixed

  • /ledger and /receipts could not answer for a plugin user. Both call
    provledger by name, and the command lives in the plugin's venv, which nothing
    put on the session's PATH: the first command failed with command not found.
    The plugin now ships bin/provledger, which Claude Code puts on the Bash tool's
    PATH while the plugin is enabled, and which hands every call to the venv. Found by
    a sandboxed install with a real headless session, the first time the plugin path
    itself was exercised. claude.ai and Cowork do not install a plugin with a
    top-level bin/; install with the Claude Code CLI.
  • A constraint added to a new ledger was recorded twice. A ledger constraint is
    mirrored into the readers' table when it is added, and the one-time reclass that
    every ledger runs on a later open copied it again — so on any ledger created
    since decision provenance, a constraint added before that first reclass became
    two. Readers folded the twins only when both writes fell in the same second;
    under load they did not, and a plan close recorded the bypass of one constraint
    twice. The reclass now leaves an already-mirrored constraint alone. Found as a
    scenario that failed about one run in fifteen under load.
  • /receipts could say more than a record says. A source is a pointer — the
    ledger holds its label, time and link, never its body — and the skill now says so
    in its hard rules: never call a linked email attached, never offer to send it,
    and never present an asserted record as something anyone agreed. Found by the
    release check's judge.
  • Failed tool calls were never logged. A failed call fires PostToolUseFailure,
    not PostToolUse, and only the latter was hooked. Both are now, and
    tool_call_log.failed (migration 032) marks the failures.

Development

  • The repository's own .venv is the development venv; run_tests.sh prefers it
    to the plugin's venv, which now holds the installed release (--which prints the
    choice). The home guard ignores SQLite's transient sidecar files.

0.4.3 — a clean-up of the development setup, and the defects it turned up

Choose a tag to compare

@yizhao95 yizhao95 released this 05 Oct 19:19
368c55f

0.4.3 — 2026-10-04

A clean-up of the development setup, and the defects it turned up. A fresh read of
the repository, with the documents checked against the code, found claims that had
drifted, paths only a new user takes that had never run, and seams between two
scripts that each passed their own tests.

Fixed

  • Words the user never said were recorded as theirs. Two kinds of text reached
    the prompt hook and became utterance rows — the source of the stated tier: a
    subagent's report handed back to the session (<agent-message …>), and the
    prompts provLedger itself sends to a headless claude -p (the judge, the arbiter,
    ask's summary), because the user's plugins run in that child too. The hook now
    skips the first, and the runner marks its child with PROVLEDGER_HEADLESS=1,
    under which every provLedger hook stands down. Rows already written stay: the
    tables are append-only.
  • run-step.sh swallowed the review hand-off. It sent complete-step's output to
    /dev/null, so needs_agent_review — the signal that a plan on a registered
    project must stop for a review sub-agent — never reached the caller. Its last line
    now carries complete-step's and fail-step's signals.
  • The external-artifact judge broke on the real runner. It handed the runner's
    (text, detail) pair to the parser and raised TypeError; with the switch on,
    that rolled back the whole plan close. The judge now reads the text, and a judge or
    runner that fails records a silent verdict instead of rolling anything back.
  • The declaration runner inherited your language setting. It was a second copy
    of the model runner without --settings; it now uses the shared one.
  • Tests and the demo could rewrite your real graph index. With only
    PSG_REGISTRY_PATH set, init_project.sh still wrote
    ~/skill-workspace/project-graphs/PROJECT-STATE-GRAPHS.md, dropping your real
    projects from it. The index and the default out-dir now live beside that registry.
    The provenance demo no longer takes an ORCH_DB or PSG_* path from the caller's
    shell either: DEMO_HOME is its only knob, so a shell pointing ORCH_DB at your
    real ledger cannot receive the demo's rows.
  • bootstrap.sh never upgraded the backend, and failed from a project directory.
    Its marker hashed only requirements.txt, so a version bump never re-installed the
    editable backend (no provledger command, 0.1.0 metadata); and uv resolved
    -e ./orchestrator-backend against the session's working directory. It now
    re-installs when the backend's pyproject changes, and installs from the plugin root.
  • The dashboard: ?lang=zh survives the 2 s poll and the view bar (the poll's
    ETag now varies by language); launch_dashboard.sh reports a dashboard with no
    ledger yet as running instead of failing and then reporting "port in use", and
    ensure-dashboard.sh agrees; the empty state names publish-plan.sh instead of
    the broken orchestrator-cli.py; the design bundle is rebuilt and no longer carries
    the old Chinese labels; the webapp's pyproject declares httpx.
  • orchestrator-cli.py runs again; the analyzer no longer walks .venv-* directories.

Development

  • scripts/run_tests.sh runs, counts (--count) or lists the suites, each in its
    own pytest process, from the one list in scripts/suites.sh (the release check
    reads it too). It replaces count_tests.sh, and fails any suite that writes to the
    real ~/skill-workspace (scripts/home_guard.py).
  • Copies are held to their originals by tests: the plan and update-input schemas to
    the code that validates them, SKILL.md descriptions to the 1024-character limit, the
    analyzer's graph_viz.py to its copy, the selfcheck table to selfcheck.py, and the
    version string to scripts/pkg_smoke_test.py.
  • CLAUDE.md files at the root and in each component hold the development
    conventions.

Documents

  • The README is a front page again. The command reference moved to
    docs/cli.md, the dashboard's routes to
    orchestrator-webapp/README.md, and the five
    quick-start commands were re-run: two of them did not print what the page said.
  • Shipped documents no longer carry a worked case from the local answer-quality
    evaluation (a walkthrough in the README, anecdotes in skills and docstrings).
  • INSTALL, KNOWN-ISSUES, the skills' SKILL.md files and docs/decision-provenance.md
    were corrected where they disagreed with the code: stale paths and counts, tools
    that exist only in another agent harness, the hash chains verify walks, and the
    dashboard's one write.

0.4.2 — the descent path from a record to the task behind it

Choose a tag to compare

@yizhao95 yizhao95 released this 01 Oct 14:28
e2c4526

Asked why a review timeout is 4800 seconds — a decision this project had recorded
in full — three separate searches reported "not recorded". The derivation sat in a
step's log_context, and no command read that column.

This release is the path down from a record to the task behind it, plus the four
reads that path needs. The release sandbox then found four more records that
existed with no way back to them, and one that answered with the wrong row.

New reads

provledger plan <id> a task's steps, their failures and their logs — the only read that reaches a failure inside a plan that closed COMPLETED because the failure was recovered. The detour is gone from the status; these rows are the only thing that remembers it.
provledger record '#12' one record whole. Every other read bounded text at 240 characters, mid-word, with no marker — so a reader could not tell they had a fifth of a row.
provledger graph [<target>] --depth N the project graph folded to areas with how many nodes carry records, unfolding to neighbour names with their edge type. The fold is pagination, never a judgement about relevance.
why --all / --impact both were no-ops that recommended themselves — --all printed "pass --all" to a caller who had — and the blast-radius line contradicted the footer inside one output.

What was recorded and could not be surfaced

  • ledger-add's two most important kinds were unreadable. New entries were
    mirrored into change_reason, where the readers had moved, but only
    if kind == "constraint". Every decision and anti_pattern added after the
    one-time backfill landed in a table no answer path reads — and those two are the
    whole reason that ledger exists.
  • A record hid the words it quoted. The quoted span was reachable only as a
    COALESCE's last resort, so a record carrying a paraphrase showed the paraphrase
    and said nothing about having the words. That is backwards for a stated
    record, whose entire claim is that the user's own words say it.
  • A metric could not be chosen, so a claim recorded about one and later
    contradicted by an observation was unreachable by any question.
  • An outcome's own account of itself was dropped in the renderer, leaving
    {"drift": 6.6} as the whole story of a claim that turned out false.
  • A deviation printed #94, and so does a ledger record. record '#94' on a
    deviation returned an unrelated row from another plan, with no error — a silent
    wrong answer in the one place this tool has to be checkable. Deviations print
    #v94 now, and record refuses a #v cite by naming the read that holds it.

All 1144 backend tests passed while those paths were broken. Every layer's tests
ask "did this layer do its job"; none asked whether a person who asks the question
gets the sentence.

Answering

  • An unsupported number is named, not deleted. The check can tell whether a
    number sits in its own table, not whether it is true — and a figure read out of
    a step log is sourced yet absent from that table, so the rule was removing
    correct answers.
  • /receipts is a skill alongside /ledger: the reply first, the evidence
    under it with a record id per line, then a question about tone. It never sends
    anything and writes nothing.
  • The CLI no longer starts a second model. --runner defaulted to claude,
    so a bare provledger ask shelled out to a headless model with no session, no
    tools and no context. The session already has a model.
  • Reading the code is encouraged. "Never answer from the repository" was
    written to stop code being used as evidence and also forbade using it as an
    index — and grepping for the thing you are asking about is how anyone starts.
    provLedger supplements the code: code shows the winner and never the rejected
    option, who asked, or what it cost. The one rule is not to read the code and
    invent a reason, because the real one is usually recorded.

Before every release

scripts/release-e2e.sh clones into an empty directory, follows INSTALL.md as
written, builds a dummy project, exercises both question surfaces, drives the
dashboard in a real browser, and has a model mark the answers against key points
fixed in advance. Every place it must deviate from the document is reported,
because that is where a new reader gets stuck.

It found this release's five defects, and two false reds of its own — which is
the other half of the lesson: a gate is worth only what its reds are worth.

Install

See INSTALL.md. §4 now installs requirements.txt, the one
authoritative list; the hand-written copy it replaced had drifted, omitting
httpx and never installing the backend at all.

On Debian/Ubuntu/WSL, §3's first command needs sudo apt install python3-venv
first, or take the uv option. The document records the error; the system package
is not something a document can fix.

provLedger 0.4.1

Choose a tag to compare

@yizhao95 yizhao95 released this 30 Sep 21:08
0a1055b

A fix release. Both defects were found by doing rather than by reading, which is the point.

The dashboard did nothing when clicked — on Chrome and Edge only. A handler cancelled htmx's own swap and then called htmx.swap() to do the replacement itself, an API that arrived in htmx 2.x while the page loads 1.9.10. The default was switched off and nothing took its place. Firefox and Safari were unaffected because they returned earlier, which made it look like an environment problem rather than a bug.

Your records were never at risk. The dashboard opens the ledger read-only and no write path touches the browser layer, so the hook kept recording and the pre-change check kept running the whole time — you just could not see them.

A ninth test suite existed and had never been run. The repository root's tests/ covers bootstrap, the plugin manifest, the marketplace entry, bundled skills and runtime dependencies — everything a release most needs verified — and it was missing from all three places the suite list lives. Every count from 1826 onward was 16 short.

2115 tests across nine suites, each run separately. make demo verified end to end.

claude plugin marketplace add yizhao95/prov_ledger
claude plugin install provledger@provledger

provLedger 0.4.0 — evidence

Choose a tag to compare

@yizhao95 yizhao95 released this 30 Sep 20:06
6bf11ed

A decision carries two separate things. A claim — from the task, from your own words, or from nowhere. And evidence — from written communication, found by your agent with your agent's own credentials. They never move each other: attaching an email to a reason does not change what kind of statement that reason is, it changes how far it can be checked.

Evidence sits where a test sits. A test does not change what the code does; it changes how much you can rely on the claim about it. And like a test, evidence is allowed to fail: an email that cuts against a decision is recorded as contradicting it rather than quietly left out.

An agent may not guess. A reason it cannot ground in what you said is written unstated. A pointer that turns up with nobody having said anything attaches to that blank rather than becoming a plausible reason. A blank is a fact; a fluent guess is a liability.

What is new

provledger receipts "<what they said>" — someone challenged a decision. This assembles what a reply needs and stops: a timeline, every line ending in its record id, the gaps named as gaps, the range searched counted rather than estimated, and a closing instruction that every claim must map to one of those lines. It does not write the reply. Composing a courteous reply is a model's native ability; material with its sources attached is the part it never had.

provledger reference add | pending | mark — pin a decision to where it came from as a label, a time and a link, never the body. Then: which pointers nobody has opened lately, and a verdict when somebody does. A dead link does not disappear — it becomes "this stopped opening on this date".

provledger review evidence-slots | evidence-log — the review step hands your agent a list and searches nothing itself. Your credentials stay yours. Afterwards, a record of what became of each slot, so a blank can say whether nobody looked, somebody looked and found nothing, or the search ran out of time.

utterance.origin — which door the words came through. recorded_by has existed since 0.3.0, but the writer declares it itself, so an agent running provledger note produced a row saying a person wrote it. Origin is decided by the entry point, so it is the half that can be checked.

Every plan close now anchors, not only the successful ones, and the git note says which. A plan can fail because a gate did not pass or because its own bookkeeping jammed, but the rows it recorded do not become untrue when it fails.

Fixed

A migration broke the hash chain and every suite stayed green — because every suite starts from a fresh database. The live ledger is what caught it.

The review step had no time limit at all. The first ceiling written for it was below every graph refresh this repository has on record; it now comes from that table, and the test asserts the margin rather than the number.

A review retry was created as a sibling of the attempt it retried, so a successful retry could never clear a failed one and the plan deadlocked with all its work done.

The circuit breaker suggested raising a limit that no command could raise.

Verification

2099 tests across eight suites, run separately, up from 1826. All 273 added were written before the code they cover.

Install

claude plugin marketplace add yizhao95/prov_ledger
claude plugin install provledger@provledger

The walkthrough: yizhao95.github.io/prov_ledger/walkthrough.html

provLedger 0.3.0 — decision provenance

Choose a tag to compare

@yizhao95 yizhao95 released this 17 Sep 22:23
0a653dd

Decision provenance. Why each change exists, kept next to the code, the data and the numbers, and put in front of whoever is about to change the thing again. What a reason counts as is decided by what it points at — your own words, a reference, a rule the system applied, or an admitted gap — never by whoever wrote it down.

What is new

The record. A UserPromptSubmit hook keeps what you actually said, verbatim and with its span. Free text can no longer be filed as something you stated. Rules R0–R6 fill in the reasons that follow from the code itself, so the common case costs nobody a sentence.

The check before the change. A plan is checked twice: against the history of the thing it is about to touch, and against what that thing feeds. Findings are recorded and shown, never blocking. What was surfaced and what actually changed a plan are two different tables, and neither is derived from the other.

/ledger. Ask in words why something is the way it is, or whether an alternative was ever tried. Code finds the candidates, computes the facts and computes the absences; a model may only restate that table; then code deletes any sentence that cites nothing, cites an id that does not exist, or carries a number the table does not state — and counts every deletion, so a trimmed answer never reads like a complete one.

Things that were never in the code. One sentence puts an external system, a business rule, a stakeholder decision or a hand-made figure into the same graph, with the same history and the same views. Nothing enters until you confirm it in your own words.

Integrity. provledger verify walks the three hash chains and checks them against git-note anchors written when a plan closes. provledger export produces a bundle by whitelist; personal records never leave, enforced in code rather than by convention.

Numbers in decks and reports. A figure's identity is its data source, and a file is only where it appeared. Anchors are placed by hand, can be reported lost, and are never silently moved.

Held back on purpose

Two model-driven features are implemented and switched off. The identity arbiter and the judge for changes to decks and reports must each clear a numeric gate before they may ask anyone anything, and on this release's evaluation neither did (consistency 0.60, accuracy 0.60 for the judge). A refusal is a result, not a failure. Identity is decided deterministically in the meantime, and ambiguity is reported as ambiguity.

Also in this release

The repository reads in English throughout, and the Chinese interface lives behind ?lang=zh. docs/KNOWN-ISSUES.md publishes the confirmed defects and standing limits with their workarounds. A long list of fixes to the model call path, including one that made provledger ask report "no model" at a model that was right there.

1831 tests across eight suites, run separately. Full detail in CHANGELOG.md.

Install

claude plugin marketplace add yizhao95/prov_ledger
claude plugin install provledger@provledger