| Komponente | Status | Verfügbar für MH-4b |
MH-1: 8 managed.* CommandTypes + Validators in CommandTypeRegistry | ea11637 | register_managed_proposal / approve_managed_proposal / reject_managed_proposal validators ready — Service nutzt $registry->get('...') |
MH-2: ManagedStateReader + ProposalReader dormant | 50cc5c2 | Worker-only — Service touched NICHT |
MH-3a: RiskProposalEngine dry-run | 26c8ab3 | Worker-only — Service touched NICHT |
MH-3b: ProposalWriter immutable | 62a08f4 | Worker-only — Service touched NICHT |
MH-4a: managed_proposals + managed_assets_history + ManagedProposalState Enum + Models | 83dafca | direkt konsumiert — Service READ-only auf managed_proposals für approve/reject Validation |
ManagedStateService (pause/resume/release) | MH-4c | nicht in MH-4b Scope |
Worker-Handler _handle_* | MH-6 | nicht in MH-4b |
| Filament Wizard | MH-5 | nicht in MH-4b |
| Frage | Antwort |
Wie wird proposal_json aus Datei in DB-Cache gespiegelt? | NICHT in MH-4b. Worker (MH-6) liest on-disk risk_proposals/<id>.json und UPDATE managed_proposals SET proposal_json=... NACH Engine.generate() läuft. MH-4b Service touched proposal_json niemals (auch nicht READ — außer für approve/reject validation, dann reads from DB-cache nicht Datei). |
Wie werden proposal_file_path / proposal_sha256 / proposal_cached_at gesetzt? | NICHT in MH-4b. Worker (MH-6) füllt diese Spalten nach ProposalWriter.apply() aus ApplyResult.target_path / .sha256 / .written_at. |
| Wie wird proposal_id Regex validiert? | Service hat private static helper validateProposalId(string $pid) mit regex /^[a-zA-Z0-9_-]{1,128}$/ (1:1 mirror MH-3b PROPOSAL_ID_REGEX). Aufgerufen in createApproveCommand und createRejectCommand. Nicht in createRequestCommand (proposal_id existiert dort noch nicht). |
| Welche State-Transitions sind erlaubt? | Service erzeugt nur commands, keine State-Transitions. Worker (MH-6) führt Transitions aus mit ManagedStateGuard. Service prüft READ-only: Approve/Reject erfordern state==='risk_proposed' (Caller-Side Validation — Worker macht zusätzlich Guard-Check). |
| Wie werden approve/reject vorbereitet ohne Worker auszuführen? | Service inserts commands.status='pending'. Worker-Daemon pollt commands und führt Handler aus, wenn Worker --once läuft oder Daemon pollt. Service kehrt nach DB::transaction zurück — Operator sieht Command instance + idempotency_key + UUID. |
| Wo endet MH-4b? | Service writes commands+audit_events only. Service liest managed_proposals READ-only. Service ruft NIEMALS Worker. |
| Wo beginnt MH-4c? | ManagedStateService (pause/resume/release). Selbe Pattern, andere CommandTypes. |
| Wo beginnt MH-5? | Filament-Page + Wizard + Filament-Actions die ProposalService-Methoden aufrufen. |
| Wo beginnt MH-6? | Worker-Handler _handle_request_managed_proposal etc. in command_worker.py. Liest Engine, ruft Writer, UPDATEs managed_proposals, INSERTs managed_assets_history. |
| Anforderung | MH-4b-Touch | Verdict |
| JSON Bot-SoT bleibt führend | Service touched keine JSON-Datei. Liest nur DB-Cache (managed_proposals row). | Hybrid C eingehalten |
| DB bleibt nur GUI-Cache | Service liest managed_proposals für Validation. Writes ausschließlich commands + audit_events. | konform |
| Proposal-Dateien bleiben immutable | Service touched NICHT risk_proposals/<id>.json. ProposalWriter wird NICHT aus PHP aufgerufen. | MH-3b Immutability intact |
| Kein ProposalWriter-Aufruf aus PHP | Service-Code importiert NICHT ProposalWriter. AST-pin: kein use App\Services\.*Writer|Engine. (PHP hat das nicht — Bot ist Python.) | Architekturell unmöglich + Test-Pin |
| Kein managed_state.json write | Service touched keine state files. | konform |
| Kein baseline_holdings.json write | Service touched keine state files. | konform |
| Kein command_worker Touch | command_worker.py bleibt unverändert. | konform |
| Kein Bot-Wiring | main.py / live_trade.py / paper_trade.py / risk_manager.py bleiben unverändert. | konform |
| Kein Filament Wizard | MH-5. Service ist Builder, nicht UI. | konform |
| Kein Mainnet | Service hard-restricts environment='testnet' in Payload-Core. CommandTypeRegistry-Validator prüft das zusätzlich. | Defense-in-Depth |
| # | Risiko | Severity | Mitigation |
| R1 | Approve auf Proposal mit proposal_json IS NULL — Worker hat Initial-INSERT noch nicht durchgeführt | MEDIUM | Service::createApproveCommand prüft proposal_json IS NOT NULL vor INSERT. Throws ProposalNotCachedException sonst. Test simuliert die Race. |
| R2 | proposal_id-Regex-Drift PHP↔Bot — MH-3b Bot-Regex und PHP-Service-Regex könnten divergieren | LOW | Private const PROPOSAL_ID_REGEX = '/^[a-zA-Z0-9_-]{1,128}$/' im Service + Test-Pin (test_proposal_id_regex_parity_with_bot) der gegen hardcoded mirror der MH-3b regex prüft. |
| R3 | Confidence-Override-Bypass — Operator setzt confidenceOverride=true ohne reale Notwendigkeit | LOW | Service prüft score < 0.50 UND benötigt explicit confidenceOverride=true. Audit-Metadata kennzeichnet confidence_override_used=true für Audit-Trail. Test: 3 Pfade (high-score happy, low-score-no-override exception, low-score-with-override happy + audit-flag). |
| R4 | Hard-Confirm-Bypass — User klickt approve mit falschem Confirm-String | LOW | Service exact-match-validates hardConfirm === "{$asset}:{$variant}" (case-sensitive). HardConfirmMismatchException. Test: 5 Pfade (mismatch case, mismatch asset, mismatch variant, lowercase mismatch, happy). |
| R5 | Approve+Reject Race (gleiche proposal_id) | LOW | Shared idempotency-key mh:decide:<pid>. DB-Unique-Constraint auf commands.idempotency_key (existing). Erste schreibt → ok; zweite findet existing → returnt existing Command (kein 2nd INSERT). Test: simuliert Race. |
| R6 | Service insertet managed_proposals row prematurely (proposal_id noch nicht bekannt im createRequestCommand) | MEDIUM | Architektur-Entscheidung MH-4b: Service INSERTs managed_proposals NICHT. Worker (MH-6) macht das nach Engine-Run wenn proposal_id existiert. Hierdurch entfällt die "tentative proposal_id"-Problematik. Plan-Review-Doc pinnt das. |
| R7 | Worker hat Initial-INSERT noch nicht — GUI zeigt nichts | LOW | UX-Issue, kein Service-Bug. Filament (MH-5) zeigt "Proposal angefragt, Worker pending" status basierend auf commands.status='pending' ohne managed_proposals row. Erst nach Worker-Run erscheint Row. |
| R8 | AuditMetadataScrubber-Bypass | LOW | Service nutzt existing scrubber, Pattern aus ApplyBaselineService. Test pinnt. |
| R9 | DB::transaction-Rollback bei Audit-Insert-Failure | LOW | DB::transaction wirft automatic rollback bei Exception. Test: simuliert AuditEvent::create() failure → kein Command row übrig. |
| R10 | Idempotency-Key-Collision verschiedener Operators | LOW | createRequestCommand idempotency mh:propose:{asset}:{YmdHis} ist Operator-agnostic. Wenn zwei Operators denselben Asset in derselben Sekunde anfragen → second returns first's command (audit-trail erhalten). Akzeptable Semantik. |
| Test-Klasse | Anzahl | Inhalt |
| ProposalServiceCreateRequestTest | 7 | happy path (commands + audit inserted) · asset regex validation · userId positive int · intent length cap · idempotency same-second collision · CommandTypeRegistry validator integration · environment=testnet hardcoded |
| ProposalServiceCreateApproveTest | 10 | happy path · proposalId regex · proposal-not-found · state-not-risk_proposed · already-decided · proposal_json-not-cached · hardConfirm mismatch (5 cases) · confidence-override-required · confidence-override-accepted (audit flag) · overrides whitelist (rejects strategy_group) |
| ProposalServiceCreateRejectTest | 6 | happy path · proposalId regex · proposal-not-found · state-not-risk_proposed · already-decided · reason non-empty |
| IdempotencyTests | 4 | request: same-second double-click returns existing · approve+reject share key · reject after approve returns approve's command · DB-Unique-Constraint blocks 2nd INSERT |
| MainnetGuardTest | 3 | Service payload always has environment='testnet' · Service rejects payload with environment='mainnet' explicit · CommandTypeRegistry validator blocks mainnet |
| AuditPrefixTest | 3 | All audit_events have managed. prefix · no baseline.* / runtime_config.* leakage · scrubber called for metadata |
| DbTransactionAtomicityTest | 3 | failed audit insert rolls back command · failed command insert rolls back nothing (atomicity preserved) · no orphan rows |
| ProposalIdRegexParityTest | 2 | PHP regex matches MH-3b Bot regex char-for-char · accept/reject cases match Bot tests |
| CustomExceptionTests | 4 | ProposalAlreadyDecidedException · HardConfirmMismatchException · ConfidenceOverrideRequiredException · ProposalNotCachedException (new for R1) |
| Total | ~42 Tests | (etwas über Roadmap-Schätzung ~30) |
| Datei | Status | Kategorie |
gui/app/Services/Managed/ProposalService.php | NEU | Builder Service |
gui/app/Exceptions/ProposalAlreadyDecidedException.php | NEU | Custom Exception |
gui/app/Exceptions/HardConfirmMismatchException.php | NEU | Custom Exception |
gui/app/Exceptions/ConfidenceOverrideRequiredException.php | NEU | Custom Exception |
gui/app/Exceptions/ProposalNotCachedException.php | NEU | Custom Exception (R1) |
gui/tests/Feature/ProposalServiceCreateRequestTest.php | NEU | Test |
gui/tests/Feature/ProposalServiceCreateApproveTest.php | NEU | Test |
gui/tests/Feature/ProposalServiceCreateRejectTest.php | NEU | Test |
gui/tests/Feature/ProposalServiceIdempotencyTest.php | NEU | Test |
gui/tests/Feature/ProposalServiceMainnetGuardTest.php | NEU | Test |
gui/tests/Feature/ProposalServiceAuditPrefixTest.php | NEU | Test |
gui/tests/Feature/ProposalServiceAtomicityTest.php | NEU | Test |
gui/tests/Feature/ProposalIdRegexParityTest.php | NEU | Test |
| ID | Stop wenn… |
| MH-4b-SR-1 | Service schreibt nach risk_proposals/*.json / managed_state.json / baseline_holdings.json |
| MH-4b-SR-2 | Service INSERT auf managed_proposals oder managed_assets_history |
| MH-4b-SR-3 | Service UPDATE auf managed_proposals |
| MH-4b-SR-4 | Service akzeptiert environment != 'testnet' |
| MH-4b-SR-5 | Service ruft Command::update() oder triggert Worker (shell_exec, process etc.) |
| MH-4b-SR-6 | Service-Methode NICHT in DB::transaction |
| MH-4b-SR-7 | proposal_id Regex weicht von MH-3b ab (Parity-Test bricht) |
| MH-4b-SR-8 | hardConfirm Validierung ist case-insensitive |
| MH-4b-SR-9 | confidenceOverride=true ohne audit-metadata-flag |
| MH-4b-SR-10 | Audit-Event-Prefix ist nicht managed.* |
| MH-4b-SR-11 | proposal_json IS NULL Pfad in createApproveCommand fehlt |
| MH-4b-SR-12 | Service ruft Engine.generate() / Writer.apply() (Python-side — architektonisch unmöglich, aber Test-Pin) |