Skip to content

ci: enable Rust tests in CI, migrate to nextest, fix broken tests - #113

Merged
tvolk131 merged 118 commits into
masterfrom
docs
May 6, 2026
Merged

ci: enable Rust tests in CI, migrate to nextest, fix broken tests#113
tvolk131 merged 118 commits into
masterfrom
docs

Conversation

@tvolk131

@tvolk131 tvolk131 commented May 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Enable the Rust tests step in CI that had been commented out, surfacing two tests that had been silently failing on master.
  • Migrate the test recipe to cargo-nextest for parallel execution + a unified failure report; doctests run as a separate cargo test --doc pass since nextest doesn't execute them. cargo-nextest added to the nix dev shell.
  • Fix the two now-failing tests (build_follow_list_event_preserves_legacy_content_and_rewrites_p_tags in discovery/social.rs, sync_wallet_runs_store_sync_and_publishes_pending_deletions in node.rs).
  • Drop the unused seed_markets example (870 lines, no in-tree references) and its Cargo.toml declaration.
  • Set [profile.test] opt-level = 1 to recoup some of the integration-test runtime cost now that CI runs the suite per-PR.

This branch also ships a body of accumulated deadcat-core spec/docs work that had been sitting locally on docs. The CI commit is the only non-doc commit in the diff; review history with git log --oneline master..HEAD to see the doc commits separately.

Failure root causes (for reviewer context)

  1. build_follow_list_event_preserves_… — used [0xbb; 32] as a synthetic follow pubkey. That byte pattern isn't a valid x-only secp256k1 key, so PublicKey::from_hex validation (added in the same PR as the test) rightfully rejected it. Test was broken from day one; CI never ran it.
  2. sync_wallet_runs_store_sync_and_publishes_pending_deletions8ce1d69 made sync_wallet skip sync_own_order_state when list_own_maker_pubkeys() is empty (60s → 6s win). The test never seeded an own order, so the short-circuit kicked in and synced_electrum_urls stayed empty, breaking the assertion.

Test plan

  • nix develop --command just biome-check — clean
  • nix develop --command just tsc — clean
  • nix develop --command just cargo-fmt — clean
  • nix develop --command just cargo-clippy — clean (-D warnings)
  • nix develop --command just cargo-test — 629 passed, 0 skipped, 0 failed (nextest) + doctests clean
  • CI run on this PR completes green — the real validation that the workflow change works on the Blacksmith Ubuntu 24.04 runner with the vendored elementsd-x86_64-unknown-linux-gnu

View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

tvolk131 and others added 30 commits April 16, 2026 00:32
Independently verify the three LS-QMSR TODO items:
- Path independence: confirmed (degree-1 homogeneity of C)
- Bounded loss α(N-1)/(4N)·S: confirmed as upper bound, tight for α ≤ 2
- Strict properness: disproven — displayed prices shrink 50% toward
  uniform (p = π/2 + 1/(2N)), independent of α

Add the adaptive-properness-path-independence trilemma section
explaining why this is a fundamental constraint: any state-dependent b
makes the price field non-conservative (∇C ≠ p), forcing a choice
between adaptive liquidity and price accuracy. LMSR's exponential
nonlinearity makes the trilemma operationally soft (small bias ∝ 1/α);
QMSR's linearity makes it hard (constant 50% bias).

Revise recommendations: standard QMSR replaces LS-QMSR as primary
choice for both binary and N-outcome markets. Add adaptive liquidity
alternatives (LOB, operator-managed b, time-based b growth). Add
cubic polynomial scoring rule as research direction for achieving
LS-like adaptation with smaller bias.

Also fix QMSR depth profile inconsistency (constant price impact ≠
uniform directional depth) and strengthen first-mover incentive
discussion in binary market caveats.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add seed_markets example binary for publishing real on-chain testnet
  prediction markets via DeadcatNode. Supports publish/list/delete
  commands with 22 market definitions across 6 categories. Auto-splits
  UTXOs when only one is available. Uses DEADCAT_SOURCE_NSEC and
  DEADCAT_MNEMONIC env vars.
- Fix market discovery: markets fetched from relays but not yet promoted
  in the store (candidates table) are cached in memory and merged with
  store results so they don't flash and disappear between queries.
- Add discovery:market-refresh event to notify frontend when background
  relay fetch finds new markets.
- Add wallet-seed-generation-entropy.md documenting the wallet's entropy
  sources and encryption pipeline.
Replace skeleton loading state with cat bag animation while markets
are fetched from relays. Use React Query isLoading instead of manual
marketsLoading flag. Remove dead Markets/Live/Social nav links and
Help button/modal. Center search bar in header.
Transactions and balances stayed stale until the user manually pressed
sync because no background polling existed. Add a 15-second periodic
sync loop that runs while the wallet is unlocked, emitting updated
state through the existing snapshot pipeline.
Design for grouping related markets under a parent event (e.g. "2026
NBA Champion" with one sub-market per team). Uses a deadcat-group
NIP-78 event with a-tags referencing child markets — no on-chain
protocol changes required.
Show a success toast when funds are received and an info toast when
funds are sent. Detected by diffing txids in the wallet_snapshot
event against the previous snapshot in the Zustand store.
Wallet button in the top bar shows border color based on tx state:
pulsing green for incoming unconfirmed, pulsing white for outgoing
unconfirmed, solid green for new confirmed until dismissed. Balance
pulses while unconfirmed txs exist. Transaction rows use green/red
for unconfirmed dates and status text.
Replace append-only relay market cache with full replacement so
deleted markets disappear on next discovery cycle. Skip cache
replacement when relays return empty results to prevent market
flashing during transient failures.
Fetch real chain tip in useMarkets so time-remaining shows correct
months instead of raw block distance. Convert chart x-axis from
block heights to timestamps and rename timescale buttons from
block counts to minutes.
Disable buy buttons with "Insufficient funds" label when wallet
balance is too low. Map SDK error strings to user-friendly messages.
Show separate "Unlock wallet to trade" button with lock icon when
wallet is locked instead of the generic account setup prompt.
Green badge no longer fires for outgoing transactions like market
creation or sends — only for positive balance changes.
Reorder markets so first 5 span different categories. Add 18 new
markets across all categories (40 total). Fetch real chain tip
instead of hardcoded estimate. Add UTXO retry loop between markets
with auto-split fallback. Document seeder usage and full market list.
Require account password before revealing nsec in settings. Auto-hide
nsec when wallet locks. Make entire market card clickable to open
detail view.
LazyLock requires Rust 1.80+ but CI MSRV is 1.77.2. Switch to
once_cell::sync::Lazy. Also fix redundant closure clippy warning.
QR was lost when switching away from the Liquid tab and back because
the address already existed so the generation effect skipped. Now
regenerates the QR whenever the Liquid tab is active.
- Add prepare_send / confirm_send Tauri commands with fee breakdown
  confirmation screen before broadcasting Liquid sends
- Add drain_lbtc SDK method using LWK native drain for exact max
  sends with zero dust
- Add estimate_max_send command that prepares and stores the drain tx
- Add block explorer link on transaction sent screen
- Extract shared friendlyError() for trading panel, send/receive
  modals — raw SDK errors no longer leak to the UI
- Extract DEFAULT_FEE_TARGET_BLOCKS constant
- Add PrepareSendResult type
Extract body scroll lock into reusable hook. Apply to NostrEventModal
(was missing), WalletPage, ProfilePage, OnboardingOverlay, and
SettingsPanel. Fixes background scrolling while any modal is open.
Replace cat bag loader and splash timing hacks with skeleton cards
that stay visible until markets actually arrive in the React query
cache. marketsLoading only clears inside useMarkets queryFn when
data is non-empty. No fallback timeout — skeletons persist until
relay data arrives. Removes MarketLoader, CatLoaderSvg, BagLoaderSvg,
and OverlayLoader usage from App.tsx.
1. Fix focus stealing bug in manual restore flow where typing in the
   password field caused focus to return to the seed phrase textarea.
   The ref callback was running on every re-render. Now uses useEffect
   to focus only when mnemonicExpanded changes to true.

2. Clear password fields when navigating to any password entry step
   to ensure users always start fresh. This prevents stale passwords
   from persisting between navigation flows.

https://claude.ai/code/session_018cp7GAwLjq3upwZoQUoUwo

Co-authored-by: Claude <noreply@anthropic.com>
Break the blocking dependency chain that caused 30+ second startup
when a relay was down. Four changes:

1. Add 3s connect_timeout to all relay connection calls — dead relays
   are skipped instead of hanging indefinitely

2. Make construct_and_store_node non-blocking — node is stored
   immediately, start_subscription() runs in background tokio::spawn

3. Restructure bootstrap to not await init_nostr_identity — identity
   loads in background, splash dismisses unconditionally, parallel
   Promise.all for independent backend queries

4. Remove fetch_chain_tip from market query critical path — cached
   at module level in background, never blocks discover_contracts
Markets from relays were only stored in memory (RELAY_MARKET_CACHE)
and lost on restart. Every cold start required a full relay fetch
(5-30s depending on relay health). Now the cache is saved to
relay_market_cache.json and loaded at app startup, giving sub-second
market display on returning launches.
Add missing category icons (Politics, Sports, Culture, Bitcoin,
Weather, Macro, Resolved) to HomePage CategoryIcon. Show muted icons
in page headings. Move Ending Soon next to Resolved after Macro.
Shorten to "Ending" in tab bar. Add checkmark icon for Resolved.
useLockScroll() ran on every render of ProfilePage because the
early-return guard came after the hook. That locked body/html overflow
on app start and never unlocked. Split into a wrapper that guards
profileOpen before any hooks and an inner ProfilePageContent that
only mounts when the modal is open (same pattern as SettingsPanel).
- hiddenTitle + titleBarStyle: Transparent in tauri.conf
- core:window:allow-start-dragging capability for JS drag API
- data-os attribute on <html> for platform-scoped CSS
- onMouseDown drag handler on empty header space (phi-container
  and the flex row) so users can drag the window from any empty
  area of the top bar. Scoped via event.target === currentTarget
  so interactive children still receive their normal clicks.
titleBarStyle: Transparent left the webview starting below the native
chrome, so the top strip showed through to the desktop instead of being
painted by our dark header. Overlay extends the webview edge-to-edge and
lets bg-slate-950/80 backdrop-blur paint under the traffic lights.

Adds 32px top-padding to the header's .phi-container only on macOS
(scoped via data-os="macos") so the logo sits below the OS-anchored
traffic lights rather than beside them.
- Move onMouseDown drag handler up to <header> so the top-bar corners
  outside the max-w-1440 phi-container also drag the window. Each
  nested layer keeps its own handler; target === currentTarget guards
  against interactive-child clicks.
- Lower SearchBar breakpoint from lg (1024px) to md (768px) so the
  inline input stays expanded at the app's 900px minimum width; the
  mobile search overlay is unreachable in practice and is retained
  only for correctness.
- Shrink logo from h-10 (40px) to h-9 (36px) to match the Get started
  button height in the top bar.
tvolk131 and others added 25 commits May 5, 2026 23:23
Enumerates every precondition an integrator must satisfy for correct
chain-only recovery, the failure mode if violated, what the engine
verifies, and what it does not. Closes the documentation gap where
recovery preconditions were implicit in the flow descriptions — an
integrator targeting deadcat-core now has an explicit contract to code
against, and several silent-failure modes (wrong xprv derivation path,
incomplete rescan, stale backend, wrong network) are named so they can
be defended against at the integration layer.

The rustdoc-level precondition pass is an implementation-time task
guided by this section; a verify_integration helper is noted as a
future ergonomic improvement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t × N

Closes B2 (multi-outcome expiry redemption divisibility) by restructuring
the denomination model rather than adding a covenant-level mod check.

Under the prior spec, binary markets parameterized on collateral_per_pair
directly while multi-outcome markets required cp mod N == 0 for exact
expiry redemption. Enforcing that divisibility at covenant level would
have been necessary under the self-enforcement principle (otherwise
floor-division on expiry orphans user value proportional to pairs
issued), but the 1-2-5 mantissa table is incompatible with divisibility
for N in {3, 6, 7, 9} — enforcing cp mod N == 0 would have made those
outcome counts uncreatable.

The unified model parameterizes both contracts on base_payout — the
per-outcome YES-expiry payout unit — drawn from the existing 1-2-5
table. The pair cost cp = base_payout × N is derived at covenant compile
time (N=2 for binary, N=outcome_count for multi-outcome). Divisibility
becomes structural: every expiry redemption rate is an integer multiple
of base_payout, no runtime division happens in the covenant, no
denomination-table index is unreachable for any supported N.

Changes:
- multi-outcome-market-contract.md: rename primary param, add Denomination
  model subsection with rationale, revise constraints and expiry
  redemption section to reflect structural exactness, update OP_RETURN
  hint spec and security properties table.
- contract-specification.md: rename binary and multi-outcome param
  fields, update spend paths formulas to use cp shorthand, update
  pending-refactors row for the superseded collateral_per_pair step.
- market-contract-principles.md: revise principle 12 to state exactness
  as structural rather than covenant-asserted.
- chain-only-recovery.md: rename denomination table and OP_RETURN hint
  field from collateral_per_pair to base_payout, same 4-bit encoding
  and same 1-2-5 values.
- deadcat-core-design.md: update BinaryMarketCreationParams and
  MultiOutcomeMarketCreationParams struct fields, revise "Collateral
  Per Pair" and "Standard Denomination Conventions" decision log
  entries.
- collateral-per-pair-refactor.md: mark as superseded, preserve
  historical problem statement as context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…esign

Resolves I5 via shelving rather than specifying. Cross-outcome arb
(quote_cross_outcome_arb, build_cross_outcome_arb_pset, ArbQuote,
ArbDirection, ArbPoolLeg, as_cross_outcome_arb) was named in several
places after the Stage 3 pass but never fully specified. The API
surface has unresolved design questions (scope of directions, sizing
model, staleness representation, classification rule) and landing it
prematurely would bake in choices before the arb ecosystem exists to
inform them.

Deferring is safe because cross-outcome arb is not safety-critical —
coherence gaps are pricing drift, not solvency violations. The covenant's
generic solvency-preservation spend path makes arb permissionless by
construction, so external bots can close gaps without a core-layer
builder while v1 stabilizes.

deadcat-core-design.md changes:

- Remove quote_cross_outcome_arb / build_cross_outcome_arb_pset method
  signatures from MultiOutcomeMarket impl.
- Remove ArbDirection / ArbQuote / ArbPoolLeg / ArbQuote impl type
  definitions and the CrossOutcomeArbRealized / ArbPoolLegRealized
  result types plus as_cross_outcome_arb method.
- Add a dedicated "Future: Cross-Outcome Arb API (v2)" section that
  reserves the API surface names, states the rationale for deferral,
  lists the five open design questions, and documents v1 behavior on
  observed arb-shaped txs (ingest cleanly; per-contract transitions
  preserved; no aggregate classification until v2).
- Reframe the "Cross-Outcome Arb: Quote + Build Pattern" decision log
  entry as "Cross-Outcome Arb: Deferred to v2" with the deferral
  rationale.
- Scrub prose references across the Engine API, TradeSpec narrative,
  Market view accessor, Market transition section, Multi-Contract
  Patterns decision log entry, View Types decision log entry, and
  staging notes so they reflect the deferred surface.
- Remove build_cross_outcome_arb_pset row from the multi-outcome
  builders table; replace with a deferral pointer.

trade-routing-algorithm.md changes:

- Update the "multi-outcome operations that don't route through
  quote_trade" paragraph to describe the v2 deferral.
- Update Key Files section accordingly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolves B3 (multi-outcome .simf code generation). Key decisions:

Supported N range for v1: {3, 4}. Deliberately conservative — tx size
scales ~quadratically with N and we haven't benchmarked the binary
witness size to project the practical ceiling. Extension to wider N
(e.g. {5..10}) is explicitly non-breaking, since each N has its own
CMR-committed .simf; existing markets are unaffected by adding new N.
Shrinking is breaking and should not happen once markets exist.

N=2 stays on the existing prediction_market.simf (not regenerated from
the template). Binary is the high-volume case, already deeply validated,
and running it through the multi-outcome template would cost tx weight
at the common case without structural benefit. Decision previously
deferred; now locked for v1.

Generator architecture: fully decoupled from deadcat-core at the
dependency level.

- deadcat-codegen (new dev-only workspace crate): pulls MiniJinja,
  exposes fn generate(n: usize) -> String plus a CLI binary invoked
  via `just generate-simf`.
- deadcat-core: reads committed .simf files via include_bytes!, has no
  dep on deadcat-codegen or MiniJinja. Downstream consumers of the
  published crate get pre-embedded .simf files, never see the
  generator's dep tree.
- Workspace CI runs the drift-detection test in deadcat-codegen which
  regenerates in-memory, asserts byte-exact match against committed
  files, and invokes the SimplicityHL compiler on generated output
  with canonical test params (catches both drift and semantically
  invalid template output).

Templating: MiniJinja over Tera/Handlebars/plain Rust. MiniJinja has a
materially smaller dep footprint and strictly Jinja2-compatible syntax
that doesn't collide with SimplicityHL's { } and [ ] delimiters. Tera's
advantages (Rust-web ecosystem, Zola) are irrelevant for this use case.

Not build.rs: OUT_DIR conflicts with committed-output model and silent
regeneration on `cargo build` would clobber developer edits.

Also fixed a misleading doc claim: the prior text at
multi-outcome-market-contract.md:376 said "per-N CMR is deterministic
given a template version" — incorrect, since CMR depends on the full
param set (oracle pubkey, asset IDs, base_payout, expiry_time). The
generator commits .simf source text, not a compiled CMR. Audit-workflow
reproducible CMR recipe is noted as a future tooling TODO, not v1.

Files:

- multi-outcome-market-contract.md: rewrite § Code Generation Strategy
  to reflect the MiniJinja + crate-separated + committed-outputs +
  drift-test design; add the v1 N={3,4} scope, the non-breaking
  extension note, and the CMR-depends-on-params clarification. Lock the
  N=2 "keep binary.simf canonical" decision. Update the Impact on
  deadcat-core section to reference the deferred cross-outcome arb API.
- deadcat-core-design.md: add "Multi-Outcome .simf Code Generation"
  decision log entry between the cross-outcome arb deferral and the
  view types decision, documenting the chosen/rejected alternatives.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…enomination

Resolves B1 (deterministic F-value algorithm). Pivots the runtime
algorithm from fixed-point Taylor (which required substantial spec
work on precision, term counts, and constants encoding) to bignum
rational arithmetic via num-bigint + num-rational. The closed-form
expression F(i) = max_loss_sats + floor(b × ln(cosh(s/b))) is computed
directly at arbitrary precision, with floor-to-u64 at the final step.

Correctness story: committed reference Merkle roots per param combo
live in deadcat-codegen as test fixtures. The regression test re-runs
the bignum reference on every cargo test and asserts all 256 committed
roots reproduce byte-for-byte, catching any unintended change in the
bignum implementation or its transitive dependencies. Cross-implementation
conformance (future Taylor port, cross-language ports) targets the same
committed roots — any implementation matching all 256 is provably
equivalent to bignum over the full valid parameter space.

Binary cost: zero embedded roots in deadcat-core. The runtime deps
num-bigint + num-rational add ~100 KB compiled. Cold-start cost is
5-10s per unique (max_loss_sats, half_payout_sats) combo, amortized
via per-pool in-memory/disk cache.

Pool denomination: collapses from 26-mantissa × 16-exponent (416 values
per param, 173,056 combos) to the 16-value 1-2-5 table shared with
market base_payout encoding (16 values per param, 256 combos). Pool
OP_RETURN hint shrinks from 41 bytes to 40 bytes. Range is capped at
10^7 sats per param — expanding the table is explicitly non-breaking
in a future release.

Files:

- lmsr-deterministic-table-spec.md: status flipped from Skeleton to
  Specified. Replaced the "NOT YET SPECIFIED" F-Value Computation
  Algorithm section with the bignum runtime design. Replaced the test-
  vectors section with the committed-fixtures approach. Updated Key
  Files to point at deadcat-codegen and deadcat-core.
- chain-only-recovery.md § Pool Denomination: rewritten around the
  16-value 1-2-5 table. Pool hint updated from 9+9 bits (18 bits total)
  to 4+4 bits (8 bits total). Pool hint total length 41 → 40 bytes.
  Per-field justification table updated. Builder validation updated.
- contract-specification.md: updated LmsrPoolParams comment for
  half_payout_sats denomination. Pending-refactors section gains a new
  row for the pool denomination change and reframes the F-value row
  from Pending to Specified.
- deadcat-core-design.md: new "LMSR F-Value Computation" decision log
  entry documenting the chosen bignum approach, the rejected
  alternatives (Taylor runtime, embedded tables, embedded roots,
  hybrid, 26-mantissa encoding), and why.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the from-scratch implementation sequence for deadcat-core,
deadcat-codegen, and the accompanying SimplicityHL contracts as a
six-phase plan with explicit deliverables, quality gates, and risk
flags per phase. Treats the existing deadcat-sdk crate as a reference
artifact rather than a refactor target; new covenants are written
fresh with every design decision from the pre-implementation review
baked in from day one.

Six phases:
1. SimplicityHL contracts (highest risk — covenant self-enforcement
   audit is the gating quality criterion).
2. deadcat-codegen crate (LMSR bignum reference + fixtures +
   multi-outcome .simf generator).
3. deadcat-core foundations (types, traits, covenant loading).
4. ContractEngine mechanics (ingestion, interpretation, chain sync).
5. View types and PSET builders.
6. Routing, recovery, public API polish.

Documents phase dependencies, parallelization opportunities, deferred
items (v2 cross-outcome arb, wider N range, expanded pool denomination,
Taylor runtime, cross-language ports, etc.), and known implementation
risks with mitigations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cover Nix flake integration (fetchurl derivation with pinned hashes),
justfile commands, Simplex.toml config, typed build artifacts, and
version pinning. Maps smplx capabilities to the existing three-tier
test methodology — smplx targets Tier 2 regtest integration tests
while Tiers 1/1.5 remain unaffected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Applied clear fixes and small-file updates from the deadcat-core
pre-implementation review.

contract-specification.md:
- v1 multi-outcome scope: N ∈ {3, 4} (was "proposed N=3..10")
- N=2 stays as binary prediction_market.simf, not regenerated from
  the multi-outcome template (was deferred)

enforcement-layers.md:
- Terminology sweep: collateral_per_pair → cp (= base_payout × N)

lmsr-pool-design.md / amm-scoring-rule-tradeoffs.md:
- Deterministic Table Generation section: reframed as bignum-first
  reference with Taylor runtime as non-breaking v2 optimization;
  cite lmsr-deterministic-table-spec.md for the canonical spec
- Fix stale timing claims (~80ms / ~1μs) with bignum reality
  (~5-10s per (max_loss_sats, half_payout_sats) combo; cached)
- Quoting strategy reframed to cached-tables at bignum speeds
- Note that deadcat-core mirrors covenant market-state-agnosticism;
  no engine-level gate on post-resolution trading

trade-routing-algorithm.md:
- Integer Precision section → Integer Precision and Caching;
  bignum caching strategy as the router's dominant pattern
- New Quote Staleness subsection covers pool-admin-adjust subtlety
  (pricing curve unchanged but outpoints still invalidate quotes)
  and confirms post-resolution trading is allowed

deadcat-core-design.md:
- LMSR Math section: update generate_lmsr_table language to
  reference the bignum algorithm and canonical satellite spec

deadcat-core-implementation-plan.md:
- Phase 1: committed smplx build artifacts added as deliverable
- Phase 2: smplx artifact drift test added
- Phase 6 pool lifecycle: no engine-level gate on post-resolution
  trading (pool operators manage liquidity via build_lmsr_close_pset)
- src/artifacts/ committed alongside contracts/multi_outcome/
- Typed witness adoption committed from Phase 1 (was deferred to
  Phase 5)
- just regenerate-artifacts command
- Dropped verify_integration from scope

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Applied type-system and error-enum refactoring from the deadcat-core
pre-implementation review.

Type system:
- SlotIdentity promoted to public type with BinaryMarketSlot,
  MultiOutcomeMarketSlot, PoolSlot, OrderSlot variants
- Labeled outpoints (Vec<(SlotIdentity, OutPoint)>) replace
  positional Vec<OutPoint> at six engine/store boundary types:
  InitialContractState, ContractMatch, StateUpdate (old/new),
  OutpointContractInfo, contract_outpoints signature
- "Outpoints per contract type" subsection rewritten from
  positional to labeled semantics

StateUpdate:
- Added old_state: Contract and new_state: Contract fields so the
  engine carries all domain logic and the store is a plain
  persistence layer (no implicit state derivation required)

Orders:
- OrderTracking three-variant enum: Persistent / EphemeralFresh /
  EphemeralMidLife, with rustdoc on persistence behavior and
  offered_amount accuracy per mode
- OrderState variants carry tracking and active_txid fields
- Split ingest_order into ingest_persistent_order (maker
  monitoring, full history, requires creation tx) and
  ingest_ephemeral_order (taker/discovery, no history,
  auto-untracks terminal past finality, accepts Creation or
  Current snapshot)
- Engine behavior forks on tracking for history writes and
  prune_finalized auto-cleanup
- Duplicate ingestion uniformly returns ContractAlreadyTracked
  regardless of method

Error enum:
- CovenantInvariantViolation { contract_id, kind } variant with
  nested InvariantViolationKind enum for consensus-valid
  transactions that defy covenant invariants (bug-adjacent,
  not retry-loopable)
- StaleQuote becomes enum with StaleQuoteReason (OutpointsChanged,
  ContractUntracked, ContractRemoved) replacing detail: String
- NoLiquidity expanded with outcome, side, direction fields
- InvalidContractState becomes structured with InvalidStateKind
  (WrongVariant state-machine rejection vs ConditionFailed
  runtime precondition)

Atomicity + subscriptions:
- Per-transaction atomicity of apply_transitions upgraded from
  "recommended" to "required"; documented mid-step error handling
- apply_transitions rustdoc sharpened (atomic, durable,
  idempotent per (contract_id, txid))
- New Subscription semantics paragraph on ChainSource:
  idempotent set operations, register widens coverage,
  unregister of never-registered is no-op, no duplicate
  notifications

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Applied new sections and rustdoc expansions from the deadcat-core
pre-implementation review.

New sections:
- Design Principles: "engine gates covenant-invalidity and
  impossibility, not unfavorability" + "multi-role patterns
  deferred to future versions," establishing the API's discretion
  boundary vs the correctness invariants above
- Pool and Order Lifecycle at Market Resolution: post-resolution
  trading is intentionally not gated; pool operators manage
  liquidity via build_lmsr_close_pset; UI-layer warnings are the
  appropriate safety net (not engine refusal)
- State Machine Summary: consolidated valid-transition matrices
  for all four contract types (binary market, multi-outcome
  market, LMSR pool, order); creation builders split into their
  own table; InvalidStateKind WrongVariant vs ConditionFailed
  error reporting documented

Expanded sections:
- MultiOutcome delta classification: explicit 7-variant shape
  table for IssuedPair / CancelledPair / SplitYes / MergeYes /
  SplitNo / MergeNo / CrossOutcomeSwap, plus pseudocode for the
  classification algorithm with matching precedence and edge
  cases (all-zero deltas, shape match with wrong coefficient
  falls through to Composite)
- Cross-outcome arb v1 boundary: explicit statement that
  single-contract builders don't compose into arb PSETs; new
  Available/Not-available table for external arb tooling; LMSR
  F-value runtime is pub in v1 so external tooling shares the
  bignum algorithm

Rustdoc expansions:
- as_trade: enumerated preconditions and non-matching cases
  (pool admin, market-only, arb, multi-outcome bundle,
  multi-pool-same-outcome); explicit contrast with net_effect_for
- TradeRealized: structure, engine-enforced invariants, and
  "stays single-outcome forever" v2 note
- quote_trade: expanded NoLiquidity context payload explanation;
  explicit "post-resolution trading is not gated" note
- build_trade_pset: freshness check algorithm with
  StaleQuoteReason variant breakdown; pool-admin-adjust
  subtlety called out
- PSET Construction intro: state preconditions explained with
  InvalidContractState enum structure and State Machine Summary
  cross-reference (replaces per-builder "Valid from" rustdoc
  proliferation with one authoritative reference)

Cleanup:
- Resolved TODO anchor in apply_transitions rustdoc

Not in this sprint (deferred):
- Pool Chain-Sync Model subsection consolidation — existing
  Chain Sync section organization is coherent; dedicated
  subsection is cosmetic polish

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Applied follow-up items that emerged from reviewing the sprint 1-3
commits against the pre-implementation review conversation.

lmsr-deterministic-table-spec.md:
- New "Precision Calibration" subsection describing the one-time
  binary-search calibration that empirically validates the 200+
  bit precision budget (start at 512 bits, halve until any root
  diverges from ground truth). Pinned as a development artifact
  in the spec, not per-CI regression.

lmsr-pool-design.md:
- Protocol constant encoding note: SimplicityHL lacks const::, so
  TABLE_DEPTH / S_BIAS / S_MAX_INDEX / MIN_POOL_RESERVE are encoded
  as zero-argument functions returning their literal values
  (identical CMR to inline literals, one named declaration per
  constant for audit legibility).

deadcat-core-implementation-plan.md:
- Phase 2: new deliverable 10 for precision calibration CLI
  (just calibrate-precision) + documented empirical result.
- Phase 3: new deliverable 9 for deadcat-core-store-testkit crate
  exposing run_store_compliance / run_chain_source_compliance.
- Known implementation risks: new entry 6 documenting that
  smplx pre-1.0 risk is mitigated by direct Blockstream
  collaboration.
- Ingestion methods list updated: ingest_order → ingest_persistent_order
  / ingest_ephemeral_order.

deadcat-core-design.md:
- New "ContractStore Compliance Test Kit" subsection under
  Persistence: Store Trait, enumerating 10 invariant categories
  the testkit crate enforces (outpoint tracking, lifecycle,
  sync state, atomicity, indexing, rollback, pagination,
  query ordering, processing-log separation, tracking-mode
  behavior, plus ChainSource invariants).
- Updated existing Design Decisions Log entry
  "Contract-Level Atomicity Required, Transaction-Level Recommended"
  to reflect sprint 2's upgrade: per-transaction atomicity is
  now required (title renamed to "Per-Transaction Atomicity
  Required").
- Added Pool and Order Lifecycle section: new "Ephemeral orders
  and rollback" subsection distinguishing pre-finality rollback
  (restores Consumed order to Active) from post-finality
  rollback (order is auto-untracked and gone; caller re-discovers).
- New Design Decisions Log entries:
  - Labeled Outpoints at the Engine↔Store Boundary
  - Two Order Ingestion Methods, Not One
  - No Atomic Order Promotion Method
  - Post-Resolution Trading Not Gated
  - CovenantInvariantViolation Retained as Defense-in-Depth

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The "why we don't gate post-resolution trading" reasoning was
captured in the design doc but terse ("rejected on transaction-weight
grounds") and absent from lmsr-pool-design.md, which is where a
pool-focused reader would first look.

lmsr-pool-design.md:
- New "Why the pool covenant can't feasibly gate post-resolution
  trading" subsection explaining the architectural constraint:
  covenants can only introspect the current transaction, so
  enforcement would require co-spending the market covenant's
  UTXO on every swap — roughly doubling every swap's footprint
  and imposing the cost on every trade, not just near-resolution
  ones. The engine-layer non-gating follows from the covenant
  choice.

deadcat-core-design.md:
- Pool and Order Lifecycle section: replaced terse "rejected on
  transaction-weight grounds" with explicit mechanism (market
  UTXO co-spend, ~1,000 vbyte overhead per swap, cost falls on
  every trade) plus cross-ref to the pool-design satellite.
- Design Decisions Log (Post-Resolution Trading Not Gated):
  expanded "Why" with the same architectural framing — covenant
  market-state-agnosticism is architectural necessity, not policy
  oversight — plus the same cross-ref.

trade-routing-algorithm.md:
- Quote Staleness subsection: added a parenthetical noting the
  non-gating is an architectural consequence, with cross-refs
  to both design-doc and pool-design coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolves the discussion items and drift from the latest design review pass:

- HD path constants frozen: m/86'/1145390932'/... (BIP-86 + SLIP-0044 coin_type
  "DCAT"). SLIP-0044 registration PR tracked as pre-v1-ship action item.
- derive_pool_params / derive_order_params now take MarketParams umbrella +
  OutcomeIndex, and derive_pool_params takes initial_s_index directly
  (eliminating the non-injective bps↔s_index inversion on recovery).
- Multi-outcome market hint: 37-byte unified layout (distinct type tag from
  binary); outcome_count derived from creation tx issuance count with a
  defensive amount/inflation_keys filter instead of being stored.
- Pool hint: 41 → 40 bytes, 9-bit mantissa×exp → 4-bit 1-2-5 indices (aligned
  with the pending-refactors table; 5 stale references in the design doc
  updated).
- Recovery flows updated to iterate outcomes for multi-outcome pools/orders
  and to pass initial_s_index through unchanged; "recovering without a hint"
  fallback documented as non-standard.
- Oracle BIP-340 satellite marked binary-only with pointer to the unified
  MarketResolution API in the design doc.
- ExternalOutput rustdoc tightened to flag the consolidation caveat and
  point at TransitionDetails as the authoritative per-role amount source.
- Phase 3 parallelism scope replaced with a concrete three-bucket type list.
- Phase 5 build_fill_pset reference removed (order fills go through the
  trade router, not a per-order builder).
- PredictionMarketParams → BinaryMarketParams rename propagated across
  chain-only-recovery, deterministic-rt-blinding, contract-specification,
  lmsr-pool-design, and UX stories.
- Price bound off-by-one fixed (2^24 → 0xFFFFFF = u24 max = 16,777,215).
- Multi-outcome codegen "at build time" wording clarified as dev-time
  generation with committed .simf and src/artifacts outputs.
- Trade PSET builder cross-references the composability layout algorithm.
- Three new decisions-log entries (HD path, initial_s_index sourcing,
  outcome_count derivation) plus one update to the derive-functions entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Corrections to the preceding commit surfaced by an independent review pass:

- Fix wrong coin_type numeric constant. 0x44434154 ("DCAT") is 1,145,258,324,
  not 1,145,390,932 (the latter is 0x44454754 = "DEGT"). The incorrect value
  had propagated to 14 locations across chain-only-recovery.md, the design
  doc, and the implementation plan. Confirmed 1,145,258,324 is unassigned
  in SLIP-0044.
- Recovery flows: drop the claim that outcome_count() == 1 for binary
  (contradicts the design doc's "2 for binary" at the Market view). Replace
  with explicit per-market-kind iteration (binary → OutcomeIndex::BINARY;
  multi-outcome → iterate 0..outcome_count from MultiOutcomeMarketParams)
  to avoid depending on the pre-existing outcome_count() accessor semantic
  ambiguity.
- enforcement-layers.md Layer 4 mechanism list: "1-2-5 table, 26-value
  mantissa" → "shared 16-value 1-2-5 table for market base_payout and pool
  max_loss_sats / half_payout_sats" (the 26-mantissa scheme is superseded).
- lmsr-pool-design.md derived-parameter table: replace "derived from the
  inverse logistic function" with an explicit note that the bps→s_index
  snap lives in estimate_bootstrap and the inverse no longer exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CI workflow's Rust tests step was commented out, so two tests had
been silently failing on master. Enable the step, switch the recipe to
nextest, and fix the failing tests.

* flake.nix, justfile: add cargo-nextest to the dev shell; collapse the
  workspace + deadcat-sdk test runs into a single `cargo nextest run`
  invocation, with a follow-up `cargo test --doc` pass since nextest
  doesn't execute doctests.
* .github/workflows/ci.yml: uncomment the `Rust tests` step.
* src-tauri/Cargo.toml: set [profile.test] opt-level = 1 to recoup some
  of the integration-test runtime cost now that CI runs them per-PR.
* discovery/social.rs: the test for build_follow_list_event used
  [0xbb; 32] as a synthetic follow pubkey, but that byte pattern isn't a
  valid x-only secp256k1 key, so PublicKey::from_hex rejected it.
  Replace with Keys::generate().public_key().to_hex(). Also update the
  build_mute_list_event tag-shape assertion to expect the new `client`
  tag.
* node.rs::order_cleanup_tests: 8ce1d69 made sync_wallet skip
  sync_own_order_state when list_own_maker_pubkeys is empty (60s -> 6s
  win). The sync_wallet_runs_store_sync test never seeded an own order,
  so the short-circuit kicked in and the synced_electrum_urls assertion
  failed empty. Seed a RecordedOwnOrder before calling sync_wallet.
  Also normalize a few reserve_yes_outpoint test fixtures.
* Drop unused crates/deadcat-sdk/examples/seed_markets.rs (no in-tree
  references) and its [[example]] entry in deadcat-sdk/Cargo.toml.
Copilot AI review requested due to automatic review settings May 6, 2026 04:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Resolutions:
* social.rs / node.rs: kept HEAD's test fixes (the master versions are
  the bugs this PR fixes).
* seed_markets.rs: kept master's expanded version (The Daniel's
  Apr-16 work). Required wrapping `keys` in `Arc::new(...)` on
  setup_node since master's example wasn't actually compiling against
  master's own `DeadcatNode::new(signer: Arc<dyn NostrSigner>, ...)`
  signature; CI didn't catch it because clippy --all-targets skips
  examples gated by required-features.
* views.md: kept HEAD's version and stripped stale 4ee5c77 conflict
  markers from a prior unresolved merge.
@tvolk131
tvolk131 merged commit 246e938 into master May 6, 2026
6 checks passed
@tvolk131
tvolk131 deleted the docs branch May 6, 2026 14:35
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.

6 participants