Briefs are pre-project requests. Three intake paths land in one place:
1. Manual — "New Brief" dialog on /briefs
2. REST — POST /api/briefs (auth'd)
3. Webhook — POST /api/webhooks/briefs (HMAC-signed)
Once triaged, "Promote to Project" flips Brief.status → CONVERTED,
creates the Project, and links them via convertedProjectId so the
audit trail stays intact.
Schema:
- New BriefStatus enum + Brief model, indexed on org/status/team
- Unique on (organizationId, externalId) so webhook replays are
idempotent — same upstream id = update, not insert
- Migration 20260422000000_briefs, hand-written SQL
Webhooks — now three total, each with its own secret and header:
- /api/webhooks/omg (projects — existing, unchanged)
- /api/webhooks/deliverables (NEW — keyed on project OMG # + name)
- /api/webhooks/briefs (NEW — keyed on externalId)
Extracted a shared HMAC verifier at src/lib/webhooks/hmac.ts so the
two new routes don't copy-paste the crypto code from the OMG route.
Deliverables webhook looks up the parent project by OMG job number
(the canonical key from the projects webhook); returns 404 with a
hint if the project hasn't been created yet. Brief webhook source
records "webhook:<system>" so we can tell where briefs come from.
UI:
- /briefs page: filterable/searchable table, inline status dropdown
per row, New Brief dialog, Promote to Project dialog
- Sidebar nav entry for Briefs above Projects
Env: added DELIVERABLE_WEBHOOK_SECRET / ALLOW_INSECURE and
BRIEF_WEBHOOK_SECRET / ALLOW_INSECURE alongside the existing OMG
pair in .env.example.