Batch acceptance invocations over enumerated, manifest-pinned selections - #42
Merged
Conversation
`ledger accept --group|--set` takes a selection `ledger show` already resolves and signs it in one invocation. It files N acceptance records, one per decision, each on its own version hash under `scope: version` — never one record covering N decisions. What is batched is the invocation; the binding is untouched. The plan is built from `show`'s screens rather than a second traversal, so `show --group X` and `accept --group X` cannot resolve to different sets — asserted by test, but structural first. The selection is enumerated once into explicit `(decision, hash, standing)` triples and pinned by a manifest digest under its own domain-separation prefix; a dry run is the default posture and prints it; signing requires `--confirm <manifest>`, a value only a dry run can produce, so confirm-without-a-prior-read has no spelling. Standing is inside the digest, not just the pairs: an acceptance filed between the read and the run changes what the run would *do* without changing which decisions it covers, and a signature must not absorb that silently. A mismatching run refuses and names what moved — recovered by replaying the append-only log's own prefixes until one hashes to the confirmed manifest, so nothing is cached between the two invocations and nothing can go stale. Held is derived from the record, never hand-listed: a basis pointer in the `indeterminate:` scheme says the ground is unsettled, and such a member stops the whole run by name rather than being signed around. On this repo's store that is `dec/ddd/interceptor-not-extension`, caught without the worksheet having to say so. Forked chains block the same way; an entry the actor already signed is enumerated and labelled, not dropped. No batch-only leniency: each acceptance runs the same delta-gate a single accept runs — once alone so a failure names its decision, once combined so the write is checked as the act it is. `refusal_check` gained a baseline-supplied variant so the N+1 passes reuse one baseline; the rule and the comparison stay in one copy. Identity and committer semantics are unchanged: git config, no `--as`, L009 on every record. No format change, no new gate class, no `--all-pending`, no auto-confirm, and the L1 precommitment tension is left exactly where it stands — this verb signs present members, enumerated at run time. The ruling is filed as a `ledger-design` entry (`dec:hafeok.ledger/01KZXJX693301CZSY4XNP643XY`), allocated criterion at pr stage against the tests that check it, awaiting the principal's acceptance. No acceptance is filed by this session. The PRD and the acceptance worksheet record the verb as the second interim affordance standing in for L5's workbench, next to the PR-review surface, so it does not quietly become the answer. Gates green: cargo t (46 binaries), the documented clippy gate, ddd validate (240 entries), ledger verify, cargo xtask check (0 errors), ddd diff-contracts over HEAD (12 events, 0 undischarged). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FwsbsqP5nqVPjkjZFzmH7b
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implement
ledger accept --group|--setto batch the invocation over a selection while preserving the binding semantics of acceptance. Each decision in the selection receives its own acceptance record signing its own version hash, exactly as a singleacceptwould produce.Summary
This change adds batched acceptance as an ergonomic affordance for signing multiple decisions in one pass. The key insight is that batching the act (filing multiple acceptances together) is different from batching the signature (one record covering multiple decisions). This implementation preserves the latter's semantics: every acceptance is scoped to its own version hash, and the selection is pinned by a manifest digest that must match between the read (dry run) and the write (confirmation).
Key Changes
New
batchmodule (ledger-core/src/batch.rs): Core abstraction for enumerated, pinned selectionsPlanstruct: enumerated members with their standing (signable, held, already-signed, forked) and a manifest digestOutcomestruct: result of an invocation (dry run or signed, with optional refusal)Memberstruct: one decision with its version hash, state, and standingmanifest()function: domain-separated digest over(selector, actor, members)triples including standingNew
batch_driftmodule (ledger-core/src/batch_drift.rs): Drift detection and recoverylocate(): recovers the selection a manifest was taken over by replaying log prefixeschanges(): names what moved between two selections (entries added/removed/revised/restated)New
batch_rendermodule (ledger-core/src/batch_render.rs): Terminal renderingrender(): formats the enumeration, tally, and verdict as a pasteable blockOutcomestruct so--jsonand text cannot driftNew
holdmodule (ledger-core/src/hold.rs): Held entries that block batchesHoldstruct: a basis pointer in theindeterminate:scheme and the reason it blockshold(): derives holds frombased_onpointers, not hand-listedNew
group_acceptmodule (ledger-core/src/author/group_accept.rs): Batched acceptance logicaccept_group(): enumerates the selection, pins it with a manifest, and optionally signs it--confirm <manifest>signs only if the store still enumerates to that manifestCLI adapter (
ledger-cli/src/commands/sign.rs): Two shapes ofacceptover one meaningAcceptFlagsstruct: decision (single) vs. set/group (selection) vs. confirm (manifest)--confirm <manifest>signs the selection if it still matches that manifest--jsonoutput for both shapesSelector canonicalization (
ledger-core/src/show.rs):Selector::to_string()for manifest digestsComprehensive test suite (
ledger-cli/tests/accept_group.rs): 380 lines of end-to-end scenarioshttps://claude.ai/code/session_01FwsbsqP5nqVPjkjZFzmH7b