Skip to content

refactor!: rename events and subscriptions to triggers - #277

Merged
mfw78 merged 1 commit into
mainfrom
refactor/triggers-not-events
Aug 17, 2026
Merged

refactor!: rename events and subscriptions to triggers#277
mfw78 merged 1 commit into
mainfrom
refactor/triggers-not-events

Conversation

@mfw78

@mfw78 mfw78 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Closes #239.

What this is

A module reacts to a trigger. "Subscription" is eth_subscribe's word for a transport the runtime mostly does not use, and "event" was doing three jobs at once. This renames the guest-facing vocabulary to one word before v1 freezes it.

One commit by construction: on-trigger is part of every component's exported type, so the world, the parser, the macro, the SDK and every guest have to move together.

BREAKING CHANGE

This is the pre-v1 guest-ABI freeze. It breaks every layer a module author touches.

Every compiled guest .wasm fails at instantiation until it is rebuilt. on-event is part of a component's exported type, not a name the host looks up leniently, so an existing component no longer satisfies the world. The failure is at instantiation, before any handler runs.

WIT. world event-module becomes world trigger-module, exporting on-trigger. variant event becomes variant trigger, whose cases are now block(block), event(log), schedule(schedule-tick) and extension(extension-trigger { extension-kind, payload }). record chain-logs is deleted and chain-id moves onto the log record.

Manifest. [[subscription]] with kind = becomes [[trigger]] with on =. The values are "block", "event" and "schedule"; "chain-log" and "cron" both refuse.

Metric label. event_kind becomes trigger_kind at every emit site. Metric names are unchanged and belong to #243.

Refusal labels. Roughly ten error_kind values respell: missing_subscription_kind, invalid_subscription, non_string_subscription_filter, subscription_kind_claimed, unknown_event_kind, chain_subscription_not_permitted and dead_hold_subs become their trigger equivalents. The pinned set neither gains nor loses a value, but any alerting on error_kind needs its selectors updated.

Operator messages. Six refusals are reworded, for example "manifest: [[subscription]] table {index} must declare a string kind" becomes "manifest: [[trigger]] table {index} must declare a string on".

Public Extension trait. subscriptions() becomes emits_trigger_kinds(), events() becomes open_sources(), EventSources becomes SourceContext (subscribed becomes demanded_extension_kinds), ExtensionEvent becomes ExtensionDelivery { extension_kind, attrs, trigger }, ExtensionEventStream becomes ExtensionSource.

Supervisor API. supervisor/subscriptions.rs becomes supervisor/triggers.rs, subscription_plan() becomes trigger_plan(), SubscriptionPlan becomes TriggerPlan, ChainLogSub becomes EventTrigger.

SDK and macro. Handlers are init, on_block, on_event, on_schedule, on_extension. subscribes(...) becomes sol_events(...), and nexum_sdk::events becomes nexum_sdk::sol_events.

Why event(log) rather than chain-log

The vocabulary contradicted itself one line apart: the trigger was chain-log while its filter field is event_signature and the SDK macro is sol_events. Those three now name one concept.

In Ethereum vocabulary an event is precise, contract-emitted and ABI-defined, so it names a species of trigger rather than the genus, and a block is correctly not one. The chain is implied by context. The payload keeps the name log because an event is what a contract emitted and a log is the record of it, which is why eth_getLogs is spelled that way.

Renaming the label followed necessarily. Leaving event_kind while its value became "event" would emit event_kind="event", asserting that "event" is both the genus and one of its species: the same self-contradiction this rename removes, moved one layer up. ADR-0019 already required trigger_kind, so it lands here rather than breaking the same dashboards twice.

The scope boundary

This renames what an author declares and receives. It does not rename how the host fetches from a node, where "log" is the correct RPC word.

Left alone deliberately: ProviderPool::watch_chain_logs, open_chain_log_streams, push_chain_log, CanonicalLogBatch, TaggedChainLog, log_backfill_concurrency, and the stream_reconnects_total label kind = "chain-log", which names the RPC stream rather than a trigger.

No shim, no alias, no migration hint

No compatibility shim, no type alias, no deprecated re-export, no refusal text mentioning the old spelling.

The project is pre-release, so nobody is owed a deprecation window. An unmigrated [[subscription]] already refuses loudly: the parser reports the section unclaimed and boot stops, which is the whole diagnosis a reader needs. A refusal explaining what a key used to be called is cruft that outlives the migration by years.

Deliberately unchanged

nexum:host stays at 0.1.0. Nothing is published, so the version denotes nothing to anyone and there is no consumer pinned to 0.1.0 to protect. Breaking changes land on it until there is a tag. #150 takes its compatibility baseline at that tag, which is the first moment a baseline is meaningful.

event_signature is untouched. It is the Solidity ABI hash: "event" there is the EVM's noun, and it now agrees with the trigger name instead of contradicting it.

The persisted store keys are byte-identical: last_dispatched_block:{chain_id} and chainlog_cursor:{keccak}. cursors.rs does not appear in the diff at all. A vocabulary rename must not orphan operator data, and the data-compat tests pinning both derivations pass untouched.

Downstream

videre and shepherd implement the Extension trait and are broken by this. Both are already unbuildable against main for unrelated reasons, so this adds no new blockage. Their migration is #262.

Review

Two rounds, 23 findings, 20 fixed and 3 rejected.

First round, ten findings, all minor. Stale event-module in the CORE_NAMESPACE rustdoc; a nexum-sdk/Cargo.toml comment pointing at the deleted events module; the published nexum-module-macros description still saying "event dispatch"; six fixture Cargo.toml descriptions and a flaky-bomb/component.toml comment left on the retired vocabulary while their manifests moved; two sentences in the Dispatch section of docs/02 still calling the dispatched value an event; and three cases of comment growth, all trimmed back to their pre-rename length.

Rejected: present-tense on-event in ADR-0022 and event-module in ADR-0020. ADRs are dated records; #239 enumerates the ADR edits exactly and neither is in that list, and ADR-0019 is itself the record deciding this rename and quotes every retired spelling. The sweep targets live surfaces, not decision records.

Second round, thirteen findings, two major. The event_kind label, described above. The retired "subscription" vocabulary surviving in the supervisor API, now swept. Plus ADR-0019's Decision line still naming the old core set, a docs/02 row carrying an unsupported release claim, an oversimplified retired-kind test rewritten against a realistic stale manifest, a missing BREAKING CHANGE footer, and three comment-noise items.

Rejected: renaming LogSource and ManifestSource, because ADR-0019 schedules the remaining source renames for later issues and LogSource also names a live tracing field; and one cited ADR line that the transport-layer exemption leaves true.

Verification

just ci green: content lint, zero-leak, MSRV, fmt, clippy under -D warnings, rustdoc, all twelve release wasms rebuilt, 783/783 nextest, doctests. TriggerKind::VARIANTS is ["block", "event", "schedule"], and "chain-log" and "cron" are both pinned as refusing.

AI Assistance: Claude Fable 5 and Claude Opus 5 used for the implementation, review, and this description.

@mfw78
mfw78 force-pushed the fix/shutdown-drain-and-jitter branch from d92cadf to 7ab9674 Compare August 17, 2026 08:35
@mfw78
mfw78 force-pushed the refactor/triggers-not-events branch from a4b16a5 to 7522218 Compare August 17, 2026 08:36
@mfw78
mfw78 force-pushed the fix/shutdown-drain-and-jitter branch from 7ab9674 to 01faf6c Compare August 17, 2026 12:17
@mfw78
mfw78 force-pushed the refactor/triggers-not-events branch from 7522218 to b586ec0 Compare August 17, 2026 12:20
@mfw78
mfw78 force-pushed the fix/shutdown-drain-and-jitter branch from 01faf6c to db14fc1 Compare August 17, 2026 12:42
Base automatically changed from fix/shutdown-drain-and-jitter to main August 17, 2026 12:42
@mfw78
mfw78 force-pushed the refactor/triggers-not-events branch 2 times, most recently from 5dcf7eb to d93553a Compare August 17, 2026 12:44
@mfw78

mfw78 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Dropped the nexum:host 0.1.0 to 0.2.0 bump. Nothing is published, so the version denotes nothing to anyone: there is no consumer pinned to 0.1.0 to protect, and bumping it is churn across seven WIT files, the synthesized world, the capability table and the docs for no reader.

The package stays at 0.1.0 and breaking changes land on it until there is a tag. #150's compatibility baseline gets taken at that tag, which is the first moment a baseline means anything.

Reverted in the WIT files, nexum-world's CORE table and synthesized world text, the crosscheck test, capabilities.rs and docs/02. wasi:*@0.2.0 is untouched, being a real WASI version rather than ours. Guests rebuilt, just ci green at 783 tests.

The rest of the rename is unchanged: this only undoes the version churn.

@mfw78
mfw78 force-pushed the refactor/triggers-not-events branch from d93553a to 812f826 Compare August 17, 2026 12:48
A module reacts to a trigger; "subscription" is eth_subscribe's word for a transport the runtime mostly does not use for chain logs.
One commit by construction: on-trigger is part of every component's exported type, so the world, the parser, and every guest move together.

The chain-log trigger is renamed to event in the same commit: an event is the contract-emitted ABI-defined thing the trigger filters on, so the trigger name now agrees with its event_signature field and the sol_events macro instead of contradicting them one line apart.

WIT: `world trigger-module` exports `on-trigger`; `variant trigger` carries `event(log)` (the record is `log`, with `chain-id` on it), `schedule(schedule-tick)` and `extension(extension-trigger)`.
Manifest: `[[subscription]]` with `kind` becomes `[[trigger]]` with `on`; the kebab wire values are `block`, `event` and `schedule` (`chain-log` becomes `event`, `cron` becomes `schedule` with the expression under the `cron` key), and `event_signature` stays (it is the Solidity ABI hash).
Parser and refusals: `TriggerKind`, `manifest::Trigger`, `MissingTriggerKind`, `InvalidTrigger`, `InvalidEventAddress`, `InvalidEventTopic`, `NonStringTriggerFilter`, `TriggerKindClaimed`, `UnknownTriggerKind`, `ChainTriggerNotPermitted` and `DeadHoldTriggers`; the pinned `error_kind` set respells without gaining a value.
Supervisor: `subscription_plan` becomes `trigger_plan` returning a `TriggerPlan` whose event projection is `EventTrigger`, and the dispatch metric label `event_kind` becomes `trigger_kind` as ADR-0019 decides.
Extension seam: `emits_trigger_kinds`, `open_sources`, `SourceContext`, `ExtensionDelivery` and `ExtensionSource`; videre and shepherd migrate under #262.
SDK and macro: handlers `on_block`, `on_event`, `on_schedule`, `on_extension`; `subscribes(...)` becomes `sol_events(...)`, `nexum_sdk::events` becomes `nexum_sdk::sol_events` and `ChainLogParts` becomes `LogParts`.
The chain transport keeps its RPC vocabulary (`watch_chain_logs`, the per-trigger log streams, `log_backfill_concurrency`), and the persisted store keys `chainlog_cursor:{keccak}` and `last_dispatched_block:{chain_id}` are unchanged.
Docs: 02-modules-events-packaging.md becomes 02-modules-triggers-packaging.md with its source table rewritten.

Closes #239

BREAKING CHANGE: the manifest table `[[subscription]]` with `kind` becomes `[[trigger]]` with `on`, and the wire values `chain-log` and `cron` become `event` and `schedule`; the WIT world `event-module` becomes `trigger-module` and its export `on-event` becomes `on-trigger`, so every guest rebuilds; the metric label `event_kind` becomes `trigger_kind` on `nexum_runtime_event_latency_seconds` and `nexum_runtime_dispatch_dropped_total`; the supervisor API `subscription_plan`, `SubscriptionPlan` and `ChainLogSub` become `trigger_plan`, `TriggerPlan` and `EventTrigger`.
AI Assistance: Claude Fable 5 used for the implementation.
@mfw78

mfw78 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Folded in the chain-log to event(log) rename, and one thing it forced.

The rename. The trigger variant is now event(log), the manifest value is on = "event", and TriggerKind::VARIANTS is ["block", "event", "schedule"]. The motive is not terseness: the vocabulary contradicted itself one line apart, with on = "chain-log" sitting above event_signature and beside sol_events in the macro. Those three now name one concept. In Ethereum vocabulary an event is precise, contract-emitted and ABI-defined, so it names a species of trigger rather than the genus, and the chain is implied.

The scope boundary, which is where this could have gone wrong. Renamed what an author declares and receives; left what describes how the host fetches from a node. ProviderPool::watch_chain_logs, open_chain_log_streams, push_chain_log and the stream_reconnects_total label kind = "chain-log" all keep the log word, because eth_getLogs returns logs and that is what they do.

Three exemptions held. The persisted key chainlog_cursor:<keccak> is byte-identical, and cursors.rs does not appear in the diff at all; renaming it would orphan every existing store. event_signature is unchanged. nexum:host stays at 0.1.0.

The label rename came with it, deliberately. Review found that leaving event_kind while the value became "event" would emit event_kind="event", asserting that "event" is both the genus and one of its species: the same self-contradiction this rename exists to remove, moved one layer up. ADR-0019 already required trigger_kind, so it lands here rather than breaking the same dashboards a second time later. nexum_runtime_event_latency_seconds and nexum_runtime_dispatch_dropped_total keep their names; renaming those with a dual-emit window is still #243's.

Also swept, since the retired vocabulary had survived in the supervisor API: supervisor/subscriptions.rs is now supervisor/triggers.rs, subscription_plan() is trigger_plan(), SubscriptionPlan is TriggerPlan, and ChainLogSub is EventTrigger.

13 findings, 11 fixed, 2 rejected. The rejections: LogSource and ManifestSource stay, because ADR-0019 schedules the remaining source renames for later issues and LogSource also names a live tracing field; and ADR-0019 line 57 was not falsified, because the transport layer deliberately keeps the chain-log name.

just build rebuilt all 12 guests, just ci green at 783 tests.

@mfw78
mfw78 force-pushed the refactor/triggers-not-events branch from 812f826 to f351a25 Compare August 17, 2026 13:46
@mfw78
mfw78 merged commit d021c52 into main Aug 17, 2026
5 checks passed
@mfw78
mfw78 deleted the refactor/triggers-not-events branch August 17, 2026 23:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

wit: modules react to triggers, not events or subscriptions

1 participant