Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ Severity tags:
- `[MUST]` Avoid unnecessary indirection layers (`executor`, `worker`, `engine`, etc.).
- `[MUST]` Keep one distinct responsibility per file; merge pass-through modules into functions.
- `[MUST]` Eliminate duplicated logic blocks (>10 lines) by extracting shared helpers.
- `[MUST]` Do not flatten typed `SignerError` / Coinset / Dexie outcomes to strings and re-parse
them for control flow. Retryability and 404/missing are variant matches (ADR 0027).
- `[MUST]` Use allowlists for state checks; never rely on negated blocklists.
- `[MUST]` For similar polling loops, match existing interval/accumulator style; warning cadence
must be additive (`next_warning += warning_interval`). See `docs/plan.md` → **Delivery
Expand Down
4 changes: 4 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ _Avoid_: Unique pin helpers, binding exclusion set, Direct coin picker
**Remaining-shape ownership**:
Who finishes ladder shape after the primary row is on-chain — offer-post bootstrap versus daemon coin ops — including the inverse handoff when a low-watermark split still belongs to bootstrap combine-first.
_Avoid_: Shape deferral, sub-primary policy, bootstrap vs coin-ops bounce

**Exact-denomination combine**:
Managed and CLI combine that spends only coins of the same clip size. Shape combine-first may cover a target with mixed sizes.
_Avoid_: mixed-denomination cover (for the managed/CLI path)
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ greenfloor-manager vault-asset-trace --asset wUSDC.b
# Split one coin into target denominations (waits through signature + mempool + confirmation + reorg watch)
greenfloor-manager coin-split --pair TDBX:txch --coin-id <coin-id> --amount-per-coin 1000 --number-of-coins 10

# Combine small coins into one larger coin (waits through signature + mempool + confirmation + reorg watch)
greenfloor-manager coin-combine --pair TDBX:txch --input-coin-count 10 --asset-id xch
# Combine same-denomination coins (waits through signature + mempool + confirmation + reorg watch)
greenfloor-manager coin-combine --pair TDBX:txch --input-coin-count 5 --asset-id xch
```

Coin-op wait diagnostics include:
Expand All @@ -118,6 +118,8 @@ Run the daemon:
greenfloord --program-config config/program.yaml --markets-config config/markets.yaml --once
```

`--once` exit codes: [runbook](docs/runbook.md) §6.

## Local dev tooling

Post-Python-cutover local development uses three runtimes. None of them replace
Expand Down
35 changes: 20 additions & 15 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,26 @@ accepted decision** when onboarding.

## Current (operator + engine)

| ADR | Topic |
| ---------------------------------------------------------------- | ----------------------------------------------------------------------- |
| [0026](decisions/0026-combine-dust-remainder-coin.md) | **Combine dust remainder** — extra input absorbs CAT change; keep 1 CAT |
| [0025](decisions/0025-two-sided-target-spread.md) | **Two-sided spread** — bid/ask around mid; sell-only omits the field |
| [0023](decisions/0023-canonical-cat-outer-puzzle-hash.md) | **CAT outer hash** — one `coinset/cats/outer` primitive + Coinset hex |
| [0021](decisions/0021-three-ownership-simplifications.md) | **Ownership spines** — expired maker, reconcile prep, `coin_ops::shape` |
| [0020](decisions/0020-soft-expiry-stable-makers.md) | **Soft listing expiry** — stable makers, `ensure_size_n_offer` |
| [0018](decisions/0018-coinset-parse-decomposition.md) | **Coinset submodule layout** — parse, pagination, rpc_result, json_util |
| [0017](decisions/0017-offer-submodule-decompositions.md) | **Offer submodule layout** — bootstrap planner/phase + presplit |
| [0015](decisions/0015-on-chain-offer-cancel.md) | **On-chain offer cancel** — reclaim spend, `cancel_submitted` lifecycle |
| [0014](decisions/0014-offer-publish-module-decomposition.md) | **Offer publish decomposition** — bootstrap gate + publish assets |
| [0013](decisions/0013-rust-cli-daemon-native-cutover.md) | **Native Rust CLI/daemon** — production operator path |
| [0010](decisions/0010-rust-engine-crate-naming.md) | Crate and module naming (`greenfloor-engine`, `greenfloor_engine`) |
| [0007](decisions/0007-rust-signer-and-coinset-io.md) | Vault KMS signing and Coinset IO in Rust |
| [0003](decisions/0003-parallel-offer-reservation-coordinator.md) | Parallel managed-post reservation leases |
| ADR | Topic |
| ------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| [0027](decisions/0027-typed-operator-outcomes.md) | **Typed outcomes** — domain `SignerError`; no flatten-then-reparse |
| [0026](decisions/0026-combine-dust-remainder-coin.md) | **Combine** — dust remainder on covering shape; exact-denom managed/CLI |
| [0025](decisions/0025-two-sided-target-spread.md) | **Two-sided spread** — bid/ask around mid; sell-only omits the field |
| [0024](decisions/0024-coin-ops-effective-counts-and-buffer-raid.md) | **Effective counts** — free inventory vs makers; buffer-raid splits |
| [0023](decisions/0023-canonical-cat-outer-puzzle-hash.md) | **CAT outer hash** — one `coinset/cats/outer` primitive + Coinset hex |
| [0022](decisions/0022-unique-maker-coins.md) | **Unique Direct makers** — one exact-size receive CAT per open Direct offer |
| [0021](decisions/0021-three-ownership-simplifications.md) | **Ownership spines** — expired maker, reconcile prep, `coin_ops::shape` |
| [0020](decisions/0020-soft-expiry-stable-makers.md) | **Soft listing expiry** — stable makers, `ensure_size_n_offer` |
| [0019](decisions/0019-coinset-ws-local-watches.md) | **Local watches** — Coinset WS p2/coin-id; default publish venue `coinset` |
| [0018](decisions/0018-coinset-parse-decomposition.md) | **Coinset submodule layout** — parse, pagination, rpc_result, json_util |
| [0017](decisions/0017-offer-submodule-decompositions.md) | **Offer submodule layout** — bootstrap planner/phase + presplit |
| [0016](decisions/0016-sqlite-persistence-coverage-policy.md) | **SQLite coverage** — exclude `storage/sqlite/` from llvm-cov/diff-cover |
| [0015](decisions/0015-on-chain-offer-cancel.md) | **On-chain offer cancel** — reclaim spend, `cancel_submitted` lifecycle |
| [0014](decisions/0014-offer-publish-module-decomposition.md) | **Offer publish decomposition** — bootstrap gate + publish assets |
| [0013](decisions/0013-rust-cli-daemon-native-cutover.md) | **Native Rust CLI/daemon** — production operator path |
| [0010](decisions/0010-rust-engine-crate-naming.md) | Crate and module naming (`greenfloor-engine`, `greenfloor_engine`) |
| [0007](decisions/0007-rust-signer-and-coinset-io.md) | Vault KMS signing and Coinset IO in Rust |
| [0003](decisions/0003-parallel-offer-reservation-coordinator.md) | Parallel managed-post reservation leases |

## Superseded ADRs (removed from tree; see git history)

Expand Down
2 changes: 2 additions & 0 deletions docs/decisions/0021-three-ownership-simplifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ Behavior-preserving ownership moves only (no policy changes).
ladder protect flag) rather than silently merging selectors.
- Daemon remains phase orchestrator (not a thin shell); path-specific gates, fee budgets,
and dust Preselected submit stay path-local.
- CLI apply later lives on `WatchedOfferReconciler::fetch_and_apply` (the 0021 helper
was renamed).

## Non-goals

Expand Down
14 changes: 12 additions & 2 deletions docs/decisions/0026-combine-dust-remainder-coin.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Status

Accepted (2026-08-12).
Accepted (2026-08-12; managed/CLI exact-denomination added 2026-08-13).

## Context

Expand All @@ -16,6 +16,9 @@ Lowering the dust floor to 0.1 CAT would allow the 100-mojo case but still block
20-mojo (size-10) and 50-mojo (single size-25) remainders, and would mint awkward
dust clips.

Managed/CLI combine separately covered a target with mixed coin sizes (`TargetCover`),
which hid denomination gaps and minted awkward clips.

## Decision

1. **Keep the 1 CAT dust floor.** `coin_op_min_amount_mojos` stays 1,000 for CATs.
Expand All @@ -25,10 +28,17 @@ dust clips.
remainder coin — or on a different pair whose change is already legal.
3. **Fail closed** when no covering set within `combine_input_cap` leaves legal
change. Do not emit sub-CAT outputs.
4. **Managed/CLI combine is exact-denomination.** `coin-combine` and managed combine
plans spend only coins whose amount equals the target clip, capped by YAML
`coin_ops.combine_input_coin_cap` (default 5, min 2). Shape combine-first still
covers a target and may mix sizes. No env-var cap.

## Consequences

- Two 25.025 clips plus a 4.930 remainder can fund two 24.975 buy clips (change 5.030).
- Two 25.025 clips alone still cannot fund that target.
- Daemon flat combine (no dust context) is unchanged: a solo covering pick is still
- Shape combine-first without dust context is unchanged: a solo covering pick is still
"not a combine."
- Managed/CLI combine skips with `no_spendable_combine_coin_available` instead of
covering a target with unrelated sizes. `--input-coin-count` is
`min(requested, combine_input_coin_cap)`.
31 changes: 31 additions & 0 deletions docs/decisions/0027-typed-operator-outcomes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# ADR 0027: Typed operator outcomes

## Status

Accepted (2026-08-13).

## Context

Operator control flow flattened typed Coinset, Dexie, and driver failures to strings,
then re-parsed them (retry marker lists, mixed-split normalize, forged Dexie
`success: false` JSON). That hid the real 404 / RPC / transport boundary and made
retryability a substring match.

## Decision

1. **`SignerError` by domain.** Variants wrap `Vault`, `CoinOps`, `Offer`, `Transport`,
`Persistence`, `Config`, and `Reconcile`. Do not flatten those to strings and
re-parse them for control flow.
2. **Typed transport.** `TransportError::from_reqwest` maps timeout, connect, decode,
request, and `HttpStatus`. `TransportError::Coinset(String)` is Coinset RPC
`success: false` only. Retryability is `is_retryable_upstream()` (variant match).
Mixed-split unspendable coins map at the driver boundary
(`VaultError::MixedSplitSelectedCoinsNotSpendable`).
3. **Typed Dexie.** HTTP status and visibility stay `SignerError` until the operator
JSON edge. `get_offer` has one 404 boundary. `DexieOfferFetch` is `Found`, `Missing`,
or `Mismatch`; Found requires a nested `offer` whose id matches the request.

## Consequences

- Reconcile Found fixtures nest `{ "offer": { ... } }` with a matching id.
- CLI retry/fallback reads `err.is_retryable_upstream()`, not marker lists.
5 changes: 3 additions & 2 deletions docs/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ adapter unit tests ──► greenfloor_scripts/ → engine + manager CLIs
`CatSelection::Preselected`.
- **Shared shape planning:** `coin_ops::shape` (deficit → combine-first / single-coin
funding → output amounts). Bootstrap planner and managed auto-split are thin wrappers;
`plan_coin_ops` remains the batch count/fee scheduler. See ADR 0021.
`plan_coin_ops` remains the batch count/fee scheduler. See ADR 0021. Combine policy is
ADR 0026; typed operator outcomes are ADR 0027.
- **Expired / surplus makers:** `offer::lifecycle::expired_maker` (soft-mark, CAS lease,
surplus reclaim plan). Daemon `soft_expire_phase` is a thin adapter; cancel stays
separate (ADR 0015).
Expand All @@ -56,7 +57,7 @@ Core trading/runtime (V1):
2. `config-validate` — validate program + markets YAML
3. `doctor` — readiness check (config, keys, DB, env overrides)
4. `keys-onboard` — key selection and onboarding state
5. `build-and-post-offer` — vault KMS offer build + Dexie/Splash publish
5. `build-and-post-offer` — vault KMS offer build + publish (`coinset` default; Dexie/Splash opt-in)
6. `offers-status` — offer states and recent audit events
7. `offers-reconcile` — refresh states from venue + Coinset tx signals
8. `offers-cancel` — on-chain cancel by offer id or `--cancel-open` (Dexie fetch + Coinset submit)
Expand Down
5 changes: 5 additions & 0 deletions docs/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ Pre-Rust migration detail lives in git history and

## Milestones

### 2026-08-13 — Typed operator outcomes (ADR 0027)

[#202](https://github.com/hoffmang9/greenfloor/pull/202). Typed Coinset/Dexie/`SignerError`
outcomes: ADR 0027. Exact-denomination managed combine: ADR 0026.

### 2026-08-12 — Combine dusty overshoot takes a remainder coin (ADR 0026)

When a covering combine would leave CAT dust change, the shaper retries while skipping
Expand Down
4 changes: 3 additions & 1 deletion docs/runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Optional developer bootstrap for testnet markets:
- **CAT dust via Coinset (enabled markets):** Sub-unit CAT outputs (strictly below **1000** mojos per coin) can be merged with the direct Coinset signer path, one batch per `keys.registry` signer used on the market rows. From the repo root: `PATH="$(pwd)/.venv/bin:$PATH" greenfloor-manager combine-market-cat-dust --program-config ~/.greenfloor/config/program.yaml --markets-config ~/.greenfloor/config/markets.yaml --json`. Prefer `--dry-run` or `--list-only` before live combines; `--cat-asset-id <hex>` scopes a single asset; optional `--testnet-markets-config` matches daemon overlay semantics.
- Shape denominations for the selected market context:
- Split: `greenfloor-manager coin-split --pair TDBX:txch --coin-id <coin-id> --amount-per-coin 1000 --number-of-coins 10`
- Combine: `greenfloor-manager coin-combine --pair TDBX:txch --input-coin-count 10 --asset-id xch`
- Combine: `greenfloor-manager coin-combine --pair TDBX:txch --input-coin-count 5 --asset-id xch`
- Config-driven shaping (from market `ladders.sell`): `greenfloor-manager coin-split --pair TDBX:txch --size-base-units 10`
- Config-driven combine threshold (from market `ladders.sell`): `greenfloor-manager coin-combine --pair TDBX:txch --size-base-units 10`
- Optional venue context annotation for prep commands: add `--venue dexie` or `--venue splash` (coin-prep works without it).
Expand Down Expand Up @@ -269,6 +269,7 @@ Monitor `audit_event` records in `~/.greenfloor/db/greenfloor.sqlite`:
- For `testnet11`, do not route to mainnet Coinset endpoint unless you explicitly set `GREENFLOOR_ALLOW_MAINNET_COINSET_FOR_TESTNET11=1` for temporary debugging.
- Coin combine input cap (manager/daemon coin-op execution; `~/.greenfloor/config/program.yaml` -> `coin_ops`):
- `combine_input_coin_cap` (default: `5`, min `2`)
- Managed/CLI combine is exact-denomination only. `--input-coin-count` is `min(requested, combine_input_coin_cap)`.
- Daemon tx-signal ingestion controls (`~/.greenfloor/config/program.yaml` -> `chain_signals.tx_block_trigger`):
- `mode`: must be `websocket`
- `websocket_url`: Coinset websocket endpoint (defaults by network when blank)
Expand All @@ -283,6 +284,7 @@ Monitor `audit_event` records in `~/.greenfloor/db/greenfloor.sqlite`:
- Daemon singleton lock behavior:
- daemon loop and `--once` both require exclusive lock on `state_dir/daemon.lock`.
- if lock is held, process exits with `daemon_lock_conflict` event and non-zero exit code.
- `--once` also exits 1 when any preamble or cycle error occurred, or when markets were attempted and none processed. Isolation still runs the other markets.
- Validate config + override sanity before deploy:
- `greenfloor-manager doctor` (includes warnings for invalid runtime override env values)
- Test-only overrides (debug builds / CI; not for production):
Expand Down
Loading