Skip to content

built-with-deviations Built, with deviations. This is a decision record, not documentation; the body is annotated where the build diverged.

What is still current: One total-return line and one accrual line is still how the portfolio charts performance. Everything about the PINNED class is retired: those symbols no longer exist and valuation-policy-810-plan.md carries the current rule.

Landed: feat/nav-rebuild (v0.32.0); partly superseded by the valuation policy (v0.58.0)

Header updated 2026-09-14. The body below is frozen history. All plans.

Portfolio performance rebuild: one total-return line + one accrual line ​

Superseded in part (2026-09, #810). Everything this plan says about the PINNED class — pinned-marks.ts, marketPinnedToRedemption, UNCOVERED_PAR_ASSETS and the M26 pinned shortcut — was retired by the valuation-policy program. Those symbols no longer exist: every idle asset is marked off its own price feed, and the one surviving case (an asset that IS its book's own unit) is identityUnitAsset in src/lib/portfolio/unit-prices.ts. Read valuation-policy-810-plan.md for the current rule; where the two disagree, that one wins.

Decided by Fred 2026-08-05; designed, planned and shipped in one branch (feat/nav-rebuild) against staging. This document merges the product design and the normative implementation plan, and records what actually shipped where the two differ. Baseline: origin/staging @ 98b1389 (which already carried #550 and #552, neither of them on prod). Staging moved twice while the branch was open, carrying #553 and #554, and both were merged back into it.

PR #551 (M23 realized exit value) is CLOSED and superseded; its architecture is retired. What survived from it is its tooltip framing, salvaged into the per-position basis ledger: redemption is the issuer's currently reported claim, not a maturity promise.

The methodology this document describes is documented for readers in Metrics as M23 (total return), M24 (accrual), M25 (the seizure split), M26 (fallback by construction) and M27 (an index leg's total return is flow-free); the surface it produces is documented in Portfolio.


1. Product decision ​

The /portfolio performance chart is ONE view per asset class (the USD / ETH / BTC partition is unchanged and is never converted), with TWO lines:

  1. TOTAL RETURN — mark-to-market performance of everything tracked: every position valued at its market price, with redemption value as the fallback where no market price exists. Impairments, discounts, accruals and liquidation penalties all draw here automatically.
  2. ACCRUAL — pure yield: interest and yield accrued on yield-bearing positions minus financing costs on debt. What a position produces by being held rather than what the market will pay for it. (The one structural exception is a Fluid smart leg, whose accrual is the pool's economics; M24 records it.)

The vertical gap between the lines is the cumulative valuation effect.

The price-basis toggle is REMOVED. The whole dashboard renders the market mark. The parallel redemption performance curve is gone; per-position redemption DATA is kept everywhere it exists today (redemption value, basis at entry, current basis, dislocation since entry), and the dual-mark valuation pipeline is untouched because those surfaces consume it. The expanded position's basis ledger becomes the home of redemption education.

Every user's historical curve restates on ship. Index legs gain basis movement they never carried, and the accrual line is now always the redemption-basis attribution whatever the reader used to have selected. Deliberate, and communicated in the PR body and the docs.


2. Accounting specification (normative) ​

  • PERIMETER. The fund is the set of tracked wallets. Positions include bare cash tokens (each in its book). External capital is value crossing the wallet perimeter; everything inside is the portfolio changing shape.
  • MARKS. One value per position: market price, redemption fallback when no market price exists. The fallback is sticky per asset by construction (M26): membership is derived from the static registries and cannot change between two ticks of one series. There is no per-tick market-to-redemption substitution anywhere in the pipeline, and there never was one to remove.
  • FLOWS. Every tracked-position quantity change that is not accrual books as a signed flow valued at ITS OWN mark, read at the FLOW BLOCK. Per-transaction netting is retained. There is no trade-versus-transfer classification anywhere. Consequences, all deliberate:
    • Deposits and withdrawals move value; neither line draws.
    • Internal conversions (supply, withdraw, borrow, repay, swap, unwind, loop) net to ~zero; residual mark differences at the same block book as flow, not P&L, so the line HOLDS at the last mark on any exit. Accepted residual: execution below mark in a stressed or thin market never draws.
    • A swap into an untracked token is value leaving. Same as today.
  • TOTAL RETURN, per book. Interval P&L is the sum over legs of signedΔvalue − netLegFlow at the MARKET mark, for a value, pt or none leg. An index leg is attributed FLOW-FREE (M27), from its composed-index ratio times its price relative on the capital present when the interval opened, because Aave/Spark position-token flow values bundle accrued interest and netting them removes real yield from the line. Per leg, never book-level: a book-level difference cannot see which leg vanished and so cannot suspend it, which is exactly the M21 incident class.
  • LIQUIDATIONS. Explicit detection at every venue (Fluid by state diff included). A liquidation's movements are excluded from flow netting; the penalty (value taken minus debt actually repaid, the existing M4 math) draws on the TOTAL RETURN line as a realized loss, once. Nothing about a seizure is yield, so the accrual line takes no penalty.
  • ACCRUAL, per book. The existing per-leg attribution reused: index-ratio yield for index legs, entry-implied pull-to-par accretion for PTs (never the TWAP), redemption-rate growth for value legs, funding subtracted on debt legs. Flows are structurally excluded.
  • TIP vs SETTLED. Near the live tip, flows and marks may lack minute prices; best available is used provisionally and re-derived on the settled 6h pass. Exit flows are valued at their own timestamp, never at the last cron tick.
  • HONESTY (M9 unchanged). Never fabricate a value. An unpriced endpoint or an unvalued flow books 0 on the total-return line rather than letting a whole value movement pass as return.
  • EXCLUDED book unchanged. Unpriceable and outside-coverage holdings stay outside both lines, visible in the Other tab.

3. What shipped, by area ​

3.1 Engine (src/lib/portfolio/pnl.ts) ​

ONE pass over the intervals producing TWO accumulators (a second accumulator inside the existing loop is free; a second full curve pass is not).

  • BookCurve gained totalReturn, totalReturnTwr and totalReturnAnomalies; CurvePoint gained totalReturn. cumulativeYield / totalYield / twr / realizedApy / coverageAnomalies keep their exact meaning (the ACCRUAL series in the requested mark), except that totalYield no longer subtracts liquidation penalties.
  • The total-return series is mark-independent, pinned by a test: buildBookCurve(legs, flows, 'market').totalReturn equals buildBookCurve(legs, flows, 'redemption').totalReturn. So the read path builds ONE curve, in the redemption mark, because that mark's per-leg attribution IS the accrual line.
  • New primitives: legIntervalTotalReturn, legTotalReturnValue, seizureScopeKeys. LegSnapshot gained an optional valueMarketMirror (undefined means "no second method", an explicit null means withheld).
  • An index leg is attributed flow-free on the total-return line (M27, added in the review pass below): v0 × (indexRatio × priceRelative − 1), with priceRelative = (v1/r1) ÷ (v0/r0). Aave and SparkLend position-token flow values are amount ± balanceIncrease, not the capital that moved, so ΔV − flows netted away every dollar of interest earned since the holder's last touch.
  • Suspension state is per (leg, series): a leg can be unpriced in market while priced in redemption. The M21 suspension / death / birth / bridge machinery guards EVERY leg on the total-return line, which is the correctness risk of the whole rebuild.
  • trimIdleLeadIn gained a third guard: a dropped leading span must also carry zero total return, because an accrual: 'none' par leg can have a moving market value.

3.2 Marks and valuation policy ​

  • Fallback by construction (src/lib/portfolio/pinned-marks.ts, M26). Two populations: declared-pinned wrappers, and par accounting assets with no standing market-price coverage. Derived from the static registries, never listed by hand. Covered assets keep honest market bars; a real USDC or WBTC dislocation must draw. eBTC (no bar, no rate) stays outside both lines exactly as before.
  • PT legs use the market TWAP series for total return and entry-implied accretion for accrual. value_redemption for a PT is null by design and needs no fallback. A young-TWAP revert is an unpriced tick and is bridged as today.
  • The live tip is mirror-consistent: the JIT "now" Kyber-mid override applies to the tracked-value display, while the total-return line's live point is valued by the same stored-row method as every point behind it. One series, one method.

3.3 Flows: two required fixes ​

  • WETH wrap/unwrap was invisible. WETH9 emits Deposit / Withdrawal, not Transfer, while native ETH's balance-diff flow does book, so a wrap printed phantom P&L on the ETH book under a mark-to-market line. Both events are now decoded in the wallet-venue scanner as transfer_in / transfer_out of WETH at par, netting against the native-ETH difference.
  • Native ETH at the JIT tip. The JIT derived no ETH flows, so a balance change since the last snapshot printed as pure ΔV on the live point. The JIT resync now synthesizes the same provisional balance-diff flow the cron would. Gas spend books as a small withdrawal per window: accepted, documented.

3.4 API and wire ​

  • GET /api/portfolio/history serves BOTH series in one response. HistoryPoint keeps cumulativeYield (now TOTAL RETURN; the key is NEVER renamed) and gains accrual: number | null. bookValue stays.
  • The mark query param is accepted and ignored: absent, market, redemption and a typo all serve the same response, so a client mid-rollout never takes a 400.
  • The channel crossing happens in exactly ONE place, the exported pure function historyPointsFromBuckets in api-data.ts. Inside the engine, cumulativeYield still means accrual and totalReturn means total return; only the wire swaps them.
  • SummaryResponse wire shape is unchanged (the dual-mark blocks stay populated); dead-field cleanup is a later PR.
  • The client history cache key drops mark.

3.5 UI ​

  • PriceBasisToggle and the mark state are gone. DISPLAY_MARK is a module constant so every model helper that still takes a mark keeps working unchanged.
  • PortfolioChart draws two series from the same points array: total return keeps the green Area + Line treatment and stays the headline, tooltip primary and dot carrier; accrual is a thin muted line with no area and no dots, drawn UNDER the accent so a crossing never hides the primary. Both connectNulls={false}.
  • chart-series.ts helpers take a series key. yScaleOf spans BOTH series by default (sizing the domain on the primary alone would push the accrual line outside the plot on any book down on price); isolatedIndices is asked about one series, because the plot draws dots on the primary alone.
  • src/components/portfolio/series-labels.ts owns the two line names and the one explainer, because the lazily-imported chart and the card around it both need them. Its copy contract is pinned in series-labels.test.ts.
  • Each series re-bases to its own first in-window value, so the inter-line gap reads as "the valuation effect since the window opened". The chart explainer says exactly that.

3.6 Fixture, tests, e2e ​

  • The fixture's market/redemption ratio is a per-leg CONSTANT, so total return and accrual are numerically identical on every previously seeded leg and the two lines render exactly on top of each other. A spec asserting they differ would pass on a build that plotted one series twice. scripts/fixture/seed.sql therefore seeds a separate, fully isolated wallet (its own account uid, its own position key, no flow rows) holding 150,000 sUSDe on Aave whose market discount widens from par to 2% and then takes a single-tick write-down to 10% at 2026-05-01. Over the history it earns about +$8.3k and is worth about $9.9k less than it was, so the two lines end on opposite signs.
  • New unit tests: total-return.test.ts, pinned-marks.test.ts, eth-book-flows.test.ts, series-labels.test.ts, plus additions to pnl.test.ts, assemble.test.ts, aggregate.test.ts, api-data.test.ts, api-routes.test.ts, chart-series.test.ts, PortfolioDashboard.test.tsx. Every new file is appended to package.json's explicit test list, or it silently never runs.
  • New e2e cases against the seeded wallet: both lines drawn and visibly apart, the write-down drawing on total return and never on accrual, the headline stating total return while the table states what the position earned, nothing offering a valuation to choose (and the endpoint ignoring a stale one), and the explainer's three claims. Each was mutation-tested.

3.7 Docs ​

docs/metrics.md gained M23 through M26 and the accepted boundaries, and the pre-existing staleness the research found was corrected in the same pass (the wedge is served, not rendered; the per-book headline tiles no longer exist). docs/portfolio.md's chart section was rewritten (two lines, gap semantics, the restatement, the removed toggle) and the four passages that described a figure as "market-valued whatever the price-basis toggle says" lost a referent that no longer exists. docs/database.md is untouched: there is no schema change.


4. Deviations from the plan ​

Every deviation below was made during implementation and is recorded here rather than argued in a commit message.

Stage 1 (engine, marks, flows) ​

  1. The seizure scope is WIDER on the total-return line than plan §1.3 allowed. The plan said total return keeps today's mechanism exactly. It cannot: today's mechanism skips only the leg the liquidation row NAMES, which is the collateral. Under value-series attribution the DEBT leg's drop is a reduction of a negative and prints a phantom GAIN the size of the repayment beside the penalty (verified: the Aave carry fixture would read +36 instead of −4, the Morpho one +250 beside a −350 penalty). seizureScopeKeys() therefore also covers the Morpho market prefix (Morpho's liquidate emits ONE log and no separate repayment) and every key carrying a same-transaction liquidation mechanic (which is how Aave and SparkLend reach their debt-token burn); Fluid was already covered by its NFT group prefix. The ACCRUAL series' seizure handling is byte-identical to before. This is the piece a reviewer should look hardest at.
  2. Plan §5.4 (trimIdleLeadIn's extra guard) was implemented in stage 1, though section 5 was stage 2's scope, because the function lives in pnl.ts and without it a lead-in span of accrual: 'none' par legs can be trimmed away while carrying real mark-to-market P&L.
  3. The unpriced-flow arithmetic gate is total-return only, exactly as plan §1.2 scopes it. The accrual line keeps today's behaviour, where a value/pt leg with an unpriced flow between two present endpoints still books the whole movement as yield. That is a pre-existing defect on the accrual line; fixing it would move accrual numbers this work was not chartered to move, so it is left and reported (and now documented under M24).
  4. BookMarkHeadline.cumulativeYield reads 0 rather than the negative penalty over a seizure-only history, because the accrual line no longer takes the penalty. Wire SHAPE unchanged; the field is rendered nowhere.
  5. Two non-persisted fields were added to implement plan §2.3: MarketContext.priceInBookMirror and SnapshotRow/SnapshotRowLite/ LegSnapshot.valueMarketMirror. No schema change, no migration, nothing written to Postgres.
  6. logCoverageAnomalies surfaces BOTH series with a series label and independent throttling. Slightly beyond "minimal compiling change", but it is the read-path observability an operator needs to tell a real drawdown from a missing leg, and it touches no wire shape.
  7. package-lock.json had a pre-existing version drift on origin/staging (0.29.0 against package.json's 0.31.0) which npm install corrected. Reverted, so it stays out of this PR.
  8. The JIT native-ETH flow's supersession is by the cron's provisional sweep, not by primary key. The synthetic hash keys on the interval start (last stored snapshot ts + 1) while the cron keys on the aligned window, so the settled row does not land on the same primary key. Both rows describe the same capital, so a read taken between the cron's settled write and its sweep (adjacent steps of one run) double-counts it on one interval and self-heals. Aligning the two keys would need the cron's stored convention to change, which orphans every ETH row already in the ledger. Documented at the derivation in flows.ts.

Stage 2 (wire, client, UI) ​

  1. Marker valuation and HistoryResponse.mark are now constants. Flow and liquidation markers used to be valued in the requested mark; with the mark ignored they are valued at MARKET and the response reports mark: 'market'. The field is kept on the wire for shape stability (no client reads it) and re-documented as "the basis the markers are valued on". Neither marker is rendered today, so nothing on screen moves.
  2. getHistory / getHistoryAll dropped their mark parameter entirely rather than accepting-and-ignoring it at the function level. The route still accepts the query param and ignores it, which is what plan §4.1 requires; carrying a dead argument through two server functions and six client call sites would have been the rollout hazard the plan warns about.
  3. historyPointsFromBuckets was extracted as an exported pure function. The wire's channel crossing was two adjacent lines inside a DB-bound function and therefore untestable; both directions are plausible-looking cumulative curves in the same unit, so getting it backwards would swap every reader's two lines silently. Now pinned by a spec with magnitude-unique fixtures.
  4. windowSeries was exported from PortfolioDashboard.tsx so the per-series window re-basing could be tested.
  5. src/lib/seo.ts's /portfolio description was updated, outside plan sections 4-5. It promised "yield-only performance ... with market and redemption marks" as the page's meta and OpenGraph copy, which the removal makes false.
  6. isolatedIndices is asked about ONE series, not unioned across both.yScaleOf spans both by default; the dot renderer runs on the primary line only, because plan §5.2 forbids a second per-point dot renderer and a dot in the union set would promise a mark the plot never paints.
  7. The e2e locator updates landed in stage 2's commit, though e2e is nominally stage 3's, because plan §6.3 is explicit that the panel-locator call sites move in the SAME commit as the rename.

Stage 3 (fixture, e2e, docs) ​

  1. The seeded wallet is its own ACCOUNT, not a third wallet on the existing one. Plan §6.1 asked for "a NEW isolated wallet". Adding it to the account the suite signs in as would have moved every aggregate figure the existing specs assert (wallet counts, hero totals, position counts), which §6.1 also forbids. It therefore has its own account uid and the new specs sign in as it explicitly. Not one figure any existing spec asserts moves.
  2. The seed carries the drift and the impairment step, and not the other three shapes the research listed. The research suggested a fixture also needs a null market tick on an otherwise-present leg, an on-grid liquidation, and a leg that disappears for a tick and returns. Plan §6.1 itself asks only for the drift and the step, and each of the other three would put a GAP or a marker in the middle of the chart the "two lines visible and diverging" spec reads. All three are covered where they can actually fail, in the engine's unit tests. Recorded as a deliberate gap in fixture coverage.
  3. None of the ~14 e2e panel-locator call sites moved. Plan §6.3 budgeted for them on the assumption that the panel xpath ('daily cumulative') would change with the rename. It did not: the footer caption is still true of two cumulative lines. Only the figure regex changed (cumulative net yield to total return), which is a single helper.
  4. Four passages outside plan section 7's enumeration were corrected, because the toggle's removal took their referent away: the outside-coverage section, the LTV gauge, the risk readout and the activity ledger each described a figure as market-valued "whatever the price-basis toggle says".

5. Accepted boundaries in v1 ​

Each is pinned by a test and documented under M23/M24, not silently absorbed.

  • A position opened and closed inside one interval has no endpoint snapshot and forgoes that window's realized P&L on BOTH lines.
  • A PT whose TWAP reverts (a market too young to quote) books 0 on total return for that interval while accrual keeps accreting at the entry-implied rate.
  • An idle par balance draws its own quote noise on total return. A real depeg is genuine total return; the basis points around par are accepted noise. Assets with no standing market-price coverage have no such exposure at all (M26).
  • The accrual line for a Fluid smart leg carries pool displacement by construction. For a smart side, "accrual" means what the pool produced by being held.
  • Execution below the price at the exit block never draws. An exit IS valued at its own block, so for a value or pt leg the move between the last reading and the close is realized P&L and is attributed; what is uncaptured is selling for less than the quote at that block. An index leg's exit draws nothing at all (M27).
  • A seizure whose penalty could not be valued draws nothing. The seized legs are skipped and no penalty replaces them, so the line is flat across the event rather than booking a magnitude nobody can price (M9). The chart still marks it.
  • Untracked-token conversions book one-sided, including same-wallet moves into untracked 4626 shares: the perimeter is the tracked-position set, not the wallet.
  • Cross-book seizures net only within a book, unchanged from M4.

6. Verification ​

  • npx tsc --noEmit clean; npm test green (2,016 cases after the review pass, 0 failures, 0 skipped); cd docs && npm run build green, which is the dead-link gate for PAGES (it does NOT check anchors; verified empirically, see the review pass).
  • The e2e suite was run in full against an isolated fixture Postgres and dev server (not the shared defaults, which several worktrees contend for), all four viewport projects: 512 passed, 0 failed, 0 skipped. A skipped case is not a passing case, so the skipped count is read alongside the passed one. After the review pass the /portfolio file was re-run the same way: 388 passed, 0 failed, 0 skipped.
  • The load-bearing new assertions were mutation-tested: wiring both chart lines to one series, and swapping the two channels on the wire, each redden the cases that exist to catch them. The review pass's new assertions were mutation-tested the same way (removing the cross-series guard reddens both of its cases; removing the M27 branch reddens five).
  • The merged dashboard was driven in a real browser at 1360px and 900px against the dislocated wallet: no horizontal overflow, no page or console errors, the card reading the total-return figure over a legend naming both lines, and the holdings row reading its yield beside it.
  • The column and the chart's second line now tie exactly on that wallet, which is what the docs claim and what they did not do before the review pass: the served history's final accrual is +8291.281542188775 and the one position's Yield earned is +8291.281542, against +7953.513319 on the market basis it used to read. The total-return figure moved from −9949.70 to −9937.85 on the same wallet, which is M27 removing the interest its Aave leg's flow values had been netting away.

Open item for review — RULED ON ​

The per-position "Yield earned" column now takes no valuation basis at all. It was evaluated in the market mark, like the rest of the tables. That is not "the same method on a different base": for a value or pt leg the attribution differences the mark's OWN value series, so at market it is that leg's TOTAL return, price move included. Every bare yield-bearing wrapper, every Pendle PT, every wrapper held as collateral and every Fluid smart leg was affected — measured on a 150,000 holding whose discount widens 2% in one window, the column read −1,900 while the chart's accrual line read +100 and the Dislocation P&L cell on the same row read −2,000, with the tooltip between them stating the two figures are separate and neither contains the other. The column is now the accrual attribution outright (displayYield / legYield take no mark), which is what plan §1.7 asked for and what makes the copy true.


7. Ops ​

No migration. No cron or schema change. One release-time step, now with two reasons rather than one:

Run the stored-mark repair (scripts/repair/remark-io.ts) as a dry run on prod, read the diff, then execute.

  1. Deep history. Snapshot rows written before the minute-price mirror existed carry market values with tens of basis points of noise in them, and that noise now draws on the total-return line. Whether the repair has ever run on prod is unverified.
  2. The M26 boundary (new in the review pass). The 19 par assets with no standing market-price coverage are written at par from the first tick after the deploy, while every row behind it carries whatever opportunistic bar existed. Left alone, the first interval spanning the deploy books the difference as return, once, per holder: a $500k USDS balance whose last pre-deploy bar sat 30bps off par steps the USD book's total-return line by ~$1,500, with no economic event behind it, and a running-sum line never washes it out. The repair now resolves "pinned" with the same wide predicate the writers use, so running it IS the restatement. Running it with the old narrow predicate would have done the opposite; that is fixed on this branch, so the repair must be run from this release's code, not an older checkout.

Rollout note (user-visible, self-clearing). A browser still holding the previous bundle keeps rendering the response's primary series under the retired label ("Cumulative net yield"), which is now the total-return line, and its price-basis pills refetch to a byte-identical response so the control looks stuck. It clears on a hard reload. Worth a line in the release note beside the restatement callout, since the mislabelled figure is a money figure.

Release-notes caveat: this work ships on top of #550, #552, #553 and #554, none of which is on prod, so the release that carries it bundles several rewrites of the same files. A prod regression in /portfolio has more than one candidate cause.


8. Review pass (independent reviewers, fixes applied) ​

Four reviewers read the branch. Every finding below was reproduced against the running engine before being fixed; the dispositions are on the PR.

  1. BLOCKER, fixed — a leg suspended on ONE series booked minus its whole flow on the other. buildBookCurve unions both suspension maps into the per-interval key set (so a bridged span keeps every flow that lands inside it), but seriesLegStep only handled a key absent at both endpoints when THIS series held the suspension. A key present only because the OTHER series suspended it fell through to attribute(undefined, undefined, netLegFlow), which computes v0 = v1 = 0 and returns −netLegFlow. Reproduced on the branch: a 137,000 re-deposit while the leg was dark printed as −137,000 of accrual with zero anomalies raised, and a 990 withdrawal printed as +990 of total return in the redemption curve against 0 in the market curve — so the served total-return line was not mark-independent either, contradicting the property the read path relies on when it builds only one curve. The two maps diverge routinely: a mirror-bar miss prices a leg in one mark and not the other. Fixed by short-circuiting to 0 for a key this series neither holds at an endpoint nor has suspended itself. Two tests, one per direction, both mutation-tested.
  2. BLOCKER, fixed (M27) — Aave/Spark flow values bundle accrued interest, and the total-return line netted it away. Plan §1.6 required this invariant be verified numerically. It was broken. The aToken/vToken Transfer value is amount ± balanceIncrease, the interest accrued since the user's last touch, while the level series already carries that interest. Reproduced: 1,000,000 supplied, held six months at +2.5%, 500,000 withdrawn → −24,994.75 of total return against a genuine +10.25; a full exit → a loss of exactly the accrued interest; a borrow on a long-held debt leg → a gain of the same size, so a financed position was wrong on both legs in opposite directions. No guard fires (the leg is present at both endpoints). Fixed by attributing an index leg flow-free, from its composed-index ratio times its price relative on the capital present at the interval start (M27 in metrics.md). The line keeps the price move, which is the whole reason index legs are drawn on it; what it gives up is mid-window capital's price move until the next window, the same 6h-granularity convention accrual already documents, and an index leg's exit, which now holds at the last mark. The shipped test that "verified" the invariant had fixtured a flow value Aave never emits; it is replaced by four that use the real one, all mutation-tested.
  3. MAJOR, fixed — the JIT native-ETH row could stand beside the cron's settled row for a full 6h window. The two are keyed differently, so supersession fell to the provisional sweep, which is bounded above by anchor − 64. A refresh in the ~13 minutes before a tick's anchor writes a row above that bound: both rows then land in ONE curve interval, the leg's net flow doubles against a single value step, and the ETH book's total-return line prints the whole balance move as a loss until the next tick. Fixed with a third delete in flow-basis.ts, bounded by the INTERVAL the settled row re-derives rather than by blocks, issued in the same transaction as that write and for every wallet the tick observed ETH for (including those whose net delta was zero, where no settled row is written and a partial JIT diff would otherwise stand alone). Deviation 8's "adjacent steps of one run" is now accurate.
  4. MAJOR, fixed — the release-time repair would have re-marked the newly par-pinned assets off opportunistic bars. M26 widened the pin to 19 assets (USDS, PYUSD, RLUSD, FRAX, crvUSD, FDUSD, TUSD, USDD, USD0, USR, USDf, USDG, GUSD, eUSD, rUSD, AUSD, USD1, LBTC and the native-ETH sentinel) and every live writer uses the wide predicate; both repair entrypoints still used the declared-only one. Run that way, the one prescribed ops step would have cemented a valuation-method change inside one series. Both now use marketPinnedToRedemption, which also makes the repair the RESTATEMENT that removes the one-off step those assets would otherwise take at the deploy boundary (their stored rows carry bars, their new rows carry par). The ops step below is restated accordingly, and a test pins both callers.
  5. MAJOR, fixed — the chart explainer denied a real number. "Closing a position moves neither line" is false: an exit flow is valued at ITS OWN block, so a value leg last marked at 100,000 and sold three hours later at 90,000 books −10,000 on both lines, correctly, and the branch's own spec pins the mirror case. The copy now says what an exit does and what it does not capture (execution below the price at that moment). Unit and e2e assertions moved with it.
  6. MINOR, fixed — a view transition at the live tip valued its synthetic flow with the aggregator mid while the line values the leg with the mirror bar. flowAt now uses the stored-method value, so the birth interval of a leg arriving at the tip cannot draw the mid-versus-bar half-spread.
  7. MINOR, fixed — the live refresh's native-ETH lookup scanned every partition. The query omitted venue, so the spine's key prefix stopped at (chain_id, wallet) and Postgres read and sorted the wallet's whole row set to answer a LIMIT 1, on the path a signed-in user triggers. Adding venue = 'wallet' completes the prefix. No ts floor: a long-dark wallet still has a real balance to diff against, and returning null there would leave its whole balance change unnetted.
  8. MINOR, fixed — the retired "Cumulative net yield" label survived on the building-history chart, which renders inside the same card whose kicker and legend name the two current lines. Now reads the shared label; pinned by a render test.
  9. MINOR, fixed — getHistoryAll crossed the two wire channels inline twice more, untested, while the docs claim exactly one crossing site. Both directions are now exported pure functions with a round-trip test, so a later half-fix cannot serve an aggregate response with its two lines swapped.
  10. MINOR, documented — a seizure whose penalty could not be valued draws nothing at all. The seized legs are skipped and no penalty replaces them, so the line is flat across the event rather than booking a magnitude nobody can price (M9). Pre-existing behaviour, now an explicit accepted boundary under M25 instead of an unstated one.
  11. MINOR, fixed — "accrual has no price effect in it, ever" overclaimed. A Fluid smart leg's accrual carries the pool's displacement by construction, which the same docs section admits. Both the tooltip and M24 now say what the line IS (what a position produces by being held, rather than what the market will pay for it), which stays true of a smart pair.
  12. NIT, recorded — the docs build is not an anchor gate. Verified on a throwaway copy: a link to a non-existent PAGE fails the build, a link to a non-existent ANCHOR passes silently. Nothing in this PR is broken by it (the one renamed heading is linked from nowhere), but the next rename of a cross-referenced heading should not assume it is covered.
  13. NIT, recorded — a browser still holding the pre-release bundle renders the total-return series under the retired label ("Cumulative net yield") until a hard reload, and its price-basis pills refetch to a byte-identical response, so the control looks broken. Bounded and self-clearing; it belongs in the release note beside the restatement callout, and is now in the ops section below.

Refuted, with the reason: the reviewers' claim that the row's realizedApy carries the same defect as the yield column. displayApy is the ADVERTISED (quoted) rate re-weighted for the mark, not a realized figure; realizedApyMarket is on the wire and rendered nowhere.

Private documentation. creddit.xyz