Proof: Preserve the completed implementation turn and record its outcome in the tracked archive. Assumptions: The archived files capture the relevant planning state for the completed turn. Still fake: Archiving does not validate the work by itself; external evidence still governs whether the result is trustworthy.
11 KiB
11 KiB
Implementation Turn: Verifier salt hot-path removal and executor queue guardrails
Status: open Opened: 2026-06-12
Goal
Remove verifier current_salt RPC latency from quote-response execution, fail closed quickly when no fresh signing salt is available, and make salt freshness plus executor queue delay visible to operators.
Selected backlog items
- none selected; this turn was opened directly from the live latency defect observed on 2026-06-12.
Design rules
- This turn is execution latency and visibility only.
- Do not change strategy selection, pricing, edge thresholds, notional limits, inventory checks, arming, signer identity, or pair enablement.
- Do not skip quotes because relay errors are likely.
- Do not submit a quote response if signing prerequisites are unavailable.
- Do not add active pair, edge, notional, latency, response-policy, or retention env vars.
- Keep old executor result rows readable.
- Deploy only through repo workflow.
Problem statement
The quote-response executor currently calls verifierSaltCache.getFreshSalt() from inside handleCommand. If the cache decides it needs to refresh, the command waits for the NEAR verifier current_salt RPC before signing.
Live evidence showed this can block for seconds:
ae824f64-ddfd-4310-a44f-69c401fc8ad7current_salt_source=refreshcurrent_salt_ms=7481.458- executor received the command quickly, then blocked inside salt refresh
- following commands waited in the single executor consumer queue:
command_to_executor_msaround7.5s- some later rows around
10-12s - most failed with
quote_not_found_or_finished
External verifier latency must not sit on the per-quote execution critical path.
Backend changes
1. Inspect current salt and executor paths
- Inspect:
src/core/verifier-salt-cache.mjssrc/apps/trade-executor.mjs- executor command expiry helpers
- executor timing payload construction
- runtime health and service snapshot summaries
- ops sentinel alert generation
- operator dashboard service/system views
- existing verifier salt cache and executor tests
- Identify every call path that can refresh verifier salt.
- Confirm which call paths are quote-response hot path versus background or operator-triggered work.
2. Define salt cache contract
- Add or refine a cache API with separate responsibilities:
- background refresh may perform network I/O
- hot-path read must never perform network I/O
- Candidate API:
start()starts periodic refresh and immediate refresh attemptrefreshNow()performs explicit refresh with in-flight de-duplicationgetCachedFreshSalt({ now, maxAgeMs })returns fresh salt or a typed unavailable result without I/OgetState()returns freshness, age, last refresh times, duration, in-flight state, and error
- Preserve existing public behavior where needed by non-hot-path tests, but quote-response execution must use the non-blocking API.
- Validate malformed salt before it can enter the cache.
3. Background refresh behavior
- Ensure startup kicks off refresh without making the executor command handler wait on it.
- Refresh periodically before the salt becomes stale.
- If refresh fails:
- keep a still-fresh cached salt usable until its age limit expires
- expose last error and stale/unavailable state
- do not block command handling
- Record:
last_refresh_started_atlast_refresh_completed_atlast_refresh_duration_mslast_refresh_errorrefresh_in_flightsalt_age_mssalt_fresh
4. Trade-executor hot path
- Replace hot-path
await verifierSaltCache.getFreshSalt()with non-blocking cached salt read. - If no fresh cached salt is available:
- publish a durable trade execution result
- use status
rejected - use explicit result code such as
verifier_salt_unavailable - include salt cache state summary in the result payload
- do not call
buildQuoteResponseSubmission - do not call the relay
- Keep command expiry before signing so stale queued commands remain rejected as
stale_execute_command. - Keep idempotency and executor state-store semantics intact.
- Remove misleading timing for salt refresh from hot-path results; after this turn hot-path timing should show local cache read or unavailable.
5. Queue delay guardrails
- Keep recording
command_to_executor_msandquote_age_at_executor_receipt_ms. - Add summary state for recent command queue delay, using existing durable result payloads or in-memory rolling metrics.
- Add thresholded runtime health fields:
- recent max command-to-executor
- p90 or bucketed queue delay if practical
- latest command queue delay warning reason
- Do not use queue delay to change strategy selection or pricing in this turn.
6. Runtime health and alerts
- Extend trade-executor
/stateand/healthoutput with salt cache freshness and queue-delay status. - Add ops-sentinel warning alerts for:
- verifier salt stale or unavailable
- verifier salt refresh failing
- executor command queue delay above a conservative threshold
- Keep alert scopes executor/service-level unless pair-specific evidence is available.
- Do not disarm automatically unless an existing safety invariant already does so.
7. Operator dashboard
- Surface salt cache state in the system/service view:
- fresh/stale/unavailable
- salt age
- last refresh time
- last refresh duration
- last refresh error
- Surface executor queue delay alongside existing trade-executor health.
- Preserve existing lifecycle table timing fields and old-row compatibility.
- Avoid presenting salt failures as trade PnL or settled outcomes.
8. Tests
- Add verifier salt cache tests:
- cached fresh read returns immediately and does not call the loader
- missing/stale salt returns unavailable without loader call
- background refresh records success duration and freshness
- background refresh records error without destroying still-fresh salt
- malformed salts fail closed
- Add trade-executor tests or refactorable helper tests:
- slow salt loader cannot block quote command handling
- no cached fresh salt publishes
verifier_salt_unavailable - no relay call happens when salt is unavailable
- stale command expiry still happens before salt lookup
- successful command uses cached salt and records
current_salt_source=cache
- Add runtime health and alert tests for salt and queue warnings.
- Add dashboard/static tests for salt and queue visibility.
9. Validation and deploy
- Run targeted tests:
- verifier salt cache
- trade executor
- runtime health / ops sentinel
- dashboard static/operator tests
- Run full
npm test. - Build operator dashboard bundle.
- Commit with required workflow metadata.
- Push to
mainso repo workflow deploys. - Do not manually patch or roll Kubernetes deployments.
10. Live evidence after deploy
- Confirm all repo-owned deployments run the new image.
- Query recent trade execution results:
current_salt_sourcecurrent_salt_mscommand_to_executor_msquote_age_at_executor_receipt_msresult_codefailure_category
- Prove no post-deploy quote-response result used
current_salt_source=refresh. - Report command-to-executor p50/p90/p99 after rollout.
- Report salt cache health from trade-executor state or health.
- Confirm recent quote flow still writes decisions, commands, and results.
Data and persistence
- Use existing executor result payloads for per-command salt and queue timing.
- Add fields additively so old rows remain readable.
- Prefer in-memory runtime state for recent queue summaries unless durable aggregation is already available.
- Do not add new long-term storage unless required for dashboard or alert correctness.
Edge cases
- Salt cache empty at startup: commands reject quickly until background refresh succeeds.
- Salt cache stale: commands reject quickly, no relay call.
- Salt refresh in flight: commands use still-fresh cached salt or reject; they do not wait.
- Salt refresh fails while cached salt is still fresh: commands may continue using cached salt.
- Salt refresh fails and cache is stale: commands reject with explicit salt-unavailable result.
- Malformed salt from verifier: fail closed and expose error.
- Existing stale command: reject as stale command before salt lookup.
- Executor paused or disarmed: existing behavior remains.
- Old result rows without salt fields remain visible.
Concrete implementation order
Phase 1. Audit and contracts
- Inspect current salt cache and executor usage.
- Add salt-cache contract tests for non-blocking cached reads and refresh state.
- Decide exact result code and payload shape for salt-unavailable rejection.
Phase 2. Salt cache refactor
- Implement non-blocking cached salt read.
- Implement refresh state fields and in-flight de-duplication.
- Ensure background refresh starts and continues.
- Keep existing cache tests passing with updated contract.
Phase 3. Executor integration
- Change quote-response hot path to use cached salt only.
- Add salt-unavailable rejection result.
- Add timing/state fields for cache read and unavailable rejection.
- Add regression tests for slow salt loader and no relay call.
Phase 4. Health, alerts, dashboard
- Expose salt freshness and queue delay in trade-executor state/health.
- Add ops-sentinel/runtime alert tests.
- Add dashboard visibility tests and UI fields.
Phase 5. Validation and deployment
- Run targeted tests.
- Run full
npm test. - Build dashboard bundle.
- Commit and push.
- Collect live post-deploy evidence.
Test plan
test/verifier-salt-cache.test.mjstest/trade-executor*.test.mjsor new extracted executor helper teststest/runtime-health.test.mjstest/ops-sentinel*.test.mjstest/operator-dashboard*.test.mjs- Static config tests proving no new active env knobs are added
- Full
npm test - Dashboard build
Validation checklist against the proof
- Quote-response hot path contains no verifier salt network refresh.
- Slow
current_saltloader does not cause multi-second command queue delay. - Missing/stale salt creates durable explicit rejection without relay call.
- Salt freshness and refresh errors are operator-visible.
- Executor queue delay is operator-visible and alertable.
- Strategy behavior, edge, notional, inventory, arming, signer, and pair enablement are unchanged.
- Repo workflow deploys the result.
- Live results show
current_salt_source=cacheor salt-unavailable rejection, not hot-pathrefresh.
Known fakes allowed at start of this turn
- Venue-native terminal fill ids remain unavailable.
- Fee-complete realized PnL remains unavailable.
- Executor concurrency remains single-consumer unless proven necessary after salt hot-path removal.