Story-2960: backfill run_origin, contract the agent-run predicates

Why a contract step

Story-2959's expand step (0214) left run_origin nullable and kept two individual agent names (vega_matter_frame, atlas_dock_chat) inside agent_run CHECK predicates as a transition tolerance. A predicate that names an agent is a coupling every new agent must edit. This step closes the window: origins are derived for every existing row, the column goes NOT NULL, and no predicate names an agent ever again.

The backfill is derived, not guessed

Every live INSERT path was read before choosing the rule:

WriterRun kindOrigin
workers/framer_trigger/processor.py Worker-minted from the domain-event outbox; the ONLY writer that sets trigger_event_id (its delivery-dedupe key) and the only one that may leave user_id NULL. system — even when the waking event had a user actor; the user acted on the matter, the worker initiated the run.
clerk_worker/runner.py claim, app_api/router_ws.py and agent_launch_core.py blocked-run inserts Launch-edge runs; user_id is always bound. A kind='compact' claim is broker maintenance, never a user turn. user (system for compact claims)
UPDATE agents.agent_run
SET run_origin = CASE
  WHEN trigger_event_id IS NOT NULL THEN 'system'  -- worker-minted
  ELSE 'user' END                                   -- launch-edge-minted
WHERE run_origin IS NULL

All four writers now set run_origin on INSERT, so NOT NULL lands with every path already honouring it.

Shapes, not names

agent_run_user_present contracts to user_id IS NOT NULL OR run_origin = 'system'. agent_run_scope_present keeps the chat/form shapes and replaces both named-agent clauses: a matter-anchored system run (the framer shape) and a scope-free user run (the dock shape — dock_chat launches are contractually forbidden from carrying matter/session/form scope).
0214 (transition)0216 (contracted)
OR agent_key = 'vega_matter_frame' (user + scope) run_origin = 'system' — every framer row is worker-minted, so the backfill makes the name redundant.
OR agent_key = 'atlas_dock_chat' (scope) OR (matter_id IS NULL AND chat_session_id IS NULL AND form_id IS NULL AND run_origin = 'user') — the dock shape named by origin.

Key hygiene (R1: no dead vocabulary)

context_judge STAYS — the Story-2873 compaction rail claims real rows through the clerk worker. record_registrar and indigo are SHED from the run/approval CHECKs: no INSERT path anywhere writes an agent_run row with those keys (the registrar consumer mints a bare run_id for provenance, never a row). EPIC-2834 re-admits its artifact seats with the broker writers that use them. chat_session's key set stays decoupled and untouched.

Proof

Recall

Step 1 widened and kept names as tolerance; step 2 derives origins from who mints the run and lets the contract speak in shapes, so the next agent is a key, not a predicate edit.