mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-31 00:40:46 +02:00
Elon round-4 must-fix: hasActiveTaskForIssueAndAgent swallowed DB errors by
returning true, so both call sites could turn a query failure into a
success-shaped `deferred/already_active` — a silent false success, exactly what
this issue forbids for admission-query failures.
- hasActiveTaskForIssueAndAgent now returns (bool, error).
- Two pure decision helpers govern the branches, fail closed on error:
- decidePostMergeMiss: on query error, do NOT enqueue a fresh task (duplicate
concurrent-run risk) AND report non-success blocked/internal_error; a
confirmed active task defers; a confirmed-none enqueues fresh.
- decideSuppressedLeaderOutcome: on query error, blocked/internal_error; a
confirmed active run defers; else self_trigger_suppressed. Never a fabricated
deferred.
- Renamed reason already_handled -> self_trigger_suppressed (Elon non-blocking
note): the old name implied the new comment was already processed, but the
real meaning is a suppressed self-trigger.
Deterministic unit tests cover the query-failure branch at both call sites
(no fresh enqueue, non-success outcome) — a real DB fault can't be forced
through valid handler inputs, and the decision is what governs the behavior.
Backend handler+service suites and go vet green.
Co-authored-by: multica-agent <github@multica.ai>
49 lines
2.6 KiB
Go
49 lines
2.6 KiB
Go
// Package dispatch holds the canonical, cross-layer vocabulary for execution
|
|
// admission outcomes (MUL-4525). It is a leaf package (no internal deps) so both
|
|
// the service layer — which MAKES the admission/skip decision and therefore owns
|
|
// the reason at its source — and the handler layer — which serializes it to the
|
|
// wire — share one enum and can never drift.
|
|
//
|
|
// A ReasonCode is decided at the branch that blocks/skips a run and carried
|
|
// through to the response verbatim; it is never reverse-engineered from a
|
|
// human-readable failure string. Codes are stable, localizable by clients, and
|
|
// enumeration-safe: a code never reveals whether a private agent exists, its
|
|
// name, or its owner.
|
|
package dispatch
|
|
|
|
// ReasonCode is a stable, client-localizable admission/dispatch reason.
|
|
type ReasonCode string
|
|
|
|
const (
|
|
// ReasonQueued / ReasonCoalesced / ReasonDeferred are the success-path codes.
|
|
ReasonQueued ReasonCode = "queued"
|
|
ReasonCoalesced ReasonCode = "coalesced"
|
|
ReasonDeferred ReasonCode = "deferred"
|
|
|
|
// ReasonInvocationNotAllowed: the acting principal may not trigger this
|
|
// target under the invocation-permission model. Deliberately generic — it
|
|
// does not distinguish "target is private" from "target does not exist".
|
|
ReasonInvocationNotAllowed ReasonCode = "invocation_not_allowed"
|
|
// ReasonTargetUnavailable: the target cannot run (archived agent, deleted /
|
|
// archived squad, unresolvable leader, or no assignee).
|
|
ReasonTargetUnavailable ReasonCode = "target_unavailable"
|
|
// ReasonRuntimeOffline: the target is permitted but its runtime is not bound
|
|
// / not online at dispatch time.
|
|
ReasonRuntimeOffline ReasonCode = "runtime_offline"
|
|
// ReasonAttributionBlocked: a fail-closed workspace could not resolve a
|
|
// responsible human for the run, so it was refused.
|
|
ReasonAttributionBlocked ReasonCode = "attribution_blocked"
|
|
// ReasonAlreadyActive: a run is already active/pending for this target and
|
|
// this trigger did not coalesce.
|
|
ReasonAlreadyActive ReasonCode = "already_active"
|
|
// ReasonSelfTriggerSuppressed: the target was intentionally not (re-)triggered
|
|
// because doing so would be a self-trigger the guard suppresses, and no active
|
|
// run remains to cover it — e.g. a squad leader's own @mention of its squad
|
|
// whose latest task is already terminal. Not a permission block, but NOT
|
|
// success: nothing new runs. (Named to avoid implying the NEW comment was
|
|
// already processed.)
|
|
ReasonSelfTriggerSuppressed ReasonCode = "self_trigger_suppressed"
|
|
// ReasonInternalError: an unexpected server error prevented a clean decision.
|
|
ReasonInternalError ReasonCode = "internal_error"
|
|
)
|