dexbot 1.6.5 → 1.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -4
- package/README.md +108 -36
- package/analysis/README.md +11 -16
- package/analysis/account_resolver.ts +1 -11
- package/analysis/bot_usage/kibana_bot_queries.ts +1 -1
- package/analysis/chain_pool.ts +1 -1
- package/analysis/fills_source.ts +0 -6
- package/analysis/grid_correction_check.ts +20 -26
- package/analysis/resolve_source.ts +1 -1
- package/analysis/trade_profitability.ts +2 -5
- package/analysis/trend_detection/README.md +1 -3
- package/analysis/trend_detection/package.json +1 -1
- package/claw/package.json +1 -1
- package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
- package/claw/runtimes/openclaw-plugin/package.json +1 -1
- package/claw/tests/test_claw_mcp_transport.ts +2 -2
- package/dist/analysis/account_resolver.d.ts +1 -4
- package/dist/analysis/account_resolver.d.ts.map +1 -1
- package/dist/analysis/account_resolver.js +1 -1
- package/dist/analysis/account_resolver.js.map +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts +1 -2
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts.map +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.js +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.js.map +1 -1
- package/dist/analysis/chain_pool.d.ts +1 -3
- package/dist/analysis/chain_pool.d.ts.map +1 -1
- package/dist/analysis/chain_pool.js +1 -1
- package/dist/analysis/chain_pool.js.map +1 -1
- package/dist/analysis/fills_source.d.ts +1 -51
- package/dist/analysis/fills_source.d.ts.map +1 -1
- package/dist/analysis/fills_source.js +1 -1
- package/dist/analysis/fills_source.js.map +1 -1
- package/dist/analysis/grid_correction_check.d.ts +2 -3
- package/dist/analysis/grid_correction_check.d.ts.map +1 -1
- package/dist/analysis/grid_correction_check.js +18 -26
- package/dist/analysis/grid_correction_check.js.map +1 -1
- package/dist/analysis/resolve_source.d.ts +1 -1
- package/dist/analysis/resolve_source.d.ts.map +1 -1
- package/dist/analysis/trade_profitability.d.ts.map +1 -1
- package/dist/analysis/trade_profitability.js +2 -7
- package/dist/analysis/trade_profitability.js.map +1 -1
- package/dist/dexbot.d.ts.map +1 -1
- package/dist/dexbot.js +82 -83
- package/dist/dexbot.js.map +1 -1
- package/dist/market_adapter/core/kibana_candles.d.ts +1 -15
- package/dist/market_adapter/core/kibana_candles.d.ts.map +1 -1
- package/dist/market_adapter/core/kibana_candles.js +1 -1
- package/dist/market_adapter/core/kibana_candles.js.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts +1 -35
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.js +10 -54
- package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -1
- package/dist/market_adapter/inputs/kibana_feed_source.d.ts +1 -8
- package/dist/market_adapter/inputs/kibana_feed_source.d.ts.map +1 -1
- package/dist/market_adapter/inputs/kibana_feed_source.js +1 -1
- package/dist/market_adapter/inputs/kibana_feed_source.js.map +1 -1
- package/dist/market_adapter/inputs/window_cache.d.ts +2 -63
- package/dist/market_adapter/inputs/window_cache.d.ts.map +1 -1
- package/dist/market_adapter/inputs/window_cache.js +22 -102
- package/dist/market_adapter/inputs/window_cache.js.map +1 -1
- package/dist/modules/account_bots.d.ts +16 -2
- package/dist/modules/account_bots.d.ts.map +1 -1
- package/dist/modules/account_bots.js +108 -44
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/account_orders.d.ts +20 -2
- package/dist/modules/account_orders.d.ts.map +1 -1
- package/dist/modules/account_orders.js +63 -3
- package/dist/modules/account_orders.js.map +1 -1
- package/dist/modules/bitshares-native/chain_client.d.ts +10 -0
- package/dist/modules/bitshares-native/chain_client.d.ts.map +1 -1
- package/dist/modules/bitshares-native/chain_client.js +197 -28
- package/dist/modules/bitshares-native/chain_client.js.map +1 -1
- package/dist/modules/bitshares-native/crypto/ecc.browser.d.ts.map +1 -1
- package/dist/modules/bitshares-native/crypto/ecc.browser.js +0 -31
- package/dist/modules/bitshares-native/crypto/ecc.browser.js.map +1 -1
- package/dist/modules/bitshares-native/subscriptions.d.ts.map +1 -1
- package/dist/modules/bitshares-native/subscriptions.js +312 -10
- package/dist/modules/bitshares-native/subscriptions.js.map +1 -1
- package/dist/modules/bitshares-native/transport.d.ts +1 -0
- package/dist/modules/bitshares-native/transport.d.ts.map +1 -1
- package/dist/modules/bitshares-native/transport.js +97 -3
- package/dist/modules/bitshares-native/transport.js.map +1 -1
- package/dist/modules/bitshares_client.d.ts +20 -0
- package/dist/modules/bitshares_client.d.ts.map +1 -1
- package/dist/modules/bitshares_client.js +29 -0
- package/dist/modules/bitshares_client.js.map +1 -1
- package/dist/modules/chain_keys.d.ts +37 -22
- package/dist/modules/chain_keys.d.ts.map +1 -1
- package/dist/modules/chain_keys.js +145 -44
- package/dist/modules/chain_keys.js.map +1 -1
- package/dist/modules/cli_start_onboarding.d.ts +10 -0
- package/dist/modules/cli_start_onboarding.d.ts.map +1 -0
- package/dist/modules/cli_start_onboarding.js +15 -0
- package/dist/modules/cli_start_onboarding.js.map +1 -0
- package/dist/modules/cli_start_output.d.ts +6 -0
- package/dist/modules/cli_start_output.d.ts.map +1 -0
- package/dist/modules/cli_start_output.js +8 -0
- package/dist/modules/cli_start_output.js.map +1 -0
- package/dist/modules/constants.d.ts +13 -0
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +103 -1
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/credit_runtime.d.ts +0 -2
- package/dist/modules/credit_runtime.d.ts.map +1 -1
- package/dist/modules/credit_runtime.js +1 -10
- package/dist/modules/credit_runtime.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +13 -10
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +0 -9
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +13 -0
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +62 -10
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_fill_runtime.d.ts +6 -1
- package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_fill_runtime.js +22 -3
- package/dist/modules/dexbot_fill_runtime.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts +24 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +45 -1
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/dexbot_state_recovery.d.ts.map +1 -1
- package/dist/modules/dexbot_state_recovery.js +13 -1
- package/dist/modules/dexbot_state_recovery.js.map +1 -1
- package/dist/modules/order/accounting.d.ts +4 -18
- package/dist/modules/order/accounting.d.ts.map +1 -1
- package/dist/modules/order/accounting.js +4 -34
- package/dist/modules/order/accounting.js.map +1 -1
- package/dist/modules/order/export.d.ts +9 -12
- package/dist/modules/order/export.d.ts.map +1 -1
- package/dist/modules/order/export.js +9 -12
- package/dist/modules/order/export.js.map +1 -1
- package/dist/modules/order/grid.d.ts +35 -40
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +76 -44
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile.js +111 -20
- package/dist/modules/order/grid_reconcile.js.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts +5 -3
- package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.js +29 -3
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
- package/dist/modules/order/logger.d.ts +12 -2
- package/dist/modules/order/logger.d.ts.map +1 -1
- package/dist/modules/order/logger.js +29 -7
- package/dist/modules/order/logger.js.map +1 -1
- package/dist/modules/order/manager.d.ts +49 -8
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +72 -34
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +22 -7
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +200 -33
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/order/utils/system.d.ts +97 -0
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +274 -2
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/order/utils/withPoolRef.d.ts +12 -0
- package/dist/modules/order/utils/withPoolRef.d.ts.map +1 -1
- package/dist/modules/order/utils/withPoolRef.js +17 -0
- package/dist/modules/order/utils/withPoolRef.js.map +1 -1
- package/dist/modules/order/working_grid.d.ts +8 -13
- package/dist/modules/order/working_grid.d.ts.map +1 -1
- package/dist/modules/order/working_grid.js +8 -29
- package/dist/modules/order/working_grid.js.map +1 -1
- package/dist/modules/paths.d.ts +7 -1
- package/dist/modules/paths.d.ts.map +1 -1
- package/dist/modules/paths.js +10 -3
- package/dist/modules/paths.js.map +1 -1
- package/dist/modules/utils/text_width.d.ts +19 -0
- package/dist/modules/utils/text_width.d.ts.map +1 -0
- package/dist/modules/utils/text_width.js +97 -0
- package/dist/modules/utils/text_width.js.map +1 -0
- package/dist/pm2.d.ts.map +1 -1
- package/dist/pm2.js +95 -18
- package/dist/pm2.js.map +1 -1
- package/dist/scripts/sync-version.js +0 -22
- package/dist/scripts/sync-version.js.map +1 -1
- package/dist/scripts/update.js +62 -13
- package/dist/scripts/update.js.map +1 -1
- package/dist/scripts/update_dist_freshness.d.ts +12 -0
- package/dist/scripts/update_dist_freshness.d.ts.map +1 -1
- package/dist/scripts/update_dist_freshness.js +30 -1
- package/dist/scripts/update_dist_freshness.js.map +1 -1
- package/dist/unlock.d.ts +14 -2
- package/dist/unlock.d.ts.map +1 -1
- package/dist/unlock.js +73 -4
- package/dist/unlock.js.map +1 -1
- package/docs/BITSHARES_ONBOARDING.md +9 -6
- package/docs/COW_INVARIANTS.md +9 -0
- package/docs/CREDENTIAL_SECURITY.md +26 -0
- package/docs/DEXBOT_COMPARISON.md +11 -11
- package/docs/EVOLUTION.md +9 -7
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +1 -1
- package/docs/GRID_PRICE_INVARIANT.md +573 -0
- package/docs/GRID_RECALCULATION.md +1 -1
- package/docs/GRID_RECONCILE.md +60 -39
- package/docs/LOGGING.md +17 -2
- package/docs/ORDER_ENGINE_POST_1.0_RETROSPECTIVE.md +555 -0
- package/docs/README.md +3 -3
- package/docs/WORKFLOW.md +12 -6
- package/docs/architecture.md +40 -38
- package/docs/developer_guide.md +4 -8
- package/market_adapter/README.md +16 -12
- package/modules/README.md +1 -1
- package/package.json +8 -4
- package/scripts/README.md +0 -9
- package/scripts/reset-settings.sh +1 -1
- package/analysis/ama_fitting/package.json +0 -12
- package/analysis/analyze_derivatives.ts +0 -320
- package/analysis/derivative_chart_generator.ts +0 -874
- package/analysis/trend_detection/SIGNAL_DOCUMENTATION.md +0 -403
- package/analysis/trend_detection/derivative_analyzer.ts +0 -1059
- package/dist/analysis/analyze_derivatives.d.ts +0 -68
- package/dist/analysis/analyze_derivatives.d.ts.map +0 -1
- package/dist/analysis/analyze_derivatives.js +0 -287
- package/dist/analysis/analyze_derivatives.js.map +0 -1
- package/dist/analysis/derivative_chart_generator.d.ts +0 -12
- package/dist/analysis/derivative_chart_generator.d.ts.map +0 -1
- package/dist/analysis/derivative_chart_generator.js +0 -891
- package/dist/analysis/derivative_chart_generator.js.map +0 -1
- package/dist/analysis/trend_detection/derivative_analyzer.d.ts +0 -248
- package/dist/analysis/trend_detection/derivative_analyzer.d.ts.map +0 -1
- package/dist/analysis/trend_detection/derivative_analyzer.js +0 -910
- package/dist/analysis/trend_detection/derivative_analyzer.js.map +0 -1
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
# DEXBot2 Order Engine Retrospective (Post-1.0.0)
|
|
2
|
+
|
|
3
|
+
> Single hub for the order engine's post-stable evolution: a **synthesis** of why it kept
|
|
4
|
+
> misbehaving (Part I) and the preserved **incident & fix ledger** (Part II), formerly
|
|
5
|
+
> `CONSOLIDATED_ORPHAN_FIX_SUMMARY.md`.
|
|
6
|
+
>
|
|
7
|
+
> **Sources:** `CHANGELOG.md` v1.0.0 → v1.6.3; `docs/GRID_PRICE_INVARIANT.md`;
|
|
8
|
+
> `docs/COW_INVARIANTS.md`. Generic terms only — no live markets, accounts, or order ids.
|
|
9
|
+
>
|
|
10
|
+
> **Scope / snapshot:** v1.0.0 → v1.6.3.
|
|
11
|
+
>
|
|
12
|
+
> **Section numbering:** Part II deliberately keeps its original `§0`–`§8` numbers because
|
|
13
|
+
> source-code comments and tests cite them (e.g. `… §2 fix #1`, `… §3 lineage`). Part I uses
|
|
14
|
+
> `R1`–`R5` so the two never collide. The closing appendix is unnumbered (`A`) so the cited `§` range stays fixed.
|
|
15
|
+
>
|
|
16
|
+
> **How to read:** start with **Part I** for the explanation; drop into **Part II** when you need
|
|
17
|
+
> the per-incident evidence, commit hashes, and verification targets.
|
|
18
|
+
>
|
|
19
|
+
> **See also:** [GRID_PRICE_INVARIANT.md](GRID_PRICE_INVARIANT.md) ·
|
|
20
|
+
> [COW_INVARIANTS.md](COW_INVARIANTS.md) · [GRID_RECONCILE.md](GRID_RECONCILE.md) ·
|
|
21
|
+
> [EVOLUTION.md](EVOLUTION.md) · [CHANGELOG.md](../CHANGELOG.md).
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Contents
|
|
26
|
+
|
|
27
|
+
**Part I — Synthesis**
|
|
28
|
+
- R1. The one root cause
|
|
29
|
+
- R2. Recurring bug families
|
|
30
|
+
- R3. Meta-patterns (the actual "madness")
|
|
31
|
+
- R4. What actually fixed it
|
|
32
|
+
- R5. Lessons
|
|
33
|
+
|
|
34
|
+
**Part II — Incident & Fix Ledger**
|
|
35
|
+
- 0. Terminology
|
|
36
|
+
- 1. Gap-Band Orphan — Prevention Plan (P1–P6)
|
|
37
|
+
- 2. Ladder Recenter Orphan — Root Cause (Aug 26–30 2026)
|
|
38
|
+
- 3. Orphan-Fill & Fund-Invariant — Root Cause & Fix Plan
|
|
39
|
+
- 4. Price-First Alignment Plan
|
|
40
|
+
- 5. Cross-doc fix lineup (dependency order)
|
|
41
|
+
- 6. Consolidated invariants
|
|
42
|
+
- 7. Consolidated verification — grid-correction check as regression gate
|
|
43
|
+
- 8. Provenance & supersession record
|
|
44
|
+
- A. Grid-Price Invariant — former failure trace (appendix)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Part I — Synthesis
|
|
49
|
+
|
|
50
|
+
### R1. The one root cause
|
|
51
|
+
|
|
52
|
+
BitShares offers **no atomic grid swap**. The engine can only broadcast cancel /
|
|
53
|
+
limit-order-update / create operations over an unreliable network and then *guess* whether
|
|
54
|
+
they landed. A lost or ambiguous response — **"uncertain broadcast"** — means the bot either
|
|
55
|
+
forgets real orders (**orphans**) or re-places over live ones (**duplicates**). There is no
|
|
56
|
+
rollback, so the runtime is a perpetual reconciliation loop trying to infer chain truth from
|
|
57
|
+
local state.
|
|
58
|
+
|
|
59
|
+
**Everything below is fallout from that.**
|
|
60
|
+
|
|
61
|
+
### R2. Recurring bug families
|
|
62
|
+
|
|
63
|
+
| Family | Symptom | Representative handling |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| **Orphan orders** | Live chain orders absent from local state; locked funds, accidental cancels, re-placement | Adoption, committed-order protection, durable orphan gates — fixed/re-broken repeatedly |
|
|
66
|
+
| **Ghost / phantom orders** | Local slot with no real order, or a live order no slot owns; `size > 0` with no `orderId` | Virtualization guards, ghost-order cleanup, dust cancel |
|
|
67
|
+
| **Self-trading / crossing** | Re-pricing a BUY above own SELLs (or vice versa) | `LAST-FILL-GUARD` (pivot formula revised twice) + final pre-broadcast pivot gate |
|
|
68
|
+
| **Grid-price invariant** | Two sources of truth for a slot price (genesis ladder vs mutable `slot.price`); orphan adoption corrupted the ladder | Enforce ladder price at every emission site; escalate persistent violations to resync |
|
|
69
|
+
| **Boundary drift / gap band** | Buy/sell split index moved without matching fills, stranding live orders in the band | Prevention plans P1–P6 landed, then largely reverted; replaced by fill-driven boundary + last-fill guard |
|
|
70
|
+
| **Fund-accounting races** | Stale totals, optimistic deductions, "orphan-fill death spiral" | Lag guards, tolerance widening, rebuild-from-chain, invariant escalation |
|
|
71
|
+
| **Concurrency / locks** | Non-reentrant lock, force-release orphaned timers, TOCTOU, chunked-broadcast self-fills | Reentrant `AsyncLock`, generation counters, swept-band exclusion |
|
|
72
|
+
| **Infinite loops** | grid-bloat → resync → bloat; plan→skip→restore holes; `NO_FEASIBLE_BOUNDARY` freeze | Cooldowns, grace windows, minimal cancel ladders |
|
|
73
|
+
| **Silent hangs** | Retries that depended on a future event that never came | Level-triggered scheduling with time-based watchdogs |
|
|
74
|
+
| **Stale queues** | Queued price corrections replayed onto a resynced grid, reverting good placements | Pre-broadcast validation against the live slot |
|
|
75
|
+
| **Shelf / reserve orders** | Non-grid orders poisoning every "orders on grid" counter (issue #27) | Slot-N gating across all counters |
|
|
76
|
+
|
|
77
|
+
Families **not** detailed in Part II: silent hangs and stale correction queues are covered in
|
|
78
|
+
`CHANGELOG.md` (v1.6.1 and v1.6.3 respectively); shelf/reserve orders in issue #27 and the v1.6.x
|
|
79
|
+
notes; the grid-price invariant has its own doc — [GRID_PRICE_INVARIANT.md](GRID_PRICE_INVARIANT.md).
|
|
80
|
+
|
|
81
|
+
### R3. Meta-patterns (the actual "madness")
|
|
82
|
+
|
|
83
|
+
1. **Revert yo-yo.** `MarketAnchor` / `BOUNDARY-EVIDENCE` / `BAND-EXCLUSION` were added
|
|
84
|
+
then reverted. Gap-band sweeps shipped and were pulled after cancelling legitimate
|
|
85
|
+
orders. Part II annotates fixes as `LANDED` / `REVERTED` / `SUPERSEDED`.
|
|
86
|
+
2. **Guards on guards.** Each fix exposed a new failure mode, so the engine accumulated
|
|
87
|
+
fallback paths, origin stamps, bypass flags, and tolerance knobs. New bug → another
|
|
88
|
+
guard, rarely a simpler model.
|
|
89
|
+
3. **Incident documentation as a genre.** Part II exists because the same classes of bug
|
|
90
|
+
kept returning and the fix plans themselves kept being superseded.
|
|
91
|
+
4. **Daemon vs demon.** The literal daemons (credential/signing/node) were boundable —
|
|
92
|
+
socket leaks, shutdown races, node failover. The demons were *policy* for uncertain
|
|
93
|
+
state; you can restart a daemon, you can only *model* an ambiguous broadcast, and the
|
|
94
|
+
model kept being wrong.
|
|
95
|
+
|
|
96
|
+
### R4. What actually fixed it
|
|
97
|
+
|
|
98
|
+
- **Genesis-frozen price-slot determinism** (v1.4.25+): `slot-N` → exact ladder price
|
|
99
|
+
becomes the single source of truth, replacing tolerance-based price matching. Killed a
|
|
100
|
+
whole family of orphan/duplicate bugs.
|
|
101
|
+
- **Single ownership:** one writer for the boundary, one owner for the adapter/market
|
|
102
|
+
config, one authoritative set of counters.
|
|
103
|
+
- **Global last-fill guard + final pre-broadcast gate:** one pivot rule, enforced at the
|
|
104
|
+
last moment before broadcast, with fail-open semantics for unjudgeable ops.
|
|
105
|
+
|
|
106
|
+
The winning strategy was **removing writers and sources of truth**, not adding handlers.
|
|
107
|
+
|
|
108
|
+
### R5. Lessons
|
|
109
|
+
|
|
110
|
+
1. In a no-atomicity, unreliable-response system, **state reconciliation is the product** —
|
|
111
|
+
treat it as such.
|
|
112
|
+
2. Prefer **one source of truth** over many reconcilers with tolerances.
|
|
113
|
+
3. Guard at the **last responsible moment** (pre-broadcast), not at every intermediate step.
|
|
114
|
+
4. Every new guard is a liability if it is not backed by a **repair path**; self-healing
|
|
115
|
+
beats fail-closed alone.
|
|
116
|
+
5. Make deferred work **level-triggered** — never let a retry depend on a future event.
|
|
117
|
+
6. If a fix class keeps returning, the model is wrong, not the fix.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Part II — Incident & Fix Ledger (formerly `CONSOLIDATED_ORPHAN_FIX_SUMMARY.md`)
|
|
122
|
+
|
|
123
|
+
**What this part is:** the per-incident evidence behind Part I's synthesis — root causes, fix
|
|
124
|
+
plans, `LANDED`/`REVERTED`/`SUPERSEDED` statuses, commit hashes, and verification targets.
|
|
125
|
+
|
|
126
|
+
> **Provenance:** consolidates four superseded test-branch plans, now deleted:
|
|
127
|
+
> `GAP_BAND_ORPHAN_PREVENTION_PLAN.md` · `LADDER_RECENTER_ORPHAN_ROOT_CAUSE.md` ·
|
|
128
|
+
> `ORPHAN_FILL_INVARIANT_ROOT_CAUSE_AND_FIX.md` · `PRICE_FIRST_ALIGNMENT_PLAN.md`.
|
|
129
|
+
> Content is merged in condensed form — incident data, fix lists, invariants, verification targets
|
|
130
|
+
> and rollback gates are preserved; some narrative rationale is compressed. The four originals
|
|
131
|
+
> were added only on `test`.
|
|
132
|
+
|
|
133
|
+
**Scope:** `modules/order/{grid,strategy,sync_engine,manager,grid_reconcile*}` · `modules/order/utils/{math,order,system}` · `modules/{dexbot_class,dexbot_cow_runtime,dexbot_maintenance_runtime,dexbot_fill_runtime,dexbot_state_recovery,chain_orders,config,constants,paths}` · `market_adapter` re-anchor triggers · `analysis/grid_correction_check.ts`
|
|
134
|
+
**Statuses corrected against `test` HEAD `a54863ca` (2026-08-31), refreshed against `1382f267` (2026-09-13):** the source docs' own wording (`proposed` / `analysis complete` / `investigation complete — fix plan pending` / `Phase 2 flag enabled`) predated later fix commits, so each plan item now carries a `LANDED` / `REVERTED` / `SUPERSEDED` annotation with the implementing commit hash. Key corrections: GAP P1–P5 landed `f94d6ec4` but the P2 sweep was reverted (`3713c496`) and the P1 writer + post-commit assert reverted (`e2898e51`); PRICE_FIRST Phase 2 projection was enabled (`1bbf1a23`) then removed (`3713c496`) — the anchor is shadow telemetry only.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### 0. Terminology
|
|
139
|
+
|
|
140
|
+
**Naming:** instance/order IDs are genericized (`bot-A` = grid bot, `bot-B`/`bot-C` = credit bots, `bot-D/D2` = grid bots; `<order-N>` = `1.7.x`). Same token = same order within a section. Market pairs are genericized as `<pair>`; account names `1.2.x` placeholders.
|
|
141
|
+
|
|
142
|
+
**Grid terms:** `boundary` = discrete sell/buy split index; `gap band` = `SPREAD/virtual` slots around boundary; `sellStartIdx = splitIdx - floor(gap/2) - 1`; center-priced ladder `createOrderGrid` = `startPrice × step^(k+½)` (`modules/order/grid.ts:441,449`); `MarketAnchor` = fill-derived `lastFillPrice/maxFilledSellPrice/minFilledBuyPrice/lastFillSide`; tolerance helpers `calculatePriceTolerance = (1/satsA+1/satsB)×price` (`modules/order/utils/math.ts:820-824`), `PRICE_TOLERANCE_MAX_PERCENT 0.01` (`constants.ts:461`).
|
|
143
|
+
|
|
144
|
+
**Validation tool:** `analysis/grid_correction_check.ts` (test-only, mirrors `analysis/trade_profitability.ts` Kibana path `https://kibana.bitshares.dev`, `bitshares-*`, `operation_type:4` `fill_order` only; `buildFillQuery`, paginated `search_after`, `_source` `block_data` fields). **Invariants:** `sell→sell` with no `buy` between must rise (`curr > prev`, equal OK), `buy→buy` must fall (`curr < prev`); `per-order` aggregated default (weighted avg collapses multi-fill `1.7.x`), `--per-fill` raw. Resolution via `bot_key_utils.ts` `loadBotMeta/computeBotKey`. Reports violations, `%`, daily histogram, time/range, table with price/Δ%/times/orderIds; `--json/--csv` export; exit `0` pass / `2` fail. Strict sat-level is ground truth (`--tolerance P` allows `b < a*(1-P/100)` sell / `b > a*(1+P/100)` buy). Used as regression gate below; pre-fix per-account baselines are intentionally omitted here — §7 states the targets (`0` violations).
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
### 1. Gap-Band Orphan — Prevention Plan (P1–P6)
|
|
149
|
+
|
|
150
|
+
> **Source:** `GAP_BAND_ORPHAN_PREVENTION_PLAN.md` · **Status:** implemented `f94d6ec4` (2026-08-29), with subsequent amendments — landing map below.
|
|
151
|
+
|
|
152
|
+
**Landing map (corrected 2026-08-31):**
|
|
153
|
+
- **P1** — landed `f94d6ec4` (same-batch CANCEL injection in `COWRebalanceEngine.execute` + `_assertGapBandIntactPostCommit` cancelOnly). **REVERTED `e2898e51`** ("remove placement guards that interfered with normal operation") — both sites removed; stranding is now handled by `calculateIdealBoundary` + `LAST-FILL-GUARD` + adoption path (P3 gate removed `e7231534`).
|
|
154
|
+
- **P2** — landed `f94d6ec4`; **REVERTED `3713c496`** after a live-bot incident (the sweep cancelled two legitimate boundary buys under a stale boundary). In-gap chain orders are now *adopted* by the normal reconcile path once the boundary is re-derived via `calculateIdealBoundary`; cancellation is never used as a self-healing net.
|
|
155
|
+
- **P3** — landed `f94d6ec4` (`validateBoundaryAgainstChainEvidence` + reconcile phase-1 gate + `placementsAllowed` adoption-only mode); amended `3713c496` (anchor demoted to non-authoritative hint, can never veto); extended `a54863ca` (NO_FEASIBLE escalates a straddle-cancel ladder instead of freezing). **REVERTED `e7231534`** — `validateBoundaryAgainstChainEvidence` (`grid_reconcile.ts:359`) and `placementsAllowed` (`grid_reconcile_internal.ts:1601`) removed; **current** boundary derives from `calculateIdealBoundary` (`order/utils/order.ts:1216` via `grid.ts:160`) + `LAST-FILL-GUARD` (`manager.ts:1679`/`dexbot_cow_runtime.ts:1624`), not a P3 chain-evidence gate.
|
|
156
|
+
- **P4** — landed `f94d6ec4` (`dexbot_state_recovery.ts:581 rejectCorruptedGridSnapshot` clears in-memory + persisted boundary on reject). Live.
|
|
157
|
+
- **P5** — landed `f94d6ec4` (skip/defer logging + `STRUCTURAL_RESYNC_MAX_DEFER_MS` force, `dexbot_maintenance_runtime.ts:2242-2266`). Live.
|
|
158
|
+
- **P6** — landed `f94d6ec4` (2026-08-29) as boundary chain-evidence regression coverage, since superseded by the boundary-geometry suite (`tests/test_boundary_anchor_recovery.ts`, `tests/test_boundary_restore_validation.ts`) and the slot-invariant guard tests.
|
|
159
|
+
|
|
160
|
+
#### 1.1 Incident summary
|
|
161
|
+
|
|
162
|
+
*Incident window: 2026-08-29 09:29–12:00 UTC · `<pair>`, generic `1.2.x`/`1.7.x` ids.*
|
|
163
|
+
|
|
164
|
+
1. After corrupted-snapshot rejection, recovery restored **stale persisted boundary 131** while true market was ~140+ (`Restored boundary index: 131` then `[RECOVERY][SNAPSHOT-REJECT] drift sell=0.00 buy=2036.67`).
|
|
165
|
+
2. With boundary 131, slot-144 was valid sell-rail; startup reconcile adopted/updated unmatched chain sell to slot-144 (`Startup: Updating chain SELL ... -> grid slot-144`), broadcast in 35-op startup batch.
|
|
166
|
+
3. Fills swept up; price-anchored boundary correction (`2bc1efe3`) snapped boundary up. Slot-144 fell inside new gap band; local record re-typed `SPREAD/virtual, size->0, orderId=""` (spread normalization) while live chain order stayed inside gap.
|
|
167
|
+
4. No cleanup caught it: sync pass-2 only queues `cancelOnly` for orphans duplicating an **active price level** (slot-144 empty → no match); adoption refuses gap slots (`no adoptable slot found`); runtime reconcile only warns (`no active same-side grid order exists`); startup excess-cancel only on `chainCount > targetCount`.
|
|
168
|
+
5. Detector fired `09:33:57 GAP-BAND INVARIANT VIOLATION after commit: placed sell slot-136 ...` but only set `structuralResyncRequested`; resync was silently skipped/never ran. Orphan partially filled `09:53, 10:08, 11:36` via `Processing funds for unknown order ...`, fully filled ~12:00 bot offline.
|
|
169
|
+
|
|
170
|
+
**Core defect:** invariant *"no live order inside gap band"* enforced by detectors/warnings, never by a writer.
|
|
171
|
+
|
|
172
|
+
#### 1.2 P1 — Cancel in the same batch that strands (highest leverage)
|
|
173
|
+
|
|
174
|
+
*Status: LANDED `f94d6ec4`; REVERTED `e2898e51` — see landing map.*
|
|
175
|
+
- `calculateTargetGrid` (`modules/order/strategy.ts` SPREAD GUARD) already identifies stray in-band slots with live orders and keeps them `BUY/SELL`; comment claims `cancelled by sync pass-1 type-mismatch handling` — never fired. **Change:** emit `cancelOnly` ops for orderId-bearing in-band slots in same COW plan (model: duplicate-price `cancelOnly` at `sync_engine.ts` pass-2).
|
|
176
|
+
- Belt-and-braces: `_assertGapBandIntactPostCommit` (`manager.ts`) already collects `problems {id, price}`; on detection immediately queue `cancelOnly` for those ids instead of only flagging. **With P1, incident dies at 09:33:57 — stranded order cancelled same commit.**
|
|
177
|
+
|
|
178
|
+
#### 1.3 P2 — Gap-band orphan sweep (self-healing net)
|
|
179
|
+
|
|
180
|
+
*Status: LANDED `f94d6ec4`; REVERTED `3713c496` — replaced by adopt-after-chain-evidence-correction; see landing map.*
|
|
181
|
+
- Sync pass-2 (`sync_engine.ts` unmatched-chain branch) + runtime reconcile (`grid_reconcile.ts` phase-1): when unmatched chain price sits **strictly inside implied gap band** (shared `MathUtils.isSlotInRail` geometry test) → queue `cancelOnly` instead of `no adoptable slot found` / `no active same-side grid order exists`. Cleans any already-live orphan within one sync cycle.
|
|
182
|
+
|
|
183
|
+
#### 1.4 P3 — Never place against an unvalidated boundary (origin)
|
|
184
|
+
|
|
185
|
+
*Status: LANDED `f94d6ec4`; amended `3713c496`, `a54863ca`; **REVERTED `e7231534`** — gate removed, retained as historical context.*
|
|
186
|
+
Poison input was `_restoreBoundary(131)` from stale snapshot; `validatePersistedBoundary` couldn't catch (band empty at restore, stranding created by placement).
|
|
187
|
+
- **As landed:** after restore/rebuild, re-derived boundary from chain before placement: placed-order distribution (highest live BUY / lowest live SELL), market anchor, recent fill evidence via `computePriceAnchoredBoundaryTarget` (`order/utils/order.ts`). If derived disagreed beyond threshold, used derived + loud log.
|
|
188
|
+
- **Gate (removed):** `_reconcileStartupSide` (`grid_reconcile_internal.ts`) had `placementsAllowed` adoption-only mode — creating/price-updating into rail required boundary validated against chain+fills; without validation → deferred placements. Removed `e7231534` (P3 reverted) and fully deleted as dead code in current determinism cleanup — boundary now via `calculateIdealBoundary` + `LAST-FILL-GUARD`, not chain-evidence veto.
|
|
189
|
+
|
|
190
|
+
#### 1.5 P4 — Snapshot-reject must discard boundary
|
|
191
|
+
|
|
192
|
+
*Status: LANDED `f94d6ec4` — live.*
|
|
193
|
+
Sequence `Restored boundary index: 131` *then* `[SNAPSHOT-REJECT] Deleting corrupted snapshot` — rejected boundary stayed. In `recoverFromPersistedGrid` (`dexbot_state_recovery.ts`): validate first, restore only on pass; on fail discard boundary with snapshot, fall through to re-derivation (originally P3 `validateBoundaryAgainstChainEvidence`; P3 reverted `e7231534` — now `calculateIdealBoundary` + `LAST-FILL-GUARD`).
|
|
194
|
+
|
|
195
|
+
#### 1.6 P5 — Structural resync requests must not vanish
|
|
196
|
+
|
|
197
|
+
*Status: LANDED `f94d6ec4` — live.*
|
|
198
|
+
`09:33:57 Requesting structural resync` swallowed (`requestStructuralGridResync` in `dexbot_maintenance_runtime.ts` skips if already scheduled/running, `_batchInFlight` deferral re-arms timer uncapped). Hours unanswered.
|
|
199
|
+
- Log every skip/defer with reason; add max-defer deadline forcing resync after re-arms.
|
|
200
|
+
|
|
201
|
+
#### 1.7 P6 — Regression tests
|
|
202
|
+
|
|
203
|
+
*Status: LANDED, then superseded — the boundary chain-evidence and geometry assertions are now held by `tests/test_boundary_anchor_recovery.ts` (ANCHOR-001..004) and `tests/test_boundary_restore_validation.ts`.*
|
|
204
|
+
- A (P1): stale boundary → startup updates into slot X → boundary snaps past X → assert cancel emitted same batch, no live in-band. (P1 later reverted `e2898e51`.)
|
|
205
|
+
- B (P2): seed live orphan strictly inside gap with no duplicate price → sync/reconcile → assert `cancelOnly` within one cycle. (P2 reverted `3713c496`.)
|
|
206
|
+
- C (P3/P4): corrupted snapshot stale vs fill evidence → rebuild → assert placements use re-derived boundary, rejected boundary not reused. **Both tests deleted `e7231534`** with P3 revert; P4 behavior now covered by `test_dexbot_state_recovery` + `test_periodic_sync_fill_rebalance`.
|
|
207
|
+
|
|
208
|
+
#### 1.8 Rollout order
|
|
209
|
+
|
|
210
|
+
1. **P1+P2+P4** (writer cancel + sweep + reject-discard) — self-enforcing invariant
|
|
211
|
+
2. **P5** observability/force
|
|
212
|
+
3. **P3** re-derivation gate (largest surface, fast-follow) — **REVERTED `e7231534`** (gate removed; `placementsAllowed` deleted)
|
|
213
|
+
4. **P6** tests accompany each step (**DELETED `e7231534`** — no P6 tests remain)
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
### 2. Ladder Recenter Orphan — Root Cause (Aug 26–30 2026)
|
|
218
|
+
|
|
219
|
+
> **Source:** `LADDER_RECENTER_ORPHAN_ROOT_CAUSE.md` · **Status:** fixes #1–#7 landed 2026-08-30.
|
|
220
|
+
|
|
221
|
+
#### 2.1 Symptom
|
|
222
|
+
|
|
223
|
+
Falling market Aug 28 after ~17:00 UTC: bot **placed new SELLs below own live sells** (self-undercut). Next day orphans filled on bounce, proceeds credited outside grid accounting (`[ORPHAN-FILL] Processing funds for unknown order`), drift `±22` slots `[ANCHOR-DIVERGENCE]` from Aug 29 12:40. Not boundary crawl — dominant trigger is **regeneration re-anchor** (separate from PRICE_FIRST lag mode).
|
|
224
|
+
|
|
225
|
+
#### 2.2 Reproduction (systemic, not bot-D-only)
|
|
226
|
+
|
|
227
|
+
*Audit window: post-`cd690000` ("heal and prevent sized-orphan phantom orders"), `2026-08-30T00:24:26Z→07:48Z`.*
|
|
228
|
+
|
|
229
|
+
| Metric | bot-A | bot-B | bot-C | Basis |
|
|
230
|
+
|---|---|---|---|---|
|
|
231
|
+
| Distinct orphaned IDs (`Unmatched chain order`) full log Aug 17–30 | 229 | 107 | 172 | full file |
|
|
232
|
+
| Distinct post-commit window | 58 | 60 | 91 | 00:24:26Z→end |
|
|
233
|
+
| Occurrences post-commit | 280 | 228 | 241 | same |
|
|
234
|
+
| Adoptions post-commit | 2 | 14 | 64 | vs orphans 1–2 orders magnitude low |
|
|
235
|
+
| `Restored boundary` (snapshot restores) | 51 | 53 | 52 | full file |
|
|
236
|
+
| Structural resyncs (`[CR-RESET] rms_structural_grid_resync`) | 2 | 2 | 2 | full file |
|
|
237
|
+
|
|
238
|
+
`00:00–00:24Z` quiet → 58/60/91 attributable to post-`cd690000` code. Confirms re-anchor (fix #1) is root cause; phantom-order heal treated symptom.
|
|
239
|
+
|
|
240
|
+
#### 2.3 Four modes, one symptom family
|
|
241
|
+
|
|
242
|
+
**Mode A — orphan (re-anchor) — primary.** Aug 28 evening: regeneration re-centers ladder, live asks unmanaged, new sell rail starts below them. Timeline Table §2.6.
|
|
243
|
+
|
|
244
|
+
**Mode B — lag (stale boundary).** Aug 29 09:32 swept up through sell rail slots 147–158 → then `09:32:21 Placed sell slot-129 -> <order-1>` filled 250 ms later, `09:32:33 slot-131`, `09:32:42 slot-132` — 15–25 levels below rail; `ANCHOR-DIVERGENCE projected=138 bookkept=123 drift=15`. Boundary crawled down with falling-market buys, re-rolled slots 129–132 as SELL into rising market. One instant fill set `maxFilledSellPrice=1044.79`, guard caught later attempts `15:47` (1 slot below) and `17:27` (4+8 below 1099.37/1112.62). **What PRICE_FIRST Phase 2 fixes.**
|
|
245
|
+
|
|
246
|
+
**Mode C — COW commit geometry (separate).** Attribution corrected: `Batch transaction failed: Execution error: Order does not exist … cannot update` confirmed post-commit on bot-B: `<order-4>` `00:28:51 chunk1/3`, `<order-5>` `06:43:09 chunk5/9`, `<order-6>` `00:29:30` (`limit_order_update delta=-2341206 targets missing`). Broadcast refs missing orderId, aborts partial grid. `GAP-BAND VIOLATION after commit` = 0 in post-commit bot-A/B/C logs (evidence is bot-D `4` + D2 `11` dated Aug 26/28/29). Need: (a) COW rejects placement inside gap pre-broadcast; (b) pre-broadcast existence check off slot→orderId map. Gap case hardening independent.
|
|
247
|
+
|
|
248
|
+
**Mode D — boundary-evidence re-derivation not persisted (freeze).** `grid_reconcile.ts:359-502` re-derives corrected boundary via `manager._restoreBoundary (:380)` but didn't survive next resync; bot-A re-derived at *every* hourly resync: `117→118 (00:28:48)`, `118→119 (01:00)`, `118→115 (02:00)`, `118→114 (03:00,04:00)`, `118→115 (04:30,05:00)`, `122→119 (07:00)` — 8× identical `ERROR` in ~7h. Bot-C `NO_FEASIBLE_BOUNDARY` → adoption-only at `00:27:17, 00:31:12, 04:27:48`; after `04:27:48` placed **zero orders 04:27:48→07:15:27 (~2h48m)** — only fills + `[ORPHAN-FILL]`. Earlier freeze `00:31→00:54`. Resumed only via fill-driven rotation `07:15`. → Fix #5 (LANDED).
|
|
249
|
+
|
|
250
|
+
**Post-commit hardening (§2.3 sub):** (1) lagging-node read-back thrash bot-B `06:43:09–06:47:09` after chunk5/9 fail — fresh CREATEs absent (`<order-7> likely lagging node`), protection piled to 14, all CREATEs rejected, 30s cap forced resync mid-broadcast ~4 min/24 events; (2) sync lock `timed out after 20000ms` + `[SYNC] abandoned force-released 69355ms` (~70s lost), bot-C ~110 `Structural resync defer` warnings at 250 ms/30s; (3) `[MAINT] n/m price correction(s) failed` ratios `2/3,2/2,1/8,2/25,1/23,1/14` — stale slot maps.
|
|
251
|
+
|
|
252
|
+
#### 2.4 Incident timeline (bot-D Aug 28 17:09→Aug 29 17:27)
|
|
253
|
+
|
|
254
|
+
| Time | Event |
|
|
255
|
+
|---|---|
|
|
256
|
+
| Aug 28 17:09 | Sells fill at 1110–1176 (slots 140–148), tracking correct |
|
|
257
|
+
| 17:10:01 | `[RECOVERY] structural full grid resync for pending broadcasts before COW create` → regeneration + AMA refreshed |
|
|
258
|
+
| 17:10:02 | **16+ live sells `[SYNC] Unmatched chain order ... no adoptable slot found`** diff 0.43–0.57 vs tolerance 0.036 |
|
|
259
|
+
| 17:10–18:37 | 7 more regenerations (14 on Aug 28), each re-anchors to newest AMA center, 31 distinct orphaned sells that evening; 2071 warnings |
|
|
260
|
+
| 17:34:33 | Mode B sells slots 132/135/136 (~1054–1064) below fill range |
|
|
261
|
+
| 17:36:03 | 11 sells slots 124–131/133/134 (~1028–1061); slot-124 filled 150 ms after |
|
|
262
|
+
| 17:37:48 | Recovery resync restores boundary 119 (from 139/142) mid-drop — Mode A; new sell rail below orphans. Boundary crawled 142→139 silently to ~123 by 17:36 → slots 124–136 re-rolled to SELL, 20–80 BTS below own 17:09 fills |
|
|
263
|
+
| 17:37:39–17:55:09 | Slots 124→130 swept (~3s), then 131→136 — all 14 filled |
|
|
264
|
+
| 17:45 | `[DIVERGENCE-COW] Boundary-shift commit blocked (RECOVERY_EXHAUSTED)` — loop: blocked→resync→orphans→divergence |
|
|
265
|
+
| 18:02 | First surplus cancels via DIVERGENCE-COW ~50 min after orphaning |
|
|
266
|
+
| Aug 29 12:29 | Orphaned sells fill on bounce → outside grid accounting |
|
|
267
|
+
| 12:40–17:27 | `[ANCHOR-DIVERGENCE]` ±22; 17:27 periodic sync finds 40 filled at once (buys 978–1026, sells 1099–1112); PLACEMENT-GUARD rotates up to 31 buys + 8 sells/batch |
|
|
268
|
+
|
|
269
|
+
+ 11 graceful restarts in 30h (`Restored boundary` 57×).
|
|
270
|
+
|
|
271
|
+
#### 2.5 Root-cause chain (code refs)
|
|
272
|
+
|
|
273
|
+
1. Ladder quantized to center. 2. Recovery resyncs re-anchor via two config points (see Fix #1). 3. Tolerance rounding-scale `≈0.036` at 1110 vs AMA shift `0.045%` → `~0.5` shift = 15× tolerance → pass-1 + pass-2 adoption (`sync_engine.ts:146-156,:1040-1060`) reject. 4. `PRICE_DRIFT_TOLERANCE_MULTIPLIER 4` (`constants.ts:557`) → budget 0.144 < 0.43 diff → no `price-drift-orphan` → only logged (`dexbot_maintenance_runtime.ts:1311-1317`). 5. Regenerated ladder sell rail below orphans → self-undercut; `maxFilledSellPrice/minFilledBuyPrice` built from grid-tracked fills only → guard unenforceable once orphans unmanaged; orphans later fill outside accounting.
|
|
274
|
+
|
|
275
|
+
#### 2.6 Ranked fixes (with 2026-08-30 landed status)
|
|
276
|
+
|
|
277
|
+
**#1 Stop re-anchoring on recovery resyncs (two config points, highest leverage) — LANDED 2026-08-30 (`4838bcb0`).**
|
|
278
|
+
`rms_structural_grid_resync` → both flipped: (`dexbot_maintenance_runtime.ts:2327` `requestGridReset` call, hardcoded `refreshCenterPrice:false`) + (`:102` `GRID_RESYNC_REASONS.rms_structural_grid_resync.shouldRefreshCenterPrice: false`, covers the `buildGridResyncOptions('rms_structural_grid_resync')` call sites at `:1710-1712`). Structural resync keeps geometry; re-anchor only on manual/trigger-file and `GRID_RESYNC_REASONS` market-adapter delta/slope/bootstrap — intentionally stay `true`. `requestGridReset` default `TRUE` (`:2194`) so manual resets still re-anchor. Risk if center moved: geometry briefly mispriced but managed vs orphaning.
|
|
279
|
+
|
|
280
|
+
**#2 Regeneration must preserve order identity (defense-in-depth) — LANDED bounded 4×.**
|
|
281
|
+
`recalculateGrid` after `initializeGrid` adopts `chainOpenOrders` into nearest slots at chain price before reconcile; alt widen pass-2 fallback to ~0.25 step for VIRTUAL only. `ORPHAN_ADOPTION_TOLERANCE_MULTIPLIER 4` (`constants.ts` GRID_LIMITS) → 0.144 at 1110 (~¼ step); bot-D diffs 0.43–0.57 beyond band — coherent layering: #1 removes large diffs, residual → #3 surplus cancel. Also pre-empts `price-drift-orphan` on empty slots. Invariant: resync (incl. startup reconcile) never leaves live order unmanaged. Also covers startup reconcile (bot-A `<order-8>` 06:42→07:00 adopted to slot-140 still `Unmatched`; `<order-9>` re-adopted 6× 01:00–05:00 then `06:42:15 ORPHAN-FILL`).
|
|
282
|
+
|
|
283
|
+
**#3 Reconcile is update-first: never pre-emptively cancel unmatched (revised) — LANDED 2026-08-30 revised.**
|
|
284
|
+
Original force-cancel regressed reset path (fresh grid no ACTIVE bindings → cancelled instead of batch-updated). Revised `reconcileGridOrders`: never cancels in duplicate/route phase; `_reconcileStartupSide` 1. updates onto target slots batched (`plannedUpdates → _executeStartupUpdateBatch`), 2. creates missing, 3. cancels surplus only (`chainCount-targetCount` farthest-first) + Phase-3 stale surplus beyond target on fresh post-Phase-2 read. Invariant holds via adoption-or-surplus; guard `matchedOnGrid>0 || neededSlots===0` prevents fresh-grid nuking; pending-broadcast protection stays.
|
|
285
|
+
|
|
286
|
+
**#4 Log live boundary on every role (observability, zero risk) — LANDED.**
|
|
287
|
+
Debug line: `[BOUNDARY] boundary=123 sellStart=128 spread=4 anchorProjected=138 maxFilledSell=1044.79` on role/placement — makes Mode B diagnosable.
|
|
288
|
+
|
|
289
|
+
**#5 Persist boundary-evidence + define NO_FEASIBLE recovery (Mode D, high impact) — LANDED 2026-08-30 (`4838bcb0`), extended `a54863ca`.**
|
|
290
|
+
(a) Persistence: `recalculateGrid` persists the reconciled grid after reconcile, so the `manager._restoreBoundary` re-derivation (`grid_reconcile.ts:380`) survives to the next cycle. After fix: `[BOUNDARY-EVIDENCE]` contradiction fires at most once per stale boundary. (b) NO_FEASIBLE: bounded periodic re-validation (5min cooldown, 20 attempts) landed with `4838bcb0`; `a54863ca` went further — the validator enumerates straddling orders (`conflictingBuyIds/conflictingSellIds`) and the reconcile escalates a cancel ladder (smaller single side → other side → both, `grid_reconcile.ts:417-444`), re-derives the boundary and proceeds with placements; the adoption-only freeze remains only as a defensive fallback.
|
|
291
|
+
|
|
292
|
+
**#6 Retry/backoff post-broadcast adoption read-back — LANDED (cooldown `4838bcb0` + retry `d808c052`).**
|
|
293
|
+
`deferUncertainBroadcastRead` (`dexbot_cow_runtime.ts:301`) 30s escalation cooldown bounds thrash; `adoptPlacedBatchFromChain` retries lagging/failed by-id reads (3 attempts, 2s/4s backoff) before deferring with pending-broadcast protection kept. Cross-node failover remains open.
|
|
294
|
+
|
|
295
|
+
**#7 Sync-lock/maintenance observability — LANDED.**
|
|
296
|
+
Throttle `Structural resync defer` to first/last per cap window (was 120/30s); log/retry `[MAINT] n/m failed` with orderId/reason.
|
|
297
|
+
|
|
298
|
+
**Mode C hardening — OPEN.** (a) COW pre-broadcast gap-band reject; (b) order-existence check off slot→orderId map. Note: the GAP-P1 same-batch cancel that covered commit-time in-gap placement was reverted `e2898e51`; **P3 gate removed `e7231534`** — commit-time in-gap placement is currently covered by `LAST-FILL-GUARD` + adoption (no chain-evidence veto).
|
|
299
|
+
|
|
300
|
+
#### 2.7 Relation to PRICE_FIRST draft
|
|
301
|
+
|
|
302
|
+
#1 removes Mode A & major boundary churn; PRICE_FIRST fixes Mode B (09:32 triple direct target, 17:27 sweep budget < displacement shows lag at scale); #5 complementary (draft solves *which* boundary, #5 makes it stick). Sequence: #1 → PRICE_FIRST flag → #2/#3 → #4/#5.
|
|
303
|
+
|
|
304
|
+
#### 2.8 Verification — moved to §7 (plus gap/Mode C/drift targets there)
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
### 3. Orphan-Fill & Fund-Invariant — Root Cause & Fix Plan
|
|
309
|
+
|
|
310
|
+
> **Source:** `ORPHAN_FILL_INVARIANT_ROOT_CAUSE_AND_FIX.md` · **Status:** P0s + rotation landed `1b27f6eb`/`4838bcb0`/`d808c052`; P1 atomicity partial; duplicate-cancel hard-error and P2 counters still open.
|
|
311
|
+
> **Scope:** `dexbot_cow_runtime.ts`, `sync_engine.ts`, `chain_orders.ts`, `dexbot_state_recovery.ts`, `dexbot_maintenance_runtime.ts`, `strategy.ts`, `constants.ts` · **Evidence:** `profiles/logs/<bot>.log` Aug 28 17:40–17:57, Aug 29 15:13–15:19.
|
|
312
|
+
|
|
313
|
+
#### 3.1 TL;DR — one cascade
|
|
314
|
+
|
|
315
|
+
1. COW concurrency lets fill batch plan against stale master and double-place at same price. 2. Second batch commit refused (`master mutation during rebalancing`) → fresh chain orders not in master. 3. Fallback adoption re-reads capped `get_full_accounts` truncating freshest orders + lossy duplicate-price skip → master diverges. 4. Fund recalc flags violation → recovery deletes (now "corrupted") snapshot, regenerates from truncated read (40/42) permanently orphaning two orders. 5. Orphans fill no-slot → `ORPHAN-FILL` → more violations → recovery reloads stale boundary → slot-90 same-slot refill loop.
|
|
316
|
+
|
|
317
|
+
Slot-90 loop is *proximate* form; rotation suppression is mechanism, cascade is upstream cause detaching boundary.
|
|
318
|
+
|
|
319
|
+
#### 3.2 Full picture
|
|
320
|
+
|
|
321
|
+
**Step 1 — Race:** ~`17:40:39–51` market moved fast, buys filled, boundary crawled down, sells `<order-1>…<order-4>`, `<order-5>`, `<order-6>…<order-10>`. `_batchInFlight` serializes fill consumer but planning+broadcast not atomic with master commit: Batch A broadcasts `<order-1>…<order-4>,<order-5>` at `17:40:39`; Batch B plans stale, places `<order-6>/<order-7>` at same prices (`17:40:42 a COW broadcast is already in flight; deferring`), commit refused `17:40:51.237 [COW] Refusing stale working grid commit: master mutation during rebalancing` (now `utils/validate.ts:1102`), adoption `Orphaned chain order … duplicates price level of active slot-88` (`sync_engine.ts:906`).
|
|
322
|
+
|
|
323
|
+
**Step 2 — Refused → invariant:** Fresh sells not recorded, adoption `adoptPlacedBatchFromChain` (`dexbot_cow_runtime.ts:3364`) capped window drops freshest (`chain_orders.ts:573-638`), adoption lossy `<order-6> NOT adopted / <order-9>→slot-106 / <order-10>→slot-107`, collides with re-plan `Rejecting CREATE for slot-105/106 existing orderId=<order-8>/<order-9>` (`17:40:51.335`). Fund recalc `CRITICAL: Fund invariant violation (SELL): blockchainTotal (474877) != trackedTotal (473173) diff:1703.62 allowed:474.87`.
|
|
324
|
+
|
|
325
|
+
**Step 3 — Recovery:** `Fund invariant → state recovery (1/5)` → `Restored boundary 82` → `[SNAPSHOT-REJECT] drift sell=1703.62 — Deleting snapshot` → `Synchronization from 40 blockchain orders — TRUNCATED: 42 live, 40 read` → regeneration matches `165→91,167→108,168→110` but `169,174` never matched (dropped).
|
|
326
|
+
|
|
327
|
+
**Step 4 — ORPHAN-FILL:** `17:57:18 WARN [ORPHAN-FILL] Processing funds for unknown order <order-10>/<order-9> (not in grid but crediting proceeds)` — no slot virtualized → more violations → Aug 29 stale boundary `90` (`Restored boundary index: 90`).
|
|
328
|
+
|
|
329
|
+
**Step 5 — Rotation same-slot:** `15:19:06` partial fill `<order-11>` slot-90 dust `2.92`, dust detection cancelled the residual, synthesizing a fill with `skipBoundaryShift:true` (removed `1b27f6eb`; defensive check removed this commit — `order/utils/order.ts` and `manager.ts` no longer inspect `skipBoundaryShift`; reintroduction would silently restore the same-slot fill loop) → `isShiftEligibleFill` false → boundary crawl suppressed. `processFilledOrders` only rebalances non-partial (`manager.ts:1460`) but synthetic `isDelayedRotationTrigger` forces rebalance with boundary frozen → slot-90 stays `BUY` → `Placed buy slot-90 -> <order-12>` `15:19:15` → `FILL DETECTED slot-90` `15:19:39`. Boundary detached (`projected=72 drift -18`, `PROJECTION_ENABLED false` `constants.ts:729`; the projection override itself was removed `3713c496`) → buy above market re-fills. Root fix: filled buy should rotate (SELL or bottom BUY), not re-stamp same price — LANDED `1b27f6eb`.
|
|
330
|
+
|
|
331
|
+
#### 3.3 Fix plan (layered; statuses corrected against HEAD — see §3 header)
|
|
332
|
+
|
|
333
|
+
**P0 Reliable ID-based adoption after refused commit — LANDED `1b27f6eb` (hardened `d808c052`).** Kills orphan/invariant at source: `adoptPlacedBatchFromChain` builds `collectKnownOnChainOrderIds(mgr, placedResults, placedContexts)` (`dexbot_cow_runtime.ts:3281,3364`) = `mgr.grid[*].orderId ∪ fresh CREATE ids from `extractBatchOperationResults(result)[i][1]`↔`executedContexts[i]` (`:3293`); re-read by ID `chainOrders.batchReadOrders (chain_orders.ts:530)` → `syncFromOpenOrders(fullChain, {skipAccounting:false})`; lagging-node deferral: if ANY fresh CREATE id `null` → defer (`return false`) keep protection (later read adopts), by-id error → defer (no fallback to window); absent master ids = expected. `d808c052` added a retry (3 attempts, 2s/4s backoff) before deferring. Window read remains fallback only and defers on `truncated`.
|
|
334
|
+
|
|
335
|
+
**P0 Recovery must not lose orders when snapshot deleted — LANDED `1b27f6eb`.** `recoverFromPersistedGrid` (`dexbot_state_recovery.ts:314`) on truncated window falls back to `batchReadOrders` over `persistedGrid[*].orderId` (recovers bot's own incl. ID-adopted creates now persisted), without virtualizing ACTIVE; cannot discover brand-new orphans (prevented above); with no known ids still defers. Intentionally lossy for unenumeratable = reconcile virtualize-as-filled acceptable here; adoption path hard-defers.
|
|
336
|
+
|
|
337
|
+
**P1 Atomic plan→broadcast→commit — PARTIAL.** Landed pieces: `restoreBoundaryAfterAdoption` re-applies the batch's shifted boundary immediately after by-id adoption (`1b27f6eb`); Phase-2 reconcile wrapped in a refcounted broadcast flag with `_awaitBroadcastIdle` deferral (`d808c052`); stale-commit refusal lives at `utils/validate.ts:1102`. Still open: full re-plan from fresh master after winning `waitForCowBroadcastSingleFlight` + a COW-plan mutex covering the entire plan→broadcast→commit; dust-cancel `checkGridHealth` waits.
|
|
338
|
+
|
|
339
|
+
**P1 Rotation same-slot — LANDED core (`1b27f6eb`); sub-fixes superseded.** (1) `skipBoundaryShift:true` removed from both dust synthetic fills (was `dexbot_maintenance_runtime.ts:1821/:1826`) — dust fills are shift-eligible again; regression guard `tests/test_rotation_no_same_slot.ts` now 4 cases (ROT-4 same-slot loop case removed this commit, guard deleted); no defensive `skipBoundaryShift` check remains (`order/utils/order.ts` + `manager.ts` removed). Reintroduction of the flag would silently restore the same-slot fill loop. (2) moot — dust fills now trigger the crawl naturally. (3) SUPERSEDED — the Phase-2 projection override was removed entirely (`3713c496`); `ANCHOR.PROJECTION_ENABLED` (`constants.ts:729`) is an unused constant. (4) SUPERSEDED — the `d808c052` anchor-refill/price-sanity guard extensions were removed `e2898e51`; the original `calculateTargetGrid` fill-range rotation (`strategy.ts:345-369`) remains.
|
|
340
|
+
|
|
341
|
+
**P1 Duplicate-orphan cancellation reliable — OPEN (partially mitigated).** Verify `queueCorrection` cancelOnly (`sync_engine.ts:913`) executes, not blocked by in-flight; hard error if cannot cancel. `d808c052` mitigates the consequences (unknown-fill adoption-before-credit + empty-read guard) but the explicit verify/hard-error behavior is still open.
|
|
342
|
+
|
|
343
|
+
**P2 Observability — PARTIAL (counters landed 2026-09-13).** `d808c052` added anchor price-outlier rejection with rate-limited warns (that warn surface was later removed `e2898e51`). Since then the GRID-PRICE-INVARIANT guard added per-batch/per-site emitted-price counters and **blocks** an off-grid emission (`[GRID-PRICE-INVARIANT] site=<site> checked=N violated=M unchecked=K`, six emission sites) and a `pivotSlot=`/`pivotOffGrid=true` marker plus a numeric `pivotOffGrid=<n>` per-action count on the LAST-FILL-GUARD batch summary (the count says how many guarded probes used an unsnapped pivot) — see `docs/GRID_PRICE_INVARIANT.md`. Two self-healing escalations now sit on top of those counters: a slot rejected off-grid for
|
|
344
|
+
`GRID_PRICE_INVARIANT_RESYNC_THRESHOLD` (3) **consecutive** batches escalates to the structural resync
|
|
345
|
+
(`grid-price-invariant-violation`), because the recurring planner carries the slot's corrupt price straight
|
|
346
|
+
from `manager.orders` and would otherwise re-plan-and-reject it forever; and a deferred hold unchanged for
|
|
347
|
+
`DEFERRED_HOLD_ESCALATE_MS` (24h) escalates to the same resync (`deferred-hold-stale`), giving
|
|
348
|
+
"held indefinitely" the exit the per-cycle hold policy lacks. Both reuse the existing debounced,
|
|
349
|
+
batch-in-flight-aware resync path. The hold age is measured **per stranded
|
|
350
|
+
order** (`_strandedHoldSince`, a bot-scoped map keyed `id@price/size:reason`),
|
|
351
|
+
not from the whole-held-set signature clock (`_lastHeldChainOrderSignatureSince`
|
|
352
|
+
drives LOGGING only — a whole-set age clock would be reset by unrelated churn,
|
|
353
|
+
because one hold flapping in and out restarts it, starving a genuinely stranded
|
|
354
|
+
order of escalation forever — HOLD-010) and **not** from
|
|
355
|
+
`_lastUnmatchedChainOrdersAt` — the latter refreshes on every observing sync
|
|
356
|
+
and would make an age gate unfirable (HOLD-007).
|
|
357
|
+
|
|
358
|
+
Still open: drift logging/alert >active window, re-stamp BUY above anchor metric, refused-commit and orphan-fill counters. Deferred-hold observability is no longer open — the `[HOLD]` summary now names side/price/size/reason/off-grid distance and slow-re-warns on an unchanged hold (`modules/dexbot_maintenance_runtime.ts`, `docs/GRID_PRICE_INVARIANT.md`).
|
|
359
|
+
|
|
360
|
+
#### 3.4 Key code references (from source table)
|
|
361
|
+
|
|
362
|
+
`dexbot_cow_runtime.ts:3281,3364` by-id adoption (`collectKnownOnChainOrderIds`/`adoptPlacedBatchFromChain`); `:3293` placed IDs from broadcast result; stale-commit refusal `utils/validate.ts:1102`; `sync_engine.ts:906-913` duplicate skip + `queueCorrection`; `chain_orders.ts:573-638` truncation cap; `:503 readSingleOrder, :530 batchReadOrders` immune; `dexbot_state_recovery.ts:314,581` recovery/reject; `dexbot_maintenance_runtime.ts:806 performGridResync`; `order/utils/order.ts:1560-1561` `isShiftEligibleFill`; `strategy.ts:345-369` fill-range rotation guard; `constants.ts:729` `PROJECTION_ENABLED` (unused since `3713c496`); `<bot>.log 15:19:06→39` slot-90 loop.
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
### 4. Price-First Alignment Plan
|
|
367
|
+
|
|
368
|
+
> **Source:** `PRICE_FIRST_ALIGNMENT_PLAN.md` · **Status:** Phase 1 shipped and live as shadow telemetry; Phase 2 projection was enabled (`1bbf1a23`) then **removed** (`3713c496`) after a live-bot incident — the anchor veto overrode chain evidence and drove destructive gap-band cancels. The anchor is now observability-only; the boundary is chain-evidence-derived. Phase 3 is moot (no second write path to retire); boundary geometry is pinned by `tests/test_boundary_anchor_recovery.ts` (ANCHOR-001..004) and `tests/test_grid_price_slot_invariant.ts`.
|
|
369
|
+
> **Scope:** grid boundary state `modules/order/` · **Related:** three-layer crawl/anchor/guard fix.
|
|
370
|
+
|
|
371
|
+
#### 4.1 Goal
|
|
372
|
+
|
|
373
|
+
One source of truth `MarketAnchor` (fill-derived), boundary demoted to rebuildable cache. Phase 2 is goal; Phase 3 deletion conditional on soak evidence. **KPI:** `[PLACEMENT-GUARD]` rotations = 0 + divergence telemetry flatlined.
|
|
374
|
+
|
|
375
|
+
#### 4.2 Background
|
|
376
|
+
|
|
377
|
+
Grid's "where is market" maintained two ways that disagree: boundary (discrete, crawled) vs fill prices (continuous, authoritative). Every incident = boundary diverging from price.
|
|
378
|
+
|
|
379
|
+
#### 4.3 Key decisions
|
|
380
|
+
|
|
381
|
+
| # | Decision | Rationale |
|
|
382
|
+
|---|---|---|
|
|
383
|
+
| D1 | Price overrides funds, bounded fund floor (≤½ active window; severe shortfalls reduce sizes via `calculateBudgetedSizes`) | Preserve I1 unconditionally; sizing degrades gracefully |
|
|
384
|
+
| D2 | Projection reuses `getSellStartIdx`/`isSlotInRail` only | No new gap conventions, avoid baking `splitIdx - floor(gap/2) - 1` asymmetries |
|
|
385
|
+
| D3 | Single boolean flag `projectionEnabled` | One test matrix, legacy stays as rollback |
|
|
386
|
+
| D4 | Fills applied in block order (`block_num` before trailing) | Replay order would flip correction direction |
|
|
387
|
+
| D5 | Anchor never persisted — book-seeded (highest buy / lowest sell) startup, `startPrice` fallback | No schema/migration, fossilized book; guard covers until first fill |
|
|
388
|
+
| D6 | Deletion evidence-gated (14-day) | Phases 1–2 carry safety; deletion hygiene must earn risk |
|
|
389
|
+
|
|
390
|
+
#### 4.4 Invariants I1–I6
|
|
391
|
+
|
|
392
|
+
I1 No order on wrong side of just-filled price (`sell ≤ maxFill, buy > minFill`) — `test_last_fill_guard.ts` (LFG-1..7); I2 Single eligible fill ±1 crawl — `test_multi_partial_consolidation.ts`; I3 Just-filled BUY refills as BUY at fill price — `test_multifill_opposite_partial.ts`; I4 Boundary never exceeds gap-aware ceiling / collapses on degenerate geometry — `test_boundary_restore_validation.ts`; I5 Price-less fills degrade conservatively — `test_boundary_anchor_recovery.ts` ANCHOR-003; I6 COW-commit-only writes (`_setBoundary`) — `test_cow_boundary_slot_replacement.ts`.
|
|
393
|
+
|
|
394
|
+
#### 4.5 Phase 1 — MarketAnchor + divergence telemetry (no behavior change)
|
|
395
|
+
|
|
396
|
+
*Status: SHIPPED.*
|
|
397
|
+
In-memory `marketAnchor` on manager (`dexbot_fill_runtime.ts`) `lastFillPrice/maxFilledSellPrice/minFilledBuyPrice/lastFillSide/updatedAt` updated on eligible fill (`isShiftEligibleFill`), `buildFillKey` deduped, block ordered (D4, D5); book-seeding; shadow log `[ANCHOR-DIVERGENCE] projected=X bookkept=Y drift=Z` `|Z|>1` warn (>~3), thresholds `constants.ts`; freshness 15 min OR >3 increments beyond anchor (`ANCHOR_FRESHNESS`); replay protection history uses latest only; tagging replay vs live via `sync_engine.ts`. Gate: suite green + unit for idempotent/block/replay/freshness/book-seeding.
|
|
398
|
+
|
|
399
|
+
#### 4.6 Phase 2 — Projection live (goal)
|
|
400
|
+
|
|
401
|
+
*Status: SUPERSEDED — landed `1bbf1a23` (flag on), REMOVED `3713c496`.*
|
|
402
|
+
Pure `projectAnchorToGrid(anchor, allSlots, gapSlots)` via `computePriceAnchoredBoundaryTarget` (`order/utils/order.ts`), gap centered on traded range + I4 ceiling; `calculateTargetGrid` (`strategy.ts`): `boundaryIdx = anchorFresh ? project(...) : legacyDeriveTargetBoundary(...)` gated `projectionEnabled` (D3); fund floor D1 at most ½ window (`syncBoundaryToFunds/calculateFundDrivenBoundary` `utils/system.ts`); placement guard unchanged as enforcement I1; stale-anchor continues from last range decayed to AMA center (not fall back to crawl). Gate: suite green flag on/off + property harness random bursts/re-plans/replays/empty-book all I1–I6; flip per-bot start smaller, 48h soak watcher `ANCHOR-DIVERGENCE`/`PLACEMENT-GUARD`/fund friction. **Done = incident class closed, rollback = flag flip.**
|
|
403
|
+
**Post-mortem (`3713c496`):** the projection override plus the anchor's veto role let a fresh fill-anchor override chain evidence under a stale boundary, and the gap-band sweep then cancelled legitimate orders. Removed: the `projectionEnabled` gating, the `projectAnchorToGrid` boundary override, and the fund-floor pull in `calculateTargetGrid`. `strategy.ts:232-236` records the decision — the anchor is shadow telemetry only (`[ANCHOR-DIVERGENCE]`/`[ANCHOR-STALE]`). The original fill-range rotation guard remains at `strategy.ts:345-369`; the `d808c052` price-sanity/anchor-refill guard extensions were removed `e2898e51`.
|
|
404
|
+
|
|
405
|
+
#### 4.7 Phase 3 — Conditional deletion
|
|
406
|
+
|
|
407
|
+
*Status: MOOT — Phase 2 was removed (`3713c496`), so there is no second write path to retire; the legacy machinery below is the live path. Original gate: 14-day zero rotations + flat divergence.*
|
|
408
|
+
3a: `deriveTargetBoundary` collapses to initial-recovery + projection; delete `netShift`/cross-chunk budget/window-cap; delete `_boundaryShiftBudget/_boundaryShiftBudgetBase/_boundaryAnchor` (`dexbot_class.ts`) + re-plan restore (`dexbot_cow_runtime.ts`); fund sync now D1 floor only; spread-correction promotes via anchor; remaining readers projection-based; `manager._setBoundary` stays (I6). 3b: `account_orders.loadBoundaryIdx` (`validatePersistedBoundary` `dexbot_state_recovery.ts`) + `storeGrid` (`utils/system.ts`) stop persisting boundary; startup = load grid → book-seed → project. Gate: harness + fund-floor/recovery cases; kill-switch: corrupt/delete snapshot must converge from chain alone.
|
|
409
|
+
|
|
410
|
+
#### 4.8 Phase 4 — Boundary geometry coverage (pin, don't refactor)
|
|
411
|
+
|
|
412
|
+
*Status: LANDED with the projection, then superseded — boundary geometry is pinned by `tests/test_boundary_anchor_recovery.ts` (ANCHOR-001..004) and `tests/test_grid_price_slot_invariant.ts`.*
|
|
413
|
+
|
|
414
|
+
#### 4.9 Sequencing & risk
|
|
415
|
+
|
|
416
|
+
Work on `test`; per-phase mergeable unit with soak; flag flip is sharp edge — per-bot; D1 validated in soak; if 14-day never clean → stop at Phase 2 (guard+projection hold invariants).
|
|
417
|
+
|
|
418
|
+
| Phase | Size | Risk | Rollback |
|
|
419
|
+
|---|---|---|---|
|
|
420
|
+
| 1 | 2–3d | none | delete struct |
|
|
421
|
+
| 2 | 3–5d | medium | `projectionEnabled=false` |
|
|
422
|
+
| 3 | 3–4d | low (evidence-gated) | revert |
|
|
423
|
+
| 4 | 1d | none | n/a |
|
|
424
|
+
|
|
425
|
+
#### 4.10 Definition of Done
|
|
426
|
+
|
|
427
|
+
Goal (Phase 2): I1–I6 property pass, full suite green, harness random, 48h per-bot no fund-invariant; Full alignment (Phase 3): 14-day zero guard rotations + zero divergence warnings, no path increments `boundaryIdx` (only read/project), answer "where will next sell be?" from one struct.
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
### 5. Cross-doc fix lineup (deduplicated, dependency order)
|
|
432
|
+
|
|
433
|
+
1. **GAP P1+P2+P4** + **LADDER #1** earliest (gap writer + re-anchor stop — highest leverage, prevents Aug 28 cascade 7 regs×orphaning). — *GAP P1/P2 landed `f94d6ec4` then reverted (`3713c496`/`e2898e51`); GAP P4 + LADDER #1 live.*
|
|
434
|
+
2. **LADDER #4 / PRICE Phase 1** log-only, parallel. — *done (`4838bcb0`; Phase 1 telemetry live).*
|
|
435
|
+
3. **PRICE Phase 2** flag per-bot 48h (Mode B lag) — price-first truth. — *SUPERSEDED: landed `1bbf1a23`, removed `3713c496` (see §4).*
|
|
436
|
+
4. **LADDER #2/#3** identity + update-first hardening. — *done (`4838bcb0`).*
|
|
437
|
+
5. **LADDER #5 / ORPHAN P0 recovery-by-ID** persistence (long freeze). — *done (`4838bcb0`, extended `a54863ca`; recovery-by-ID `1b27f6eb`).*
|
|
438
|
+
6. **ORPHAN P0 ID-based adoption** + **P1 atomicity** (stale-plan race). — *P0 done (`1b27f6eb`, retry `d808c052`); atomicity partial.*
|
|
439
|
+
7. **ORPHAN P1 rotation + P1 duplicate + GAP P3/P5 + Mode C guards + P2 guardrails** fast-follow. — *rotation + GAP P5 done; GAP P3 reverted `e7231534` (`validateBoundaryAgainstChainEvidence`/`placementsAllowed` removed); duplicate-cancel hard-error, Mode C guards and P2 counters open.*
|
|
440
|
+
8. **PRICE Phase 3/4** only after evidence (`grid_correction_check` see §7). If soak never clean, stop at Phase 2. — *Phase 3 moot; Phase 4 golden tests landed.*
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
### 6. Consolidated invariants (what must hold after all fixes)
|
|
445
|
+
|
|
446
|
+
- **Gap-band (amended `3713c496`/`e2898e51`; **P3 reverted `e7231534`**):** in-gap chain orders are never cancelled as a self-healing net; they are adopted by the normal reconcile path once the boundary is re-derived via `calculateIdealBoundary` (P3 `validateBoundaryAgainstChainEvidence` gate removed `e7231534`). Placement into the band is prevented by `LAST-FILL-GUARD` / `findCrossedOrder`, not by P3 cancellation sweeps.
|
|
447
|
+
- **Identity:** every resync (incl. startup reconcile) leaves no live order unmanaged (adopted-or-cancelled-as-surplus, surplus = `chainCount-targetCount` farthest-first).
|
|
448
|
+
- **Monotonicity (grid-check):** adjacent same-direction fills monotonic rising sell / falling buy (equal OK).
|
|
449
|
+
- **I1–I6 PRICE_FIRST** plus COW-commit-only boundary writes and price-less degrades conservatively.
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
### 7. Consolidated verification — grid-correction check as regression gate
|
|
454
|
+
|
|
455
|
+
**Tooling (new):** `analysis/grid_correction_check.ts` (see §0) — primary external check for all docs' inversions; run per market:
|
|
456
|
+
```
|
|
457
|
+
npm run analysis:grid-check -- --bot-key <bot-key> --account 1.2.xxxxx --hours 168
|
|
458
|
+
npm run analysis:grid-check -- --bot-key <bot-key> --account 1.2.xxxxx --hours 720
|
|
459
|
+
npm run analysis:grid-check -- --bot-key <bot-key> --account 1.2.xxxxx --hours 168 --json out.json --csv out.csv
|
|
460
|
+
# cross-pair opt: --include-cross-pair ; per-fill opt: --per-fill ; tolerance opt: --tolerance 0.1
|
|
461
|
+
```
|
|
462
|
+
Pairs-checked base excludes cross-pair unless flagged; strict sat-level detection (`detectViolations`, `aggregateByOrder`).
|
|
463
|
+
|
|
464
|
+
**Targets (replay the windows in the 4 docs):**
|
|
465
|
+
- Regeneration unchanged center → `0` `Unmatched chain order`; recovery `rms_structural_grid_resync` not promote AMA.
|
|
466
|
+
- Shifted center → every live order adopted or immediate cancel (no silent unmanaged); startup reconcile replays: bot-A `<order-8>` 07:00 and `<order-9>` 6× loop must stay managed, no `ORPHAN-FILL`.
|
|
467
|
+
- Boundary re-derivation persists: bot-A `00:28–07:00` must not repeat `[BOUNDARY-EVIDENCE]` for unchanged chain.
|
|
468
|
+
- `NO_FEASIBLE_BOUNDARY` bot-C `04:27:48` resumes on next sync tick (bounded, not hours) with WARN.
|
|
469
|
+
- Post-broadcast read-back retry: bot-B `06:43` no `14` piled / forced resync mid-broadcast.
|
|
470
|
+
- COW commit never places inside gap; pre-broadcast existence check: bot-B `<order-4/5>` + bot-D/D2 `GAP-BAND` fixtures → `0`. *(Amended: the commit-time in-gap cancel sweep was reverted `e2898e51` — current expectation is adoption-after-chain-evidence-correction; `0` unmanaged in-gap orders remains the target.)*
|
|
471
|
+
- Aug 28 burst `17:09–17:16` (3 regs/6m, sells 1110–1176) → `0` orphans, `0` sells below asks.
|
|
472
|
+
- **Orphan regression over ~7h post-`cd690000` on bot-A/B/C:** distinct `Unmatched` `58/60/91 → ~0` (full `229/107/172 → ~0`), `Fund invariant violation` `0` (was `3077.07→3184.93` etc.), `ORPHAN-FILL` `0` (was `7/33/22`), repeated `[BOUNDARY-EVIDENCE]` `0`, deferred minutes `0`, `[MAINT] n/m failed` `0`.
|
|
473
|
+
- **Grid-correction soak (external, strict):** pre-fix baselines on the affected markets showed double-digit `%` same-direction inversion rates over 7d/30d windows → `0` sustained. Sustain 48h per-bot (Phase 2) and 14-day zero-guard/divergence (Phase 3). *(Phase 2/3 soak gates moot since `3713c496` — projection removed; the divergence telemetry remains as a monitoring signal.)* **Failure = place `sell→sell` lower / `buy→buy` higher.**
|
|
474
|
+
|
|
475
|
+
Original doc regression excerpts preserved: GAP P6 A/B/C; LADDER §2.8 checks (10 bullets); ORPHAN §3 guardrails; PRICE §4.7/4.10 kill-switch. The randomized chaos/property harness was the PRICE_FIRST Phase 2 gate (§4.6), not part of LADDER §2.8.
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
### 8. Provenance & supersession record
|
|
480
|
+
|
|
481
|
+
- [x] Deleted the four superseded plans: `git rm docs/GAP_BAND_ORPHAN_PREVENTION_PLAN.md docs/LADDER_RECENTER_ORPHAN_ROOT_CAUSE.md docs/ORPHAN_FILL_INVARIANT_ROOT_CAUSE_AND_FIX.md docs/PRICE_FIRST_ALIGNMENT_PLAN.md` (staged 2026-08-31)
|
|
482
|
+
- [x] No other doc/index lists the four (verified 2026-08-31); `analysis/README.md` documents the `grid_correction_check` tool.
|
|
483
|
+
- [x] Stale `*.md` filename references in code comments/tests updated to the consolidated doc (9 module files, 6 test files), then re-pointed to this hub when it absorbed the summary.
|
|
484
|
+
|
|
485
|
+
*Generated 2026-08-31 from the 4 test-only docs at their HEAD contents; statuses corrected against `test` HEAD `a54863ca` the same day, then refreshed against `1382f267` (2026-09-13). Landed/reverted/superseded annotations carry commit hashes where known; **line references in §§1-4 were re-verified at `a54863ca` and are subject to drift** — treat them as pointers, not assertions. The GRID-PRICE-INVARIANT guard is **BLOCKING** at all six emission sites (CREATE / UPDATE / CREATE-FALLBACK in `modules/dexbot_cow_runtime.ts`, RECONCILE-CREATE / RECONCILE-UPDATE / STARTUP-CREATE in `modules/order/grid_reconcile_internal.ts`): an off-grid emission is skipped, not merely counted, so a `violated>0` line means an emission was *prevented* rather than a writer merely *pinned*. Counters are still reported (`site=`, `checked=`, `violated=`, `unchecked=`) for observability — see `docs/GRID_PRICE_INVARIANT.md`.*
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
### A. Grid-Price Invariant — former failure trace
|
|
490
|
+
|
|
491
|
+
> Companion to [GRID_PRICE_INVARIANT.md](GRID_PRICE_INVARIANT.md), which states the
|
|
492
|
+
> invariant and its live enforcement. This appendix preserves the historical trace
|
|
493
|
+
> that motivated the guard; the writers below are all closed.
|
|
494
|
+
|
|
495
|
+
**Two contradictory sources of truth** for "what price is this slot":
|
|
496
|
+
|
|
497
|
+
| Concept | Source | Used by (then) |
|
|
498
|
+
|---|---|---|
|
|
499
|
+
| Genesis table | `priceForSlot(idx, genesis)` | grid build, load validation |
|
|
500
|
+
| Live slot price | `slot.price`, mutated from chain | COW broadcast, fill-guard pivot |
|
|
501
|
+
|
|
502
|
+
Every observed violation was the second winning over the first. Five mechanisms fed it:
|
|
503
|
+
|
|
504
|
+
- **S1 — chain price overwrote the slot's identity price.** Orphan adoption set
|
|
505
|
+
`price: chainOrder.price`, so a slot's id and its price could disagree.
|
|
506
|
+
- **S2 — pre-broadcast substitution re-broadcast the corrupted price.** The planned
|
|
507
|
+
(genesis-derived) price was replaced with `liveSlot.price` — whatever S1 wrote — and
|
|
508
|
+
logged at `debug`, so it was invisible.
|
|
509
|
+
- **S3 — guards tested range membership, never grid membership.**
|
|
510
|
+
- **S4 — the fill-guard pivot was untrusted.** `_lastFilledPrice` was written from fill
|
|
511
|
+
prices at four sites, none validated against the grid.
|
|
512
|
+
- **S5 — bypasses skipped even that guard.** Spread-correction origins bypass it by
|
|
513
|
+
design; the guard ran on the "final post-freshness price", i.e. the price S2 had
|
|
514
|
+
already replaced.
|
|
515
|
+
|
|
516
|
+
The unifying hazard: an off-grid price becomes *grid evidence*, then is re-emitted as
|
|
517
|
+
if it were real market structure.
|
|
518
|
+
|
|
519
|
+
**The writer chain (both links now closed).**
|
|
520
|
+
|
|
521
|
+
1. **Adoption overwrote the grid level** — `modules/order/sync_engine.ts`, the legacy
|
|
522
|
+
no-genesis fallback (`sync-pass2-adopt-orphan` branch):
|
|
523
|
+
```ts
|
|
524
|
+
const adoptedOrder = {
|
|
525
|
+
...adoptedSlot, orderId: chainOrderId, type: chainOrder.type,
|
|
526
|
+
state: adoptedState, size: chainOrder.size,
|
|
527
|
+
price: chainOrder.price, // <-- genesis price discarded
|
|
528
|
+
...
|
|
529
|
+
};
|
|
530
|
+
```
|
|
531
|
+
It also matched by price *tolerance* rather than `slotIndexForPrice` + `isSlotInRail`,
|
|
532
|
+
so it could land an order in a slot whose genesis price was far away — then cement
|
|
533
|
+
that distance.
|
|
534
|
+
|
|
535
|
+
2. **Pre-broadcast substitution propagated it** — `modules/dexbot_cow_runtime.ts`, the
|
|
536
|
+
CREATE branch's `liveSlot`/`priceDrift` block:
|
|
537
|
+
```ts
|
|
538
|
+
const effectiveOrder = (priceDrift > 0)
|
|
539
|
+
? { ...order, price: livePrice, ... } // planned price discarded
|
|
540
|
+
: order;
|
|
541
|
+
```
|
|
542
|
+
The name "freshness" implied the live price was *more* correct — only safe if
|
|
543
|
+
`slot.price` were invariantly a genesis level, the exact thing nothing enforced.
|
|
544
|
+
|
|
545
|
+
**Observed signature:** escalating BUYs at `0.363 → 0.565 → 0.614 → 0.739 → 0.793`
|
|
546
|
+
against a ~0.31 market — each a `BUY > pivot` violation — then matched lots sold back
|
|
547
|
+
at ~0.306 for roughly -61%, plus a 107-violation burst.
|
|
548
|
+
|
|
549
|
+
**Provenance caveat (recorded here, not in the contract doc).** Which writer
|
|
550
|
+
caused the 2026-08-30 event is **not confirmed**: the mechanism is proven real
|
|
551
|
+
at HEAD, but mapping it to that event is inference from fill
|
|
552
|
+
prices/timestamps plus the removed `d808c052`/`e2898e51` code. That window's
|
|
553
|
+
own logs are gone (retention starts 2026-09-11), so the attribution cannot
|
|
554
|
+
retroactively be tightened — treat the writer chain above as the class
|
|
555
|
+
mechanism, not as a per-event timeline.
|
package/docs/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This directory contains the comprehensive technical documentation for the DEXBot2 trading bot. It is designed to guide developers from high-level architecture down to the nuances of fund accounting and state management.
|
|
4
4
|
|
|
5
|
-
**Version context:** v1.6.
|
|
5
|
+
**Version context:** v1.6.7 (released).
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -32,7 +32,7 @@ This directory contains the comprehensive technical documentation for the DEXBot
|
|
|
32
32
|
|
|
33
33
|
### 📈 [Analysis](../analysis/README.md)
|
|
34
34
|
*Research runners, chart generators, and tuning helpers.*
|
|
35
|
-
- **Trend Detection**:
|
|
35
|
+
- **Trend Detection**: Hurst, Kalman, and regime analysis tools
|
|
36
36
|
- **AMA Fitting**: Parameter fitting, comparison charts, and LP data workflows
|
|
37
37
|
- **Bot Fitting**: Grid parameter sweep backtests for AMA winners
|
|
38
38
|
- **TradingView Exports**: Chart export utilities for visual analysis
|
|
@@ -158,7 +158,7 @@ This directory contains the comprehensive technical documentation for the DEXBot
|
|
|
158
158
|
- **Configurable startPrice & gridPrice**: Fixed numeric, pool, book-derived, or AMA keyword pricing modes
|
|
159
159
|
- **Pool ID Caching**: Optimization for price derivation
|
|
160
160
|
- **Order State Helper Functions**: Centralized predicate functions for state checking
|
|
161
|
-
- **Signal Concepts**: Dynamic weights, regime detection,
|
|
161
|
+
- **Signal Concepts**: Dynamic weights, regime detection, and market adapter integration
|
|
162
162
|
- **Debt Policy**: Native MPA and credit offer configuration and runtime rules
|
|
163
163
|
- **Practical How-Tos**: Adding features step by step, common pitfalls to avoid, and useful debugging commands.
|
|
164
164
|
- **Glossary**: Definitions of project-specific terminology (e.g., "Virtual Orders", "Rotation", "Pipeline Safety", "WorkingGrid", "COW Commit", "Dynamic Weight", "Regime Detection").
|