Skip to content

T-2QGZ-ops-sdlc-notify-send-test-grows-phase-2-s-minimal-notifier

State: closed/done · Impact: medium · Complexity: medium

sdlc notify send/sdlc notify test ops, growing Phase 2’s minimal desktop/ntfy notifier into the full notify: surface.

The channel-routing notify() rewrite and lib/services/notify/webhook.ts that this task’s ops and call-site rewiring build on landed under T-IIZZ instead of here (built early so this task had a real runtime to build on from the start — see that task’s Approach item 5 and Areas table). This task’s own work:

  1. sdlc notify send --channel <name> [--message <text>]: dispatch straight to one named channel from notify.channels, bypassing event routing — the manual “does this channel work” primitive. Non-zero exit on that channel’s failure.

  2. sdlc notify test: iterate every channel in notify.channels, send each a canned probe message, print one pass/fail line per channel, exit non-zero if any failed.

  3. Rewire every Phase-2 call site to fire one of the 9 canonical event names (dropping the retired orchestrator.notify-shaped config read):

    FileOld eventNew eventNote
    orchestrator/ticks/work.ts (hook-park-consumed path)'task.parked''task.parked'unchanged — already canonical
    orchestrator/ticks/work.ts (step-failure path)'work-park''task.parked'one canonical name for “this task’s dispatch stood down,” whichever path discovered it
    orchestrator/ticks/issues.ts'stage.ceiling_reached''stage.ceiling_reached'unchanged — already canonical
    services/hooks/run.ts (on_fail: block)'hook.blocked''hook.blocked'unchanged — already canonical
    services/hooks/run.ts (on_fail: park)'hook.parked'(dropped)superseded by the work tick’s own later 'task.parked'; not one of the 9 configurable names anyway
    orchestrator/ticks/_pr_action.ts (max_rounds exhausted)'max-rounds''pr.ci_failed' or 'pr.needs_response'a reason threaded in from the caller picks which
    orchestrator/ticks/prs.ts / merges.ts——pass that reason ('ci-failed' for a CI-FAILED verdict, 'needs-response' for NEEDS-RESPONSE or a CONFLICTS-driven pr-respond) into _pr_action.ts
  4. Wire the two additional events that already have a live detector on this branch, beyond the Phase-2 call sites:

    FileEventFires when
    services/lease/ops/reconcile.ts'lease.expired'once per expired-task-leases anomaly the run observes (report mode already reads the world; this adds a best-effort notify, no new write)
    services/session/ops/launch.ts'session.ended'a headless session’s record is finalized (the second writeSessionRecord call, after the child exits) — interactive sessions have no record and stay unwired, matching the module’s own “a person IS the audit trail” contract
  5. Regenerate lib/_generated/op-manifest.ts (bun run solutions/ontological/scripts/gen-op-manifest.ts) so the two new ops are in the compiled binary’s static import graph.

AreaNote
solutions/ontological/lib/services/notify/ops/send.tsnew op: sdlc notify send
solutions/ontological/lib/services/notify/ops/test.tsnew op: sdlc notify test
solutions/ontological/lib/services/orchestrator/ticks/{work,issues,prs,merges,_pr_action}.tsrewired call sites
solutions/ontological/lib/services/hooks/run.tsrewired call site
solutions/ontological/lib/services/lease/ops/reconcile.tsnew lease.expired wiring
solutions/ontological/lib/services/session/ops/launch.tsnew session.ended wiring
  • AC-1: sdlc notify test reports one pass/fail line per entry in notify.channels, exit 0 iff every channel’s send succeeded; exercised in tests with a faked transport per channel type (no real network call, no real desktop notification).
  • AC-2: sdlc notify send --channel <name> dispatches to exactly that channel and exits non-zero when it fails.
  • AC-3: Every event notify() fires is one of the 9 canonical names; no code or test references 'work-park', 'hook.parked', 'max-rounds', or orchestrator.notify after this task.
  • AC-4: An event absent from notify.events, or mapped to [], is a silent no-op when notify() fires it — no channel dispatched, no error.
  • AC-5: lease.expired fires from sdlc lease reconcile’s expired-task-leases detector and session.ended fires when a headless sdlc session launch --headless run’s session record is finalized — both exercised with faked channel transports.

Both are valid config/event names (T-IIZZ’s schema accepts routing them), but no code on this branch detects either condition. Inventing either detector is a materially larger, separately-scoped task, not a Phase-8 notifier-surface change. Declared as a parked follow-up in the phase hand-back.

EventWhy no detector exists today
phase.ready_for_reviewPresently a Tier-2 phase-orchestrator judgment call (moving a PR out of draft once its gate passes), not a deterministic tick.
orchestrator.dead_stopExplicitly named in orchestrator/ops/watch.ts’s own doc comment as a known blind spot with no detector today.
  • T-IIZZ (the notify: schema, and the channel-routing notify() + webhook.ts runtime it grew, that this task’s ops and call-site rewiring build on).

T-IIZZ


← Back to Tasks