Context
Surfaced in PR #477 (#401 teachback gate) round-6 blind architectural review. Must be resolved before Phase 2 flip ships (issue #481).
The gap
_trigger_for_transition at pact-plugin/hooks/teachback_gate.py:492-564 requires both from_state AND to_state to be non-empty to return a named trigger. On cold-journal scenarios — where no prior teachback_state_transition event exists for the task_id in the current session journal — from_state = "" is passed. No named branch matches, so the trigger falls through to "unknown".
Affected trigger attributions (all silently degrade to "unknown" on cold-journal first observation):
lead_approve (cycle-4 R2-A2): under_review → active
content_fixed (cycle-5 M-R4-3): correcting → active
auto_downgrade (cycle-6 M-R5-A): under_review → correcting with unaddressed_items reason
lead_correct (cycle-6 M-R5-A): under_review → correcting with corrections_pending reason
content_invalid (cycle-4 T10): active → teachback_pending
When cold-journal happens
- Session boundary (
claude --resume where task was created in prior session)
- Fresh teammate spawn on existing task
- Any scenario where
_emit_state_transition_if_changed fires before any prior transition was journaled for this task_id
Why this is a Phase 2 gate (not merge gate for #477)
Phase 1 is advisory-mode: the gate doesn't block tool calls based on trigger attribution. Observability is degraded; behavior is unaffected.
Phase 2 flip (issue #481) is where this matters. The Phase-2 auditor's forgery detection depends on distinguishing lead-authored approvals from teammate-authored resubmissions. If cold-journal emits trigger="unknown" instead of the attribution-carrying values, the auditor can't distinguish them from state_transition events alone.
Partial mitigation exists: teachback_gate_advisory.reason carries reason_code via a separate event type. If the Phase-2 auditor correlates across event types ({state_transition.from_state/to_state, advisory.reason, state_transition.task_id}), this is workable. If it relies on state_transition.trigger as self-contained, attribution is lost on session boundaries.
Resolution paths
Three options (decide during Phase 2 design):
Option 1 — explicit cold triggers: extend _trigger_for_transition to accept reason_code on empty-from_state fallback and return reason-specific triggers (e.g., cold_unaddressed, cold_invalid, cold_approve). Preserves attribution on first-observation.
Option 2 — suppress cold emission: don't emit teachback_state_transition at all on cold-journal. Let teachback_gate_advisory be the sole attribution source for first-observation. Simpler journal shape; requires auditor to correlate.
Option 3 — auditor-level correlation: accept current behavior; design Phase-2 auditor to correlate state_transition + advisory by task_id. Zero code change here; audit design burden.
Blocker rationale
Issue #481 (Phase 2 blocking-mode flip) should reference this issue as a prerequisite. Whichever of the 3 options is chosen, the Phase-2 auditor design must accommodate cold-journal attribution.
Related observability
Round-6 reviewers also flagged a convergent MEDIUM: integration-test gap on the auto_downgrade emission wire-up at teachback_gate.py:269. Being addressed in cycle-7 remediation on PR #477 separately.
Context
Surfaced in PR #477 (#401 teachback gate) round-6 blind architectural review. Must be resolved before Phase 2 flip ships (issue #481).
The gap
_trigger_for_transitionatpact-plugin/hooks/teachback_gate.py:492-564requires bothfrom_stateANDto_stateto be non-empty to return a named trigger. On cold-journal scenarios — where no priorteachback_state_transitionevent exists for the task_id in the current session journal —from_state = ""is passed. No named branch matches, so the trigger falls through to"unknown".Affected trigger attributions (all silently degrade to
"unknown"on cold-journal first observation):lead_approve(cycle-4 R2-A2): under_review → activecontent_fixed(cycle-5 M-R4-3): correcting → activeauto_downgrade(cycle-6 M-R5-A): under_review → correcting withunaddressed_itemsreasonlead_correct(cycle-6 M-R5-A): under_review → correcting withcorrections_pendingreasoncontent_invalid(cycle-4 T10): active → teachback_pendingWhen cold-journal happens
claude --resumewhere task was created in prior session)_emit_state_transition_if_changedfires before any prior transition was journaled for this task_idWhy this is a Phase 2 gate (not merge gate for #477)
Phase 1 is advisory-mode: the gate doesn't block tool calls based on trigger attribution. Observability is degraded; behavior is unaffected.
Phase 2 flip (issue #481) is where this matters. The Phase-2 auditor's forgery detection depends on distinguishing lead-authored approvals from teammate-authored resubmissions. If cold-journal emits
trigger="unknown"instead of the attribution-carrying values, the auditor can't distinguish them fromstate_transitionevents alone.Partial mitigation exists:
teachback_gate_advisory.reasoncarriesreason_codevia a separate event type. If the Phase-2 auditor correlates across event types ({state_transition.from_state/to_state, advisory.reason, state_transition.task_id}), this is workable. If it relies onstate_transition.triggeras self-contained, attribution is lost on session boundaries.Resolution paths
Three options (decide during Phase 2 design):
Option 1 — explicit cold triggers: extend
_trigger_for_transitionto acceptreason_codeon empty-from_statefallback and return reason-specific triggers (e.g.,cold_unaddressed,cold_invalid,cold_approve). Preserves attribution on first-observation.Option 2 — suppress cold emission: don't emit
teachback_state_transitionat all on cold-journal. Letteachback_gate_advisorybe the sole attribution source for first-observation. Simpler journal shape; requires auditor to correlate.Option 3 — auditor-level correlation: accept current behavior; design Phase-2 auditor to correlate
state_transition+advisoryby task_id. Zero code change here; audit design burden.Blocker rationale
Issue #481 (Phase 2 blocking-mode flip) should reference this issue as a prerequisite. Whichever of the 3 options is chosen, the Phase-2 auditor design must accommodate cold-journal attribution.
Related observability
Round-6 reviewers also flagged a convergent MEDIUM: integration-test gap on the
auto_downgradeemission wire-up atteachback_gate.py:269. Being addressed in cycle-7 remediation on PR #477 separately.