Both verbs compose a single atomic action whose None return (the verb's
own result) flowed out of run_intent and was dereferenced as t.status,
HTTP 500-ing with no actionable rejection:
- F016 submit_root: submit_for_review returns None when the root->master
PR was already opened / the task raced out of in_progress. Post-runner
None-guard extracted into _submit_root_finalize -> invalid_state
(re-fetch; if awaiting_pr_review the PR is open, wait for reviewer;
else re-delegate fixes and retry) instead of None.status.
- F017 i_am_blocked: escalate returns None in four cases (no task, no
agent, no resolvable escalation-target slug, no target agent row) e.g.
a developer whose role has no PM above it. _run_i_am_blocked_intent
now guards updated is None -> (t, invalid_state rejection) with
remediation (re-fetch + escalate to CEO directly / retry) instead of
the caller deref'ing None.status -> 500 + respawn-loop.
TDD red->green; ruff + mypy clean; gateway suite green (58 passed).