Date: 2026-06-23
Status: Stub — problem statement only. Captures a gap noticed while account-keying the order-gateway (A-3402); intentionally defers the design.
account_permissions
carries
five
independent
bits
per
(user, account): can_list,
can_read,
can_set_limits,
can_reduce_or_close,
can_trade (rs/sdk-internal/db/src/entities.rs).
The
order-gateway
authorizes
money routes
against
these
via
ax_db::entities::authorize(user, account, Action).
There are, confusingly, two unrelated "close-only" notions in the gateway today:
user.is_close_only
—
a
per-user
risk
flag.
Enforced
on
the
place
path: can_send_order
→
evaluate_order_using_ep3_position
rejects
with OrderDecision::CloseOnlyViolation
unless
is_closing()
holds
for
the order.
(is_closing()
already
returns
exactly
"does
this
order
reduce/close the
account's
position,"
so
the
predicate
we
need
exists.)
account_permissions.can_reduce_or_close
—
an
account-level
permission. Consulted
only
on
cancel
paths
(resolve_cancelable_order, cancel_all_orders,
admin
cancel-all).
It
is
never
consulted
when placing
an
order.
Placing
an
order
authorizes
Action::Trade
only
(place_order_common).
So
a user
holding
can_reduce_or_close = true, can_trade = false
on
an
account
can cancel
orders
there
but
cannot
place
a
closing
order
—
even
though
the permission's
name
reads
as
"may
place
orders
that
reduce
or
close."
The account-permission
close
side
is
simply
not
wired
into
placement;
the
only close-only
enforcement
on
placement
comes
from
the
unrelated
per-user is_close_only
flag.
This
matches
the
multi-accounts
plan's
step
6
(#2180),
which
centralized
money routes
on
a
single
authorize(Action::Trade).
So
"placing
requires can_trade"
was
deliberate,
and
reduce-only
placement
was
never
a
feature
— this
RFC
is
about
whether
it
should
be
one.
The
"reduce-only
key
for
the
de-risking
bot
/
liquidation
agent"
workflow
is
a standard
exchange
capability
and
is
implied
by
the
existence
of
a
distinct can_reduce_or_close
bit.
Granting
such
a
delegate
can_trade
to
let
them place
closing
orders
over-grants
(they
can
also
open
new
risk).
A
plausible
shape:
at
the
place
edge,
admit
the
order
when can_trade || (can_reduce_or_close && order_is_closing),
folding
the account-permission
close
case
into
the
existing
is_close_only
enforcement
so there
is
one
close-only
code
path
rather
than
two.
is_closing()
already supplies
order_is_closing.
user.is_close_only
with account_permissions.can_reduce_or_close.
Plan
step
10b
already
moves is_close_only
off
the
user
onto
trading_accounts
—
does
that
subsume
this, or
are
they
distinct
(risk
freeze
vs.
delegated
permission)?
can_send_order),
but
the
permission
gate runs
at
the
edge
before
that.
Either
push
the
permission
decision
into can_send_order,
or
pre-gate
on
can_trade || can_reduce_or_close
at
the edge
and
let
can_send_order
reject
a
non-closing
order
from
a
reduce-only principal.
ReduceOrClose;
a
can_trade-only principal
(no
can_reduce_or_close)
currently
could
not
cancel.
Is
that intended,
or
should
can_trade
imply
can_reduce_or_close?
cancel-all
handler
performs
no
permission check
today
(REST's
does,
via
Action::ReduceOrClose).
Fold
a
fix
into
this work.
No behavior change. This RFC only records the gap so the eventual design is deliberate rather than incidental.