mirror of
https://github.com/hanzoai/notify.git
synced 2026-10-30 15:04:32 +00:00
notifyd reached github.com/hanzoai/kms/sdk/go/kmsclient transitively through
base v1.5.8's plugins/platform. That repo is archived and renamed (kms-v1), so
the module resolves only from the proxy's immutable cache: it cannot take a
push, therefore it cannot take a security fix. Holding secrets code there is
the worst version of that problem.
base v1.5.16 already speaks native ZAP to luxfi/kms, so the bump removes the
dependency outright rather than pinning a patched copy of it. Nothing here
imported the SDK directly; the whole reach was through base.
Two changes the bump forces:
- hanzoai/tasks v1.52.9 (required by base v1.5.16) renamed client.Options
.HostPort to .Address. Same meaning, and it now also accepts a unix socket
path.
- KMSEndpoint is no longer handed to the platform plugin. Base rejects an
http(s) KMS endpoint outright, while notify's own kmsbridge deliberately
rewrites zap:// to http:// because it speaks HTTP. Feeding base the
normalized HTTP endpoint would panic MustRegister at boot. Unset, base uses
its in-cluster ZAP default; notify's own secret reads are untouched.
go vet and the full test suite are clean before and after under CGO_ENABLED=0,
which is how Base ships and now enforces at runtime: 50 packages and 133 test
names, identical on both sides.
Co-authored-by: Hanzo Dev <[email protected]>
146 lines
4.5 KiB
Go
146 lines
4.5 KiB
Go
// Package tasks wires Hanzo Notify into hanzoai/tasks as a durable
|
|
// execution substrate.
|
|
//
|
|
// One workflow type, NotifySendWorkflow, takes a SendRequest and walks
|
|
// the same code path the sync HTTP handler does:
|
|
//
|
|
// resolve provider → render template → call library Send →
|
|
// write message row → write meter row → set status
|
|
//
|
|
// The worker registers itself on a single task queue ("notify-send").
|
|
// Async POST /v1/notify/send Dispatch hands off to ExecuteWorkflow; the
|
|
// sync path calls the same internal SendOnce function directly.
|
|
package tasks
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/hanzoai/tasks/pkg/sdk/client"
|
|
"github.com/hanzoai/tasks/pkg/sdk/worker"
|
|
luxlog "github.com/luxfi/log"
|
|
)
|
|
|
|
// Config is the worker's runtime configuration. Mirrors auto's engine.Config.
|
|
type Config struct {
|
|
// Address is the tasks server ZAP endpoint (host:port).
|
|
Address string
|
|
|
|
// Namespace is the tasks namespace this worker serves.
|
|
Namespace string
|
|
|
|
// TaskQueue is the queue both the workflow and activities sit on.
|
|
TaskQueue string
|
|
}
|
|
|
|
// Dispatcher is the narrow surface the HTTP routes need to enqueue an
|
|
// async send. *Worker satisfies it; tests pass an in-memory stub so the
|
|
// send handler can be exercised without dialing tasksd.
|
|
type Dispatcher interface {
|
|
// Dispatch enqueues a NotifySendWorkflow execution and returns the
|
|
// task id. Implementations must return a non-empty id on success.
|
|
Dispatch(ctx context.Context, req SendInput) (string, error)
|
|
|
|
// Started reports whether the dispatcher is ready to accept work.
|
|
// Routes use this to fail-closed (503) when async is requested
|
|
// against an unstarted worker.
|
|
Started() bool
|
|
}
|
|
|
|
// Worker owns the tasks client + worker lifecycle. Mirrors auto's
|
|
// engine.Worker (we deliberately copy the shape because they're siblings).
|
|
type Worker struct {
|
|
cfg Config
|
|
logger luxlog.Logger
|
|
cli client.Client
|
|
wk worker.Worker
|
|
started bool
|
|
// activities is the type-checked closure surface the workflow calls;
|
|
// kept as a pointer so registration sites and callers refer to the
|
|
// same instance.
|
|
activities *Activities
|
|
}
|
|
|
|
// New validates config and returns an un-started worker. The ZAP
|
|
// connection happens in Start so `--help`, `migrate`, and any non-serve
|
|
// command runs without a live tasks server.
|
|
func New(cfg Config, acts *Activities) (*Worker, error) {
|
|
if cfg.Address == "" {
|
|
return nil, errors.New("tasks: TASKS_ADDR is required")
|
|
}
|
|
if acts == nil {
|
|
return nil, errors.New("tasks: Activities is required")
|
|
}
|
|
if cfg.Namespace == "" {
|
|
// CONTRACT.md §6: single-tenant services run under "default" until
|
|
// they serve a second tenant. Notify is multi-tenant in principle
|
|
// but ships with one shared queue today; per-org namespaces land
|
|
// in the follow-up tracked at hanzoai/notify#TODO.
|
|
cfg.Namespace = "default"
|
|
}
|
|
if cfg.TaskQueue == "" {
|
|
cfg.TaskQueue = "notify-send"
|
|
}
|
|
return &Worker{
|
|
cfg: cfg,
|
|
logger: luxlog.New("module", "notify.tasks"),
|
|
activities: acts,
|
|
}, nil
|
|
}
|
|
|
|
// Start dials the tasks server, registers the workflow + activities,
|
|
// and starts the poll loop. Idempotent.
|
|
func (w *Worker) Start() error {
|
|
if w.started {
|
|
return nil
|
|
}
|
|
cli, err := client.Dial(client.Options{
|
|
Address: w.cfg.Address,
|
|
Namespace: w.cfg.Namespace,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("tasks: dial: %w", err)
|
|
}
|
|
wk := worker.New(cli, w.cfg.TaskQueue, worker.Options{Logger: w.logger})
|
|
wk.RegisterWorkflow(NotifySendWorkflow)
|
|
wk.RegisterActivity(w.activities.Deliver)
|
|
if err := wk.Start(); err != nil {
|
|
return err
|
|
}
|
|
w.cli = cli
|
|
w.wk = wk
|
|
w.started = true
|
|
return nil
|
|
}
|
|
|
|
// Stop unblocks poll loops and tears down subscriptions.
|
|
func (w *Worker) Stop() {
|
|
if !w.started {
|
|
return
|
|
}
|
|
w.wk.Stop()
|
|
w.started = false
|
|
}
|
|
|
|
// Started reports whether the worker is connected and active.
|
|
// Routes use this to decide async dispatch vs. 503.
|
|
func (w *Worker) Started() bool { return w.started }
|
|
|
|
// Dispatch enqueues a NotifySendWorkflow execution; returns the task id.
|
|
// The workflow runs asynchronously; clients poll via GET /v1/notify/
|
|
// messages/{id} (the workflow updates that record).
|
|
func (w *Worker) Dispatch(ctx context.Context, req SendInput) (string, error) {
|
|
if !w.started {
|
|
return "", errors.New("tasks: worker not started")
|
|
}
|
|
run, err := w.cli.ExecuteWorkflow(ctx, client.StartWorkflowOptions{
|
|
ID: "notify-send-" + req.MessageID,
|
|
TaskQueue: w.cfg.TaskQueue,
|
|
}, "NotifySendWorkflow", req)
|
|
if err != nil {
|
|
return "", fmt.Errorf("tasks: dispatch: %w", err)
|
|
}
|
|
return run.GetID(), nil
|
|
}
|