6.1 Hook entries#
Install writes one managed entry into the store's setup/hooks under the
multi-event all key:
{id: `module_${id}`, name: `MODULE - ${id}`,
description: 'Managed by the platform for an installed module - do not edit.',
events: <kinds from manifest.events>, filter, mask: manifest.events_mask, on,
collection: <union of per-event collection lists>,
endpoint: `${endpoint}/event`} // hosted/platform: the platform's own ingress leg (later phase)The long form events: [{kind, collection, filter, on}] expands to one
entry per distinct (filter, collection), suffixed module_{id}_{n} — a
hook entry carries one filter map. The entry signs with the store secret
hook_module_{id} = module_key, so one key signs events and every
other envelope. The entry is upserted by id on install / update and every
module_{id}* entry is removed on uninstall; setup/hooks is cached with a
3 s revalidation, so the next delivery sees a change at once. The rest of the
delivery contract — payload shape, masking, on: create|update|delete,
tombstones, the x-drop-* context headers — is the ordinary hook feed
(docs/hooks-and-caching.md).
Vocabulary: orders, invoices, data, forms, files and the new
module_records — a write to store/{sid}/modules/{id}/{collection}/{docId}
(a records collection, §5.5; label modules/{id}/{collection}/{docId}),
fired by the store write dispatcher in main and, through the shim, in a
connected cloud. Writes to the install doc and to the reserved outbox/,
state/ and logs/ fire nothing. templates / elements are
render signals and never module events.
6.2 Retries — the outbox#
Per-store hooks are best-effort; module entries are retried. Two things
differ for a module_* entry: the delivery carries the envelope header set
(x-drop-module, x-drop-point: event, x-drop-store, x-drop-env,
x-drop-request), and a failed delivery writes
store/{sid}/modules/{id}/outbox/{request} = {label, endpoint, headers, body, attempts: 1, next_at: now + 5m, last_status, last_error, created_at}.
The outbox sweep (POST /system/module/outbox/sweep, master — no cron
since 2026-10-24: the moduleOutbox schedule was removed, failed
deliveries are still recorded and stamp health) re-delivers rows with next_at <= now (200 per store per run) with the stored headers — the same
x-drop-request — and:
| Attempt | Wait before it |
|---|---|
| 1 (the original) | — |
| 2 | 5 m |
| 3 | 15 m |
| 4 | 1 h |
| 5 | 4 h |
| (6th failure) | 12 h → dead: true |
Five attempts ≈ 17 h. After the fifth failure the row is marked dead,
install.health flips to failing with point: 'event'; dead rows are
listed by POST /api/module/logs (the detail page's Logs tab) and purged
30 days after created_at. Success deletes the row.
6.3 Dedupe on the receiver#
Retries mean a non-idempotent receiver sees duplicates. Two headers make
dedupe trivial: x-drop-request (identical on every retry of one delivery)
and x-drop-updated (the doc's update time — drop anything older than what
you already hold). expressModuleRouter dedupes x-drop-request by default
with a 1,000-entry LRU; a module that keeps its own ledger (a records
collection or state/) should key on the request id.