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
56 changes: 55 additions & 1 deletion autonomy/brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,14 +429,16 @@ def _sync_live_exposure(self, outcomes: list[TradeOutcome]) -> None:
OutcomeKind.FILLED: "filled",
OutcomeKind.CANCELED: "canceled",
OutcomeKind.EXPIRED: "expired",
OutcomeKind.REJECTED: "rejected",
}
for outcome in outcomes:
order_id = str(outcome.order_id or "").strip()
if not order_id:
continue
terminal_state = terminal_states.get(outcome.kind)
if outcome.kind is OutcomeKind.PARTIALLY_FILLED or terminal_state:
tracker.record_cumulative_fill(
proposal_id = tracker.client_order_id_for(order_id)
persisted = tracker.record_cumulative_fill(
order_id,
int(outcome.fill_count),
(
Expand All @@ -445,6 +447,58 @@ def _sync_live_exposure(self, outcomes: list[TradeOutcome]) -> None:
),
terminal_state=terminal_state,
)
capital_adapter = getattr(
self.executor, "capital_envelope_adapter", None
)
witnessed_state = str(
outcome.detail.get("state") or ""
).strip().lower()
terminal_witnesses = {
"filled": {"filled", "executed"},
"canceled": {"canceled", "cancelled"},
"expired": {"expired"},
"rejected": {"rejected"},
}
if (
persisted
and terminal_state is not None
and witnessed_state
in terminal_witnesses.get(terminal_state, set())
and proposal_id
and capital_adapter is not None
and outcome.broker_contacted is True
):
receipt = {
"schema": "dummy.terminal-order-reconciliation.v1",
"terminal_status": terminal_state,
"proposal_id": proposal_id,
"decision_id": outcome.decision_id,
"market_ticker": outcome.market_ticker,
"order_id": order_id,
"fill_count": int(outcome.fill_count),
"fill_price_cents": outcome.fill_price_cents,
"broker_contacted": True,
"outcome_created_at": outcome.created_at,
"detail": dict(outcome.detail),
"local_exposure_projection_persisted": True,
}
try:
capital_adapter.release_from_terminal_reconciliation(
proposal_id=proposal_id,
terminal_status=terminal_state,
reconciliation_receipt=receipt,
)
except Exception:
# Retaining the capital reservation is the fail-closed
# response to a missing/malformed terminal receipt.
logger.exception(
"DumbMoney terminal reservation release failed",
extra={
"component": "reconciliation",
"proposal_id": proposal_id,
"order_id": order_id,
},
)

def _close_position(self, state: RiskState, open_decision: dict[str, Any],
result_yes: bool) -> None:
Expand Down
40 changes: 40 additions & 0 deletions autonomy/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ def __init__(
exchange_status_fn: Callable[[], dict[str, Any]] | None = None,
now_fn: Callable[[], float] | None = None,
execution_policy: ExecutionPolicy | None = None,
capital_envelope_adapter: Any | None = None,
core_submission_authority: Callable[[], bool] | None = None,
) -> None:
self.mode = mode
# Typed execution policy (Wave-2 A2/F2; Wave-5 taker path). The default
Expand Down Expand Up @@ -131,6 +133,11 @@ def __init__(
# failed, or malformed status blocks the submit
# (exchange_status_unavailable_at_submit), as does maintenance/halt.
self.exchange_status_fn = exchange_status_fn
# Optional DumbMoney deny-only capital ceiling. This adapter cannot
# replace the session acknowledgement, local risk state, kill switch,
# central firewall, or live-submit authority.
self.capital_envelope_adapter = capital_envelope_adapter
self.core_submission_authority = core_submission_authority
self._now_fn = now_fn or (lambda: datetime.now(timezone.utc).timestamp())
# Counter so a refusal is never a silent skip: surfaced on the executor
# for the daemon/dashboard, and each refusal is also recorded as a
Expand Down Expand Up @@ -331,11 +338,17 @@ def _make_firewall(self):
from live_firewall.exposure_tracker import get_persistent_exposure_tracker
from live_firewall.firewall import LiveBrokerFirewall

if self.capital_envelope_adapter is None:
raise RuntimeError(
"DumbMoney capital adapter is mandatory for LIVE execution"
)
return LiveBrokerFirewall(
KalshiClient(),
get_persistent_exposure_tracker(),
autonomy_risk_state_path=self.risk_state_path,
require_autonomy_risk_state=True,
capital_envelope_adapter=self.capital_envelope_adapter,
core_submission_authority=self.core_submission_authority,
)

def _risk_state_digest(self) -> str | None:
Expand Down Expand Up @@ -545,6 +558,11 @@ async def _execute_live_via_firewall(
) -> TradeOutcome:
if market is None or market.ticker != decision.market_ticker:
return self._blocked(decision, "live_market_context_missing_or_mismatched")
if self.capital_envelope_adapter is None:
return self._blocked(
decision,
"dumbmoney_capital_adapter_required_for_live_execution",
)
try:
firewall = self._make_firewall()
except Exception as exc:
Expand All @@ -560,6 +578,27 @@ async def _execute_live_via_firewall(
risk_digest = self._risk_state_digest()
if risk_digest is None:
return self._blocked(decision, "persisted_autonomy_risk_state_unavailable")
capital_binding: dict[str, Any] = {}
if self.capital_envelope_adapter is not None:
from live_firewall.dumbmoney_capital import strategy_binding_hash

try:
capital_binding = self.capital_envelope_adapter.binding_for(
strategy_hash=strategy_binding_hash(
f"autonomy_strategy_v2:{decision.decision_id}"
),
passport_hash=strategy_binding_hash(
f"autonomy_forecast:{decision.decision_id}"
),
authorized_instrument=(
f"event_contract:{decision.market_ticker}"
),
)
except Exception as exc:
return self._blocked(
decision,
f"signed_capital_envelope_unavailable:{type(exc).__name__}",
)
client = getattr(firewall, "client", None)
get_orderbook = getattr(client, "get_orderbook", None)
if not callable(get_orderbook):
Expand Down Expand Up @@ -597,6 +636,7 @@ async def _execute_live_via_firewall(
"risk_state_sha256": risk_digest,
"risk_snapshot": dict(decision.risk_snapshot),
"expiration_ts": expiration_ts,
**capital_binding,
}
request = LiveOrderRequest(
**request_fields,
Expand Down
76 changes: 55 additions & 21 deletions autonomy/kill_reconciliation.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,28 +63,62 @@ def queue_kill_reconciliation(
*,
kill_asserted_at: str,
receipt_path: Path | None = DEFAULT_KILL_RECONCILIATION_PATH,
capital_envelope_adapter: Any | None = None,
) -> dict[str, Any]:
"""Record that cancel/reconcile remains required; perform no broker I/O."""
receipt = _stamp(
{
"kill_reconciliation_version": KILL_RECONCILIATION_VERSION,
"generated_at": datetime.now(timezone.utc).isoformat(),
"kill_asserted_at": kill_asserted_at,
"status": "PENDING_CANCEL_AND_RECONCILE",
"submission_authority": False,
"cancel_authority": False,
"broker_contacted": False,
"canceled_order_ids": [],
"open_orders_after": None,
"residual_positions": None,
"flat_book_observed": False,
"liquidation_attempted": False,
"operator_action": (
"run the separately authorized central cancellation and "
"reconciliation procedure"
),
}
)
"""Record that cancel/reconcile remains required; perform no broker I/O.

A DumbMoney adapter may be supplied to mirror the state into the compact
operational journal. This queue path always records ``cancel_authority``
false; a capital grant never supplies cancellation authority.
"""
payload: dict[str, Any] = {
"kill_reconciliation_version": KILL_RECONCILIATION_VERSION,
"generated_at": datetime.now(timezone.utc).isoformat(),
"kill_asserted_at": kill_asserted_at,
"status": "PENDING_CANCEL_AND_RECONCILE",
"submission_authority": False,
"cancel_authority": False,
"broker_contacted": False,
"canceled_order_ids": [],
"open_orders_after": None,
"residual_positions": None,
"flat_book_observed": False,
"liquidation_attempted": False,
"operator_action": (
"run the separately authorized central cancellation and "
"reconciliation procedure"
),
}
if capital_envelope_adapter is not None:
try:
parsed_kill_at = datetime.fromisoformat(
str(kill_asserted_at).replace("Z", "+00:00")
)
if parsed_kill_at.tzinfo is None:
raise ValueError("kill_asserted_at must be timezone-aware")
canonical_kill_at = (
parsed_kill_at.astimezone(timezone.utc)
.isoformat()
.replace("+00:00", "Z")
)
operational = capital_envelope_adapter.enter_cancel_only(
reason="Dummy kill reconciliation queued",
kill_asserted_at=canonical_kill_at,
cancel_authorized=False,
)
payload["dumbmoney_operational_event_sha256"] = operational[
"event_sha256"
]
payload["dumbmoney_operational_status"] = (
"CANCEL_AUTHORITY_REQUIRED"
)
except Exception as exc:
# The KILL queue still persists locally; journal failure can only
# reduce authority and is exposed for operator reconciliation.
payload["dumbmoney_operational_status"] = (
f"JOURNAL_FAILED:{type(exc).__name__}"
)
receipt = _stamp(payload)
_write_atomic(receipt_path, receipt)
return receipt

Expand Down
Loading
Loading