@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.
Files changed (107) hide show
  1. package/LICENSE +98 -0
  2. package/README.md +231 -0
  3. package/dist/cli.js +446 -0
  4. package/dist/config.js +104 -0
  5. package/dist/daemon.js +353 -0
  6. package/dist/http.js +31 -0
  7. package/dist/log-store.js +48 -0
  8. package/dist/presence.js +157 -0
  9. package/dist/provision.js +29 -0
  10. package/dist/push.js +6 -0
  11. package/dist/web-push-sender.js +41 -0
  12. package/mufl_code/20E39399689579D5AFA5132C59DEDDD328C3BCA1A74D546F2193B36585DDD283.muflo +0 -0
  13. package/mufl_code/config.mufl +21 -0
  14. package/mufl_code/core/.github/FUNDING.yml +3 -0
  15. package/mufl_code/core/.github/workflows/docs.yml +39 -0
  16. package/mufl_code/core/.github/workflows/tests.yml +66 -0
  17. package/mufl_code/core/COMMERCIAL-LICENCE.md +25 -0
  18. package/mufl_code/core/COMPATIBILITY.md +221 -0
  19. package/mufl_code/core/CONTRIBUTING.md +37 -0
  20. package/mufl_code/core/LICENSE +98 -0
  21. package/mufl_code/core/NOTIFICATIONS_CONTRACT.md +290 -0
  22. package/mufl_code/core/README.md +91 -0
  23. package/mufl_code/core/SECURITY.md +40 -0
  24. package/mufl_code/core/TRADEMARKS.md +24 -0
  25. package/mufl_code/core/a2a_capabilities.mm +449 -0
  26. package/mufl_code/core/a2a_cluster.mm +601 -0
  27. package/mufl_code/core/a2a_control.mm +90 -0
  28. package/mufl_code/core/a2a_messaging.mm +4817 -0
  29. package/mufl_code/core/a2a_monitoring.mm +94 -0
  30. package/mufl_code/core/a2a_notification_integration.mm +144 -0
  31. package/mufl_code/core/a2a_notifications.mm +1127 -0
  32. package/mufl_code/core/a2a_protocol.mm +246 -0
  33. package/mufl_code/core/a2a_versions.mm +801 -0
  34. package/mufl_code/core/config.mufl +29 -0
  35. package/mufl_code/core/docs/.vitepress/config.mts +58 -0
  36. package/mufl_code/core/docs/citations.json +688 -0
  37. package/mufl_code/core/docs/guide/01-vendor-the-core.md +50 -0
  38. package/mufl_code/core/docs/guide/02-configure-and-compile.md +100 -0
  39. package/mufl_code/core/docs/guide/03-wire-the-host.md +154 -0
  40. package/mufl_code/core/docs/guide/04-connect-and-message.md +156 -0
  41. package/mufl_code/core/docs/guide/05-test-your-app.md +75 -0
  42. package/mufl_code/core/docs/guide/index.md +63 -0
  43. package/mufl_code/core/docs/how-it-works/capabilities-and-control.md +77 -0
  44. package/mufl_code/core/docs/how-it-works/cluster.md +65 -0
  45. package/mufl_code/core/docs/how-it-works/identity.md +56 -0
  46. package/mufl_code/core/docs/how-it-works/invites-and-contacts.md +76 -0
  47. package/mufl_code/core/docs/how-it-works/messaging.md +55 -0
  48. package/mufl_code/core/docs/how-it-works/monitoring-and-config.md +72 -0
  49. package/mufl_code/core/docs/how-it-works/overview.md +45 -0
  50. package/mufl_code/core/docs/how-it-works/versioning.md +116 -0
  51. package/mufl_code/core/docs/index.md +27 -0
  52. package/mufl_code/core/docs/package-lock.json +3467 -0
  53. package/mufl_code/core/docs/package.json +15 -0
  54. package/mufl_code/core/docs/reference/contributing.md +26 -0
  55. package/mufl_code/core/docs/reference/glossary.md +108 -0
  56. package/mufl_code/core/docs/reference/implementations.md +23 -0
  57. package/mufl_code/core/docs/reference/modules.md +19 -0
  58. package/mufl_code/core/docs/scripts/check-citations.mjs +36 -0
  59. package/mufl_code/core/docs/scripts/check-denylist.mjs +30 -0
  60. package/mufl_code/core/docs/scripts/gen-llms.mjs +32 -0
  61. package/mufl_code/core/docs/workflows/cluster.md +89 -0
  62. package/mufl_code/core/docs/workflows/contact-exchange.md +60 -0
  63. package/mufl_code/core/docs/workflows/contact-restore.md +60 -0
  64. package/mufl_code/core/docs/workflows/control-verbs.md +70 -0
  65. package/mufl_code/core/docs/workflows/index.md +57 -0
  66. package/mufl_code/core/docs/workflows/introductions.md +60 -0
  67. package/mufl_code/core/docs/workflows/messaging.md +75 -0
  68. package/mufl_code/core/docs/workflows/monitoring.md +84 -0
  69. package/mufl_code/core/docs/workflows/notifications.md +109 -0
  70. package/mufl_code/core/release-notes/0.1.md +60 -0
  71. package/mufl_code/core/release-notes/0.3.md +22 -0
  72. package/mufl_code/core/release-notes/0.4.md +97 -0
  73. package/mufl_code/core/tests/README.md +83 -0
  74. package/mufl_code/core/tests/compat/README.md +82 -0
  75. package/mufl_code/core/tests/compat/compat.mjs +245 -0
  76. package/mufl_code/core/tests/compat/compat_peer.mjs +146 -0
  77. package/mufl_code/core/tests/compat/run_compat.sh +96 -0
  78. package/mufl_code/core/tests/corpus.mjs +139 -0
  79. package/mufl_code/core/tests/corpus_actor.mu +242 -0
  80. package/mufl_code/core/tests/fatcompile_actor.mu +52 -0
  81. package/mufl_code/core/tests/mig.mjs +805 -0
  82. package/mufl_code/core/tests/mig_actor.mu +518 -0
  83. package/mufl_code/core/tests/migapp.mjs +431 -0
  84. package/mufl_code/core/tests/migapp_actor.mu +294 -0
  85. package/mufl_code/core/tests/mufl_semantics/run.sh +69 -0
  86. package/mufl_code/core/tests/mufl_semantics/t11_three_version.mufl +47 -0
  87. package/mufl_code/core/tests/mufl_semantics/t2_extra_fields.mufl +11 -0
  88. package/mufl_code/core/tests/mufl_semantics/t7_runtime.mufl +30 -0
  89. package/mufl_code/core/tests/mufl_semantics/t9_passthrough.mufl +19 -0
  90. package/mufl_code/core/tests/mufl_semantics/t_str_order.mufl +19 -0
  91. package/mufl_code/core/tests/notif.mjs +755 -0
  92. package/mufl_code/core/tests/notif_actor.mu +450 -0
  93. package/mufl_code/core/tests/protocol_container.mm +18 -0
  94. package/mufl_code/core/tests/run.sh +65 -0
  95. package/mufl_code/core/tests/run_corpus.sh +51 -0
  96. package/mufl_code/core/tests/run_fatcompile.sh +40 -0
  97. package/mufl_code/core/tests/run_mig.sh +51 -0
  98. package/mufl_code/core/tests/run_migapp.sh +51 -0
  99. package/mufl_code/core/tests/run_notif.sh +57 -0
  100. package/mufl_code/core/tests/test.mjs +750 -0
  101. package/mufl_code/core/tests/test_actor.mu +564 -0
  102. package/mufl_code/core/tests/test_common.mjs +93 -0
  103. package/mufl_code/core/version.mm +24 -0
  104. package/mufl_code/packet.mu +170 -0
  105. package/mufl_code/protocol_container.mm +18 -0
  106. package/package.json +43 -0
  107. package/scripts/gen-vapid.mjs +16 -0
@@ -0,0 +1,24 @@
1
+ /*
2
+ This library provides a simple version tracking mechanism for the shared
3
+ ours mufl core. It contains a hardcoded read-only version that can be
4
+ accessed by any library or application that loads it.
5
+ This library has no dependencies and can be included in any packet.
6
+ */
7
+ library version
8
+ {
9
+ metadef version_t: (
10
+ $MAJ -> int,
11
+ $MIN -> int,
12
+ $PATCH -> int
13
+ ).
14
+
15
+ fn create_version (maj: int, min: int, patch: int) = ($MIN -> min, $MAJ -> maj, $PATCH -> patch).
16
+
17
+
18
+ // This MUST be updated every time we update ANY code in the shared core.
19
+ hidden {
20
+ core_version = create_version 0 9 0.
21
+ }
22
+
23
+ fn get_core_version(_) = core_version.
24
+ }
@@ -0,0 +1,170 @@
1
+ // ours-notifications service packet — the Notifications-as-a-Service node.
2
+ //
3
+ // Plays the SERVICE half of a2a_notifications (registration + token state,
4
+ // post_notification ingest) plus the ordinary a2a_messaging surface so
5
+ // recipients can connect via invites. Storage and WebPush egress live in the
6
+ // HOST DAEMON (src/daemon.ts): the notification hooks below emit $notify_agent
7
+ // events the daemon consumes ("notification_posted" / "marked_read" /
8
+ // "unregistered"); the daemon owns notifications.json and the web-push calls.
9
+ //
10
+ // The manifest advertises core.notifications (a2a_capabilities), with a no-op
11
+ // verb handler — the capability defines no control verbs in v1
12
+ // (NOTIFICATIONS_CONTRACT.md in the shared core).
13
+ application actor loads libraries
14
+ identity_proof_document,
15
+ attestation_document,
16
+ native_attestation_document,
17
+ transaction_message_decoder,
18
+ address_document,
19
+ address_document_types,
20
+ key_utils,
21
+ key_storage,
22
+ continuation,
23
+ encrypted_channel,
24
+ a2a_protocol,
25
+ a2a_messaging,
26
+ a2a_notifications,
27
+ a2a_capabilities,
28
+ current_transaction_info,
29
+ protocol_container,
30
+ registration_proof,
31
+ version
32
+ uses transactions
33
+ {
34
+ hidden
35
+ {
36
+ // Minimal inbox — the service is not a chat endpoint, but the core's
37
+ // receive path requires a storage hook; kept for operator debugging.
38
+ metadef msg_t: ($sender -> global_id, $text -> str, $wire_id -> str).
39
+ inbox is msg_t[] = [].
40
+
41
+ _read_or_abort = grab( _read_or_abort ).
42
+ key_storage::init ($_read_or_abort -> _read_or_abort).
43
+ encrypted_channel::init ($_read_or_abort -> _read_or_abort).
44
+
45
+ fn _save_state (_) = (transaction::action::return_data ($kind -> $save_state)).
46
+ fn _return_data (payload: any) = (transaction::action::return_data ($kind -> $data, $payload -> payload)).
47
+ fn _notify_agent (payload: any) = (transaction::action::return_data ($kind -> $notify_agent, $payload -> payload)).
48
+
49
+ // Messaging hooks: storage-minimum (deposit inbound text, no-ops else).
50
+ a2a_messaging::init (
51
+ $_read_or_abort -> _read_or_abort,
52
+ $on_message_received -> fn (arg: any) -> transaction::action::type[]
53
+ {
54
+ sid = (arg $sender_id) safe global_id.
55
+ txt = (arg $text) safe str.
56
+ wid is str = "".
57
+ if (arg $wire_id) != NIL { wid -> (arg $wire_id) safe str. }
58
+ inbox (_count inbox|) -> ($sender -> sid, $text -> txt, $wire_id -> wid).
59
+ return [ _notify_agent ($event -> "message_received"), _save_state NIL ].
60
+ },
61
+ $on_message_sent -> fn (_: any) -> transaction::action::type[] { return []. },
62
+ $on_contact_removed -> fn (_: any) -> transaction::action::type[] { return []. },
63
+ $on_file_received -> fn (_: any) -> transaction::action::type[] { return []. },
64
+ $on_file_sent -> fn (_: any) -> transaction::action::type[] { return []. }
65
+ ).
66
+
67
+ // Notification hooks: hand each event to the daemon (the app-side
68
+ // storage/egress owner) as a $notify_agent action.
69
+ a2a_notifications::init (
70
+ $_read_or_abort -> _read_or_abort,
71
+ $on_notification_posted -> fn (arg: any) -> transaction::action::type[]
72
+ {
73
+ return [ _notify_agent (
74
+ $event -> "notification_posted",
75
+ $notification -> (arg $notification),
76
+ $bindings -> (arg $bindings)
77
+ ) ].
78
+ },
79
+ $on_notifications_marked_read -> fn (arg: any) -> transaction::action::type[]
80
+ {
81
+ return [ _notify_agent (
82
+ $event -> "marked_read",
83
+ $recipient_cid -> (arg $recipient_cid),
84
+ $notif_ids -> (arg $notif_ids)
85
+ ) ].
86
+ },
87
+ $on_unregistered -> fn (arg: any) -> transaction::action::type[]
88
+ {
89
+ return [ _notify_agent (
90
+ $event -> "unregistered",
91
+ $recipient_cid -> (arg $recipient_cid)
92
+ ) ].
93
+ },
94
+ // Client-side hook — this packet never registers WITH a service.
95
+ $on_notify_registration -> fn (_: any) -> transaction::action::type[] { return []. }
96
+ ).
97
+
98
+ // Manifest: advertise core.notifications. No control verbs in v1, so
99
+ // the handler is a declared no-op (init fail-fasts without one).
100
+ cap_handlers is (str ->> (any -> transaction::action::type[])) = (,).
101
+ cap_handlers (a2a_capabilities::cap_notifications) -> fn (_: any) -> transaction::action::type[] { return []. }
102
+ cap_supported is str[] = [ a2a_capabilities::cap_notifications ].
103
+
104
+ a2a_capabilities::init (
105
+ $describe -> fn (_: any) -> a2a_capabilities::app_manifest_t {
106
+ caps is (str ->> a2a_capabilities::capability_t) = (,).
107
+ caps (a2a_capabilities::cap_notifications) -> (
108
+ $cap -> a2a_capabilities::cap_notifications, $version -> 1, $params -> "", $secrets -> (,)
109
+ ).
110
+ return (
111
+ $version -> 1,
112
+ $app_id -> "network.ours.notifications",
113
+ $name -> "ours notifications service",
114
+ $description -> "Notifications-as-a-Service node: accepts token-authorized notification posts and pushes them out-of-band (WebPush).",
115
+ $monitoring_status -> "off",
116
+ $capabilities -> caps
117
+ ).
118
+ },
119
+ $authorizer -> a2a_messaging::authorize_control,
120
+ $supported -> cap_supported,
121
+ $handlers -> cap_handlers,
122
+ $on_unknown -> fn (_: any) -> transaction::action::type[] { return []. }
123
+ ).
124
+ }
125
+
126
+ // On recreation the daemon injects the persisted SIGN secret as init_arg
127
+ // (Serialize()-hex of the secretkey_sign); reseeding restores the container
128
+ // id so registrations/tokens survive a restart (adapt #77).
129
+ trn __init arg
130
+ {
131
+ if _typeof arg == "STRING" {
132
+ key_storage::reseed_identity_from_secret
133
+ ((_read_or_abort (_hex_string_to_binary (arg SAFE(str)))) SAFE(secretkey_sign)).
134
+ }
135
+ return ::transaction::success[].
136
+ }
137
+
138
+ // ---- host surface (daemon-driven) ------------------------------------
139
+
140
+ trn readonly get_version _ { return version::get_core_version NIL. }
141
+ trn readonly export_address_document _ { return (_write address_document::get_my_address_document()). }
142
+
143
+ // Identity persistence: the daemon stores this beside the state blob and
144
+ // reinjects it via __init on restart.
145
+ trn readonly export_signing_secret _
146
+ {
147
+ return key_storage::export_identity_signing_secret().
148
+ }
149
+
150
+ // The core's send_message delivers to the legacy ::actor::receive_message
151
+ // name; route it into the shared handler (→ inbox hook above).
152
+ trn receive_message args: any { return a2a_messaging::handle_receive_message args. }
153
+ trn readonly list_incoming_messages _ { return ($inbox -> inbox). }
154
+
155
+ // Full persisted state: BOTH the messaging core half and the notify half.
156
+ trn readonly export_state _
157
+ {
158
+ return (
159
+ $core -> (a2a_messaging::export_core_state NIL),
160
+ $notify -> (a2a_notifications::export_notify_state NIL)
161
+ ).
162
+ }
163
+ trn import_state data: any
164
+ {
165
+ current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
166
+ a2a_messaging::import_core_state (data $core).
167
+ if (data $notify) != NIL { a2a_notifications::import_notify_state (data $notify). }
168
+ return transaction::success [ _return_data ($imported -> TRUE), _save_state NIL ].
169
+ }
170
+ }
@@ -0,0 +1,18 @@
1
+ // Minimal protocol_container library — provides ::protocol_container::init_my_ipd,
2
+ // which the ADAPT wrapper runs on every packet during broker registration
3
+ // (possession-proof / identity-proof-document setup, adapt #77). Mirrors the
4
+ // toolkit's per-unit protocol_container stub; deps resolve from the stdlib.
5
+ library protocol_container loads libraries
6
+ identity_proof_document,
7
+ identity_proof_document_types,
8
+ native_attestation_document,
9
+ browser_attestation_document,
10
+ current_transaction_info
11
+ uses transactions
12
+ {
13
+ trn init_my_ipd _ {
14
+ current_transaction_info::validate_origin (::transaction::envelope::origin::user,).
15
+ identity_proof_document_types::set_my_ipd(identity_proof_document::create()).
16
+ return ::transaction::success [].
17
+ }
18
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@ours.network/notifications",
3
+ "version": "0.1.1",
4
+ "description": "ours.network Notifications-as-a-Service — a2a_notifications service packet + WebPush daemon, with a start/stop/status/install-service CLI (ours-notify). Self-provisions identity + VAPID; runs on any host, point DNS at the discovery port.",
5
+ "license": "FSL-1.1-Apache-2.0",
6
+ "type": "module",
7
+ "bin": {
8
+ "ours-notify": "dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "mufl_code",
13
+ "scripts/gen-vapid.mjs",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "prepare": "scripts/compile-mufl.sh && tsc",
20
+ "start": "node dist/cli.js start",
21
+ "stop": "node dist/cli.js stop",
22
+ "status": "node dist/cli.js status",
23
+ "serve": "node dist/cli.js serve",
24
+ "test": "npm run build && node --test --test-force-exit --test-concurrency=1 tests/*.test.mjs"
25
+ },
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "dependencies": {
30
+ "@adapt-toolkit/sdk": "0.10.12",
31
+ "@adapt-toolkit/sdk-native": "0.10.12",
32
+ "web-push": "^3.6.7",
33
+ "ws": "^8.18.0"
34
+ },
35
+ "devDependencies": {
36
+ "@adapt-toolkit/broker": "0.10.12",
37
+ "@adapt-toolkit/mufl": "0.10.12",
38
+ "@types/node": "^22.0.0",
39
+ "@types/web-push": "^3.6.3",
40
+ "playwright": "^1.61.1",
41
+ "typescript": "^5.5.0"
42
+ }
43
+ }
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ // Generate a VAPID keypair for this service instance.
3
+ // node scripts/gen-vapid.mjs [outdir] (default: ./vapid)
4
+ // Writes outdir/vapid.public (0644) and outdir/vapid.private (0600).
5
+ // The PRIVATE key is daemon-side only — it must never reach packet state
6
+ // (the daemon injects only the public half via set_vapid_public_key).
7
+ import webpush from 'web-push';
8
+ import * as fs from 'node:fs';
9
+ import { resolve } from 'node:path';
10
+
11
+ const outDir = resolve(process.argv[2] || 'vapid');
12
+ fs.mkdirSync(outDir, { recursive: true });
13
+ const { publicKey, privateKey } = webpush.generateVAPIDKeys();
14
+ fs.writeFileSync(resolve(outDir, 'vapid.public'), publicKey + '\n', { mode: 0o644 });
15
+ fs.writeFileSync(resolve(outDir, 'vapid.private'), privateKey + '\n', { mode: 0o600 });
16
+ console.log(`VAPID keypair written to ${outDir}/vapid.public + ${outDir}/vapid.private (0600)`);