neon-phal-plugin-notifications
Notification Manager for Neon/OVOS, packaged as a PHAL plugin. It is the single
chokepoint for ovos.notification.api.* traffic on the hub messagebus: it
validates producer requests, owns notification state (active / snoozed /
dismissed / expired), persists that state across restarts, enforces emission
and dismissal policy, and emits notify / dismiss / snoozed events for
consumers (the Node app today, other plugins later).
The plugin never renders anything and never talks to MQ. Bus in, bus out.
Transport lives in neon-messagebus-mq-connector and neon-hana; rendering
lives in consumers.
Requirements
Notification data models come from neon-data-models. Until the next release
they live on the FEAT_Notifications branch; install that branch first:
pip install git+https://github.com/NeonGeckoCom/neon-data-models@FEAT_Notifications
pip install neon-phal-plugin-notifications
Configuration
All keys live under the plugin's PHAL section. Every key is optional.
PHAL:
neon-phal-plugin-notifications:
store: json # persistence adapter; only "json" ships
store_path: null # override the JSON file path (default:
# $XDG_DATA_HOME/neon/neon-phal-plugin-notifications/notifications.json)
allow_global: # skill_ids permitted to use scope=GLOBAL
- skill-alerts.neongeckocom
allow_non_removable: # skill_ids permitted to set removable_by_user=false
- skill-alerts.neongeckocom
rate_limit: 30 # accepted `set` requests per producer per window
rate_window_seconds: 3600
consumers_allowed_to_dismiss: [] # empty/absent = every consumer may dismiss
consumers_blocked_from_dismiss: []
retention_max_age_days: 7 # tombstone window for dismissed/expired entries
tick_seconds: 60 # expiry + retention sweep interval
Policy defaults are asymmetric on purpose: dismissal is open, high-impact
emission (GLOBAL scope, non-removable) is closed except for seeded first-party
producers. An unlisted producer that requests either is refused with a reason
in .set.response; nothing is silently downgraded.
Message API
Every request gets a .response reply on the request's own context, so MQ
routing survives the round trip.
| Consumed | Reply |
|---|---|
ovos.notification.api.set {notification} |
.set.response {notification_id, status, reason?} |
ovos.notification.api.remove {notification_id?, skill_id?, dismissed_by?} |
.remove.response {notification_ids, status, reason?} |
ovos.notification.api.get {notification_id} |
.get.response {notification} |
ovos.notification.api.list {skill_id?, state?, since?, node_id?, user_id?} |
.list.response {notifications, states} |
ovos.notification.api.snooze {notification_id, duration} |
.snooze.response {notification_id, status, renotify_at?, reason?} |
ovos.notification.api.interaction {notification_id, action_id, callback_data?} |
none; re-emitted for the producer, then dismissed if the action's dismiss_on_activate is true |
ovos.notification.api.sync.request {since?} |
.sync.request.response {notifications, states, server_time} plus one notify per entry |
ovos.notification.api.set.controlled / .remove.controlled |
deprecated wrappers; reply as .set.response / .remove.response |
| Emitted | When |
|---|---|
ovos.notification.api.notify {notification} |
a set is accepted, a snooze elapses, mycroft.ready, sync.request |
ovos.notification.api.dismiss {notification_id, skill_id, scope, target, dismissed_by} |
a notification is removed, dismissed, or expires (dismissed_by: "expired") |
ovos.notification.api.snoozed {notification_id, renotify_at, scope, target} |
a snooze is granted |
Behavior notes:
setis an upsert. The sameskill_idre-sending an existingnotification_idreplaces it in place, resets it to ACTIVE, and re-emitsnotify. Anotification_idowned by a differentskill_idis refused.created_atis stamped by the manager on first accept and preserved on updates;updated_atis bumped on every change (upsert, snooze, re-notify, dismissal, expiry, per-client dismissal) and rides on the wireNotification. Everysincefilter (list,sync.request, and so the HANA REST catch-up) compares againstupdated_at.- Retention is a tombstone window, not history: DISMISSED and EXPIRED entries
stay for
retention_max_age_daysso a node that was offline learns on catch-up that a notification was dismissed elsewhere, then they are pruned. ACTIVE and SNOOZED entries are never pruned. - A missing
sessionis filled fromMessage.context.session. A CLIENT-scoped notification with notargettakes the requester's node identity from the context (context.node.node_id, thencontext.client_id, then a non-localsession.session_id). removeneedsnotification_id(the producer is resolved from the stored record; askill_idthat disagrees with it is refused) orskill_idalone (remove everything that producer owns). Neither is refused. The requester isdismissed_by, elseskill_id, else the context's node/session identity. A requester equal to the producer bypassesremovable_by_user; anyone else is a consumer subject toremovable_by_userand the consumer dismiss lists.dismiss_policy=PER_CLIENT: a consumer dismissal records that client and leaves the shared state ACTIVE;listwithnode_idreports DISMISSED for that client only.SHAREDdismissals set DISMISSED for everyone.- Legacy flat GUI-API payloads (
sender,text,action,type,style) are up-converted with a deprecation log.
Testing
pip install -e .[test]
pytest tests/
Metadata
Release files for neon-phal-plugin-notifications 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| neon_phal_plugin_notifications-0.1.0.tar.gz | 27.5 kB | Without provenance | Details for neon_phal_plugin_notifications-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| neon_phal_plugin_notifications-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for neon_phal_plugin_notifications-0.1.0-py3-none-any.whl |
Total release size: 54.6 kB
Release files / neon_phal_plugin_notifications-0.1.0.tar.gz
| Download URL | neon_phal_plugin_notifications-0.1.0.tar.gz |
|---|---|
| Size | 27.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dbe761b596b251c0b3b15f115ce9ee70f7f27ff6ae42d16199f20d5f9c4bdecf
|
|
BLAKE2b-256 checksum How to use checksums |
69cc303360e0cd1c9e0242304b9fb9452a1f3268ecc1d13ac9fbade82a113d18
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / neon_phal_plugin_notifications-0.1.0-py3-none-any.whl
| Download URL | neon_phal_plugin_notifications-0.1.0-py3-none-any.whl |
|---|---|
| Size | 27.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4b598b700743b8b8e81e552c5c0706c4a80c1424aa8b1de17c3e9f0c1680918e
|
|
BLAKE2b-256 checksum How to use checksums |
06da49b88e6b9b7b711ea4eaee9a6c14a76962b4a8bee302c13c51ae93a08463
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Security for neon-phal-plugin-notifications 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.