Argus0.1.5

Packages

@argus/ingress

The webhook front door. ADO service hooks are retried, unsigned, and fire for plenty of non-events (vote churn, status changes), so this package's one job is to turn that noise into exactly the right queue jobs — cheaply, idempotently, and without ever calling a model. It exists as its own package so the always-on, internet-facing surface stays small and auditable.

Place in the system

Depends on @argus/ado (re-fetching PRs and threads), @argus/db (the event-log dedup store), and the Queue contract from @argus/shared — it never imports a queue implementation; bin/argus-ingress.ts injects one.

The decision pipeline

Order is load-bearing — each step is cheaper than the next:

flowchart TD
  hook["POST /webhook"] --> auth{"Basic auth +<br/>x-argus-webhook-secret?"}
  auth -- "no (constant-time, uniform 401)" --> r401["401"]
  auth -- yes --> self{"authored by<br/>ARGUS_IDENTITY_ID?"}
  self -- "yes — loop prevention" --> drop["dropped (logged reason)"]
  self -- no --> action{"actionable?<br/>re-fetched from ADO, not the body"}
  action -- "head unchanged / no mention,<br/>not an Argus thread" --> drop
  action -- yes --> dedup{"(subscription_id,<br/>notification_id) seen?"}
  dedup -- "yes — ADO retry" --> drop
  dedup -- no --> map["map event → job (§5.3)"]
  map --> enqueue["enqueue, session = per-PR key"]
  enqueue --> r202["202 — under 2 s, no model call"]

Figure 1 · The ingress decision pipeline — each gate is cheaper than the one after it, and no path reaches a model.

The webhook body is untrusted: it contributes IDs only, and every decision is made against data re-fetched from the ADO REST API. Mentions are detected from the comment's mentioned-identity GUIDs, never by string-matching "argus" in prose.

Key exports

How it's tested

server.test.ts drives the real Fastify server with FakeAdoClient and in-memory stores: auth rejections (each factor alone), self-event drops, vote-churn drops, dedup on replay, and the event→job mapping.