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
buildIngressServer()— the Fastify server:/webhook,GET /health.processWebhookEvent()+PipelineDeps— the pipeline above, pure enough to test with fakes.checkWebhookAuth()/constantTimeEquals()/WEBHOOK_SECRET_HEADER— the two-factor auth check.WebhookEnvelopeSchema,PrResourceSchema,CommentResourceSchema— zod over the (untrusted) payload shapes.MemoryEventLog,MemoryPrSessionStore— in-process stores forDATABASE_URL=memory.
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.
