Skip to content

Phase 2 blocker: cold-journal trigger attribution degrades to 'unknown' (affects #481) #485

Description

@michael-wojcik

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions