Repository navigation
fix(gateway,payments): no fabricated SWF accruals, distribution time or chain balance - #424
Merged
Merged
Conversation
…or chain balance Readmodels found these in N34's review of #421 (bus #3284). Economics owns the ledger. 1. Both release paths (routes/settlement.ts and facades/settlement.facade.ts) accrued swfAccrue(..., 1000, "USDC", "base") for every released milestone, whatever was paid. Every SWF money figure the routes serve was derived from that constant. Passing the real amount would not fix it: the fund's ledger is in memory, and the escrow routes no share of a release to it, so any accrual records money that never moved. Neither path accrues now. swfAccrue stays for a real flow into the fund, once one exists. 2. GET /api/swf/summary reported lastDistributionAt as now when nothing had ever been distributed. It also labeled the in-memory ledger as a Base USDC chain balance. lastDistributionAt is now null until an epoch completes, and chainBalances is []. A new optional `unavailable` field names those two, so a reader shows "unavailable" instead of a guess. SWFSummary in @pcc/spec is updated to match. Tests: - payments: the summary invents neither field, and reports the real distribution time once an epoch completes. - gateway: a source ratchet (no gateway file may call swfAccrue), and the live summary. All four fail on master's code. payments 301/301; gateway settlement and SWF tests 197/197 (13 files); tsc clean on spec, payments, gateway and dashboard. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
…bels simulation totals Cross-family review EC2 (astra, SHIP-WITH-FIXES at 4e79258). Each finding was reproduced at 4e79258 with a test that fails there, then fixed: - F1 (HIGH): accepting terms, activating an equity position, recording revenue and distributing an epoch now answer 501 not_available before anything is read or written. At 4e79258 they answered [200, 409, 409, 409] and the portfolio showed deployed capital and earned revenue that no transfer backed. The distribute handler is #421's hunk verbatim. - F2 (MEDIUM): lastDistributionAt is always null. A completed epoch is a simulated split whose endTime is its scheduled deadline, so the summary reported a future time as a distribution. - F3 (MEDIUM): the summary says basis "simulation" and lists the three money totals as unavailable, so a zero reads as an empty simulation ledger, not a measured fund. - F5 (MEDIUM): the ratchet now catches any spelling. swfAccrue may be named only at its definition (no import, alias or re-export), and .accrue( may appear only in routes/swf.ts. Behavioural tests show settlement release leaves the SWF ledger unchanged. Two comments that named the helper are reworded. F4 (the dashboard's MOCK_SUMMARY page) is outside this diff; #394 retires that page. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu
added a commit
that referenced
this pull request
Sep 30, 2026
…eath it (N79 round 2) astra's pack 116b on #462 @38cf4116: DO-NOT-SHIP, with three HIGH findings and one MEDIUM. Each was reproduced first with a failing test at 38cf411 (n79-refund-vs-settlement.test.ts): - F1 HIGH: a repeated failure write while /complete is in flight refunded ("expected 'refunded' to be 'skipped'"); - F2 HIGH: cancelling one job refunded an escrow another job shares; - F3 HIGH: a refund landed while SettlementService.releaseMilestone awaited the chain ("expected 'refund_pending' to be 'skipped'"); - F4 MEDIUM: a completion that failed after a failure write left the failed job's escrow funded. Two more were reproduced the same way: - the keeper, which sweeps a findAll() snapshot across awaits, drove an escrow given back mid-sweep (astra's Q2 note); - the raw chain release route let a refund land while it awaited the chain (the F3 class, found by extending the audit). The fix is one mechanism: DURABLE SETTLEMENT OWNERSHIP on the escrow row. - A settlement takes its escrow with a compare-and-set from funded/active to `completing` (already in @pcc/spec's vocabulary), synchronously at its claim: /complete (same stretch as the job claim), resume-settlement, SettlementService.releaseMilestone (before the chain await) and the raw release route (by contract address). - A refund never lands on a `completing` escrow, however often the job's mutable status is rewritten (F1, F3, raw route). - A settlement that gives up BEFORE recording evidence hands the escrow back (prior status). If the job ended meanwhile, the escrow goes to the payer then, in one transaction (F4). After evidence exists, the settlement keeps it, since resume-settlement continues it. - A release that went through records its milestone `released`, and the escrow `completed` once every milestone is. - The refund gives back a whole escrow only if no other job references it; a shared escrow is skipped as `escrow_shared` (F2). - The keeper re-reads the row right before each drive. Tests: the six reproductions, plus the gaps astra named: - a failure on the LAST write rolls back every milestone and the status; - a failure after evidence keeps ownership; - resume takes a legacy row (the write repeated, as in F1); - a partial release hands the escrow back; - failed releases (service and raw route) hand it back. All 40 mutants (both rounds) are killed. The gateway suite passes: 3053 passed, 6 skipped (six threads; the Spark is at load 37, where two /complete tests time out at 5 s and pass alone). tsc is clean. It merges cleanly with #326, #385, #403, #424, #441, #445, #450, #451 and #459.
LamaSu
added a commit
that referenced
this pull request
Oct 3, 2026
#360 was SHIP at c73d708 (EC1c) with full CI green, but master moved on 9/30–10/01 (#424, #459 N80, #436, #338, #477, #483 and others), and three files conflicted. Resolutions: - packages/spec/src/index.ts: both sides added exports at the same place. Both are kept: #360's `economics` namespace, and master's money-status and readmodels exports. - packages/gateway/src/routes/swf.ts: both sides made POST /api/swf/epochs/:epochId/distribute answer 501 not_available before anything is read. Master's hunk (#421's, reviewed and merged with #424) is kept. The behavior is identical. - packages/gateway/src/routes/rewards.ts: #360 made the reward, certificate and treasury routes answer 501 and deleted their fixtures. Master's N80 (#459, reviewed and merged) changed only the certificates: an empty list, 404 for one, and a 501 mint that claims nothing. It kept the reward and treasury fixtures. Kept: master's N80 certificates, and #360's 501s for rewards and treasury, with no fixture import. economics-no-fabrication.test.ts drops its three certificate rows (certificates-n80.test.ts pins N80), and the reward and treasury rows still assert 501. On the merged tree: spec 1286/1286; contracts 267/267; mcp-server 76/76; gateway 3717 passed, 13 skipped, 0 failed; dashboard 353/353; tsc clean for contracts, mcp-server, gateway and dashboard. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
From lane pcc-economics (df42dbe5). Readmodels (c255d7dc) found these in N34's review of #421 (bus #3284). Economics owns the ledger, so the fix is here.
What was fabricated
routes/settlement.ts:204andfacades/settlement.facade.ts:692) calledswfAccrue("settlement", jobId, 1000, "USDC", "base")on every released milestone, whatever amount was released./api/swfroutes serve came from that constant: the summary'stotalBalanceandchainBalances, epoch totals, accruals, forecasts and equity seeds.GET /api/swf/summaryreportedlastDistributionAtas the current time when nothing had ever been distributed.What changes
routes/swf.ts: "Shared service instance (in-memory mock)"), and the escrow routes no share of a release to it. Any accrual therefore records money that never moved, including the "2% of released value" the old comment described.swfAccruestays, for a real flow into the fund once one exists.lastDistributionAtisnulluntil an epoch completes.chainBalancesis[].unavailable: ["lastDistributionAt", "chainBalances"]tells readers to show "unavailable" instead of a zero, a guess or the current time.SWFSummaryin@pcc/specis updated to match. The only typed consumer, the dashboard's mock SWF page, still type-checks; feat: PX-12 EconomicAgreementView, who gets paid what, when and why (server preview; retire fabricated IP pages) #394 retires that page.Decision parked for the operator
Should the SWF be funded from the protocol fee on chain, for example by making it the V-next
feeRecipientor a share of it? Until that is decided and built, the fund correctly shows no inflow.Coordination
POST /api/swf/epochs/:id/distribute501 outside an explicit demo mode, with marked demo responses. feat(spec): economic agreements v1, an exact compiler to V-next per-unit payouts #360 (economics) makes it an unconditional 501. They conflict textually inswf.ts; whichever lands second takes the other's hunk (agreed on #3296).swf.ts, so it conflicts with neither.Tests
swf-no-fabricated-accrual.test.ts:routes/swf.tsmay callswfAccrue;tscclean on spec, payments, gateway and dashboard.Review: this touches money figures, so by the steward's rule it needs a coord-watch cross-family verdict on the exact merge SHA. Merging is the operator's decision.
🤖 Generated with Claude Code
https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K