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
13 changes: 9 additions & 4 deletions docs/paper_execution_command_consumer.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ The shared consumer owns only the rules that must be identical across
platforms:

1. validate the runtime-loaded strategy release before touching the queue;
2. atomically claim only due, still-queued commands;
3. validate the immutable paper-risk admission receipt and release binding;
4. apply the enforced runtime command gate to every reconciled proposal; and
5. append a create-only lifecycle: `claimed` → `submitted` → `accepted` →
2. validate the runtime-owned platform, account scope, and strategy-profile
binding before reconciling a claimed command;
3. atomically claim only due, still-queued commands;
4. validate the immutable paper-risk admission receipt and release binding;
5. apply the enforced runtime command gate to every reconciled proposal; and
6. append a create-only lifecycle: `claimed` → `submitted` → `accepted` →
`filled`, or `rejected` / `reconciliation_required`.

Each platform supplies `reconcile_command(command)`. It owns its broker
Expand All @@ -39,6 +41,9 @@ execution translation.
- Pass the currently loaded release receipt and exact promoted
`StrategyReleaseIdentity`. Missing or mismatched evidence blocks before a
command is claimed.
- Pass `PaperExecutionCommandBinding` from the runtime target, with the exact
platform, account scope, and strategy profile. A command intended for a
different consumer is rejected without calling the platform reconciler.
- Classify exposure from reconciled before/after positions, not an order side.
Under a `reducing_only` admission, every proposal must prove `reduces`.
- If reconciliation, storage, or lifecycle progression fails after a claim,
Expand Down
4 changes: 4 additions & 0 deletions src/quant_platform_kit/common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,11 @@
)
from .paper_execution_command_consumer import (
PAPER_EXECUTION_COMMAND_CONSUMER_SCHEMA_VERSION,
PaperExecutionCommandBinding,
PaperExecutionCommandReconciler,
PaperExecutionProposal,
PaperExecutionReconciliation,
build_paper_execution_command_binding,
consume_due_paper_execution_commands,
)
from .runtime_logging import (
Expand Down Expand Up @@ -404,9 +406,11 @@
"PaperRiskAdmissionDisposition",
"PaperRiskAdmissionReceipt",
"PAPER_EXECUTION_COMMAND_CONSUMER_SCHEMA_VERSION",
"PaperExecutionCommandBinding",
"PaperExecutionCommandReconciler",
"PaperExecutionProposal",
"PaperExecutionReconciliation",
"build_paper_execution_command_binding",
"build_paper_risk_admission_receipt",
"calculate_paper_risk_admission_receipt_sha256",
"canonical_paper_risk_admission_receipt_json",
Expand Down
101 changes: 98 additions & 3 deletions src/quant_platform_kit/common/paper_execution_command_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
enforcement=RuntimeCommandGateEnforcement.ENFORCE,
)
_SYMBOL_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$")
_COMMAND_BINDING_FIELD_PATTERN = re.compile(r"^[a-z][a-z0-9]*(?:[._=-][a-z0-9]+)*$")


def _safe_symbol(value: object) -> str:
Expand All @@ -57,6 +58,13 @@ def _safe_symbol(value: object) -> str:
return symbol


def _command_binding_field(value: object, *, field_name: str) -> str:
field = str(value or "").strip().lower()
if not _COMMAND_BINDING_FIELD_PATTERN.fullmatch(field):
raise ValueError(f"{field_name} must be a lowercase scoped identifier")
return field


def _json_audit_mapping(value: Mapping[str, object]) -> dict[str, object]:
"""Normalize a JSON-safe, platform-supplied audit record.

Expand Down Expand Up @@ -106,6 +114,71 @@ def to_dict(self) -> dict[str, object]:
}


@dataclass(frozen=True)
class PaperExecutionCommandBinding:
"""The one platform/account/strategy scope allowed to consume a command.

Release identity proves which approved strategy build was loaded; this
binding proves that a valid command was delivered to the intended platform
runtime. It is supplied from the runtime target, never from a command.
"""

platform: str
account_scope: str
strategy_profile: str

def __post_init__(self) -> None:
object.__setattr__(self, "platform", _command_binding_field(self.platform, field_name="platform"))
object.__setattr__(
self,
"account_scope",
_command_binding_field(self.account_scope, field_name="account_scope"),
)
object.__setattr__(
self,
"strategy_profile",
_command_binding_field(self.strategy_profile, field_name="strategy_profile"),
)

def to_dict(self) -> dict[str, str]:
return {
"platform": self.platform,
"account_scope": self.account_scope,
"strategy_profile": self.strategy_profile,
}


def build_paper_execution_command_binding(
value: PaperExecutionCommandBinding | Mapping[str, object] | None,
) -> PaperExecutionCommandBinding:
"""Build a strict runtime-owned command binding from explicit fields."""

if isinstance(value, PaperExecutionCommandBinding):
return value
if not isinstance(value, Mapping) or set(value) != {"platform", "account_scope", "strategy_profile"}:
raise ValueError("paper execution command binding requires platform, account_scope, strategy_profile")
return PaperExecutionCommandBinding(
platform=str(value["platform"]),
account_scope=str(value["account_scope"]),
strategy_profile=str(value["strategy_profile"]),
)


def _command_binding_findings(
command: ExecutionCommand,
*,
expected_binding: PaperExecutionCommandBinding,
) -> tuple[str, ...]:
findings: list[str] = []
if command.platform != expected_binding.platform:
findings.append("command_platform_mismatch")
if command.account_scope != expected_binding.account_scope:
findings.append("command_account_scope_mismatch")
if command.strategy_profile != expected_binding.strategy_profile:
findings.append("command_strategy_profile_mismatch")
return tuple(findings)


@dataclass(frozen=True)
class PaperExecutionReconciliation:
"""Platform evidence after reconciling current positions and market data."""
Expand Down Expand Up @@ -209,6 +282,7 @@ def consume_due_paper_execution_commands(
reconcile_command: PaperExecutionCommandReconciler,
runtime_release_receipt: Mapping[str, Any] | None,
expected_strategy_release: StrategyReleaseIdentity | Mapping[str, object] | None,
expected_command_binding: PaperExecutionCommandBinding | Mapping[str, object] | None,
) -> dict[str, object]:
"""Claim and simulate due paper commands without creating broker orders.

Expand All @@ -221,6 +295,15 @@ def consume_due_paper_execution_commands(
raise RuntimeError("paper durable execution command store is required")
if not callable(reconcile_command):
raise ValueError("reconcile_command must be callable")
try:
expected_binding = build_paper_execution_command_binding(expected_command_binding)
except ValueError:
return {
"schema_version": PAPER_EXECUTION_COMMAND_CONSUMER_SCHEMA_VERSION,
"status": "blocked",
"reason": "command_binding_invalid",
"commands": [],
}
try:
expected_release = build_strategy_release_identity(expected_strategy_release)
except ValueError:
Expand Down Expand Up @@ -262,9 +345,19 @@ def consume_due_paper_execution_commands(
expected_strategy_release=expected_release,
).findings
)
reconciliation = reconcile_command(command)
if not isinstance(reconciliation, PaperExecutionReconciliation):
raise ValueError("reconcile_command must return PaperExecutionReconciliation")
binding_findings = _command_binding_findings(
command,
expected_binding=expected_binding,
)
if binding_findings:
reconciliation = PaperExecutionReconciliation(
proposals=(),
integrity_findings=binding_findings,
)
else:
reconciliation = reconcile_command(command)
if not isinstance(reconciliation, PaperExecutionReconciliation):
raise ValueError("reconcile_command must return PaperExecutionReconciliation")
integrity_findings.extend(reconciliation.integrity_findings)
integrity_findings = list(
dict.fromkeys(normalize_runtime_command_integrity_findings(integrity_findings))
Expand Down Expand Up @@ -364,8 +457,10 @@ def consume_due_paper_execution_commands(

__all__ = [
"PAPER_EXECUTION_COMMAND_CONSUMER_SCHEMA_VERSION",
"PaperExecutionCommandBinding",
"PaperExecutionCommandReconciler",
"PaperExecutionProposal",
"PaperExecutionReconciliation",
"build_paper_execution_command_binding",
"consume_due_paper_execution_commands",
]
8 changes: 8 additions & 0 deletions src/quant_platform_kit/common/runtime_command_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,11 @@ class RuntimeCommandIntegrityFinding(str, Enum):
ACCOUNT_IDENTITY_PLATFORM_MISMATCH = "account_identity_platform_mismatch"
ACCOUNT_IDENTITY_TYPE_MISMATCH = "account_identity_type_mismatch"
BROKER_OUTCOME_UNKNOWN = "broker_outcome_unknown"
COMMAND_ACCOUNT_SCOPE_MISMATCH = "command_account_scope_mismatch"
COMMAND_BINDING_INVALID = "command_binding_invalid"
COMMAND_DIGEST_MISMATCH = "command_digest_mismatch"
COMMAND_PLATFORM_MISMATCH = "command_platform_mismatch"
COMMAND_STRATEGY_PROFILE_MISMATCH = "command_strategy_profile_mismatch"
DATA_ARTIFACT_INVALID = "data_artifact_invalid"
DATA_STALE = "data_stale"
DATA_UNAVAILABLE = "data_unavailable"
Expand Down Expand Up @@ -114,7 +118,11 @@ class RuntimeCommandIntegrityFinding(str, Enum):
RuntimeCommandIntegrityFinding.ACCOUNT_IDENTITY_PLATFORM_MISMATCH.value,
RuntimeCommandIntegrityFinding.ACCOUNT_IDENTITY_TYPE_MISMATCH.value,
RuntimeCommandIntegrityFinding.BROKER_OUTCOME_UNKNOWN.value,
RuntimeCommandIntegrityFinding.COMMAND_ACCOUNT_SCOPE_MISMATCH.value,
RuntimeCommandIntegrityFinding.COMMAND_BINDING_INVALID.value,
RuntimeCommandIntegrityFinding.COMMAND_DIGEST_MISMATCH.value,
RuntimeCommandIntegrityFinding.COMMAND_PLATFORM_MISMATCH.value,
RuntimeCommandIntegrityFinding.COMMAND_STRATEGY_PROFILE_MISMATCH.value,
RuntimeCommandIntegrityFinding.DURABLE_EVENT_HISTORY_INVALID.value,
RuntimeCommandIntegrityFinding.EXECUTION_REPLAY_DETECTED.value,
RuntimeCommandIntegrityFinding.INVALID_EFFECTIVE_SESSION.value,
Expand Down
72 changes: 72 additions & 0 deletions tests/test_paper_execution_command_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ def _runtime_receipt() -> dict[str, object]:
return build_runtime_loaded_receipt(strategy_release=_release_identity())


def _binding() -> dict[str, str]:
return {
"platform": "longbridge",
"account_scope": "paper-sg",
"strategy_profile": "soxl_soxx_trend_income",
}


def _increasing_reconciliation(_: ExecutionCommand) -> PaperExecutionReconciliation:
return PaperExecutionReconciliation(
proposals=(
Expand All @@ -95,6 +103,7 @@ def test_consumer_persists_a_paper_only_lifecycle_for_reconciled_proposals(tmp_p
reconcile_command=_increasing_reconciliation,
runtime_release_receipt=_runtime_receipt(),
expected_strategy_release=_release_identity(),
expected_command_binding=_binding(),
)

assert result == {
Expand Down Expand Up @@ -143,6 +152,7 @@ def test_consumer_requires_runtime_release_before_claiming(tmp_path: Path) -> No
reconcile_command=_increasing_reconciliation,
runtime_release_receipt=None,
expected_strategy_release=_release_identity(),
expected_command_binding=_binding(),
)

assert result["status"] == "blocked"
Expand All @@ -165,6 +175,7 @@ def test_consumer_rejects_new_risk_when_admission_is_reducing_only(tmp_path: Pat
reconcile_command=_increasing_reconciliation,
runtime_release_receipt=_runtime_receipt(),
expected_strategy_release=_release_identity(),
expected_command_binding=_binding(),
)

assert result["commands"][0]["status"] == "rejected"
Expand All @@ -191,6 +202,7 @@ def _failing_reconciliation(_: ExecutionCommand) -> PaperExecutionReconciliation
reconcile_command=_failing_reconciliation,
runtime_release_receipt=_runtime_receipt(),
expected_strategy_release=_release_identity(),
expected_command_binding=_binding(),
)

assert result["commands"] == [
Expand Down Expand Up @@ -224,9 +236,69 @@ def _unknown_finding(_: ExecutionCommand) -> PaperExecutionReconciliation:
reconcile_command=_unknown_finding,
runtime_release_receipt=_runtime_receipt(),
expected_strategy_release=_release_identity(),
expected_command_binding=_binding(),
)

assert result["commands"][0]["status"] == "rejected"
receipt = store.events(command)[-1].details["runtime_command_gate_receipts"][0]
assert receipt["mode"] == "halted"
assert receipt["reasons"] == ["unknown_integrity_finding"]


def test_consumer_rejects_a_command_for_another_platform_without_reconciling(tmp_path: Path) -> None:
store = ExecutionCommandStore(local_dir=tmp_path)
command = ExecutionCommand.from_decision(
platform="schwab",
account_scope="paper-sg",
strategy_profile="soxl_soxx_trend_income",
execution_mode="paper",
signal_date="2026-08-24",
effective_date="2026-08-25",
execution_timing_contract="next_trading_day",
decision_digest="d" * 64,
intent=_command().intent,
created_at="2026-08-24T20:00:00+00:00",
)
assert store.enqueue(command)
reconciled = False

def _must_not_reconcile(_: ExecutionCommand) -> PaperExecutionReconciliation:
nonlocal reconciled
reconciled = True
return _increasing_reconciliation(command)

result = consume_due_paper_execution_commands(
store=store,
as_of_session="2026-08-25",
claimant="paper-command-verify",
reconcile_command=_must_not_reconcile,
runtime_release_receipt=_runtime_receipt(),
expected_strategy_release=_release_identity(),
expected_command_binding=_binding(),
)

assert reconciled is False
assert result["commands"][0]["status"] == "rejected"
receipt = store.events(command)[-1].details["runtime_command_gate_receipts"][0]
assert receipt["mode"] == "halted"
assert receipt["reasons"] == ["command_platform_mismatch"]


def test_consumer_blocks_before_claiming_when_runtime_binding_is_invalid(tmp_path: Path) -> None:
store = ExecutionCommandStore(local_dir=tmp_path)
command = _command()
assert store.enqueue(command)

result = consume_due_paper_execution_commands(
store=store,
as_of_session="2026-08-25",
claimant="paper-command-verify",
reconcile_command=_increasing_reconciliation,
runtime_release_receipt=_runtime_receipt(),
expected_strategy_release=_release_identity(),
expected_command_binding={"platform": "longbridge"},
)

assert result["status"] == "blocked"
assert result["reason"] == "command_binding_invalid"
assert store.current_state(command) is ExecutionCommandState.QUEUED