Skip to content

[web+cli] keel orders cancel: the cancel asymmetry — entries one-click, exits typed #707

Description

@eaitbrahim

The principle

Cancelling an open entry order is refusing risk — frictionless by constitution, safe as a one-click web action. Cancelling an open exit or protective order is removing protection — never one click; walled off, explained, and redirected to the typed friction of the terminal. No broker makes this distinction. It falls straight out of keel's constitution (exits degrade to typed friction; refusing entries is always allowed).

Context (verified)

  • No orders CLI group and no cancel command exist todaycancel appears nowhere in keel/cli.py or keel/commands/. Cancellation lives only inside executor internals: _cancel_at_exchange (keel/execution/executor.py:1668), _clear_resting_bracket (:268), the _roll_stop: a crash between cancel and replace leaves the position naked AND silent #519 cancel-before-place protocol.
  • The broker port has cancel_order(order_id) -> CancelOutcome (keel/data/cb_client.py:364); the Alpaca adapter implements refused/unknown outcomes.
  • The web's only write surface today is POST /api/setup/<key> behind: Host check, session cookie, Sec-Fetch-Site, X-Keel-Client: 1, HMAC X-Keel-CSRF (keel/web/security.py:59-71). This issue adds the first non-setup write action — hence its own security pass.

Scope

  1. CLI: keel orders cancel <id> — entry-side orders cancel on y/N; exit-side or protective orders require the typed phrase. Add keel orders list while the group exists (reads the [web] Orders view: the per-order honesty record — expected vs actual fill, fee as paid, provenance #700 surface).
  2. Web: POST /api/orders/cancel behind the full existing guard machinery, plus:
    • Strict side/role guard: if the target order is side == sell, or is linked as any position's bracket_order_id (protective legs are the real hazard, not merely the side label), respond 403 with a typed payload:
      {
        "error": "EXIT_PROTECTION_LOCKED",
        "message": "Exits and protective brackets cannot be cancelled from the web console. Use 'keel orders cancel <id>' in the terminal."
      }
    • Only open/resting statuses are cancellable; anything else is a named refusal, never a silent no-op.
    • Entry-side cancel clears the resting bracket exactly as the executor's _roll_stop: a crash between cancel and replace leaves the position naked AND silent #519 protocol does — an entry must never die leaving an orphaned protective leg.
  3. Security pass: idempotency on repeated cancels, rate sanity, an audit log line per attempt (allowed and refused), and the refusal path tested as hard as the happy path.

Acceptance

  • Mutant tests: removing the side guard or the bracket-link guard fails the suite; removing the bracket-clearing fails the suite.
  • 403 payload contract pinned by test; CLI typed-phrase path pinned by test.
  • The security review sign-off lands as a comment here before merge.

Sprint 3, after #698#705 — it opens a new write surface and borrows their patterns. Explicitly does not wait on #648 (loopback-only; remote exposure is a separate rail).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

featureNew capability (groups under Features)railsUn-overridable safety rail / guard (Compliance & rails)

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions