Files
pad/internal/store/migrations/122_app_webhooks.sql
T
xarmian aadf3c09d6 feat(apps): app-owned webhooks, held until redeem hands out the secret (TASK-3408, SPEC-6 U10a) (#1778)
* feat(apps): app-owned webhooks, held until redeem hands out the secret (TASK-3408, SPEC-6 U10a)

One webhooks row per install (app_install_id), created from the manifest
at provisioning, kept in step by a reviewed upgrade, deleted by uninstall.
Its signing secret is handed to the app only by redeem, which rotates it
and stamps secret_delivered_at; until then the hook is HELD (lead ruling,
day 86) and the owner sees "needs a new install code to activate".
Installs provisioned before U10 are backfilled at start, held.

App hooks stay off every owner door: ListWebhooks (the owner list and the
owner dispatcher), the scoped get/test and delete, and the plan count.
Events resolve to THIS install's companions only (via_app).

GET /workspaces/{ws}/apps/{installID} and the lifecycle responses carry
the hook's status. Delivery is U10b.

Migrations 122 / pg 096.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* fix(apps): the webhook backfill re-decides under the install lock from the stored manifest (codex r1, TASK-3408)

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* test(apps): NUL census baseline for the app webhook columns (TASK-3408)

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
2026-10-04 18:21:57 -04:00

18 lines
1.0 KiB
SQL

-- Migration 122: app-owned webhooks (SPEC-6 U10a, TASK-3408).
--
-- One webhook row per app install (DOC-3371 §5 "App-owned webhooks"), beside
-- the owners' hooks in the same table. app_install_id marks it; NULL is an
-- owner hook. An app hook is the install's, not the owner's: the owner
-- webhook routes, the owner dispatcher and the plan's webhook count all
-- exclude it.
--
-- secret_delivered_at is the HOLD (lead ruling, day 86): an app hook delivers
-- nothing until a redeem has handed its signing secret to the app. Events in
-- the meantime are skipped, not queued, since the app could not verify them.
--
-- For an app hook, events holds the declared subscriptions resolved to
-- companion collection IDs at write time: [{"name": ..., "collection_ids": [...]}].
ALTER TABLE webhooks ADD COLUMN app_install_id TEXT REFERENCES app_installs(id) ON DELETE CASCADE;
ALTER TABLE webhooks ADD COLUMN secret_delivered_at TEXT;
CREATE UNIQUE INDEX IF NOT EXISTS idx_webhooks_app_install ON webhooks (app_install_id) WHERE app_install_id IS NOT NULL;