From 56903a4c7e90d590d0c5ecb0b0507595e15e39de Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 06:16:18 -0400 Subject: [PATCH] Declare the inbound Slack events endpoint on the capability ratchet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Main went red on a collision git could not see. #96 added surfaces.ts — a ratchet requiring every API route appear in exactly one list, so a new endpoint cannot ship without its author either binding it or writing down the deferral. #98 added /api/integrations/slack/events. Neither branch touched the other's file, both were green, and the two together fail: ● every API route declares its capability, or declares that it has not › no handler is unaccounted for + "/api/integrations/slack/events" This is the ratchet working, not a flaw in it: the endpoint really was unaccounted for. The reason it is DEFERRED rather than BOUND is read off the handler rather than assumed. Slack is the caller and the signature over the raw bytes is the authentication, so there is no session to gate; and one delivery resolves through teamId to every institution that connected that workspace, so there is no single tenant whose capability could be consulted. That is the same shape as the other infrastructure entries, so it sits with them. Gates from a clean worktree, exit codes captured before any pipe: prisma generate 0 / tsc --noEmit 0 (tsc 5.9.3, so not a silent 127) / jest 141 suites, 2183 passed / next build 0. Negative control, read per test rather than by suite exit code: deleting the entry flips exactly "no handler is unaccounted for" red and restoring it green. Co-Authored-By: Claude Opus 5 (1M context) --- apps/web/src/lib/capability-registry/surfaces.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/web/src/lib/capability-registry/surfaces.ts b/apps/web/src/lib/capability-registry/surfaces.ts index fb771844..67f9ded2 100644 --- a/apps/web/src/lib/capability-registry/surfaces.ts +++ b/apps/web/src/lib/capability-registry/surfaces.ts @@ -62,6 +62,8 @@ export const API_PENDING_BINDING: Readonly> = { "/api/auth/[...nextauth]": "sign-in transport — a capability gate here would make a tenant unable to authenticate its way to being told why", "/api/health": "load-balancer health check — has no tenant and no session", + "/api/integrations/slack/events": + "inbound webhook — Slack is the caller and the signature over the raw bytes is the authentication, so there is no session; and one delivery resolves through teamId to every institution that connected that workspace, so there is no single tenant to ask about", "/api/jobs/reminders": "scheduled job — runs for every institution at once, with no tenant asking", "/api/notifications": "per-user shell, like /notifications — not per-tenant-capability", "/api/org-image/[orgId]": "image bytes for the club spine",