fix(webhooks): materialize stack-declared webhooks into the dispatcher (#3461)#3489
Merged
Conversation
#3461) The spec `WebhookSchema` authoring surface (`defineStack({ webhooks })` / `defineWebhook()`, `object` / `isActive`) was disconnected from the runtime dispatcher, which fans out off `sys_webhook` DATA rows (`object_name` / `active`) written only by hand through the object's CRUD UI. Nothing turned a declared webhook into a dispatchable row, so authoring `webhooks:` on a stack was a silent no-op (ADR-0078) — the showcase itself shipped one that did nothing. - `bootstrapDeclaredWebhooks` reads declared `webhook` metadata from the ObjectQL registry (where manifest decomposition already parks `stack.webhooks`), validates each through `WebhookSchema.parse()` (the spec schema finally gets a real consumer), and materializes it into a `sys_webhook` row: `object → object_name`, `isActive → active`, full envelope → `definition_json`. Runs on the DATA ENGINE alone, before the auto-enqueuer's first refresh — NOT gated behind the realtime/messaging dispatch prerequisites (else a realtime-less deployment reproduces the silent no-op). - Seed-not-clobber provenance (mirrors sys_sharing_rule #2909): `sys_webhook` gains `managed_by` / `customized`. Declared webhooks re-seed as `managed_by: 'package'`; a row an admin created (`admin`) or edited (`customized`, stamped by a beforeUpdate hook) is never overwritten. - showcase: require the `webhooks` + `realtime` capabilities (so the dispatcher actually mounts) and ship the demo webhook inactive (placeholder endpoint). - Fix the stale `SysWebhookDelivery` import in the i18n extract config (dead since delivery moved to service-messaging). Connector `webhooks` remain not-yet-enforced (#3197). Registering `webhook` as a metadata type + GOVERNED liveness enrollment is a tracked follow-up. Verified: 9 new unit tests (mapping / idempotency / seed-not-clobber / invalid / end-to-end dispatch), red-proofed; and a real showcase boot — declared webhook materializes into a sys_webhook row, same-DB reboot stays idempotent, and an admin's customized edit survives redeploy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
17 tasks
xuyushun441-sys
pushed a commit
that referenced
this pull request
Jul 26, 2026
`sys_webhook_delivery` was removed from @objectstack/plugin-webhooks when outbound delivery moved to @objectstack/service-messaging (`sys_http_delivery`, ADR-0018 M3), but its translation blocks lingered in all four generated locale bundles — loaded at runtime yet referenced by nothing. - Removed the `sys_webhook_delivery` node from each *.objects.generated.ts bundle (en/zh-CN/ja-JP/es-ES); WebhooksTranslations now carries only sys_webhook. - Corrected the stale ownership comment on SysWebhook that still named sys_webhook_delivery as a live sibling. The dangling SysWebhookDelivery import in scripts/i18n-extract.config.ts was fixed independently on main by #3489, so it is not part of this change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang
added a commit
that referenced
this pull request
Jul 26, 2026
#3492) `sys_webhook_delivery` was removed from @objectstack/plugin-webhooks when outbound delivery moved to @objectstack/service-messaging (`sys_http_delivery`, ADR-0018 M3), but its translation blocks lingered in all four generated locale bundles — loaded at runtime yet referenced by nothing. - Removed the `sys_webhook_delivery` node from each *.objects.generated.ts bundle (en/zh-CN/ja-JP/es-ES); WebhooksTranslations now carries only sys_webhook. - Corrected the stale ownership comment on SysWebhook that still named sys_webhook_delivery as a live sibling. The dangling SysWebhookDelivery import in scripts/i18n-extract.config.ts was fixed independently on main by #3489, so it is not part of this change. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
xuyushun441-sys
pushed a commit
that referenced
this pull request
Jul 27, 2026
…-dead props (#3490) Second half of the Test Core lockstep the bound-class + ledger flip require. The bridge (#3489) made `url` live, so the CLI compile lint's per-webhook author warning must move OFF it onto the props that are STILL dead + misleading: retryPolicy/body/payloadFields/includeSession now carry `authorWarn` (folded into definition_json but never read — authored retry/body/field-projection/session do nothing). `tags` stays unmarked (benign display). `lint-liveness-properties.test.ts` asserts the new behavior: a webhook that authors only now-live props is silent, and the showcase-shaped webhook warns exactly once — on its inert `retryPolicy`, not on `url`. isActive test comment updated (it is live now, not "unmarked dead"). Verified: lint-liveness-properties.test.ts (17) + proof-registry.test.ts (23) green; liveness gate still green (webhook 17 classified, proofs resolve).
os-zhuang
added a commit
that referenced
this pull request
Jul 27, 2026
…ation / Webhook (#3494) (#3516) * feat(spec)!: prune still-dead aspirational config from Theme / Translation / Webhook (#3494) Removes the authorable-but-never-consumed props confirmed dead by the 2026-06 liveness audit (follow-up to #1878/#1893; same enforce-or-remove treatment as #2377/#3464): - Theme: spacing, breakpoints, logo, density, wcagContrast, rtl, touchTarget, keyboardNavigation (+ SpacingSchema, BreakpointsSchema, DensityModeSchema, WcagContrastLevelSchema and their aliases/types) — the objectui theme engine never emitted or consumed them. - Translation: fileOrganization, messageFormat, lazyLoad, cache (+ MessageFormatSchema, TranslationFileOrganizationSchema) — no ICU engine exists; interpolation is always simple substitution. - Webhook: body, payloadFields, includeSession, authentication (non-HMAC; HMAC via secret stays), retryPolicy, tags + the entire inbound WebhookReceiverSchema — the delivery path sends a fixed envelope and the outbox owns its own retry schedule. Liveness ledger entries for the removed props are dropped (dead->live flip for materialized props stays with #3490). Kept deliberately: supportedLocales (live pinyin-search reader), job retryPolicy/timeout (being built in the follow-up PR), and all webhook props materialized by the #3489 bridge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(cli): drop lint assertions for the pruned webhook authentication/retryPolicy props The liveness compile lint no longer warns on authentication — the prop (and its ledger entry) were removed outright, so the experimental-marker test case is obsolete; the showcase-shaped fixture loses its dead retryPolicy key. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
xuyushun441-sys
pushed a commit
that referenced
this pull request
Jul 27, 2026
Resolve conflicts in webhook.json + lint-liveness-properties.test.ts against #3494 (feat(spec)!: prune still-dead aspirational config from Webhook), which deleted body/payloadFields/includeSession/retryPolicy/tags/authentication from WebhookSchema. With those gone and the #3489 bridge making the rest live, the webhook ledger is 11 props ALL live — no dead surface, so no authorWarn carrier — and the lint test asserts an authored webhook is silent (the old `url` carrier is now live). proof-registry (23) / lint (14) / liveness gate (webhook 11 live) / webhook-materialization dogfood proof all green post-merge.
os-zhuang
added a commit
that referenced
this pull request
Jul 27, 2026
…lization proof (#3490) (#3518) * chore(spec): flip webhook liveness ledger dead→live + webhook-materialization proof (#3490) Follow-up to #3489. The materializer bridge made stack-authored webhooks reach the dispatcher, so the webhook liveness ledger (packages/spec/liveness/webhook.json) was stale — it still classified all 16 authorable props `dead` from the pre-bridge "nothing materializes an authored webhook" vantage point. Because `webhook` rides the SPEC_ONLY_SCHEMAS gate path (walk-only), the liveness CI did NOT catch the dead→live drift; this updates it by hand. - Flip the props the materializer + dispatcher consume to `live` with evidence (materializer bootstrap-declared-webhooks.ts + dispatcher auto-enqueuer.ts): object/isActive/url/triggers/method/name/headers/secret/timeoutMs, plus display-only label/description. Drop the `url` authorWarn (authoring is live). - Keep `dead`: body/payloadFields/includeSession/retryPolicy/tags — folded into definition_json by the bridge but parseRow never reads them (no consumer, the #1878 delivery-layer worklist). `authentication` stays experimental. - Add a `webhook-materialization` ADR-0054 high-risk proof class (proof-registry.mts, bound to webhook.object) + its dogfood proof: bootStack with the messaging + webhook plugins but NO realtime, asserting the sys_webhook row materializes with object→object_name / isActive→active. Pins the #3461 integration seam (the bridge was first gated behind the realtime dispatch guard, silently materializing nothing). - Refresh the stale "enforce-or-remove pending #3461" prose in check-liveness.mts and webhook.json's _note (the disconnect is closed by #3489). Verified: liveness gate green (webhook 17 classified: live 11 / dead 5 / exp 1; webhook-materialization proof resolves) and red-proofed (breaking the proof tag → exit 1); the dogfood proof passes (logs confirm hasRealtime:false yet the row materializes). Liveness-ledger + gate assets and the private dogfood package only — no package release. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(spec): sync proof-registry test to the webhook-materialization bound class (#3490) The registry's BOUND_PROOF_PATHS expected-list and the real-proof-wiring `ledgerFor` map must be updated in lockstep when a bound high-risk class is added — add `webhook/object` and the `webhook` → webhook.json ledger path. The Test Core failure on the first push was exactly this lockstep assertion doing its job. (Validate Package Dependencies is red on OSV-Scanner audit hits for pre-existing transitive deps — next / react-router / sharp / tar / js-yaml / brace-expansion — none introduced here; that job is not a required check and is triggered only because this PR touches pnpm-lock.yaml to add the dogfood workspace deps.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(spec,cli): redirect webhook authorWarn from now-live url to still-dead props (#3490) Second half of the Test Core lockstep the bound-class + ledger flip require. The bridge (#3489) made `url` live, so the CLI compile lint's per-webhook author warning must move OFF it onto the props that are STILL dead + misleading: retryPolicy/body/payloadFields/includeSession now carry `authorWarn` (folded into definition_json but never read — authored retry/body/field-projection/session do nothing). `tags` stays unmarked (benign display). `lint-liveness-properties.test.ts` asserts the new behavior: a webhook that authors only now-live props is silent, and the showcase-shaped webhook warns exactly once — on its inert `retryPolicy`, not on `url`. isActive test comment updated (it is live now, not "unmarked dead"). Verified: lint-liveness-properties.test.ts (17) + proof-registry.test.ts (23) green; liveness gate still green (webhook 17 classified, proofs resolve). --------- Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3461.
Problem
The spec
WebhookSchemaauthoring surface was disconnected from the runtime dispatcher — not merely a naming drift. A webhook authored declaratively (defineStack({ webhooks })/defineWebhook(), declaringobject/isActive) is decomposed into the ObjectQL registry aswebhookmetadata, but the dispatcher (AutoEnqueuer) fans out offsys_webhookdata rows (object_name/active) that until now were only ever written by hand through the object's CRUD UI. No ingestion path turned a declared webhook into a dispatchable row, so authoringwebhooks:on a stack was a silent no-op (ADR-0078). The showcase app itself shipped awebhooks:entry that did nothing — and, separately, never required thewebhookscapability, so the dispatcher plugin wasn't even mounted.Per the issue's decision, this is Option A — build the bridge (Option B would retire a public authoring surface the showcase already uses).
What changed
bootstrap-declared-webhooks.ts— reads declaredwebhookmetadata from the registry (falling back to the metadata service), validates each throughWebhookSchema.parse()(the spec schema finally has a real consumer + default-fill), and materializes asys_webhookrow:object → object_name,isActive → active, full validated envelope →definition_json(whence the enqueuer reads headers/secret/timeout). Modeled on the siblingbootstrapDeclaredSharingRules.bootAutoEnqueue, which meant a realtime-less deployment silently failed to materialize — the very no-op class this closes. Caught during the real-boot dogfood.)sys_sharing_rule, Audit sibling declared-metadata↔record two-store types (sys_position, sys_sharing_rule, sys_capability) per ADR-0094 addendum #2909):sys_webhookgainsmanaged_by/customized. Declared webhooks re-seed every boot asmanaged_by: 'package'; a row an admin created (admin) or edited (customized, stamped by abeforeUpdatehook inwebhook-provenance.ts) is never overwritten — a deactivated noisy webhook survives redeploys.webhooks+realtimecapabilities so the dispatcher actually mounts, and ship the demo webhook inactive (thehooks.exampleURL is a placeholder; activate it in Setup).WebhookSchemadocstring documents the materialization contract. Connectorwebhooksremain not-yet-enforced (Audit: several event/subscription/connector enums are schema-only (declared, no runtime consumer) #3197).SysWebhookDeliveryimport in the i18n extract config (dead since delivery moved toservice-messaging).Verification
9 new unit tests (
bootstrap-declared-webhooks.test.ts) — mapping, idempotency, declared-change propagation, seed-not-clobber, admin-name-collision, invalid-webhook skip, no-op-when-empty, and end-to-end dispatch (declared → materialized →AutoEnqueuerfires with headers/secret fromdefinition_json). Red-proofed (breaking the mapping + the skip guard fails the right tests). Full suite: 23 passing.Real showcase boot (
objectstack dev, SQLite):sys_webhookrow —object_name=showcase_task,active=0,managed_by=package✅customizededit (active=1) survives redeploy despite the declaredisActive:false✅Build + DTS type-check green.
Follow-ups (out of scope)
webhookas a first-class metadata type + enroll it in the livenessGOVERNEDset (would have caught this automatically) — tracked.webhooksenforcement (Audit: several event/subscription/connector enums are schema-only (declared, no runtime consumer) #3197).sys_webhook_deliveryi18n blocks (pre-existing migration debt).🤖 Generated with Claude Code