Skip to content

Probability-rank exit migration ​

This is an operator step. Implementing or testing rank-exit does not authorize touching production.

Current V2 protective cutover ​

canonical_first_breach_v2 may be installed while a position remains open only through the coordinated target:

bash
scripts/deploy-vps.sh engine-protection

The ordinary engine target keeps its flat precondition. engine-bot, manual binary replacement, HEALTH_GATE=bypass and versions_restore are forbidden for this cutover: none proves a mutually consistent V1/V2 config, state and exchange-order set.

Before stopping the process, the target must prove all of the following from live read-only sources:

  • persistent Drain is active and will remain active;
  • unresolved submit/cancel outcomes are zero;
  • the exact open-position inventory is captured;
  • every live order is a SELL with an exchange id;
  • each open position has at most one live SELL owner;
  • no live BUY exists.

The protection target then sends SIGKILL to pm-engine and stops the dashboard gracefully. This distinction is mandatory: the engine's normal SIGTERM handler cancels every resting order, including the sole protective SELL. SIGKILL leaves that venue order for the documented bootstrap reconciliation path. The stopped SQLite backup must pass integrity checking and match the complete live-preflight inventory exactly before any candidate artifact is installed. The ordinary flat engine target continues to use graceful stop.

The stopped coordinated window retains one complete rollback set: pre-v12 config.db, engine.db, engine/dashboard/CLI binaries, compose definition, SPA and release manifest. SQLite files are copied only through the repository's backup APIs. The new binary must rehearse typed v11 → v12 loading before the active config is published; that migration changes only the schema stamp and rank_deterioration_exit.policy, preserving the accepted 4 / 0 / 1 entry, slip and exit boundaries.

After start, keep Drain active until bootstrap exchange reconciliation is complete. Strict acceptance compares the exact pre/post inventory and requires:

  • no new BUY;
  • no increased position remainder;
  • zero unresolved outcomes;
  • at most one SELL owner per position;
  • every owner change to be either a terminal SELL-fill whose cumulative shares cover the previously sold plus remaining position, or an FSM-explained handoff;
  • exactly one analytics gap owned by the new run, with table_name='(unknown)', reason='process_crash', unknown row count and an end equal to that run's start; this is the explicit lost-tail evidence from the mandatory SIGKILL, while any additional or differently shaped gap rejects the release (the ordinary flat target still requires zero gaps);
  • a fresh targeted canonical revision, agreement of Ранг and Защита after one strategy tick, and durable policy canonical_first_breach_v2.

If the candidate fails before any external cancel, submit or observed fill, stop it with SIGKILL so shutdown cannot create a cancel side effect, restore the complete rollback set atomically, verify its fingerprints, then start the previous binary under Drain. After the first external cancel, submit or observed fill the rollback set is no longer safe: do not roll databases or the FSM backward. Keep Drain, reconcile the sole SELL owner and deploy a forward fix. Removing Drain and manual sale are separate operator decisions.

After that external-action boundary, retain the named rollback set only as forensic and provenance evidence until the carried position is terminal and the position/economic-ledger reconciliation is accepted. It must not be restored. Remove it after that acceptance; the staged release material remains subject to the deploy script's STAGING_KEEP policy.

Do not rewrite the old manifest to hide an incomplete cutover. A subsequent protective release must name the interrupted attempt exactly:

bash
PROTECTION_FORWARD_FROM=<release-id> scripts/deploy-vps.sh engine-protection

This exception remains read-only until the ordinary coordinated cutover. It is accepted only when the named id is the latest cutover attempt, every mandatory staging and rollback artifact exists, the rollback set passes its recorded SHA256SUMS, the installed artifacts and both running fingerprints equal that attempt, Drain is active, and /seed equals the valid SQLite SELL inventory with no BUY or unresolved outcome. The same attempt inventory must contain one SELL owner for every still-open position. This complete predicate is repeated under the release lock immediately before services stop. If any fact differs, the command refuses; PROTECTION_FORWARD_FROM is not a general manifest bypass and is rejected for every other deploy target.

Legacy V1 migrations ​

The commands below document older V1 schema transitions. They are not a substitute for engine-protection during the V2 cutover.

Preconditions ​

  1. Stop pm-engine; keep the dashboard available only for read-only checks.
  2. Record the selected strategy label and paths to its config.db and engine.db.
  3. Reconcile every open SELL against the exchange. Resolve every ambiguous submit, prove that each outcome has at most one live SELL owner, and refresh the remaining position inventory. Do not infer terminality from a transport error or an unknown order response alone.
  4. If an active sharp-stop, forced or manual FAK exists, wait for a terminal result and reconcile its fill before migrating.

Backup-first sequence ​

Run the configuration migration first. It creates its own timestamped backup, rejects conflicting rank/legacy objects, writes an explicit V1 object for every directional row, and removes the legacy settings only inside one transaction:

bash
scripts/migrate-selection-probability-rank.sh /path/to/config.db

For a database already migrated to probability rank but still lacking the separate exit boundary, run the idempotent threshold migration instead. It accepts only the reviewed entry-rank-4 shape and writes exit rank 2:

bash
scripts/migrate-rank-exit-threshold.sh /path/to/config.db

With the engine still stopped, repeat the exchange/order/inventory checks from the preconditions. Then attest that reconciliation was completed and migrate the durable FSM rows:

bash
scripts/migrate-rank-exit-state.sh --reconciled \
  /path/to/engine.db /path/to/config.db <strategy-label>

The state script creates a compact, transactionally consistent timestamped engine backup with SQLite VACUUM INTO before validation. This avoids the pathological .backup behavior of reset-heavy engine databases whose physical file is mostly freelist pages. It refuses ambiguous submits, concurrent SELL owners, unsupported owner roles and unknown legacy phases. It preserves the acknowledged protective price and FSM phase, resets the maker epoch at migration time, clears old rank evidence, and copies the configured rank_deterioration_exit.exit_probability_rank into each V1 row. Re-running it against rows from the earlier rank migration rewrites the old entry boundary to the explicit exit boundary, clears rank evidence gathered under the old comparison, and preserves the protective phase/order. Legacy rows remain for forensics with terminal reason migrated_rank_v1.

For an engine database that already has rank_exit_states from the pure-rank V1 cutover, run the incremental strict migration before installing the two-dimensional binary:

bash
scripts/migrate-rank-exit-absolute-probability-state.sh --reconciled \
  /path/to/engine.db /path/to/config.db <strategy-label>

It requires exactly one enabled directional row with explicit min_probability_milli, makes its own compact backup, rebuilds the table with required threshold_no_price_pip and nullable last_no_price_pip, and clears rank-only evidence that cannot be proven conjunctive. Existing protective phase/order identity is preserved; a latched phase cannot submit/refire FAK until a new fresh snapshot supplies both dimensions. A partial schema or rows belonging to another strategy aborts instead of receiving a default.

Verification before a later deploy ​

Verify that every active position has at most one live SELL and every migrated row uses the configured threshold:

sql
SELECT r.signal_uid, r.phase, r.policy, r.threshold_rank,
       r.threshold_no_price_pip, r.last_rank, r.last_no_price_pip,
       r.protective_price_pip
FROM rank_exit_states r
WHERE r.terminal_reason IS NULL
ORDER BY r.signal_uid;

Only then may a separately approved deployment use scripts/deploy-vps.sh engine. After restart, verify the dashboard seed shows rank_exit_phase, rank_exit_threshold_rank and rank_exit_current_rank, and the reference-entry/current No probability fields, and verify on the exchange that each held outcome still has at most one SELL.

Entry-relative threshold cutover — 2026-08-02 ​

The 65¢ entry-admission floor is not an exit threshold. Before installing the entry-relative binary, stop and reconcile pm-engine and run:

bash
scripts/migrate-rank-exit-entry-reference.sh --reconciled \
  /path/to/engine.db <strategy-label>

The migration makes a compact backup, requires every durable rank row to join exactly one position owned by the selected strategy, and replaces threshold_no_price_pip with that position's exact avg_entry_price_pip. Evidence gathered under the old 65¢ comparison is cleared; phase and SELL ownership remain intact. A missing/duplicate position or invalid entry price aborts before mutation. The governed production path is RANK_STATE_MIGRATION=entry-reference scripts/deploy-vps.sh engine.

After restart, verify every active state threshold equals the position average entry price, a below-entry rank-1/2 position enters observation while retaining its ordinary target for 30 minutes, an above-entry rank-2 position remains Normal, and no token has two live SELLs. A confirmed observation must replace the target with one bounded post-only rank-maker; no new rank-FAK may appear.

Production entry-relative observation cutover record — 2026-08-02 ​

Operator-approved cutover completed for commit dbce7e11, instance harvester-engine and strategy harvester-engine-v2:

  • preflight: 18 open positions, 15 ordinary live SELL owners and 3 known sub-lot dust positions; 0 ambiguous submits, 0 multiple-live-SELL signals, 0 live SELLs without exchange ids; all 18 active rank rows still required entry-reference migration;

  • compact state backup: engine.db.bak-rank-entry-reference-20260802T042239-098825417Z;

  • deployed only through RANK_STATE_MIGRATION=entry-reference HEALTH_GATE=strict scripts/deploy-vps.sh engine;

    engine SHA-256 48910ead306f321a0ec2055b23bbebeaa858d992cf1cc0cd720373e324d93c80, dashboard SHA-256 7dc5662cf7ee8c3f97641f3b50d54edbe2b7a6af2d3a0159e91468c9b37c920a;

  • postflight: bootstrap reached Ready with action_count=0, active threshold mismatches=0, ambiguous submits=0, multiple-live-SELL signals=0 and newly authored rank-takers=0; one position closed independently during the cutover, leaving 17 active positions, 14 ordinary live SELLs and the same 3 dust positions;

  • a live rank=2, No=87.5¢, entry-reference 92¢ position entered break_even_watch while its ordinary target remained the sole live SELL, proving the observation-only handoff on production data;

  • dashboard /health returned ok, /api/local/seed exposed rank_exit_reference_entry_price, both containers were running with restart count 0, and neither process emitted a new ERROR/panic in the final two-minute postflight window;

  • the pre-existing entry halt remained active after bootstrap as draining=true, reason=accounting_invariant, emergency_exit=false. Rank-exit deployment does not clear an accounting drain; new BUY/DCA remains blocked until that independent invariant is diagnosed and deliberately resolved.

Rollback ​

Keep the engine stopped. Restore both timestamped backups printed by the two scripts, restore the previous binary through the normal deployment mechanism, then repeat exchange/order/inventory reconciliation before restarting.

Production cutover record — 2026-08-01 ​

Operator-approved cutover completed for instance harvester-engine and strategy harvester-engine-v2:

  • preflight and postflight: 0 active positions, 0 live SELLs, 0 ambiguous or unresolved submits, 0 signals with multiple live SELL owners;
  • config backup: config.db.bak-selection-rank-20260801T080253-478322101Z;
  • compact state backup: engine.db.bak-rank-exit-20260801T081601-448018729Z;
  • effective config at that historical cutover: neg_risk_probability_rank, entry threshold 4, explicit rank_deterioration_exit.enabled=true, policy v1, no legacy section;
  • deployed only through scripts/deploy-vps.sh engine; engine SHA-256 a09771619e435867c74b698feffa7acf121a1663474a122db8e2f99c013a61bc, dashboard SHA-256 5d5d436e526d7523f6eb6241a50a68e35fc824b2b6d600888edd77afcc308583;
  • bootstrap reconciliation completed with action_count=0, lifecycle reached Ready, both containers remained running with restart count 0, dashboard health returned {"status":"ok"} and critical-log smoke returned 0 lines.

An interrupted pathological CLI .backup attempt is explicitly marked *.incomplete; it is not a rollback source. Use the compact state backup listed above.

Production exit-boundary cutover record — 2026-08-01 ​

The separately approved exit-boundary cutover completed for commit 2fab21b0 and instance harvester-engine:

  • the stable preflight contained 10 open positions, 10 exchange-identified live SELL owners, 0 ambiguous submits and 0 positions with multiple live SELL owners;
  • config backup: config.db.bak-rank-exit-threshold-20260801T183956-920925236Z;
  • compact state rollback backup: engine.db.bak-rank-exit-20260801T184037-570200524Z;
  • the enabled strategy now has entry rank 4 and inclusive exit rank 2; all 10 active rank_exit_states rows use threshold 2 with pre-migration evidence cleared; the migration preserved 8 Normal and 2 BreakEvenWatch phases and their acknowledged SELL ownership;
  • deployed only through scripts/deploy-vps.sh engine; engine SHA-256 2441de56bbee3c691248cbb0fb51f79997c154a080dfc45830791dea5316711e, dashboard SHA-256 2106844880117e4f9243a673c4d10d66916a56657156fb216750918d522a31c1;
  • bootstrap registered all 10 exchange order ids, completed with action_count=0, and reached Ready; dashboard health returned ok and the strict standalone health gate passed (the only warnings were the expected disabled RTDS feed);
  • the operator then applied the updated strategy through the system-restart endpoint without cancel, exit or statistics-reset actions. The second bootstrap also completed with action_count=0 and reached Ready; the rank-3 hold band reconciled the two prior BreakEvenWatch positions back to their ordinary targets, leaving 10 ordinary live SELL owners, 0 ambiguous submits and 0 multiple-owner positions. The engine's restart count became 1 as expected, while the dashboard remained at 0; the repeated strict health gate passed with the same expected disabled-RTDS warnings.