@ours.network/notifications 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +98 -0
- package/README.md +231 -0
- package/dist/cli.js +446 -0
- package/dist/config.js +104 -0
- package/dist/daemon.js +353 -0
- package/dist/http.js +31 -0
- package/dist/log-store.js +48 -0
- package/dist/presence.js +157 -0
- package/dist/provision.js +29 -0
- package/dist/push.js +6 -0
- package/dist/web-push-sender.js +41 -0
- package/mufl_code/20E39399689579D5AFA5132C59DEDDD328C3BCA1A74D546F2193B36585DDD283.muflo +0 -0
- package/mufl_code/config.mufl +21 -0
- package/mufl_code/core/.github/FUNDING.yml +3 -0
- package/mufl_code/core/.github/workflows/docs.yml +39 -0
- package/mufl_code/core/.github/workflows/tests.yml +66 -0
- package/mufl_code/core/COMMERCIAL-LICENCE.md +25 -0
- package/mufl_code/core/COMPATIBILITY.md +221 -0
- package/mufl_code/core/CONTRIBUTING.md +37 -0
- package/mufl_code/core/LICENSE +98 -0
- package/mufl_code/core/NOTIFICATIONS_CONTRACT.md +290 -0
- package/mufl_code/core/README.md +91 -0
- package/mufl_code/core/SECURITY.md +40 -0
- package/mufl_code/core/TRADEMARKS.md +24 -0
- package/mufl_code/core/a2a_capabilities.mm +449 -0
- package/mufl_code/core/a2a_cluster.mm +601 -0
- package/mufl_code/core/a2a_control.mm +90 -0
- package/mufl_code/core/a2a_messaging.mm +4817 -0
- package/mufl_code/core/a2a_monitoring.mm +94 -0
- package/mufl_code/core/a2a_notification_integration.mm +144 -0
- package/mufl_code/core/a2a_notifications.mm +1127 -0
- package/mufl_code/core/a2a_protocol.mm +246 -0
- package/mufl_code/core/a2a_versions.mm +801 -0
- package/mufl_code/core/config.mufl +29 -0
- package/mufl_code/core/docs/.vitepress/config.mts +58 -0
- package/mufl_code/core/docs/citations.json +688 -0
- package/mufl_code/core/docs/guide/01-vendor-the-core.md +50 -0
- package/mufl_code/core/docs/guide/02-configure-and-compile.md +100 -0
- package/mufl_code/core/docs/guide/03-wire-the-host.md +154 -0
- package/mufl_code/core/docs/guide/04-connect-and-message.md +156 -0
- package/mufl_code/core/docs/guide/05-test-your-app.md +75 -0
- package/mufl_code/core/docs/guide/index.md +63 -0
- package/mufl_code/core/docs/how-it-works/capabilities-and-control.md +77 -0
- package/mufl_code/core/docs/how-it-works/cluster.md +65 -0
- package/mufl_code/core/docs/how-it-works/identity.md +56 -0
- package/mufl_code/core/docs/how-it-works/invites-and-contacts.md +76 -0
- package/mufl_code/core/docs/how-it-works/messaging.md +55 -0
- package/mufl_code/core/docs/how-it-works/monitoring-and-config.md +72 -0
- package/mufl_code/core/docs/how-it-works/overview.md +45 -0
- package/mufl_code/core/docs/how-it-works/versioning.md +116 -0
- package/mufl_code/core/docs/index.md +27 -0
- package/mufl_code/core/docs/package-lock.json +3467 -0
- package/mufl_code/core/docs/package.json +15 -0
- package/mufl_code/core/docs/reference/contributing.md +26 -0
- package/mufl_code/core/docs/reference/glossary.md +108 -0
- package/mufl_code/core/docs/reference/implementations.md +23 -0
- package/mufl_code/core/docs/reference/modules.md +19 -0
- package/mufl_code/core/docs/scripts/check-citations.mjs +36 -0
- package/mufl_code/core/docs/scripts/check-denylist.mjs +30 -0
- package/mufl_code/core/docs/scripts/gen-llms.mjs +32 -0
- package/mufl_code/core/docs/workflows/cluster.md +89 -0
- package/mufl_code/core/docs/workflows/contact-exchange.md +60 -0
- package/mufl_code/core/docs/workflows/contact-restore.md +60 -0
- package/mufl_code/core/docs/workflows/control-verbs.md +70 -0
- package/mufl_code/core/docs/workflows/index.md +57 -0
- package/mufl_code/core/docs/workflows/introductions.md +60 -0
- package/mufl_code/core/docs/workflows/messaging.md +75 -0
- package/mufl_code/core/docs/workflows/monitoring.md +84 -0
- package/mufl_code/core/docs/workflows/notifications.md +109 -0
- package/mufl_code/core/release-notes/0.1.md +60 -0
- package/mufl_code/core/release-notes/0.3.md +22 -0
- package/mufl_code/core/release-notes/0.4.md +97 -0
- package/mufl_code/core/tests/README.md +83 -0
- package/mufl_code/core/tests/compat/README.md +82 -0
- package/mufl_code/core/tests/compat/compat.mjs +245 -0
- package/mufl_code/core/tests/compat/compat_peer.mjs +146 -0
- package/mufl_code/core/tests/compat/run_compat.sh +96 -0
- package/mufl_code/core/tests/corpus.mjs +139 -0
- package/mufl_code/core/tests/corpus_actor.mu +242 -0
- package/mufl_code/core/tests/fatcompile_actor.mu +52 -0
- package/mufl_code/core/tests/mig.mjs +805 -0
- package/mufl_code/core/tests/mig_actor.mu +518 -0
- package/mufl_code/core/tests/migapp.mjs +431 -0
- package/mufl_code/core/tests/migapp_actor.mu +294 -0
- package/mufl_code/core/tests/mufl_semantics/run.sh +69 -0
- package/mufl_code/core/tests/mufl_semantics/t11_three_version.mufl +47 -0
- package/mufl_code/core/tests/mufl_semantics/t2_extra_fields.mufl +11 -0
- package/mufl_code/core/tests/mufl_semantics/t7_runtime.mufl +30 -0
- package/mufl_code/core/tests/mufl_semantics/t9_passthrough.mufl +19 -0
- package/mufl_code/core/tests/mufl_semantics/t_str_order.mufl +19 -0
- package/mufl_code/core/tests/notif.mjs +755 -0
- package/mufl_code/core/tests/notif_actor.mu +450 -0
- package/mufl_code/core/tests/protocol_container.mm +18 -0
- package/mufl_code/core/tests/run.sh +65 -0
- package/mufl_code/core/tests/run_corpus.sh +51 -0
- package/mufl_code/core/tests/run_fatcompile.sh +40 -0
- package/mufl_code/core/tests/run_mig.sh +51 -0
- package/mufl_code/core/tests/run_migapp.sh +51 -0
- package/mufl_code/core/tests/run_notif.sh +57 -0
- package/mufl_code/core/tests/test.mjs +750 -0
- package/mufl_code/core/tests/test_actor.mu +564 -0
- package/mufl_code/core/tests/test_common.mjs +93 -0
- package/mufl_code/core/version.mm +24 -0
- package/mufl_code/packet.mu +170 -0
- package/mufl_code/protocol_container.mm +18 -0
- package/package.json +43 -0
- package/scripts/gen-vapid.mjs +16 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Shared ours monitoring library — CONTROL-PLANE (receiver) side.
|
|
2
|
+
//
|
|
3
|
+
// Monitoring is split across two libraries by WHERE the state lives:
|
|
4
|
+
//
|
|
5
|
+
// - NODE side (a2a_messaging): the forced copy GENERATION
|
|
6
|
+
// (monitor_copy_actions in the send/receive chokepoint) AND the gate state
|
|
7
|
+
// it reads (monitoring_proxy / proxy_pending) plus that state's mutators —
|
|
8
|
+
// the 6-digit bind ceremony (set_proxy_pending / verify_proxy_code), the
|
|
9
|
+
// CP-authenticated disable_monitoring, and get_monitoring_status. They MUST
|
|
10
|
+
// live in a2a_messaging because the gate state is `hidden` there, and hidden
|
|
11
|
+
// state is mutable only by its declaring library — that is precisely what
|
|
12
|
+
// stops an app (or any other library) from assigning monitoring_proxy -> NIL
|
|
13
|
+
// to switch monitoring off without the ceremony.
|
|
14
|
+
//
|
|
15
|
+
// - CONTROL-PLANE side (HERE): the receiver for the copies a monitored node
|
|
16
|
+
// forwards. It touches NO gate state — it only validates the sender is a
|
|
17
|
+
// known contact and hands the copy to the app's storage hook. This is the
|
|
18
|
+
// only piece that can safely live outside a2a_messaging.
|
|
19
|
+
//
|
|
20
|
+
// Security model (founder directive, the ours monitoring design's accepted self-assertion limitation): monitoring is secured by the
|
|
21
|
+
// bind ceremony + open source + eviction; a node self-asserts monitoring_status,
|
|
22
|
+
// it is NOT cert-enforced. The "app can't override" guarantee is the narrower,
|
|
23
|
+
// real one: copy generation is unconditional core code and the gate state is
|
|
24
|
+
// non-app-writable (hidden), so an honest app cannot silently disable monitoring.
|
|
25
|
+
library a2a_monitoring loads libraries
|
|
26
|
+
current_transaction_info,
|
|
27
|
+
encrypted_channel,
|
|
28
|
+
a2a_messaging
|
|
29
|
+
uses transactions
|
|
30
|
+
{
|
|
31
|
+
hidden
|
|
32
|
+
{
|
|
33
|
+
// App-injected: store one received monitoring copy. Storage stays app-side
|
|
34
|
+
// (the messenger persists the feed), like a2a_messaging's message hooks.
|
|
35
|
+
// Receives ($source_id, $copy). Fires only for a known-contact sender.
|
|
36
|
+
on_monitoring_copy_received is (any -> transaction::action::type[]) = fn (_: any) -> transaction::action::type[] { abort "on_monitoring_copy_received hook is unset in a2a_monitoring (call a2a_monitoring::init)." when TRUE. return []. }
|
|
37
|
+
// core 3.2: store one pushed roster snapshot. Fires only for a known-contact
|
|
38
|
+
// sender (the node this control plane monitors). Receives ($source_id, $version, $members).
|
|
39
|
+
on_roster_update is (any -> transaction::action::type[]) = fn (_: any) -> transaction::action::type[] { abort "on_roster_update hook is unset in a2a_monitoring (call a2a_monitoring::init)." when TRUE. return []. }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
init = fn (_:(
|
|
43
|
+
$on_monitoring_copy_received -> cb: (any -> transaction::action::type[]),
|
|
44
|
+
$on_roster_update -> rcb: (any -> transaction::action::type[])
|
|
45
|
+
))
|
|
46
|
+
{
|
|
47
|
+
on_monitoring_copy_received -> cb.
|
|
48
|
+
on_roster_update -> rcb.
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Receive a forced copy from a node this control plane monitors. Storage is
|
|
52
|
+
// the app's (the on_monitoring_copy_received hook); the source is the channel-
|
|
53
|
+
// authenticated envelope sender, which must be a known contact. Touches no
|
|
54
|
+
// gate state — purely the CP-side ingest.
|
|
55
|
+
fn handle_receive_monitoring_copy (args: any) -> transaction::results::type
|
|
56
|
+
{
|
|
57
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::external,).
|
|
58
|
+
encrypted_channel::check_encrypted_or_abort().
|
|
59
|
+
|
|
60
|
+
sender_id = current_transaction_info::get_external_envelope_or_abort() $from.
|
|
61
|
+
abort "Monitoring copy from an unknown sender was rejected." when (a2a_messaging::contacts sender_id) == NIL.
|
|
62
|
+
copy = (args $copy) safe a2a_messaging::monitoring_copy_t.
|
|
63
|
+
abort "Unsupported monitoring copy version." when (copy $version) != 1.
|
|
64
|
+
|
|
65
|
+
return transaction::success (on_monitoring_copy_received (
|
|
66
|
+
$source_id -> sender_id,
|
|
67
|
+
$copy -> copy
|
|
68
|
+
)).
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
trn receive_monitoring_copy args: any
|
|
72
|
+
{
|
|
73
|
+
return handle_receive_monitoring_copy args.
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// core 3.2: receive a roster snapshot pushed by a node we control. Same CP-side
|
|
77
|
+
// ingest contract as receive_monitoring_copy — known-contact sender only, no gate
|
|
78
|
+
// state touched; the app stores it (on_roster_update). $members is opaque (the app
|
|
79
|
+
// interprets member_t); $version lets the app dedup/order.
|
|
80
|
+
fn handle_receive_roster_update (args: any) -> transaction::results::type
|
|
81
|
+
{
|
|
82
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::external,).
|
|
83
|
+
encrypted_channel::check_encrypted_or_abort().
|
|
84
|
+
sender_id = current_transaction_info::get_external_envelope_or_abort() $from.
|
|
85
|
+
abort "Roster update from an unknown sender was rejected." when (a2a_messaging::contacts sender_id) == NIL.
|
|
86
|
+
return transaction::success (on_roster_update (
|
|
87
|
+
$source_id -> sender_id,
|
|
88
|
+
$version -> (args $version),
|
|
89
|
+
$members -> (args $members)
|
|
90
|
+
)).
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
trn receive_roster_update args: any { return handle_receive_roster_update args. }
|
|
94
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Core-owned notification integration for ordinary message sends.
|
|
2
|
+
//
|
|
3
|
+
// Recipients hand each sender a scoped notify_address_t over their established
|
|
4
|
+
// encrypted channel. This library owns those opaque handouts, persists them,
|
|
5
|
+
// and installs the optional a2a_messaging successful-send middleware. Missing
|
|
6
|
+
// or deleted handouts are no-ops; notification delivery is fire-and-forget.
|
|
7
|
+
library a2a_notification_integration loads libraries
|
|
8
|
+
current_transaction_info,
|
|
9
|
+
encrypted_channel,
|
|
10
|
+
a2a_messaging,
|
|
11
|
+
a2a_notifications
|
|
12
|
+
uses transactions
|
|
13
|
+
{
|
|
14
|
+
receive_notify_address_tx = "::a2a_notification_integration::receive_notify_address".
|
|
15
|
+
|
|
16
|
+
// Peer cid -> validated serialized notify_address_t. Opaque serialized
|
|
17
|
+
// storage composes cleanly beside a2a_messaging::export_core_state.
|
|
18
|
+
received_notify_addresses is (global_id ->> bin) = (,).
|
|
19
|
+
|
|
20
|
+
metadef integration_state_t: (
|
|
21
|
+
$version -> int,
|
|
22
|
+
$received_notify_addresses -> (global_id ->> bin)
|
|
23
|
+
).
|
|
24
|
+
|
|
25
|
+
fn _save_state (_) = (transaction::action::return_data ($kind -> $save_state)).
|
|
26
|
+
|
|
27
|
+
// Relationship binding is checked at the handout boundary. The service
|
|
28
|
+
// later validates the token against its signed live exact-token store; the
|
|
29
|
+
// current protocol intentionally requires no sender<->service contact.
|
|
30
|
+
fn validate_address_for (recipient: global_id, blob: bin) -> a2a_notifications::notify_address_t
|
|
31
|
+
{
|
|
32
|
+
addr = a2a_notifications::decode_notify_address blob.
|
|
33
|
+
abort "Notify address token recipient does not match its sender." when (addr $token $c $recipient_cid) != recipient.
|
|
34
|
+
abort "Notify address token is not scoped to this sender." when (addr $token $c $scope) != _str (_get_container_id()).
|
|
35
|
+
return addr.
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Ordinary consumers are notification clients, not notification services.
|
|
39
|
+
// Install safe no-op service/UI callbacks here so they only need to init
|
|
40
|
+
// this integration. A service host may call a2a_notifications::init after
|
|
41
|
+
// this function to replace the defaults with durable/WebPush hooks.
|
|
42
|
+
init = fn (_:($_read_or_abort -> read: (bin->any)))
|
|
43
|
+
{
|
|
44
|
+
a2a_notifications::init (
|
|
45
|
+
$_read_or_abort -> read,
|
|
46
|
+
$on_notification_posted -> fn (_: any) -> transaction::action::type[] { return []. },
|
|
47
|
+
$on_notifications_marked_read -> fn (_: any) -> transaction::action::type[] { return []. },
|
|
48
|
+
$on_unregistered -> fn (_: any) -> transaction::action::type[] { return []. },
|
|
49
|
+
$on_notify_registration -> fn (_: any) -> transaction::action::type[] { return []. }
|
|
50
|
+
).
|
|
51
|
+
a2a_messaging::set_post_send_middleware (fn (arg: any) -> transaction::action::type[]
|
|
52
|
+
{
|
|
53
|
+
target_id = (arg $target_id) safe global_id.
|
|
54
|
+
address = received_notify_addresses target_id.
|
|
55
|
+
if address == NIL { return []. }
|
|
56
|
+
|
|
57
|
+
wire_id = (arg $wire_id) safe str.
|
|
58
|
+
// Built only from stringified global ids and wire ids, whose hex
|
|
59
|
+
// alphabets require no JSON escaping.
|
|
60
|
+
payload = "{\"v\":1,\"kind\":\"message\",\"sender\":\"" +
|
|
61
|
+
(_str (_get_container_id())) + "\",\"wire_id\":\"" + wire_id + "\"}".
|
|
62
|
+
return [ a2a_notifications::notification_send_action address? payload wire_id ].
|
|
63
|
+
}).
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fn handle_receive_notify_address (args: any) -> transaction::results::type
|
|
67
|
+
{
|
|
68
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::external,).
|
|
69
|
+
encrypted_channel::check_encrypted_or_abort ().
|
|
70
|
+
from = current_transaction_info::get_external_envelope_or_abort() $from.
|
|
71
|
+
|
|
72
|
+
if (args $address) == NIL
|
|
73
|
+
{
|
|
74
|
+
if (received_notify_addresses from) != NIL { delete received_notify_addresses from. }
|
|
75
|
+
return transaction::success [ _save_state NIL ].
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
blob = (args $address) safe bin.
|
|
79
|
+
validate_address_for from blob.
|
|
80
|
+
received_notify_addresses from -> blob.
|
|
81
|
+
return transaction::success [ _save_state NIL ].
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
trn receive_notify_address args: any
|
|
85
|
+
{
|
|
86
|
+
return handle_receive_notify_address args.
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Recipient-side distribution: build the sender-scoped handout from the
|
|
90
|
+
// notification client mirror and deliver it over the existing encrypted
|
|
91
|
+
// contact channel. Consumers retain only UI policy, not protocol state.
|
|
92
|
+
trn send_notify_address _:($service -> service_ref: str, $contact -> contact_ref: str)
|
|
93
|
+
{
|
|
94
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
95
|
+
target_id = a2a_messaging::resolve_contact contact_ref.
|
|
96
|
+
blob = a2a_notifications::build_notify_address service_ref contact_ref.
|
|
97
|
+
return encrypted_channel::execute_transaction target_id (fn (_) -> transaction::results::type {
|
|
98
|
+
return transaction::success [
|
|
99
|
+
encrypted_channel::send_encrypted_tx target_id (
|
|
100
|
+
$name -> receive_notify_address_tx,
|
|
101
|
+
$targ -> ($address -> blob)
|
|
102
|
+
),
|
|
103
|
+
transaction::action::return_data ($kind -> $data, $payload -> ($sent_to -> target_id)),
|
|
104
|
+
_save_state NIL
|
|
105
|
+
].
|
|
106
|
+
}).
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// NIL is the wire-level mute/removal signal and intentionally leaves the
|
|
110
|
+
// ordinary messaging relationship untouched.
|
|
111
|
+
trn delete_notify_address _:($contact -> contact_ref: str)
|
|
112
|
+
{
|
|
113
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
114
|
+
target_id = a2a_messaging::resolve_contact contact_ref.
|
|
115
|
+
return encrypted_channel::execute_transaction target_id (fn (_) -> transaction::results::type {
|
|
116
|
+
return transaction::success [
|
|
117
|
+
encrypted_channel::send_encrypted_tx target_id (
|
|
118
|
+
$name -> receive_notify_address_tx,
|
|
119
|
+
$targ -> ($address -> NIL)
|
|
120
|
+
),
|
|
121
|
+
transaction::action::return_data ($kind -> $data, $payload -> ($sent_to -> target_id)),
|
|
122
|
+
_save_state NIL
|
|
123
|
+
].
|
|
124
|
+
}).
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fn export_state (_) -> integration_state_t
|
|
128
|
+
{
|
|
129
|
+
return (
|
|
130
|
+
$version -> 1,
|
|
131
|
+
$received_notify_addresses -> received_notify_addresses
|
|
132
|
+
).
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fn import_state (data: any) -> nil
|
|
136
|
+
{
|
|
137
|
+
abort "Unsupported notification integration state version." when ((data $version) safe int) != 1.
|
|
138
|
+
imported = (data $received_notify_addresses) safe (global_id ->> bin).
|
|
139
|
+
// Validate now so a corrupt local snapshot cannot turn a later accepted
|
|
140
|
+
// send_message into a middleware parse abort.
|
|
141
|
+
sc imported -- (peer -> blob) { validate_address_for peer blob. }
|
|
142
|
+
received_notify_addresses -> imported.
|
|
143
|
+
}
|
|
144
|
+
}
|