Background
Three smaller PR #663 follow-ups grouped by theme: dispatch-gate observability, dispatch-gate recursion-marker robustness, and post-calibration default-mode flip for the inline-mission gate.
Task A — F12 None-actor observability
task_lifecycle_gate.evaluate_lifecycle currently skips advisory rule emission when trustworthy_actor_name(...) returns None (cannot resolve the actor from the harness-trustworthy actor-identity chain). Skip-on-unresolvable was an explicit decision documented in test_skips_when_actor_unresolvable and architect-blind's S-2 sketch.
Add observability so this skip is not invisible:
- Counter-incrementing log when the gate skips due to None actor (stderr only, not journal — keep PostToolUse silent)
- Optional metrics endpoint or
~/.claude/teams/{team}/lifecycle-skip-count.json writeback for periodic audit
- Counter-test: synthesize a None-actor input and confirm the counter increments
The goal is to detect drift if None-actor cases become common (would indicate the actor-identity chain is broken upstream). Surfaced as architect-blind sketch D.
Task B — F12 recursion-marker harness-stamp
task_lifecycle_gate emits metadata.completion_disputed writebacks when it detects a self-completion outside the carve-outs. To prevent the writeback itself from re-triggering the gate (recursion), it sets metadata.gate_writeback as a self-skip marker.
The current marker is a plain boolean. Harden against drift by stamping the harness identity into the marker:
metadata["gate_writeback"] = {
"v": 1,
"stamped_at": utc_iso(),
"harness": pact_session_context.get("plugin_root", "unknown"),
}
Read-side: skip when gate_writeback["v"] == 1 AND the writeback is recent (within session). This bounds the marker's effect to the current harness; an old session's marker on a re-loaded task does not skip a new gate evaluation.
Surfaced as security-blind S-12 sketch E.
Task C — Post-calibration default-flip for inline-mission gate
PACT_DISPATCH_INLINE_MISSION_MODE ships in v4.1.3 with default warn (advisory) and tri-state values warn|deny|shadow. Default is warn so calibration data accumulates without blocking production dispatches.
After ~30 days of warn-mode telemetry:
- Review the
long_inline_mission warn-rate. If <1% of dispatches trip the threshold (800 chars), confidence that the threshold is well-calibrated.
- Flip default to
deny. Add migration note. Bump plugin minor version (this changes user-observable behavior).
- Existing
warn users can opt out via env-var.
If the warn-rate is higher than expected (>5%), the threshold needs adjustment before flipping. Use the warn-mode data to find the right threshold. The 800-char number was a security-team estimate, not empirical.
Surfaced by backend-coder-blind in PR #663 review as F-11.
Relationship to PR #663
Test plan
Task A: parametrized tests for None-actor cases. Counter-test by removing the counter increment and confirming an audit test catches missing telemetry.
Task B: round-trip test that asserts a v1 marker round-trips through evaluate_lifecycle without re-triggering the gate. Counter-test by stripping v from the marker and confirming the gate re-evaluates.
Task C: the flip is a one-line default change plus migration note. Test plan is calibration-data review, not unit tests.
Background
Three smaller PR #663 follow-ups grouped by theme: dispatch-gate observability, dispatch-gate recursion-marker robustness, and post-calibration default-mode flip for the inline-mission gate.
Task A — F12 None-actor observability
task_lifecycle_gate.evaluate_lifecyclecurrently skips advisory rule emission whentrustworthy_actor_name(...)returns None (cannot resolve the actor from the harness-trustworthy actor-identity chain). Skip-on-unresolvable was an explicit decision documented intest_skips_when_actor_unresolvableand architect-blind's S-2 sketch.Add observability so this skip is not invisible:
~/.claude/teams/{team}/lifecycle-skip-count.jsonwriteback for periodic auditThe goal is to detect drift if None-actor cases become common (would indicate the actor-identity chain is broken upstream). Surfaced as architect-blind sketch D.
Task B — F12 recursion-marker harness-stamp
task_lifecycle_gateemitsmetadata.completion_disputedwritebacks when it detects a self-completion outside the carve-outs. To prevent the writeback itself from re-triggering the gate (recursion), it setsmetadata.gate_writebackas a self-skip marker.The current marker is a plain boolean. Harden against drift by stamping the harness identity into the marker:
Read-side: skip when
gate_writeback["v"] == 1AND the writeback is recent (within session). This bounds the marker's effect to the current harness; an old session's marker on a re-loaded task does not skip a new gate evaluation.Surfaced as security-blind S-12 sketch E.
Task C — Post-calibration default-flip for inline-mission gate
PACT_DISPATCH_INLINE_MISSION_MODEships in v4.1.3 with defaultwarn(advisory) and tri-state valueswarn|deny|shadow. Default iswarnso calibration data accumulates without blocking production dispatches.After ~30 days of
warn-mode telemetry:long_inline_missionwarn-rate. If <1% of dispatches trip the threshold (800 chars), confidence that the threshold is well-calibrated.deny. Add migration note. Bump plugin minor version (this changes user-observable behavior).warnusers can opt out via env-var.If the warn-rate is higher than expected (>5%), the threshold needs adjustment before flipping. Use the warn-mode data to find the right threshold. The 800-char number was a security-team estimate, not empirical.
Surfaced by backend-coder-blind in PR #663 review as F-11.
Relationship to PR #663
task_lifecycle_gate.pyintroduced by PR Dispatch-protocol hardening: rename Task→Agent + dispatch_gate + task_lifecycle_gate + bootstrap_gate F24/F25 (#662) #663.dispatch_gate.pyintroduced by PR Dispatch-protocol hardening: rename Task→Agent + dispatch_gate + task_lifecycle_gate + bootstrap_gate F24/F25 (#662) #663. The env-var read site (PACT_DISPATCH_INLINE_MISSION_MODE) and thelong_inline_missionrule are the surfaces to modify.Test plan
Task A: parametrized tests for None-actor cases. Counter-test by removing the counter increment and confirming an audit test catches missing telemetry.
Task B: round-trip test that asserts a v1 marker round-trips through evaluate_lifecycle without re-triggering the gate. Counter-test by stripping
vfrom the marker and confirming the gate re-evaluates.Task C: the flip is a one-line default change plus migration note. Test plan is calibration-data review, not unit tests.