Skip to content

Commit ed1c054

Browse files
0xaslanclaude
andauthored
Resolve Predict predeploy audit items (#1097)
* Document Propbook Block Scholes feed split * Predict: remove PLP price circuit breakers from finish_flush (P-1) The [min_plp_price, max_plp_price] band and pool-NAV dust floor in finish_flush had no on-chain recovery path: an out-of-band frozen mark bricked the entire flush (valuation, sweeps, rebalance, LP fills) until package upgrade, including the legitimate 100x-appreciation and post-drawdown recapitalization cases. The flush mark is the exact NAV, so any price it produces is fair by construction; the true arithmetic degeneracies (zero-share supply fill, zero-payout withdraw fill, zero pool value) are owned by the drain's EInvalidDrainMark guards at the fill site, which only bind on a pending degenerate head request the owner can cancel. - Delete assert_plp_price_in_bounds, its error consts, and the min_plp_price/max_plp_price macros; renumber remaining plp abort codes contiguously 0-9 (pre-deploy, name-referenced only) - Repurpose the three CB expected_failure tests into positive flushes asserting exact pool NAV at formerly-forbidden marks - Add EInvalidDrainMark boundary tests for zero-share supply and zero-payout withdraw fills, one unit past each boundary - Drop the now-dead min_bootstrap >= min_withdraw relationship test (its only consumer was the deleted dust-band guard) - Update lp-nav-zero-brick notes, risks.md, and resolve P-1 in open-items.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Predict: close resolved open items * Predict: derive partial-close survivor floor directly (P-3) The partial-close floor split computed the removed floor with a double-floor fraction (mul over a floored close fraction) and derived the survivor by subtraction, so a max-size leveraged order closed to a small remainder could leave survivor floor_shares > quantity and abort order::replacement with EInvalidFloorShares. Derive the survivor floor directly with a single round-down mul_div_down(old_floor_shares, remaining_quantity, old_quantity) — bounded by remaining_quantity whenever F <= Q holds at mint — and set the removed floor to the conserved difference. That difference equals ceil(old_floor * close_quantity / old_quantity), bit-identical to the mul_div_up redeem deduction the old code computed separately, so the trader-facing redeem_amount is unchanged and the duplicate removed- floor derivation collapses into one. Both payout-tree atoms (quantity, floor_shares) remain exactly conserved across the split, preserving the tree's bit-exact insert/remove round-trip. - Add a max-size 6x partial-close regression down to one lot: survivor quantity == 10_000, floor_shares == 4_166 (= floor(5Q/12 * lot / Q)), position present, market backed; red-checked against the old split - Resolve P-3 in open-items.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Propbook: admin-gated canonical binding replacement (P-4) Canonical bindings become current accepted bindings instead of insert-only one-shots. Adds replace_pyth_binding_for_underlying and the atomic replace_block_scholes_bindings_for_underlying (spot+forward+SVI, one bs_source_id), with OracleRebound carrying old and new source/object IDs. No unbind path; source-to-underlying assignment stays sticky across replacement. Predict already resolves the current binding by underlying at call time, so it follows a rebind with no contract changes. Coverage: registry replace/abort/sticky-source tests, Predict pricer rejects old feeds and follows rebound feeds, and passive settlement rejects old Pyth and settles on the rebound feed after exact-ms backfill. README documents the replacement flow, the spot-only recovery path, and the Pyth backfill runbook; P-4 removed from the predeploy tracker. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Predict: accept S-1 settlement liveness + cadence config observability Resolve the S-1 deploy gate as an accepted operational assumption: exact-timestamp settlement is a monitored sub-minute transient under testnet operations (~870 settles, zero missing exact prints, zero manual settles). Evidence, keeper controls, the one stream-channel incident, and the residual keeper-availability risk are documented in predeploy/settlement-liveness.md; no contract change is planned. Add read/observability surface for cadence deployment policy: - Emit CadenceConfigUpdated from registry::set_cadence_config after the stored update (all-zero fields = cadence disabled, matching the validated config shapes) - Expose registry::cadence_config plus public CadenceConfig getters (cadence_-prefixed; bare names collide with DeployableMarket getters) and a cadence_enabled predicate - Promote expiry_market::settlement_price to public as user-facing terminal protocol state Known follow-ups: indexer decode struct/handler for CadenceConfigUpdated, and a getter round-trip test for the new CadenceConfig read surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Resolve settled sweep liveness item * Resolve Predict coverage hygiene items * Predict audit skill: add mini depth tier for cleanup triage A cheap pre-audit sweep that surfaces easy cleanups raw (no verify panels) so deep runs can spend their finding slots on new ground: - depth:'mini' preset in all three harnesses: maxRounds 1, verifyCap 0 (findings reported unverified — the operator is the verifier), finder effort dropped to medium - profile:'cleanup' on the orchestrator: only the cleanup-tier lenses (surface-area, assertions, architecture) — the inverse of 'security' - verifyCap now resolved with a typeof-check instead of ||, so an explicit 0 no longer silently falls back to the default 60 - SKILL.md: mini row in the depth table + a mini-pass recipe, including the feed-forward loop (fix/disposition mini findings, then pass the run's findings.json as the deep run's priorAdjudications) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Remove obsolete Propbook feed split spec * Predict mini audit sweep: curate findings into open-items Mini cleanup-triage run at b34b0cd (rule-sweep all 11 families + orchestrator cleanup lenses, depth:mini, operator-verified). Adds: - P-8: PoolVault.protocol_reserve_balance is accrue-only, no withdraw path - C-4: LP flush drain hard-aborts on zero-value head request / NAV==0 mark - Maintainability and Pre-Deploy Hygiene section (H-1..H-3) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Public-read classification policy + audit enforcement A zero-caller public getter is not automatically dead (devInspect/PTB consumers are invisible to grep) — but exposure intent must be recorded where it cannot drift. New move.md rule: a public read with no in-repo consumer names its consumer class in its doc comment; zero callers with no stated class = unclassified, default delete pre-deploy (add-later is upgrade-compatible, remove-later is not). The one-way door applies to `public` only; `public(package)` is not frozen by upgrades. Promotes the substance of local-journal D003 into committed policy. Enforcement: surface-area lens reports classify-or-delete instead of dead; rule-sweep public-api-exposure family checks the doc-comment classification on the same enumeration (no new family). open-items H-1 reworded to classify-or-delete; package-only stake_config getters moved to plain cleanup (H-3) since they carry no deploy deadline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Resolve Propbook H-1 public read cleanup * Rules cleanup: dedup move.md, fix stale claims in unit-tests/indexer/CLAUDE - move.md: remove three verbatim duplicate rules from General Move Patterns (object-binding-before-branch, phase-boundary, emit-after- transition) and fold the validate-before-mutate summary into its one unique clause (irreversible resources) — the full versions live in Validation And Ownership / API Shape. Move the embed-repeated-field- group rule up to API Shape; the checklist return-tuple and events sections become pointers to the canonical rules. - unit-tests.md: examples referenced removed APIs (PredictManager, pre-extraction oracle::create_test_oracle / predict::mint shapes); rewritten as schematic examples using the current account/registry/ propbook world. - indexer.md: the "Recommended Indices" all exist via the 2026-02-03 ticker-performance migration — relabel so agents don't re-add them. - CLAUDE.md: path-scoped rule injection is now native Claude Code behavior (observed live with empty hooks config); keep read-it- yourself as the baseline instruction, correct the mechanism claim. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * AGENTS.md refresh: fix stale state, complete routing, promote D-ids - Routing: add predict-harness.md path rule and the harness-strategy manual trigger (present in CLAUDE.md, missing here); correct the rule-injection mechanism claim (native in recent Claude Code, absent in Codex — read-it-yourself stays the baseline). - Repo layout: list all eight crates (predict-* and oracle-* stacks were missing). - Predict rework: the consolidation landed on main; the "Rust crates need rewiring" claim was stale — the predict/oracle crates exist and handle the async-LP events. Replaced with the verified current state: event-parity audit still open (handlers for removed events remain; PoolValued and the refund events have no handler). - Settled decisions: add the static-floor knockout entry and promote D025 (no redeem ask bound), D026 (strike math stays u64), D031 (no oracle deviation guards) from the local decision journal into this committed ledger. Clears all three predict-audit preflight warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Resolve Predict H-3 config cleanup * Address Predict test review cleanup * Predict audit skill: add hygiene trio to the rule sweep Three new mechanical families closing the categories no harness swept: - signature-shape: input ordering by role, wide same-typed primitive runs, receiver-syntax accessor naming — with the mandatory warning that Move signature reorders silently break positional TS PTB callers (scripts/harness/sims) at runtime. - module-layout: visibility-group function ordering, copyright header + module-doc presence, naming conventions (one finding per module). - comment-hygiene: narrating/stale comments, math comments matching the called function and rounding direction, missing public-API/config docs. Rule sweep is now 14 families; SKILL.md counts updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Predict hygiene batch 1: dedup, layout, and doc cleanups Mechanical, behavior-preserving fixes from the mini-audit triage + the hygiene sweep (all suites green: predict 356, propbook 54, account 11): - lp_book: remove_for_recipient now peeks ownership then delegates to remove() instead of cloning it; RequestEntry gains a struct doc naming the per-queue amount unit. - plp: settled-vs-live dispatch shared by value_expiry and rebalance_expiry_cash extracted into settle_or_rebalance_expiry. - propbook registry: bind_block_scholes_surface_to_underlying no longer preflights the source/binding asserts bind_oracle re-runs (same module, same codes — atomicity makes the preflight valueless). - strike_exposure: liquidation-candidate loop uses vector do! macro. - ewma: the thrice-repeated gas-scaling overflow comment and expression become one scaled_gas_price helper. - protocol_config: set_protocol_reserve_profit_share (public) moved out of the public(package) group. - account: read-only getters ordered before share/load_account_mut. - stake_config: the relational upper > 2*lower check moves from config_constants to the owning setter per the Config rule (single- value bounds stay); EInvalidBenefitPowers now aborts from stake_config and the test pin follows; config_constants codes renumbered contiguous; StakeConfig gains a struct doc. - tests: share_exposure_harness helpers renamed create_and_share_exposure_harness (they create AND share). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Predict hygiene batch 2: role-order signature cleanup (package-only) Reorder six package-internal signatures to the move.md input-role order (objects before primitives; consumed asset values directly after the receiver). All call sites are in-package or in tests — sweep-verified zero TypeScript PTB callers, so no off-chain surface moves: - pricing::load_live_pricer: four feed refs before the id/expiry primitives (wrapper + 5 direct test callers updated). - lp_book::drain: (book, ledger, mark, pool_vault_id, budgets, ctx). - lp_book::request_supply/request_withdraw: Coin directly after book. - pool_accounting::receive_expiry_cash: Balance before the id. - oracle_lane::update/insert_at: the OracleRead before the event id. - strike_payout_tree::walk_linear: memo before tick_size. Suites green: predict 356/356, propbook 54/54. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Rules: codify hygiene-sweep exceptions (batch 3, docs only) Five patterns the hygiene sweep confirmed are intentional, promoted to committed rules so future sweeps and agents don't re-flag or "fix" them: - &AccumulatorRoot is execution context (tail with clock/ctx) — never reorder existing public entrypoints; positional TS PTB callers break at runtime. Role order applies to new functions. - emit_* helpers in events/ may mirror event-struct field order. - init-only private helpers come early with init. - Constant-like macro funs live at module top, exempt from visibility-group ordering. - Trivial public field getters under a getters header need no per-function docs when the struct/fields are documented. Sweep family texts updated to match (signature-shape, module-layout, comment-hygiene). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Predict hygiene batch 4: close the audit's test-coverage gaps Eight new tests, all through existing fixtures — zero new #[test_only] source seams (predict 363/363, propbook 55/55, both suites green): - plp::EMarketNotSettled: rebate claim on a past-expiry market with no exact Propbook print aborts at the plp gate. - predict_account::EExpirySummaryHasOpenPositions: rebate claim with an unredeemed position refuses to resolve the summary. - strike_exposure::EReferenceTickAlreadySet: reachable only through a P-4 admin pyth rebind whose replacement feed carries a conflicting exact print — exercised via create_and_rebind_oracle. - registry pause-cap: positive market mint-pause + revoked-cap EPauseCapNotValid (mirrors the existing trading-pause pair). - plp::unstake_deep positive path (new unstake_deep_bundle helper in flow_test_helpers): all staked custody returns, stake mirrors zeroed. - protocol_config::set_ewma_params/set_ewma_enabled positive coverage. - forward_feed::EWrongSource, completing the per-feed guard symmetry. Four guards dispositioned as documented defense-in-depth instead of contrived tests (per unit-tests Rule 12): the market_manager SVI NotBound assert (forward+SVI bind atomically; forward assert fires first), both EInvalidDeploymentExpiry asserts (expiry always comes from next_deployable_market), pricing::ECannotBeNegative (analytically non-negative inside the |rho|<=1 envelope), and the pyth Lazer parse guards (no Move constructor for pyth_lazer::Update). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Predict hygiene batch 5: parked-item decisions (user-adjudicated) Resolves the quick parked items per user decisions (predict 363/363, propbook 55/55, harness + simulations tsc clean): - Rename to committed conventions, pre-deploy while free: create_and_share_expiry_market, create_and_share_builder_code (create_and_share rule), set_template_cadence_config (template-setter rule) — including TS PTB target strings (harness runtime, simulations runtime/sim) and doc/skill references. - Split EInvalidMarketTickSize: the grid check (zero / off-unit) keeps the code; the raw-strike overflow ceiling is now EMarketTickSizeTooLarge, with the overflow test repointed. - Doc lines: OrderMinted/LiveOrderRedeemed builder_code_id field contract (none when no builder fee paid — attribution follows the fee, applied once in the emit helper); pyth_feed div_ceil us->ms rounding direction (<=1ms optimistic, immaterial vs multi-second freshness windows). Adjudicated as won't-fix/settled (recorded in the run dispositions): loop-assert hoist (no gas win, abort-selection change), emit-helper attribution placement (kept), stake/unstake valuation gating (excluded from lp_pool_value), StrikeExposure parent id (already documented), predict_account raw-ID read lookups (accepted). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Open-items: track the remaining hygiene design questions (H-4..H-6, G-1) The non-mechanical tail from the 2026-07-02 hygiene sweeps, promoted so nothing evaporates: complete the public-read classify-or-delete pass (H-4), the careful trade-flow dedup batch (H-5), the CadenceParams / god-module maintainability backlog (H-6), and the pause-gate scope decision for pool cash growth + lock_capital (G-1). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Predict hygiene batch 6: resolve H-4 and G-1 (user-adjudicated) H-4 — public-read classification completed: - Pricer.expiry_market_id narrowed to public(package) (only in-package callers: the expiry_market binding assert and one test). - The propbook raw_* read families classified as kept provenance/ observability API via a group comment under each feed's Read Functions header, matching the README's documented raw/normalized read-pattern split. Future sweeps stop flagging them. G-1 — pause/valuation gate exemptions decided and recorded in the AGENTS.md settled list: top_up_live_expiry_cash growth is intentionally pause-exempt (pause blocks risk at the mint gate; top-up backs existing exposure and funds exits), and lock_capital needs no valuation gate (only legal at supply==0, when ENotBootstrapped on both LP request entrypoints means no queue entry or holder the lock protects exists). Both removed from open-items; suites green (predict 363/363, propbook 55/55). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Predict audit skill: add polish trio to the rule sweep Three new families for classes no prior sweep covered: - test-independence: unit-tests Rule 1 conformance — expected values independent of the implementation, no self-asserting snapshots, no measured-from-output tolerances (the LN_2 bug class). - docs-drift: committed prose vs HEAD — every named identifier resolves, flows and vocabulary match current code. - abort-code-multiplexing: one error code per distinct predicate (the EInvalidMarketTickSize precedent, done exhaustively). Rule sweep is now 17 families; SKILL.md counts updated. Also formats account_events.move (only prettier drift in the three packages). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Docs: fix HEAD drift found by the docs-drift sweep All verified against source at HEAD 969dcf2. Post-rework prose drift, doc-only (zero code change): - Tick domain: u24 / "24-bit" / 2²⁴−1 → u30 / 30-bit / 2³⁰−1 (constants::tick_bits!() = 30) across markets-and-positions, invariants, configuration, glossary. - Removed cap/proof model still documented: drop EProofRequiredForLiveRedeem; live redeem requires account::Auth. - Wrong identifiers: `redeem` → `redeem_live`; `drain_lp_requests` → `lp_book::drain`; event `PoolValued` → `FlushExecuted` (field pool_value); `profit_credits`/`profit_debits` → `profit_basis_credits`/`profit_basis_debits`; Registry.underlying_configs → MarketManager.underlying_configs. - Missing admission_tick_size in set_template_cadence_config (prose + mermaid) and in the create_and_share_expiry_market snapshot list. - account README: bare deposit/withdraw are stored-balance-only (self, coin)/(self, amount, ctx); document settle / deposit_funds / withdraw_funds as the accumulator-settling entrypoints. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Predict hygiene batch 7: abort-code split + test-independence hard-codes From the 17-family prove-clean sweep's polish trio. abort-code-multiplexing — split the one genuinely-misleading name: - pricing: the BS data-ABSENCE asserts (never-published spot/forward/svi) now abort EBlockScholesPriceUnavailable / EBlockScholesSVIUnavailable, distinct from the freshness asserts that keep EBlockScholesPriceStale / EBlockScholesSVIStale. The name previously lied when a feed had never published. Adds two absence expected_failure tests (skip the oracle seed). The remaining multi-site codes (EBalanceTooLow = absence vs below; EInsufficientCash = solvency-floor vs availability; the envelope/overflow clusters) are acceptable single-condition groupings — dispositioned, not split. test-independence (unit-tests Rule 1) — expected values no longer built from the production formula or a source fixed-point helper: - strike_exposure_c1: survivor floor hard-coded to the independently computed 4_166 (derivation in a comment), deleting the helper that re-implemented the production split. - backing_buffer / cash_backing: the gap buffer is expressed as gap / 4 (λ_default = 0.25) using test constants, not math::mul(λ, gap); the math/config_constants helper + now-unused imports removed. The math::mul(λ, gap) == QUANTITY/4 assert stays as the independent check of the production helper itself. predict 365/365. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Predict dev-system consolidation: one tracked knowledge system The development-process state was split between the tracked predeploy/ directory and gitignored personal scratch (.claude/predict-design/), with the connective artifacts (experiment tracker, decision journal) invisible to teammates. This consolidates to one coherent, tracked system. - predeploy/README.md becomes the system map: mission, authority order, surface table, lifecycle loops (finding->decision, experiment, audit, guard-change), and pickup entry points - Promote the harness experiment tracker to predeploy/experiments.md: rewired to current IDs (C-*/RP-*), every experiment carries a driving ID and a pre-registered decision rule; adds E6 (RP-2 dust-window) and retires the genesis-CB probe (breaker removed in cc67ed9) - Extract-and-delete the design-era journals (DECISION_JOURNAL, HISTORY): rejected-directions ledger + AUD-002 genesis-defaults promoted to AGENTS.md; D035 single-owner-facts event rule promoted to move.md; everything else verified superseded or already promoted - Promote versioning-and-loaders.md (unimplemented proposal) to predeploy - Add guard-removal duty-inventory + response-ladder rules to move.md; matching review obligations + checklist lines in code-review.md - packages/predict/README.md gains the development-system front door; CLAUDE.md/AGENTS.md route to the map; predict-design/.redesign are declared personal-scratch-only Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xct5NpFmALuRtz2tQL18yG * Predict dev-system enforcement: cross-reference linter + audit register wiring The system's premise is that no session retains memory, so the artifacts must machine-check each other. This adds the enforcement layer: - predeploy/check.py: stdlib linter — every register pinning test exists as a real `fun` under tests/, RP-*/E*/item-ID cross-references resolve across the trackers, MEASURED risk claims link an existing findings doc, and named paths are live (historical/external-repo lines exempt). Mutation-tested both ways (injected fake test + dead path -> FATAL). First real run caught two drifts: a pre-extraction oracle path in the versioning proposal (annotated) and validated the keeper-runbook cross-repo reference handling. - predict-audit wiring: check.py joins preflight in SKILL.md Step 1; register obligations added (re-verify every RP entry at HEAD, don't re-flag verified decisions, adversarially re-derive 1-2 entries per run); primer prior-awareness now covers response-policies.md and the AGENTS.md rejected-directions ledger, and states that entry-vs-HEAD drift is itself a finding. - code-review.md: run check.py on diffs touching predeploy/, guards, or register-named tests; SKILL.md D004 citation re-pointed at the CLAUDE.md guardrail (caught by preflight's own D-id check after the journal extraction). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xct5NpFmALuRtz2tQL18yG * Predict hygiene H-2: mint fee decomposition derived once, in settle_mint_payment mint_prepared_exact_quantity and settle_mint_payment each derived the builder-fee/subsidy/trader-fee split, so the max_cost slippage bound and the actual withdrawal stayed equal only by hand-maintained sync. Move the max_cost assert into settle_mint_payment, next to the withdraw_amount it bounds: one derivation site, and the guard checks the exact total withdrawn by construction. Behavior-preserving (suite 367/367; abort point unchanged within the same atomic flow). Adds a boundary pair pinning the all-in semantics: mint succeeds at max_cost == net_premium + fee (505e6 for the at-the-money 1e9 fixture, exact post-mint balance asserted) and aborts EMintCostAboveMax one unit below. Both pass pre- and post-refactor. Removes H-2 from the predeploy tracker (check.py clean). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Docs: document the BS absence abort codes from the batch-7 split Batch 7 split the never-published/non-normalizable BS read aborts into EBlockScholesPriceUnavailable / EBlockScholesSVIUnavailable, distinct from the freshness Stale codes. The concept docs and decisions.md only named the Stale codes; integrators map abort codes, so the absence path needs naming too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xct5NpFmALuRtz2tQL18yG * Address PR #1097 review: in-branch drift + tooling self-consistency The full-PR multi-agent review found zero Critical/High and no correctness or guard regressions; these are the confirmed doc-drift and system- self-consistency findings, fixed in-branch because this PR is itself the docs-drift-and-consistency sweep. Doc drift (code contradicts the docs at HEAD): - Three docs (liquidity-and-nav, architecture, glossary) claimed the LP drain refunds degenerate dust requests; lp_book::drain aborts EInvalidDrainMark (the exact claim risks.md was already corrected for) — reworded to the current abort behavior, C-4 as the intended fix. - invariants.md order-id bit total 184 -> 196 (missed by the u24->u30 fix). - registry.move stale "insert-only bindings" comment -> "current (admin-replaceable)" after P-4. - AGENTS.md event-parity note named PoolValued/SupplyRefunded/WithdrawRefunded as current-but-unhandled; PoolValued is now FlushExecuted and the refund events don't exist (unimplemented C-4) — corrected. - versioning-and-loaders.md "Shipped today" described a nonexistent allowed_versions VecSet-mirror surface; corrected to the real ProtocolConfig.version_watermark model + a staleness note. Fixed a pre-rename create_expiry_market reference. - unit-tests.md worked example cited the deleted assert_plp_price_in_bounds. - harness runtime.ts dangling #lp-nav-zero-brick anchor / stale NAV==0 semantics -> C-4 / RP-2. Tooling self-consistency (the PR contradicted its own new rules): - check.py pinning-tests check was block-driven, so a missing / mislabelled / un-backticked Pinning-tests field slipped past silently; rewritten entry-driven so every RP entry must link a test or say "not yet catalogued" (mutation-tested). RP-3's field corrected to disclose that its cited test reaches NAV==0 via an underwater market and does not exercise the sticky-exclusion clamp's own trigger. - All four audit workflows' settled-source lists now include response-policies.md, so verify agents can return "settled" with an RP-id per the register obligation this PR added to SKILL.md/primer.md. Deferred to open-items H-7 (test strengthenings, not blockers): the RP-3 clamp-trigger test, cadence public-read classification + coverage, the pricing forward-absence pinning test, and one-sided boundary assertions. Verification: sui move build --warnings-are-errors clean; 367/367 tests; harness tsc 0; check.py + preflight clean; all four workflows parse. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xct5NpFmALuRtz2tQL18yG --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 83e1891 commit ed1c054

87 files changed

Lines changed: 3694 additions & 798 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/rules/code-review.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ Read this manual-trigger file when the user asks for a code review. It is routed
2222
- Ownership and responsibility boundaries for modules/functions: verify state is mutated by its owner, helpers do not absorb parent responsibilities, and callers do not duplicate leaf invariants without a sequencing reason.
2323
- Producer-side policy: flag cross-module returns that pre-apply a consumer's policy — especially lossy transforms (clamp at zero, saturating subtraction, `min`/`max`, rounding) that a downstream consumer then corrects for or re-derives. Bug signatures: the same economic quantity clamped at two altitudes, stance-named returns (`*_optimistic`, `net_*`), or tests that must invert a producer's step to state expectations.
2424
- Non-functional regressions in "behavior-preserving" changes: before approving a hot-path/loop refactor, confirm a deliberate loop-invariant hoist wasn't traded away for a cleaner signature — recomputing a per-iteration value previously sampled once is a gas regression even if outputs are identical (D020/D021).
25+
- Guard removals or weakenings: require a duty inventory in the diff — what else the guard incidentally bounded (arithmetic headroom, ratio/scale sanity, a downstream cast or consumer precondition) — plus an entry in `packages/predict/predeploy/response-policies.md`. Reasoning that lives only in the commit message is a finding (the P-1 circuit breaker removal `cc67ed9f` silently dropped the only u64-headroom bound on LP fill math).
26+
- State-triggered aborts over market-controlled variables (NAV, prices, post-loss balances) in shared/mandatory paths (flush, keeper batches, permissionless cleanup): flag as undecided response policies unless registered in `response-policies.md`. Check the response sits on the right blast-radius ladder rung (abort = single-user recoverable only → skip/carry for batches → pause only with a recovery path → designed wind-down) and that boundary tests pin each classification edge — including representability edges (a bare `as u64` cast on an unbounded ratio is an untracked abort path).
27+
- Doc claims about failure behavior (`docs/risks.md`, module docs) must match code at HEAD. A doc describing intended-but-unimplemented behavior as shipped is a finding (precedent: risks.md claimed the LP drain refunds degenerate requests while the code hard-aborted, C-4).
28+
- When the diff touches `packages/predict/predeploy/`, Predict guards, or tests named in the register, run `python3 packages/predict/predeploy/check.py` — it verifies register pinning tests exist, tracker cross-references resolve, MEASURED claims link evidence, and named paths are live. A FATAL is a review finding.
2529

2630
## Comments and Documentation
2731

@@ -72,3 +76,5 @@ Read this manual-trigger file when the user asks for a code review. It is routed
7276
- [ ] Do timestamp updates match the field's documented semantics?
7377
- [ ] Do function inputs match ownership: objects for owned/current state, narrow values for pure formulas, no wide same-typed primitive lists?
7478
- [ ] Do cross-module returns carry owned facts, with every lossy clamp applied once at the policy owner?
79+
- [ ] If a guard was removed or weakened, does the diff include its duty inventory and a `response-policies.md` entry?
80+
- [ ] Do doc claims about failure behavior (risks.md, module docs) match the code in this diff, with pinning tests?

.claude/rules/indexer.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,11 @@ Calls `/ticker` internally, so inherits all its performance issues.
6060
### margin_manager_state Table
6161
- Manager lookups: filter/join by `deepbook_pool_id`
6262

63-
## Recommended Indices
63+
## Performance Indices (already applied)
6464

65-
These composite indices significantly improve query performance:
65+
These composite indices exist via migration
66+
`2026-02-03-000000-0000_add_ticker_performance_indexes` — do not re-add them.
67+
Kept here as the query-pattern reference for new endpoints:
6668

6769
```sql
6870
-- order_fills: volume and ticker queries

.claude/rules/move.md

Lines changed: 15 additions & 20 deletions
Large diffs are not rendered by default.

.claude/rules/unit-tests.md

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -62,27 +62,29 @@ assert_eq!(normal_cdf(0, false), 500_000_000);
6262
A test that only verifies "doesn't abort" is not a test. Every `#[test]` function must have at least one `assert!` or `assert_eq!` that checks a return value or state change.
6363

6464
```move
65-
// BAD — tests nothing, will pass even if deposit is silently broken
65+
// BAD — tests nothing, will pass even if deposit is silently broken (schematic)
6666
#[test]
6767
fun deposit_adds_funds() {
68-
let mut pm = create_predict_manager(ctx);
69-
pm.deposit(coin::mint_for_testing(1000, ctx));
70-
destroy(pm);
68+
let mut account = new_test_account(ctx);
69+
account.deposit(coin::mint_for_testing<SUI>(1000, ctx));
70+
destroy(account);
7171
}
7272
73-
// GOOD — verifies the state change actually happened
73+
// GOOD — verifies the state change actually happened (schematic)
7474
#[test]
7575
fun deposit_adds_funds() {
76-
let mut pm = create_predict_manager(ctx);
77-
pm.deposit(coin::mint_for_testing(1000, ctx));
78-
assert_eq!(pm.balance<SUI>(), 1000);
79-
destroy(pm);
76+
let mut account = new_test_account(ctx);
77+
account.deposit(coin::mint_for_testing<SUI>(1000, ctx));
78+
assert_eq!(account.balance<SUI>(/* ... */), 1000);
79+
destroy(account);
8080
}
8181
```
8282

8383
### 4. Cover every abort code with an `expected_failure` test
8484

85-
For every *reachable* `const E*` error constant in a source module, there must be at least one test that triggers it; untested abort codes are untested error paths. Coverage applies regardless of visibility: if a guard behind a `public(package)` helper can't be triggered as-is, that is a signal to make it independently testable — extract the pure precondition into a `public(package)` checker taking only its scalar inputs (e.g. `assert_plp_price_in_bounds(pool_nav, total_supply)`) and test that directly. "The guard is package-internal" is never a reason to ship an untested abort code; add the root-free unit test or ledger it as a tracked deploy-gate. For a code that is genuinely structurally-unreachable in production, document why instead of contriving a bypass test (Rule 12 forbids bypassing production validation just to reach a code).
85+
For every *reachable* `const E*` error constant in a source module, there must be at least one test that triggers it; untested abort codes are untested error paths. Coverage applies regardless of visibility: if a guard behind a `public(package)` helper can't be triggered as-is, that is a signal to make it independently testable — extract the pure precondition into a `public(package)` checker taking only its scalar inputs (e.g. a `lp_book::supply_shares(amount, mark)` helper whose `EInvalidDrainMark` guard is unit-testable directly) and test that directly. "The guard is package-internal" is never a reason to ship an untested abort code; add the root-free unit test or ledger it as a tracked deploy-gate. For a code that is genuinely structurally-unreachable in production, document why instead of contriving a bypass test (Rule 12 forbids bypassing production validation just to reach a code).
86+
87+
Use the source module's named error constant in `expected_failure(abort_code = ...)`, not a numeric literal. The `E*` constant is the source of truth for the semantic error; if its numeric value is intentionally renumbered, behavior tests should not need updates. Numeric abort-code literals are allowed only for opaque VM/framework aborts with no named source constant, or for a deliberate ABI-layout test that is clearly labeled as such and not pretending to be behavior coverage.
8688

8789
```move
8890
#[test, expected_failure(abort_code = predict::EOracleSettled)]
@@ -219,32 +221,24 @@ If a test claims to model a real protocol scenario, its fixture must be valid un
219221
- Do not add test-only error codes or bypass paths just to validate a helper constructor itself unless the helper is the thing being intentionally tested.
220222

221223
```move
222-
// BAD — hides an impossible production state behind a permissive helper
223-
let oracle = oracle::create_test_oracle(...); // unconstrained strike grid
224-
predict::mint(&mut predict, &mut manager, &oracle, key, quantity, &clock, ctx);
225-
226-
// GOOD — production-behavior test uses production-valid fixture rules
227-
let oracle = oracle::create_test_oracle_with_grid(
228-
b"BTC".to_string(),
229-
svi,
230-
prices,
231-
0,
232-
expiry,
233-
0,
234-
min_strike,
235-
max_strike,
236-
tick_size,
237-
ctx,
238-
);
239-
predict::mint(&mut predict, &mut manager, &oracle, key, quantity, &clock, ctx);
224+
// BAD — hides an impossible production state behind a permissive helper (schematic)
225+
let market = new_unconstrained_test_market(ctx); // bypasses cadence/tick admission
226+
market.mint_exact_quantity(/* ... */, &clock, ctx);
227+
228+
// GOOD — production-behavior test builds state through production flows (schematic):
229+
// create the market via registry::create_and_share_expiry_market under a valid cadence
230+
// config, seed the propbook feeds for that market's ACTUAL expiry, then mint.
231+
let market = create_market_via_registry(&mut registry, /* ... */, ctx);
232+
seed_feeds_for_expiry(&mut pyth_feed, &mut bs_feeds, market.expiry(), /* ... */);
233+
market.mint_exact_quantity(/* ... */, &clock, ctx);
240234
```
241235

242236
### 13. Shared-object tests should capture IDs and use `take_shared_by_id`
243237

244238
When a test creates a shared object in `test_scenario`, capture its ID immediately and use that ID in later transactions. Do not rely on ambient `take_shared<T>()` when the object identity is already known.
245239

246240
- Good:
247-
- setup helper returns `predict_id`, `registry_id`, `manager_id`, `oracle_id`
241+
- setup helper returns `registry_id`, `market_id`, `vault_id`, `account_id`
248242
- later transactions use `take_shared_by_id<T>(id)`
249243
- shared objects are returned with `return_shared(...)` before `next_tx(...)`
250244
- Bad:

0 commit comments

Comments
 (0)