Webhook Relay
Webhook Relay keeps a list of relay rules. Each rule matches recorded webhook
events by event type and names a target URL. For every matched event you can
queue a delivery and then record its outcome. The app ships no dedicated host
page, renderer component or app-specific runtime branch. Its source is
apps/webhook-relay/app.toml; the native records are in
crates/shared/schema/app-webhook-relay/src/lib.rs.
This release records delivery state. It does not send the outbound HTTP request itself, and nothing runs automatically when an event is written. The friction log explains which parts of the app contract stop it.
Supported workflow
The relay starts empty. New rule opens a dialog that takes a name, a source label, the event type to match and a target URL. New rules are enabled.
The Inbound events form on the home page records an event: source, event type and a raw JSON payload. The server sets the receive time. A person or an agent with the app's write grant fills this in; there is no public webhook endpoint.
Open on a rule shows its page:
- Find matching events lists the 50 newest events whose type equals the rule's event type.
- Queue delivery on a matched event records a
pendingdelivery for that event under the rule. Paused rules hide this button. - Show deliveries lists the rule's deliveries, newest first. Each one has
Mark delivered, Retry (status
retrying) and Record failure, which stores an error message with statusfailed. The list reloads after every change. - Pause rule and Resume rule switch
enabled. Edit rule changes the name, source, event type and target URL. Delete rule hides the rule.
One package, existing host
| Package declaration | Shared implementation |
|---|---|
Native RelayRule, WebhookEvent and Delivery schemas | Descriptor validation, typed CBOR records and per-field merge |
relay-rule-list pipeline | Authorized dataset read, filters, deterministic sort and projection |
event-matches and delivery-list pipelines | The invoke_pipeline action with one signed input binding, applied as an overlay filter |
| Rule, event and delivery actions | Durable entity operations, optimistic projection for rules and explicit field masks |
| Write-then-read delivery actions | One action flow chaining apply_entity_operation into invoke_pipeline |
foreground_snapshot_v1 | The installed host's bounded foreground refresh scheduler |
The release requests read and write access to app.webhook_relay.RelayRule,
app.webhook_relay.WebhookEvent and app.webhook_relay.Delivery, and
invocation of the three pipelines. Like the other app.* datasets, these are
served by DES's portable app.* adapter and read owner-scoped. The release
requests no network, secret, background, notification or infrastructure
capability.
Author, build and install
rac app validate apps/webhook-relay/app.toml
rac app build apps/webhook-relay/app.toml
rac des spec publish --manifest apps/webhook-relay/pipelines/relay-rule-list.toml
rac des spec publish --manifest apps/webhook-relay/pipelines/event-matches.toml
rac des spec publish --manifest apps/webhook-relay/pipelines/delivery-list.toml
rac app publish apps/webhook-relay/app.toml
rac app install webhook-relay --version 0.1.0 \
--installation-id webhook-relay-example \
--grant relay-read,relay-write,relay-pipelines
Publication requires the same registry authority and grant review described in
the release lifecycle. Only
build/app-release.json is checked in.
Bounds
The rule list returns at most 200 live rules, sorted by name. Matching returns the 50 newest events of one type, and the delivery list returns 100 rows for one rule. Match and list filters are fixed when the pipelines are signed; the only value chosen at run time is the rule's event type or identity.
Friction log
These are the problems we hit while building the app. We describe them here without filing tickets. The ticket asked for ingest, routing, compute-on-write and outbound delivery with retry. Ingest and routing work; the other two do not fit the current contract.
- No inbound webhook endpoint. The app TOML's top-level keys are
app,agent,deployment,background_jobs,entities,pipelines,functions,capabilities,actions,views,assetsanddependencies(crates/shared/schema/app-manifest/src/toml_projection.rs:15-38). None of them declares an HTTP ingress route. Events are therefore written through the authenticated entity write path, from the app form or by an agent with the write grant. A third party cannot POST to the app. - No compute-on-write. The same key list has no write trigger, so recording
an event cannot start matching or queue deliveries. The nearest thing is
background_jobs: a signed schedule that invokes a declared pipeline every 1 minute to 30 days (crates/shared/schema/app-manifest/src/background.rs:26-31). That is polling, not a reaction to a write, so we left it out of this minimal loop. Matching runs when someone presses Find matching events. - Outbound egress exists but cannot record its own result. A pipeline can
contain an
http_fetchstage with a method and body template. Release resolution requires anetwork_requestcapability that covers the stage'surl_template(crates/shared/schema/app-manifest/src/resolve/pipeline_validation.rs:586-603). Three things kept it out of this app:- Capability patterns are exact strings or
*-suffixed prefixes (crates/shared/schema/app-manifest/src/validate/pins.rs:24-30) checked against the signed template. A per-rule, user-enteredtarget_urlneeds either one host fixed at signing or a*pattern that allows egress to any host. - DES rejects storage sinks on HTTP nodes ("HTTP pipeline node storage sinks
require a publication workflow",
crates/service/data-engine-service/src/pipeline_executor_runner.rs:88-94). A delivery pipeline therefore cannot write theDeliveryoutcome itself. A client action would have to read the response and write it back. - We did not verify that a portable
invoke_pipelinefrom the browser host reaches the executor's HTTP runner, or what happens to a failed request.
- Capability patterns are exact strings or
- No automatic retry. The background scheduler says that uncertain effects
"require reconciliation, never an automatic retry"
(
crates/service/data-engine-service/src/app_background.rs:1-2). Retry is recorded state (retrying) that an operator or external worker acts on. - No arithmetic in action flows, and server time only on create.
entity_operation_now_ms_fieldis accepted only forcreate(crates/shared/schema/app-manifest/src/validate/action.rs:266-280), soupdated_at_msis the time the delivery was queued and never changes. Bindings copy values without computing them, soattemptscannot be incremented. It stays at the value it was created with (0). - One parameter per pipeline invocation. Matching filters on event type
only, and the rule's
sourceis a label. A match on (source, event_type) needs two filter bindings or a join against the rule, and the overlay supports one binding. - Pipeline results are not live collections. Matches and deliveries are
flat result rows in local state. Optimistic projection needs a grouped,
hydrated collection, so every delivery change chains a
delivery-listrun to show the new state. - Enablement is display-only. A paused rule hides Queue delivery through
v-if. Nothing on the server stops a write that references a paused rule. - Host tests need a remount. Rows written into local state by an action do
not re-render an already-mounted page in the test harness, so
webhookRelay.test.tsremounts the page after each pipeline run, asmetricsNotebook.test.tsdoes. - Badge labels need a binding.
label="{{ $item.status }}"renders the literal text;:label="$item.status"binds the value.
Metrics Notebook
A gallery app that saves named metric queries and runs them through signed, parameterized pipelines on the shared host.
Tasks, repositories and applications from RAC
Use RAC from a laptop, CI job or agent with your platform identity. The commands call authenticated public APIs; the service prepares the checkout, environment and chosen harness. No local Kubernetes access or operator forge token is needed.