Skip to content

feat(scheduler): pipeline notify — ctx.notify for proactive channel delivery#11

Closed
ogarciarevett wants to merge 1 commit into
mainfrom
feat/pipeline-notify
Closed

feat(scheduler): pipeline notify — ctx.notify for proactive channel delivery#11
ogarciarevett wants to merge 1 commit into
mainfrom
feat/pipeline-notify

Conversation

@ogarciarevett

Copy link
Copy Markdown
Owner

Why

A pipeline running under the scheduler could think (complete), record, spawn, and read signals — but had no way to reach the user. The transport (OutboundIntent.kind:"notify" + ChannelHandler.send) and the destination (the owner chat id captured at scan-to-connect pairing) both already existed; only the pipeline-facing seam was missing. This adds it: the outbound, pipeline-initiated complement to the shipped inbound chatbot flow.

Downstream specs already assume it (pipeline-career, pipeline-secretary plan delivery via handler.send({ kind:"notify", ... })).

What changed

  • PipelineContext.notify(text, opts?) — gated by NETWORK_FETCH (the egress capability send already requires). Backed by a NotifyFn injected through BuildContextDeps + SchedulerOptions exactly where complete is threaded (top-level run and the sub-agent child context).
  • Decoupled core — the scheduler types use channel?: string, not the connections ChannelId union, so vesper-core/scheduler keeps zero dependency on the connections feature layer. The host owns channel identity.
  • Graceful degradation — a missing channel / destination / resolver returns { delivered:false, reason }; only a capability violation throws (a side-channel must not crash a pipeline).
  • Host resolver makeNotifyFn (CLI) — resolves the channel (explicit → config.notify.defaultChannel → first running channel with a paired owner) and the pairing-persisted owner defaultChatId, sends through the daemon's already-authenticated running handler (no re-auth, no second socket), and audits each send on the existing events table (notification_sent / notification_failed, reusing recordConnectionEvent).
  • config.notify.defaultChannel normalization (drop unknown / non-string, never throw — matches normalizePresence).
  • Daemon wiringmakeNotifyFn late-binds the channel registry (built after the scheduler) through a getter, injected into the Scheduler.

No migration, no new capability, no new dependency. Secret/PII containment: the audit payload is {channel} only — the message body and chat id never reach the log (a test asserts neither serializes).

Test plan

  • bun test — 890 pass / 0 fail (+20: context, scheduler-context, config, make-notify).
  • 100% line + function coverage on the two new units (make-notify.ts, context.ts).
  • bun run lint (Biome) — clean (exit 0).
  • tsc --noEmit — adds 0 new errors (pre-existing errors in unchanged code remain; CI skips tsc).
  • Transport mocked end-to-end — the suite sends to nothing.
  • Not yet exercised against a live channel (no tokens in CI).

Issue-capped workspace: the record is specs/pipeline-notify.md (local) + the cycle-log.md entry + this PR (Rule 11 fallback).

…elivery

Pipeline-facing seam so a running pipeline can push a notification to the user
out a connected channel — the outbound complement to the inbound chatbot flow.

- PipelineContext.notify(text, opts?) gated by NETWORK_FETCH; NotifyFn injected
  via BuildContextDeps + SchedulerOptions where `complete` is threaded
  (top-level + sub-agent). Core stays decoupled (channel: string, not ChannelId).
- Graceful: missing channel/destination/resolver -> {delivered:false,reason};
  only a capability violation throws.
- Host makeNotifyFn (CLI) resolves channel + pairing-persisted owner chatId,
  sends through the daemon's running handler, audits each send on events
  (notification_sent/notification_failed; body + chat id never logged).
- config.notify.defaultChannel normalization; no migration, no new capability,
  no new dependency.

890 tests / 0 fail (+20); 100% line+func coverage on new units; Biome clean.
@ogarciarevett

Copy link
Copy Markdown
Owner Author

Closing as already-merged: the full pipeline-notify slice (ctx.notify, NotifyFn seam, makeNotifyFn, config.notify, audit kinds + all tests) landed in main via #12's squash merge — the feat/signal-channel branch was cut on top of feat/pipeline-notify, so #12 carried both slices. Every file here is byte-identical to main; this PR's remaining diff would only delete Signal and revert the docs. Nothing is lost. (Verified: make-notify.ts, scheduler/context.ts, scheduler/types.ts, config.ts identical between main and this branch.)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant