@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,50 @@
|
|
|
1
|
+
# 01 · Vendor the core
|
|
2
|
+
|
|
3
|
+
Your application never copies protocol code — it pins the core as a **git submodule**
|
|
4
|
+
so the exact protocol revision you compiled against is recorded in your repo. This is
|
|
5
|
+
how every ours.network client consumes the core (the
|
|
6
|
+
[README](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/README.md) documents
|
|
7
|
+
the same one-liner).
|
|
8
|
+
|
|
9
|
+
**Prereqs:**
|
|
10
|
+
|
|
11
|
+
- `git` installed.
|
|
12
|
+
- Network access to `github.com` (or a local mirror of this repo).
|
|
13
|
+
|
|
14
|
+
**Steps:**
|
|
15
|
+
|
|
16
|
+
1. Create your application repo:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
mkdir my-app && cd my-app
|
|
20
|
+
git init
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
2. Add the core as a submodule under `mufl_code/core`. The `mufl_code/` directory is
|
|
24
|
+
your packet's compile root; the core **must** land in a `core/` subfolder of it,
|
|
25
|
+
because that is where your `config.mufl` will look for it
|
|
26
|
+
(see [02 · Configure & compile](./02-configure-and-compile.md)):
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
git submodule add https://github.com/adapt-toolkit/ours-mufl-core.git mufl_code/core
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
(SSH form: `git@github.com:adapt-toolkit/ours-mufl-core.git`.)
|
|
33
|
+
|
|
34
|
+
When cloning your app later, pull the pinned core with `git submodule update --init`.
|
|
35
|
+
|
|
36
|
+
**Verify:**
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
ls mufl_code/core/*.mm
|
|
40
|
+
git submodule status
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Success markers:
|
|
44
|
+
|
|
45
|
+
- `ls` lists the seven protocol libraries, ending
|
|
46
|
+
`mufl_code/core/a2a_protocol.mm` … `mufl_code/core/version.mm`.
|
|
47
|
+
- `git submodule status` prints one line: a commit hash followed by
|
|
48
|
+
`mufl_code/core`.
|
|
49
|
+
|
|
50
|
+
Next: [02 · Configure & compile](./02-configure-and-compile.md).
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# 02 · Configure & compile
|
|
2
|
+
|
|
3
|
+
The core is a set of pure MUFL libraries with no standalone build — *your* `config.mufl`
|
|
4
|
+
merges its exports with the MUFL stdlib, and *your* application file loads the libraries
|
|
5
|
+
by name. This page produces your first compiled packet (`.muflo`).
|
|
6
|
+
|
|
7
|
+
**Prereqs:**
|
|
8
|
+
|
|
9
|
+
- [01 · Vendor the core](./01-vendor-the-core.md) completed (`mufl_code/core` populated).
|
|
10
|
+
- `$ADAPT_TOOLKIT` exported (see [Start here](./index.md)).
|
|
11
|
+
|
|
12
|
+
**Steps:**
|
|
13
|
+
|
|
14
|
+
1. Create `mufl_code/config.mufl`. The core ships its own compile configuration whose
|
|
15
|
+
`$exports` block lists the seven libraries; your top-level config pulls it in with
|
|
16
|
+
`config_load #"core"` and merges it with the stdlib:
|
|
17
|
+
|
|
18
|
+
```mufl
|
|
19
|
+
config script
|
|
20
|
+
{
|
|
21
|
+
stdlib_config = (config_load #$MUFL_STDLIB_PATH).
|
|
22
|
+
core_config = (config_load #"core").
|
|
23
|
+
(
|
|
24
|
+
$imports ->
|
|
25
|
+
(
|
|
26
|
+
$libraries ->
|
|
27
|
+
(stdlib_config $exports $libraries)
|
|
28
|
+
'(core_config $exports $libraries)
|
|
29
|
+
'($protocol_container -> #"protocol_container.mm"),
|
|
30
|
+
),
|
|
31
|
+
$exports -> ( $libraries -> (,), $applications -> (,) )
|
|
32
|
+
).
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
2. Create `mufl_code/protocol_container.mm`. This is a **boot requirement**: the SDK
|
|
37
|
+
runs `::protocol_container::init_my_ipd` on every packet during broker registration,
|
|
38
|
+
so every application packet must ship this library (the config above maps it in).
|
|
39
|
+
[03 · Wire the host](./03-wire-the-host.md) explains the boot sequence; the stub is:
|
|
40
|
+
|
|
41
|
+
```mufl
|
|
42
|
+
// Minimal protocol_container library — provides ::protocol_container::init_my_ipd,
|
|
43
|
+
// which the ADAPT wrapper runs on every packet during broker registration
|
|
44
|
+
// (possession-proof / identity-proof-document setup). Mirrors the toolkit's
|
|
45
|
+
// per-unit protocol_container stub; deps resolve from the stdlib.
|
|
46
|
+
library protocol_container loads libraries
|
|
47
|
+
identity_proof_document,
|
|
48
|
+
identity_proof_document_types,
|
|
49
|
+
native_attestation_document,
|
|
50
|
+
browser_attestation_document,
|
|
51
|
+
current_transaction_info
|
|
52
|
+
uses transactions
|
|
53
|
+
{
|
|
54
|
+
trn init_my_ipd _ {
|
|
55
|
+
current_transaction_info::validate_origin (::transaction::envelope::origin::user,).
|
|
56
|
+
identity_proof_document_types::set_my_ipd(identity_proof_document::create()).
|
|
57
|
+
return ::transaction::success [].
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
3. Create a minimal application, `mufl_code/my_agent.mu`. The file name is yours; the
|
|
63
|
+
application **name** must be `actor` (the wire-visible inbound transaction names are
|
|
64
|
+
`::actor::*` — [03 · Wire the host](./03-wire-the-host.md) covers why):
|
|
65
|
+
|
|
66
|
+
```mufl
|
|
67
|
+
// Minimal first packet: proves the config merge + compile work.
|
|
68
|
+
application actor loads libraries
|
|
69
|
+
protocol_container,
|
|
70
|
+
version
|
|
71
|
+
uses transactions
|
|
72
|
+
{
|
|
73
|
+
trn readonly get_version _ { return ($core -> (version::get_core_version NIL)). }
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
4. Compile from inside `mufl_code/`:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
cd mufl_code
|
|
81
|
+
MUFL_STDLIB_PATH="$ADAPT_TOOLKIT/mufl_stdlib" \
|
|
82
|
+
"$ADAPT_TOOLKIT/build.linux.release/mufl-compile" \
|
|
83
|
+
-mp "$ADAPT_TOOLKIT/meta" -mp "$ADAPT_TOOLKIT/transactions" my_agent.mu
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`Unused symbol` warnings from stdlib libraries are expected noise.
|
|
87
|
+
|
|
88
|
+
**Verify:**
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
ls *.muflo
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Success markers:
|
|
95
|
+
|
|
96
|
+
- The compiler's last line is `SAVED TO FILE: <…muflo>`.
|
|
97
|
+
- `ls *.muflo` shows exactly one content-hash-named unit, e.g.
|
|
98
|
+
`2272124BF5B3D5E124487F68AFCC075581688F01A055EBC54F8C6EC6CC3047A5.muflo`.
|
|
99
|
+
|
|
100
|
+
Next: [03 · Wire the host](./03-wire-the-host.md).
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# 03 · Wire the host
|
|
2
|
+
|
|
3
|
+
The core deliberately owns **no storage**: your application decides where messages,
|
|
4
|
+
files, and state live, and hands the core a set of hooks at init time. This page
|
|
5
|
+
replaces the minimal actor from page 02 with the real host wiring. The core's own
|
|
6
|
+
[`tests/test_actor.mu`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/tests/test_actor.mu)
|
|
7
|
+
is the living reference implementation of this minimal wiring — the actor below is a
|
|
8
|
+
trimmed version of the same pattern.
|
|
9
|
+
|
|
10
|
+
Four things every host actor must get right:
|
|
11
|
+
|
|
12
|
+
1. **The application is named `actor`.** Two wire-visible inbound transaction names are
|
|
13
|
+
fixed as `::actor::accept_contact` and `::actor::receive_message` (compatibility with
|
|
14
|
+
pre-migration clients — see the header of
|
|
15
|
+
[`a2a_messaging.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_messaging.mm)).
|
|
16
|
+
A peer's `send_message` delivers to `::actor::receive_message`, so your application
|
|
17
|
+
must carry that name and ship a one-line shim that delegates to
|
|
18
|
+
`a2a_messaging::handle_receive_message`. All newer inbound legs (invite redeem,
|
|
19
|
+
files, restore) are library-routed and need no shim.
|
|
20
|
+
2. **Init wiring.** `key_storage`, `encrypted_channel`, and `a2a_messaging` each take an
|
|
21
|
+
`init` call in your actor's `hidden` block: the first two need the `_read_or_abort`
|
|
22
|
+
deserialization primitive; `a2a_messaging::init` additionally takes your storage
|
|
23
|
+
hooks (`on_message_received`, `on_message_sent`, `on_contact_removed`,
|
|
24
|
+
`on_file_received`, `on_file_sent`). Hooks return host-protocol *actions* — the
|
|
25
|
+
`return_data` records your Node driver resolves on (page 04).
|
|
26
|
+
3. **Export/import composition.** The core's portable state is
|
|
27
|
+
`a2a_messaging::export_core_state` / `import_core_state`; your `export_state` wraps
|
|
28
|
+
it under a `$core` key **alongside** your own app state, so a migration moves both.
|
|
29
|
+
Ephemeral invite secrets are deliberately excluded from the export.
|
|
30
|
+
4. **Packet-boot requirement.** The SDK runs `::protocol_container::init_my_ipd` on
|
|
31
|
+
every packet during broker registration — that is why page 02 shipped the
|
|
32
|
+
`protocol_container` stub and why the actor loads it. Note: REAL multi-process broker
|
|
33
|
+
routing additionally requires a `registration_proof` (a broker nonce-challenge); the
|
|
34
|
+
local loopback used in pages 04–05 delivers packets inside one wrapper process, so it
|
|
35
|
+
never exercises that step. When you move to a deployed broker, use a consumer repo's
|
|
36
|
+
registration wiring as your reference.
|
|
37
|
+
|
|
38
|
+
**Prereqs:**
|
|
39
|
+
|
|
40
|
+
- [02 · Configure & compile](./02-configure-and-compile.md) completed (first `.muflo` built).
|
|
41
|
+
- `$ADAPT_TOOLKIT` exported.
|
|
42
|
+
|
|
43
|
+
**Steps:**
|
|
44
|
+
|
|
45
|
+
1. Replace `mufl_code/my_agent.mu` with the wired actor:
|
|
46
|
+
|
|
47
|
+
```mufl
|
|
48
|
+
// my_agent — a minimal host actor consuming the ours-mufl-core protocol.
|
|
49
|
+
// Modeled on the core's own tests/test_actor.mu (the living reference for
|
|
50
|
+
// minimal host wiring): storage hooks, init wiring, export/import composition.
|
|
51
|
+
application actor loads libraries
|
|
52
|
+
identity_proof_document,
|
|
53
|
+
attestation_document,
|
|
54
|
+
native_attestation_document,
|
|
55
|
+
transaction_message_decoder,
|
|
56
|
+
address_document,
|
|
57
|
+
address_document_types,
|
|
58
|
+
key_utils,
|
|
59
|
+
key_storage,
|
|
60
|
+
continuation,
|
|
61
|
+
encrypted_channel,
|
|
62
|
+
a2a_protocol,
|
|
63
|
+
a2a_messaging,
|
|
64
|
+
current_transaction_info,
|
|
65
|
+
protocol_container,
|
|
66
|
+
version
|
|
67
|
+
uses transactions
|
|
68
|
+
{
|
|
69
|
+
hidden
|
|
70
|
+
{
|
|
71
|
+
// The app owns message storage; the core calls the hook.
|
|
72
|
+
metadef msg_t: ($sender -> global_id, $text -> str, $wire_id -> str).
|
|
73
|
+
inbox is msg_t[] = [].
|
|
74
|
+
|
|
75
|
+
// Wire the deserialization primitive into the libraries that need it.
|
|
76
|
+
_read_or_abort = grab( _read_or_abort ).
|
|
77
|
+
key_storage::init ($_read_or_abort -> _read_or_abort).
|
|
78
|
+
encrypted_channel::init ($_read_or_abort -> _read_or_abort).
|
|
79
|
+
|
|
80
|
+
// Host-protocol action helpers (the driver resolves on kind "data").
|
|
81
|
+
fn _save_state (_) = (transaction::action::return_data ($kind -> $save_state)).
|
|
82
|
+
fn _return_data (payload: any) = (transaction::action::return_data ($kind -> $data, $payload -> payload)).
|
|
83
|
+
fn _notify_agent (payload: any) = (transaction::action::return_data ($kind -> $notify_agent, $payload -> payload)).
|
|
84
|
+
|
|
85
|
+
// Storage hooks: deposit inbound messages; the rest are no-ops here.
|
|
86
|
+
a2a_messaging::init (
|
|
87
|
+
$_read_or_abort -> _read_or_abort,
|
|
88
|
+
$on_message_received -> fn (arg: any) -> transaction::action::type[]
|
|
89
|
+
{
|
|
90
|
+
sid = (arg $sender_id) safe global_id.
|
|
91
|
+
txt = (arg $text) safe str.
|
|
92
|
+
wid is str = "".
|
|
93
|
+
if (arg $wire_id) != NIL { wid -> (arg $wire_id) safe str. }
|
|
94
|
+
inbox (_count inbox|) -> ($sender -> sid, $text -> txt, $wire_id -> wid).
|
|
95
|
+
return [ _notify_agent ($event -> $message_received), _save_state NIL ].
|
|
96
|
+
},
|
|
97
|
+
$on_message_sent -> fn (_: any) -> transaction::action::type[] { return []. },
|
|
98
|
+
$on_contact_removed -> fn (_: any) -> transaction::action::type[] { return []. },
|
|
99
|
+
$on_file_received -> fn (_: any) -> transaction::action::type[] { return []. },
|
|
100
|
+
$on_file_sent -> fn (_: any) -> transaction::action::type[] { return []. }
|
|
101
|
+
).
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// The core's send_message delivers to the legacy ::actor::receive_message name;
|
|
105
|
+
// this shim routes it into the core receive handler (→ on_message_received hook).
|
|
106
|
+
trn receive_message args: any { return a2a_messaging::handle_receive_message args. }
|
|
107
|
+
trn readonly list_incoming_messages _ { return ($inbox -> inbox). }
|
|
108
|
+
|
|
109
|
+
// Version probe: the compiled-in core version, observable at runtime.
|
|
110
|
+
trn readonly get_version _ { return ($core -> (version::get_core_version NIL)). }
|
|
111
|
+
|
|
112
|
+
// Migration: compose YOUR app state around the core's portable export.
|
|
113
|
+
trn readonly export_state _
|
|
114
|
+
{
|
|
115
|
+
return ($core -> (a2a_messaging::export_core_state NIL), $app -> ($inbox -> inbox)).
|
|
116
|
+
}
|
|
117
|
+
trn import_state data: any
|
|
118
|
+
{
|
|
119
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
120
|
+
a2a_messaging::import_core_state (data $core).
|
|
121
|
+
inbox -> ((data $app $inbox) safe (msg_t[])).
|
|
122
|
+
return transaction::success [ _return_data ($imported -> TRUE), _save_state NIL ].
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Syntax gotcha: typed-list casts need parentheses — `safe (msg_t[])`, not
|
|
128
|
+
`safe msg_t[]`.
|
|
129
|
+
|
|
130
|
+
2. Recompile (remove the previous unit first; the output name is a content hash, so a
|
|
131
|
+
changed source produces a *second* `.muflo` otherwise):
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
cd mufl_code
|
|
135
|
+
rm -f *.muflo
|
|
136
|
+
MUFL_STDLIB_PATH="$ADAPT_TOOLKIT/mufl_stdlib" \
|
|
137
|
+
"$ADAPT_TOOLKIT/build.linux.release/mufl-compile" \
|
|
138
|
+
-mp "$ADAPT_TOOLKIT/meta" -mp "$ADAPT_TOOLKIT/transactions" my_agent.mu
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Verify:**
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
ls *.muflo
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Success markers:
|
|
148
|
+
|
|
149
|
+
- Compiler ends with `SAVED TO FILE: <…muflo>`.
|
|
150
|
+
- `ls *.muflo` shows exactly one unit (a new hash — the old one is gone).
|
|
151
|
+
|
|
152
|
+
For what the hooks feed into — deferred sends, degraded contacts, restore — see
|
|
153
|
+
[Messaging](../how-it-works/messaging.md). Next:
|
|
154
|
+
[04 · Connect & message](./04-connect-and-message.md).
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# 04 · Connect & message
|
|
2
|
+
|
|
3
|
+
Time to run the packet. The `@adapt-toolkit` Node SDK boots compiled units, a local
|
|
4
|
+
dev broker relays between them, and a small driver script walks the protocol:
|
|
5
|
+
`generate_invite` on one packet, `add_contact` on the other, then `send_message` both
|
|
6
|
+
ways — asserting **receiver-side** state (the peer's inbox and contact book), not just
|
|
7
|
+
that a send returned. This is the same loopback pattern the core's own
|
|
8
|
+
[`tests/test.mjs`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/tests/test.mjs)
|
|
9
|
+
uses. For what the three legs of the invite redeem do on the wire, see
|
|
10
|
+
[Invites & contacts](../how-it-works/invites-and-contacts.md).
|
|
11
|
+
|
|
12
|
+
**Prereqs:**
|
|
13
|
+
|
|
14
|
+
- [03 · Wire the host](./03-wire-the-host.md) completed (wired `.muflo` in `mufl_code/`).
|
|
15
|
+
- `$OURS_SDK_NODE_MODULES` and `$DEV_BROKER` exported (see [Start here](./index.md)).
|
|
16
|
+
- Node 18+.
|
|
17
|
+
|
|
18
|
+
**Steps:**
|
|
19
|
+
|
|
20
|
+
1. Make the SDK resolvable from `mufl_code/` (both the driver and the broker launcher
|
|
21
|
+
need a `node_modules` that contains `@adapt-toolkit`):
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
cd mufl_code
|
|
25
|
+
ln -sfn "$OURS_SDK_NODE_MODULES" node_modules
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
2. Create `mufl_code/drive.mjs`:
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
#!/usr/bin/env node
|
|
32
|
+
// Loopback driver: two packets of YOUR app on one local broker.
|
|
33
|
+
// invite -> contact -> message, asserted RECEIVER-side.
|
|
34
|
+
import { resolve } from 'node:path';
|
|
35
|
+
import * as fs from 'node:fs';
|
|
36
|
+
import { adapt_wrapper } from '@adapt-toolkit/sdk/executables';
|
|
37
|
+
import { PacketWrapperConfigurator } from '@adapt-toolkit/sdk/wrappers';
|
|
38
|
+
import { object_to_adapt_value } from '@adapt-toolkit/sdk/wrapper';
|
|
39
|
+
|
|
40
|
+
const BROKER_URL = process.env.BROKER_URL || 'ws://127.0.0.1:9799';
|
|
41
|
+
const UNIT_DIR = resolve('.');
|
|
42
|
+
const unitHash = fs.readdirSync(UNIT_DIR).find((f) => f.endsWith('.muflo')).slice(0, -'.muflo'.length);
|
|
43
|
+
const UNIT = new Uint8Array(fs.readFileSync(resolve(UNIT_DIR, `${unitHash}.muflo`)));
|
|
44
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
45
|
+
let failures = 0;
|
|
46
|
+
const ok = (c, m) => { console.log(`${c ? 'ok' : 'FAIL'} - ${m}`); if (!c) failures++; };
|
|
47
|
+
|
|
48
|
+
let wrapper;
|
|
49
|
+
function mk(name) { return { name, pw: null, cid: '', pending: [] }; }
|
|
50
|
+
function wire(id) {
|
|
51
|
+
id.pw.on_return_data = (d) => {
|
|
52
|
+
const kind = d.Reduce('kind').Visualize();
|
|
53
|
+
if (kind !== 'data') return; // ignore save_state / notify_agent here
|
|
54
|
+
const p = id.pending.shift(); if (!p) return;
|
|
55
|
+
clearTimeout(p.timer); p.resolve(d.Reduce('payload'));
|
|
56
|
+
};
|
|
57
|
+
id.pw.on_transaction_failure = (msg) => {
|
|
58
|
+
const p = id.pending.shift();
|
|
59
|
+
if (p) { clearTimeout(p.timer); p.reject(new Error(msg)); }
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function mutate(id, name, targ) {
|
|
63
|
+
return new Promise((res, rej) => {
|
|
64
|
+
const timer = setTimeout(() => rej(new Error(`${id.name}.${name} timed out`)), 20000);
|
|
65
|
+
id.pending.push({ resolve: res, reject: rej, timer });
|
|
66
|
+
id.pw.add_client_message(object_to_adapt_value({ name, targ }));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const ro = (id, name) => id.pw.packet.ExecuteTransaction(object_to_adapt_value({ name, targ: undefined }));
|
|
70
|
+
const binv = (id, buf) => id.pw.packet.NewBinaryFromBuffer(Buffer.from(buf));
|
|
71
|
+
async function mkPacket(id, seed) {
|
|
72
|
+
const cfg = new PacketWrapperConfigurator();
|
|
73
|
+
cfg.process_arguments(['--unit_hash', unitHash, '--seed_phrase', seed, '--unit_dir_path', UNIT_DIR]);
|
|
74
|
+
await new Promise((res, rej) => {
|
|
75
|
+
const t = setTimeout(() => rej(new Error(`${id.name} create timeout`)), 30000);
|
|
76
|
+
wrapper.packet_manager.create_packet(cfg, (pw) => {
|
|
77
|
+
clearTimeout(t); id.pw = pw; id.cid = pw.packet.GetContainerID().Visualize(); wire(id); res();
|
|
78
|
+
}, UNIT);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function main() {
|
|
83
|
+
wrapper = await adapt_wrapper.start(['--broker_address', BROKER_URL, '--test_mode',
|
|
84
|
+
'--logger_config', '--level', 'WARNING', '--stdout', 'stderr', '--logger_config_end']);
|
|
85
|
+
wrapper.start();
|
|
86
|
+
await sleep(1500);
|
|
87
|
+
|
|
88
|
+
const A = mk('A'); const B = mk('B');
|
|
89
|
+
await mkPacket(A, 'my-app-A-01'); await mkPacket(B, 'my-app-B-02');
|
|
90
|
+
await sleep(1200);
|
|
91
|
+
await mutate(A, '::a2a_messaging::set_my_name', { name: 'Alice' });
|
|
92
|
+
await mutate(B, '::a2a_messaging::set_my_name', { name: 'Bob' });
|
|
93
|
+
|
|
94
|
+
// compiled-in core version is observable at runtime
|
|
95
|
+
console.log('core version:', ro(A, '::actor::get_version').Reduce('core').Visualize());
|
|
96
|
+
|
|
97
|
+
// 1. A mints an invite
|
|
98
|
+
const m = await mutate(A, '::a2a_messaging::generate_invite', { name: 'Bob' });
|
|
99
|
+
const invite = Buffer.from(m.Reduce('invite').GetBinary());
|
|
100
|
+
ok(invite.length > 0, 'generate_invite returned an invite blob');
|
|
101
|
+
|
|
102
|
+
// 2. B redeems it
|
|
103
|
+
await mutate(B, '::a2a_messaging::add_contact', { invite: binv(B, invite), name: 'Alice' });
|
|
104
|
+
await sleep(5000);
|
|
105
|
+
|
|
106
|
+
// 3. RECEIVER-side: both contact books list the other
|
|
107
|
+
const lcA = ro(A, '::a2a_messaging::list_contacts').Visualize();
|
|
108
|
+
const lcB = ro(B, '::a2a_messaging::list_contacts').Visualize();
|
|
109
|
+
ok(new RegExp(B.cid).test(lcA), 'A list_contacts includes B cid');
|
|
110
|
+
ok(new RegExp(A.cid).test(lcB), 'B list_contacts includes A cid');
|
|
111
|
+
|
|
112
|
+
// 4. message round-trips over the encrypted channel, both directions
|
|
113
|
+
await mutate(A, '::a2a_messaging::send_message', { contact: B.cid, text: 'hello from Alice' });
|
|
114
|
+
await sleep(2500);
|
|
115
|
+
ok(/hello from Alice/.test(ro(B, '::actor::list_incoming_messages').Visualize()),
|
|
116
|
+
'B received A message (receiver-side inbox)');
|
|
117
|
+
await mutate(B, '::a2a_messaging::send_message', { contact: A.cid, text: 'hello from Bob' });
|
|
118
|
+
await sleep(2500);
|
|
119
|
+
ok(/hello from Bob/.test(ro(A, '::actor::list_incoming_messages').Visualize()),
|
|
120
|
+
'A received B message (receiver-side inbox)');
|
|
121
|
+
|
|
122
|
+
console.log(failures === 0 ? 'ROUND-TRIP OK' : `${failures} FAILURE(S)`);
|
|
123
|
+
await sleep(500);
|
|
124
|
+
process.exit(failures === 0 ? 0 : 1);
|
|
125
|
+
}
|
|
126
|
+
main().catch((e) => { console.error('DRIVER ERR:', e.stack ?? e.message); process.exit(1); });
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Reading the driver: `mutate` submits a state-changing transaction through the
|
|
130
|
+
wrapper's client-message path and resolves on the `$kind -> $data` action your hooks
|
|
131
|
+
returned; `ro` executes a read-only transaction synchronously. The SDK's leak-tracker
|
|
132
|
+
prints `###` lines at exit — expected noise.
|
|
133
|
+
|
|
134
|
+
3. Start the dev broker, run the driver, stop the broker:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
node "$DEV_BROKER" --host 127.0.0.1 --port 9799 --test_mode > broker.log 2>&1 &
|
|
138
|
+
sleep 3
|
|
139
|
+
BROKER_URL=ws://127.0.0.1:9799 node drive.mjs 2>/dev/null | grep -vE '^###'
|
|
140
|
+
echo "EXIT=${PIPESTATUS[0]}"
|
|
141
|
+
kill %1
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Verify:** the driver prints a `core version:` line (the `MAJ`/`MIN`/`PATCH` of the
|
|
145
|
+
core revision you vendored — see [Versioning](../how-it-works/versioning.md)) and five
|
|
146
|
+
`ok -` assertion lines, ending with the success markers:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
ok - A received B message (receiver-side inbox)
|
|
150
|
+
ROUND-TRIP OK
|
|
151
|
+
EXIT=0
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Any `FAIL -` line or a non-zero `EXIT` means the round-trip did not complete.
|
|
155
|
+
|
|
156
|
+
Next: [05 · Test your app](./05-test-your-app.md).
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# 05 · Test your app
|
|
2
|
+
|
|
3
|
+
Two layers of testing keep a consumer honest: a **loopback suite for your own app**,
|
|
4
|
+
and the **core's own suite** run against the exact core revision you vendored.
|
|
5
|
+
|
|
6
|
+
## The loopback pattern for your app
|
|
7
|
+
|
|
8
|
+
Grow `drive.mjs` from page 04 into a real suite the way the core's tests do
|
|
9
|
+
(see [`tests/README.md`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/tests/README.md)):
|
|
10
|
+
|
|
11
|
+
- **A derived test actor.** Don't test against your production actor alone — derive a
|
|
12
|
+
test build that adds `qa_*` probe transactions: read-only state counters
|
|
13
|
+
(`qa_state`-style counts of contacts, peer ADs, pending invites), exporters
|
|
14
|
+
(`qa_export_ad`), and adversarial injectors that bare-send crafted wire payloads. The
|
|
15
|
+
core's [`tests/test_actor.mu`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/tests/test_actor.mu)
|
|
16
|
+
shows the full probe vocabulary.
|
|
17
|
+
- **Assert receiver-side.** A send that "succeeded" proves little; assert the *peer's*
|
|
18
|
+
state changed (its inbox, its contact book) and that rejected inputs changed
|
|
19
|
+
*nothing* — the core suite's negative scenarios (replayed invites, tampered boxes,
|
|
20
|
+
foreign address documents) all assert state-unchanged on the target.
|
|
21
|
+
- **One broker, many packets.** Spin all packets on one `dev-broker.mjs` in
|
|
22
|
+
`--test_mode`; inbound aborts surface via `on_transaction_failure`, so collect them
|
|
23
|
+
per-packet and grep them in assertions.
|
|
24
|
+
|
|
25
|
+
## Run the core's own suite (sanity check)
|
|
26
|
+
|
|
27
|
+
The vendored submodule ships its suite: 10 scenarios, 36+ assertions over invite
|
|
28
|
+
redeem, tamper rejection, export secrecy, and migration
|
|
29
|
+
(see [Invites & contacts](../how-it-works/invites-and-contacts.md)). Running it against
|
|
30
|
+
your checkout proves your toolkit + SDK + broker environment is sound and the core
|
|
31
|
+
revision you pinned behaves as released.
|
|
32
|
+
|
|
33
|
+
**Prereqs:**
|
|
34
|
+
|
|
35
|
+
- Pages [01](./01-vendor-the-core.md)–[04](./04-connect-and-message.md) completed.
|
|
36
|
+
- `$ADAPT_TOOLKIT`, `$OURS_SDK_NODE_MODULES`, `$DEV_BROKER` exported.
|
|
37
|
+
- No leftover dev broker on the chosen port (the suite boots its own; `PORT=9791`
|
|
38
|
+
below avoids clashing with page 04's broker if you left it running).
|
|
39
|
+
|
|
40
|
+
**Steps:**
|
|
41
|
+
|
|
42
|
+
1. From your app repo **root** (`my-app`, not `mufl_code/`), run the suite inside the
|
|
43
|
+
submodule, pointing it at your
|
|
44
|
+
environment (`tests/run.sh` honors these as env overrides; it bundles the
|
|
45
|
+
`protocol_container.mm` stub itself, so you no longer pass one in):
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
cd mufl_code/core
|
|
49
|
+
ADAPT_TOOLKIT="$ADAPT_TOOLKIT" \
|
|
50
|
+
OURS_SDK_NODE_MODULES="$OURS_SDK_NODE_MODULES" \
|
|
51
|
+
DEV_BROKER="$DEV_BROKER" \
|
|
52
|
+
PORT=9791 ./tests/run.sh
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The run takes a few minutes: it copies the core into a throwaway harness, compiles
|
|
56
|
+
the test actor, boots a broker on `PORT`, and drives all scenarios. `###` /
|
|
57
|
+
`Leak for AdaptValue` lines and `EVAL_ERROR` stderr noise are expected — the
|
|
58
|
+
adversarial scenarios deliberately trigger aborts.
|
|
59
|
+
|
|
60
|
+
**Verify:**
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
echo "EXIT=$?"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Success markers (the verdict is the final scorecard plus the exit code):
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
================ SCORECARD ================
|
|
70
|
+
ALL TESTS PASSED
|
|
71
|
+
EXIT=0
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
That closes the loop: an empty directory, a vendored core, a compiled packet, a live
|
|
75
|
+
encrypted round-trip, and the protocol's own suite green against your environment.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Start here
|
|
2
|
+
|
|
3
|
+
> **These docs are written for agents.** ours.network expects most applications to be
|
|
4
|
+
> built by coding agents working on behalf of a human. Pages are structured as executable
|
|
5
|
+
> runbooks — exact paths, copy-paste commands, verification steps — rather than narrative
|
|
6
|
+
> tutorials. Humans are welcome; give your agent the URL of
|
|
7
|
+
> [`llms-full.txt`](https://adapt-toolkit.github.io/ours-mufl-core/llms-full.txt) and it can ingest the entire
|
|
8
|
+
> documentation in one fetch.
|
|
9
|
+
|
|
10
|
+
This guide takes you from an empty directory to a working ours.network application:
|
|
11
|
+
a MUFL packet that **consumes** the shared protocol core, plus the host-side driver
|
|
12
|
+
that boots it, connects it to a peer, and exchanges encrypted messages. Every
|
|
13
|
+
ours.network client is built this way — it vendors this repo as a git submodule and
|
|
14
|
+
compiles it into its own packet (see [Overview](../how-it-works/overview.md)).
|
|
15
|
+
|
|
16
|
+
**Scope note — integration only.** This guide never touches protocol code. You will not
|
|
17
|
+
edit any `.mm` file of the core; a change there is a protocol revision for the whole
|
|
18
|
+
network. Everything you write here — your `config.mufl`, your actor (`.mu` file), your
|
|
19
|
+
Node driver — is *your application*, layered on top of an unmodified core. If you think
|
|
20
|
+
the protocol itself needs a change, read
|
|
21
|
+
[Contributing](../reference/contributing.md) instead.
|
|
22
|
+
|
|
23
|
+
## What you will build
|
|
24
|
+
|
|
25
|
+
| Page | Result |
|
|
26
|
+
|------|--------|
|
|
27
|
+
| [01 · Vendor the core](./01-vendor-the-core.md) | The core checked out as a git submodule under `mufl_code/core` |
|
|
28
|
+
| [02 · Configure & compile](./02-configure-and-compile.md) | A `config.mufl` that merges the core with the stdlib, and a first compiled packet |
|
|
29
|
+
| [03 · Wire the host](./03-wire-the-host.md) | An actor with the storage hooks, init wiring, and export/import composition the core expects |
|
|
30
|
+
| [04 · Connect & message](./04-connect-and-message.md) | Two packets on a local broker: invite → contact → encrypted message round-trip |
|
|
31
|
+
| [05 · Test your app](./05-test-your-app.md) | A loopback test pattern for your app, plus the core's own suite as a sanity check |
|
|
32
|
+
|
|
33
|
+
**Prereqs:**
|
|
34
|
+
|
|
35
|
+
- The **ADAPT toolkit** — the `mufl-compile` binary plus the `mufl_stdlib` / `meta` /
|
|
36
|
+
`transactions` module trees. The pages refer to its root as `$ADAPT_TOOLKIT`
|
|
37
|
+
(`$ADAPT_TOOLKIT/build.linux.release/mufl-compile` must exist).
|
|
38
|
+
- The **`@adapt-toolkit` Node SDK** — a `node_modules` directory containing
|
|
39
|
+
`@adapt-toolkit` (any ours.network consumer checkout has one). The pages refer to it
|
|
40
|
+
as `$OURS_SDK_NODE_MODULES`.
|
|
41
|
+
- A **local dev broker launcher** — `dev-broker.mjs`, a thin launcher over the SDK's
|
|
42
|
+
broker exports (ships with the consumer repos). The pages refer to it as `$DEV_BROKER`.
|
|
43
|
+
It must run from a directory whose `node_modules` resolves `@adapt-toolkit`; the
|
|
44
|
+
layout built in this guide takes care of that.
|
|
45
|
+
- **git** and **Node 18+**.
|
|
46
|
+
|
|
47
|
+
These are the same three knobs the core's own test suite takes
|
|
48
|
+
(`ADAPT_TOOLKIT` / `OURS_SDK_NODE_MODULES` / `DEV_BROKER` — see
|
|
49
|
+
[05 · Test your app](./05-test-your-app.md)). Export them once per shell:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
export ADAPT_TOOLKIT=/path/to/adapt-toolkit
|
|
53
|
+
export OURS_SDK_NODE_MODULES=/path/to/consumer/node_modules
|
|
54
|
+
export DEV_BROKER=/path/to/dev-broker.mjs
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Steps:** start at [01 · Vendor the core](./01-vendor-the-core.md); each page builds
|
|
58
|
+
on the previous one and ends with a Verify block whose success markers come from a
|
|
59
|
+
live run of exactly the commands shown.
|
|
60
|
+
|
|
61
|
+
**Verify:** after page 04 you have a bidirectional encrypted round-trip
|
|
62
|
+
(`ROUND-TRIP OK`, exit 0); after page 05 the core's own suite reports
|
|
63
|
+
`SCORECARD` / `ALL TESTS PASSED` with exit 0.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Capabilities & control
|
|
2
|
+
|
|
3
|
+
Every ours node advertises a typed app manifest — a set of named capabilities,
|
|
4
|
+
each with its own schema and version — and receives structured requests through a
|
|
5
|
+
shared control envelope. `a2a_capabilities.mm` owns the envelope definition and
|
|
6
|
+
dispatch; `a2a_control.mm` owns the encrypted transport that carries envelopes
|
|
7
|
+
between nodes.
|
|
8
|
+
|
|
9
|
+
Sources:
|
|
10
|
+
[`a2a_capabilities.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_capabilities.mm),
|
|
11
|
+
[`a2a_control.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_control.mm).
|
|
12
|
+
|
|
13
|
+
## The control envelope
|
|
14
|
+
|
|
15
|
+
All inbound capability requests arrive as a `control_envelope_t` record:
|
|
16
|
+
|
|
17
|
+
```mufl
|
|
18
|
+
metadef control_envelope_t: ($cap -> str, $verb -> str, $args -> any, $req_id -> str).
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- `$cap` — the stable capability id (e.g. `"core.configuration"`). Dispatch keys
|
|
22
|
+
only on this field; adding a capability never changes the wire shape.
|
|
23
|
+
- `$verb` — the operation within that capability.
|
|
24
|
+
- `$args` — native-typed arguments the handler interprets per verb.
|
|
25
|
+
- `$req_id` — correlation token. The sender receives a `response_envelope_t`
|
|
26
|
+
keyed on `(sender_id, $req_id)`. An empty `$req_id` signals fire-and-forget:
|
|
27
|
+
the envelope is processed but no response is expected.
|
|
28
|
+
|
|
29
|
+
## Transport
|
|
30
|
+
|
|
31
|
+
`send_control` in `a2a_control.mm` delivers an opaque payload to a named contact
|
|
32
|
+
over the `encrypted_channel`. The inbound side is the `control_message`
|
|
33
|
+
transaction, which validates origin and sender before invoking the
|
|
34
|
+
`$on_control_received` hook the app wires at startup.
|
|
35
|
+
|
|
36
|
+
The `a2a_capabilities` dispatch layer sits one step above this transport: the
|
|
37
|
+
app adapts the opaque `control_message` payload into a `control_envelope_t`
|
|
38
|
+
record and calls `dispatch`.
|
|
39
|
+
|
|
40
|
+
## Dispatch and the authz chokepoint
|
|
41
|
+
|
|
42
|
+
`dispatch` enforces a single, non-bypassable pre-route authorization check
|
|
43
|
+
before any handler runs:
|
|
44
|
+
|
|
45
|
+
1. `control_auth_class($cap, $verb)` classifies the requested operation as
|
|
46
|
+
`"bootstrap"`, `"controller"`, or `"deny"`. (`get_manifest` is a standalone
|
|
47
|
+
`trn readonly` that never routes through dispatch and is not classified here.)
|
|
48
|
+
2. `"controller"` verbs require the stateful `authorizer` gate (wired at init via
|
|
49
|
+
`a2a_messaging::authorize_control`) to confirm the sender is the bound control
|
|
50
|
+
plane. The gate is fail-closed: an unset authorizer aborts rather than
|
|
51
|
+
permitting the verb.
|
|
52
|
+
3. Unknown or unlisted cap/verb combinations always classify as `"deny"` — a new
|
|
53
|
+
verb must be consciously listed in `control_auth_class` to become reachable.
|
|
54
|
+
|
|
55
|
+
Handlers run only after this chokepoint clears. They return
|
|
56
|
+
`transaction::action::type[]` arrays; the daemon marshals responses to JSON and
|
|
57
|
+
ships them — no in-MUFL JSON encoding, no ad-hoc send inside a handler.
|
|
58
|
+
|
|
59
|
+
## Well-known capability ids
|
|
60
|
+
|
|
61
|
+
Four ids are reserved in `a2a_capabilities.mm`:
|
|
62
|
+
|
|
63
|
+
| Constant | Value | Purpose |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `cap_configuration` | `"core.configuration"` | Opaque app config, read/written only by the bound control plane. |
|
|
66
|
+
| `cap_monitoring` | `"core.monitoring"` | Monitoring bind ceremony and disable. |
|
|
67
|
+
| `cap_connect` | `"core.connect"` | Peer introduction via the control plane. |
|
|
68
|
+
| `cap_cluster` | `"core.cluster"` | Child/subagent lifecycle and contact management. |
|
|
69
|
+
|
|
70
|
+
The `"core.*"` namespace is reserved; application capabilities use `"app.*"`.
|
|
71
|
+
Every node advertises `core.monitoring` — it is governance-required and
|
|
72
|
+
auto-present. Adding a new capability means registering a new id string and
|
|
73
|
+
wiring its handler; the wire shape is unchanged.
|
|
74
|
+
|
|
75
|
+
See [Cluster](./cluster.md) for the `core.cluster` verb surface, and
|
|
76
|
+
[Monitoring & config](./monitoring-and-config.md) for how `core.monitoring` and
|
|
77
|
+
`core.configuration` interact with the hidden gate state in `a2a_messaging`.
|