Skip to content

test(e2e): riverdale economy - multi-party, versioned fibers, assets#190

Merged
scasplte2 merged 6 commits into
mainfrom
feat/riverdale-economy-e2e
Jun 25, 2026
Merged

test(e2e): riverdale economy - multi-party, versioned fibers, assets#190
scasplte2 merged 6 commits into
mainfrom
feat/riverdale-economy-e2e

Conversation

@ottobot-ai

Copy link
Copy Markdown
Collaborator

What this is

Phase 1 of promoting the in-process RiverdaleEconomyStateMachineSuite (2,843-line unit test) into a real-cluster e2e, on par with rule110 / sigma-mixer / staked-oracle-pool. This PR lands the harness enablement + a thin 3-fiber de-risk slice; the full 6-fiber economy is the follow-up.

Design doc: docs/e2e/riverdale-economy-e2e-design.md.

Why harness work, not just JSON

The riverdale economy is fundamentally cross-fiber (manufacturer→retailer shipments, broadcasts, tax sweeps) with assets evolving over time. The combiner already does cross-fiber _triggers/_spawn and real asset custody — but the e2e runner was single-fiber-per-flow and had no way to drive or observe any of it. So ~70% of the work is general-purpose, back-compatible harness extensions (every new step field is optional; existing examples are unchanged).

Harness extensions (376d27a)

  • Named multi-fiber: create … as:"alice" mints + registers a fiber alias; any step fiber:"alice" targets it.
  • Per-party signers:["alice"]: signs with a wallet subset — the proofs ARE the owners/authorizers, so this is the party model (and enables wrong-party negative tests).
  • Poll-only assertState / assertAsset: observe a fiber changed indirectly by a cross-fiber trigger, or real on-chain asset custody after a _transferAsset (reads /assets/{id}/state-proof).
  • Asset ops createAssetPolicy / mintAsset / applyMorphism: plain-JSON messages (the chain JAR decodes them; the published SDK doesn't yet export asset builders, and batchSign signs any object) — no SDK change needed.

The slice (48b1eab) — e2e-test/examples/riverdale-economy/

One causal flow over 3 party-owned fibers (alice=manufacturer, bob=retailer, carol=consumer):

  • cross-fiber _triggersfulfill_order fires the retailer's receive_shipment (no declared dependency needed; only FiberPolicy.acceptedCallers, unset = any caller).
  • real _transferAsset custody — the GOODS instance moves manufacturer-fiber → retailer-fiber in the same transition, asserted via assertAsset. Recipient is a bare UUID string (parseRecipientAssetHolder.Fiber).
  • versioned upgrade — retailer verified-bound to retailer.machine@1.0.0, upgraded to @2.0.0 with a migration adding loyaltyPoints, then a v2-only redeem_loyalty.
  • two real assets — GOODS (T|C) into the manufacturer fiber; RVD (Fungible) into carol's wallet; both custody reads asserted. First e2e to exercise real asset custody (staked-oracle's assets are nominal).

All wire shapes were verified against chain sources (quoted in example.ts header + README).

Validation

The harness + slice are tsc-clean (the remaining e2e-test type errors are pre-existing SDK-staleness in untouched files; the suite runs via tsx, no typecheck gate). Local cluster bring-up drifted across retries in my environment (stale GL0 genesis surviving down --clean), so CI — fresh clone, no accumulated state — is the validation path. New isolated lane: riverdale-economy (concurrency 1, wallets alice,bob,carol, ordinalThreshold 15).

Watch in the CI run (first-time-on-cluster unknowns)

  • createAssetPolicy confirm: the /registry/{name} response shape for an AssetPolicyPackage (my confirm predicate scans any target variant's versions.versions).
  • assets/{id}/state-proof .record custody reads (holder/amount/sequenceNumber).
  • SupplyPolicy with omitted optionals + morphisms:{} round-tripping the dropNulls signing canonical.

Follow-up (not in this PR)

Full economy fan-out: consumer/bank/fed/gov fibers, RVD lending + commerce + tax-sweep, a negative-test flow, auction _spawn, Fed v1→v2, and extra morphisms (Fractionalize/Stake/Burn).

🤖 Generated with Claude Code

ottobot-ai and others added 2 commits June 25, 2026 13:51
Phase 1 harness enablement for the Riverdale-economy e2e — the riverdale
unit test is cross-fiber (manufacturer->retailer triggers, fed->bank
broadcast, real asset moves), but the runner was single-fiber-per-flow.
These are general-purpose, back-compatible (every new step field is
optional; single-fiber flows are unchanged):

- Named multi-fiber: a `create` step with `as:"alice"` mints a fresh
  fiberId and registers an alias; any step with `fiber:"alice"` (or a raw
  id) targets it. session.cid remains the default.
- Per-step `signers:["alice"]`: signs with a wallet SUBSET, so the proofs
  (== owners/authorizers) model the acting party; enables wrong-party
  negative tests. Omit = all wallets (today's behavior).
- Poll-only `assertState` / `assertAsset`: observe a fiber changed
  INDIRECTLY by a cross-fiber trigger, or real on-chain asset custody
  after a `_transferAsset`/morphism (reads the `/assets/{id}/state-proof`
  `.record`). No transaction; standard retry budget.
- Asset ops `createAssetPolicy` / `mintAsset` / `applyMorphism`: plain-JSON
  messages (the chain JAR decodes the variants; the published SDK doesn't
  yet export asset builders, and batchSign signs any object) confirmed via
  registry / asset-proof reads. First e2e to exercise REAL asset custody.

New: lib/assertHelpers.ts (deepEqual/selectSigners/holderMatches/pollers),
lib/asset/{createAssetPolicy,mintAsset,applyMorphism}.ts.
Design: docs/e2e/riverdale-economy-e2e-design.md.

runner.ts + new files are tsc-clean; the remaining e2e-test type errors are
pre-existing SDK-staleness in untouched files (not a typecheck gate — runs
via tsx).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5TUSJPD8FCtJagf7siXgt
The first e2e to exercise REAL asset custody and cross-fiber orchestration
— a thin 3-fiber "supply-chain + upgrade" slice that de-risks the Phase 1
harness extensions before the full economy fan-out.

One causal flow over 3 party-owned fibers (alice=manufacturer,
bob=retailer, carol=consumer):
- cross-fiber `_triggers`: manufacturer.fulfill_order fires the retailer's
  receive_shipment (no declared dependency needed — only FiberPolicy
  acceptedCallers, unset = any caller; verified in EffectExtractor /
  TriggerDispatcher).
- real `_transferAsset` custody: the GOODS instance moves manufacturer
  fiber -> retailer fiber in the SAME transition, asserted via assertAsset.
  Recipient is a bare UUID string (parseRecipient maps it to
  AssetHolder.Fiber), NOT an AssetHolder object.
- versioned upgrade: retailer is verified-bound to retailer.machine@1.0.0,
  upgraded to @2.0.0 with a migration adding loyaltyPoints, then drives a
  v2-only redeem_loyalty.
- two real assets: GOODS (T|C) minted into the manufacturer fiber, RVD
  (Fungible) minted to carol's wallet; both custody reads asserted.

17 files; uses the new runner steps (as/fiber/signers/assertState/
assertAsset/createAssetPolicy/mintAsset). All wire shapes verified against
the chain sources (quoted in the example.ts header + README).

CI: own isolated `riverdale-economy` lane (concurrency 1, wallets
alice,bob,carol, ordinalThreshold 15) — local cluster bring-up drifted
across retries, so CI (fresh clone, no stale state) is the validation path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5TUSJPD8FCtJagf7siXgt
@ottobot-ai ottobot-ai changed the title e2e: Riverdale economy — multi-fiber + real assets + versioned upgrade (de-risk slice) test(e2e): riverdale-economy slice - multi-fiber + real assets Jun 25, 2026
@ottobot-ai ottobot-ai changed the title test(e2e): riverdale-economy slice - multi-fiber + real assets test(e2e): riverdale economy - multi-party, versioned fibers, assets Jun 25, 2026
ottobot-ai and others added 4 commits June 25, 2026 16:06
Phase 2 (part 1/2) — the new economy state machines, authored against
PHASE2-CONTRACT.md. Inert until wired into example.ts (next commit), so the
green slice flow is unaffected (retailer v1/v2 gain a process_sale transition;
same file used for publish+create keeps the verified-binding logicHash aligned).

- consumer: ACTIVE/debt_current/marketplace_selling — loan_funded, buy
  (triggers retailer.process_sale + transfers RVD shard), make_payment
  (triggers bank.payment_received + transfers RVD), pay_taxes, list_item
  (_spawn auction child; accept_bid triggers parent.sale_completed), sale_completed.
- bank: operating/loan_servicing — rate_adjustment, underwrite (triggers
  consumer.loan_funded + transfers RVD), payment_received.
- fed: v1 set_rate (triggers bank.rate_adjustment); v2 adds emergency_lending
  (v2-only) + migration. Versioned like the retailer.
- retailer v1+v2: + process_sale (transfers GOODS to buyer + revenue).

Still TODO (part 2): gov fiber, events, asset choreography (mint RVD to bank,
Fractionalize, Burn), and the two example.ts flows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 2: scale the proven de-risk slice into the full 6-party economy, on
the now-validated multi-fiber + asset harness.

Six party-owned fibers (alice=manufacturer, bob=retailer, carol=consumer,
dave=bank, erin=fed, frank=gov) in one causal flow (P0..P12):
- supply chain: manufacturer.fulfill_order -> retailer.receive_shipment +
  _transferAsset GOODS
- monetary policy: fed.set_rate -> bank.rate_adjustment (broadcast-capable)
- lending: bank.underwrite -> consumer.loan_funded + _transferAsset RVD
  (bank fiber -> consumer fiber)
- commerce: consumer.buy -> retailer.process_sale + RVD consumer->retailer
  + GOODS retailer->consumer
- servicing: consumer.make_payment -> bank.payment_received + RVD
- TWO versioned upgrades: retailer.machine AND fed.machine v1->v2 with
  migrations + v2-only transitions (redeem_loyalty, emergency_lending)
- governance tax sweep: gov.collect_taxes broadcasts pay_taxes to all three
  taxpayers in one tx; consumer remits RVD to the gov fiber
- auction: consumer.list_item _spawn's a child auction; bid -> accept ->
  triggers the parent's sale_completed
Plus FLOW 2: graceful negative tests (wrong-party, replay, mint-over-cap,
non-monotonic publish -> all ML0 rejections, state unchanged).

Assets: GOODS + RVD minted, transferred fiber<->fiber across every economic
edge, custody asserted via assertAsset throughout.

Morphisms: fiber-held ApplyMorphism is R1-rejected (AssetCombiner
requireWalletHolder gates ALL kinds before applyKind), so RVD payment legs
are separate whole instances moved via _transferAsset. STAKE is wired LIVE
(non-consuming: bumps seq, record survives -> the runner's seq-advance
confirm observes it). FRACTIONALIZE + BURN are CONSUMING (they remove the
source record), which the current applyMorphism confirm can't observe, so
they ship as ready body files + commented steps; wiring them live needs an
additive runner consuming-morphism confirm mode (next).

CI: riverdale-economy lane timeout 55, wallets alice,bob,carol,dave,erin,
frank. tsc-clean; all JSON valid.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5TUSJPD8FCtJagf7siXgt
First full-economy CI run: 58/60 of FLOW 1 passed — every cross-fiber trigger,
real _transferAsset custody (lending/commerce money+goods/servicing), the
broadcast tax sweep, the spawned auction, and BOTH v1->v2 versioned upgrades
worked on a real cluster. Two follow-ups, both informed by that run:

- Defer the wallet-context morphisms (Stake/Fractionalize/Burn). STAKE hit a
  DL1 HTTP 400: the runner waitForDl1Syncs FIBER commits but never waits for an
  asset's assetCommit to reach DL1 after a mint, so applyMorphism raced DL1.
  Plus consuming morphisms (Fractionalize/Burn) can't be confirmed by the
  source-seq-advance predicate. Both need small runner additions — tracked as
  the morphism fast-follow; body files + ids + policy remain shipped.
- Drop the wrong-party negative: the run proved a primary state-machine
  transition is NOT owner-gated (the guard is the gate). Ownership gates
  registry ops, script callers, asset holders (R1), and spawned children — not
  arbitrary transitions. Kept the three valid negatives (replay, mint-over-cap,
  non-monotonic publish).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The first full-economy run (58/60) showed the asset-op path lacked the DL1
sync the fiber path has: the runner waitForDl1Sync's FIBER commits but never
waited for an asset's `assetCommit` to reach DL1 after a mint. A subsequent
applyMorphism is structurally validated at DL1 against OnChain.assetCommits
(AssetRules.applyMorphismStructural — unknown asset is a HARD reject), so a
mint→morphism on the same asset raced the ML0→GL0→DL1 propagation and 400'd
"unknown asset" (hit even non-consuming STAKE).

- Add waitForDl1AssetSync (asset analogue of waitForDl1Sync): after a
  mintAsset confirms at ML0, gate on every DL1 node carrying
  assetCommits[assetId] before the next step.
- Re-enable the P12 STAKE morphism live: mint RVD into dave's wallet, STAKE
  it (R1: signer == holder), assert the seq advanced (non-consuming: holder +
  amount survive). STAKE keeps the morphism pillar live in the economy.

FRACTIONALIZE + BURN stay deferred (CONSUMING — they remove the source
record, which the source-seq-advance confirm can't observe; they need a
terminal/consuming confirm mode). Body files + ids + policy morphisms shipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5TUSJPD8FCtJagf7siXgt
@scasplte2 scasplte2 merged commit 7a5a1a3 into main Jun 25, 2026
10 checks passed
@scasplte2 scasplte2 deleted the feat/riverdale-economy-e2e branch June 25, 2026 23:57
ottobot-ai added a commit that referenced this pull request Jun 26, 2026
Follow-up to the green riverdale-economy economy (PR #190): the run was
unreadable — the background keepalive + per-send DL1 lines printed live
(50+ lines over 20 min) while the flow output was buffered and dumped only
at the end, and every step just said "OK" with no view of the economy
moving.

Harness (shared by all lanes, back-compatible):
- keepalive: gate the first-accepted + per-10 heartbeat behind E2E_VERBOSE;
  stop() prints one line ("[keepalive] silent · N sent, M failed").
- sendDataTransaction: fix describeUpdate (it read message.value on the
  UNWRAPPED generator output → always "update"; now reads the envelope key
  + inner fiberId/assetId/name); gate the healthy 3/3 per-send line behind
  E2E_VERBOSE. Divergence/failure lines always print.
- FlowLogger: live (write-through) mode; flush() is a no-op when live.
- Concurrency: an INTERACTIVE single-example run (TTY) defaults to 1 so its
  flows stream live. The isTTY gate keeps CI identical — tictactoe is a
  single-example lane with 3 parallel flows that must NOT be serialized.

Visibility (riverdale-economy):
- new poll-only step actions `phase` (section banner) and `economy` (reads
  the ML0 checkpoint once and prints a per-party table: state + a few
  stateData fields + held assets, with deltas vs the previous snapshot).
- assertState/assertAsset now summarize what they observed (state+seq /
  holder+amount) instead of a bare OK.
- example.ts: 13 phase banners + 12 economy snapshots across P0..P12 (all
  functional/assert steps unchanged); friendly assetAsset labels.

tsc-clean. phase/economy are additive early-continue no-ops; verbose-gating
defaults to quiet, so all lanes get a quieter, more readable run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5TUSJPD8FCtJagf7siXgt
scasplte2 pushed a commit that referenced this pull request Jun 26, 2026
* feat(e2e): readable economy run - quiet logs + economy snapshots

Follow-up to the green riverdale-economy economy (PR #190): the run was
unreadable — the background keepalive + per-send DL1 lines printed live
(50+ lines over 20 min) while the flow output was buffered and dumped only
at the end, and every step just said "OK" with no view of the economy
moving.

Harness (shared by all lanes, back-compatible):
- keepalive: gate the first-accepted + per-10 heartbeat behind E2E_VERBOSE;
  stop() prints one line ("[keepalive] silent · N sent, M failed").
- sendDataTransaction: fix describeUpdate (it read message.value on the
  UNWRAPPED generator output → always "update"; now reads the envelope key
  + inner fiberId/assetId/name); gate the healthy 3/3 per-send line behind
  E2E_VERBOSE. Divergence/failure lines always print.
- FlowLogger: live (write-through) mode; flush() is a no-op when live.
- Concurrency: an INTERACTIVE single-example run (TTY) defaults to 1 so its
  flows stream live. The isTTY gate keeps CI identical — tictactoe is a
  single-example lane with 3 parallel flows that must NOT be serialized.

Visibility (riverdale-economy):
- new poll-only step actions `phase` (section banner) and `economy` (reads
  the ML0 checkpoint once and prints a per-party table: state + a few
  stateData fields + held assets, with deltas vs the previous snapshot).
- assertState/assertAsset now summarize what they observed (state+seq /
  holder+amount) instead of a bare OK.
- example.ts: 13 phase banners + 12 economy snapshots across P0..P12 (all
  functional/assert steps unchanged); friendly assetAsset labels.

tsc-clean. phase/economy are additive early-continue no-ops; verbose-gating
defaults to quiet, so all lanes get a quieter, more readable run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5TUSJPD8FCtJagf7siXgt

* feat(e2e): wire FRACTIONALIZE/BURN live + color economy deltas

Consuming-morphism confirmation in the runner's applyMorphism path (the two
modes a source-seq-advance confirm can't observe):
- FRACTIONALIZE removes the source + writes one shard per shardId → confirm
  via the FIRST output shard's existence (confirmPath points at it).
- BURN/DECOMPOSE remove the source record → confirm via the source's ABSENCE
  (it existed pre-send, so exists→404 means the morphism committed); a small
  resubmit-aware absence poll, never re-burning a gone asset.
Non-consuming morphisms (STAKE/Transfer/Wrap) keep the source-seq-advance
confirm.

example.ts P12: STAKE was already live; now FRACTIONALIZE (mint 900 RVD into
carol's wallet → 3×300 shards, asserted) and BURN (mint 200 into frank's
wallet → burn → the economy snapshot shows it disappear) run live too. All
three approved wallet morphisms are now exercised end-to-end.

Economy snapshots: color the deltas — yellow for a changed value / departed
instance (`taxesPaid 0→50`, `RVD-tax 50→0`), green for a newly-acquired
instance (`+RVD-loan×10000`) — so the eye lands on what moved. ANSI codes are
only in the value columns, so label alignment is unaffected.

tsc-clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5TUSJPD8FCtJagf7siXgt

* feat(e2e): parallel-batch for independent setup steps

Cut the riverdale-economy wall-clock by running the INDEPENDENT setup steps
concurrently. The causal economic chain (lend→buy→repay→tax) stays serial —
only steps with no inter-dependency are batched.

Runner: a `parallel: true` step flag. The per-step body is extracted into a
`processStep(step, i, sw, sl, slog)` closure; a maximal run of consecutive
`parallel` steps executes via Promise.allSettled, each writing to its OWN
buffered FlowLogger that flushes in step order (concurrent output never
interleaves). The SEQUENTIAL path is byte-for-byte unchanged — the else
branch calls processStep with the flow's live logger + the same
try/catch → {passed:false, failedStep:i+1} — so no other lane is affected
(no existing example sets `parallel`). 12 step-level `continue`s became
`return`s; tsc-clean guarantees none dangle.

example.ts FLOW 1 — 3 batches (12 steps):
- P0: the 2 createAssetPolicy + retailer.machine@1.0.0 (distinct registry
  names, no shared lineage). Same-package later versions (retailer@2.0.0,
  fed@1.0.0/2.0.0) stay SEQUENTIAL: a lineage is monotonic append-only
  (VersionLineage.publish → NonMonotonic), so racing 1.0.0/2.0.0 could land
  2.0.0 first and reject 1.0.0 (the N3 negative). Causal, not parallel.
- P1: all six fiber creates (each mints its own `as` alias — distinct
  session.fibers key, no shared-mutable race).
- P2: the three RVD-leg mints into the consumer fiber (distinct assetIds).
  GOODS + RVD_LOAN mints stay sequential (split off by their assertAsset).

Concurrency-safe: every batched step writes a distinct registry name / `as`
alias / assetId, so no shared key is touched by two concurrent steps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5TUSJPD8FCtJagf7siXgt

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants