seller fee stage 2a: the seller node remits the platform fee automatically after each collect and retries with backoff while it runs; maxplayer seller fees remit is inspection and recovery - #979
Merged
Conversation
|
Someone is attempting to deploy a commit to the MakePrisms Team on Vercel. A member of the Team first needs to authorize it. |
maxplayer seller fees remit — pay the accrued platform fee to the platform Lightning address by an explicit, confirmed, idempotent commandmaxplayer seller fees remit is inspection and recovery
maxy-player
pushed a commit
to maxy-player/maxplayerai
that referenced
this pull request
Sep 8, 2026
…nal head, as in round 7) body-r8-final.md (65,217 B, sha256 d5bfc1958fadbcd8ef4255d2dcc5fee21b537661dc88d4fe64d0a23067f80e1b) is the body posted to PR MakePrisms#979 at 2026-09-08T10:53:59Z; read-back 65,218 B sha256 3f862b0828314a585ffca754e4d3c48f166950aeed71081d9e94571d477dcd2a (GitHub trailing newline). CI on 4714623: run 34217843662 completed success 2026-09-08T11:07:41Z, all seven checks. No .rs, docs or Cargo change: executable head stays 1a5c7c5, final head stays 4714623.
maxy-player
pushed a commit
to maxy-player/maxplayerai
that referenced
this pull request
Sep 8, 2026
…ame credential_proxy flake x3, 44 fee_remit ok each), §5 CI: no run exists at 08c476c because PR MakePrisms#979 conflicts with main since d55ceaf
maxy-player
pushed a commit
to maxy-player/maxplayerai
that referenced
this pull request
Sep 8, 2026
… 60,969 B sha256 635b77e3…, live read-back identical (file + one trailing LF); CI recorded BLOCKED - INTEGRATION
added 25 commits
September 8, 2026 14:41
…latform fee by an explicit, confirmed, idempotent command Stage 1 (MakePrisms#973) made the platform fee a journaled number. This makes it payable, by ONE operator-run command and nothing else: - `platform_fee::PLATFORM_FEE_ADDRESS = "maxplayer@agi.cash"` (Josip, 2026-09-07): one pub const beside the rate, deliberately not seller-configurable; every remittance journals the literal paid. - `lnurl_pay` (new, `wallet`-gated, reqwest only): LUD-16/LUD-06 resolution, fail-closed with a typed error per hazard (non-200/non-JSON, tag, callback host/scheme/credentials, bounds, `pr` amount mismatch, expired invoice, any http://). Millisat↔sat conversion in three named functions; the adversarial amount-parse gate is a test. - `wallet_ops`: `melt_quote_async` (quote without paying, for the dry run) and `melt_status_for_invoice_async` (reconcile an interrupted attempt); `MeltOutcome.quote_id` so a settled row names the mint's quote. `melt_async` is the ONLY payment path; its body is unchanged. - store v10 (additive): `fee_remittances` + `receipts.remittance_id`; `plan_remittance` pins the unremitted receipts in the same transaction that journals the attempt and refuses a duplicate planned record (crossmint_hop precedent, plus a partial unique index); `settle_remittance` keeps them discharged, `fail_remittance` releases them. `accrued_fees` now reports unremitted / remitted / in-flight. - `maxplayer seller fees remit [--dry-run | --confirm]`: dry run by default. `--confirm` pays the unremitted total with the melt fee taken OUT of it (probe the reserve on the gross, invoice the net, refuse if the second quote does not fit), refuses below the destination's minimum with the shortfall, is idempotent, and reconciles an interrupted attempt with the mint instead of paying again. No timer, no sweep, nothing on the payment path calls it.
…fee AUTOMATICALLY after each collect; `seller fees remit` becomes inspection and recovery Reverses the gate the previous commit (3cda4ee) was built to: that commit shipped `maxplayer seller fees remit` as the ONLY path that pays the accrued platform fee and proved that nothing on the collect path reached it. Addendum 1 (maxie, 2026-09-07T09:05Z, after Josip's question in the lane) says a fee the seller has to remember to pay is not a fee: the node remits as a consequence of collecting. This commit is that reversal, on top of 3cda4ee's LNURL client, store and command, which stay. - `fee_remit` (new, `maxplayer-core`, `wallet`-gated): the ONE remit entry point, moved out of the CLI. `remit(store, effects, trigger, now, out)` has exactly two callers — the collect path (`RemitTrigger::Collect`, via `remit_best_effort` → `remit_after_collect_live`) and the CLI (`DryRun` / `Command`). `RemitOutcome` / `Refusal` are typed; threshold refusals (nothing unremitted, below the destination's minimum) are the expected steady state and are never journaled as attempts. Every attempt that meant to pay is journaled with its outcome. - `seller_node/run.rs`: after `collect_receipt` returns `Collected::New` — and only then (`remit_follows_collect`; never `Duplicate`, never `Err`) — `remit_platform_fee_after_collect` starts one best-effort attempt on a plain OS thread (the wallet's `*_blocking` wrappers refuse a Tokio context; a 20 s LNURL timeout must not stall the wrap loop). The receipt is written and the job is paid before it starts; every outcome is logged, none propagated. The "Accrued, never remitted" comments are rewritten. - `[platform_fee] auto_remit` (`MAXPLAYER_PLATFORM_FEE__AUTO_REMIT`), default true: the operational off switch. It stops the automatic ATTEMPT and nothing else — accrual is untouched, the balance stays owed and visible, `--confirm` still pays, and the table has no key for the rate or the address (`deny_unknown_fields`). - store: `fee_remit_attempts` (additive, `CREATE TABLE IF NOT EXISTS`, still v10): trigger ('collect'|'command'), the balance the attempt saw, outcome ('paid'|'refused'|'failed'), detail, and the `fee_remittances` row it planned, if any. `record_remit_attempt` / `recent_remit_attempts`. The one-planned-row index + `plan_remittance`'s in-transaction checks are what make two concurrent collects pay at most once (tested with two connections). - `maxplayer seller fees remit`: dry run by default; now also prints whether auto-remit is on and the recent attempts with outcomes (reconciliation); `--confirm` forces an attempt now. - Gates (addendum §5): 2a — call-site greps; 2b — a test on the real collect write where the remittance fails twice and the receipt, the paid state and the owed balance are intact; 2c — two racing attempts, two sqlite connections, exactly one remittance and one melt. - Docs: SELLER-QUICKSTART platform fee section rewritten for the automatic path; platform_fee and lnurl_pay module docs no longer claim nothing automatic exists. `wallet_ops::melt_async` is still the only outgoing payment path; its body is unchanged.
…ode's own clock — base 30 s, doubling to a 30-minute cap, full jitter — for as long as the node runs Addendum 1 left a failed remittance waiting for the NEXT COLLECT, which on a quiet node is hours or days and on a seller who stops selling is never. Addendum 2 (maxie, 2026-09-07T10:25Z, after Josip's "keep retrying as long as the seller is running") supersedes that one sentence: the next collect is still a trigger, but the retry mechanism is the run loop. - `seller_node/run.rs`: a remit tick in the live loop's `select!` — a re-armed `tokio::time::Sleep` (not an `interval`, which fires at once on first poll) whose delay comes from the node's ONE `RemitBackoff` after every outcome. The first attempt after boot waits one jittered base delay. The attempt runs on a plain OS thread (the wallet's blocking wrappers refuse a Tokio context) and reports back over a oneshot; while it is in flight the timer arm is disabled, so attempts never stack. It is an arm of the loop, so a requested shutdown or a relay-pool close ends it: nothing is left running after the node exits (gate 2e drives the real loop and proves it). - `fee_remit::RemitBackoff`: base 30 s, doubling on consecutive FAILURES only, capped at 30 min; full jitter — the slept delay is uniform in [0, computed], never the delay plus a wobble, because every seller backs off against the same LNURL host and the same mint and correlated retries turn an outage into a flood (the doc comment says so, so nobody "simplifies" it away). Reset to base on a successful remittance and on nothing else. Below the threshold and a zero balance are NOT failures: they neither escalate nor reset. Non-threshold refusals, `MeltFailed` and effect errors are. `jittered()` is pure and overflow-proof; gate 2d tests the computation, nothing sleeps. - `fee_remit::RemitFlight`: single-flight is now REQUIRED — one attempt in flight per process; the collect thread or the tick that cannot take the permit skips and returns (no queue, no block, no failure). The permit drops on unwind. The store's one-`planned`-row rule remains the correctness argument. - One backoff for the node, shared by both paths: the collect path's outcome moves it too, so a payment there resets it and a failure there escalates it. - `auto_remit = false` covers BOTH paths (§4): the tick arm is disabled for the node's life and the collect path skips; accrual and the journal are untouched; `--confirm` still pays. - Log discipline (§5): the first failure of a streak in full with its error; later attempts one line each with the streak length and next delay; one line on entering the cap; a success after a streak says how many attempts failed and how long the fee sat owed. Threshold outcomes are verbose-only. - `RemitTrigger::Retry` / `fee_remit_attempts.trigger = 'retry'`: the CHECK is widened to three values. The table is new in v10, which has not shipped, so this is its first definition on every store that will ever have it — no table is rebuilt; SCHEMA_VERSION stays 10. - Gates: 2a — the entry point has exactly THREE callers (collect path, retry tick, CLI); 2d and 2e added; 2b, 2c and every brief §4 gate stand. - Docs: SELLER-QUICKSTART, platform_fee, home (`auto_remit`), seller_fees, fee_remit module docs no longer say "the next collect tries again". No test asserted "no retry until the next collect", so none was replaced; the two summary strings that said it were rewritten. `wallet_ops::melt_async` is still the only outgoing payment path; its body is unchanged.
…d ceiling on the melt, enforced at the moment of spending; a planned remittance is owned under a lease; shutdown drains Finishes the round-1 DENY at ebb4f30 (verdict rungs B, D, E, G, H, I), on top of the WIP commit a68511a that was cut mid-turn. B (2f): `wallet_ops::melt_within_async` — the existing gated melt with an optional `MeltCeiling` {max_debit_sats, invoice_sats, planned_quote_id} checked against the quote the mint raises AT PAYMENT TIME, before `prepare_melt` selects a proof. A reserve that grew between the plan's estimate and the payment is refused as `MeltExceedsCeiling`; the remitter journals a failed attempt, releases the row, and the accrued balance is unchanged. `melt_async` is now the `None`-ceiling call of the same function; one melt implementation. D/G (2g): `fee_remittances.owner` + `lease_until_unix` (REMIT_LEASE 300 s). Reconciliation releases a planned row only when its quote is terminal (FAILED / EXPIRED), or the row is this process's own, or the owner's lease has run out — never on UNPAID alone. The payer re-validates ownership (with SPEND_MARGIN of lease left) immediately before spending; the melt's ceiling and this check are one pre-spend gate. The test fake issues distinct invoices per owner. Reconciled settlements persist the paying quote id, its reserve, and `settled_by = 'reconciliation'`; the actual melt fee is NULL there because the cdk 0.17 wallet MeltQuote carries no fee_paid, and the row and the read-out say so. SCHEMA_VERSION 10 → 11, additive, old rows read as unowned. E: boot delay = base + uniform [0, base] (30–60 s, never zero). Shutdown DRAINS: the loop stops scheduling, then waits up to REMIT_DRAIN_BOUND (60 s) for the in-flight attempt, logging one incident line if the bound elapses. A shared outcome (collect success or failure) re-arms the live retry timer via a Notify. One logging policy across both paths; the cap transition is one line. H: platform_fee.rs, lib.rs, seller_fees.rs usage, SELLER-QUICKSTART rewritten against this head. I: rustfmt applied to this delivery's own hunks only (the crate's pre-existing drift is left as found); the two clippy findings on added lines fixed.
… store and remit path; tests not yet updated
…e v11→v12 migration test; not yet compiled
…r held under re-arm; tests
…lt_blocking names, quickstart
…e_remit.rs and store.rs); the repo's pre-existing fmt debt is left as it is
…t compile against the v12 store: non-exhaustive match on RemittanceState, three test literals missing spending_since_unix); render test covers the new state
…pay-by-quote-id step (pay_melt_quote_async), status by quote id, MeltEstimate carries the quote's expiry; operator melt path unchanged in behaviour
… fence; clock read inside the admit transaction; fail_remittance replaced by release_remittance with reason predicates (zero rows = hold); tests not yet updated
…ads the clock inside; release predicates hold on the wrong reason; v12→v13 migration test (unbound spending row keeps v12's release)
…e / pay_melt_quote by id / melt_status_for_quote, AfterQuote + AfterDecision hooks); reconcile_decision returns the conditional release; bound-quote UNPAID terminal only past expiry + margin; remit_inner not yet rewired
…ling before the fence, fence binds the quote with the clock read inside the store, pay by quote id (never re-quote), post-fence failures leave the row spending; reconciliation asks by bound quote id and releases conditionally (zero rows = hold); lib compiles, tests not yet
…e Arc both sides read, test mutates), payment quote distinct from estimate, pay by id refuses non-UNPAID/expired quotes and marks PAID, status by id, AfterQuote/AfterDecision gates; fee_remit tests not yet updated
…rry their transition; bound-quote UNPAID terminal at expiry + margin (961 yes, 960 no); UNKNOWN holds; unbound (pre-v13) spending row keeps v12's release; core lib-test compiles
…ound quote: reconciliation by id, expiry + margin (2061 releases, 2001 holds), ceiling refusal before the fence; fee_remit:: 25/25
…s the bound quote on a SPENDING row; stale melt_within_blocking names → pay_melt_quote_blocking (lnurl_pay, seller_fees, fee_remit module doc)
…und quote checked by id while the estimate expired, (b2) bound quote expired past the margin released on Q and its owner refuses to pay it, (c) paused after quote and past lease, (B2) stale lease-release decision cannot revoke a later admission (three gates, shared clock), (d) full path FAILED/UNPAID-live/PENDING/UNKNOWN with melts counted; PauseAt::AfterQuote
…-exact; (b2) raises Q with mint expiry 130 at clock 60 so the fence admits it and the shared clock at 200 makes it terminal; (d) confirm run after a release has nothing in flight to ask about
…, the conditional release, every 2g test and the one-debit claim's bound (host clock vs mint clock beyond SPEND_MARGIN); drain comment qualifies planned vs spending; quickstart one-debit paragraph reworded as what the tests prove plus its bound
…e.rs and seller_fees.rs (27 hunks; the 68 pre-existing rustfmt diffs in those files are left as they are, as in round 3)
added 24 commits
September 8, 2026 14:42
31deed8; raw logs to /tmp/w-remit-r10/logs. No .rs change.
…denominators, census, same-model, evidence row, URL inventory) + gates-summary skeleton. No .rs change.
…gs preserved uncommitted in the seat folder, cmp-verified copies; runner not restarted). No .rs change.
…en, 47/47 --exact, fmt 0 lines of ours rewritten, clippy INSIDE=1 (fee_remit.rs:5642 identity_op in record 45's assert — fix owed, moves the exec head), money-path run 1 credential_proxy flake only, CI list (68fa0eb r9-pin race flake = the §2 defect; 1df95d8 seat-claim flake not ours). No .rs change.
… fee_remit). No .rs change.
…tity_op; test only)
…only, 44/44 fee_remit) + runner b script for the second exec head 08c476c. No .rs change.
…clippy INSIDE 1 -> 0, fmt 0 of ours; money-path 1/3 (credential_proxy flake). No .rs change.
… credential_proxy flake, 44 fee_remit ok
…ame credential_proxy flake x3, 44 fee_remit ok each), §5 CI: no run exists at 08c476c because PR MakePrisms#979 conflicts with main since d55ceaf
…227 lines, 63,299 B), H4/exec-delta/census re-measured at 08c476c (census 83, r9's 41 does not reproduce — flagged)
…read at :5454), H3 record 37 is a new drift fixture / 40 is the reserve-shrink success, same-model claim corrected + dated, evidence table 69 rows -> 68
…body 65,283 -> 58,407 B, room for the round-10 sections (permalink sha filled after this commit)
… race assertions, 0 added #[ignore]/#[should_panic])
… records 45/46), D/2c (records 47/48, 0 added #[ignore]), H1 (16 comment sites relocated by text match at 08c476c + store.rs:2274 + quickstart :1133-1146)
…, money-path x3, per-file diffstat, and CI as BLOCKED - INTEGRATION per maxie ruling a99f2572 (absent runs are not green, not failed; 1df95d8's real red named as red)
…d sites), 2a (3 origin callers, spawns/clocks by brace depth not a line threshold), 3 (46 hits, ONE non-test: platform_fee.rs:68), 4 (prepare 1 / cancel 3 / confirm 1)
…8c476c (two exec heads named, runner never restarted, 58 logs cmp-verified and uncommitted per ruling b0b39e5e)
…t only (88 changed, 2 ambiguous) + full resolve check: 225 citations, 0 out of range; the five effect call sites the remap produced match the ones I read by hand
…ts DISCHARGED by 31deed8 (phrase now 0 occurrences), refusal-count citations re-measured (:3638/:5851/:5945, format string :1559), credential_proxy flake added 3/3, recovery path + force-release + BudgetGate still owed
… 60,969 B sha256 635b77e3…, live read-back identical (file + one trailing LF); CI recorded BLOCKED - INTEGRATION
…ot a 32 MiB race The declared-over-cap test asked the right question through a client that could not hear the answer. `reqwest` was handed a sized 32 MiB + 1 body, so it began streaming while the proxy — which decides from `content-length` alone — had already answered `413` and closed. The write error then raced the response read, and `send()` surfaced whichever arrived first: `413` on a good day, `ConnectionReset` on a bad one. The refusal was never in doubt; only the transport was. So the test now drives the socket itself: it declares the over-cap length, sends the headers and NOT ONE body byte, then reads the status line under a bounded timeout and asserts the real `HTTP/1.1 413`. There is nothing left to race, and a proxy that went back to waiting for the body would fail on the deadline instead of hanging. The no-upstream-contact half is strengthened while it is open. It used to read `!stub.is_finished()`, which only proves the stub never SERVED a connection; a dialled connection completes in the kernel's backlog whether or not anything accepts it. The upstream here is a listener nothing accepts from, and after the `413` is in hand the test proves an `accept()` finds nothing waiting — no connection was opened at all. Test transport only: no runtime, cap or fee behaviour is touched, and the two adjacent cap tests (under-cap relay, undeclared chunked cut-off) are unchanged and still pass.
maxy-player
force-pushed
the
feat/seller-fee-remit
branch
from
September 9, 2026 00:33
d17fd33 to
c53705a
Compare
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The 196 files under reports/ are this delivery's round-by-round working notes, gate logs and evidence tables. They are agent artefacts, not product, and do not belong in the merged tree (owner's ask, via maxie 2026-09-09). Preserved off-branch BEFORE removal and proved so: every one of the 196 blobs extracted with `git archive c53705a` and each file's sha256 compared against `git cat-file blob <oid>` at that head plus its `git ls-tree -l` size — 196/196 identical, 0 mismatches, 13,125,086 bytes, at /Users/forge/forge/v2/worker/reports-archive/979-c53705a1/ with MANIFEST.json (52,812 B, sha256 208929d5fd359e0ad03bf7eb9ccfced0b6e4a6ff35cd0bceb91993afae8dc050) and SHA256SUMS.txt (22,477 B, sha256 4c8ba34a28fa5a38d76f5a07898d0c7356d8af60fee0ec4c6a7a39caee343484). Deletions only: no product file is touched and no history is rewritten.
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.
Seller fee stage 2a — the platform fee becomes payable and the seller node pays it automatically, with a retry loop behind it; the accrued gross is a hard ceiling on the spend; a planned remittance is owned under a lease, passes ONE compare-and-set fence in the store immediately before the melt, and that fence BINDS the one mint quote the owner may pay; every release is a conditional state transition carrying its reason's predicate; and — from round 5 — a spending row bound to a quote is released by nobody, on no clock: it settles when the mint reports that quote PAID and is HELD otherwise, so a second attempt is never admitted while a payment may still land. Final head:
a0aafe8cd5996abe68cf22c7423fa0940e7efaac— the removal of this delivery'sreports/working notes from the branch, deletions only, no.rsbyte touched (executable head unchanged atc53705a192ec4f8dac19ad1177e03561ba518929, the reviewed head published to this branch on 2026-09-09; the round-9 head240240c3f26bd35c3a8c5a3d70229f9730a0a221and round 10's08c476cadc012d0e12546cf2ceb0a729534178fcare preserved below as historical evidence), basea6217328(stage 1, #973). 105 commits ata269952(a pre-rebase count); at the published head the delivery isd55ceaf..c53705a1= 141 commits with zero merges of ours (a6217328..c53705a1also shows 7 merge commits, every onemain's own, inherited by the rebase), all authored and committed asw-seller-fee-stage2a-remit <worker@forge.local>exceptec56d0c, whose author/committer NAME isw-ecash-mutual-credit(emailworker@forge.localunchanged): the shared checkout's.git/confighas helduser.name = w-ecash-mutual-creditsince 2026-09-05T03:07:27Z and that commit omittedgit -c user.name=… -c user.email=…, falling through to it — disclosed below, not amended (append-only) — the commit list, round by round with one line per commit, iscommit-history-a6217328-240240c.md(moved out of this body for the character cap; it ends at240240c, the executable head, and the reports-only commits after it up to this body's pin — the final head). The round-5-to-10 sections below are written against240240cand08c476cand are preserved as historical evidence; the head pinned, reviewed and published isc53705a1. Append-only throughd17fd33, then ONE authorised integration rebase (2026-09-09).ebb4f30,77cce72,534247b,6fc77e1,6fd13df,19f30d3,da0ee92and4714623(the eight graded heads) are ancestors of240240cand of the pre-rebase graded headd17fd33ee1e187fdfec6c7a6cc8581c812bb356b. None of them is an ancestor of the published head, because the delivery was replayed ontomain:git merge-base --is-ancestor <sha> c53705a1is false for all eight and for240240c,08c476candd17fd33— measured, not assumed. Every pre-rebase commit is retained and still resolves on the fork (gh api repos/maxy-player/maxplayerai/commits/<sha>returns293e0e6a…,fde92e73…andd17fd33e…, checked 2026-09-09T02:12Z), so every permalink in this body still opens.⛔ This is the product's first seller-fee payment code (operator melts —
maxplayer wallet melt— existed before it).allow_real_mintsdefaults to true, so on a default install this pays real sats to a real Lightning address from the seller's real ecash.Heads (addendum 9 §5, addendum 10 §5). Executable head being reported and published:
c53705a192ec4f8dac19ad1177e03561ba518929. Its lineage, each step under its own order and gated before the next:31deed8c68bbacca97ec1a7bf34d1c6ebc408736(steps 1–3; CI-green 7/7, run 34275523983) →08c476cadc012d0e12546cf2ceb0a729534178fc(one.rscommit,1 file, +2 / −3: record 45's assert literal, killing the one clippy diagnostic inside our added lines — this corrects the08c476cbb0b0…this body cited before, which resolves to no object) → reports-only commits tod17fd33ee1e187fdfec6c7a6cc8581c812bb356b, the graded pre-rebase head →d56cc163e406d32227b2eeaf4c6634c158cc0808(integration: 140 commits replayed ontomainatd55ceafe287b97481eefbd304574ad1db6e93d3a) →c53705a1(one test-transport repair,1 file, +57 / −17, every line inside a single test fn).d55ceaf..c53705a1= 141 commits, 0 merges. Every section below headed08c476cis pre-rebase historical evidence; what supersedes it — the gates re-run atd56cc163andc53705a1, and published CI — is in the CI section. Pre-rebase,git diff --name-only 08c476c..HEAD | grep -v '^reports/' | wc -lwas 0 — no.rs, docs or Cargo change after the executable head, the only delta being 11reports/w-remit-r10/files (+682 / −3).Every gate below ran at the pre-rebase executable head
08c476c(re-run after the rebase and after the repair — see the CI section), serially, byreports/w-remit-r10/run-gates-r10b.sh(committedc94dbd4), launched 20:52:19Z as pid 53734; it finished on its own at 21:05:40Z withSTAGE3 money-path x3 done — ALL DONE, was never restarted, and no second runner was launched. Each log's line 1 isgit rev-parse HEAD— the reports-only commit the runner happened to be sitting on (c94dbd4,a44c0beorff6e47a) — and line 2 isexec head 08c476c; .rs diff to HEAD:, empty in all 58 logs, so every one of them is.rs-identical to the head being reported. Why there are two executable heads this round:31deed8is the last code commit of steps 1–3 and is the head CI last ran on (run 34275523983, success 7/7). Its gate set was clean but for one clippy diagnostic inside the added lines —identity_opon record 45's assert literal1 + 0 + 1 + 1atfee_remit.rs:5642.08c476cis the one commit that fixes it (1 + 0 + 1 + 1→3, message “invoice 1 + Lightning 0 + ACTUAL input 1 + swap 1 = 3”):git diff --numstat 31deed8..08c476c -- '*.rs'= 1 file, +2 / −3, a test assertion literal, no money code and no changed expectation. Because a.rsbyte moved, the entire gate set — three suites, fmt, clippy, all 47--exactrecords and the money-path trio — was re-run at08c476crather than carried over. Raw logs live in the seat folder, uncommitted, per the evidence-location ruling;reports/w-remit-r10/gates-summary-08c476c.mdcarries every result line with each log's bytes and sha256. Correction of round 7's report (addendum 9 §4.3): the previous body called the2d802a3core, CLI and money-path suites "green". At2d802a3the core and CLI suites passed; the money-path trio did not — 1423/2, 1422/3, 1423/2 (the disclosed out-of-diff flakes,money-path-ci-{1,2,3}-2d802a3.loginreports/w-remit-r7/), as the money-path section already stated. The "green" summary is withdrawn; the FAILED counts stand.§5 statement — what moves money, under which flag, what the default does, what bounds it
maxplayer_core::fee_remit::remit(fee_remit.rs:946→remit_inner:1045), and inside it exactly three effects on one trait —RemitEffects::prepare_melt/confirm_melt/cancel_melt(trait:348/:356/:360; live impl:453/:475/:487) — with one live call site for prepare (:1786), one for confirm (:1931) and three for cancel (:1811post-swap arithmetic refused,:1864fence lost,:1915pay-time margin). The round-8 body counted two, omitting the arithmetic cancellation round 8 itself added (verdict at4714623N4 item 1); the census is ofeffects.cancel_melt(call sites inremit_inner: three, no other. The live impl goes towallet_ops::prepare_melt_payment_blocking(wallet_ops.rs:1577; theallow_real_mints/mint_allowedgate:1590) →prepared_melt_thread(:1637, a dedicated OS thread that owns the wallet and the prepared melt until a verdict arrives): the quote's stored amount and reserve are checked against the ceiling (:1678,MeltExceedsCeiling:56), then the SDK'sWallet::prepare_meltruns — in the pinned CDK 0.17.2 source (cdk-0.17.2/src/wallet/melt/saga/mod.rs:286–460) that step posts no proof-bearing or fee-bearing request: it reserves the quote and the proofs in the wallet's own store (reserve_melt_quote,reserve_proofs,add_saga) and reads keysets through the SDK's metadata cache, which may GET the mint's keysets and info when the cache is unpopulated or past its TTL (keysets.rs:44–51→mint_metadata_cache.rs:366: cache → database →load_from_mint); the round-8 body's "local only … nothing is posted to the mint" was false in that respect and is withdrawn (N4 item 2) — then the active keyset'sinput_fee_ppkis read (:1709, before the gate; a failed read cancels the preparation:1716and refuses) andMeltCeiling::admits_confirmable(:479, called:1733; round 9 — it replacesadmits_total) takes the bound on the SDK's prepared figuresinput_fee()/swap_fee()(melt/mod.rs:428–450) and on the post-swap recomputation of the input fee: any shortfall ⇒PreparedMelt::cancelfirst (prepared.cancel():1782;melt/mod.rs:673→ saga:817–831, best-effort local compensation: proofs back to Unspent, quote released) and then the typed refusal —MeltExceedsCeiling(:56;:1747),MeltWouldNotConfirm(:89;:1756) orMeltTotalExceedsCeiling(:73;:1771); under it ⇒ aPreparedMeltPayment(:606) whoseconfirm(:629) isPreparedMelt::confirm(melt/mod.rs:515→confirm_prepared_melt_with_options:831–887) — the pre-melt swap (swap_no_reserve:687–697) andclient.post_melt(:907–911) live there and nowhere else on this path — and whosecancel(:645) or drop (:676) reverts. The round-4–6 pay-by-id functionpay_melt_quote_async(:1352; ceiling:1396;prepare_melt("e.id)→confirm():1419/:1427, preserved context fromd30e690c) is retained with its tests and has no live caller at this head (pay_melt_quote_blocking:2256is called by nothing outside tests). The operator'smaxplayer wallet meltpath (melt_async:1285→melt_within_async:1304, which raises its own quote and keeps its invoice-plus-reserve check) is untouched in behaviour and out of scope (addendum 8 §6). No second payment implementation; the payer never raises a second quote for a row it holds.MeltCeiling { max_debit_sats: gross, invoice_sats, planned_quote_id }(wallet_ops.rs:454) carriesadmits(:468, invoice + reserve) inremit_innerright after the payment quote Q is raised (fee_remit.rs:1576) and again in the wallet thread beforeprepare_melt(wallet_ops.rs:1678), thenadmits_confirmable(:479;total_debit:507, saturating; round 9 —admits_totalno longer exists) on the prepared melt's four parts and the post-swap recomputation. Step 1c,confirm_would_succeed(fee_remit.rs:2494, called at:1808): from the SDK's prepared figures and the active keyset'sinput_fee_ppk(read in the wallet thread before its gate,wallet_ops.rs:1709, viaactive_keyset_input_fee_ppk:1533; stored onMeltPreparation:582) it computes the post-swap target = invoice + reserve + prepared input fee, that target's binary denomination split, the actual input fee on that split (fee_forwallet_ops.rs:311= ceil(ppk × count / 1000), the pinned crate'sfees.rs:35–48;binary_split:318,post_swap_figures:331,confirm_bound:396— the arithmetic lives inwallet_opssince round 9), and requires target ≥ invoice + reserve + actual fee and invoice + reserve + actual fee + swap fee ≤ gross — exactly what CDK'sconfirmchecks after its swap (melt/saga/mod.rs:678target,:704–712recompute +InsufficientFunds). Either failing ⇒ the prepared melt is cancelled first (local,effects.cancel_meltfee_remit.rs:1811) andrefuse_before_fence(:1546) runs: one printed line (:1559—REFUSED before spending — {reason}; ceiling {gross} sats; nothing left the wallet; the row stays planned and the next attempt re-quotes.), attempt journaledFailed, and the row stays Planned — unbound, owner and lease intact, receipts pinned, nothing written to it; the next attempt's reconciliation releases it as its owner's earlier attempt and re-plans (addendum 10 §2; round 8 released itOwnPlannedand printed two lines — verdict at4714623§3.3). Nothing posted, no Spending row exists to hold. Spend order inremit_inner(:1045): quote →admits(:1576, reserve-only) → re-plan once when the live reserve differs from the estimate's (replan_remittance:1714, oneRe-planned:line:1737; round 9, addendum 10 §1.4) → margin (:1763) → prepare + the wallet'sadmits_confirmable(:1786) →confirm_would_succeed(:1808) →after_quote(:1835) → fence (admit_remittance_spend:1843) → pay-time margin (:1910) → confirm (:1931); a fence or pay-time-margin refusal cancels the prepared melt first (:1864,:1915). Planning is fee-aware and confirmable (plan_confirmable_invoice:2455): it searches down from gross − reserve for the largest invoice for which the same two inequalities hold under the estimate quote's swap fee and ppk (gross 20, reserve 2, 1000 ppk, one 32-sat proof ⇒ invoice 13, paying at 19 — not 14, which the SDK would refuse after its swap); a gross for which no invoice fits isRefusal::FeesDoNotFitat planning (variant:560; raised:1285,:1367,:1383). Ceiling meaning unchanged — gross accrued-unremitted, every cost comes out of it (Josip's ruling; no rate change). What the mint's fee reserve bounds (N4 item 7 / N3, addendum 10 §3, landed in round-9 commit2521a44): the reserve is the ceiling on the Lightning fee the mint may keep (NUT-05) and nothing else — the proof input fee and the swap fee are charged separately and bounded separately by the arithmetic above, and the SDK's inclusivefee_paid(Lightning + actual input fee) can exceed the reserve by that input fee. The source says so where round 8 called it a bound on the whole fee:wallet_ops.rs:292–295(MeltOutcome::fee_reserve_sats),:528–531(MeltEstimate); the PAID-reconciliation line printsLightning fee at most {reserve} sats (the quote's reserve)and records the inclusive fee as not observed (fee_remit.rs:1117;fee = None, nothing manufactured); the plan line labels the reserve(bounds the Lightning fee)and each fee quantity apart (:1399,:1405); asserted byinterrupted_after_the_plan_is_reconciled_as_paid_without_a_second_melt(:4091; record 44, new) and the plan-line assertions of records 28 and 30. The debit bound of every two-process test is at most one payment of the planned gross; the fee-bearing tests measure the wallet, not a counter (round-8 section below): (i) pool 32 → 15, delta 17 ≤ 20 with prepared input fee 4 ≠ actual 3 (record 28,:5176; the round-8 body said 2 here and 4 in its round-8 section — N4 item 3); (ii) prepared figures fit, post-swap arithmetic does not ⇒ refused before the fence, pool 32 unchanged, zero swaps, zero melts; planner case pool 32 → 13, delta 19 ≤ 20, one swap, one melt.SellerStore::plan_remittance(store.rs:2101) refuses to plan while ANY remittance is in flight —plannedorspending— inside its ownIMMEDIATEtransaction (PlanRefused::InFlight,:2122), and the partial unique indexfee_remittances_one_planned ON (state) WHERE state = 'planned'(:1075) refuses underneath it. Neither predicate reads a clock. A second process therefore cannot even plan against a balance whose row is held; its--dry-runand--confirmboth end in oneHELD:line.SellerStore::admit_remittance_spend(store.rs:2206; the statement,:2180–2182):UPDATE fee_remittances SET spending_since_unix = :now, spending_quote_id = :q WHERE remittance_id = :id AND state = 'planned' AND spending_since_unix IS NULL AND owner = :me AND lease_until_unix > :now + :margin, oneIMMEDIATEtransaction,:nowproduced by a clock closure called inside the store call after the lock is held (fee_remit.rs:1843–1833). Zero rows changed ⇒ refuse: no spend, attempt journaledRefused(:1859–1869). One row changed ⇒ the row is Spending, bound to Q, and from there only the mint's PAID on Q resolves it.reconcile_decision(fee_remit.rs:788): PAID → settle, whatever the row (:802); then a spending row with a quote bound →Refusal::SpendingHeld, unconditionally (round 6,c7930c3; the hold:812, its comment:821–812): UNPAID however long past its expiry, FAILED, PENDING, UNKNOWN, or a quote this wallet does not know — held, all five as the SAME refusal, so all five render the same singleHELD:line.Settling(:837, after the bound-spending hold) is now only a PLANNED row's (or a legacy unbound row's) PENDING/UNKNOWN hold. No clock, no margin, noTerminalBoundQuoterelease. The store's fence doc says the same since round-9 commit2521a44(store.rs:2199–2202: "Once admitted the row is HELD until the mint reports the bound quote PAID … no terminal-state release, no clock release"); at4714623that comment still said an admitted bound row is released when its quote goes terminal — addendum 5's withdrawn premise, in the added set although round 8 did not touchstore.rs(N4 item 8). The refusal (fee_remit.rs:587, Display:665) is ONE line:HELD: remittance {id} is SPENDING (admitted by {owner} at unix {since}), bound to melt quote {q}; mint {url} reports melt quote {q} {STATE} (expiry unix {e}); {gross} sats of receipts stay pinned to it — a spending row is released by nobody and on no clock; it settles only when the mint reports that quote PAID; an operator decision, not a timeout, resolves it. The owner's own local refusal to pay a bound quote insideSPEND_MARGINof expiry (:1910–1905) is attempt avoidance, not a safety bound (SPEND_MARGINdoc:246; the comment at:1904–1885); the row stays spending and held either way.SellerStore::release_remittance(id, &ReleaseOn, now)(store.rs:2438), oneUPDATEper reason in anIMMEDIATEtransaction, each carrying its predicate (:2449–2474):TerminalUnboundSpending(a v12 row, no quote bound) —spending_since_unix IS NOT NULL AND spending_quote_id IS NULL;TerminalQuotePlanned—spending_since_unix IS NULL;LeaseExpired { now }—… IS NULL AND lease_until_unix <= :now;OwnPlanned { owner }—… IS NULL AND owner = :me. Zero rows changed ⇒ HOLD (Refusal::RowChangedUnderMe,fee_remit.rs:609,:1147).ReleaseOn::TerminalBoundQuote { quote_id }(store.rs:372, SQL:2449) still exists as the store's conditional transition with its store tests, but reconciliation no longer emits it (added-line census:TerminalBoundQuote8, all in store.rs;fee_remit.rs0) — kept, not called, named under "Out of scope" as a decision for the owners.fail_remittance: 0 in the tree.settle_remittance(:2355) unchanged. Lease expiry never touches a spending row.run.rs:7210–7211→:7239); (2) the retry tick, an arm of the run loop'sselect!(:4235→:7316); (3)maxplayer seller fees remit --confirm(seller_fees.rs:418). Both automatic callers are unattended.remit_innerstep 1 (fee_remit.rs:1045onward; the decision isreconcile_decision:788, whose bound-spending hold is:812), asRefusal::SpendingHeld, before it plans anything; behind that, the store'splan_remittancerefuses a second plan withPlanRefused::InFlight(store.rs:2121–2122) inside its own transaction — the layer that closes the race when two runs both saw no row (gate 2c) and is exercised directly, on B's own connection while A is paused inside its payment, in the delayed-confirm test (round 6,ec56d0c). The balance accumulates unremitted behind the held row.maxplayer seller fees remitprints exactly oneHELD:line and exits 3 (REFUSED,seller_fees.rs:38;exit_code_for:429) on--dry-runand on--confirmalike. A melt the mint has genuinely FAILED therefore freezes the seller's remittance until an operator acts — and this round adds no operator command to clear it (no override, per addendum 6 §1.3; the path is owed, below). We chose that cost over the alternative, which was a release rule that CDK 0.17.2 makes unsound (next section).BudgetGateby a pre-existing, owner-pending decision (wallet_ops.rs:1183send_async,:1316melt_within_async, both pre-dating this PR);pay_melt_quote_asyncinherits it. This PR adds the first unattended caller of that path. Not wired this round. What bounds the path: the accrued-gross ceiling at spend time, the fence (one admission per row, one quote per admission), exclusion at admission (one row in flight, no time term), the hold (no release while a payment may still land),allow_real_mints/home::mint_allowed(fail-closed before the wallet is opened), and theauto_remitoff switch.--confirmpays (seller_fees.rs:110,:413;RemitTrigger::pays,fee_remit.rs:526).[platform_fee] auto_remit = falsedisables both automatic paths (run.rs:4154–4166,:4235,:4260,:7240). It does not touch accrual, cannot change rate or destination, does not gate--confirm, does not forgive the fee.platform_fee::PLATFORM_FEE_ADDRESS = "maxplayer@agi.cash"(platform_fee.rs:68), onepub const, not seller-configurable.minSendablenothing is attempted.REMIT_LEASE300 s (fee_remit.rs:237),SPEND_MARGIN60 s (:246). Neither lengthened (addendum 6 §1.4).Rounds 5–8 — B3 / D / G / H (addenda 6–7), B4 / H1–H4 / I (addendum 8), F1–F4 (addendum 9) — moved out of this body
The four sections (verdicts at
6fc77e1,6fd13df,19f30d3,da0ee92) stand verbatim, including their round-9 corrections (F3's withdrawal of "local only" — N4 item 2 — and F4's re-anchoring at240240c), inrounds-5-8-sections.md(body lines 30–55 at37b3615); the round-8 body as pinned isreports/w-remit-r8/body-pin-4714623.txt. They were moved, not cut, because GitHub caps a PR body at 65,536 characters and the round-9 body measured 90,260 (ruling 2026-09-08T18:32Z item 2). Everything those rounds established that still governs the money path is restated at240240cin the §5 statement above (fence, quote binding, hold, CDK fee model, three cancel sites, prepare's true bound) and in the Round 9 section below.Prior-finding ledger (round 8's rows): B/F1 fake admits SDK-impossible success → closed at
1a5c7c5(records 32, 28, 37). G/F2 inclusivefee_paiddouble-counted → closed (record 38 + PAID print). H/F3 false recovery/cancel prose → closed as prose; the recovery path itself is owed, not built. H/F4 stale current-caller prose, body §196 / §10 → closed. Rounds 1–7 rows unchanged.Round 9 — what discharges N1–N4 (verdict at
4714623, addendum 10)Round 9's N1–N4 discharge, in full, is moved out of this body to keep it under GitHub's 65,536-byte cap:
rounds-9-sections.md— verbatim, nothing rewritten. In one line each: N1/B one bound, the SDK's, taken at the confirm arithmetic (admits_totalgone, census 0 added lines); N2/B a pre-fence arithmetic refusal writes nothing and prints one line; N3/G the reserve bounds the Lightning fee and the labels say so; N4/H the eight contradictions closed where each lived. Two corrections the verdict's §7.2 required are carried both there and here: record 39 paysfee_paid5 (Lightning 2 + actual proof input 3; 13 + 5 + 1 = 19, the gross exactly — the round-9 body's "4" was inconsistent with its own delta of 19, re-read atfee_remit.rs:5454), and the round-7 "fitting" regressions stay recorded as failures rather than relabelled fixtures (see the round-7 paragraph below).Round 10 — what addendum 11 discharges (verdict at
1ee5cb2; two executable heads,31deed8then08c476c)N1 / B — the gross probe is gone; the exact search alone decides (commit
1df95d8). Round 9 kept a cheap veto in front of the planner:reserve + expected fees ≥ gross ⇒ FeesDoNotFit, evaluated on the gross before any invoice was chosen. It refused grosses that a smaller invoice pays inside the same gross. That probe is deleted. What remains at planning is the reserve-only precheck (invoice + reserve against the hard ceiling) and thenplan_confirmable_invoice's exact downward search, which is the only thing that may returnFeesDoNotFit. Two witnesses, both required successes, both reading the wallet delta as the whole claim — record 45 (fee_remit.rs:5577): gross 3 = fees 2 + 1, reserve 0, 1000 ppk, one 32-sat proof; the probe expectedpopcount(4) = 1 + swap 1 = 2and vetoed at reserve 1 (and at 2/0); the search instead takes invoice 1 — need 1 =[1], prepared 1, target 2 =[2], actual 1, worst case 1 + 0 + 1 + 1 = 3 ≤ 3 — and pays once: swap 32 → 2 (fee 1, change[16, 8, 4, 1]= 29), melt[2], Lightning 0, actual input 1, sofee_paid1; the test asserts(net_sats, melt_fee_sats) == (1, 1)and the line “invoice amount (maxplayer@agi.cash receives): 1 sats”; pool 32 → 29, delta 3 = the gross exactly, never over, row Settled. Record 46 (:5685): gross 8 = fees 5 + 3, reserve 5 at estimate and at payment, ceiling 3; the search rejects invoice 3 (worst 11) and invoice 2 (worst 10) and takes invoice 1 — need 6 =[4, 2], prepared 2, target 8 =[8], actual 1, worst 1 + 5 + 1 + 1 = 8 ≤ 8; swap 32 → 8 (fee 1, change[16, 4, 2, 1]= 23), melt[8], the mint keeps the whole 5-sat reserve as its worst-case Lightning fee plus actual input 1 ⇒fee_paid6, melt change 1; asserts(1, 6), pool 32 → 24, delta 8 = the gross exactly. Neither witness widens the bound: both pay strictly inside the gross the seller already earned.D / 2c — the racing loser is held, not silently dropped (commit
98272ba). Two ordered tests, no timing, no sleep. Record 47 (:4788,a_racer_that_starts_after_the_winners_fence_is_held_on_the_winners_bound_quote_and_the_winner_pays_once): after A crosses the fence, the row is assertedRemittanceState::Spendingwithremittance_id == "hash-13-2-a"andspending_quote_id == Some(X_PAYMENT_QUOTE)— B is then refusedRefusal::SpendingHeld { quote_id: Some(X_PAYMENT_QUOTE), observed: "this wallet holds no such melt quote", held_sats: 15 }, and the whole operator line is asserted verbatim, including “15 sats of receipts stay pinned to it — a spending row is released by nobody and on no clock; it settles only when the mint reports that quote PAID; an operator decision, not a timeout, resolves it. REFUSED — nothing moved by this run”. B's side is asserted to have done nothing but look:b.quote_status_calls == 1(“B asked its wallet about A's bound quote, once”),b.invoices.is_empty(),b.quotes.is_empty() && b.melts.is_empty(). Record 48 (:4903): started after A settled, B finds nothing unremitted —a.melts.len() == 1, B prints “Nothing to remit. REFUSED — nothing moved.”, there is no row in flight to reconcile, androws.len() == 1(“one row, A's”). Neither test is skipped or retried to pass: across the whole added seta6217328..08c476cthe count of added#[ignore]and#[should_panic]attributes is 0.H1 — one spend model at every site, comments and docs only (commit
31deed8). The verdict found the module doc, the error types and the quickstart each describing a different spend model. One model now: reserve-only precheck → optional re-plan once → prepare (which may GET mint metadata/keysets but posts nothing fee-bearing) → post-swap actual arithmetic → fence → confirm; a refusal before the fence leaves the row Planned, unbound and ours with its receipts pinned, and the bound is actual-vs-prepared.31deed8is 3 files, +84 / −52, and touches no code and no assertion. Its sixteen comment sites, each relocated by exact text match in the tree at08c476c(not carried over from the commit's own line numbers):fee_remit.rsmodule doc:40–44,:46–52,:96–99,:121;MeltFailure:253–258;LiveEffects:462–466; the spend-order comment:1467–1468,:1470–1476,:1478–1486; the 1a re-plan note:1593–1595; the 1c refuse-before-fence note:1806–1807; the belt-behind-the-braces note:1980–1984; and the three stale test comments:5793–5795,:5893–5896,:6112–6113,:6140–6141. Plusstore.rs:2274(thereplan_remittancedoc: “smaller or larger, whatever fits at the live reserve”, replacing the old “smaller”) anddocs/SELLER-QUICKSTART.md:1133–1146. All sixteenfee_remit.rssites and all 14 added quickstart lines were located by matching their text in the head being reported, so every line number above is a line number at08c476c; the four test-comment sites sit one line lower than in31deed8because the08c476cfix removes a line above them.Prior-finding ledger (round 9's rows): B/N1 fitting planner rejection and two-gate bound → closed at
240240c(records 39, 42, 43; re-plan records 40, 41). B/N2 pre-fence refusal deviated from ordered state/output → closed (records 37, 29, 14 on the Planned/one-line contract). G/N3 aggregate-fee labels → closed (record 44; source2521a44). H/N4 eight body/source contradictions → closed in this body and2521a44. Owed rows unchanged (recovery path, BudgetGate, force-release,pay_melt_quote_*/TerminalBoundQuoteremoval, rebase-or-merge) — "Out of scope" below.Gates — command and output, re-run at the executable head
08c476c(local, macOS arm64)Every line below was re-run in the tree being reported, not carried over from round 9:
git diff --name-only 08c476c..HEADhas 0 non-reports/files, so the.rstree these greps ran against is08c476c. Transcript verbatim, also committed asreports/w-remit-r10/gates-greps-08c476c.txt.Gate 1 —
allow_real_mintsdefaults to true (home.rs:1546,#[serde(default = …)]at:1411), all 8 wallet entry points honourmint_allowed(wallet_ops.rs:1183 · 1243 · 1318 · 1363 · 1590 · 1875 · 1930 · 1969), and the paying command is--confirm-gated (seller_fees.rs:110,:413;pays()fee_remit.rs:526).Gate 2a — the origin callers are the three fixed ones: the CLI at
seller_fees.rs:418, and the seller node's two atrun.rs:3321/:3323; the rest of the hits are theremit_best_effort/remit_live_best_effortdefinitions (fee_remit.rs:2147/:2170/:2177) and test call sites. No fourth path and no startup path. Excluding the five#[cfg(test)]modules by brace depth (not by a single line threshold), the non-test spawn sites inrun.rsare:303,:2093,:6051,:7264,:7327— none of them a remit clock — and the only non-testremitlines mentioning a clock are:4153/:4159(the loop's own boot/backoffSleep, re-armed throughremit_pacing_cha…) and a doc comment at:7308.Gates 2b–2g, E, v13, the CLI and the round-7/8/9/10 regressions — one
--exactrun per named test: all 47 recorded green, listed in the suites section above and inexact-summary-08c476c.txt.Gate 3 — one destination. The literal
maxplayer@agi.cashappears 46 times incrates/*.rsat08c476c, and exactly one of them is outside a test module:platform_fee.rs:68,pub const PLATFORM_FEE_ADDRESS: &str = "maxplayer@agi.cash";. The onlyfee_address/platform_fee_addresshit is a comment (platform_fee.rs:56) explaining why a seller-editable address is not offered. No second destination exists.Gate 4 — the spend edge. In
fee_remit.rsthe effect call sites areeffects.prepare_melt(once (:1786),effects.cancel_melt(three times (:1811post-swap arithmetic refused,:1864fence lost,:1915pay-time margin) andeffects.confirm_melt(once (:1931) — the same three-cancel shape round 8 undercounted as two, now re-counted at this head.wallet_ops.rsover the whole delivery: +1,640 / −30.Full suites at
08c476c(local, macOS arm64; each suite its own command with--no-fail-fast, so a failure anywhere cannot hide the rest)Same totals as at the first executable head
31deed8(1431 / 143 / 180). Logs, with line 1 of each = the reports-onlyHEADthe runner saw and line 2 = its.rsdiff to08c476c(empty in all three, so each is.rs-identical to the executable head):core-wallet-nff-08c476c.log136,402 B4bb361b15f78cb03(line 1c94dbd4),cli-default-08c476c.log16,866 Be6148fe9e3785005(a44c0be),cli-acp-wallet-08c476c.log18,585 Be615448676dc5931(a44c0be).47
--exactrecords (44 carried + records 45–48 new; #33 retired in round 9): every oneok. 1 passed; 0 failed, 0 failed, 0 not-1-passed, each log carrying the same empty-diff line 2, checked on all 47 rather than sampled (exact-summary-08c476c.txt, 51 lines).claims.md — the seven claim groups the verdict at
4714623failed, re-verified by hand at240240c: 7 / 7 PASSclaims.md(committed37b3615) quotes, per group, the body sentence now (body line), the source line now (grep -nat240240c, code quoted) and a PASS/FAIL with the reason: (1)from_preparedzeroesswap_feeunconditionally (saga/mod.rs:592, no branch) — PASS; (2) order reserve-only precheckfee_remit.rs:1576→ prepare:1786→ post-swap arithmetic:1808→ fence:1843— PASS; (3) threeeffects.cancel_melt(sites:1811/:1864/:1915, all insideremit_inner(:1045–:2030), no fourth — PASS; (4) prepare may GET keysets through the SDK's metadata cache (keysets.rs:44–51→mint_metadata_cache.rs:376), posts no proof- or fee-bearing request; "cache-only" 0 hits — PASS; (5) record 28 prepared input fee 4 ≠ actual 3 at both body sites (:5176,:5207,:5248) — PASS; (6) onewriteln!(:1557–1539, single-line format string), row stays Planned (:3619; called:5873,:5967,:6172) — PASS, with the occurrence-count note carried to Known defects below; (7) no blanket "all claims correct" sentence remains (0 grep hits) — PASS as the citation-level claim it now is. The file is the semantic sample the verdict said a zero-offset self-check cannot replace; the self-check itself (190 sites / 223 needles / 0 misses at240240c) iscitation-selfcheck-240240c.txt.fmt / clippy — the WHOLE delivery
a6217328..08c476c, additions counted, and how each number was gotAdded set re-measured at
08c476c(not carried from round 9):git diff --numstat a6217328..08c476c -- '*.rs'= 10 files, +16,409 / −278; withdocs/SELLER-QUICKSTART.md, 11 files, +16,558 / −293; hunksgit diff -U0 … | grep -c '^@@'= 176.The four overlapping fmt blocks are the same four as at
31deed8, with the same blame, and none of them is a line this branch added:home.rs:1445(the rewritten line is orveth'sff3918e4) andlib.rs:47/:55/:68(pre-existingpub modlines, blamedb63eb96/b741eaf3/7d80595a, orveth). Clippy INSIDE fell 1 → 0 between the two executable heads:08c476cis exactly the fix for the one diagnostic31deed8carried (fee_remit.rs:5642,clippy::identity_opon record 45's assert literal), and the total moved 126 → 125 — one diagnostic, nothing else.Known defects — named, not fixed; plus three from round 9 now discharged (ruling 2026-09-08T18:32Z item 3)
Discharged this round by
31deed8(comments and docs only — no code, no assertion): all three round-9 stale test comments. Each said the refused row was released (“Planned → Failed, receipts back”) where the test asserts it stays Planned. At08c476cthe phrasereleased (Planned → Failed, receipts back)occurs 0 times infee_remit.rs, and the three comments now read “the row NOT written (stays Planned, unbound, receipts pinned; the next attempt's reconciliation releases it — addendum 10 §2)”: abovea_fee_bearing_schedule_whose_prepared_figures_fit_but_post_swap_arithmetic_does_not_is_refused_before_the_fence(fee_remit.rs:5798), abovea_fee_bearing_total_that_exceeds_the_gross_by_the_fee_is_refused_before_any_swap_or_melt(:5899) and insidea_reserve_that_grows_between_estimate_and_payment_is_refused_before_spending(:6116). I also checked the one remaining “planned → failed, receipts back” in the file (:6202): it is accurate — it describes reconciling this process's own earlier attempt, which is exactly what that path does — so it is not a fourth instance.Still open, named not fixed (a
.rschange would move the executable head again and force a third full gate re-run):out.matches("REFUSED before spending").count() == 1atfee_remit.rs:3638,:5851and:5945(the verdict at4714623criticised this form). It is sound at08c476conly because the format string at:1559is one line with no embedded newline and the{reason}text carries none; anout.lines()-based assertion is owed, not written.credential_proxy::tests::a_declared_over_cap_body_is_refused_before_the_upstream_sees_itpanics on areqwestConnectionReset(os 54) atcredential_proxy.rs:3520while writing a body to its own loopback stub — reproduced 3 runs out of 3 at this head (ephemeral ports 62173 / 62805 / 63481). It is not this branch's code, no fee, remit, melt or wallet frame appears on its stack, and all 44fee_remittests passed in every one of those runs. Named here rather than fixed or silenced: it is not mine, and touching it would move the executable head.Money-path binary ×3 at
08c476c(addendum 4 §4) — CI's exact command (.github/workflows/ci.yml:233)One failure per run, the same one, three times, and it is not on the money path: the test resets its own loopback connection while writing a body to its own stub; no fee, remit, melt or wallet code appears on that stack. 44 named
fee_remittests ran in each of the three runs and all 44 are inside the 1,434 passed — no fee or remit test failed in any run. Logs (bytes · sha256(16) · line 1):money-path-ci-1-08c476c.log130,874 B2c80da572f6daaf3a44c0be·money-path-ci-2-08c476c.log130,762 Be6d895882833bee5a44c0be·money-path-ci-3-08c476c.log130,762 B5e9edf401f8b166cff6e47a. The runner finished on its own at 21:05:40Z (STAGE3 money-path x3 done — ALL DONE); it was never restarted and no second runner was launched.CI — PASS at the published head
c53705a1: run 34295509051, seven jobs green; the earlier integration block preserved below as historyCurrent state.
c53705a192ec4f8dac19ad1177e03561ba518929was published tofeat/seller-fee-remitat 2026-09-09T00:33:16Z, the conflict withmainwas gone with it, and GitHub created the run that could not exist before: 34295509051 —pull_request,.github/workflows/ci.yml, 00:33:20Z → 00:47:05Z, completed / success, run and all seven job records bound to headc53705a1. PR state at the same read:open,draft: false,mergeable: true,mergeable_state: clean, based55ceaf.CI at the final head
a0aafe8cd5996abe68cf22c7423fa0940e7efaac— thereports/removal. Run 34298627586,pull_request, completed / success, bound to that head, with all seven jobs green — the same seven as below (ids 102300586400, 102300586464, 102300586514, 102300586523, 102300586525, 102300586560, 102300586573).So both heads of this delivery are CI-green: the reviewed executable head
c53705a1(run 34295509051) and the final head that only deletesreports/.What those jobs actually check out, stated exactly. Every job log fetches the synthetic merge
9b639fe6cde7df620227160c9062e0d5a58e62b2, notc53705a1, as runnerHEAD. Its parents are based55ceafand headc53705a1, and its treeb09af0f44c8d8d67ebfbdb1939909051184077a9equals the tree of the local immutablec53705a1— so this is valid published coverage of the reviewed tree. The PR'smerge_commit_shais that same synthetic merge; it is not evidence of a completed merge, and this PR is not merged. The eighth check on the head is Vercel Preview Comments, not a CI job.Local gates at the two frozen heads, failures included. At the integration head
d56cc163all 47--exactrecords passed and the suites carried over clean, but the money-path trio failed three times —exit=101,1497 passed; 1 failed, each on the same over-capcredential_proxytest, which reset its own loopback connection while writing a 32 MiB body against a refusal the proxy had already sent. Those three failures stand as failures. The repair commitc53705a1changed only that test's transport (1 file, +57 / −17, all inside one test fn; no runtime, no cap, no fee code): declarecontent-length: MAX_REQUEST_BODY_BYTES + 1, send zero body bytes, read the status line under a 10 s timeout, assertHTTP/1.1 413, and prove no upstream contact by anaccept()that must find nothing waiting. Atc53705a1: the changed test, both adjacent cap tests and the wholecredential_proxymodule (62 passed / 0 failed) green; all 47--exactrecordsok. 1 passed; 0 failed; and the money-path trioexit=0,1498 passed; 0 failed; 2 ignoredthree times (148.74s / 150.59s / 145.49s —1497 + 1 = 1498, the one test that was failing). Every log carriesHEADandgit status --porcelainat top and bottom; they live in the seat folder, uncommitted, per the evidence-location ruling.Historical — the integration block, 2026-09-08T20:36Z to 2026-09-09T00:33Z. For that window there was no CI run at
08c476cor any head after it. Not a queue, not a skip, not a path filter: GitHub builds apull_requestrun againstrefs/pull/979/merge, and when that merge ref cannot be computed it creates no run at all. The PR wentmergeable: CONFLICTINGwhenmainmoved tod55ceafat 20:35:58Z, two minutes after the last run started.Per maxie's ruling of 2026-09-08 (
979-r10-integration-ci-ruling-20260908.md, 1,621 B, mode 0444, sha256a99f2572…eebb2d, read whole and digested by me) those absent runs were BLOCKED — INTEGRATION: not green, not failed, not a feature DENY on their own. That block is now cleared by measurement, not by waiver — the rebase resolved the conflict, the leased publication produced run 34295509051, and it passed. Nothing here waives a CI requirement, and publication is not merge approval: merge remains a human act.Real failures, named as failures, never folded into the conflict story: run 34273382506 at
1df95d8failed — 6 of 7 jobs green, the Test the full shipped feature combo (acp + wallet) job (102220345699) reportingFAILED. 1560 passed; 1 failed; 3 ignoredonseller_node::run::tests::an_accept_naming_another_seats_claim_never_binds_the_loser, orveth's seat-claim test, not this branch's code; the same job flaked at68fa0eb(run 34265888505) on the r9 race test that this round's §2 work fixed. Run 34275523983 at31deed8was success on all 7 jobs, including Money-path tests and the acp+wallet job.Per-file diffstat
a6217328..08c476c(product files;git diff --numstat)Report-inclusive total at the reports head:
git diff --stat a6217328..HEAD | tail -1= 197 files changed, 260,376 insertions(+), 293 deletions(−) — the difference from the product figures above is this round's evidence and report files.Evidence files — removed from the branch at the final head, preserved externally against a verified manifest
reports/is no longer in the merged tree. These 196 files are agent working notes — round plans, gate logs, evidence tables — not product, and the owner asked for them out before merge. The removal is the final head's only content: 196 files changed, 244,648 deletions(-),git diff --name-status c53705a1..a0aafe8is196 Dand nothing else, and every root tree entry other thanreportshas the same object id at both heads, so no product file moved by a byte.Preserved before removal, and proved before it rather than after. All 196 blobs were extracted with
git archive c53705a1 reports; each file's sha256 was then checked againstgit cat-file blob <oid>at that head and its size againstgit ls-tree -l— 196/196 identical, 0 mismatches, 13,125,086 bytes — at/Users/forge/forge/v2/worker/reports-archive/979-c53705a1/tree/withMANIFEST.json(52,812 B, sha256208929d5fd359e0ad03bf7eb9ccfced0b6e4a6ff35cd0bceb91993afae8dc050; per file: path, bytes, sha256, mode, blob oid, verified flag) andSHA256SUMS.txt(22,477 B, sha2564c8ba34a28fa5a38d76f5a07898d0c7356d8af60fee0ec4c6a7a39caee343484). Every file this body names — the round-10 gate summaries and transcripts, and round 9's ownreports/w-remit-r9/set — is in that archive under its original path, at the hash recorded there.The permalinks in this body still open, because they point at pre-rebase commits whose objects the fork retains (checked 2026-09-09); but an unreachable object is a courtesy, not a guarantee, so the external manifest is the durable copy and the digests above are what a grader should check against.
Not committed, and deliberately so (evidence-location ruling, sha
b0b39e5e…cd39599): the 58 raw gate logs live at/Users/forge/forge/v2/worker/reports/w-remit-r10/logs-b/, outside the product repo. Each was copied only after its done line appeared in the runner'sprogress.txtand verified withcmpagainst the runner's own copy; a final pass afterALL DONEre-cmp'd all 58 with 0 mismatches, beside manifestSHA256SUMS-b.txtand ledgerEVIDENCE-LEDGER.md. Every log's sha256 is ingates-summary-08c476c.md.Round 9's table, corrected:
evidence-table.txtgoes from 69 rows to 68 — the 0-byteexec2-runner.outrow is dropped, because a row with no committed blob is not evidence (verdict §7.2).Disclosures
ec56d0ccarries the wrong author/committer NAME (w-ecash-mutual-credit; email unchanged). Measured cause: the shared checkout's.git/configdefault (user.name = w-ecash-mutual-creditsince 2026-09-05T03:07:27Z) plus one commit made withoutgit -c; round 6's "switched by another worker between my commits" was not measured and is withdrawn. Not amended (append-only); every later commit sets the identity explicitly.2d802a3→24ba082, five clippy diagnostics), once inside round 8 (b1c702a→1a5c7c5, one clippy diagnostic and nine rustfmt blocks on round-8 lines; +fmt only, one constant assertion computed from the fake's pool instead) and once inside round 9 (2521a44→240240c, rustfmt on eleven round-9 test hunks and one clippyidentity_opin a test message; no behaviour change). Every gate was re-run at the head it is reported for; the earlier logs stay inreports/.fee_paidinclusive of the actual input fee — and the self-check test pins that model. It still uses a binary denomination split for a power-of-two keyset and the active keyset's singleinput_fee_ppk; a mint with several fee-bearing keysets or a non-power-of-two keyset is not modelled. §1.5 bound (disclosed, not solved): the fee metadata can change between prepare and confirm and the SDK takes no caller maximum; in that window CDK's post-swap refusal fires after the fence and the bound-Spending hold covers it.pay_melt_quote_async/pay_melt_quote_blocking(rounds 4–6's pay-by-id path) remain inwallet_opswith their tests and have no live caller at this head. Removal is a decision for the owners; the code is retained so the graded rounds' path is still readable.da0ee92): the fake let invoice 12 (reserve 0, ppk 1000, pool[32]) and invoice 14 pay where CDK refuses after its swap. Neither historical failure was relabelled into a passing fixture: they stay recorded as failures in the round-7 verdict record. What the suite carries now is different material (verdict §7.2 H3, re-read at 08c476c). Record 37 (fee_remit.rs:5176) is a new drift fixture — reserve 3 at estimate and at payment, unchanged, with the prepared input fee overridden to 0 — not the old reserve 3 → 0 inputs; it is refused before the fence. The old reserve-shrink schedule is carried instead as a required success, record 40 (:5318,a_reserve_that_shrinks_between_estimate_and_payment_is_re_planned_once_and_pays_invoice_15): plan 12 → re-plan 15, paid once. Record 30 restores the other round-7 case as invoice 13. No expectation was changed to accept a swap loss on a failed melt.240240cfailed on the disclosed out-of-diffcredential_proxyloopback flake (1430/1; runs 1 and 3 1431/0), as did round 8's runs 2 and 3 at1a5c7c5(1424/3, with thejob_lifecyclelive-mint pair), round 7's run 3 and the2d802a3trio (2/3/2 — never green; round 7's body said "green" once in its heads paragraph, withdrawn above); the first-attempt core lib run at2521a44hit the same flake once (1426/1), the re-run at240240cdid not. Reported as FAILED counts, never as green.recover_incomplete_sagasis not wired on the remittance path this round, by instruction; the prose says so. Owed too: operator force-release / human approval of a held row; owed-vs-held accounting; removal ofpay_melt_quote_*andTerminalBoundQuote.d17fd33. Then, under maxie's integration order and the owner's publication order, the delivery was replayed ontomainatd55ceaf(140 commits, 0 merges) and published tofeat/seller-fee-remitwith one--force-with-lease=refs/heads/feat/seller-fee-remit:d17fd33ee1e187fdfec6c7a6cc8581c812bb356b,+ d17fd33...c53705a (forced update), exit 0 (publish-push-c53705a1.log, 585 B). No unconditional force, no merge, no tag, no push tomain, no new PR. The sole conflict,crates/maxplayer/src/sell.rs(thesell_usagehelp string, in two commits, same region), was resolved keeping both sides;git diff --numstatis identical across all 207 files before and after the rebase, the whole pre/post difference being 46@@hunk offsets plus the 6 resolution lines.d56cc163(exit=101,1497 passed; 1 failed, the same over-capcredential_proxytest each time). Reported as failures, never waived, never retried to green; the transport-only repair atc53705a1and the1498/0/2trio after it are in the CI section. A negative control — the assertion mutated to expect200— failed printing the realHTTP/1.1 413 Payload Too Large, then was reverted to a byte-identical file, so the pass is not vacuous. Round 8'sreports/w-remit-r8/is committed (036302b,4714623); rounds 4–7'sreports/directories stay untracked in the worktree.Out of scope, named not done (addendum 9 §7)
BudgetGatewiring (owner decision, errata 1). The operator melt path —maxplayer wallet melt,melt_within_*— keeps its reserve-only ceiling (invoice + reserve, no proof-fee bound, no confirmability check) and is not touched. The automatic threshold sweep, the signed platform parameter, any rate change, owed-vs-held accounting, an operator force-release / human-approval path for a held row, legacy unbound rows, removal ofpay_melt_quote_*andTerminalBoundQuote(owners' call), wiringrecover_incomplete_sagas(a supported recovery path for a stranded local reservation — owed), repo-wide lint debt (125 clippy diagnostics and 2,106 rustfmt blocks on pre-existing lines), the three out-of-diff flakes, and rebase-or-merge asmainmoves. Named as owed; none built.