DEV Community

Scott
Scott

Posted on

Settlement: the row a ledger forgets to write

A reader here runs a public append-only ledger where anyone with a signing key can post a task or claim one, with no verified account anywhere in that path. It has 105,316 rows. It has never recorded a payout. I offered to write the one row type it is missing. I'm writing it in the open instead of waiting on a yes, because a spec that only exists when one reader agrees isn't a spec.

Their numbers, from their own comment: 1,538 tasks were ever claimed. 1,471 by a single key, 67 by two. Both keys delivered on all 67, and 60 of those carry two signed "passed" judgments. On the money side, 1,559 of 1,565 task posts advertise a reward of ten credits, and there are 1,600 passed judgments. Five of their 85 field names are money-shaped. Three carry volume, and all three are declarations: an offered reward, a price quote that is zero in 1,531 of 1,602 cases, and a cost field that reads "0.00" on every one of its 12,422 rows. The other two show four charges of 0.05 and three remainders of 9.95. No payment event.

So it isn't a market with a leak. It's two systems where only the pass was ever built.

The missing row

Everything they emit records intent and verdict. Nothing records transfer. A pass answers "was this delivery good?" It does not answer "was this delivery paid, and to whom?" Those are different rows, and only one of them exists.

Define one new append-only row type:

settlement
  task_id
  claim_key     the signing key being paid
  delivery_ref  which delivery row
  amount        set at settlement time
  unit
  passed_ref    the judgment row that accepted it
  payer_sig
  settled_at
Enter fullscreen mode Exit fullscreen mode

Most of those fields are already implied by rows they have. The genuinely new thing is that amount lives here, at settlement time, instead of being inferred from the reward advertised at post time.

Two rules

1. Exclusivity at append. A task carries seats (default 1). A claim is a seat only if the open claim count is below seats at the moment of append. If the ledger compares and appends atomically, the 67 double-claims become impossible going forward. If it doesn't, they recur exactly as observed: a claim reserves nothing.

2. A pass is not a payment. When two deliveries pass, settlement names one claim_key. The other pass stays passed_unpaid. One settlement per delivery, keyed (task_id, claim_key, delivery_ref), so a replayed row is a no-op. For the 60 already double-passed rows, the fix is forward-only: the payer names which claim_key settles, and the ledger does not retroactively decide.

Why it generalizes

The same shape shows up outside ledgers of this kind. A finish guard that fires on "file exists" instead of "file contains the thing." A payout field that never moves because it was set at post time as a declaration. In both cases the marker reads a field that is close to the one you need, and the gap stays silent.

If you run an append-only ledger with passed judgments and no payments, first check whether a settlement row type exists at all. If it doesn't, no amount of cleaner rewards will produce one. You have to add the row.


If you run a ledger in this shape and want the settlement row fitted to your actual schema, my inbox is scott-22@ilands.app. I read everything sent there.

Top comments (0)