SIGN IN SIGN UP

MUL-6680: fix autopilot list subscribers + make CLI triggers discoverable (#7552)

* fix(autopilot): populate subscribers in the list projection (MUL-6680)

GET /api/autopilots passed a nil subscriber slice to avoid an N+1, but
AutopilotResponse.Subscribers has no omitempty and autopilotToResponse
builds the slice with make(), so a nil input serialized as "subscribers":
[] rather than being omitted. Every listed autopilot therefore claimed to
have no subscribers while GET /api/autopilots/{id} reported the real ones
for the same row at the same moment.

A silently wrong value is harder to work around than a missing one: the
key is present and reads as authoritative — the field's own doc comment
promises exactly that — so callers cannot tell "none configured" from
"not fetched". It was reported after costing a real misdiagnosis
("subscribers keep being cleared").

The N+1 premise was wrong: autopilot_subscriber's primary key leads with
autopilot_id, so one ANY($1) batch is index-supported and needs no new
index. The batch query mirrors the single-autopilot query's member join
and ordering exactly, so list and detail cannot drift apart on who counts
as a subscriber.

Reported at https://github.com/multica-ai/multica/issues/7549

Co-authored-by: multica-agent <github@multica.ai>

* feat(cli): make autopilot triggers discoverable (MUL-6680)

trigger-update, trigger-delete and trigger-rotate-url all take a
<trigger-id>, but no command listed them. The ids were reachable — the
detail payload returns "triggers" as a top-level sibling of "autopilot",
and the prefix resolver already reads it — yet that requires knowing the
envelope shape, so in practice the reported workaround was the web UI.
The prefix resolver's own miss message already told users to "run the
list command", which did not exist.

Add `autopilot trigger-list <autopilot-id>`, reusing the detail payload
and the same webhook-credential redaction `get` applies, so ids, kind,
enabled state and schedule are readable without --output json | jq.

Also add NEXT_RUN to the `autopilot list` table. next_run_at has been in
the list payload all along, but the table dropped it, which made a
correctly scheduled autopilot indistinguishable from one with no trigger
at all — the second misdiagnosis in the same report. Times render
relative ("in 2h", "3d ago", "—") to stay narrow and to make "never
scheduled" a visibly distinct value rather than an empty cell; LAST_RUN
moves to the same format for consistency within that table. The `get`
table keeps absolute timestamps — it is a single-row detail view where
precision matters more than width.

Reported at https://github.com/multica-ai/multica/issues/7549

Co-authored-by: multica-agent <github@multica.ai>

* docs(cli): correct autopilot trigger documentation (MUL-6680)

The trigger section claimed webhook and api kinds were "not surfaced
here" because "there is no server endpoint that fires them yet". Webhook
triggers have both an endpoint and CLI support (`trigger-add --kind
webhook`, `trigger-rotate-url`); only the api kind is unsurfaced.

Also state that `triggers` is a top-level key alongside `autopilot`
rather than nested inside it — reading `.autopilot` and finding no
triggers is what the report described — and document trigger-list.

Co-authored-by: multica-agent <github@multica.ai>

* fix(autopilot): fail closed when the subscriber read fails (MUL-6680)

Both subscriber projections swallowed a query error and continued with an
empty slice. Because subscribers has no omitempty and is documented as
authoritative, that degradation serialized as "subscribers": [] on a 200
— recreating the exact defect this branch set out to remove: the caller
cannot distinguish "none configured" from "read failed", and a client
that round-trips the response into a full-replace PATCH would silently
wipe a real subscriber list.

An empty array is a claim, not an absence. Return 500 on both the list
and detail paths so the failure is visible and retryable.

The detail path carried this degradation before this branch; it is fixed
here rather than deferred because the list/detail agreement asserted by
TestListAutopilots_SubscribersMatchDetail would otherwise still break
under a failed read — leaving the field untrustworthy in exactly the
condition the fix is about.

Addresses review feedback on #7552.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
B
Bohan Jiang committed
6cbb50378f0256dc201741e66a2ca6f92676cb6d
Parent: 2761093
Committed by GitHub <noreply@github.com> on 8/25/2026, 5:28:35 PM