fix(api): register the v1 stamp endpoint scope and derive the webhook event catalogue from one source (#1930)
POST /api/v1/companies/{companyId}/inbox-items/{id}/stamp registered itself
with scope documents:write but had no V1_ENDPOINT_SCOPES entry, and the
wrapper resolves the required scope from that map before it validates the
bearer token, so the route answered NOT_FOUND to every caller. Add the entry,
drop the three phantom entries that had no route (GET openapi.yaml, GET
companies/:companyId, GET companies/:companyId/events), and add a parity test
that pins the scope map to the endpoint registry in both directions, checks
every pattern against an existing route file, and checks every v1 route file
is imported by load-routes.ts.
The webhook event catalogue was hand-copied in three places and had drifted:
the fan-out handler delivered 28 events while the v1 create enum, the OpenAPI
spec, the generated agent skill and the docs page listed 24, so the four
reconciliation.* events could not be subscribed to. lib/webhooks/public-events.ts
is now the single source; the handler set, the Zod enum and the docs section
derive from it, with tests that pin each surface to the catalogue. The PATCH
webhook docs no longer tell agents to delete and recreate a webhook to rotate
its secret: POST .../rotate-secret exists.
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5
parent
f08fc2c274
commit
d3869e6694
+5
-37
@@ -28,48 +28,16 @@ import { createServiceClientNoCookies } from '@/lib/auth/api-keys'
|
||||
import { createLogger } from '@/lib/logger'
|
||||
import { API_V1_VERSION } from '@/lib/api/v1/version'
|
||||
import { kickWebhookDispatch } from './dispatch-kick'
|
||||
import { PUBLIC_WEBHOOK_EVENTS as PUBLIC_WEBHOOK_EVENT_CATALOGUE } from './public-events'
|
||||
|
||||
const log = createLogger('webhooks/handler')
|
||||
|
||||
/**
|
||||
* Set of event types that the v1 webhook surface delivers. Restricted to the
|
||||
* resource-state-change events that are useful to external integrations;
|
||||
* MCP telemetry events and internal-only flows (event_log writes, etc.) are
|
||||
* deliberately excluded.
|
||||
*
|
||||
* Adding a new event type to this set is a public-API change: bump
|
||||
* API_V1_VERSION + add to the changelog when you do.
|
||||
* Set of event types that the v1 webhook surface delivers. Derived from the
|
||||
* public catalogue in ./public-events.ts, which is also what the v1 create
|
||||
* schema and the docs page enumerate: add new event types THERE, not here.
|
||||
*/
|
||||
const PUBLIC_WEBHOOK_EVENTS = new Set<CoreEventType>([
|
||||
'invoice.created',
|
||||
'invoice.sent',
|
||||
'invoice.paid',
|
||||
'credit_note.created',
|
||||
'customer.created',
|
||||
'supplier.created',
|
||||
'supplier_invoice.registered',
|
||||
'supplier_invoice.approved',
|
||||
'supplier_invoice.paid',
|
||||
'supplier_invoice.credited',
|
||||
'supplier_invoice.uncredited',
|
||||
'transaction.categorized',
|
||||
'transaction.reconciled',
|
||||
'reconciliation.matched',
|
||||
'reconciliation.unmatched',
|
||||
'reconciliation.signed_off',
|
||||
'reconciliation.reopened',
|
||||
'journal_entry.committed',
|
||||
'journal_entry.reversed',
|
||||
'journal_entry.corrected',
|
||||
'period.locked',
|
||||
'period.unlocked',
|
||||
'period.year_closed',
|
||||
'salary_run.created',
|
||||
'salary_run.approved',
|
||||
'salary_run.booked',
|
||||
'agi.generated',
|
||||
'document.uploaded',
|
||||
])
|
||||
const PUBLIC_WEBHOOK_EVENTS = new Set<CoreEventType>(PUBLIC_WEBHOOK_EVENT_CATALOGUE)
|
||||
|
||||
let registered = false
|
||||
|
||||
|
||||
Reference in New Issue
Block a user