@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,65 @@
|
|
|
1
|
+
# Cluster
|
|
2
|
+
|
|
3
|
+
The `core.cluster` capability, defined in `a2a_cluster.mm`, gives a root node a
|
|
4
|
+
uniform surface for child/subagent lifecycle, per-child monitoring authorization,
|
|
5
|
+
the host-local contact book, and introductions between children and contacts.
|
|
6
|
+
The authz chokepoint in `a2a_capabilities::dispatch` gates every verb — handlers
|
|
7
|
+
run only after it clears.
|
|
8
|
+
|
|
9
|
+
Source: [`a2a_cluster.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_cluster.mm).
|
|
10
|
+
|
|
11
|
+
## Registry
|
|
12
|
+
|
|
13
|
+
The root packet holds a `cluster_members` map (keyed by container id) of
|
|
14
|
+
`member_t` rows. Each row tracks the child's name, role id, bio, persona,
|
|
15
|
+
current monitoring state, and advertised capability ids. The registry is a
|
|
16
|
+
projection of host truth: `reconcile` runs on boot and on a periodic schedule to
|
|
17
|
+
add children the host knows about but the registry doesn't, and to drop members
|
|
18
|
+
the host has removed.
|
|
19
|
+
|
|
20
|
+
```mufl
|
|
21
|
+
metadef member_t: ($cid -> global_id, $role_id -> str, $name -> str,
|
|
22
|
+
$bio -> str, $persona -> str, $monitoring -> str, $caps -> str[]).
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Lifecycle verbs
|
|
26
|
+
|
|
27
|
+
The `cluster_handler` function switches on the `$verb` field of each inbound
|
|
28
|
+
`control_envelope_t` and dispatches to the appropriate handler. The authorized
|
|
29
|
+
bare verb names (as they appear on the wire and in `control_auth_class`) are:
|
|
30
|
+
|
|
31
|
+
| Verb | Effect |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `list` | Returns the current `cluster_members` registry. |
|
|
34
|
+
| `set_bio` | Updates the bio field for a child in the registry (registry-only, no host op). |
|
|
35
|
+
| `set_persona` | Updates the persona field for a child in the registry (registry-only, no host op). |
|
|
36
|
+
| `set_monitoring` | Emits a `host_set_child_monitoring` notify-action; confirmed by `confirm_child_monitoring`. |
|
|
37
|
+
| `create` | Emits a `host_provision_child` notify-action; the daemon provisions the child packet and calls back `register_provisioned_child`. |
|
|
38
|
+
| `remove` | Emits a `host_destroy_child` notify-action; confirmed by `confirm_child_destroyed`. |
|
|
39
|
+
| `contact` | Emits a `host_mint_child_invite` notify-action; the daemon runs `generate_invite` inside the child packet and calls back `register_child_invite`. |
|
|
40
|
+
| `introduce` | Composes `a2a_messaging::emit_pair` to introduce two established contacts to each other. |
|
|
41
|
+
|
|
42
|
+
For the full verb surface see [`a2a_cluster.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_cluster.mm).
|
|
43
|
+
|
|
44
|
+
Create and remove are asynchronous: the handler stores a pending-req (keyed by a
|
|
45
|
+
monotonic handle) and immediately acknowledges `{pending: true}`. The matching
|
|
46
|
+
host callback consumes the pending-req atomically and routes the final response to
|
|
47
|
+
the stored controller.
|
|
48
|
+
|
|
49
|
+
## Per-child monitoring authorization
|
|
50
|
+
|
|
51
|
+
`set_monitoring` derives the control-plane identity from the root's own
|
|
52
|
+
ceremony-pinned `monitoring_proxy` — never from the request args. The root must
|
|
53
|
+
be bound to a cluster control plane before enabling monitoring for any child.
|
|
54
|
+
Disabling does not require a bound CP; it clears the child's proxy immediately.
|
|
55
|
+
|
|
56
|
+
## Cross-cluster introductions
|
|
57
|
+
|
|
58
|
+
The `introduce` verb accepts two contact references (`$peer_a`, `$peer_b`) and
|
|
59
|
+
emits a pair of introduction messages via `a2a_messaging::emit_pair`. Both peers
|
|
60
|
+
must already be established contacts with stored address documents. The
|
|
61
|
+
`core.connect` capability exposes the same `introduce` verb (via
|
|
62
|
+
`connect_handler`) for peer-to-peer introduction without cluster context.
|
|
63
|
+
|
|
64
|
+
See [Capabilities & control](./capabilities-and-control.md) for the envelope and
|
|
65
|
+
dispatch model that gates every verb above.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Identity: roots & roles
|
|
2
|
+
|
|
3
|
+
An identity in ours.network is either a **root** — a self-sovereign keypair — or a **delegated
|
|
4
|
+
role** anchored to a root. A root's `delegation_cert` field is `NIL`; detection is structural,
|
|
5
|
+
not a flag. A role carries a signed `delegation_cert_t` that binds the role's container id and
|
|
6
|
+
address-document hash to its root, signed by the root's keys.
|
|
7
|
+
|
|
8
|
+
Source: [`a2a_protocol.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_protocol.mm).
|
|
9
|
+
|
|
10
|
+
## Delegation verification
|
|
11
|
+
|
|
12
|
+
When a peer presents its identity during invite redemption, verify the full chain with
|
|
13
|
+
`verify_peer_delegation`:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
fn verify_peer_delegation (
|
|
17
|
+
peer_cid: global_id,
|
|
18
|
+
peer_ad_hash: hash_code,
|
|
19
|
+
cert: delegation_cert_t,
|
|
20
|
+
rp: root_profile_t
|
|
21
|
+
) -> contact_root_t
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The root profile carries the root's key list, so no prior knowledge of the root is needed. On
|
|
25
|
+
success the function returns a `contact_root_t` (root container id, root name, role id) to store
|
|
26
|
+
beside the contact. Any mismatch — wrong version, mismatched cid, bad signature — aborts.
|
|
27
|
+
|
|
28
|
+
## Control-plane governance (§3c)
|
|
29
|
+
|
|
30
|
+
Two verifiers support the optional non-enforcing governance edge between a root and its control
|
|
31
|
+
plane:
|
|
32
|
+
|
|
33
|
+
- **`verify_cp_attestation`** — verifies the CP's signed attestation that it governs a given
|
|
34
|
+
root. Requires the caller to have run `process_address_document` on the CP's address document
|
|
35
|
+
first.
|
|
36
|
+
- **`verify_root_cp_binding`** — verifies the root's self-signed edge naming the CP, domain-
|
|
37
|
+
separated by `cp_attestation_context_tag`. The authoritative monitoring gate remains the
|
|
38
|
+
6-digit bind ceremony; these verifiers let peers TOFU-pin the governance edge without an extra
|
|
39
|
+
round-trip.
|
|
40
|
+
|
|
41
|
+
Source: [`a2a_protocol.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_protocol.mm).
|
|
42
|
+
|
|
43
|
+
## Wire shapes and the structural-records rule
|
|
44
|
+
|
|
45
|
+
All identity wire shapes are MUFL record types defined in `a2a_protocol.mm`. The key protocol
|
|
46
|
+
guarantee is: moving a shape between libraries never changes its bytes; renaming or retyping a
|
|
47
|
+
`$field` does. Field names are the version boundary. Example shapes:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
metadef delegation_cert_t: ($c -> delegation_core_t, $s -> crypto_signature).
|
|
51
|
+
metadef root_profile_t: ($p -> root_profile_core_t, $s -> crypto_signature).
|
|
52
|
+
metadef root_cp_binding_t: ($c -> root_cp_binding_core_t, $s -> crypto_signature).
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The invite shape carries no identity keys — identity material moves inside the encrypted
|
|
56
|
+
two-message redeem hop (see [Invites & contacts](./invites-and-contacts.md)).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Invites & contacts
|
|
2
|
+
|
|
3
|
+
Contact establishment uses a **slim ephemeral-key invite**. The invite blob carries
|
|
4
|
+
only an ephemeral encryption pubkey, the inviter's container id, a display name, and a crypto
|
|
5
|
+
scheme id — no long-term keys, no self-signatures. Identity material moves inside two encrypted
|
|
6
|
+
messages exchanged after the initial out-of-band transfer.
|
|
7
|
+
|
|
8
|
+
Source: [`a2a_messaging.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_messaging.mm).
|
|
9
|
+
Sequence diagrams: [Contact exchange](../workflows/contact-exchange.md) and
|
|
10
|
+
[Introductions](../workflows/introductions.md).
|
|
11
|
+
|
|
12
|
+
## Invite shape
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
metadef invite_eph_t: (
|
|
16
|
+
$d -> global_id, -- invite id (correlates legs; single-use)
|
|
17
|
+
$c -> global_id, -- inviter container id
|
|
18
|
+
$n -> str, -- inviter display name
|
|
19
|
+
$k -> publickey_encrypt, -- inviter ephemeral pubkey
|
|
20
|
+
$v -> int -- crypto scheme id
|
|
21
|
+
).
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Generate an invite with `generate_invite`. The returned blob is the value to share out-of-band
|
|
25
|
+
(QR code, deep link, etc.).
|
|
26
|
+
|
|
27
|
+
## Three-leg redeem flow
|
|
28
|
+
|
|
29
|
+
| Leg | Direction | Mechanism | What moves |
|
|
30
|
+
|-----|-----------|-----------|------------|
|
|
31
|
+
| OOB | inviter → responder | out-of-band | slim `invite_eph_t` blob |
|
|
32
|
+
| Leg 1 | responder → inviter | BARE send (box to invite's eph pub) | responder identity bundle |
|
|
33
|
+
| Leg 2 | inviter (receives leg 1) | verify bundle, consume invite, emit leg 3 | ← inviter now holds responder's verified AD |
|
|
34
|
+
| Leg 3 | inviter → responder | BARE send (box to responder's eph pub) | inviter identity bundle |
|
|
35
|
+
|
|
36
|
+
Both boxes carry the same identity bundle shape: address document + optional delegation cert +
|
|
37
|
+
optional root profile + optional §3c CP binding. After leg 3, both sides hold the other's
|
|
38
|
+
verified address document and `encrypted_channel` resumes for all subsequent traffic.
|
|
39
|
+
|
|
40
|
+
Redeem an invite with `add_contact` (leg 1). The inviter processes the responder's leg 1 via
|
|
41
|
+
`submit_invite_response` and replies with leg 3 (`complete_invite`).
|
|
42
|
+
|
|
43
|
+
## Single-use guarantee
|
|
44
|
+
|
|
45
|
+
An invite is consumed at the first valid leg-2 (inviter's receipt of leg 1). A second attempt for the same
|
|
46
|
+
`invite_id` aborts with `already-redeemed` and mutates no state. The ephemeral private key is
|
|
47
|
+
deleted atomically with the invite record (INV-4: secrets are never exported).
|
|
48
|
+
|
|
49
|
+
Test suite scenario T3 (`single-use`) asserts a second leg-1 for the same `invite_id` aborts
|
|
50
|
+
and leaves inviter state unchanged. Scenario T4 (`invalid-then-valid`) asserts that a failed
|
|
51
|
+
box-open does not consume the invite, so a subsequent valid redeem succeeds. See
|
|
52
|
+
[`tests/README.md`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/tests/README.md).
|
|
53
|
+
|
|
54
|
+
## Pending-invite transience
|
|
55
|
+
|
|
56
|
+
Responder-side pending redemption state (`pending_redemptions`) is not exported and does not
|
|
57
|
+
survive a daemon restart. An interrupted handshake does not leave half-registered state; the
|
|
58
|
+
responder must run `add_contact` again with a fresh invite. This is the fail-closed design: no
|
|
59
|
+
dangling partial contacts after a restart.
|
|
60
|
+
|
|
61
|
+
## Introductions via core.connect
|
|
62
|
+
|
|
63
|
+
When two nodes share a control plane that advertises `core.connect`, the CP can introduce them
|
|
64
|
+
without another out-of-band invite. The CP sends each node the other's signed address document
|
|
65
|
+
via `ingest_connect_descriptor`. The receiving node verifies:
|
|
66
|
+
|
|
67
|
+
1. The relay came from its bound CP (or the CP its root designated).
|
|
68
|
+
2. Its own live manifest advertises `core.connect`.
|
|
69
|
+
3. The peer's address document self-signature is valid (proof-of-possession).
|
|
70
|
+
|
|
71
|
+
The contact is registered immediately — no SAS, no confirmation step.
|
|
72
|
+
|
|
73
|
+
Scenario T1 (`happy-flat`) verifies the end-to-end invite + `send_message` round-trip in both
|
|
74
|
+
directions. Scenarios T5–T8 cover adversarial inputs (tampered box, cid-bind mismatch, stripped
|
|
75
|
+
PoP, unexpected inviter on leg 3). See
|
|
76
|
+
[`tests/README.md`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/tests/README.md).
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Messaging
|
|
2
|
+
|
|
3
|
+
All message and file traffic between contacts rides the `encrypted_channel`, established during
|
|
4
|
+
invite redemption and persisted across restarts by replaying stored peer address documents on
|
|
5
|
+
import. `a2a_messaging.mm` is the single path for all send/receive operations.
|
|
6
|
+
|
|
7
|
+
Source: [`a2a_messaging.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_messaging.mm).
|
|
8
|
+
Module description: [`README.md`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/README.md)
|
|
9
|
+
— "Contact and messaging transactions (generate invite, add/remove contact, send message, send
|
|
10
|
+
file, inbound receive) and the introduction flow."
|
|
11
|
+
|
|
12
|
+
Sequence diagrams: [Send & receive messages](../workflows/messaging.md) and
|
|
13
|
+
[Contact restore](../workflows/contact-restore.md).
|
|
14
|
+
|
|
15
|
+
## Outbound
|
|
16
|
+
|
|
17
|
+
- **`send_message`** — sends text to a named or container-id-referenced contact. Accepts an
|
|
18
|
+
optional `reply_to` pointer (`$wire_id`, `$sentence`) for threaded replies. Every message
|
|
19
|
+
receives a stable `wire_id` (a stringified `_new_id`) that the peer can reference in its own
|
|
20
|
+
replies.
|
|
21
|
+
- **`send_file`** — sends binary data with a `$filename` and optional `$mime`. Files and text
|
|
22
|
+
messages share the same `wire_id` namespace; a reply pointer can reference either.
|
|
23
|
+
|
|
24
|
+
Both fail if the contact has no registered address document (see Contact-restore below).
|
|
25
|
+
|
|
26
|
+
## Inbound
|
|
27
|
+
|
|
28
|
+
| Transaction name | Source constant | Payload |
|
|
29
|
+
|-----------------|-----------------|---------|
|
|
30
|
+
| `::actor::receive_message` | `receive_message_tx` | `$text`, `$wire_id`, optional `$reply_to`, sender id from envelope |
|
|
31
|
+
| `::a2a_messaging::receive_file` | `receive_file_tx` | `$filename`, `$mime`, `$data`, `$wire_id`, optional `$reply_to` |
|
|
32
|
+
|
|
33
|
+
The core fires the app-injected `on_message_received` / `on_file_received` storage hooks.
|
|
34
|
+
Message storage is the consumer's responsibility; the core handles wire, validation, and contact
|
|
35
|
+
resolution.
|
|
36
|
+
|
|
37
|
+
## Receiver-side state
|
|
38
|
+
|
|
39
|
+
`contacts` (keyed by container id) and `peer_ads` (keyed by container id) are the persistent
|
|
40
|
+
contact state. `encrypted_channel` resolves the peer's encryption key from `peer_ads` on every
|
|
41
|
+
send. `peer_ads` entries survive code upgrades because `import_core_state` replays each stored
|
|
42
|
+
address document through `process_address_document` — no re-handshake is needed.
|
|
43
|
+
|
|
44
|
+
## Contact-restore across breaking changes
|
|
45
|
+
|
|
46
|
+
If a migration carries `contacts` but drops `peer_ads` for a contact, that contact becomes a
|
|
47
|
+
**DEGRADED contact** (cid present, no address document). `send_message` to a degraded contact
|
|
48
|
+
queues the message and fires a `request_contact_restore` handshake to re-fetch the peer's
|
|
49
|
+
address document. Once the peer's AD is re-established, queued messages flush automatically.
|
|
50
|
+
|
|
51
|
+
`send_file` to a degraded contact fails fast with an explicit error — binary payloads are not
|
|
52
|
+
queued. `send_message` toward the same contact will queue and drive the restore.
|
|
53
|
+
|
|
54
|
+
See [Identity: roots & roles](./identity.md) for the address document and key structure that
|
|
55
|
+
`encrypted_channel` depends on.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Monitoring & config
|
|
2
|
+
|
|
3
|
+
Monitoring and configuration state is split across two libraries by where the
|
|
4
|
+
hidden gate state lives: `a2a_messaging.mm` owns the node-side gate and copy
|
|
5
|
+
generation; `a2a_monitoring.mm` owns the control-plane receiver.
|
|
6
|
+
|
|
7
|
+
Sources:
|
|
8
|
+
[`a2a_messaging.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_messaging.mm),
|
|
9
|
+
[`a2a_monitoring.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_monitoring.mm).
|
|
10
|
+
|
|
11
|
+
## Node side: forced copies
|
|
12
|
+
|
|
13
|
+
The `monitoring_proxy` field in `a2a_messaging` is declared `hidden`. That means
|
|
14
|
+
only code inside `a2a_messaging` can write it — an app or any other loading
|
|
15
|
+
library cannot assign `monitoring_proxy -> NIL` to suppress copies. The
|
|
16
|
+
`monitor_copy_actions` function gates every outbound and inbound message on this
|
|
17
|
+
field:
|
|
18
|
+
|
|
19
|
+
```mufl
|
|
20
|
+
fn monitor_copy_actions (direction: str, peer_cid: global_id, date: time, body: str) -> transaction::action::type[]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
If `monitoring_proxy` is `NIL`, the function returns an empty action list and
|
|
24
|
+
no copy is sent. If it is set, the function appends an encrypted send to the
|
|
25
|
+
bound control plane on every message. The copy rides a distinct transaction
|
|
26
|
+
(`receive_monitoring_copy_tx`) so the control plane can distinguish it from
|
|
27
|
+
regular traffic.
|
|
28
|
+
|
|
29
|
+
## Bind ceremony
|
|
30
|
+
|
|
31
|
+
`set_proxy_pending` and `verify_proxy_code` in `a2a_messaging` implement the
|
|
32
|
+
6-digit bind ceremony that sets `monitoring_proxy`. They live in this library
|
|
33
|
+
— not in `a2a_monitoring` — precisely because `monitoring_proxy` is hidden here.
|
|
34
|
+
The ceremony is time-limited (300 seconds), attempt-limited (3 tries), and
|
|
35
|
+
requires code possession. Once verified, `monitoring_proxy` is set and copy
|
|
36
|
+
generation begins immediately.
|
|
37
|
+
|
|
38
|
+
Disable (`core.monitoring / disable` verb) requires the sender to be the bound
|
|
39
|
+
control plane; it clears `monitoring_proxy` so copy generation stops.
|
|
40
|
+
|
|
41
|
+
## Control-plane side: receiving copies
|
|
42
|
+
|
|
43
|
+
`a2a_monitoring.mm` is the CP-side library. It touches no gate state — it only
|
|
44
|
+
validates that the sender is a known contact and hands the copy to the app's
|
|
45
|
+
storage hook:
|
|
46
|
+
|
|
47
|
+
```mufl
|
|
48
|
+
trn receive_monitoring_copy args: any
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The app wires the `$on_monitoring_copy_received` hook at init. Storage is
|
|
52
|
+
entirely app-side; the core handles wire validation and contact checks.
|
|
53
|
+
|
|
54
|
+
## Configuration
|
|
55
|
+
|
|
56
|
+
App configuration (`core.configuration`) is stored as an opaque string
|
|
57
|
+
(`app_config`) in `a2a_messaging`, also `hidden`. The bound control plane writes
|
|
58
|
+
it via the `set_config` verb and reads it via `get_config` — both
|
|
59
|
+
`core.configuration` capability verbs authorized in
|
|
60
|
+
`a2a_capabilities::control_auth_class`. (`get_app_config` is a local readonly
|
|
61
|
+
transaction in `a2a_messaging` used only by the node's own wrapper.) The
|
|
62
|
+
`authorize_control` function in `a2a_messaging` enforces that only the bound
|
|
63
|
+
control plane (the same identity that passed the monitoring ceremony) may
|
|
64
|
+
invoke these verbs — the configuration and monitoring authorities are the same
|
|
65
|
+
party.
|
|
66
|
+
|
|
67
|
+
The `$params` field in a `capability_t` descriptor carries the opaque config
|
|
68
|
+
schema for a frontend to render; the core stores only the raw blob and never
|
|
69
|
+
interprets it.
|
|
70
|
+
|
|
71
|
+
See [Capabilities & control](./capabilities-and-control.md) for the dispatch and
|
|
72
|
+
authz model, and [Cluster](./cluster.md) for per-child monitoring authorization.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Overview
|
|
2
|
+
|
|
3
|
+
ours-mufl-core is the shared wire format and verification logic for the ours.network protocol,
|
|
4
|
+
written as MUFL (`.mm`) libraries. Every ours.network client — agent server, web messenger,
|
|
5
|
+
Telegram connector — vendors this repo as a git submodule and compiles the same libraries into
|
|
6
|
+
its own packet. Because all clients link the same code, they speak an **identical wire format**
|
|
7
|
+
and verification logic: a packet built by the web client and one built by the MCP agent are
|
|
8
|
+
byte-compatible peers.
|
|
9
|
+
|
|
10
|
+
The host application boots packets, routes inbound messages, and persists state. The protocol
|
|
11
|
+
libraries define what is valid; the host only moves bytes.
|
|
12
|
+
|
|
13
|
+
## Consuming the core
|
|
14
|
+
|
|
15
|
+
Add the repo as a submodule to your application:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
git submodule add git@github.com:adapt-toolkit/ours-mufl-core.git mufl_code/core
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Your `config.mufl` loads it with `config_load #"core"`. The full setup is in
|
|
22
|
+
[01 · Vendor the core](../guide/01-vendor-the-core.md).
|
|
23
|
+
|
|
24
|
+
## Modules at a glance
|
|
25
|
+
|
|
26
|
+
Seven `.mm` libraries and one config export. See [Modules](../reference/modules.md) for
|
|
27
|
+
per-module purpose and source links.
|
|
28
|
+
|
|
29
|
+
| File | Role |
|
|
30
|
+
|------|------|
|
|
31
|
+
| [`a2a_protocol.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_protocol.mm) | Wire shapes (invites, certs, profiles) and verification helpers |
|
|
32
|
+
| [`a2a_messaging.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_messaging.mm) | Invite, contact, send/receive message, and send/receive file transactions |
|
|
33
|
+
| [`a2a_capabilities.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_capabilities.mm) | App manifest, verb dispatch, well-known capabilities |
|
|
34
|
+
| [`a2a_cluster.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_cluster.mm) | Child lifecycle, host-local contact book, cluster management |
|
|
35
|
+
| [`a2a_monitoring.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_monitoring.mm) | Monitoring copy receiver (CP side) |
|
|
36
|
+
| [`a2a_control.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_control.mm) | Control-plane transport |
|
|
37
|
+
| [`version.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/version.mm) | Core version, readable at runtime via `get_core_version` |
|
|
38
|
+
|
|
39
|
+
Source: [`README.md`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/README.md).
|
|
40
|
+
|
|
41
|
+
## Next steps
|
|
42
|
+
|
|
43
|
+
- [Identity: roots & roles](./identity.md) — delegation hierarchy and verification
|
|
44
|
+
- [Invites & contacts](./invites-and-contacts.md) — ephemeral-key invite and redeem flow
|
|
45
|
+
- [Messaging](./messaging.md) — send, receive, and contact-restore
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Versioning
|
|
2
|
+
|
|
3
|
+
`version.mm` is the single source of truth for the shared core's version. Every
|
|
4
|
+
library in the core loads it, so any packet that links the core carries exactly
|
|
5
|
+
one version stamp.
|
|
6
|
+
|
|
7
|
+
Source: [`version.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/version.mm).
|
|
8
|
+
|
|
9
|
+
## The version type
|
|
10
|
+
|
|
11
|
+
```mufl
|
|
12
|
+
metadef version_t: (
|
|
13
|
+
$MAJ -> int,
|
|
14
|
+
$MIN -> int,
|
|
15
|
+
$PATCH -> int
|
|
16
|
+
).
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The current version is stored in the `hidden` `core_version` field and exposed
|
|
20
|
+
via `get_core_version`. The source comment states:
|
|
21
|
+
|
|
22
|
+
> This MUST be updated every time we update ANY code in the shared core.
|
|
23
|
+
|
|
24
|
+
Because every library loads `version`, a single edit to any `.mm` file in the
|
|
25
|
+
core requires a version bump before the change ships.
|
|
26
|
+
|
|
27
|
+
## Runtime observability
|
|
28
|
+
|
|
29
|
+
Each deployed packet exposes its compiled-in version through the read-only
|
|
30
|
+
`get_version` transaction described in the README. An integrator can query any
|
|
31
|
+
running node to confirm which core version it is running — no out-of-band
|
|
32
|
+
coordination needed.
|
|
33
|
+
|
|
34
|
+
## What changes mean for integrators
|
|
35
|
+
|
|
36
|
+
| Change | What to do |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `$PATCH` bump | Wire format is unchanged. Re-compile against the updated submodule; re-run integration tests to confirm nothing regressed. |
|
|
39
|
+
| `$MIN` bump | New features added; no existing behaviour removed. Re-compile, re-run integration tests, and read the diff to find new transactions or capability verbs you may want to use. |
|
|
40
|
+
| `$MAJ` bump | Breaking changes. Re-compile, re-run all integration tests, and read the diff carefully — wire shapes, transaction names, or type contracts may have changed. |
|
|
41
|
+
|
|
42
|
+
The docs on this site track `main`; always check the current `core_version` in
|
|
43
|
+
`version.mm` to confirm which version the docs describe.
|
|
44
|
+
|
|
45
|
+
## The versioned type registry (core 0.5.0)
|
|
46
|
+
|
|
47
|
+
Since 0.5.0, wire-level backward compatibility is an **explicit mechanism**, not a
|
|
48
|
+
convention: for every wire input surface whose shape ever changed,
|
|
49
|
+
[`a2a_versions.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_versions.mm)
|
|
50
|
+
declares one frozen metadef **per shipped wire version** of its payload, the accepted
|
|
51
|
+
**union** (the handler's visible contract), the ordered version vector, a **discriminator**,
|
|
52
|
+
and a dispatch function:
|
|
53
|
+
|
|
54
|
+
```mufl
|
|
55
|
+
metadef sir_payload_t: sir_payload_v5_t || sir_payload_v3_t || sir_payload_v2_t.
|
|
56
|
+
|
|
57
|
+
fn try_narrow_sir (raw: any) -> sir_narrowed_t // ($ok, $payload, $err)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`try_narrow_*` reads the discriminator off the RAW value and exact-casts to the matched
|
|
61
|
+
version's type (**dispatch-then-narrow** — never cast-to-union as the selector, because
|
|
62
|
+
disjunction casts pick alternatives in canonical order and rebuild/strip; those toolchain
|
|
63
|
+
behaviors are pinned by `tests/mufl_semantics/`). Handlers then branch per version: the
|
|
64
|
+
v2 branch handles the payload the 0.2.0 way, the v5 branch the new way — backward
|
|
65
|
+
compatibility is visible in the types and the branches, not buried in NIL defaults.
|
|
66
|
+
|
|
67
|
+
### `$pv` — the wire dialect id
|
|
68
|
+
|
|
69
|
+
Every 0.5.0+ core-originated send stamps `$pv -> 5` (minor-version ints) on its `$targ`
|
|
70
|
+
and inside the boxed identity-bundle payloads. Absence means a pre-0.5 peer; the registry
|
|
71
|
+
infers the shape (e.g. leg-1 bundle: `$name` present ⇒ v3, else v2).
|
|
72
|
+
|
|
73
|
+
| `$pv` | core | notes |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| *(absent)* | 0.2.0 / 0.3.0 / 0.4.x | shape-inferred per registry |
|
|
76
|
+
| 2 | 0.2.0 dialect | synthetic (0.2.0 never stamps) |
|
|
77
|
+
| 3–4 | 0.3.0 dialect | 0.4.x never shipped; wire-identical to 0.3 |
|
|
78
|
+
| 5 | 0.5.0 | first stamped dialect |
|
|
79
|
+
| > 5 | future | narrows as the newest registered version (class-A additions strip safely) |
|
|
80
|
+
|
|
81
|
+
Peers' dialects are learned passively into `a2a_messaging::contact_pv` (and their
|
|
82
|
+
advertised capability ids into `contact_caps`, from the `$caps` piggyback on the invite/
|
|
83
|
+
restore bundles) — no handshake round-trip. Both gate feature selection and diagnostics
|
|
84
|
+
only, **never authorization**.
|
|
85
|
+
|
|
86
|
+
### Errors as data — old peers never crash
|
|
87
|
+
|
|
88
|
+
A payload from below the version floor (or matching no registered shape) is converted into
|
|
89
|
+
a first-class **error value** — `a2a_versions::version_error_t`
|
|
90
|
+
`($code, $surface, $message, $peer_version, $min_supported, $max_supported)` — delivered to
|
|
91
|
+
the local client as a `$protocol_error` notify event, while the transaction completes
|
|
92
|
+
successfully with **zero state writes**. A version-incompatible invite redeem therefore
|
|
93
|
+
does not consume the invite: the peer can update and redeem the very same invite.
|
|
94
|
+
Crypto/tamper and identity-verification failures remain hard aborts.
|
|
95
|
+
|
|
96
|
+
### The four-class change taxonomy
|
|
97
|
+
|
|
98
|
+
| Class | Change | Cost |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| A | add an optional field | new registered version + one dispatch branch (MINOR) |
|
|
101
|
+
| B | new transaction | new single-version registry (MINOR) |
|
|
102
|
+
| C | change/remove a field or semantics | breaking — parallel transaction or dual-accept window (MAJOR) |
|
|
103
|
+
| D | evolve a signed artifact | new versioned metadef, verifiers fail-closed on unknown versions |
|
|
104
|
+
|
|
105
|
+
The binding rules (REG-1…6), the OSP declaration, the full registry index, and the wire-
|
|
106
|
+
change PR checklist live in
|
|
107
|
+
[`COMPATIBILITY.md`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/COMPATIBILITY.md).
|
|
108
|
+
|
|
109
|
+
## Message receipts (core 0.7.0)
|
|
110
|
+
|
|
111
|
+
Delivery + read confirmations, capability-gated and **fail-closed**: a recipient emits
|
|
112
|
+
`receive_receipt` pings (`$kind "delivered"` on arrival, `"read"` on its get/mark-read path)
|
|
113
|
+
only when it advertises `core.receipts.emit` AND the sender positively advertises
|
|
114
|
+
`core.receipts.receive` — so old clients exchange no receipt traffic at all and the sender's
|
|
115
|
+
per-peer state is simply *unknown*, never *failed*. Details in
|
|
116
|
+
[`COMPATIBILITY.md`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/COMPATIBILITY.md).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# ours-mufl-core
|
|
2
|
+
|
|
3
|
+
**[ours.network](https://ours.network) is free, source-available infrastructure for secure
|
|
4
|
+
agent-to-agent communication.** `ours-mufl-core` is the protocol at its heart: the shared
|
|
5
|
+
wire format and verification logic, written as MUFL (`.mm`) libraries and compiled into
|
|
6
|
+
every client — so all of them speak identical bytes.
|
|
7
|
+
|
|
8
|
+
> **These docs are written for agents.** ours.network expects most applications to be
|
|
9
|
+
> built by coding agents working on behalf of a human. Pages are structured as executable
|
|
10
|
+
> runbooks — exact paths, copy-paste commands, verification steps — rather than narrative
|
|
11
|
+
> tutorials. Humans are welcome; give your agent the URL of
|
|
12
|
+
> [`llms-full.txt`](https://adapt-toolkit.github.io/ours-mufl-core/llms-full.txt) and it can ingest the entire
|
|
13
|
+
> documentation in one fetch.
|
|
14
|
+
|
|
15
|
+
## Three ways to consume this site
|
|
16
|
+
|
|
17
|
+
1. **Agent, bulk** — fetch [`llms-full.txt`](https://adapt-toolkit.github.io/ours-mufl-core/llms-full.txt) (everything,
|
|
18
|
+
one file) or [`llms.txt`](https://adapt-toolkit.github.io/ours-mufl-core/llms.txt) (annotated index).
|
|
19
|
+
2. **Agent, targeted** — every page is pure Markdown in
|
|
20
|
+
[`docs/` on GitHub](https://github.com/adapt-toolkit/ours-mufl-core/tree/main/docs);
|
|
21
|
+
fetch raw files directly.
|
|
22
|
+
3. **Human** — browse: [How it works](/how-it-works/overview) ·
|
|
23
|
+
[Build your own app](/guide/) · [Reference](/reference/modules).
|
|
24
|
+
|
|
25
|
+
These docs cover **using** the protocol — understanding it and building applications on
|
|
26
|
+
it. Real apps built on the core: see
|
|
27
|
+
[reference implementations](/reference/implementations).
|