"""Presentation contract: goal state is distinct from execution outcome.""" import re FAILURES = {'failed', 'check_failed', 'publish_failed', 'deploy_failed', 'limit_reached', 'no_changes', 'needs_input', 'not_applicable'} QUESTIONS = {'inspector_rueckfrage', 'fixer_rueckfrage', 'entwurf_frage', 'operator_klaerung_noetig'} FAILED_PHASES = {'planung_mit_feedback', 'durch_kinder_blockiert', 'inspector_fehlgeschlagen', 'fixer_fehlgeschlagen', 'recherche_fehlgeschlagen', 'entwurf_fehlgeschlagen'} def verified_deployment(card): return bool(card.get('deployment_status') == 'deployed' and re.fullmatch(r'[0-9a-f]{40}', str(card.get('deployed_commit_hash', ''))) and str(card.get('activated_system', '')).startswith('/nix/store/')) def presentation(card, children=(), active=False): phase = card.get('phase', 'geplant') status = card.get('last_run_status') or 'not_started' group, label, actor, reason = 'ready', 'Ready', 'You', 'Choose the next action.' blocking = [c['id'] for c in children if c.get('last_run_status') in FAILURES or c.get('phase') in QUESTIONS | FAILED_PHASES] if phase == 'archiviert': group, label, actor, reason = 'archived', 'Archived', 'You', 'Reactivate to continue; archiving does not cancel work.' elif blocking: group, label, actor, reason = 'attention', 'Blocked by children', 'You', ', '.join(blocking) elif phase in QUESTIONS: group, label, actor, reason = 'attention', 'Needs your input', 'You', card.get('clarification_question') or card.get('human_action') or 'Answer the outstanding question.' elif phase in FAILED_PHASES or status in FAILURES: group, label, actor = 'attention', 'Needs attention', 'You' label = {'planung_mit_feedback': 'Review rejected', 'durch_kinder_blockiert': 'Blocked by children'}.get(phase, {'no_changes': 'No change — verify outcome', 'publish_failed': 'Publication failed', 'deploy_failed': 'Deployment failed', 'not_applicable': 'No applicable repair'}.get(status, label)) reason = card.get('failure_detail') or card.get('human_action') or 'Read the result and choose a recovery action.' elif phase == 'deployed': if verified_deployment(card): group, label, actor, reason = 'done', 'Deployed', 'None', 'Reviewed revision activated successfully.' else: group, label, actor, reason = 'attention', 'Deployment unverified', 'You', 'Legacy/manual status has no verified activation receipt. Diagnose before restarting work.' elif phase == 'bereit_fuer_deployment': group, label, actor, reason = 'deploy', 'Deploying', 'Deployment runner', 'Positive review automatically releases activation.' if children and all(verified_deployment(c) for c in children): group, label, actor, reason = 'done', 'Children deployed', 'None', 'All child revisions have activation receipts.' elif phase == 'recherche_fertig': group, label, actor, reason = 'done', 'Research complete', 'You', 'Read the findings before planning implementation.' elif phase in {'wartet_auf_kinder', 'aufgeteilt'}: group, label, actor, reason = 'running', 'Waiting for children', 'Scheduler', 'Child outcomes determine completion.' elif card.get('source') == 'idea': group, label, actor, reason = 'backlog', 'Idea', 'You', 'Refine this idea with the Planner.' elif phase not in {'geplant', 'recherche_angefordert'}: group, label, actor, reason = 'running', 'Running' if active else 'Queued / awaiting result', 'Worker', 'Waiting for a durable worker result.' elif card.get('blocked_by'): group, label, actor, reason = 'ready', 'Waiting for dependencies', 'Scheduler', ', '.join(card['blocked_by']) return {'group': group, 'label': label, 'next_actor': actor, 'reason': reason, 'job_state': 'active' if active else 'idle', 'last_outcome': status, 'blocking_children': blocking, 'children_total': len(children), 'children_successful': sum(verified_deployment(c) or c.get('phase') == 'recherche_fertig' for c in children)}