MH-4 PHP Services + DB-Cache-Tables — Plan-Review

Projekt: Steve-TradingBot · Phase: RECON-MH-4 · Author: claude-opus-4-7[1m]
Generated: 2026-05-12 18:52 UTC · master HEAD: 62a08f4 (MH-3b closed)
Status: NO CODE Plan-Review only — Operator-GO erforderlich vor MH-4 Code-Phase


1 — Live-State Verification

CheckSollIstOK
master HEAD62a08f462a08f4 mh-3b: add immutable proposal writer for risk proposal files
git statuscleanempty output
Bot in-container PID2998429984 python3 main.py --paper
Worker Host PID338185running (Healthcheck-Drift, kein Funktionsproblem)
cmd 13cancelled13 | apply_baseline_holdings | cancelled
BINANCE_TESTNETtrueBINANCE_TESTNET=true
runtime_config.jsonabsentnot present
baseline_holdings.jsonabsentnot present
managed_state.jsonabsentnot present
state/risk_proposals/absentnot present (test-only via tempfile sandbox)
Tracebacks last 200 lines00 matches

2 — Artefakt-Konsum (MH-0.5 → MH-3b)

KomponenteStatusBedeutung für MH-4
MH-1: 8 managed.* CommandTypes in CommandTypeRegistryea11637Services nutzen CommandTypeRegistry::makeCommandPayload() für Validierung
MH-2: ManagedStateReader + ProposalReader dormant50cc5c2Worker (MH-6) konsumiert; Service touched NICHT
MH-3a: RiskProposalEngine V1-Minimal, dry_run-only26c8ab3Worker (MH-6) calls engine; Service touched NICHT
MH-3b: ProposalWriter immutable atomic no-overwrite62a08f4Worker (MH-6) calls writer; Service touched NICHT
Worker-Handler managed.*nicht vorhandenMH-6
Filament ManagedHoldings Page / Wizardnicht vorhandenMH-5
managed_proposals / managed_assets_history Tablesnicht vorhandenMH-4 Migration
ProposalService / ManagedStateService PHPnicht vorhandenMH-4 Code

Pattern-Referenz: gui/app/Services/Apply/Baseline/ApplyBaselineService.php (existing, RECON-2.3). Behält:

3 — MH-4 Scope aus Roadmap (verifiziert)

Canonical MH-Phase-Tabelle (aus 00_overview.md §4)

MH-PhaseInhaltRestart?Migration?Mainnet?Risk
MH-3 ✅Engine (MH-3a) + Writer (MH-3b)nononomedium
MH-4PHP ProposalService + ManagedStateService + Testsnoyes (DB-Cache-Tabellen)nomedium
MH-5Filament Multi-Step Wizardnononomedium
MH-6Bot-Worker Handler für 8 Command-Typesnononomedium
MH-7Bot-Side Wiringyesnonohigh

Was MH-4 GENAU liefert

  1. DB-Migration: 2 neue Tabellen (managed_proposals, managed_assets_history)
  2. Eloquent Models: ManagedProposal, ManagedAssetHistory
  3. ProposalService mit 3 Builder-Methoden:
  4. ManagedStateService mit 3 Builder-Methoden:
  5. ~30 PHPUnit Tests (Builder + Idempotency + Validator + Migration up/down)

Was MH-4 NICHT liefert (BACKLOG für andere MH-Phasen)

TabuPhase
Filament Page / Wizard / ActionsMH-5
Worker-Handler _handle_request_managed_proposal etc.MH-6
Engine-Aufruf (ProposalEngine.generate)MH-6 (Worker)
JSON-Writes (managed_state.json, risk_proposals/)MH-6 (Worker)
flag_managed_drift (Bot-Self-Emit)MH-7 (Bot-Side per-cycle Hook)
dry_run_promote_managed (optional in MH-4 oder MH-5 Wizard)tbd
audit_snapshot files state/audit_snapshots/<id>.jsonMH-6 (Worker SM-6)
Two-File-Atomic (managed_state + baseline_holdings)MH-6 (Worker)

Service-Verantwortlichkeiten (per 05_commandbus_worker §1 Pipeline)

Operator-Klick (GUI / MH-5)
   ↓
Filament Action (admin-only, MH-5)
   ↓
Service.createXxxCommand()                           ← MH-4
   ↓
DB::transaction:
   1. INSERT commands (status='pending', idempotency_key='mh:...')
   2. INSERT audit_events (managed.asset_proposal_requested, etc.)
   3. INSERT managed_proposals (für proposal-create: state='proposal_pending')
   ↓
Worker --once / Daemon                               ← MH-6
   ↓
claim_next() → _handle_<type>() →
   - load Readers (Baseline / Managed / Proposal)
   - call Engine.generate / Writer.apply / ProposalReader.read
   - UPDATE managed_proposals (state='risk_proposed', proposal_json=...)
   - INSERT managed_assets_history
   - emit audit_event managed.* (succeeded)
   - write audit_snapshot file

4 — Konfliktcheck Sonder-Anforderungen

AnforderungMH-4-TouchVerdict
Q-MH-15 Hybrid C (JSON=SoT, DB=Cache)Service schreibt NUR DB (commands + audit_events + managed_proposals.initial_row). Niemals JSON. Worker (MH-6) macht JSON-first-then-DB-update.konform
G-DR-14 Two-File-AtomicService touched NIE managed_state.json oder baseline_holdings.json. Pattern gilt für Worker (MH-6).kein Konflikt
Proposal-Immutability (MH-3b)Service INSERTs managed_proposals mit proposal_json=NULL (initial); Worker fillt es nach Engine-Run. Niemals UPDATE auf einer abgeschlossenen Proposal-Row mit decided_at IS NOT NULL. Service-Validator prüft das in createApproveCommand / createRejectCommand.konform
Kein managed transferService-Layer berührt nie baseline_holdings.json. release_managed_asset schreibt nur commands.payload; Worker setzt später frozen zurück.konform
Kein synthetic entry applysynthetic_entry ist im proposal_json Feld (DB-cache, JSON-Spalte). Service NICHT setzen; Worker setzt es nach Engine-Output.konform
Kein Worker-HandlerMH-4 PHP-only. command_worker.py bleibt unverändert.konform
Kein Bot-Wiringmain.py / paper_trade.py / live_trade.py bleiben unverändert.konform
Kein MainnetService hard-restricts environment = 'testnet' in jedem Builder (Pattern aus ApplyBaselineService).Defense-in-Depth Layer 5

5 — Identifizierte Risiken

#RisikoSeverityMitigation
R1Migration auf live GUI-DBtradingbot_gui hostet commands, audit_events, baseline, runtime; jede Migration berührt diese DBMEDIUM(a) Nur additive CREATE TABLE (keine ALTER). (b) down() mit dropIfExists rollback-able. (c) pg_dump Backup vor Migration-Run. (d) Migration läuft nur in testnet-DB (Mainnet-DB existiert nicht — Mainnet ist hard-blocked). (e) Schema-only, keine Backfill-Daten.
R2State-Enum-Drift PHP↔Bot — die state-Spalte in managed_proposals muss enum-konsistent sein zur Bot-Side State-MachineMEDIUMService definiert PHP-Konstante ManagedProposalStates::ALL parallel zu Bot-Side KNOWN_STATES in managed_state_reader.py. AST-Parity-Test in MH-4 prüft beide Listen stimmen überein (analog G6.5 Parity-Test).
R3Idempotency-Key approve/reject Race — Operator klickt approve und reject simultan; beide schreiben mit gleichem Key mh:decide:<proposal_id>LOWDB-Unique-Constraint auf commands.idempotency_key (existiert bereits). Service-Code nutzt INSERT ... ON CONFLICT (idempotency_key) DO NOTHING Pattern. Test simuliert Race.
R4Proposal-not-found bei approve/reject — User sieht stale GUI; Proposal ist bereits expired / decidedLOWService::createApproveCommand validiert managed_proposals.state IN ('risk_proposed') + decided_at IS NULL vor Command-Insert. Throws ProposalAlreadyDecidedException sonst.
R5Wizard Hard-Confirm-Pattern (Q-MH-11)<asset>:<variant> String muss matchenMEDIUMService::createApproveCommand bekommt hardConfirmString-Param; validiert genau-match gegen <asset>:<variant> (case-sensitive). Throws HardConfirmMismatchException. Pattern analog G10-3b ApplyProfile.
R6Confidence-Override-Flag bei niedrigem Score (G-SR-9)LOWService::createApproveCommand prüft confidence.overall_score < 0.50 → benötigt confidence_override=true Param. Sonst ConfidenceOverrideRequiredException.
R7Audit-Event-Prefix-Drift (G-DR-11)LOWAlle managed.* audit-events nutzen 'managed.' Prefix; Test pinnt das.
R8AuditMetadataScrubber-BypassLOWService nutzt AuditMetadataScrubber (existing, G9-2) für metadata-Felder. Pattern aus ApplyBaselineService übernommen.
R9Migration-Rollback-Cost wenn schon Daten drin sindLOWInitial roll-out hat 0 Rows in managed_proposals. Rollback = dropTable. Bei späterem Rollback nach Production-Use: separate Backup-Restore-Phase.
R10DB-Cache-Drift wenn Worker JSON schreibt aber DB-INSERT fehlschlägtMEDIUM (für MH-6)NICHT MH-4-Scope — wird in MH-6 Worker-Handler via managed.cache_drift_detected audit + Retry-Logik gehandhabt. Service-side nur initial INSERT, immer in DB::transaction.

6 — Test-Plan

Test-KlasseAnzahlInhalt
ManagedProposalsMigrationTest3up creates tables + indexes; down rolls back; idempotent re-migration
ManagedAssetsHistoryMigrationTest3up + down + index verification
ManagedProposalModelTest4model-cast (proposal_json as array); fillable; state-enum allowed values; relations to User
ManagedAssetHistoryModelTest3model-cast; fillable; relations
ProposalServiceCreateRequestTest5INSERT commands + audit + managed_proposals; idempotency; environment=testnet hardcoded; CommandType validator; payload schema
ProposalServiceCreateApproveTest6INSERT commands + audit; idempotency-key collision; proposal-not-found rejection; hard-confirm mismatch rejection; confidence-override required path; happy path
ProposalServiceCreateRejectTest4INSERT commands + audit; same idempotency key as approve; reason required; happy path
ManagedStateServiceCreatePauseTest3INSERT commands + audit; distinct key per click; happy path
ManagedStateServiceCreateResumeTest3analog pause
ManagedStateServiceCreateReleaseTest4strategy-param required; INSERT + audit; happy path; testnet-only
ManagedProposalStatesEnumTest2enum values pinned (10 states); parity with Bot-side KNOWN_STATES (via fixture / hardcoded mirror)
MainnetGuardTest3every Service hard-restricts environment='testnet'; Service rejects payload with environment='mainnet'
AuditPrefixTest2every audit_event has managed. prefix; no baseline.* / runtime_config.* / apply_profile_testnet.* prefix bleed
DbTransactionAtomicityTest3failed INSERT mid-transaction rolls back; no orphan commands without audit_event; no orphan managed_proposals without command
Total~48 Tests(etwas über Roadmap-Schätzung ~30; Coverage-Reserve für Edge-Cases)

7 — Betroffene Dateien (Final-Inventory)

Neue Files

DateiStatusKategorie
gui/database/migrations/2026_05_xx_xxxxxx_create_managed_proposals_table.phpNEUMigration
gui/database/migrations/2026_05_xx_xxxxxx_create_managed_assets_history_table.phpNEUMigration
gui/app/Models/ManagedProposal.phpNEUEloquent Model
gui/app/Models/ManagedAssetHistory.phpNEUEloquent Model
gui/app/Enums/ManagedProposalState.phpNEUPHP Enum (10 states)
gui/app/Services/Managed/ProposalService.phpNEUBuilder Service
gui/app/Services/Managed/ManagedStateService.phpNEUBuilder Service
gui/app/Exceptions/ProposalAlreadyDecidedException.phpNEUCustom Exception
gui/app/Exceptions/HardConfirmMismatchException.phpNEUCustom Exception
gui/app/Exceptions/ConfidenceOverrideRequiredException.phpNEUCustom Exception
gui/tests/Feature/ManagedProposalsMigrationTest.phpNEUTest
gui/tests/Feature/ProposalServiceTest.phpNEUTest
gui/tests/Feature/ManagedStateServiceTest.phpNEUTest
gui/tests/Unit/ManagedProposalStatesEnumTest.phpNEUTest

Unverändert

Erwartete LOC: ~600 PHP services + ~150 migrations/models + ~50 enum + ~900 tests = ~1700 LOC total.

8 — DB-Migrations-Plan

Migration 1: managed_proposals

CREATE TABLE managed_proposals (
    proposal_id           UUID PRIMARY KEY,
    asset                 VARCHAR(32) NOT NULL,
    state                 VARCHAR(32) NOT NULL,
    proposal_json         JSON,
    generated_at          TIMESTAMP,
    expires_at            TIMESTAMP,
    decided_at            TIMESTAMP NULL,
    requested_by_user_id  BIGINT NULL REFERENCES users(id) ON DELETE SET NULL,
    decided_by_user_id    BIGINT NULL REFERENCES users(id) ON DELETE SET NULL,
    decision              VARCHAR(16) NULL,
    created_at            TIMESTAMP DEFAULT now(),
    updated_at            TIMESTAMP DEFAULT now()
);
CREATE INDEX idx_managed_proposals_state    ON managed_proposals(state);
CREATE INDEX idx_managed_proposals_asset    ON managed_proposals(asset);
CREATE INDEX idx_managed_proposals_expires  ON managed_proposals(expires_at);

Migration 2: managed_assets_history

CREATE TABLE managed_assets_history (
    id            BIGSERIAL PRIMARY KEY,
    asset         VARCHAR(32) NOT NULL,
    state_from    VARCHAR(32),
    state_to      VARCHAR(32) NOT NULL,
    actor         VARCHAR(64) NOT NULL,
    event_type    VARCHAR(64) NOT NULL,
    metadata_json JSON,
    ts            TIMESTAMP NOT NULL
);
CREATE INDEX idx_managed_history_asset_ts ON managed_assets_history(asset, ts);
CREATE INDEX idx_managed_history_event    ON managed_assets_history(event_type);

Rollback Plan

Migration-Workflow

  1. Pre-Migration Backup: pg_dump -Fc -d tradingbot_gui > /srv/shares/backups/mh-4-pre-migration-<UTC>.pgdump
  2. php artisan migrate --pretend → SQL preview
  3. php artisan migrate --path=database/migrations/2026_05_xx_xxxxxx_create_managed_proposals_table.php
  4. php artisan migrate --path=database/migrations/2026_05_xx_xxxxxx_create_managed_assets_history_table.php
  5. Verifizieren: \d managed_proposals + \d managed_assets_history + INDEX-Check
  6. Rollback-Test in Staging-DB (falls vorhanden) oder unittest-suite-Sandbox

9 — Stop-Regeln MH-4

IDStop wenn…
MH-4-SR-1Service schreibt direkt nach managed_state.json / baseline_holdings.json / risk_proposals/*.json
MH-4-SR-2Service spawned Worker oder triggert command_worker.py --once
MH-4-SR-3Service akzeptiert environment != 'testnet'
MH-4-SR-4Migration verändert eine existierende Tabelle (ALTER auf commands / audit_events / baseline_holdings)
MH-4-SR-5Idempotency-Key fehlt bei approve_managed_proposal oder reject_managed_proposal
MH-4-SR-6audit_event-Prefix ist nicht managed.* (Bleed in baseline.* / runtime_config.*)
MH-4-SR-7Migration läuft auf Mainnet-DB (es gibt keine; Mainnet hard-blocked)
MH-4-SR-8Service ruft Engine.generate() oder Writer.apply() direkt auf (das ist Worker-Job)
MH-4-SR-9Eloquent-Model erlaubt state ausserhalb der 10 KNOWN_STATES
MH-4-SR-10State-Enum-Parity-Test PHP↔Bot bricht
MH-4-SR-11Service-Methode NICHT in DB::transaction (atomic INSERTs gebrochen)
MH-4-SR-12Hard-Confirm-String nicht case-sensitive validiert

10 — Backup / Restart-Bedarf

AktionPflicht?Begründung
pg_dump GUI-DBJAdurable rule feedback_backup_before_live_actions.md — Migration läuft auf live DB
live_portfolio.json snapshotNEINkein State-Touch
.env snapshotNEINkeine Mutation
state/ snapshotNEINkein State-Touch
Memory-SicherungNEINClosure-Pin reicht
Bot-RestartNEINkein Bot-Code-Touch
Worker-RestartNEINcommand_worker.py unverändert
docker cpNEINPHP läuft in GUI-Container; Files werden via Live-Volume gemounted (Laravel hot-reload)
GUI-Container Cache-ClearJA (sicherheitshalber)php artisan config:clear && php artisan route:clear && php artisan view:clear nach Migration

MH-4 = Code+Migration+Test+Commit auf master + pg_dump-Backup. Bot bleibt komplett unbehelligt.

11 — Kleinste sichere Code-Phase + GO/NO-GO Empfehlung

Subschnitt-Optionen

VarianteInhaltLOCTestsRiskMigration
MH-4-monolithicAlles zusammen: 2 Migrationen + 2 Services + 6 Methoden + Tests + Enum + Exceptions~1700~48MEDIUMja, atomar
MH-4a ← empfohlenNUR Migration + Eloquent Models + Enum + Migration-Tests + Model-Tests~350~13LOWja
MH-4b (folgt)ProposalService (3 Methoden) + Tests~450~15LOWnein
MH-4c (folgt)ManagedStateService (3 Methoden) + Tests~350~10LOWnein
MH-4d (folgt)DbTransactionAtomicity + MainnetGuard + AuditPrefix Tests~150~10LOWnein

Empfehlung: MH-4a — Migration + Models + Enum atomar zuerst

Begründung:

  1. Maximaler Scope-Lock: Schema-additiv-only, kein Business-Logic-Code. Klar auditierbar.
  2. Migration ist riskiest sub-step: separates Commit → vor Service-Logic isoliert verifizieren
  3. Eloquent Models + Enum = passive data-shape Layer; keine INSERT INTO commands etc.
  4. State-Enum-Parity-Test pinnt PHP↔Bot Konvention sofort beim ersten Commit
  5. Rollback-Cost minimal: migrate:rollback + git revert
  6. MH-4b/c folgen sequenziell: Services bauen auf existing Schema auf, einfacher zu reviewen

Pre-Implementation-Q an Operator

  1. MH-4a Subschnitt oder MH-4-monolithic?Empfehlung MH-4a (max scope-lock, Migration isoliert verifizierbar)
  2. ManagedProposalState als PHP-Enum (Laravel 9+ feature) oder als Plain-String-Constants Class? ← Empfehlung PHP Enum (typsicher, IDE-Autocomplete, Eloquent cast)
  3. Foreign Keys auf users(id): ON DELETE SET NULL oder RESTRICT? Roadmap-Schema sagt SET NULL für requested_by_user_id + decided_by_user_id. ← Empfehlung SET NULL (Audit-Trail durabel, User-Löschung erlaubt)
  4. Migration-Run via php artisan migrate in GUI-Container oder via --pretend + manuell? ← Empfehlung: zuerst --pretend, dann live nach Operator-Approval auf pg_dump-Backup
  5. managed_proposals.proposal_json Type: JSON (Postgres native) oder JSONB? Schema-Doc sagt JSON. ← Empfehlung JSON wie spec sagt; JSONB wäre nice-to-have für Indexing aber MH-4 hat keine JSON-path Queries

Scope-Größe + Risiko-Bewertung MH-4a

AspektBewertung
LOC~350
Tests~13
KomplexitätPattern-Mirror existing migrations + ApplyBaselineService
Blast-RadiusLOW (additive Schema only)
Roll-Back-Costminimal: migrate:rollback + git revert
Dependencies-KlärungQ-MH-15 + State-Enum-Liste (10 states)
Backup-PflichtJA pg_dump
Restart-PflichtNEIN
Mainnet-TouchNEIN
docker cpNEIN

STOP vor Implementierung. Erwarte Operator-GO mit Subschnitt-Wahl (MH-4a empfohlen) + Q1-Q5-Approval.

© Steve-TradingBot · RECON-MH-4 · Plan-Review (no-code phase)