Context
PR #14061 delivered the #14058 backup failure diagnosis escalation hook+sink and is approved. Vega's review intentionally left a follow-up instead of blocking the PR: ProcessSupervisorService.escalateFailedTaskOutcome() currently builds the recovery-diagnosis inline because PR #14056's buildSupervisedTaskDiagnosis helper is approved but not merged. Importing that helper directly in #14061 would have stacked the PR, which is forbidden for this lane.
Vega and @neo-gpt converged on the semantic class: a failed maintenance task should route as recoveryClass: 'ambiguous', not crash, because the supervisor observed the failure but not its cause. Vega owns aligning the helper after #14056 merges; this ticket owns consuming that helper from the backup escalation path afterward.
Live latest-open sweep: checked latest 30 open issues at 2026-06-26T05:15Z. Closest tickets were #14058 (delivered hook+sink), #14055/#14056 (producer helper), and #14030 (parent backup reliability), but no equivalent post-merge helper-consumption follow-up existed. Targeted GitHub search backup escalation supervised-task diagnosis helper #14056 #14061 returned no open issue. A2A in-flight sweep over the latest 30 messages found Vega's #14061 approval/follow-up and my agreement, but no competing claim. KB/local keyword sweeps found no duplicate for escalateFailedTaskOutcome / buildSupervisedTaskDiagnosis.
The Problem
Leaving the inline diagnosis build in ProcessSupervisorService after #14056 lands would create two producer paths for the same supervised-task failure diagnosis contract. That is exactly the class of drift the producer-core split was meant to avoid: one helper says what a supervised-task failure diagnosis means, while the backup escalation path silently carries a local variant.
There are two small documentation polish points from the same review loop that should travel with this refactor instead of being remembered only in A2A:
RecoveryActuatorService.escalateDiagnosis() now has observable return statuses (escalated, rejected variants) consumed by tests/operators; the return-shape contract should be captured in the ticket/contract ledger surface.
Orchestrator now has a bidirectional ProcessSupervisorService <-> RecoveryActuatorService setter cross-link; a one-line comment should explain that this is service graph reconciliation, not arbitrary circular wiring.
The Architectural Reality
- #14061 intentionally built the diagnosis inline to avoid stacking on unmerged PR #14056.
- #14056 owns the shared producer helper for supervised-task failure/overdue diagnosis events.
- The agreed maintenance-task failure class is
ambiguous: the failed task needs escalation, not an automatic crash-recovery action.
RecoveryActuatorService.escalateDiagnosis() is an alarm-only sink. It must keep rejecting non-escalate diagnosis events and must not execute target actions.
Orchestrator wires services reactively; the new cross-link keeps the supervisor able to call the actuator sink while the actuator still knows about the supervisor for ordinary recovery actions.
The Fix
After #14056 is merged and its helper has the agreed failed -> ambiguous behavior:
- Refactor
ProcessSupervisorService.escalateFailedTaskOutcome() to consume the shared supervised-task diagnosis producer instead of constructing the diagnosis inline.
- Preserve #14061 behavior: failed
backup outcomes escalate once, use targetIdentity: {kind: 'supervised-task', id: 'backup'}, and never call privileged target actions.
- Add or update focused unit coverage proving the helper-generated failed-backup diagnosis still routes as
ambiguous and preserves the maintenance-task-failure reason/details.
- Add a concise comment to the
Orchestrator service setter cross-link explaining the bidirectional service graph reconciliation.
- Backfill the return-shape Contract Ledger for
RecoveryActuatorService.escalateDiagnosis() on the #14058/#14061 follow-up surface, including escalated, invalid-diagnosis, and diagnosis-not-escalatable outcomes.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
ProcessSupervisorService.escalateFailedTaskOutcome() diagnosis producer |
#14056 producer-core helper + #14061 approved hook+sink |
Use the shared helper for failed backup supervised-task diagnoses once available |
Keep inline build only until #14056 lands |
Method JSDoc / review follow-up ticket |
Unit test asserts helper-compatible ambiguous classification and sink call |
RecoveryActuatorService.escalateDiagnosis() return shape |
#14058 Contract Ledger + #14061 implementation |
Document returned statuses and rejection reason codes as consumed contract |
Existing JSDoc remains source until ledger backfill lands |
Contract Ledger matrix / JSDoc |
Unit tests assert escalated and rejected statuses |
Orchestrator supervisor/actuator cross-link |
Runtime service graph wiring in #14061 |
Add one-line source comment explaining why both services receive each other through setter reconciliation |
No behavior change |
Inline code comment |
Source preflight / focused unit suite |
Decision Record impact
Aligned with ADR-0025 / ADR-0026: single producer contract for diagnosis events, alarm-only escalation for ambiguous maintenance failures, and bounded recovery action. No ADR amendment required.
Acceptance Criteria
Out of Scope
- Reworking #14061's approved alarm-only sink.
- Changing #14056 before it lands; Vega owns the helper alignment lane.
- Adding scheduling-overdue detection; that remains Vega's AC1 piece-3 after #14056 merges.
- Restarting or retrying backup automatically.
Avoided Traps
- Do not import from an unmerged PR or stack a non-dev branch.
- Do not classify generic maintenance-task failure as
crash without causal evidence.
- Do not turn the helper refactor into a second escalation design cycle; preserve #14061 behavior.
Related
Refs #14039
Related: #14056, #14058, #14061, #14030, #14055
BLOCKED_BY #14056
Origin Session ID: 35f83031-f1a6-41a7-9c3b-089b87307db9
Handoff Retrieval Hints: query_raw_memories("#14061 approved follow-up buildSupervisedTaskDiagnosis ambiguous backup escalation"); A2A anchors: MESSAGE:472f2a10-82e3-4b74-b224-fab140aa0174, MESSAGE:4ebd5c85-4708-468e-9bd1-22ed23761ce6; source anchors: ProcessSupervisorService.escalateFailedTaskOutcome, RecoveryActuatorService.escalateDiagnosis, Orchestrator.afterSetProcessSupervisorService.
Context
PR #14061 delivered the #14058 backup failure diagnosis escalation hook+sink and is approved. Vega's review intentionally left a follow-up instead of blocking the PR:
ProcessSupervisorService.escalateFailedTaskOutcome()currently builds therecovery-diagnosisinline because PR #14056'sbuildSupervisedTaskDiagnosishelper is approved but not merged. Importing that helper directly in #14061 would have stacked the PR, which is forbidden for this lane.Vega and @neo-gpt converged on the semantic class: a failed maintenance task should route as
recoveryClass: 'ambiguous', notcrash, because the supervisor observed the failure but not its cause. Vega owns aligning the helper after #14056 merges; this ticket owns consuming that helper from the backup escalation path afterward.Live latest-open sweep: checked latest 30 open issues at 2026-06-26T05:15Z. Closest tickets were #14058 (delivered hook+sink), #14055/#14056 (producer helper), and #14030 (parent backup reliability), but no equivalent post-merge helper-consumption follow-up existed. Targeted GitHub search
backup escalation supervised-task diagnosis helper #14056 #14061returned no open issue. A2A in-flight sweep over the latest 30 messages found Vega's #14061 approval/follow-up and my agreement, but no competing claim. KB/local keyword sweeps found no duplicate forescalateFailedTaskOutcome/buildSupervisedTaskDiagnosis.The Problem
Leaving the inline diagnosis build in
ProcessSupervisorServiceafter #14056 lands would create two producer paths for the same supervised-task failure diagnosis contract. That is exactly the class of drift the producer-core split was meant to avoid: one helper says what a supervised-task failure diagnosis means, while the backup escalation path silently carries a local variant.There are two small documentation polish points from the same review loop that should travel with this refactor instead of being remembered only in A2A:
RecoveryActuatorService.escalateDiagnosis()now has observable return statuses (escalated,rejectedvariants) consumed by tests/operators; the return-shape contract should be captured in the ticket/contract ledger surface.Orchestratornow has a bidirectionalProcessSupervisorService<->RecoveryActuatorServicesetter cross-link; a one-line comment should explain that this is service graph reconciliation, not arbitrary circular wiring.The Architectural Reality
ambiguous: the failed task needs escalation, not an automatic crash-recovery action.RecoveryActuatorService.escalateDiagnosis()is an alarm-only sink. It must keep rejecting non-escalate diagnosis events and must not execute target actions.Orchestratorwires services reactively; the new cross-link keeps the supervisor able to call the actuator sink while the actuator still knows about the supervisor for ordinary recovery actions.The Fix
After #14056 is merged and its helper has the agreed
failed->ambiguousbehavior:ProcessSupervisorService.escalateFailedTaskOutcome()to consume the shared supervised-task diagnosis producer instead of constructing the diagnosis inline.backupoutcomes escalate once, usetargetIdentity: {kind: 'supervised-task', id: 'backup'}, and never call privileged target actions.ambiguousand preserves themaintenance-task-failurereason/details.Orchestratorservice setter cross-link explaining the bidirectional service graph reconciliation.RecoveryActuatorService.escalateDiagnosis()on the #14058/#14061 follow-up surface, includingescalated,invalid-diagnosis, anddiagnosis-not-escalatableoutcomes.Contract Ledger Matrix
ProcessSupervisorService.escalateFailedTaskOutcome()diagnosis producerambiguousclassification and sink callRecoveryActuatorService.escalateDiagnosis()return shapeescalatedand rejected statusesOrchestratorsupervisor/actuator cross-linkDecision Record impact
Aligned with ADR-0025 / ADR-0026: single producer contract for diagnosis events, alarm-only escalation for ambiguous maintenance failures, and bounded recovery action. No ADR amendment required.
Acceptance Criteria
ProcessSupervisorService.escalateFailedTaskOutcome()consumes the shared supervised-task diagnosis helper after #14056 is available.recoveryClass: 'ambiguous',reasonCode: 'maintenance-task-failure', andtargetIdentity.kind: 'supervised-task'.RecoveryActuatorService.escalateDiagnosis()return statuses are captured in a Contract Ledger/JSDoc surface.Orchestratorbidirectional service wiring has a concise explanatory comment.Out of Scope
Avoided Traps
crashwithout causal evidence.Related
Refs #14039
Related: #14056, #14058, #14061, #14030, #14055
BLOCKED_BY #14056
Origin Session ID: 35f83031-f1a6-41a7-9c3b-089b87307db9
Handoff Retrieval Hints:
query_raw_memories("#14061 approved follow-up buildSupervisedTaskDiagnosis ambiguous backup escalation"); A2A anchors:MESSAGE:472f2a10-82e3-4b74-b224-fab140aa0174,MESSAGE:4ebd5c85-4708-468e-9bd1-22ed23761ce6; source anchors:ProcessSupervisorService.escalateFailedTaskOutcome,RecoveryActuatorService.escalateDiagnosis,Orchestrator.afterSetProcessSupervisorService.