@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,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ours-mufl-core-docs",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"docs:dev": "vitepress dev .",
|
|
7
|
+
"docs:build": "vitepress build .",
|
|
8
|
+
"docs:check": "node scripts/check-citations.mjs && node scripts/check-denylist.mjs"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"mermaid": "^11.16.0",
|
|
12
|
+
"vitepress": "^1.6.0",
|
|
13
|
+
"vitepress-plugin-mermaid": "^2.0.17"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
The full contribution guidelines are in
|
|
4
|
+
[`CONTRIBUTING.md`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/CONTRIBUTING.md)
|
|
5
|
+
in the repository root.
|
|
6
|
+
|
|
7
|
+
## Current posture
|
|
8
|
+
|
|
9
|
+
Until the Contributor Licence Agreement (CLA) process is live, the project accepts
|
|
10
|
+
**issues and feedback only** — pull requests are not open. This applies to the protocol
|
|
11
|
+
repository. If you are integrating the core into your own agent, that integration is entirely
|
|
12
|
+
yours; no CLA or coordination is needed.
|
|
13
|
+
|
|
14
|
+
## How to contribute
|
|
15
|
+
|
|
16
|
+
- **Bug reports and questions** — open an issue on the
|
|
17
|
+
[ours-mufl-core repository](https://github.com/adapt-toolkit/ours-mufl-core).
|
|
18
|
+
- **Feature discussion** — open an issue describing the use case. Protocol changes affect every
|
|
19
|
+
client, so discussion precedes any design.
|
|
20
|
+
- **Security issues** — do not open a public issue; see `SECURITY.md` in the repository root.
|
|
21
|
+
- **Code contributions** — on hold until the CLA is live. See `CONTRIBUTING.md` for the rationale.
|
|
22
|
+
|
|
23
|
+
## Integration feedback
|
|
24
|
+
|
|
25
|
+
If you are building on the core and encounter something that does not match the docs, open an
|
|
26
|
+
issue. Integration experience is the most useful signal at this stage.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Glossary
|
|
2
|
+
|
|
3
|
+
Terms used across the protocol documentation.
|
|
4
|
+
Source of truth: the `.mm` files and
|
|
5
|
+
[`README.md`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/README.md)
|
|
6
|
+
(the identical wire guarantee).
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
**boxed send**
|
|
11
|
+
An encrypted message sent over a one-way ephemeral channel using a known public key, without a
|
|
12
|
+
pre-established `encrypted_channel`. Used during the invite redeem hop to carry identity
|
|
13
|
+
material before a persistent channel exists.
|
|
14
|
+
See [Invites & contacts](../how-it-works/invites-and-contacts.md).
|
|
15
|
+
|
|
16
|
+
**broker**
|
|
17
|
+
The relay service operated by ours.network that routes packets between agents. Agents do not
|
|
18
|
+
connect directly to each other; the broker forwards ciphertext without being able to read it.
|
|
19
|
+
|
|
20
|
+
**capability**
|
|
21
|
+
A named feature set advertised in an agent's app manifest. Each capability has a stable string
|
|
22
|
+
id (e.g. `"core.monitoring"`) and a schema; the control envelope routes requests to its handler
|
|
23
|
+
by `$cap` id.
|
|
24
|
+
See [Capabilities & control](../how-it-works/capabilities-and-control.md).
|
|
25
|
+
|
|
26
|
+
**cid / container\_id**
|
|
27
|
+
The stable, globally unique identifier for a MUFL packet instance (a running agent). Represented
|
|
28
|
+
as a `global_id`. Peers reference each other by container id in address documents and contact
|
|
29
|
+
registries.
|
|
30
|
+
|
|
31
|
+
**contact book**
|
|
32
|
+
The host-local registry of children and contacts maintained by the `core.cluster` capability
|
|
33
|
+
in `a2a_cluster.mm`. Entries record name, role, bio, persona, monitoring state, and capability
|
|
34
|
+
ids.
|
|
35
|
+
See [Cluster](../how-it-works/cluster.md).
|
|
36
|
+
|
|
37
|
+
**delegation cert**
|
|
38
|
+
A signed record (`delegation_cert_t`) that binds a role's container id and address-document hash
|
|
39
|
+
to its root, signed by the root's keys. A root identity carries no delegation cert (`NIL`);
|
|
40
|
+
detection is structural.
|
|
41
|
+
See [Identity: roots & roles](../how-it-works/identity.md).
|
|
42
|
+
|
|
43
|
+
**encrypted channel**
|
|
44
|
+
The persistent, per-contact end-to-end-encrypted session established during invite redemption and
|
|
45
|
+
restored from stored peer address documents on restart. All message and file traffic rides this
|
|
46
|
+
channel.
|
|
47
|
+
See [Messaging](../how-it-works/messaging.md).
|
|
48
|
+
|
|
49
|
+
**envelope**
|
|
50
|
+
A `control_envelope_t` record (`$cap`, `$verb`, `$args`, `$req_id`) that carries a capability
|
|
51
|
+
request. All inbound capability requests arrive in this shape; dispatch keys on `$cap`.
|
|
52
|
+
See [Capabilities & control](../how-it-works/capabilities-and-control.md).
|
|
53
|
+
|
|
54
|
+
**hidden state**
|
|
55
|
+
A MUFL field declared `hidden` that only code inside the declaring library can write. Used for
|
|
56
|
+
`monitoring_proxy` and `app_config` in `a2a_messaging.mm` to prevent any external library or
|
|
57
|
+
app from suppressing forced copies or overwriting configuration.
|
|
58
|
+
See [Monitoring & config](../how-it-works/monitoring-and-config.md).
|
|
59
|
+
|
|
60
|
+
**introduction**
|
|
61
|
+
A contact-establishment shortcut via a shared control plane. The CP sends each side the other's
|
|
62
|
+
signed address document via `ingest_connect_descriptor`; no out-of-band invite is needed.
|
|
63
|
+
Requires both nodes to advertise `core.connect`.
|
|
64
|
+
See [Invites & contacts](../how-it-works/invites-and-contacts.md).
|
|
65
|
+
|
|
66
|
+
**invite leg**
|
|
67
|
+
One step of the invite redeem flow (OOB transfer, leg 1, leg 2, leg 3). Leg 1 is the responder's
|
|
68
|
+
BARE send to the inviter; leg 2 is the inviter's receipt and verification of leg 1 (consuming the
|
|
69
|
+
invite and emitting leg 3); leg 3 is the inviter's BARE send reply to the responder.
|
|
70
|
+
See [Invites & contacts](../how-it-works/invites-and-contacts.md).
|
|
71
|
+
|
|
72
|
+
**MAJ / MIN**
|
|
73
|
+
The major (`$MAJ`) and minor (`$MIN`) components of `version_t`. A `$MAJ` bump signals breaking
|
|
74
|
+
changes; a `$MIN` bump signals new features with no removals.
|
|
75
|
+
See [Versioning](../how-it-works/versioning.md).
|
|
76
|
+
|
|
77
|
+
**monitoring copy**
|
|
78
|
+
A forced encrypted copy of every inbound and outbound message sent to the bound control plane
|
|
79
|
+
when `monitoring_proxy` is set. Generated by `monitor_copy_actions` in `a2a_messaging.mm`;
|
|
80
|
+
received by `a2a_monitoring.mm` on the CP side.
|
|
81
|
+
See [Monitoring & config](../how-it-works/monitoring-and-config.md).
|
|
82
|
+
|
|
83
|
+
**packet**
|
|
84
|
+
A compiled MUFL application instance — the unit that runs as an agent. Consumers vendor the
|
|
85
|
+
core and compile it together with their own libraries into a packet. Every packet that links the
|
|
86
|
+
core speaks the same wire format.
|
|
87
|
+
|
|
88
|
+
**role**
|
|
89
|
+
A delegated identity anchored to a root. A role carries a `delegation_cert_t` signed by its
|
|
90
|
+
root. Roles are the typical identity type for child/subagents in a cluster.
|
|
91
|
+
See [Identity: roots & roles](../how-it-works/identity.md).
|
|
92
|
+
|
|
93
|
+
**root**
|
|
94
|
+
A self-sovereign keypair identity with no delegation cert (`NIL`). A root is the anchor of an
|
|
95
|
+
identity hierarchy; roles are derived from it.
|
|
96
|
+
See [Identity: roots & roles](../how-it-works/identity.md).
|
|
97
|
+
|
|
98
|
+
**verb**
|
|
99
|
+
The operation requested within a capability, carried in the `$verb` field of a
|
|
100
|
+
`control_envelope_t`. Verbs must be consciously listed in `control_auth_class` to become
|
|
101
|
+
reachable through dispatch.
|
|
102
|
+
See [Capabilities & control](../how-it-works/capabilities-and-control.md).
|
|
103
|
+
|
|
104
|
+
**wrapper**
|
|
105
|
+
The host-side shim that connects the MUFL packet to the node runtime: wires the
|
|
106
|
+
`protocol_container`, routes inbound messages to `::actor::receive_message`, and marshals
|
|
107
|
+
notify-actions to host primitives.
|
|
108
|
+
See [Wire the host](../guide/03-wire-the-host.md).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Reference implementations
|
|
2
|
+
|
|
3
|
+
These two applications are the living examples of everything in the
|
|
4
|
+
[integration guide](../guide/index.md): real agents built on the core, each vendoring this repo
|
|
5
|
+
as a git submodule. Read their `config.mufl`, host wiring, and tests — not this page — for the
|
|
6
|
+
details.
|
|
7
|
+
|
|
8
|
+
## ours-mcp
|
|
9
|
+
|
|
10
|
+
[**ours-mcp**](https://github.com/adapt-toolkit/ours-mcp) is the MCP agent server. It vendors
|
|
11
|
+
the core at `packages/core/mufl_code/core`. Its `config.mufl` shows how the core export merges
|
|
12
|
+
with the packet's own libraries; its host wiring shows how the MUFL actor connects to the node
|
|
13
|
+
runtime; its tests show end-to-end protocol flows in production use.
|
|
14
|
+
|
|
15
|
+
## ours-tg-connector
|
|
16
|
+
|
|
17
|
+
[**ours-tg-connector**](https://github.com/adapt-toolkit/ours-tg-connector) is the Telegram
|
|
18
|
+
connector. It vendors the core at `mufl_code/core`. The same integration pattern —
|
|
19
|
+
`config_load #"core"`, host wiring, test suite — applied to a different transport.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
The guide describes the pattern. These repos are the pattern applied.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Modules
|
|
2
|
+
|
|
3
|
+
The core is eight `.mm` libraries and one config export loaded by `config_load #"core"` from a consumer's
|
|
4
|
+
`config.mufl`. The source is the authoritative reference; the
|
|
5
|
+
[how-it-works](../how-it-works/overview.md) pages explain the protocol design behind each module.
|
|
6
|
+
|
|
7
|
+
| File | Purpose |
|
|
8
|
+
|------|---------|
|
|
9
|
+
| [`a2a_capabilities.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_capabilities.mm) | App manifest, capability/verb envelope and dispatch, well-known capability ids (`core.configuration`, `core.monitoring`, `core.connect`, `core.cluster`). |
|
|
10
|
+
| [`a2a_protocol.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_protocol.mm) | Wire-facing shapes (invites, delegation certificates, root profiles, contact roots, introduction credentials) and the shared verification helpers. |
|
|
11
|
+
| [`a2a_messaging.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_messaging.mm) | Contact and messaging transactions: invite generation/redemption, add/remove contact, send message, send file, inbound receive, and the introduction flow. |
|
|
12
|
+
| [`a2a_cluster.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_cluster.mm) | The `core.cluster` capability handler: child/subagent lifecycle, per-child monitoring authorization, host-local contact book, and introductions between children and contacts. |
|
|
13
|
+
| [`a2a_monitoring.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_monitoring.mm) | Control-plane receiver side of monitoring copies: validates sender and hands the copy to the app's storage hook. |
|
|
14
|
+
| [`a2a_control.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_control.mm) | Control-plane transport: an opaque payload delivered to a contact over the `encrypted_channel`, validated on receipt. |
|
|
15
|
+
| [`a2a_notifications.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_notifications.mm) | Notification service protocol: per-contact scoped tokens (`$scope`), registration and WebPush bindings, token issuance/rotation/revocation, receive-mute, and the bare signed-send notification ingest. |
|
|
16
|
+
| [`version.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/version.mm) | Core version record, readable at runtime via `get_core_version`. |
|
|
17
|
+
| [`config.mufl`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/config.mufl) | Compile configuration: exports the libraries above for `config_load #"core"`. |
|
|
18
|
+
|
|
19
|
+
Source: [`README.md`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/README.md).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Validates docs/citations.json: every cited file exists in the repo,
|
|
2
|
+
// every cited symbol greps in that file, every cited page exists,
|
|
3
|
+
// and every non-stub content page has at least one citation entry.
|
|
4
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs'
|
|
5
|
+
import { join, dirname } from 'node:path'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
7
|
+
|
|
8
|
+
const docsRoot = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
9
|
+
const repoRoot = join(docsRoot, '..')
|
|
10
|
+
const manifest = JSON.parse(readFileSync(join(docsRoot, 'citations.json'), 'utf8'))
|
|
11
|
+
const errors = []
|
|
12
|
+
|
|
13
|
+
for (const [page, cites] of Object.entries(manifest)) {
|
|
14
|
+
if (!existsSync(join(docsRoot, page))) errors.push(`cited page missing: ${page}`)
|
|
15
|
+
for (const { file, symbol } of cites) {
|
|
16
|
+
const target = join(repoRoot, file)
|
|
17
|
+
if (!existsSync(target)) { errors.push(`${page}: cited file missing: ${file}`); continue }
|
|
18
|
+
if (symbol && !readFileSync(target, 'utf8').includes(symbol))
|
|
19
|
+
errors.push(`${page}: symbol "${symbol}" not found in ${file}`)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// coverage: every content page that is no longer a stub must be in the manifest
|
|
24
|
+
const stubMarker = '*(content lands in Tasks'
|
|
25
|
+
for (const dir of ['how-it-works', 'workflows', 'guide', 'reference']) {
|
|
26
|
+
for (const f of readdirSync(join(docsRoot, dir))) {
|
|
27
|
+
if (!f.endsWith('.md')) continue
|
|
28
|
+
const rel = `${dir}/${f}`
|
|
29
|
+
const body = readFileSync(join(docsRoot, rel), 'utf8')
|
|
30
|
+
if (!body.includes(stubMarker) && !(rel in manifest))
|
|
31
|
+
errors.push(`content page has no citations registered: ${rel}`)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (errors.length) { console.error('CITATION CHECK FAILED\n' + errors.join('\n')); process.exit(1) }
|
|
36
|
+
console.log('citation check OK:', Object.keys(manifest).length, 'pages')
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Internal team-process artifacts, host paths, and unmerged-feature references
|
|
2
|
+
// must never ship (SPEC C1/C2/C6).
|
|
3
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
4
|
+
import { join, dirname, relative } from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
|
|
7
|
+
// Patterns are assembled from fragments so this file itself never matches the
|
|
8
|
+
// deny-list checker (a literal match in scripts/ would be a false positive).
|
|
9
|
+
const DENY = [
|
|
10
|
+
new RegExp('WS-' + 'B'),
|
|
11
|
+
new RegExp('WS-' + 'C'),
|
|
12
|
+
new RegExp('WS-' + 'D'),
|
|
13
|
+
new RegExp('\\bcritic\\b', 'i'),
|
|
14
|
+
new RegExp('TEAM-' + 'PROTOCOL'),
|
|
15
|
+
new RegExp('Coordinator-' + 'frozen'),
|
|
16
|
+
new RegExp('/home/' + 'fleet'),
|
|
17
|
+
]
|
|
18
|
+
const docsRoot = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
19
|
+
const errors = []
|
|
20
|
+
const walk = d => { for (const e of readdirSync(d)) {
|
|
21
|
+
const p = join(d, e)
|
|
22
|
+
if (e === 'node_modules' || e === '.vitepress' || e === 'scripts') continue
|
|
23
|
+
if (statSync(p).isDirectory()) walk(p)
|
|
24
|
+
else if (e.endsWith('.md')) for (const term of DENY)
|
|
25
|
+
if (term.test(readFileSync(p, 'utf8')))
|
|
26
|
+
errors.push(`${relative(docsRoot, p)}: matches ${term}`)
|
|
27
|
+
}}
|
|
28
|
+
walk(docsRoot)
|
|
29
|
+
if (errors.length) { console.error('DENYLIST CHECK FAILED\n' + errors.join('\n')); process.exit(1) }
|
|
30
|
+
console.log('denylist check OK')
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Emits llms.txt (annotated index) and llms-full.txt (all pages concatenated)
|
|
2
|
+
// into .vitepress/dist after a build. Order follows the sidebar sections.
|
|
3
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs'
|
|
4
|
+
import { join, dirname } from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
|
|
7
|
+
const docsRoot = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
8
|
+
const dist = join(docsRoot, '.vitepress/dist')
|
|
9
|
+
const SITE = 'https://adapt-toolkit.github.io/ours-mufl-core'
|
|
10
|
+
const ORDER = ['index.md',
|
|
11
|
+
...['overview','identity','invites-and-contacts','messaging','capabilities-and-control',
|
|
12
|
+
'cluster','monitoring-and-config','versioning'].map(p => `how-it-works/${p}.md`),
|
|
13
|
+
...['index','contact-exchange','messaging','contact-restore','monitoring',
|
|
14
|
+
'control-verbs','introductions','cluster'].map(p => `workflows/${p}.md`),
|
|
15
|
+
...['index','01-vendor-the-core','02-configure-and-compile','03-wire-the-host',
|
|
16
|
+
'04-connect-and-message','05-test-your-app'].map(p => `guide/${p}.md`),
|
|
17
|
+
...['modules','implementations','glossary','contributing'].map(p => `reference/${p}.md`)]
|
|
18
|
+
|
|
19
|
+
let index = `# ours-mufl-core — agent docs index\n\n`
|
|
20
|
+
let full = ''
|
|
21
|
+
for (const rel of ORDER) {
|
|
22
|
+
const p = join(docsRoot, rel)
|
|
23
|
+
if (!existsSync(p)) continue
|
|
24
|
+
const body = readFileSync(p, 'utf8')
|
|
25
|
+
const title = (body.match(/^#\s+(.+)$/m) ?? [,rel])[1]
|
|
26
|
+
const url = `${SITE}/${rel.replace(/index\.md$/, '').replace(/\.md$/, '')}`
|
|
27
|
+
index += `- [${title}](${url})\n`
|
|
28
|
+
full += `\n\n----- ${rel} -----\n\n${body}`
|
|
29
|
+
}
|
|
30
|
+
writeFileSync(join(dist, 'llms.txt'), index)
|
|
31
|
+
writeFileSync(join(dist, 'llms-full.txt'), full.trimStart())
|
|
32
|
+
console.log('llms.txt + llms-full.txt written')
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Cluster lifecycle
|
|
2
|
+
|
|
3
|
+
`core.cluster` verbs manage hosted children (subagents) of a root identity. Operations that
|
|
4
|
+
need host work — provisioning a packet, destroying one, minting a child's invite, binding a
|
|
5
|
+
child's monitoring — are **asynchronous**: the verb handler validates, persists a pending
|
|
6
|
+
request keyed by a unique handle, emits a *host-primitive* notify to the local daemon, and
|
|
7
|
+
acknowledges `$pending` immediately. The daemon does the work and calls back a host-only
|
|
8
|
+
transaction that consumes the pending request and routes the real result to the original
|
|
9
|
+
controller.
|
|
10
|
+
|
|
11
|
+
Traced from [`a2a_cluster.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_cluster.mm)
|
|
12
|
+
(`cluster_handler`, `register_provisioned_child`, `sweep_and_settle`, `reconcile`) and
|
|
13
|
+
[`a2a_messaging.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_messaging.mm)
|
|
14
|
+
(`relay_enroll_delegated_node`, `handle_enroll_delegated_node`).
|
|
15
|
+
|
|
16
|
+
## Async child create
|
|
17
|
+
|
|
18
|
+
```mermaid
|
|
19
|
+
sequenceDiagram
|
|
20
|
+
autonumber
|
|
21
|
+
participant C as Controller (control plane)
|
|
22
|
+
participant R as Root packet
|
|
23
|
+
participant D as Root host (daemon)
|
|
24
|
+
participant K as New child packet
|
|
25
|
+
|
|
26
|
+
C->>R: control_message - core.cluster / create ($name, $bio, $req_id)
|
|
27
|
+
Note over R: dispatch authz - cluster_handler - duplicate check<br/>(registry AND pending creates, name is a global op-key)
|
|
28
|
+
Note over R: pending_reqs[handle] = (sender, $req_id, verb, op-key) -<br/>pending_create_names[$name]
|
|
29
|
+
R-->>D: notify host_provision_child ($name, $bio, $pending_handle)
|
|
30
|
+
R-->>C: immediate ack: $ok TRUE, $pending TRUE
|
|
31
|
+
|
|
32
|
+
Note over D: provision the child packet + delegate its role
|
|
33
|
+
D->>K: create the packet (host runtime op)
|
|
34
|
+
D->>R: register_provisioned_child ($pending_handle, $role_id, $child_ad)
|
|
35
|
+
Note over R: host-only gates: origin user + handle must match an<br/>outstanding create - consumed atomically (unsolicited callbacks abort)
|
|
36
|
+
Note over R: cluster_members[cid] registered - caps seeded from my own manifest
|
|
37
|
+
R-->>D: async reply routed to the STORED controller ($target, $response)
|
|
38
|
+
D->>C: response: $cid, $name, $monitoring "off" (correlated by $req_id)
|
|
39
|
+
R->>C: receive_roster_update ($version, $members) - push, if a CP is bound
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`remove` (destroy + `confirm_child_destroyed`), `contact` (mint the **child's** invite in the
|
|
43
|
+
child's own packet + `register_child_invite` — a root-minted invite would connect the caller to
|
|
44
|
+
the root, not the child), and `set_monitoring` (host-bind or host-clear the child's proxy +
|
|
45
|
+
`confirm_child_monitoring`) follow the same handle pattern with their own host primitives.
|
|
46
|
+
|
|
47
|
+
Two safety nets close the loop:
|
|
48
|
+
|
|
49
|
+
- **`reconcile`** — host truth joined with the registry: backfills children created
|
|
50
|
+
out-of-band, drops members no longer hosted, preserves CP-authoritative fields (`$bio`,
|
|
51
|
+
`$monitoring`) for existing members.
|
|
52
|
+
- **`sweep_and_settle`** — pending requests older than 120 s are settled: a create whose name
|
|
53
|
+
now exists is adopted as success, otherwise timed out; an expired create also clears its
|
|
54
|
+
global name key so a lost callback never leaves a name permanently un-creatable.
|
|
55
|
+
|
|
56
|
+
## Cluster enrollment (one root bind conveys the whole cluster)
|
|
57
|
+
|
|
58
|
+
The control plane holds `peer_ads` for every cluster member off a **single root bind**: the
|
|
59
|
+
root relays each child's public material; the child never participates.
|
|
60
|
+
|
|
61
|
+
```mermaid
|
|
62
|
+
sequenceDiagram
|
|
63
|
+
autonumber
|
|
64
|
+
participant D as Root host
|
|
65
|
+
participant R as Root packet
|
|
66
|
+
participant CP as Control-plane packet
|
|
67
|
+
|
|
68
|
+
D->>R: relay_enroll_delegated_node ($proxy, $child_ad, $delegation_cert, $root_profile)
|
|
69
|
+
Note over R: root-only (a role cannot relay)
|
|
70
|
+
R->>CP: enroll_delegated_node ($child_ad, $delegation_cert, $root_profile) - encrypted channel
|
|
71
|
+
Note over CP: gates: child document self-signature (proof-of-possession) -<br/>verify_peer_delegation binds child cid + document hash to a root,<br/>root-signed - the SENDER must BE that root<br/>(possession of a valid chain is not enough)
|
|
72
|
+
Note over CP: contact registered as root_name/role_id -<br/>re-enroll just refreshes the stored document
|
|
73
|
+
CP-->>CP: notify $enrolled (or $reenrolled) to its host
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Per-child monitoring, derived — never named
|
|
77
|
+
|
|
78
|
+
On `set_monitoring` enable, the CP a child gets bound to is **derived from the root's own
|
|
79
|
+
ceremony-pinned `monitoring_proxy`** (`bound_cp_cid`), never taken from the request arguments —
|
|
80
|
+
so a child can only ever be bound to the CP its root actually ceremonied. The daemon first
|
|
81
|
+
host-injects the CP as a contact into the child (`host_register_monitoring_cp`, with the CP
|
|
82
|
+
document verified), because a network introduction would be rejected by the child's CP-only
|
|
83
|
+
acceptance gate and would race the ceremony. Disable host-clears the child's proxy and drops
|
|
84
|
+
the injected CP contact (`host_clear_child_monitoring`).
|
|
85
|
+
|
|
86
|
+
Roster changes (create, remove, bio/persona updates, monitoring flips, reconcile diffs) push a
|
|
87
|
+
sequenced snapshot to the bound CP — `receive_roster_update`, ingested on the CP side by the
|
|
88
|
+
`on_roster_update` hook (see
|
|
89
|
+
[`a2a_monitoring.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_monitoring.mm)).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Contact exchange (invite redeem)
|
|
2
|
+
|
|
3
|
+
Two strangers become mutual contacts through a **slim ephemeral-key invite** and a three-leg
|
|
4
|
+
redeem handshake. The invite blob itself carries no identity material — only an ephemeral
|
|
5
|
+
pubkey and correlation data (see [Invites & contacts](../how-it-works/invites-and-contacts.md)
|
|
6
|
+
for the invite shape). Identity bundles move inside boxes on legs 1 and 3, both of which are
|
|
7
|
+
**bare sends**: the two sides are not each other's contacts yet, so the encrypted channel
|
|
8
|
+
cannot carry them.
|
|
9
|
+
|
|
10
|
+
Traced from [`a2a_messaging.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_messaging.mm)
|
|
11
|
+
(`generate_invite` / `mint_eph_invite`, `add_contact`, `handle_submit_invite_response`,
|
|
12
|
+
`handle_complete_invite`).
|
|
13
|
+
|
|
14
|
+
```mermaid
|
|
15
|
+
sequenceDiagram
|
|
16
|
+
autonumber
|
|
17
|
+
participant IH as Inviter host
|
|
18
|
+
participant I as Inviter packet
|
|
19
|
+
participant R as Responder packet
|
|
20
|
+
participant RH as Responder host
|
|
21
|
+
|
|
22
|
+
IH->>I: generate_invite ($name)
|
|
23
|
+
Note over I: mint_eph_invite - fresh ephemeral keypair.<br/>pending_invites[id] = pub half + assigned name.<br/>pending_invite_keys[id] = private half (hidden, never exported)
|
|
24
|
+
I-->>IH: $invite blob, $invite_id
|
|
25
|
+
|
|
26
|
+
Note over IH,RH: invite blob travels out-of-band (QR, link, chat)
|
|
27
|
+
|
|
28
|
+
RH->>R: add_contact ($invite, $name)
|
|
29
|
+
Note over R: LEG 1 - fresh responder ephemeral keypair.<br/>Box my identity bundle to the invite's eph pubkey.<br/>pending_redemptions[id] + pending_redemption_keys[id]
|
|
30
|
+
R->>I: submit_invite_response ($invite_id, $epk, $v, $data) - bare boxed send
|
|
31
|
+
|
|
32
|
+
Note over I: LEG 2 gates, in order, no writes until all pass:<br/>pending lookup (single-use) - box-open -<br/>cid-bind + PoP self-sig - optional delegation chain
|
|
33
|
+
Note over I: register contact + peer_ads, consume<br/>pending_invites + pending_invite_keys atomically
|
|
34
|
+
I->>R: complete_invite ($invite_id, $epk, $v, $data) - bare boxed send
|
|
35
|
+
I-->>IH: notify $contact_accepted
|
|
36
|
+
|
|
37
|
+
Note over R: LEG 3 gates: pending lookup - expected-inviter<br/>cid pin - box-open with kept eph key -<br/>cid-bind + PoP - optional chain
|
|
38
|
+
Note over R: register contact + peer_ads, clear<br/>pending_redemptions + pending_redemption_keys
|
|
39
|
+
R-->>RH: notify $contact_added
|
|
40
|
+
|
|
41
|
+
Note over I,R: both sides registered - encrypted_channel carries all further traffic
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Key properties visible in the flow
|
|
45
|
+
|
|
46
|
+
- **Single-use**: the first valid leg 2 consumes `pending_invites[id]` *and*
|
|
47
|
+
`pending_invite_keys[id]` together. A replayed leg 1 aborts with `already-redeemed` and
|
|
48
|
+
mutates nothing; a leg 1 that fails a gate (bad box, forged bundle) consumes nothing.
|
|
49
|
+
- **Disclosure order**: the responder discloses its identity first (leg 1); the inviter answers
|
|
50
|
+
with its own bundle only after the responder's bundle verified (leg 3).
|
|
51
|
+
- **Why bare sends**: on leg 1 the inviter is not registered on the responder side (and vice
|
|
52
|
+
versa on leg 3), so `send_encrypted_tx` could not resolve a source key. The box to the
|
|
53
|
+
ephemeral key is the confidentiality; envelope signing plus the cid-bind and
|
|
54
|
+
proof-of-possession checks are the authenticity.
|
|
55
|
+
- **Role invites**: when either side is a delegated role, its bundle also carries the delegation
|
|
56
|
+
cert, root profile, and optional root-CP binding — verified with `verify_identity_bundle`, so
|
|
57
|
+
each side learns the other's verified root linkage (`contact_roots`).
|
|
58
|
+
|
|
59
|
+
An invite can also be minted for a hosted child via the cluster `contact` verb — same
|
|
60
|
+
construction path (`mint_eph_invite`), see [Cluster lifecycle](./cluster.md).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Contact restore
|
|
2
|
+
|
|
3
|
+
A **degraded contact** is a cid present in `contacts` but missing from `peer_ads` — typically
|
|
4
|
+
after a breaking-change migration carried the contact but had to drop its address document.
|
|
5
|
+
Contact restore re-runs the key exchange between two *mutually known* addresses: the same
|
|
6
|
+
machinery as the invite legs (identity bundle, box to an ephemeral key, gates before any
|
|
7
|
+
write), but the trust anchor is "a signed request from an address already in my contacts"
|
|
8
|
+
instead of an out-of-band invite token.
|
|
9
|
+
|
|
10
|
+
Traced from [`a2a_messaging.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_messaging.mm)
|
|
11
|
+
(`begin_contact_restore`, `handle_request_contact_restore`, `handle_submit_restore_response`,
|
|
12
|
+
`handle_complete_restore`, `flush_deferred`, `restore_degraded_contacts`).
|
|
13
|
+
|
|
14
|
+
```mermaid
|
|
15
|
+
sequenceDiagram
|
|
16
|
+
autonumber
|
|
17
|
+
participant H as Requester host
|
|
18
|
+
participant A as Requester packet (degraded contact for B)
|
|
19
|
+
participant B as Responder packet (still holds A in contacts)
|
|
20
|
+
|
|
21
|
+
Note over H,A: trigger: send_message toward the degraded contact<br/>(queues into deferred_msgs) OR the host boot/GC sweep<br/>restore_degraded_contacts (up to 30 attempts per contact)
|
|
22
|
+
Note over A: begin_contact_restore - fresh ephemeral keypair + $rid.<br/>pending_restores[B] (replaces any outstanding attempt)
|
|
23
|
+
A->>B: LEG 0 request_contact_restore ($rid, $epk, $v) - bare signed send
|
|
24
|
+
|
|
25
|
+
Note over B: gate: sender in contacts - else SILENT no-op<br/>(no error reply, so address knowledge never leaks)
|
|
26
|
+
Note over B: pending_restore_replies[A] + fresh reply eph keypair.<br/>Nothing installed or replaced yet
|
|
27
|
+
B->>A: LEG 1 submit_restore_response ($rid, $epk, $v, $data) - B's bundle boxed to A's eph key
|
|
28
|
+
|
|
29
|
+
Note over A: gates: pending lookup - $rid pin - box-open -<br/>cid-bind + PoP - optional chain
|
|
30
|
+
Note over A: reinstall peer_ads[B], consume pending_restores single-use
|
|
31
|
+
A->>B: LEG 2 complete_restore ($rid, $epk, $v, $data) - A's bundle boxed to B's reply eph key
|
|
32
|
+
A-->>H: notify $contact_restored
|
|
33
|
+
|
|
34
|
+
Note over B: gates: reply lookup - $rid pin - box-open - cid-bind + PoP - chain
|
|
35
|
+
Note over B: REPLACE peer_ads[A] (a reseeded peer rolls fresh keys,<br/>so even a present-but-stale document must be replaced)
|
|
36
|
+
B-->>B: notify $contact_restored (to its own host)
|
|
37
|
+
|
|
38
|
+
H->>A: flush_deferred ($contact) - host-driven on the notify
|
|
39
|
+
A->>B: receive_message x queued - encrypted channel, original wire_ids preserved
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Why the pieces are shaped this way
|
|
43
|
+
|
|
44
|
+
- **Leg 0 is unboxed** (just `$rid`, an ephemeral pubkey, and a scheme id): there is nothing
|
|
45
|
+
secret to carry yet, and the framework signs every envelope, so the responder authenticates
|
|
46
|
+
the requester from the envelope alone.
|
|
47
|
+
- **Silent no-op for strangers**: a request from an address not in `contacts` returns success
|
|
48
|
+
with no actions — whether an address is known never leaks.
|
|
49
|
+
- **The flush is host-driven**, not automatic: firing `flush_deferred` on the
|
|
50
|
+
`$contact_restored` notify means the encrypted sends can never race the restore legs' bare
|
|
51
|
+
sends on the wire.
|
|
52
|
+
- **Retry budget**: the host sweep re-fires on its GC cadence, up to `restore_max_attempts`
|
|
53
|
+
(30) per contact; a peer that upgraded and came back online answers on the first
|
|
54
|
+
post-upgrade attempt. Each re-mint supersedes the previous ephemeral key, so a stale leg-1
|
|
55
|
+
reply fails both the `$rid` check and the unbox.
|
|
56
|
+
- **Observability**: `list_degraded_contacts` and `list_deferred_queues` are the readonly
|
|
57
|
+
views the host sweep keys off.
|
|
58
|
+
|
|
59
|
+
This flow is what makes contacts survive breaking changes — see the migration contract in
|
|
60
|
+
[Versioning](../how-it-works/versioning.md).
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Control-plane verb calls
|
|
2
|
+
|
|
3
|
+
All capability verbs — configuration, monitoring, connect, cluster — ride **one** transport
|
|
4
|
+
transaction (`a2a_control`'s `control_message`) carrying a typed envelope
|
|
5
|
+
`($cap, $verb, $args, $req_id)`. The receiving side routes it through a single dispatch
|
|
6
|
+
chokepoint with a fail-closed authorization table. MUFL has no JSON codec, so the daemon
|
|
7
|
+
adapts JSON to native records on the way in and marshals the native response envelope back to
|
|
8
|
+
JSON on the way out — generically, with no per-verb logic in the host.
|
|
9
|
+
|
|
10
|
+
Traced from [`a2a_control.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_control.mm)
|
|
11
|
+
(`send_control`, `control_message`),
|
|
12
|
+
[`a2a_capabilities.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_capabilities.mm)
|
|
13
|
+
(`dispatch`, `control_auth_class`), and
|
|
14
|
+
[`a2a_messaging.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_messaging.mm)
|
|
15
|
+
(`authorize_control`).
|
|
16
|
+
|
|
17
|
+
```mermaid
|
|
18
|
+
sequenceDiagram
|
|
19
|
+
autonumber
|
|
20
|
+
participant C as Controller node (e.g. messenger)
|
|
21
|
+
participant T as Target packet
|
|
22
|
+
participant D as Target host (daemon)
|
|
23
|
+
|
|
24
|
+
C->>T: send_control - control_message ($payload JSON, $app_id) - encrypted channel
|
|
25
|
+
Note over T: gates: external + encrypted + sender is a known contact
|
|
26
|
+
T-->>D: on_control_received hook ($sender_id, $payload, $app_id, $date)
|
|
27
|
+
Note over D: adapt: parse $payload JSON into a native envelope<br/>($cap, $verb, $args, $req_id)
|
|
28
|
+
D->>T: a2a_capabilities::dispatch (sender, envelope)
|
|
29
|
+
Note over T: control_auth_class ($cap, $verb) - fail-closed:<br/>public - pass / bootstrap - pass (bind checks its own code) /<br/>controller - authorize_control: sender must BE the bound<br/>monitoring proxy / anything unlisted - deny
|
|
30
|
+
alt authorized
|
|
31
|
+
Note over T: handler for $cap runs (a2a_cluster::cluster_handler,<br/>monitoring_handler, connect_handler, app handlers)
|
|
32
|
+
T-->>D: response_envelope ($req_id, $ok, $result, $err) as return_data
|
|
33
|
+
else denied
|
|
34
|
+
T-->>D: response_envelope $ok FALSE ($err unknown_verb / unauthorized)
|
|
35
|
+
end
|
|
36
|
+
Note over D: marshal the native envelope to JSON
|
|
37
|
+
D->>T: send_control back toward the controller
|
|
38
|
+
T->>C: control_message (response payload)
|
|
39
|
+
Note over C: correlate on (sender, $req_id)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## The authorization classes
|
|
43
|
+
|
|
44
|
+
`control_auth_class` is a pure table in the lowest layer; the stateful half
|
|
45
|
+
(`authorize_control`, which reads the hidden `monitoring_proxy`) is wired in at init and
|
|
46
|
+
enforced *inside* `dispatch`, so an app cannot wire routing while forgetting the gate — a
|
|
47
|
+
controller-class verb with no authorizer wired aborts.
|
|
48
|
+
|
|
49
|
+
| Class | Verbs | Who may call |
|
|
50
|
+
|-------|-------|--------------|
|
|
51
|
+
| `public` | none via dispatch (`get_manifest` is a standalone readonly transaction, never routed through dispatch) | anyone |
|
|
52
|
+
| `bootstrap` | `core.monitoring` / `bind` | whoever presents the 6-digit code (pre-bind by definition) |
|
|
53
|
+
| `controller` | the explicit list: `core.cluster` verbs, `core.monitoring` / `disable`, `core.connect` / `introduce`, `core.configuration` `get_config` / `set_config` | only the bound control plane |
|
|
54
|
+
| `deny` | everything else | no one — a new verb must be consciously classified to become reachable |
|
|
55
|
+
|
|
56
|
+
## Async verbs
|
|
57
|
+
|
|
58
|
+
Some cluster verbs cannot complete inside one transaction (provisioning a child takes host
|
|
59
|
+
work). Their handlers return an immediate `$pending` acknowledgment and the real result is
|
|
60
|
+
routed later by a host callback to the *stored* original controller — see
|
|
61
|
+
[Cluster lifecycle](./cluster.md) for that pattern.
|
|
62
|
+
|
|
63
|
+
## Configuration writes
|
|
64
|
+
|
|
65
|
+
`set_app_config` follows the same trust rule as every controller-class verb: external,
|
|
66
|
+
encrypted, sender must be the bound CP (`require_bound_cp_or_abort`). The blob is opaque to the
|
|
67
|
+
core — a `$config_updated` notify wakes the host wrapper, which pulls it via `get_app_config`
|
|
68
|
+
and applies the operational parts. See
|
|
69
|
+
[Capabilities & control](../how-it-works/capabilities-and-control.md) for the envelope types
|
|
70
|
+
and manifest shape.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Transaction flows
|
|
2
|
+
|
|
3
|
+
Each page in this section traces **one conceptual protocol workflow** as a sequence diagram:
|
|
4
|
+
which transaction invokes which, how the call leaves the node, and how the result comes back.
|
|
5
|
+
Every arrow is labeled with the real transaction or function name from the `.mm` source — the
|
|
6
|
+
diagrams are traced from the code, not from a design document.
|
|
7
|
+
|
|
8
|
+
## How to read the diagrams
|
|
9
|
+
|
|
10
|
+
**Participants.** A *packet* is the compiled MUFL protocol container of one identity. A *host*
|
|
11
|
+
is the daemon embedding that packet (the reference host is the ours MCP daemon). A *control
|
|
12
|
+
plane* (CP) is a peer node bound via the monitoring ceremony. Arrows between two packets are
|
|
13
|
+
network sends; delivery is the ADAPT framework's job — if the receiver is offline, the ADAPT
|
|
14
|
+
broker holds the pending message.
|
|
15
|
+
|
|
16
|
+
**Transaction origins.** Every transaction validates its origin first
|
|
17
|
+
(`current_transaction_info::validate_origin_or_abort`):
|
|
18
|
+
|
|
19
|
+
| Origin | Meaning | Drawn as |
|
|
20
|
+
|--------|---------|----------|
|
|
21
|
+
| `origin::user` | fired by the local host/daemon on behalf of the operator | `Host ->> Packet` |
|
|
22
|
+
| `origin::external` | arrived from the network (envelope carries the authenticated sender in `$from`) | `Packet A ->> Packet B` |
|
|
23
|
+
|
|
24
|
+
**Send mechanisms.** Two kinds of arrows leave a packet:
|
|
25
|
+
|
|
26
|
+
- **Encrypted-channel send** (`encrypted_channel::send_encrypted_tx`) — the normal path between
|
|
27
|
+
registered contacts; requires the peer's address document in `peer_ads`.
|
|
28
|
+
- **Bare send** (`transaction::action::send`) — used only when the peer is *not yet* (or no
|
|
29
|
+
longer) resolvable as a contact: the invite redeem legs and the contact-restore legs. Payload
|
|
30
|
+
confidentiality then comes from a box to an ephemeral key carried in the flow itself, and the
|
|
31
|
+
framework signs every envelope, so the receiver still authenticates the sender.
|
|
32
|
+
|
|
33
|
+
**Results.** A transaction returns *actions*: network sends, `$data` (the caller's return
|
|
34
|
+
value), `$notify_agent` (an event for the host — drawn as a dashed arrow back to the host), and
|
|
35
|
+
`$save_state` (persist the packet — emitted only at the end of a complete step, never
|
|
36
|
+
mid-handshake, so a crash restores to the last stable point).
|
|
37
|
+
|
|
38
|
+
## The workflows
|
|
39
|
+
|
|
40
|
+
| Workflow | What it covers |
|
|
41
|
+
|----------|----------------|
|
|
42
|
+
| [Contact exchange](./contact-exchange.md) | `generate_invite` → `add_contact` → the three redeem legs |
|
|
43
|
+
| [Send & receive messages](./messaging.md) | `send_message` / `receive_message`, `send_file` / `receive_file`, storage hooks |
|
|
44
|
+
| [Contact restore](./contact-restore.md) | self-healing a degraded contact: restore legs 0–2 + `flush_deferred` |
|
|
45
|
+
| [Monitoring bind & copies](./monitoring.md) | the 6-digit bind ceremony, forced copies, disable |
|
|
46
|
+
| [Control-plane verb calls](./control-verbs.md) | `send_control` → `dispatch` → capability handler → response envelope |
|
|
47
|
+
| [Introductions](./introductions.md) | `core.connect`: a shared CP connects two nodes without an invite |
|
|
48
|
+
| [Cluster lifecycle](./cluster.md) | async child create via host primitives, cluster enrollment, roster push |
|
|
49
|
+
| [Notifications](./notifications.md) | `notify_register` → `notify_issue_tokens` → `send_notification` / `post_notification`, mute/rotate/revoke |
|
|
50
|
+
|
|
51
|
+
All flows were traced from
|
|
52
|
+
[`a2a_messaging.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_messaging.mm),
|
|
53
|
+
[`a2a_control.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_control.mm),
|
|
54
|
+
[`a2a_capabilities.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_capabilities.mm),
|
|
55
|
+
[`a2a_cluster.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_cluster.mm) and
|
|
56
|
+
[`a2a_monitoring.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_monitoring.mm) and
|
|
57
|
+
[`a2a_notifications.mm`](https://github.com/adapt-toolkit/ours-mufl-core/blob/main/a2a_notifications.mm).
|