Archive first live trade loop and open funding visibility turn
Proof: Preserve the completed first live BTC/EURe trade loop and establish the next approved implementation proof around pre-credit funding visibility and operator alerts. Assumptions: The live-trade loop is sufficiently proven by the recorded deposits, withdrawals, durable command/result chain, and successful mainnet quote responses; the next highest-value slice is operational visibility rather than new execution breadth. Still fake: The newly opened funding-visibility and alert turn is planning only; no pre-credit watcher or durable alert evaluator is implemented yet.
This commit is contained in:
parent
16e7b79978
commit
54dc05a94c
20 changed files with 2234 additions and 0 deletions
162
AGENTS.md
Normal file
162
AGENTS.md
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
# unrip agent rules
|
||||||
|
|
||||||
|
This repository is run with a data-first trading-system workflow.
|
||||||
|
|
||||||
|
## Core rule
|
||||||
|
Every change must directly serve the currently approved turn.
|
||||||
|
|
||||||
|
## What a turn is
|
||||||
|
A turn is the currently approved slice of work. There are two lanes:
|
||||||
|
|
||||||
|
- Implementation turn:
|
||||||
|
- defined by `PROOF.md` and `IMPLEMENTATION.md`
|
||||||
|
- the job is to implement what `IMPLEMENTATION.md` says, validate it against `PROOF.md`, and keep working until the scoped fake paths are removed or explicitly recorded as remaining blockers
|
||||||
|
- Research turn:
|
||||||
|
- defined by `research/ACTIVE.md`
|
||||||
|
- the job is to produce evidence against the active research charter, not product shape
|
||||||
|
|
||||||
|
`BACKLOG.md` is not the turn.
|
||||||
|
`ARCHIVE.md` is not the turn.
|
||||||
|
They are planning and history artifacts.
|
||||||
|
|
||||||
|
## Read order by task
|
||||||
|
Use the smallest context needed for the current task.
|
||||||
|
|
||||||
|
### For implementation work
|
||||||
|
Read only:
|
||||||
|
|
||||||
|
1. `THESIS.md`
|
||||||
|
2. `PROOF.md`
|
||||||
|
3. `IMPLEMENTATION.md`
|
||||||
|
|
||||||
|
Do not read `BACKLOG.md`, `ARCHIVE.md`, or `research/ACTIVE.md` during an implementation turn unless the user explicitly asks to re-plan, open a new turn, review history, or switch to research.
|
||||||
|
|
||||||
|
### For research work
|
||||||
|
Read only:
|
||||||
|
|
||||||
|
1. `THESIS.md`
|
||||||
|
2. `research/ACTIVE.md`
|
||||||
|
|
||||||
|
Do not read `BACKLOG.md`, `ARCHIVE.md`, `PROOF.md`, or `IMPLEMENTATION.md` unless the user explicitly asks to re-plan, compare lanes, or promote research into implementation.
|
||||||
|
|
||||||
|
### For planning, re-planning, or archiving
|
||||||
|
Read:
|
||||||
|
|
||||||
|
1. `THESIS.md`
|
||||||
|
2. the live turn files for the lane being changed
|
||||||
|
3. `BACKLOG.md`
|
||||||
|
4. `ARCHIVE.md`
|
||||||
|
|
||||||
|
## Hard constraints
|
||||||
|
- Do not invent or adopt a new roadmap on your own.
|
||||||
|
- Do not expand scope beyond the active implementation proof or research charter.
|
||||||
|
- No backlog generation instead of implementation.
|
||||||
|
- No scaffolding ahead of demonstrated need.
|
||||||
|
- Quote collection and analytics are first-class from day one. They are not a later add-on.
|
||||||
|
- Do not present scaffolding, dashboards, placeholders, or mock flows as product progress.
|
||||||
|
- State assumptions before coding when the environment, venue, chain, or source behavior is uncertain.
|
||||||
|
- Declare what is still fake in every commit.
|
||||||
|
- Do not read `BACKLOG.md` or `ARCHIVE.md` during an implementation turn unless the user explicitly asks to re-plan, open a new turn, or inspect history.
|
||||||
|
- If you discover adjacent work, add it to `BACKLOG.md` instead of absorbing it into the current turn.
|
||||||
|
- Changes that widen risk require explicit user approval:
|
||||||
|
- live funds
|
||||||
|
- secret creation or rotation
|
||||||
|
- permanent infrastructure spend
|
||||||
|
- long-running external jobs
|
||||||
|
- destructive data migrations
|
||||||
|
- The long-term thesis may be proposed, but `THESIS.md` must not be rewritten without explicit user approval.
|
||||||
|
|
||||||
|
## Iteration archive rule
|
||||||
|
When the user says to plan the next iteration, next turn, implementation turn, or proof sprint:
|
||||||
|
|
||||||
|
- first preserve the finished turn before drafting new planning docs
|
||||||
|
- do not rewrite the live turn files until the current turn has been archived
|
||||||
|
- use the existing repo workflow and archive locations:
|
||||||
|
- implementation turn snapshots go in `archive/implementation/`
|
||||||
|
- research turn snapshots go in `archive/research/`
|
||||||
|
- prefer the tracked scripts:
|
||||||
|
- `python3 scripts/workflow/close_turn.py ...`
|
||||||
|
- `python3 scripts/workflow/open_turn.py ...`
|
||||||
|
|
||||||
|
Planning or archiving is the one time it is correct to read `BACKLOG.md` and `ARCHIVE.md`.
|
||||||
|
|
||||||
|
## Planning inputs
|
||||||
|
- `iteration`, `implementation turn`, `proof sprint`, and `next turn` mean the same planning slice unless the user says otherwise.
|
||||||
|
- Use `BACKLOG.md` only while planning, re-planning, or archiving. Do not read it while implementing or validating the active turn unless the user explicitly asks.
|
||||||
|
- Select backlog items and bugs that belong together under one proof topic instead of mixing unrelated work into the same slice.
|
||||||
|
- When an item is pulled into the live turn, remove it from `BACKLOG.md` and record the planning event in `ARCHIVE.md`.
|
||||||
|
|
||||||
|
## Planning quality bar
|
||||||
|
- `IMPLEMENTATION.md` must be detailed enough that coding does not depend on rediscovering the plan mid-turn.
|
||||||
|
- `IMPLEMENTATION.md` should cover the end-to-end system touched by the proof: ingest, pricing, inventory, persistence, strategy, execution, control surfaces, logging, validation, tests, failure modes, and important edge cases.
|
||||||
|
- `PROOF.md` must be specific and falsifiable: scope, non-goals, definition of done, validation evidence, failure conditions, and clear statements about what is real versus fake.
|
||||||
|
- Both planning documents should think through the whole operator workflow and full system path, not isolated file edits.
|
||||||
|
|
||||||
|
## Turn lanes
|
||||||
|
- Implementation lane:
|
||||||
|
- Governed by `PROOF.md` and `IMPLEMENTATION.md`.
|
||||||
|
- Must make the shared live and historical data path more real unless the turn is explicitly marked as pure ops.
|
||||||
|
- “Doing the turn” means implementing `IMPLEMENTATION.md`, validating against `PROOF.md`, fixing what fails, and continuing until the definition of done is met or a hard blocker is reached.
|
||||||
|
- Research lane:
|
||||||
|
- Governed by `research/ACTIVE.md`.
|
||||||
|
- Must name the hypothesis, dataset, metric, assumptions, and falsification condition.
|
||||||
|
- Research output is evidence, not product shape.
|
||||||
|
|
||||||
|
## Commit rules
|
||||||
|
Every non-merge commit message body must include:
|
||||||
|
- `Proof: ...`
|
||||||
|
- `Assumptions: ...`
|
||||||
|
- `Still fake: ...`
|
||||||
|
|
||||||
|
Install the tracked hook before relying on this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/workflow/install_hooks.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Post-implementation loop
|
||||||
|
- After implementation and validation, expect the user to test the slice and suggest small fixes.
|
||||||
|
- Treat those small fixes as part of closing the same turn unless the user explicitly changes scope.
|
||||||
|
|
||||||
|
## Bug-fix rule
|
||||||
|
- If a bug is found, fix it, inspect analogous or affected locations, and apply the needed follow-up fixes there too.
|
||||||
|
- No bug fix is done without a regression test.
|
||||||
|
- If a meaningful automated test cannot be added, stop and explain why instead of claiming the fix is complete.
|
||||||
|
|
||||||
|
## Real progress vs fake progress
|
||||||
|
Real progress means the repository can do more of the active proof with validated evidence from real systems. In this repo that means things like:
|
||||||
|
- ingesting real NEAR Intents flow
|
||||||
|
- storing durable inventory, pricing, decision, and execution records
|
||||||
|
- producing a real decision from live data
|
||||||
|
- making a real execution attempt through repo-controlled code
|
||||||
|
- proving blocked-path safety with explicit evidence
|
||||||
|
|
||||||
|
Fake progress includes:
|
||||||
|
- docs-only motion without stronger runtime truth
|
||||||
|
- speculative architecture not required by the active proof
|
||||||
|
- dashboards or control surfaces without the underlying real path
|
||||||
|
- placeholders or mocks presented as finished work
|
||||||
|
- invented data, unverifiable claims, or abstractions not yet required
|
||||||
|
|
||||||
|
## Safety rules
|
||||||
|
- Prefer the smallest real implementation that proves the active turn works.
|
||||||
|
- Keep secrets out of git, docs, and chat history.
|
||||||
|
- Use self-hosted or directly controlled infrastructure by default.
|
||||||
|
- If something is fake or incomplete, say so plainly.
|
||||||
|
|
||||||
|
## Review standard
|
||||||
|
Before claiming a turn is done, be able to state:
|
||||||
|
- what became more real
|
||||||
|
- what was validated against real data or systems
|
||||||
|
- what is still fake
|
||||||
|
- what was deliberately not built
|
||||||
|
|
||||||
|
## Working rule
|
||||||
|
Within an approved turn, continue implementing and validating until:
|
||||||
|
- `PROOF.md` is satisfied for the active scope, or
|
||||||
|
- a hard blocker requires user input.
|
||||||
|
|
||||||
|
Do not silently open the next turn yourself.
|
||||||
|
Do not stop at “the structure exists.”
|
||||||
|
Do not stop while the active turn still depends on hidden dummy paths.
|
||||||
|
If something remains fake at the end of the turn, name it plainly.
|
||||||
16
ARCHIVE.md
Normal file
16
ARCHIVE.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Archive Index
|
||||||
|
|
||||||
|
This file records turn openings, closures, and archived snapshots.
|
||||||
|
|
||||||
|
Legacy note:
|
||||||
|
- Work completed before `2026-04-01` predates this workflow and is not retroactively indexed here.
|
||||||
|
|
||||||
|
## Implementation Turns
|
||||||
|
|
||||||
|
- 2026-04-02: `first-non-mocked-tradeable-loop-for-one-pair` closed with status `passed`. A live active-pair quote flowed through funding, inventory sync, strategy, and real mainnet quote responses with durable history.
|
||||||
|
## Research Turns
|
||||||
|
|
||||||
|
## Planning Events
|
||||||
|
- 2026-04-01: workflow files initialized for thesis, implementation proof, backlog, archive, and research lane.
|
||||||
|
- 2026-04-01: active implementation proof rewritten from durable-history scaffolding to the first executable trade loop for one pair.
|
||||||
|
- 2026-04-02: opened implementation turn `pre-credit-funding-visibility-and-operator-alerts` from backlog items O003, O004.
|
||||||
36
BACKLOG.md
Normal file
36
BACKLOG.md
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Backlog
|
||||||
|
|
||||||
|
This file is the candidate pool for future work. It is not the active plan.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Add ideas here when they do not belong in the current turn.
|
||||||
|
- Promotion from backlog to an active turn is a separate planning step.
|
||||||
|
- `scripts/workflow/add_backlog.py` can append new items with stable IDs.
|
||||||
|
|
||||||
|
## Implementation Candidates
|
||||||
|
- [I001] Hybrid reference-price service using Kraken stream and CoinGecko poll or fallback for the active pair inputs.
|
||||||
|
- [I002] PostgreSQL event store plus history writer for quotes, reference prices, decisions, commands, and execution results.
|
||||||
|
- [I003] Strategy engine that consumes `norm.swap_demand` plus reference prices and emits auditable decisions.
|
||||||
|
- [I004] Real Near Intents executor service using pre-funded internal inventory, with explicit arming and idempotent result reporting.
|
||||||
|
- [I005] Import local EUR/BTC history on disk into research tables or files for replay and baseline checks.
|
||||||
|
- [I006] Decision-to-command safety gate with explicit arm or disarm state, notional caps, and inventory freshness checks.
|
||||||
|
- [I007] Inventory-aware execution rule: implement both directions, but only fire the side backed by credited internal source-asset inventory.
|
||||||
|
- [I008] Inventory-sync service for NEAR Intents internal balances and pending funding state.
|
||||||
|
- [I009] Liquidity-manager service for deposit addresses, funding actions, and treasury visibility.
|
||||||
|
|
||||||
|
## Research Candidates
|
||||||
|
- [R001] Compare Kraken and CoinGecko drift and freshness for the assets needed to price the active pair.
|
||||||
|
- [R002] Test whether the active pair's implied rate diverges from external reference prices enough to justify execution after a simple 2% gross threshold.
|
||||||
|
- [R003] Measure whether stale quotes correlate with worse execution quality or higher reject rates.
|
||||||
|
- [R004] Import and inspect the local historical EUR/BTC data to see how it can seed replay and backtests.
|
||||||
|
- [R005] Measure how long treasury funding takes from external transfer to credited internal inventory.
|
||||||
|
|
||||||
|
## Ops Candidates
|
||||||
|
- [O001] PostgreSQL backup and retention plan for analytics and audit history.
|
||||||
|
- [O002] Signing-secret and NEAR Intents account management for real execution credentials.
|
||||||
|
|
||||||
|
## Bugs
|
||||||
|
- [B001] The previous storage-only turn did not reach a tradeable loop and pulled the workflow toward scaffolding.
|
||||||
|
- [B002] No reference price source exists, so the system cannot estimate edge.
|
||||||
|
- [B003] Dummy reactor and dummy executor prevent a non-mocked trade path.
|
||||||
|
- [B004] The prior plan assumed external hot wallets were on the trade hot path instead of pre-funded NEAR Intents inventory.
|
||||||
235
IMPLEMENTATION.md
Normal file
235
IMPLEMENTATION.md
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
# Implementation Turn: pre-credit funding visibility and operator alerts
|
||||||
|
|
||||||
|
Status: open
|
||||||
|
Opened: 2026-04-02
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Make the already-live BTC/EURe loop operationally understandable between external funding and spendable credit, and make critical stale or failed states queryable as durable alert records instead of transient logs.
|
||||||
|
|
||||||
|
## Selected backlog items
|
||||||
|
- [O003] Alerts for stale reference prices, stale inventory state, stuck funding actions, and failed executor submissions.
|
||||||
|
- [O004] Pre-credit funding visibility for slow chains: watch configured deposit addresses at chain level, track inbound transfers through mempool and on-chain confirmation before bridge credit, persist that state separately from spendable inventory, and alert operators when funding is seen, delayed, or stuck.
|
||||||
|
|
||||||
|
## Design rule
|
||||||
|
Keep the already-proven execution and inventory truth path intact:
|
||||||
|
- verifier and bridge credit remain the only spendable truth
|
||||||
|
- pre-credit visibility is additive observability
|
||||||
|
- alerts are additive operability
|
||||||
|
|
||||||
|
## Event backbone
|
||||||
|
Retain Kafka as the backbone. Add only the minimal new topics required:
|
||||||
|
|
||||||
|
- `ops.funding_observation`
|
||||||
|
- `ops.alert`
|
||||||
|
|
||||||
|
These topics are append-only evidence streams, not control planes.
|
||||||
|
|
||||||
|
## Durable store
|
||||||
|
Extend PostgreSQL with new append-only families:
|
||||||
|
- `funding_observations`
|
||||||
|
- `ops_alerts`
|
||||||
|
|
||||||
|
If a current-state materialization is needed, derive it from append-only records or keep it as a clearly named snapshot table. Do not replace the append-only record.
|
||||||
|
|
||||||
|
## Service changes
|
||||||
|
|
||||||
|
### 1. `liquidity-manager`
|
||||||
|
Extend the existing treasury owner instead of inventing a broad new funding stack.
|
||||||
|
|
||||||
|
New responsibilities:
|
||||||
|
- retain active funding handles by chain and asset
|
||||||
|
- poll configured chain observers for those handles
|
||||||
|
- emit `ops.funding_observation`
|
||||||
|
- correlate chain observations with bridge `recent_deposits`
|
||||||
|
- expose latest observations and credit-correlation state through `/state`
|
||||||
|
|
||||||
|
Expected state shape additions:
|
||||||
|
- `funding_observations_by_handle`
|
||||||
|
- `latest_funding_observation_at`
|
||||||
|
- `uncredited_funding_total_by_asset`
|
||||||
|
- `credit_correlation`
|
||||||
|
|
||||||
|
Control additions:
|
||||||
|
- `POST /refresh-funding-observations`
|
||||||
|
- optional `POST /pause-funding-observer`
|
||||||
|
- optional `POST /resume-funding-observer`
|
||||||
|
|
||||||
|
Important implementation constraints:
|
||||||
|
- do not change withdrawal behavior
|
||||||
|
- do not reuse spendable inventory fields for pre-credit state
|
||||||
|
- keep BTC and EURe observation records in one shared schema
|
||||||
|
|
||||||
|
### 2. `inventory-sync`
|
||||||
|
Keep current spendable accounting intact.
|
||||||
|
|
||||||
|
Possible additions:
|
||||||
|
- read the latest funding observations
|
||||||
|
- expose a separate `pre_credit_inbound` or `funding_visibility` field in `/state`
|
||||||
|
|
||||||
|
Hard rule:
|
||||||
|
- `spendable`, `pending_inbound`, and strategy-facing credited truth must not become looser
|
||||||
|
|
||||||
|
### 3. `history-writer`
|
||||||
|
Consume and persist the new topics:
|
||||||
|
- `ops.funding_observation`
|
||||||
|
- `ops.alert`
|
||||||
|
|
||||||
|
Expose through `/state`:
|
||||||
|
- latest funding-observation write time
|
||||||
|
- latest alert write time
|
||||||
|
- counts or offsets for the new topics
|
||||||
|
|
||||||
|
Add query-friendly indexes for:
|
||||||
|
- `tx_hash`
|
||||||
|
- `funding_handle`
|
||||||
|
- `alert_code`
|
||||||
|
- `ingested_at`
|
||||||
|
|
||||||
|
### 4. `ops-sentinel` or equivalent alert evaluator
|
||||||
|
Add one small service only if needed to keep alert logic separate and testable.
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
- consume:
|
||||||
|
- `ref.market_price`
|
||||||
|
- `state.intent_inventory`
|
||||||
|
- `ops.liquidity_action`
|
||||||
|
- `ops.funding_observation`
|
||||||
|
- `exec.trade_result`
|
||||||
|
- evaluate policy windows for stale and stuck conditions
|
||||||
|
- emit `ops.alert` raise/clear transitions
|
||||||
|
- expose current alert state
|
||||||
|
|
||||||
|
Preferred alert model:
|
||||||
|
- stable `alert_code`
|
||||||
|
- `status`: `raised` or `cleared`
|
||||||
|
- `severity`
|
||||||
|
- `reason`
|
||||||
|
- `first_raised_at`
|
||||||
|
- `last_evaluated_at`
|
||||||
|
- correlation IDs when available
|
||||||
|
|
||||||
|
Minimal alert set for this turn:
|
||||||
|
- `reference_price_stale`
|
||||||
|
- `inventory_snapshot_stale`
|
||||||
|
- `funding_seen_unconfirmed`
|
||||||
|
- `funding_confirmed_credit_pending`
|
||||||
|
- `funding_stuck`
|
||||||
|
- `executor_submission_failed`
|
||||||
|
|
||||||
|
Do not add Slack, email, or paging integrations in this turn unless required to prove the path. Durable alert records plus HTTP state are sufficient.
|
||||||
|
|
||||||
|
## Chain observer plan
|
||||||
|
|
||||||
|
### BTC
|
||||||
|
Must be the first-class proof path.
|
||||||
|
|
||||||
|
Implementation expectations:
|
||||||
|
- configurable observer endpoint
|
||||||
|
- look up the configured BTC deposit address
|
||||||
|
- detect:
|
||||||
|
- mempool appearance when available
|
||||||
|
- confirmation count
|
||||||
|
- credited transition once bridge/verifier catches up
|
||||||
|
|
||||||
|
Assumption to keep explicit in code:
|
||||||
|
- a chain observer can disappear or lag independently of the bridge
|
||||||
|
|
||||||
|
### Gnosis / EURe
|
||||||
|
Nice-to-have within the same schema, but BTC is the proof-critical path.
|
||||||
|
|
||||||
|
If included this turn:
|
||||||
|
- watch the configured deposit address for EURe token transfers
|
||||||
|
- represent observation state in the same event model
|
||||||
|
|
||||||
|
## Record shapes
|
||||||
|
|
||||||
|
### `ops.funding_observation`
|
||||||
|
Required fields:
|
||||||
|
- `funding_observation_id`
|
||||||
|
- `account_id`
|
||||||
|
- `asset_id`
|
||||||
|
- `chain`
|
||||||
|
- `funding_handle`
|
||||||
|
- `source`
|
||||||
|
- `tx_hash`
|
||||||
|
- `status`
|
||||||
|
- `amount`
|
||||||
|
- `confirmations`
|
||||||
|
- `first_seen_at`
|
||||||
|
- `last_seen_at`
|
||||||
|
- `credited_at` when known
|
||||||
|
- `bridge_deposit_tx_hash` when correlated
|
||||||
|
|
||||||
|
### `ops.alert`
|
||||||
|
Required fields:
|
||||||
|
- `alert_event_id`
|
||||||
|
- `alert_code`
|
||||||
|
- `status`
|
||||||
|
- `severity`
|
||||||
|
- `reason`
|
||||||
|
- `service_scope`
|
||||||
|
- `pair` when relevant
|
||||||
|
- `asset_id` when relevant
|
||||||
|
- `tx_hash` when relevant
|
||||||
|
- `raised_at`
|
||||||
|
- `cleared_at`
|
||||||
|
- `details`
|
||||||
|
|
||||||
|
## Control surface expectations
|
||||||
|
|
||||||
|
### `liquidity-manager`
|
||||||
|
Must expose:
|
||||||
|
- active deposit handles
|
||||||
|
- latest pre-credit funding observations
|
||||||
|
- latest credit correlation
|
||||||
|
- whether the funding observer is healthy or paused
|
||||||
|
|
||||||
|
### alert evaluator
|
||||||
|
Must expose:
|
||||||
|
- current active alerts
|
||||||
|
- latest cleared alerts
|
||||||
|
- per-alert evaluation timestamps
|
||||||
|
- pause state
|
||||||
|
|
||||||
|
### `history-writer`
|
||||||
|
Must expose the new topic offsets and write status for funding observations and alerts.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
Required automated coverage:
|
||||||
|
- BTC funding observation remains non-spendable before credit
|
||||||
|
- alert transitions raise then clear on recovered stale state
|
||||||
|
- funding observation correlates to a later credited deposit without losing the original tx hash
|
||||||
|
- executor failure produces an alert event
|
||||||
|
|
||||||
|
If a meaningful automated test cannot be written for a subpath, stop and record why instead of hand-waving.
|
||||||
|
|
||||||
|
## Validation plan
|
||||||
|
- Safe induced stale-price alert:
|
||||||
|
- pause `market-reference-ingest`
|
||||||
|
- wait past freshness window
|
||||||
|
- observe `reference_price_stale`
|
||||||
|
- resume and observe clear
|
||||||
|
- Safe induced stale-inventory alert:
|
||||||
|
- pause `inventory-sync`
|
||||||
|
- wait past freshness window
|
||||||
|
- observe `inventory_snapshot_stale`
|
||||||
|
- resume and observe clear
|
||||||
|
- Funding visibility proof:
|
||||||
|
- use a real deposit address
|
||||||
|
- observe pre-credit chain state before bridge credit where timing allows
|
||||||
|
- later observe credit correlation
|
||||||
|
- Executor failure alert proof:
|
||||||
|
- use a controlled non-destructive failure mode such as temporary relay endpoint override in a safe environment or a replayable failure fixture
|
||||||
|
- verify `executor_submission_failed`
|
||||||
|
|
||||||
|
## Out of scope on purpose
|
||||||
|
- No new trading strategy
|
||||||
|
- No historical backtest engine
|
||||||
|
- No broad observability stack
|
||||||
|
- No polished dashboard frontend
|
||||||
|
- No automated treasury refills
|
||||||
|
|
||||||
|
## Still fake at turn open
|
||||||
|
- Pre-credit funding visibility is still missing from the live cluster.
|
||||||
|
- Alert state is still mostly implicit in service logs and manual inspection.
|
||||||
|
- There is no durable operator-facing record yet for "funds are on the way but not spendable."
|
||||||
154
PROOF.md
Normal file
154
PROOF.md
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
# Implementation Proof: pre-credit funding visibility and operator alerts
|
||||||
|
|
||||||
|
Status: open
|
||||||
|
Opened: 2026-04-02
|
||||||
|
|
||||||
|
## Target outcome
|
||||||
|
The next turn is complete only when `unrip` can show operators the gap between "funds sent" and "funds spendable" with durable evidence, and can surface actionable alert state for the live loop without requiring log-diving or manual SQL every time something stalls.
|
||||||
|
|
||||||
|
This turn does not expand the trade hot path. It makes the existing live system more explainable and more operable.
|
||||||
|
|
||||||
|
## Hypothesis
|
||||||
|
`unrip` becomes materially safer to operate once it can:
|
||||||
|
|
||||||
|
1. observe configured funding handles before NEAR Intents credit
|
||||||
|
2. persist chain-level funding observations separately from spendable inventory
|
||||||
|
3. link pre-credit observations to later bridge and verifier credit where possible
|
||||||
|
4. emit durable alert state for stale prices, stale inventory, stuck funding, and failed execution submissions
|
||||||
|
5. expose that state through the same small control surfaces and PostgreSQL audit trail as the rest of the system
|
||||||
|
|
||||||
|
If pre-credit funding remains invisible, or alert state still lives only in transient logs, the live loop is still too opaque for routine funded operation.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
- [O003] Alerts for stale reference prices, stale inventory state, stuck funding actions, and failed executor submissions.
|
||||||
|
- [O004] Pre-credit funding visibility for slow chains: watch configured deposit addresses at chain level, track inbound transfers through mempool and on-chain confirmation before bridge credit, persist that state separately from spendable inventory, and alert operators when funding is seen, delayed, or stuck.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
- No new venue, pair, or strategy logic.
|
||||||
|
- No dashboard or polished UI.
|
||||||
|
- No automatic treasury actions or auto-refunding.
|
||||||
|
- No attempt to treat chain-level observations as spendable inventory.
|
||||||
|
- No change to the live execution arming model.
|
||||||
|
|
||||||
|
## Source-of-truth rule
|
||||||
|
Spendable inventory remains the existing truth:
|
||||||
|
- bridge and verifier credit determine spendable balances
|
||||||
|
- chain-level observations are visibility only
|
||||||
|
|
||||||
|
The new pre-credit path must never be allowed to make a direction tradable earlier than the verifier does.
|
||||||
|
|
||||||
|
## Required runtime behavior
|
||||||
|
|
||||||
|
### Funding visibility
|
||||||
|
- The system must know the currently active funding handles for BTC and EURe.
|
||||||
|
- For configured chains, it must watch those handles before NEAR Intents credit appears.
|
||||||
|
- It must distinguish at least these states where applicable:
|
||||||
|
- `SEEN_UNCONFIRMED`
|
||||||
|
- `SEEN_CONFIRMED`
|
||||||
|
- `CREDIT_PENDING`
|
||||||
|
- `CREDITED`
|
||||||
|
- `FAILED_OR_STUCK`
|
||||||
|
- BTC is the must-prove chain because that is where live funding latency was operationally visible.
|
||||||
|
- Gnosis support may share the same event model even if its confirmation behavior is simpler.
|
||||||
|
|
||||||
|
### Alerts
|
||||||
|
- Alerts must be durable records, not only log lines.
|
||||||
|
- At minimum the system must raise and clear alert state for:
|
||||||
|
- stale reference price
|
||||||
|
- stale inventory snapshot
|
||||||
|
- funding seen but not credited within policy
|
||||||
|
- execution submission failure
|
||||||
|
- Alert transitions must be inspectable through HTTP state and PostgreSQL.
|
||||||
|
|
||||||
|
## Service expectations
|
||||||
|
|
||||||
|
### `liquidity-manager`
|
||||||
|
Must become the owner of chain-level funding observations because it already owns deposit handles and treasury state.
|
||||||
|
|
||||||
|
It must:
|
||||||
|
- refresh and retain active funding handles
|
||||||
|
- ingest chain-level funding observations
|
||||||
|
- reconcile them against bridge deposit state
|
||||||
|
- publish durable funding-observation records
|
||||||
|
- expose current per-handle funding state
|
||||||
|
|
||||||
|
It must not:
|
||||||
|
- mark funds spendable
|
||||||
|
- trade on pre-credit observations
|
||||||
|
|
||||||
|
### `inventory-sync`
|
||||||
|
May surface pre-credit funding context, but only under a clearly separate non-spendable field.
|
||||||
|
|
||||||
|
It must not:
|
||||||
|
- merge pre-credit observations into `spendable`
|
||||||
|
|
||||||
|
### `history-writer`
|
||||||
|
Must persist the new record families:
|
||||||
|
- funding observations
|
||||||
|
- alert events
|
||||||
|
- optionally current alert snapshots if the implementation separates events from state
|
||||||
|
|
||||||
|
### Alert evaluator
|
||||||
|
This may be a new small service or a tightly scoped extension of an existing one, but it must:
|
||||||
|
- evaluate staleness and stuck conditions from durable inputs
|
||||||
|
- emit durable alert events
|
||||||
|
- expose current alert state and the latest reasons
|
||||||
|
|
||||||
|
No broad orchestration or dashboard service should be introduced just to satisfy this proof.
|
||||||
|
|
||||||
|
## Required durable storage
|
||||||
|
PostgreSQL must store at least:
|
||||||
|
- funding observations before bridge credit
|
||||||
|
- alert events or alert snapshots
|
||||||
|
- enough timestamps and IDs to correlate:
|
||||||
|
- funding handle
|
||||||
|
- chain tx hash
|
||||||
|
- later bridge tx hash or deposit record
|
||||||
|
- resulting verifier credit snapshot when available
|
||||||
|
|
||||||
|
Kafka remains the event backbone.
|
||||||
|
|
||||||
|
## Required control surface
|
||||||
|
At minimum operators must be able to inspect:
|
||||||
|
- active funding handles
|
||||||
|
- latest pre-credit observations by handle
|
||||||
|
- confirmation depth or equivalent chain state when available
|
||||||
|
- whether a funding action is still pending credit
|
||||||
|
- current active alerts and their reasons
|
||||||
|
|
||||||
|
If a new alert service exists, it must expose:
|
||||||
|
- `GET /healthz`
|
||||||
|
- `GET /state`
|
||||||
|
- `POST /pause`
|
||||||
|
- `POST /resume`
|
||||||
|
|
||||||
|
## Definition of done
|
||||||
|
- The live cluster still runs the previously proven funded trade loop unchanged for spendable truth.
|
||||||
|
- At least one real funding handle is watched at chain level before bridge credit.
|
||||||
|
- For at least one real deposit path, the system records a pre-credit observation before or during confirmation and later records the credited state separately.
|
||||||
|
- PostgreSQL contains durable records for the pre-credit funding path and alert path.
|
||||||
|
- Operators can inspect current funding observations and current alerts through control APIs.
|
||||||
|
- A stale price or stale inventory condition can be induced safely and becomes a durable alert.
|
||||||
|
- A funding delay or manually injected stuck condition can be represented as a durable alert with explicit reason fields.
|
||||||
|
- A failed execution submission path is represented as an alert without inventing fake venue traffic.
|
||||||
|
- Tests cover:
|
||||||
|
- pre-credit observations staying non-spendable
|
||||||
|
- alert raise and clear transitions
|
||||||
|
- correlation of funding observation to later credit when identifiers are available
|
||||||
|
|
||||||
|
## Failure conditions
|
||||||
|
- Funding observations exist only in logs and are not queryable later.
|
||||||
|
- Pre-credit observations leak into spendable inventory or strategy gating.
|
||||||
|
- Alerts cannot be queried as current state.
|
||||||
|
- The only proof of stuck funding is a human manually watching a block explorer.
|
||||||
|
- The implementation adds a dashboard shell without stronger runtime truth.
|
||||||
|
|
||||||
|
## Current real
|
||||||
|
- The first funded BTC/EURe live loop is already proven:
|
||||||
|
- real quote ingest
|
||||||
|
- real reference pricing
|
||||||
|
- real credited inventory
|
||||||
|
- real strategy decisions
|
||||||
|
- real `quote_response` submissions
|
||||||
|
- durable event chain in PostgreSQL
|
||||||
|
- Portfolio metrics are now durably computed and exposed, but alerting and pre-credit funding visibility are still incomplete.
|
||||||
50
THESIS.md
Normal file
50
THESIS.md
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# unrip thesis
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Build a data-first trading system whose first-class artifact is trustworthy market and execution history.
|
||||||
|
|
||||||
|
The bot is one consumer of that truth. Analytics and backtesting are not bolted on later; they are part of the product from the beginning.
|
||||||
|
|
||||||
|
## Product nucleus
|
||||||
|
The nucleus of the system is one shared truth pipeline:
|
||||||
|
|
||||||
|
1. observe live market and intent flow
|
||||||
|
2. persist raw and normalized events durably
|
||||||
|
3. replay the same history for analytics and backtests
|
||||||
|
4. score candidate actions from the same canonical data model
|
||||||
|
5. execute only behind explicit safety gates and full auditability
|
||||||
|
|
||||||
|
## Architectural invariants
|
||||||
|
- Live decisions and historical analysis must share the same canonical event model whenever practical.
|
||||||
|
- Raw events are kept alongside normalized and derived records.
|
||||||
|
- Every important decision should be reproducible from stored inputs and explicit assumptions.
|
||||||
|
- Execution must not outpace observability. If the system cannot explain what happened, it is not ready to trade.
|
||||||
|
- Quote collection and analytics are core product work, not support work.
|
||||||
|
|
||||||
|
## Near-term thesis
|
||||||
|
Near term, `unrip` should become a narrow but truthful trading-data and decision pipeline for one real pair on one venue.
|
||||||
|
|
||||||
|
That means:
|
||||||
|
- real upstream data
|
||||||
|
- durable storage beyond transient bus retention
|
||||||
|
- replayable history
|
||||||
|
- measurable candidate decisions
|
||||||
|
- no pretending that execution is safe before the data and analysis path is trustworthy
|
||||||
|
|
||||||
|
## Long-term thesis
|
||||||
|
Long term, the same system should support:
|
||||||
|
- automated trading on selected pairs
|
||||||
|
- analytics and backtesting from retained ground-truth data
|
||||||
|
- cross-chain and cross-asset execution routing
|
||||||
|
|
||||||
|
## Non-goals right now
|
||||||
|
- polished operator UI before the data loop is truthful
|
||||||
|
- broad multi-venue coverage before one core loop is real
|
||||||
|
- strategy claims without named assumptions and falsification criteria
|
||||||
|
- live trading with real funds before paper or tightly gated execution is trustworthy
|
||||||
|
|
||||||
|
## Approval boundaries
|
||||||
|
The agent may propose changes to the thesis, but the user must approve:
|
||||||
|
- changes to the core product definition
|
||||||
|
- changes that raise the risk class of the system
|
||||||
|
- changes that create lasting infra cost or operational burden
|
||||||
59
WORKFLOW.md
Normal file
59
WORKFLOW.md
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Workflow
|
||||||
|
|
||||||
|
This repository uses a small tracked workflow layer instead of a large agent orchestration system.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
- `THESIS.md`: stable product intent
|
||||||
|
- `PROOF.md`: active implementation proof
|
||||||
|
- `IMPLEMENTATION.md`: current implementation turn
|
||||||
|
- `research/ACTIVE.md`: active research charter
|
||||||
|
- `BACKLOG.md`: parked ideas and bugs
|
||||||
|
- `ARCHIVE.md`: turn history index
|
||||||
|
- `workflow/REVIEW_PROMPT.md`: adversarial review prompt
|
||||||
|
|
||||||
|
## Install the tracked git hook
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/workflow/install_hooks.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add a backlog item
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/workflow/add_backlog.py --lane implementation --summary "Reference-price service for active pair inputs"
|
||||||
|
python3 scripts/workflow/add_backlog.py --lane research --summary "Test whether implied pair rate diverges from external reference prices after fees"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Open a new implementation turn
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/workflow/open_turn.py \
|
||||||
|
--lane implementation \
|
||||||
|
--title "first executable trade loop for one pair" \
|
||||||
|
--summary "Add reference pricing, strategy, durable audit history, and a real execution path for the active pair." \
|
||||||
|
--pick I001 \
|
||||||
|
--pick I002 \
|
||||||
|
--pick I003 \
|
||||||
|
--pick I004
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `--commit` if you want the planning change committed automatically.
|
||||||
|
|
||||||
|
## Close the current turn and archive it
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/workflow/close_turn.py \
|
||||||
|
--lane implementation \
|
||||||
|
--status passed \
|
||||||
|
--summary "A live active-pair quote flowed through pricing, decision, and a real execution attempt with durable audit records."
|
||||||
|
```
|
||||||
|
|
||||||
|
The script copies the live turn files into `archive/implementation/` or `archive/research/`, updates `ARCHIVE.md`, and can make the archive commit with `--commit`.
|
||||||
|
|
||||||
|
## Build a review bundle
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/workflow/review_diff.sh HEAD~1
|
||||||
|
```
|
||||||
|
|
||||||
|
That emits a Markdown bundle containing the diff plus the adversarial review prompt for a separate review-only agent session.
|
||||||
1
archive/implementation/.gitkeep
Normal file
1
archive/implementation/.gitkeep
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -0,0 +1,544 @@
|
||||||
|
# Implementation Turn: first non-mocked tradeable loop for one pair
|
||||||
|
|
||||||
|
Status: open
|
||||||
|
Opened: 2026-04-01
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Build the first full live vertical slice that can actually attempt a trade:
|
||||||
|
|
||||||
|
1. observe a real NEAR Intents quote
|
||||||
|
2. enrich it with live external reference pricing
|
||||||
|
3. synchronize spendable inventory already credited inside NEAR Intents
|
||||||
|
4. evaluate it in a real strategy service
|
||||||
|
5. gate it by inventory and arm state
|
||||||
|
6. submit it through a real Near Intents executor
|
||||||
|
7. persist the full chain in PostgreSQL
|
||||||
|
|
||||||
|
This turn is explicitly not a storage side-quest. Storage, control surfaces, analytics support, and treasury funding visibility are included because they are required to make the trade loop trustworthy.
|
||||||
|
|
||||||
|
## Selected backlog items
|
||||||
|
- [I001] Hybrid reference-price service using Kraken stream and CoinGecko poll or fallback for the active pair inputs.
|
||||||
|
- [I002] PostgreSQL event store plus history writer for quotes, reference prices, decisions, commands, and execution results.
|
||||||
|
- [I003] Strategy engine that consumes `norm.swap_demand` plus reference prices and emits auditable decisions.
|
||||||
|
- [I004] Real Near Intents executor service using pre-funded internal inventory, with explicit arming and idempotent result reporting.
|
||||||
|
- [I006] Decision-to-command safety gate with explicit arm or disarm state, notional caps, and inventory freshness checks.
|
||||||
|
- [I007] Inventory-aware execution rule: implement both directions, but only fire the side backed by credited internal source-asset inventory.
|
||||||
|
- [I008] Inventory-sync service for NEAR Intents internal balances and pending funding state.
|
||||||
|
- [I009] Liquidity-manager service for deposit addresses, funding actions, and treasury visibility.
|
||||||
|
- [B002] No reference price source exists, so the system cannot estimate edge.
|
||||||
|
- [B003] Dummy reactor and dummy executor prevent a non-mocked trade path.
|
||||||
|
- [B004] The current plan assumed external hot wallets were on the hot trade path instead of pre-funded internal inventory.
|
||||||
|
|
||||||
|
## Architectural shape
|
||||||
|
|
||||||
|
### Event backbone
|
||||||
|
Kafka or Redpanda remains the backbone between services.
|
||||||
|
|
||||||
|
Required topic set for this turn:
|
||||||
|
- `raw.near_intents.quote`
|
||||||
|
- `norm.swap_demand`
|
||||||
|
- `ref.market_price`
|
||||||
|
- `state.intent_inventory`
|
||||||
|
- `ops.liquidity_action`
|
||||||
|
- `decision.trade_decision`
|
||||||
|
- `cmd.execute_trade`
|
||||||
|
- `exec.trade_result`
|
||||||
|
|
||||||
|
### Durable store
|
||||||
|
PostgreSQL is the first durable analytics and audit layer.
|
||||||
|
|
||||||
|
Why:
|
||||||
|
- enough write throughput for current scope
|
||||||
|
- strong queryability for replay, inspection, and debugging
|
||||||
|
- simple to operate
|
||||||
|
- better fit than inventing a warehouse right now
|
||||||
|
|
||||||
|
### Service split
|
||||||
|
The first real loop should be seven services:
|
||||||
|
- `near-intents-ingest`
|
||||||
|
- `market-reference-ingest`
|
||||||
|
- `inventory-sync`
|
||||||
|
- `liquidity-manager`
|
||||||
|
- `history-writer`
|
||||||
|
- `strategy-engine`
|
||||||
|
- `trade-executor`
|
||||||
|
|
||||||
|
## Service-by-service responsibilities
|
||||||
|
|
||||||
|
### 1. `near-intents-ingest`
|
||||||
|
Responsibilities:
|
||||||
|
- connect to the NEAR Intents quote stream
|
||||||
|
- filter or classify the active pair
|
||||||
|
- emit raw and normalized events
|
||||||
|
- expose runtime state
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- NEAR Intents websocket
|
||||||
|
- pair filter config and runtime override
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- `raw.near_intents.quote`
|
||||||
|
- `norm.swap_demand`
|
||||||
|
|
||||||
|
Control surface:
|
||||||
|
- `GET /healthz`
|
||||||
|
- `GET /state`
|
||||||
|
- `GET /pair-filter`
|
||||||
|
- `PUT /pair-filter`
|
||||||
|
- `POST /pair-filter/reset`
|
||||||
|
|
||||||
|
Important edge cases:
|
||||||
|
- websocket disconnect
|
||||||
|
- reconnect storm
|
||||||
|
- invalid JSON
|
||||||
|
- pair silent but connection healthy
|
||||||
|
|
||||||
|
### 2. `market-reference-ingest`
|
||||||
|
Responsibilities:
|
||||||
|
- subscribe to Kraken BTC/EUR pricing
|
||||||
|
- poll CoinGecko for fallback or cross-check pricing
|
||||||
|
- derive fair BTC/EURe price for both trade directions
|
||||||
|
- publish reference-price events
|
||||||
|
- expose latest source state and freshness
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- Kraken stream
|
||||||
|
- CoinGecko HTTP API
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- `ref.market_price`
|
||||||
|
|
||||||
|
Control surface:
|
||||||
|
- `GET /healthz`
|
||||||
|
- `GET /state`
|
||||||
|
- `POST /refresh`
|
||||||
|
- `POST /pause`
|
||||||
|
- `POST /resume`
|
||||||
|
|
||||||
|
Required state:
|
||||||
|
- latest Kraken price
|
||||||
|
- latest CoinGecko price
|
||||||
|
- derived fair rate
|
||||||
|
- freshness age
|
||||||
|
- source health flags
|
||||||
|
|
||||||
|
Important edge cases:
|
||||||
|
- Kraken disconnect
|
||||||
|
- CoinGecko timeout or rate limit
|
||||||
|
- both sources stale
|
||||||
|
- conflicting sources beyond tolerance
|
||||||
|
|
||||||
|
Required behavior:
|
||||||
|
- if Kraken is down but CoinGecko is fresh, degrade according to policy and record fallback usage
|
||||||
|
- if both are stale, mark the service unhealthy for decisioning
|
||||||
|
- the first implementation must make the EURe/EUR pricing assumption explicit:
|
||||||
|
- either treat EURe as 1:1 with EUR and record that plainly
|
||||||
|
- or use a separate sanity source and record the mapping logic
|
||||||
|
|
||||||
|
### 3. `inventory-sync`
|
||||||
|
Responsibilities:
|
||||||
|
- read current credited spendable inventory from NEAR Intents internal state
|
||||||
|
- distinguish credited balances from pending deposits and pending withdrawals
|
||||||
|
- publish current internal inventory state
|
||||||
|
- expose freshness and reconciliation state
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- NEAR Intents inventory or verifier surfaces
|
||||||
|
- liquidity-manager state when relevant
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- `state.intent_inventory`
|
||||||
|
|
||||||
|
Control surface:
|
||||||
|
- `GET /healthz`
|
||||||
|
- `GET /state`
|
||||||
|
- `POST /refresh`
|
||||||
|
- `POST /pause`
|
||||||
|
- `POST /resume`
|
||||||
|
|
||||||
|
Required state:
|
||||||
|
- spendable balances by asset
|
||||||
|
- pending inbound funding by asset
|
||||||
|
- pending outbound withdrawal by asset
|
||||||
|
- last sync time
|
||||||
|
- reconciliation status
|
||||||
|
|
||||||
|
Important edge cases:
|
||||||
|
- internal inventory surface unavailable
|
||||||
|
- external deposit seen but not yet credited internally
|
||||||
|
- credited balance lower than expected after funding
|
||||||
|
- stale inventory snapshot
|
||||||
|
|
||||||
|
Required behavior:
|
||||||
|
- only credited internal inventory counts as spendable
|
||||||
|
- pending treasury movements must remain non-spendable
|
||||||
|
- stale inventory state must be visible and actionable
|
||||||
|
|
||||||
|
### 4. `liquidity-manager`
|
||||||
|
Responsibilities:
|
||||||
|
- request and track deposit addresses or equivalent funding handles for treasury assets
|
||||||
|
- track treasury funding actions from external wallets into NEAR Intents
|
||||||
|
- track withdrawals and rebalance actions
|
||||||
|
- expose current funding pipeline state
|
||||||
|
- publish auditable liquidity action records
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- treasury configuration
|
||||||
|
- external funding wallets
|
||||||
|
- NEAR Intents deposit or withdrawal surfaces
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- `ops.liquidity_action`
|
||||||
|
|
||||||
|
Control surface:
|
||||||
|
- `GET /healthz`
|
||||||
|
- `GET /state`
|
||||||
|
- `POST /refresh`
|
||||||
|
- `POST /pause`
|
||||||
|
- `POST /resume`
|
||||||
|
- `POST /freeze-withdrawals`
|
||||||
|
|
||||||
|
Required state:
|
||||||
|
- active deposit addresses or funding handles
|
||||||
|
- recent funding attempts
|
||||||
|
- pending credits
|
||||||
|
- recent withdrawals
|
||||||
|
- rebalance state
|
||||||
|
|
||||||
|
Important edge cases:
|
||||||
|
- deposit address request failure
|
||||||
|
- external wallet funded but NEAR Intents credit delayed
|
||||||
|
- duplicate funding detection
|
||||||
|
- unsupported asset or chain mapping
|
||||||
|
|
||||||
|
Required behavior:
|
||||||
|
- treasury actions must remain visible and auditable
|
||||||
|
- funding must be decoupled from the per-trade hot path
|
||||||
|
|
||||||
|
### 5. `history-writer`
|
||||||
|
Responsibilities:
|
||||||
|
- consume the core topics
|
||||||
|
- write append-only rows into PostgreSQL
|
||||||
|
- preserve causality across quote, decision, and execution records
|
||||||
|
- expose lag and write state
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- all core Kafka topics
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- PostgreSQL rows
|
||||||
|
|
||||||
|
Control surface:
|
||||||
|
- `GET /healthz`
|
||||||
|
- `GET /state`
|
||||||
|
- `POST /pause`
|
||||||
|
- `POST /resume`
|
||||||
|
- `POST /drain`
|
||||||
|
|
||||||
|
Required stored record families:
|
||||||
|
- raw quotes
|
||||||
|
- normalized demand
|
||||||
|
- reference prices
|
||||||
|
- inventory snapshots
|
||||||
|
- liquidity actions
|
||||||
|
- trade decisions
|
||||||
|
- execute commands
|
||||||
|
- execution results
|
||||||
|
|
||||||
|
Important edge cases:
|
||||||
|
- PostgreSQL unavailable
|
||||||
|
- duplicate deliveries from Kafka
|
||||||
|
- offset commit mismatch after partial write
|
||||||
|
|
||||||
|
Required behavior:
|
||||||
|
- never claim success before the write is durable
|
||||||
|
- surface last committed offsets and last successful write time
|
||||||
|
|
||||||
|
### 6. `strategy-engine`
|
||||||
|
Responsibilities:
|
||||||
|
- join latest demand with latest price and inventory state
|
||||||
|
- compute implied quote rate
|
||||||
|
- compare it to fair rate
|
||||||
|
- apply freshness thresholds
|
||||||
|
- apply the initial 2% gross edge threshold
|
||||||
|
- apply max-notional and inventory checks
|
||||||
|
- emit auditable decision events
|
||||||
|
- emit `cmd.execute_trade` only when all gates pass and the system is armed
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- `norm.swap_demand`
|
||||||
|
- `ref.market_price`
|
||||||
|
- `state.intent_inventory`
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- `decision.trade_decision`
|
||||||
|
- `cmd.execute_trade`
|
||||||
|
|
||||||
|
Control surface:
|
||||||
|
- `GET /healthz`
|
||||||
|
- `GET /state`
|
||||||
|
- `POST /arm`
|
||||||
|
- `POST /disarm`
|
||||||
|
- `POST /pause`
|
||||||
|
- `POST /resume`
|
||||||
|
- `PUT /threshold`
|
||||||
|
- `PUT /limits`
|
||||||
|
|
||||||
|
Required decision state:
|
||||||
|
- arm state
|
||||||
|
- current threshold
|
||||||
|
- current notional cap
|
||||||
|
- latest decision
|
||||||
|
- latest rejected decision reason
|
||||||
|
- per-reason skip counters
|
||||||
|
|
||||||
|
Required decision record fields:
|
||||||
|
- `decision_id`
|
||||||
|
- `quote_id`
|
||||||
|
- `pair`
|
||||||
|
- `direction`
|
||||||
|
- `implied_rate`
|
||||||
|
- `reference_rate`
|
||||||
|
- `gross_edge_pct`
|
||||||
|
- `price_freshness_ms`
|
||||||
|
- `inventory_snapshot`
|
||||||
|
- `decision`
|
||||||
|
- `decision_reason`
|
||||||
|
|
||||||
|
Important edge cases:
|
||||||
|
- stale prices
|
||||||
|
- no price yet
|
||||||
|
- no inventory yet
|
||||||
|
- insufficient spendable inventory
|
||||||
|
- pending deposit exists but is not yet credited
|
||||||
|
- quote already expired
|
||||||
|
- repeated quote IDs
|
||||||
|
- one direction affordable and the other not
|
||||||
|
- fresh deploy starts armed by mistake
|
||||||
|
|
||||||
|
### 7. `trade-executor`
|
||||||
|
Responsibilities:
|
||||||
|
- consume `cmd.execute_trade`
|
||||||
|
- load the correct Near Intents signing authority
|
||||||
|
- perform the real Near Intents submission using pre-funded internal inventory
|
||||||
|
- preserve idempotency across retries and restarts
|
||||||
|
- emit `exec.trade_result`
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- `cmd.execute_trade`
|
||||||
|
- signing secrets
|
||||||
|
- optional latest inventory state
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- `exec.trade_result`
|
||||||
|
|
||||||
|
Control surface:
|
||||||
|
- `GET /healthz`
|
||||||
|
- `GET /state`
|
||||||
|
- `POST /arm`
|
||||||
|
- `POST /disarm`
|
||||||
|
- `POST /pause`
|
||||||
|
- `POST /resume`
|
||||||
|
- `POST /drain`
|
||||||
|
|
||||||
|
Required state:
|
||||||
|
- arm state
|
||||||
|
- last command seen
|
||||||
|
- last request sent
|
||||||
|
- last venue response
|
||||||
|
- in-flight command count
|
||||||
|
- completed command count
|
||||||
|
- error counters
|
||||||
|
|
||||||
|
Important edge cases:
|
||||||
|
- duplicate command delivery
|
||||||
|
- crash after submission but before result publish
|
||||||
|
- venue reject
|
||||||
|
- timeout with unknown outcome
|
||||||
|
- insufficient internal inventory detected late
|
||||||
|
- secret missing or invalid
|
||||||
|
- signer not authorized for the intended NEAR Intents account
|
||||||
|
|
||||||
|
Required behavior:
|
||||||
|
- never silently swallow a venue error
|
||||||
|
- always publish a result record for attempted commands
|
||||||
|
- make duplicate suppression durable
|
||||||
|
- never try to bridge or top up inventory during trade execution
|
||||||
|
- start disarmed by default on fresh deploy
|
||||||
|
|
||||||
|
## Persistence design
|
||||||
|
|
||||||
|
### Why PostgreSQL now
|
||||||
|
- current scale does not require a special time-series database
|
||||||
|
- rows are easier to inspect than a custom file format for this phase
|
||||||
|
- joins across decision, command, and result matter more right now than raw ingestion throughput
|
||||||
|
- treasury, inventory, and execution joins matter more than raw bus throughput at this stage
|
||||||
|
|
||||||
|
### Minimum tables or equivalent record families
|
||||||
|
- `raw_near_intents_quotes`
|
||||||
|
- `swap_demand_events`
|
||||||
|
- `market_price_events`
|
||||||
|
- `intent_inventory_snapshots`
|
||||||
|
- `liquidity_actions`
|
||||||
|
- `trade_decisions`
|
||||||
|
- `execute_trade_commands`
|
||||||
|
- `trade_execution_results`
|
||||||
|
|
||||||
|
### Query requirements
|
||||||
|
Must be able to answer:
|
||||||
|
- what was the latest fair price when this decision was made
|
||||||
|
- what spendable inventory existed when this decision was made
|
||||||
|
- what treasury funding actions were still pending
|
||||||
|
- why was this quote skipped
|
||||||
|
- what command was emitted for this quote
|
||||||
|
- what happened when the executor submitted it
|
||||||
|
- what credited internal inventory existed at the time
|
||||||
|
|
||||||
|
## Control and stop semantics
|
||||||
|
Every service must support inspection.
|
||||||
|
|
||||||
|
State endpoints must be sufficient to answer:
|
||||||
|
- is it healthy
|
||||||
|
- is it connected
|
||||||
|
- what is it currently using as input state
|
||||||
|
- what was the last successful action
|
||||||
|
- why is it blocked, if blocked
|
||||||
|
- whether the state is authoritative or stale
|
||||||
|
|
||||||
|
Stopping must be explicit:
|
||||||
|
- `pause` means stop taking new work but keep process alive
|
||||||
|
- `drain` means finish in-flight work then stop cleanly
|
||||||
|
- `disarm` means remain live and observable but refuse side effects
|
||||||
|
|
||||||
|
This matters because the operator must be able to halt strategy or execution without destroying the whole pipeline.
|
||||||
|
|
||||||
|
## Logging requirements
|
||||||
|
All services use structured JSON logs.
|
||||||
|
|
||||||
|
Stable top-level fields:
|
||||||
|
- `level`
|
||||||
|
- `service`
|
||||||
|
- `component`
|
||||||
|
- `event`
|
||||||
|
- `namespace`
|
||||||
|
- `venue`
|
||||||
|
- `topic`
|
||||||
|
- `pair`
|
||||||
|
|
||||||
|
Additional body fields when relevant:
|
||||||
|
- `quote_id`
|
||||||
|
- `decision_id`
|
||||||
|
- `command_id`
|
||||||
|
- `execution_id`
|
||||||
|
- `inventory_id`
|
||||||
|
- `liquidity_action_id`
|
||||||
|
- `gross_edge_pct`
|
||||||
|
- `price_freshness_ms`
|
||||||
|
|
||||||
|
Log when:
|
||||||
|
- source connection is lost or reestablished
|
||||||
|
- price source becomes stale
|
||||||
|
- inventory state becomes stale or recovers
|
||||||
|
- funding action is requested, seen, credited, delayed, failed, or frozen
|
||||||
|
- strategy rejects with a meaningful reason
|
||||||
|
- strategy arms or disarms
|
||||||
|
- executor arms or disarms
|
||||||
|
- command is submitted
|
||||||
|
- venue rejects or times out
|
||||||
|
- PostgreSQL disconnects or recovers
|
||||||
|
- control API changes state
|
||||||
|
|
||||||
|
Do not log every healthy message by default.
|
||||||
|
|
||||||
|
## Failure and failover behavior
|
||||||
|
|
||||||
|
### Reference pricing
|
||||||
|
- Kraken failure with fresh CoinGecko:
|
||||||
|
- allowed only if fallback policy says yes
|
||||||
|
- decision records must note fallback source use
|
||||||
|
- both sources stale:
|
||||||
|
- strategy blocks all execution
|
||||||
|
|
||||||
|
### Inventory state
|
||||||
|
- missing or stale inventory state:
|
||||||
|
- strategy may still emit a non-actionable rejected decision
|
||||||
|
- strategy must not emit `cmd.execute_trade`
|
||||||
|
- pending funding action:
|
||||||
|
- remains non-spendable
|
||||||
|
- must be visible in control state and durable records
|
||||||
|
- treasury action service down:
|
||||||
|
- already funded trading may continue if inventory-sync is fresh
|
||||||
|
- new funding or withdrawal operations must be blocked visibly
|
||||||
|
|
||||||
|
### Persistence
|
||||||
|
- PostgreSQL unavailable:
|
||||||
|
- history-writer unhealthy
|
||||||
|
- system must surface that audit history is impaired
|
||||||
|
- if the user wants strict mode, strategy or executor may be blocked until persistence returns
|
||||||
|
|
||||||
|
### Execution
|
||||||
|
- timeout with unknown venue outcome:
|
||||||
|
- emit explicit uncertain result
|
||||||
|
- preserve idempotency state for recovery
|
||||||
|
- restart after partial submission:
|
||||||
|
- executor must not blindly resubmit without checking prior state
|
||||||
|
- executor sees lower real inventory than strategy snapshot:
|
||||||
|
- emit explicit failure result
|
||||||
|
- do not attempt fallback treasury movement
|
||||||
|
|
||||||
|
## Testing and validation plan
|
||||||
|
|
||||||
|
### Unit tests
|
||||||
|
- implied-rate calculation for both directions
|
||||||
|
- explicit EURe/EUR pricing-basis test
|
||||||
|
- threshold checks
|
||||||
|
- stale-price blocking
|
||||||
|
- insufficient-inventory blocking
|
||||||
|
- pending-deposit-not-spendable blocking
|
||||||
|
- command emission only when armed
|
||||||
|
- idempotency transitions in executor
|
||||||
|
|
||||||
|
### Integration tests
|
||||||
|
- pricing service emits canonical reference-price events
|
||||||
|
- inventory-sync emits credited vs pending inventory state correctly
|
||||||
|
- liquidity-manager records funding actions and status transitions
|
||||||
|
- strategy consumes demand, pricing, and inventory and emits decision plus command as expected
|
||||||
|
- history-writer persists linked records across topics
|
||||||
|
- executor publishes result records for success and failure paths
|
||||||
|
|
||||||
|
### Runtime validation in cluster
|
||||||
|
- inspect each service `/state`
|
||||||
|
- verify live reference pricing updates
|
||||||
|
- verify live internal inventory state
|
||||||
|
- verify one treasury funding path from request or deposit tracking to credited inventory
|
||||||
|
- verify strategy and executor start disarmed on a fresh deploy
|
||||||
|
- observe a rejected decision due to block condition
|
||||||
|
- arm strategy and executor
|
||||||
|
- keep the first live max notional tiny, on the order of a few EURe, before any larger cap
|
||||||
|
- observe one command emission
|
||||||
|
- observe one real Near Intents execution attempt
|
||||||
|
- verify PostgreSQL contains the full linked chain
|
||||||
|
|
||||||
|
## Deliberately rejected for this turn
|
||||||
|
- dashboards
|
||||||
|
- broad multi-pair abstractions
|
||||||
|
- ML training infrastructure
|
||||||
|
- broad backtest framework
|
||||||
|
- polished operator UI
|
||||||
|
- warehouse or lakehouse design
|
||||||
|
|
||||||
|
## Expected deliverables
|
||||||
|
- `market-reference-ingest`
|
||||||
|
- `inventory-sync`
|
||||||
|
- `liquidity-manager`
|
||||||
|
- `history-writer`
|
||||||
|
- `strategy-engine`
|
||||||
|
- `trade-executor`
|
||||||
|
- PostgreSQL schema and migrations or equivalent setup
|
||||||
|
- updated Kafka topic creation and config
|
||||||
|
- shared control API pattern for all long-running services
|
||||||
|
- tests for strategy, inventory, persistence, and executor behavior
|
||||||
|
- docs for operations, arm or disarm flow, and inspection commands
|
||||||
|
|
||||||
|
## Validation target
|
||||||
|
This turn is only complete when the deployed system can, through repo-controlled services alone, take one live active-pair quote, price it, verify credited internal inventory, decide on it, gate it, submit a real Near Intents execution attempt, and preserve the full record chain in PostgreSQL.
|
||||||
|
|
@ -0,0 +1,356 @@
|
||||||
|
# Implementation Proof: first non-mocked tradeable loop for one pair
|
||||||
|
|
||||||
|
Status: open
|
||||||
|
Opened: 2026-04-01
|
||||||
|
|
||||||
|
## Target outcome
|
||||||
|
The active turn is not complete when the repo merely observes demand or emits placeholder commands. It is complete only when the deployed system can make a real Near Intents trade attempt for the active BTC/Gnosis EURe pair using pre-funded inventory already credited inside NEAR Intents, with the full chain stored durably for later analytics and backtesting.
|
||||||
|
|
||||||
|
## Hypothesis
|
||||||
|
`unrip` becomes materially more real once one live NEAR swap-demand event can move through this full chain without mocks:
|
||||||
|
|
||||||
|
1. real quote and demand ingestion
|
||||||
|
2. live external reference pricing
|
||||||
|
3. synchronized spendable inventory state from NEAR Intents
|
||||||
|
4. auditable strategy decision
|
||||||
|
5. inventory and safety gating
|
||||||
|
6. real Near Intents execution attempt using credited internal inventory
|
||||||
|
7. durable storage of the full event chain
|
||||||
|
|
||||||
|
If any of those links is still dummy, manual-only, or opaque after the fact, the proof is not achieved.
|
||||||
|
|
||||||
|
## Active pair and trading rule
|
||||||
|
- Active pair:
|
||||||
|
- `nep141:btc.omft.near`
|
||||||
|
- `nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near`
|
||||||
|
- Both directions must be implemented in the same decision and execution pipeline:
|
||||||
|
- BTC -> EURe
|
||||||
|
- EURe -> BTC
|
||||||
|
- Runtime spendable inventory inside NEAR Intents decides which direction can actually fire.
|
||||||
|
- External Gnosis and BTC wallets are treasury and funding inputs, not the per-trade spend path.
|
||||||
|
- Pending deposits or withdrawals must not be treated as spendable inventory.
|
||||||
|
- Initial decision rule:
|
||||||
|
- use Kraken plus CoinGecko reference prices
|
||||||
|
- make the EURe/EUR pricing assumption explicit in code and decision records
|
||||||
|
- require a gross edge of at least 2%
|
||||||
|
- require fresh reference pricing
|
||||||
|
- require fresh internal inventory state
|
||||||
|
- require sufficient spendable source-asset inventory already credited inside NEAR Intents
|
||||||
|
- keep strategy and executor disarmed by default on deploy
|
||||||
|
- start with a very small max notional for the first live attempt
|
||||||
|
|
||||||
|
## Required services
|
||||||
|
|
||||||
|
### `near-intents-ingest`
|
||||||
|
Responsibilities:
|
||||||
|
- connect to the live NEAR Intents quote feed
|
||||||
|
- maintain the active pair filter
|
||||||
|
- publish raw venue quotes
|
||||||
|
- publish normalized swap-demand events
|
||||||
|
- expose current ingest state
|
||||||
|
|
||||||
|
Must not:
|
||||||
|
- make trading decisions
|
||||||
|
- execute trades
|
||||||
|
|
||||||
|
### `market-reference-ingest`
|
||||||
|
Responsibilities:
|
||||||
|
- maintain current reference pricing for BTC/EUR and mapped BTC/EURe fair value
|
||||||
|
- use Kraken as the primary fast path
|
||||||
|
- use CoinGecko as fallback or cross-check
|
||||||
|
- publish reference-price events
|
||||||
|
- expose latest price state, freshness, and source health
|
||||||
|
|
||||||
|
Must not:
|
||||||
|
- emit trade commands
|
||||||
|
|
||||||
|
### `inventory-sync`
|
||||||
|
Responsibilities:
|
||||||
|
- read current spendable inventory from the NEAR Intents internal ledger or equivalent supported inventory surface
|
||||||
|
- distinguish credited inventory from pending deposits and withdrawals
|
||||||
|
- publish or serve current internal inventory state
|
||||||
|
- expose freshness, last successful sync time, and reconciliation status
|
||||||
|
|
||||||
|
Must not:
|
||||||
|
- initiate treasury movements
|
||||||
|
- make trading decisions
|
||||||
|
|
||||||
|
### `liquidity-manager`
|
||||||
|
Responsibilities:
|
||||||
|
- request or manage deposit addresses for supported treasury assets and chains
|
||||||
|
- track funding actions from external treasury wallets into NEAR Intents
|
||||||
|
- track pending deposits, credited deposits, withdrawals, and rebalance actions
|
||||||
|
- expose the current funding and treasury state
|
||||||
|
- publish or record liquidity actions so they are auditable
|
||||||
|
|
||||||
|
Must not:
|
||||||
|
- execute trading strategy
|
||||||
|
- spend inventory on the trade hot path
|
||||||
|
|
||||||
|
### `history-writer`
|
||||||
|
Responsibilities:
|
||||||
|
- consume the core Kafka topics
|
||||||
|
- write append-only records into PostgreSQL
|
||||||
|
- preserve enough IDs and timestamps to reconstruct causality
|
||||||
|
- expose writer lag, last committed offsets, and write health
|
||||||
|
|
||||||
|
Must not:
|
||||||
|
- serve as the message bus
|
||||||
|
- make trading decisions
|
||||||
|
|
||||||
|
### `strategy-engine`
|
||||||
|
Responsibilities:
|
||||||
|
- consume normalized demand plus fresh reference prices
|
||||||
|
- consume fresh spendable inventory state
|
||||||
|
- compute implied rate from the quote
|
||||||
|
- compare quote rate against external reference pricing
|
||||||
|
- apply the initial 2% gross edge threshold
|
||||||
|
- apply freshness, arming, and notional checks
|
||||||
|
- apply inventory-aware direction gating
|
||||||
|
- emit auditable decision events
|
||||||
|
- emit `cmd.execute_trade` only when a decision is actionable
|
||||||
|
|
||||||
|
Must not:
|
||||||
|
- hold private keys
|
||||||
|
- execute venue actions directly
|
||||||
|
|
||||||
|
### `trade-executor`
|
||||||
|
Responsibilities:
|
||||||
|
- consume `cmd.execute_trade`
|
||||||
|
- load the correct Near Intents signing authority at runtime
|
||||||
|
- perform the actual Near Intents submission against pre-funded internal inventory
|
||||||
|
- preserve idempotency and duplicate suppression
|
||||||
|
- publish `exec.trade_result`
|
||||||
|
- expose current arm state, recent attempts, and last venue error
|
||||||
|
|
||||||
|
Must not:
|
||||||
|
- invent trading logic
|
||||||
|
- bypass Kafka and manual safety gates
|
||||||
|
- perform ad hoc bridging or treasury funding on the trade hot path
|
||||||
|
|
||||||
|
## Required durable storage
|
||||||
|
PostgreSQL is the first durable analytics and audit store.
|
||||||
|
|
||||||
|
It must store at least:
|
||||||
|
- raw quotes
|
||||||
|
- normalized demand
|
||||||
|
- reference-price snapshots
|
||||||
|
- internal inventory snapshots
|
||||||
|
- liquidity and funding actions
|
||||||
|
- strategy decisions
|
||||||
|
- trade commands
|
||||||
|
- execution results
|
||||||
|
- treasury status and reconciliation metadata
|
||||||
|
|
||||||
|
Why PostgreSQL first:
|
||||||
|
- fast enough for the current streaming volume
|
||||||
|
- queryable for inspection and analytics
|
||||||
|
- simple to operate in the current cluster
|
||||||
|
- good fit for append-only audit plus current-state views
|
||||||
|
|
||||||
|
Kafka remains the streaming backbone. PostgreSQL is not the hot transport path.
|
||||||
|
|
||||||
|
## Required control surface
|
||||||
|
Every long-running service in this turn must expose a small HTTP control surface. The point is not a polished UI. The point is operability.
|
||||||
|
|
||||||
|
At minimum every service must expose:
|
||||||
|
- `GET /healthz`
|
||||||
|
- `GET /state`
|
||||||
|
|
||||||
|
Services with runtime controls must also expose the relevant action endpoints:
|
||||||
|
|
||||||
|
### `near-intents-ingest`
|
||||||
|
- inspect:
|
||||||
|
- pair filter
|
||||||
|
- connection state
|
||||||
|
- frames received
|
||||||
|
- published counts
|
||||||
|
- control:
|
||||||
|
- update pair filter
|
||||||
|
- disable or reset pair filter
|
||||||
|
|
||||||
|
### `market-reference-ingest`
|
||||||
|
- inspect:
|
||||||
|
- latest Kraken price
|
||||||
|
- latest CoinGecko price
|
||||||
|
- derived fair price
|
||||||
|
- freshness age
|
||||||
|
- source health
|
||||||
|
- control:
|
||||||
|
- pause or resume polling or streaming
|
||||||
|
- trigger ad hoc refresh
|
||||||
|
|
||||||
|
### `inventory-sync`
|
||||||
|
- inspect:
|
||||||
|
- latest spendable balances by asset
|
||||||
|
- pending deposits and withdrawals
|
||||||
|
- last sync time
|
||||||
|
- reconciliation status
|
||||||
|
- control:
|
||||||
|
- trigger sync
|
||||||
|
- pause or resume sync
|
||||||
|
|
||||||
|
### `liquidity-manager`
|
||||||
|
- inspect:
|
||||||
|
- active deposit addresses
|
||||||
|
- recent funding actions
|
||||||
|
- pending deposits
|
||||||
|
- credited deposits
|
||||||
|
- recent withdrawals
|
||||||
|
- control:
|
||||||
|
- request or rotate deposit address where supported
|
||||||
|
- refresh treasury status
|
||||||
|
- pause or resume funding trackers
|
||||||
|
- disable withdrawals or rebalance actions
|
||||||
|
|
||||||
|
### `history-writer`
|
||||||
|
- inspect:
|
||||||
|
- last persisted offsets by topic
|
||||||
|
- last write time
|
||||||
|
- error count
|
||||||
|
- database connectivity
|
||||||
|
- control:
|
||||||
|
- pause writes
|
||||||
|
- resume writes
|
||||||
|
- drain and stop cleanly
|
||||||
|
|
||||||
|
### `strategy-engine`
|
||||||
|
- inspect:
|
||||||
|
- arm state
|
||||||
|
- active threshold
|
||||||
|
- latest reference snapshot used
|
||||||
|
- latest inventory snapshot used
|
||||||
|
- latest decisions and reasons
|
||||||
|
- skipped counts by reason
|
||||||
|
- control:
|
||||||
|
- arm or disarm
|
||||||
|
- pause or resume decisions
|
||||||
|
- change threshold
|
||||||
|
- set notional cap
|
||||||
|
|
||||||
|
### `trade-executor`
|
||||||
|
- inspect:
|
||||||
|
- arm state
|
||||||
|
- last command received
|
||||||
|
- last venue response
|
||||||
|
- last error
|
||||||
|
- in-flight and completed command counts
|
||||||
|
- control:
|
||||||
|
- arm or disarm execution
|
||||||
|
- pause consumption
|
||||||
|
- drain and stop cleanly
|
||||||
|
|
||||||
|
Stopping a service must mean a graceful stop or drain, not “kill the pod and hope.”
|
||||||
|
|
||||||
|
## Logging and observability requirements
|
||||||
|
All services must emit structured JSON logs with stable fields.
|
||||||
|
|
||||||
|
Stable fields:
|
||||||
|
- `level`
|
||||||
|
- `service`
|
||||||
|
- `component`
|
||||||
|
- `event`
|
||||||
|
- `namespace`
|
||||||
|
- `venue`
|
||||||
|
- `topic`
|
||||||
|
- `pair`
|
||||||
|
|
||||||
|
High-cardinality IDs belong in the body, not labels:
|
||||||
|
- `quote_id`
|
||||||
|
- `command_id`
|
||||||
|
- `decision_id`
|
||||||
|
- `execution_id`
|
||||||
|
|
||||||
|
What must be logged:
|
||||||
|
- connection loss and recovery
|
||||||
|
- source stale state
|
||||||
|
- inventory-sync stale state
|
||||||
|
- treasury funding action requested, credited, failed, or stuck
|
||||||
|
- invalid messages
|
||||||
|
- decision accepted and decision rejected with reason
|
||||||
|
- arm and disarm actions
|
||||||
|
- execution submitted, rejected, failed, recovered, completed
|
||||||
|
- PostgreSQL disconnect and recovery
|
||||||
|
- control API failures
|
||||||
|
|
||||||
|
What should not be logged:
|
||||||
|
- per-message noise without state change
|
||||||
|
- full payload spam by default
|
||||||
|
|
||||||
|
## Required edge cases and failure handling
|
||||||
|
- stale reference prices must block decisions
|
||||||
|
- stale or missing internal inventory state must block execution
|
||||||
|
- insufficient credited inventory must block the affected direction only
|
||||||
|
- pending deposits must not be counted as spendable inventory
|
||||||
|
- deposit address created but never funded must not be mistaken for liquidity
|
||||||
|
- external transfer observed but not yet credited in NEAR Intents must stay non-spendable
|
||||||
|
- credited inventory drift between inventory-sync and executor must hard-fail the command and publish a result
|
||||||
|
- Kraken down but CoinGecko healthy should degrade, not crash, if policy allows fallback
|
||||||
|
- both reference sources stale must block decisions
|
||||||
|
- PostgreSQL down must surface a hard health failure and stop claiming the system is trade-ready
|
||||||
|
- liquidity-manager failure must block new funding operations but must not silently stop already funded trading
|
||||||
|
- duplicate `cmd.execute_trade` must not cause duplicate venue submission
|
||||||
|
- executor restart after a partial submission must preserve idempotency behavior
|
||||||
|
- Near Intents API or RPC failures must become explicit `trade_result` failure records
|
||||||
|
- pair inactivity must not be mistaken for pipeline breakage
|
||||||
|
|
||||||
|
## Definition of done
|
||||||
|
- The deployed cluster is running all required services for this loop.
|
||||||
|
- Live Kraken and CoinGecko reference prices are flowing and inspectable.
|
||||||
|
- Spendable inventory inside NEAR Intents is flowing and inspectable.
|
||||||
|
- At least one treasury funding path is documented and verified:
|
||||||
|
- deposit address or supported funding path created
|
||||||
|
- funding action observed
|
||||||
|
- credited inventory visible in internal state
|
||||||
|
- PostgreSQL contains the full event chain for at least one real quote path.
|
||||||
|
- Strategy decisions are non-dummy and include explicit reason fields with edge, freshness, and inventory context.
|
||||||
|
- Both trade directions are implemented in the same decision and execution path.
|
||||||
|
- At least one direction can be armed based on available credited inventory.
|
||||||
|
- A real Near Intents execution attempt can be triggered through the repo-controlled path.
|
||||||
|
- The resulting venue response is captured durably and inspectably.
|
||||||
|
- Each service exposes the required health and state endpoints, and controlled pause or arm semantics where appropriate.
|
||||||
|
- Fresh deploys start disarmed by default and require explicit operator arming before side effects.
|
||||||
|
- Validation includes not only happy path but blocked-path evidence:
|
||||||
|
- stale reference blocked
|
||||||
|
- insufficient inventory blocked
|
||||||
|
- pending-deposit-not-spendable blocked
|
||||||
|
- disarmed executor blocked
|
||||||
|
|
||||||
|
## Current real
|
||||||
|
- Real NEAR Intents quote data is flowing into the cluster.
|
||||||
|
- The pair filter can be configured and changed at runtime.
|
||||||
|
- Raw and normalized events already exist in Redpanda topics.
|
||||||
|
- The app is deployable and observable in Kubernetes.
|
||||||
|
- Kafka already provides the event backbone.
|
||||||
|
|
||||||
|
## Current fake or incomplete
|
||||||
|
- Reference pricing does not exist yet.
|
||||||
|
- Spendable internal inventory sync does not exist yet.
|
||||||
|
- Treasury funding or deposit management does not exist yet.
|
||||||
|
- Strategy decisions are still dummy placeholders.
|
||||||
|
- Execution is still dummy.
|
||||||
|
- The current executor is not a real Near Intents adapter.
|
||||||
|
- PostgreSQL is not yet part of the loop.
|
||||||
|
- Most services do not yet have their own control surfaces.
|
||||||
|
- Real signing and treasury secret handling is not yet wired.
|
||||||
|
|
||||||
|
## Failure conditions
|
||||||
|
This proof fails if any of the following is true:
|
||||||
|
- a trade can only happen via manual shell steps or out-of-band operator intervention
|
||||||
|
- the system depends on ad hoc bridging or external-wallet spending on the trade hot path
|
||||||
|
- decision logic is still embedded in a dummy or placeholder service
|
||||||
|
- executor logic is still simulated
|
||||||
|
- a service cannot be inspected or paused cleanly at runtime
|
||||||
|
- the system cannot explain why a quote did or did not become a trade
|
||||||
|
- the stored history cannot reconstruct quote, pricing, decision, command, and result
|
||||||
|
- both directions are not implemented
|
||||||
|
- the system attempts to execute without credited internal source inventory
|
||||||
|
|
||||||
|
## Expected validation evidence
|
||||||
|
- live `ref.market_price` events and current state from the pricing control API
|
||||||
|
- live internal inventory state from the inventory-sync control API
|
||||||
|
- PostgreSQL rows linking quote, reference, inventory snapshot, decision, command, and execution result
|
||||||
|
- a blocked decision caused by stale price or insufficient inventory
|
||||||
|
- a funding action that becomes credited inventory
|
||||||
|
- a successful command emission from the strategy service with a recorded edge above 2%
|
||||||
|
- one real Near Intents execution attempt with stored request and response metadata
|
||||||
1
archive/research/.gitkeep
Normal file
1
archive/research/.gitkeep
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
14
research/ACTIVE.md
Normal file
14
research/ACTIVE.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Research Turn
|
||||||
|
|
||||||
|
Status: idle
|
||||||
|
|
||||||
|
No approved research turn is active yet.
|
||||||
|
|
||||||
|
When opening one, capture:
|
||||||
|
- charter
|
||||||
|
- hypothesis
|
||||||
|
- dataset or source of truth
|
||||||
|
- metrics
|
||||||
|
- assumptions
|
||||||
|
- falsification condition
|
||||||
|
- expected artifact paths under `research/experiments/`
|
||||||
9
research/QUEUE.md
Normal file
9
research/QUEUE.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Research Queue
|
||||||
|
|
||||||
|
This queue holds strategy and analysis questions that are not yet an active research turn.
|
||||||
|
|
||||||
|
## Candidate charters
|
||||||
|
- Determine which Kraken symbols and CoinGecko asset IDs give a trustworthy pricing basis for the active pair.
|
||||||
|
- Measure how often the active pair's implied rate diverges from external reference prices after a simple fee model.
|
||||||
|
- Test whether stale quotes correlate with worse downstream execution quality or higher reject rates.
|
||||||
|
- Import the local historical EUR/BTC data and decide how it should seed replay and backtesting.
|
||||||
43
scripts/workflow/add_backlog.py
Executable file
43
scripts/workflow/add_backlog.py
Executable file
|
|
@ -0,0 +1,43 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from common import BACKLOG_PATH, BACKLOG_SECTION, insert_into_section, load_text, next_backlog_id, save_text
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description="Append an item to BACKLOG.md.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--lane",
|
||||||
|
required=True,
|
||||||
|
choices=("implementation", "research", "ops", "bug"),
|
||||||
|
help="Backlog section to append to.",
|
||||||
|
)
|
||||||
|
parser.add_argument("--summary", required=True, help="One-line backlog summary.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--priority",
|
||||||
|
default="soon",
|
||||||
|
choices=("now", "soon", "later"),
|
||||||
|
help="Coarse urgency label.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--tags",
|
||||||
|
default="",
|
||||||
|
help="Comma-separated tags stored inline for grepability.",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
args = parse_args()
|
||||||
|
item_id = next_backlog_id(args.lane)
|
||||||
|
tags = f" tags={args.tags}" if args.tags else ""
|
||||||
|
entry = f"- [{item_id}] ({args.priority}) {args.summary}{tags}"
|
||||||
|
updated = insert_into_section(load_text(BACKLOG_PATH), BACKLOG_SECTION[args.lane], entry)
|
||||||
|
save_text(BACKLOG_PATH, updated)
|
||||||
|
print(item_id)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
150
scripts/workflow/close_turn.py
Executable file
150
scripts/workflow/close_turn.py
Executable file
|
|
@ -0,0 +1,150 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from common import (
|
||||||
|
ARCHIVE_PATH,
|
||||||
|
IMPLEMENTATION_ARCHIVE_DIR,
|
||||||
|
IMPLEMENTATION_PATH,
|
||||||
|
PROOF_PATH,
|
||||||
|
RESEARCH_ACTIVE_PATH,
|
||||||
|
RESEARCH_ARCHIVE_DIR,
|
||||||
|
append_archive_line,
|
||||||
|
git_commit,
|
||||||
|
load_text,
|
||||||
|
save_text,
|
||||||
|
slugify,
|
||||||
|
timestamp_slug,
|
||||||
|
today_iso,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description="Archive and close the current implementation or research turn.")
|
||||||
|
parser.add_argument("--lane", required=True, choices=("implementation", "research"))
|
||||||
|
parser.add_argument(
|
||||||
|
"--status",
|
||||||
|
required=True,
|
||||||
|
choices=("passed", "failed", "paused", "abandoned"),
|
||||||
|
help="Outcome of the turn.",
|
||||||
|
)
|
||||||
|
parser.add_argument("--summary", required=True, help="One-line closure summary.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--commit",
|
||||||
|
action="store_true",
|
||||||
|
help="Commit the archive change automatically.",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def title_from_content(content: str, prefix: str) -> str:
|
||||||
|
match = re.search(rf"^# {re.escape(prefix)}: (.+)$", content, flags=re.MULTILINE)
|
||||||
|
if not match:
|
||||||
|
raise SystemExit(f"could not find title for {prefix.lower()}")
|
||||||
|
return match.group(1).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def close_implementation_turn(status: str, summary: str) -> tuple[str, list[Path]]:
|
||||||
|
proof_content = load_text(PROOF_PATH)
|
||||||
|
implementation_content = load_text(IMPLEMENTATION_PATH)
|
||||||
|
title = title_from_content(proof_content, "Implementation Proof")
|
||||||
|
slug = slugify(title)
|
||||||
|
stamp = timestamp_slug()
|
||||||
|
|
||||||
|
IMPLEMENTATION_ARCHIVE_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
proof_archive = IMPLEMENTATION_ARCHIVE_DIR / f"{stamp}-{slug}-proof.md"
|
||||||
|
implementation_archive = IMPLEMENTATION_ARCHIVE_DIR / f"{stamp}-{slug}-implementation.md"
|
||||||
|
shutil.copyfile(PROOF_PATH, proof_archive)
|
||||||
|
shutil.copyfile(IMPLEMENTATION_PATH, implementation_archive)
|
||||||
|
|
||||||
|
save_text(
|
||||||
|
PROOF_PATH,
|
||||||
|
"""# Implementation Proof
|
||||||
|
|
||||||
|
Status: idle
|
||||||
|
|
||||||
|
No approved implementation proof is active yet.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
save_text(
|
||||||
|
IMPLEMENTATION_PATH,
|
||||||
|
"""# Implementation Turn
|
||||||
|
|
||||||
|
Status: idle
|
||||||
|
|
||||||
|
No approved implementation turn is active yet.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
append_archive_line(
|
||||||
|
"implementation",
|
||||||
|
f"- {today_iso()}: `{slug}` closed with status `{status}`. {summary}",
|
||||||
|
)
|
||||||
|
return title, [proof_archive, implementation_archive]
|
||||||
|
|
||||||
|
|
||||||
|
def close_research_turn(status: str, summary: str) -> tuple[str, list[Path]]:
|
||||||
|
research_content = load_text(RESEARCH_ACTIVE_PATH)
|
||||||
|
title = title_from_content(research_content, "Research Turn")
|
||||||
|
slug = slugify(title)
|
||||||
|
stamp = timestamp_slug()
|
||||||
|
|
||||||
|
RESEARCH_ARCHIVE_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
research_archive = RESEARCH_ARCHIVE_DIR / f"{stamp}-{slug}.md"
|
||||||
|
shutil.copyfile(RESEARCH_ACTIVE_PATH, research_archive)
|
||||||
|
|
||||||
|
save_text(
|
||||||
|
RESEARCH_ACTIVE_PATH,
|
||||||
|
"""# Research Turn
|
||||||
|
|
||||||
|
Status: idle
|
||||||
|
|
||||||
|
No approved research turn is active yet.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
append_archive_line(
|
||||||
|
"research",
|
||||||
|
f"- {today_iso()}: `{slug}` closed with status `{status}`. {summary}",
|
||||||
|
)
|
||||||
|
return title, [research_archive]
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
args = parse_args()
|
||||||
|
if args.lane == "implementation" and "Status: idle" in load_text(PROOF_PATH):
|
||||||
|
raise SystemExit("no active implementation turn to close")
|
||||||
|
if args.lane == "research" and "Status: idle" in load_text(RESEARCH_ACTIVE_PATH):
|
||||||
|
raise SystemExit("no active research turn to close")
|
||||||
|
|
||||||
|
if args.lane == "implementation":
|
||||||
|
title, archived_paths = close_implementation_turn(args.status, args.summary)
|
||||||
|
else:
|
||||||
|
title, archived_paths = close_research_turn(args.status, args.summary)
|
||||||
|
|
||||||
|
if args.commit:
|
||||||
|
paths = [ARCHIVE_PATH]
|
||||||
|
if args.lane == "implementation":
|
||||||
|
paths.extend([PROOF_PATH, IMPLEMENTATION_PATH])
|
||||||
|
else:
|
||||||
|
paths.append(RESEARCH_ACTIVE_PATH)
|
||||||
|
paths.extend(archived_paths)
|
||||||
|
git_commit(
|
||||||
|
f"""Archive {args.lane} turn: {title}
|
||||||
|
|
||||||
|
Proof: Preserve the completed {args.lane} 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.""",
|
||||||
|
paths=paths,
|
||||||
|
)
|
||||||
|
|
||||||
|
for archived_path in archived_paths:
|
||||||
|
print(archived_path.relative_to(RESEARCH_ACTIVE_PATH.parent.parent))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
154
scripts/workflow/common.py
Executable file
154
scripts/workflow/common.py
Executable file
|
|
@ -0,0 +1,154 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT_DIR = Path(__file__).resolve().parents[2]
|
||||||
|
BACKLOG_PATH = ROOT_DIR / "BACKLOG.md"
|
||||||
|
ARCHIVE_PATH = ROOT_DIR / "ARCHIVE.md"
|
||||||
|
PROOF_PATH = ROOT_DIR / "PROOF.md"
|
||||||
|
IMPLEMENTATION_PATH = ROOT_DIR / "IMPLEMENTATION.md"
|
||||||
|
RESEARCH_ACTIVE_PATH = ROOT_DIR / "research/ACTIVE.md"
|
||||||
|
IMPLEMENTATION_ARCHIVE_DIR = ROOT_DIR / "archive/implementation"
|
||||||
|
RESEARCH_ARCHIVE_DIR = ROOT_DIR / "archive/research"
|
||||||
|
|
||||||
|
LANE_PREFIX = {
|
||||||
|
"implementation": "I",
|
||||||
|
"research": "R",
|
||||||
|
"ops": "O",
|
||||||
|
"bug": "B",
|
||||||
|
}
|
||||||
|
|
||||||
|
BACKLOG_SECTION = {
|
||||||
|
"implementation": "## Implementation Candidates",
|
||||||
|
"research": "## Research Candidates",
|
||||||
|
"ops": "## Ops Candidates",
|
||||||
|
"bug": "## Bugs",
|
||||||
|
}
|
||||||
|
|
||||||
|
ARCHIVE_SECTION = {
|
||||||
|
"implementation": "## Implementation Turns",
|
||||||
|
"research": "## Research Turns",
|
||||||
|
"planning": "## Planning Events",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def now_utc() -> datetime:
|
||||||
|
return datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
|
def today_iso() -> str:
|
||||||
|
return now_utc().date().isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
def timestamp_slug() -> str:
|
||||||
|
return now_utc().strftime("%Y%m%dT%H%M%SZ")
|
||||||
|
|
||||||
|
|
||||||
|
def slugify(value: str) -> str:
|
||||||
|
slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
|
||||||
|
return slug or "turn"
|
||||||
|
|
||||||
|
|
||||||
|
def load_text(path: Path) -> str:
|
||||||
|
return path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def save_text(path: Path, content: str) -> None:
|
||||||
|
path.write_text(content.rstrip() + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def insert_into_section(document: str, heading: str, block: str) -> str:
|
||||||
|
lines = document.splitlines()
|
||||||
|
try:
|
||||||
|
start = lines.index(heading)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise SystemExit(f"heading not found: {heading}") from exc
|
||||||
|
|
||||||
|
end = len(lines)
|
||||||
|
for idx in range(start + 1, len(lines)):
|
||||||
|
if lines[idx].startswith("## "):
|
||||||
|
end = idx
|
||||||
|
break
|
||||||
|
|
||||||
|
insert_at = end
|
||||||
|
return "\n".join(lines[:insert_at] + [block] + lines[insert_at:]) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def append_archive_line(section: str, line: str) -> None:
|
||||||
|
content = load_text(ARCHIVE_PATH)
|
||||||
|
updated = insert_into_section(content, ARCHIVE_SECTION[section], line)
|
||||||
|
save_text(ARCHIVE_PATH, updated)
|
||||||
|
|
||||||
|
|
||||||
|
def read_backlog_lines() -> list[str]:
|
||||||
|
return load_text(BACKLOG_PATH).splitlines()
|
||||||
|
|
||||||
|
|
||||||
|
def write_backlog_lines(lines: list[str]) -> None:
|
||||||
|
save_text(BACKLOG_PATH, "\n".join(lines))
|
||||||
|
|
||||||
|
|
||||||
|
def next_backlog_id(lane: str) -> str:
|
||||||
|
prefix = LANE_PREFIX[lane]
|
||||||
|
pattern = re.compile(rf"\[{re.escape(prefix)}(\d+)\]")
|
||||||
|
highest = 0
|
||||||
|
for line in read_backlog_lines():
|
||||||
|
match = pattern.search(line)
|
||||||
|
if match:
|
||||||
|
highest = max(highest, int(match.group(1)))
|
||||||
|
return f"{prefix}{highest + 1:03d}"
|
||||||
|
|
||||||
|
|
||||||
|
def backlog_entry_map() -> dict[str, str]:
|
||||||
|
entries: dict[str, str] = {}
|
||||||
|
pattern = re.compile(r"- \[([A-Z]\d+)\] (.+)")
|
||||||
|
for line in read_backlog_lines():
|
||||||
|
match = pattern.match(line)
|
||||||
|
if match:
|
||||||
|
entries[match.group(1)] = match.group(2)
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
def remove_backlog_ids(ids: list[str]) -> None:
|
||||||
|
id_set = set(ids)
|
||||||
|
pattern = re.compile(r"- \[([A-Z]\d+)\] ")
|
||||||
|
kept: list[str] = []
|
||||||
|
for line in read_backlog_lines():
|
||||||
|
match = pattern.match(line)
|
||||||
|
if match and match.group(1) in id_set:
|
||||||
|
continue
|
||||||
|
kept.append(line)
|
||||||
|
write_backlog_lines(kept)
|
||||||
|
|
||||||
|
|
||||||
|
def git_has_changes() -> bool:
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "-C", str(ROOT_DIR), "status", "--porcelain"],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
return bool(result.stdout.strip())
|
||||||
|
|
||||||
|
|
||||||
|
def path_has_changes(paths: list[Path]) -> bool:
|
||||||
|
rel_paths = [str(path.relative_to(ROOT_DIR)) for path in paths]
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "-C", str(ROOT_DIR), "status", "--porcelain", "--", *rel_paths],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
return bool(result.stdout.strip())
|
||||||
|
|
||||||
|
|
||||||
|
def git_commit(message: str, paths: list[Path]) -> None:
|
||||||
|
if not path_has_changes(paths):
|
||||||
|
return
|
||||||
|
rel_paths = [str(path.relative_to(ROOT_DIR)) for path in paths]
|
||||||
|
subprocess.run(["git", "-C", str(ROOT_DIR), "add", "--", *rel_paths], check=True)
|
||||||
|
subprocess.run(["git", "-C", str(ROOT_DIR), "commit", "-m", message], check=True)
|
||||||
9
scripts/workflow/install_hooks.sh
Executable file
9
scripts/workflow/install_hooks.sh
Executable file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||||
|
|
||||||
|
chmod +x "$ROOT_DIR/.githooks/commit-msg"
|
||||||
|
git -C "$ROOT_DIR" config core.hooksPath .githooks
|
||||||
|
|
||||||
|
echo "Installed tracked git hooks for $ROOT_DIR"
|
||||||
176
scripts/workflow/open_turn.py
Executable file
176
scripts/workflow/open_turn.py
Executable file
|
|
@ -0,0 +1,176 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from common import (
|
||||||
|
BACKLOG_PATH,
|
||||||
|
ARCHIVE_PATH,
|
||||||
|
IMPLEMENTATION_PATH,
|
||||||
|
PROOF_PATH,
|
||||||
|
RESEARCH_ACTIVE_PATH,
|
||||||
|
append_archive_line,
|
||||||
|
backlog_entry_map,
|
||||||
|
git_commit,
|
||||||
|
remove_backlog_ids,
|
||||||
|
save_text,
|
||||||
|
slugify,
|
||||||
|
today_iso,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description="Open a new implementation or research turn.")
|
||||||
|
parser.add_argument("--lane", required=True, choices=("implementation", "research"))
|
||||||
|
parser.add_argument("--title", required=True, help="Turn title.")
|
||||||
|
parser.add_argument("--summary", required=True, help="One-line proof or charter summary.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--pick",
|
||||||
|
action="append",
|
||||||
|
default=[],
|
||||||
|
help="Backlog ID to pull into the turn. Repeat as needed.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--commit",
|
||||||
|
action="store_true",
|
||||||
|
help="Commit the planning change automatically.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--force",
|
||||||
|
action="store_true",
|
||||||
|
help="Replace an already-open turn instead of refusing.",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def render_selected(ids: list[str], items: dict[str, str]) -> str:
|
||||||
|
if not ids:
|
||||||
|
return "- none selected"
|
||||||
|
return "\n".join(f"- [{item_id}] {items[item_id]}" for item_id in ids)
|
||||||
|
|
||||||
|
|
||||||
|
def open_implementation_turn(title: str, summary: str, selected: str) -> None:
|
||||||
|
opened = today_iso()
|
||||||
|
save_text(
|
||||||
|
PROOF_PATH,
|
||||||
|
f"""# Implementation Proof: {title}
|
||||||
|
|
||||||
|
Status: open
|
||||||
|
Opened: {opened}
|
||||||
|
|
||||||
|
## Hypothesis
|
||||||
|
{summary}
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
{selected}
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
- unchanged from `THESIS.md` unless the user approves otherwise
|
||||||
|
|
||||||
|
## Definition of done
|
||||||
|
- current turn implementation is validated with direct evidence
|
||||||
|
- remaining fakes are listed plainly
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
save_text(
|
||||||
|
IMPLEMENTATION_PATH,
|
||||||
|
f"""# Implementation Turn: {title}
|
||||||
|
|
||||||
|
Status: open
|
||||||
|
Opened: {opened}
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
{summary}
|
||||||
|
|
||||||
|
## Selected backlog items
|
||||||
|
{selected}
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Fill in the concrete implementation plan before coding if the live plan no longer matches the turn.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def open_research_turn(title: str, summary: str, selected: str) -> None:
|
||||||
|
opened = today_iso()
|
||||||
|
save_text(
|
||||||
|
RESEARCH_ACTIVE_PATH,
|
||||||
|
f"""# Research Turn: {title}
|
||||||
|
|
||||||
|
Status: open
|
||||||
|
Opened: {opened}
|
||||||
|
|
||||||
|
## Charter
|
||||||
|
{summary}
|
||||||
|
|
||||||
|
## Selected backlog items
|
||||||
|
{selected}
|
||||||
|
|
||||||
|
## Hypothesis
|
||||||
|
TBD by the approved research turn.
|
||||||
|
|
||||||
|
## Dataset or source of truth
|
||||||
|
TBD by the approved research turn.
|
||||||
|
|
||||||
|
## Metrics
|
||||||
|
TBD by the approved research turn.
|
||||||
|
|
||||||
|
## Assumptions
|
||||||
|
TBD by the approved research turn.
|
||||||
|
|
||||||
|
## Falsification condition
|
||||||
|
TBD by the approved research turn.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
args = parse_args()
|
||||||
|
if args.lane == "implementation":
|
||||||
|
if "Status: open" in PROOF_PATH.read_text(encoding="utf-8") and not args.force:
|
||||||
|
raise SystemExit("implementation turn already open; close it first or pass --force")
|
||||||
|
else:
|
||||||
|
if "Status: open" in RESEARCH_ACTIVE_PATH.read_text(encoding="utf-8") and not args.force:
|
||||||
|
raise SystemExit("research turn already open; close it first or pass --force")
|
||||||
|
|
||||||
|
entries = backlog_entry_map()
|
||||||
|
missing = [item_id for item_id in args.pick if item_id not in entries]
|
||||||
|
if missing:
|
||||||
|
raise SystemExit(f"unknown backlog IDs: {', '.join(missing)}")
|
||||||
|
|
||||||
|
selected = render_selected(args.pick, entries)
|
||||||
|
|
||||||
|
if args.lane == "implementation":
|
||||||
|
open_implementation_turn(args.title, args.summary, selected)
|
||||||
|
else:
|
||||||
|
open_research_turn(args.title, args.summary, selected)
|
||||||
|
|
||||||
|
if args.pick:
|
||||||
|
remove_backlog_ids(args.pick)
|
||||||
|
|
||||||
|
append_archive_line(
|
||||||
|
"planning",
|
||||||
|
(
|
||||||
|
f"- {today_iso()}: opened {args.lane} turn `{slugify(args.title)}` "
|
||||||
|
f"from backlog items {', '.join(args.pick) if args.pick else 'none'}."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if args.commit:
|
||||||
|
commit_paths = [BACKLOG_PATH, ARCHIVE_PATH]
|
||||||
|
if args.lane == "implementation":
|
||||||
|
commit_paths.extend([PROOF_PATH, IMPLEMENTATION_PATH])
|
||||||
|
else:
|
||||||
|
commit_paths.append(RESEARCH_ACTIVE_PATH)
|
||||||
|
git_commit(
|
||||||
|
f"""Open {args.lane} turn: {args.title}
|
||||||
|
|
||||||
|
Proof: Establish the approved {args.lane} turn and move selected backlog items into active scope.
|
||||||
|
Assumptions: The selected backlog items are the approved scope for this turn.
|
||||||
|
Still fake: Opening a turn changes planning state only; the work itself is not implemented yet.""",
|
||||||
|
paths=commit_paths,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
37
scripts/workflow/review_diff.sh
Executable file
37
scripts/workflow/review_diff.sh
Executable file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||||
|
PROMPT_FILE="$ROOT_DIR/workflow/REVIEW_PROMPT.md"
|
||||||
|
RANGE="${1:-HEAD~1}"
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "--help" ]]; then
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage: bash scripts/workflow/review_diff.sh [git-diff-range]
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
bash scripts/workflow/review_diff.sh HEAD~1
|
||||||
|
bash scripts/workflow/review_diff.sh main...HEAD
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
# Review Bundle
|
||||||
|
|
||||||
|
## Diff range
|
||||||
|
\`$RANGE\`
|
||||||
|
|
||||||
|
## Diff
|
||||||
|
\`\`\`diff
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git -C "$ROOT_DIR" diff "$RANGE"
|
||||||
|
|
||||||
|
cat <<'EOF'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prompt
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat "$PROMPT_FILE"
|
||||||
28
workflow/REVIEW_PROMPT.md
Normal file
28
workflow/REVIEW_PROMPT.md
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Adversarial review prompt
|
||||||
|
|
||||||
|
You are reviewing changes for a trading system.
|
||||||
|
|
||||||
|
Your job is to attack the work, not to praise it.
|
||||||
|
|
||||||
|
Look for:
|
||||||
|
|
||||||
|
1. Fake progress.
|
||||||
|
- Did the change make the system more real, or only more elaborate?
|
||||||
|
- Real means contact with live data, validated persistence, verified replay, or explicit evidence.
|
||||||
|
2. Smuggled scope.
|
||||||
|
- Did the diff stay inside the active proof or research charter?
|
||||||
|
3. Unstated assumptions.
|
||||||
|
- Prices, fees, latency, freshness, slippage, API guarantees, retention, and failure modes.
|
||||||
|
4. Placeholder dressed as real.
|
||||||
|
- Hardcoded values, mocks, TODOs, dead branches, or unverifiable claims.
|
||||||
|
5. Missing failure handling.
|
||||||
|
- Focus on real trading-system failures, not abstract defensive-programming trivia.
|
||||||
|
|
||||||
|
For every issue:
|
||||||
|
- state what it is
|
||||||
|
- state why it matters for this system
|
||||||
|
- state whether it should be fixed now, removed, or marked as still fake
|
||||||
|
|
||||||
|
Do not praise style.
|
||||||
|
Do not suggest adjacent features.
|
||||||
|
Do not expand scope.
|
||||||
Loading…
Add table
Reference in a new issue