Repository navigation
Rail 17 reads Coinbase; skipped DCA buys retry until the next window and alert on macOS (#1010) - #1016
Merged
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…owed docstring (#1010) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…7-day TTL (#1010) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…inery (#1010) Rail 17 reads the venue per buy, so nothing about it expires any more. - executor: delete _withdrawals_enabled and WITHDRAWAL_ATTESTATION_TTL_SEC. - withdrawals CLI: attest --enabled/--suspended only set/clear the manual brake (no timestamp, no expiry wording; the typed-yes TTY gate is unchanged). show prints the tri-state suspension and the venue's per-wallet answer via a bounded _build_broker; any build/read failure prints "venue unreadable", exit 0. - attestations: rail 17 leaves the survey; withdrawal_expiry/_withdrawal_dates deleted. - doctor: attestation_findings(subscription, now_ts, withdrawals_suspended): FAIL on suspension, WARN on unreadable store, OK otherwise; never reads the old timestamp. - status/pulse/web: WithdrawalAttestationStatus is just `suspended` (tri-state, unknown never rendered as off); console card shows the manual suspension line. - notifications: no rail-17 attestation event. - setup wizard step explains the venue read and the manual switch. - docs/fiqh-basis.md mechanics sentence + its pin test updated (full docs rewrite is Task 6). Deleted tests (expiry wording, not loosened): test_attestation_expiry.py: test_rail_17_expiry_comes_from_the_executors_constant_and_is_not_restated, test_an_unattested_rail_reports_missing_rather_than_expired, test_rail_17_passes_through_ok_then_expiring_then_expired, test_seconds_left_never_goes_negative, test_doctor_reads_rail_17s_TTL_from_the_executor_rather_than_restating_it, test_doctor_starts_warning_on_rail_17_exactly_when_the_banner_does, test_a_suspended_attestation_is_not_a_healthy_one, test_reordering_the_definitions_does_not_cross_the_wires, test_the_warning_window_never_outlives_the_attestation_it_guards, test_forgetting_rail_17s_verdict_reports_too_loud_rather_than_too_quiet. test_doctor.py: test_rail17_fresh_reports_days_remaining, test_rail17_absent_fails_and_names_the_attest_command, test_rail17_expired_fails_with_days_over. test_status.py: test_rail17_attested_fresh_shows_time_remaining, test_rail17_expired_names_the_halt_and_the_fix, test_rail17_expired_in_paper_names_the_state_not_a_halt, test_rail17_never_attested_is_said_as_such, test_rail17_suspended_attestation_still_names_the_halt, test_rail17_freshness_uses_the_executors_own_ttl. test_notifications.py: test_an_attestation_due_within_two_days_fires_and_a_fresh_one_does_not, test_an_expired_attestation_fires_too, test_both_attestation_rails_are_notified_when_both_are_unhealthy. test_payload.py: test_an_expired_withdrawal_attestation_reads_as_bad. test_fiqh_basis.py: test_rail_17s_seven_day_ttl_is_pinned_to_the_executor (replaced). Rewritten onto rail 22 / new shape: test_attestation_alert_once.py, test_attestation_banner.py, test_pulse.py rail-17 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
) Dca.detect gains a ledger-fed catch_up state (DcaCatchUp): a window whose buy did not fill is retried on every later daily cycle until it fills or the next window opens, at most one fill per rule per window. Backtests, the sim and research never set it and keep window-open-day behaviour. The agent reads each rule's own BUYs in the orders.mode the cycle writes (live/paper); an unreadable ledger or a rule row with no timestamps disables catch-up for that rule. The same-UTC-day DCA characterization test is rewritten, as its header asks, now that a dedupe exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A vetoed live DCA buy is a zero-exit cycle, so neither the wrapper's alerts nor the unconfigured webhook reached the operator. keel now posts a macOS notification itself, once per rule per UTC day, for every due buy that did not go out: vetoed, feed stale, entries withheld, or blocked by a resting order (named, with how to resolve it). Live cycles only; the sink is inert off macOS and under pytest and never fails a cycle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t bar (#1010) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lerts (#1010) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1010) F1: `keel orders cancel N` marks a live entry left `pending` with no venue order id canceled LOCALLY after a typed `yes` at a terminal; the prompt says to check the Coinbase app first, the broker is never built, and the row's raw_response records why under `local_cancel`. A row with a venue id keeps the venue path unchanged. Registered in keel.capabilities. F4: an in-flight skip names that command and says "Blocked until that order is resolved" instead of a false "Retrying daily until". F2: an in-flight DCA BUY counts as a DCA day for the same-day sleeve-sell exclusion. F3: catch-up state is read after reconcile, so a row reconcile settles this cycle is not counted in flight. F11: the runbook names what clears each kind of skip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F5: rail 17's UNKNOWN veto promises a retry only to a DCA buy. F6: `keel withdrawals show` names the exception type when the venue is unreadable (still exit 0). F7: the doctor source-scan test is now a behaviour test: a garbage or expired `withdrawals_attested_at` leaves the rail-17 finding unchanged. F8: Pulse's next DCA buy is derived from `Dca.due_window` on the clock with the ledger's catch-up state, so a pending catch-up shows as the next cycle's bar; the console column reads "next buy day (UTC)". F9: `keel status --help` and the withdrawals capability row describe the venue read and the manual suspension, not an expiring attestation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nce (#1010) F10: the rail-17 page states that a venue whose adapter lacks `withdrawals_allowed` (Alpaca, Kraken, Robinhood) has every live BUY refused, and both manual-switch lines use the live config and database. F12: the go-live runbook's garbled rail-17 sentence is rewritten. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F1 registered `keel orders cancel` (local-only) as a capability; wall 1's vocabulary must trip on it, so `cancel` joins FORBIDDEN_SUBSTRINGS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ad-and-dca-catchup
…ad-and-dca-catchup Brings in #1013 (6f816f2), a competing fix for #1010. This branch's design wins, so the merge keeps one DCA retry mechanism, `Dca.catch_up`. Removed from #1013: DcaWindowLedger, Dca.window_ledger and next_dca_bar_ts (dca.py); _dca_window_ledger, _arm_dca_ledger and the agent.dca_window_missed log (agent.py); DcaWindowAfterExpiry, dca_windows_after_expiry and live_dca_windows_after_expiry (attestations.py); dca_cadence_findings / attest.dca_cadence (doctor.py); the status and `withdrawals attest` expiry warnings; Repository.last_live_buy_created_at and signal_ts_since, which had no caller left; the old rail-17-withdrawal-attestation.md section; the tests for all of these (tests/strategy/test_dca_window_retry.py, tests/test_dca_windows_after_expiry.py and the additions to the doctor, status, repository, guards, withdrawal-attestation and agent tests). The rail-17 UNKNOWN message keeps this branch's wording. Kept: the guards.py note that rail 17 does not exempt DCA (module docstring and rail comment), the same sentence in docs/rails/rail-17-withdrawal-capability.md, and the historical plan docs/superpowers/plans/2026-10-27-rail17-ttl-dca-cadence.md, unedited. Ported to catch_up (tests/test_agent.py): test_a_partially_filled_buy_blocks_its_windows_catch_up, test_paper_era_activity_does_not_make_a_live_window_due, test_a_rule_promoted_on_its_window_open_day_catches_that_window_up, test_a_rule_promoted_on_its_windows_buying_day_catches_that_window_up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`keel orders cancel` on a partially_filled DCA BUY leaves the row `canceled` with its executed `filled_quantity` still on it. `_dca_catch_up_state` counted only `filled`, so the next cycle caught that window up and bought it a second time. `_buy_executed` (filled, or filled_quantity > 0) now decides it for both the catch-up ledger and `_bought_today`. A terminal row that executed nothing still leaves its window to catch up. The in-flight block for resting statuses is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-test docstring (#1010) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1010.
Live DCA buys kept stopping without notice whenever the 7-day withdrawal attestation (rail 17) lapsed, and a vetoed DCA window was lost for good. Since August that cost every window of the five 14-day rules. This replaces the attestation with a check keel makes itself, retries skipped DCA buys, and tells the operator every time a live buy is skipped.
Changed
allow_withdrawalsfromGET /v2/accountsfor the wallet the order spends (USD/USDC) and the coin being bought. Both allowed → pass. Either disallowed → veto naming the wallet. A coin with no wallet yet is decided on the quote wallet. Venue unreadable, quote wallet missing, suspension store unreadable, or an adapter without the read → veto (fails closed). SELLs and paper are unaffected. The existing key is enough (can_transfer: false). The flag has only ever been observedtrue; it has not been seen flipping during a real freeze, which is why the manual brake stays.keel withdrawalsis a manual switch.attest --suspendedhalts live entries with no expiry;attest --enabledclears it and still needs a typedyesat a terminal.withdrawals showprints the manual state and the venue's answer per wallet.keel dca-plannotes that catch-up can add up to one buy per rule to a calendar month; rail 14 still bounds it.Added
KEEL_DISABLE_MACOS_NOTIFYsilences it. A DCA order stuckpendingis reported daily by order id.yes, so a blocked rule can be released. Automatic venue lookup is Settle a pending DCA order with no venue id by looking it up at Coinbase by client_order_id #1017.Removed
keel status,keel doctor, notifications, the web console banner, and the weekly runbook step. Rail 22 (cash posture) and rail 14 (subscription) attestations are unchanged.Deploy note
The first live cycle after deploy retries every DCA rule whose current window is unfilled. On the windows that opened on the 2026-11-05 bar, that is all seven live rules (about $150), subject to rails 13 and 14. Checked before deploy: the live DB has no resting live BUY rows that would block a rule.
Design:
docs/superpowers/specs/2026-11-06-rail-17-venue-read-and-dca-catchup-design.md· Plan:docs/superpowers/plans/2026-11-06-rail-17-venue-read-and-dca-catchup.mdSupersedes #1013.
🤖 Generated with Claude Code