QA Anvil time-warp vs wall-clock swapDeadline breaks buyViaKumbaya (AnvilKumbayaFixture Expired)
## Summary
Follow-up from [#75](https://gitlab.com/PlasticDigits/yieldomega/-/issues/75) (indexer `entry_pay_asset='eth'` evidence via live `buyViaKumbaya`) and the [#82](https://gitlab.com/PlasticDigits/yieldomega/-/issues/82) note: on a **QA Anvil stack** that had used **`evm_increaseTime` / `anvil_increaseTime`** to move chain time, the **last-mile signed `buyViaKumbaya` tx** reverted on **`AnvilKumbayaRouter` `Expired()`** even though **source, integration tests, and Postgres roundtrip already PASS** and the **frontend reached the swap router** with correct wiring.
This is a **chain-time vs deadline encoding** mismatch in the **Kumbaya single-tx path** under **artificial time warping**, not a user-facing bug on **normal-clock** networks.
## Root cause (repo investigation)
1. **Swap deadline is wall-clock based**
`swapDeadlineUnixSec` in `frontend/src/lib/timeCurveKumbayaSwap.ts` sets
`Math.floor(Date.now() / 1000) + bufferSec` (default buffer **600s**).
The same helper is used from `useTimeCurveSaleSession`, `useTimeCurveArenaModel`, and `timeCurveKumbayaSingleTx.ts` when building `buyViaKumbaya(..., swapDeadline, ...)`.
2. **The Anvil fixture enforces deadline against `block.timestamp`**
In `contracts/src/fixtures/AnvilKumbayaFixture.sol`, `AnvilKumbayaRouter.exactOutput` does
`if (block.timestamp > params.deadline) revert Expired();`
before executing the swap.
`TimeCurveBuyRouter.buyViaKumbaya` passes that `swapDeadline` through unchanged to the Kumbaya router (`TimeCurveBuyRouter.sol` → `kumbayaRouter.exactOutput`).
3. **QA stacks advance `block.timestamp` without moving the browser clock**
e.g. `contracts/script/anvil_rich_state.sh` calls `cast rpc anvil_increaseTime` in `warp_to_at_least` / `warp_past_timcurve_deadline` to bring **chain time** to (or past) onchain `TimeCurve.deadline` and for post-end flows.
After a large warp, **`block.timestamp` can be many minutes or hours *ahead* of `Date.now()`**, so a deadline computed as `Date.now() + 600` is **still in the past** relative to `block.timestamp` at tx execution → **`Expired()`**.
4. **Contrast: elsewhere the app already reasons about chain time**
e.g. `useTimeCurveArenaModel` documents that display / phase logic should track **`block.timestamp`**, not wall-clock drift (`blockChainSec`, `effectiveLedgerSec`). The Kumbaya **swap deadline** is still the exception: it does **not** use head block time + buffer.
## Why mainnet / typical testnets are unaffected
On live chains, **head `block.timestamp`** tracks **real time** closely; `Date.now() + 600` is usually **ahead** of inclusion-time `block.timestamp`, so the Uniswap-style deadline check passes. The failure mode is **specific to Anvil (or any environment) that jumps chain time far ahead of wall time**.
## Proposed solutions (pick or combine)
### A — **Align swap deadline with chain time (recommended for correctness)**
- **Change:** When the wallet is connected to an RPC, compute
`swapDeadline = latestHeadBlockTimestamp + bufferSec`
(and optionally a small client-side margin), instead of `Date.now() + bufferSec`.
- **Where:** Centralize in `timeCurveKumbayaSwap.ts` and/or the call sites in `useTimeCurveSaleSession`, `useTimeCurveArenaModel`, and `submitKumbayaSingleTxBuy` so **single-tx and UI-built txs** share one rule.
- **Data source:** `useBlock({ watch: true })` in components, or `getBlock` / `getBlockNumber` in `submitKumbayaSingleTxBuy` via the wagmi `Config` (must run **immediately before** building calldata, like other submit-time invariants in [#82](https://gitlab.com/PlasticDigits/yieldomega/-/issues/82)).
- **Tradeoff:** Slightly more RPC coupling; on very stale heads, deadline follows chain (still desirable for onchain `block.timestamp` checks).
### B — **QA / infra: ordering and expectations**
- **Document** in the relevant playbooks (E2E / manual QA, not a code change in this task): for stacks where **`anvil_increaseTime`** is used, either:
- run **`buyViaKumbaya` live evidence *before* any large time warp** in the session, **or**
- use a **fresh Anvil** (or `anvil_reset` / equivalent) for the Kumbaya proof after warping, **or**
- **avoid warping** for the slice that must produce the indexer row (aligns with “normal-clock chains unaffected”).
- **Scripts:** Any automation that currently chains `anvil_rich_state.sh` (or similar) *before* browser Kumbaya flows should be reordered or split.
### C — **Fixture-only relaxation (low priority, risks prod parity)**
- **Option:** Dev-only / test-only router behavior (e.g. mock “infinite” deadline) **weakens** parity with production routers that enforce deadlines — **not recommended** unless only used in isolated tests, not the shared AnvilKumbaya fixture that mirrors production interfaces.
## Concrete follow-ups (for the chosen approach)
- **If A:** Implementation + a short regression test matrix: **wall-aligned chain** (today’s behavior) **and** a manual or scripted case where `anvil_increaseTime` is applied, then a **fresh** `getBlock` + `buyViaKumbaya` still succeeds.
- **If B:** Update the **agent/QA** workflow for [#75](https://gitlab.com/PlasticDigits/yieldomega/-/issues/75) evidence and cross-link from [#82](https://gitlab.com/PlasticDigits/yieldomega/-/issues/82) / [#75](https://gitlab.com/PlasticDigits/yieldomega/-/issues/75) as needed.
## Cross-links
- Indexer: `entry_pay_asset` for `BuyViaKumbaya` — [#67](https://gitlab.com/PlasticDigits/yieldomega/-/issues/67)
- Submit-time sizing — [#82](https://gitlab.com/PlasticDigits/yieldomega/-/issues/82)
- `TimeCurveBuyRouter` / Kumbaya — [#65](https://gitlab.com/PlasticDigits/yieldomega/-/issues/65), [#66](https://gitlab.com/PlasticDigits/yieldomega/-/issues/66), [#78](https://gitlab.com/PlasticDigits/yieldomega/-/issues/78)
issue