Implement BAT-006: portfolio-hub ledger integration (REQ-017~019)

- REQ-017: LedgerPort contract (application/ledger_port.py) + portfolio-hub
  adapter + in-memory stub (adapters/ledger/), TREX_PORTFOLIO_HUB_* config
- REQ-018: fill→ledger recording in execution loop; FillStatus carries
  execution_id (deterministic synth) + fee; paper/kis adapters updated
- REQ-019: ledger outbox + retry worker (application/ledger_outbox.py),
  correlation_id idempotency, outcome vocabulary; 002_create_ledger_outbox.sql
- Tests: in-memory/portfolio-hub ledger, execution ledger hook, outbox,
  domain import sweep

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 22:57:31 +09:00
co-authored by Claude Opus 4.8
parent 6277d610ea
commit 13837acc60
20 changed files with 1224 additions and 15 deletions
+46
View File
@@ -0,0 +1,46 @@
"""Static import sweep guarding the pure-domain boundary (REQ-017 AC-4, NFR-1).
`trex_core.domain` must NOT depend on portfolio-hub, HTTP, ledger adapters,
or any other infrastructure module. The sweep walks the source tree of
trex_core/domain and inspects every `import` statement.
"""
from __future__ import annotations
import ast
from pathlib import Path
_DOMAIN_ROOT = Path(__file__).resolve().parents[2] / "trex_core" / "domain"
_FORBIDDEN_PREFIXES = (
"httpx",
"fastapi",
"uvicorn",
"psycopg",
"redis",
"trex_core.adapters", # any adapter (broker, ledger, persistence, api)
"trex_core.runtime", # daemon / config
"trex_core.application", # ports/services live one layer up
)
def _iter_imports(path: Path):
tree = ast.parse(path.read_text(), filename=str(path))
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
yield alias.name
elif isinstance(node, ast.ImportFrom):
if node.module:
yield node.module
def test_domain_has_no_forbidden_imports():
offenders: list[tuple[str, str]] = []
for py in _DOMAIN_ROOT.rglob("*.py"):
for imp in _iter_imports(py):
if any(imp == p or imp.startswith(p + ".") for p in _FORBIDDEN_PREFIXES):
offenders.append((str(py.relative_to(_DOMAIN_ROOT.parent.parent)), imp))
assert offenders == [], (
"trex_core/domain must remain free of adapter/runtime/application imports; "
f"found: {offenders}"
)