@adapt-toolkit/a2adapt 0.11.2 → 0.11.4

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.
@@ -3,7 +3,7 @@
3
3
  "name": "a2adapt",
4
4
  "displayName": "a2adapt",
5
5
  "description": "Secure agent-to-agent communication channel over ADAPT: self-sovereign pubkey identity, end-to-end encryption, plan-first execution.",
6
- "version": "0.11.2",
6
+ "version": "0.11.4",
7
7
  "author": {
8
8
  "name": "Adapt Toolkit"
9
9
  },
package/dist/index.js CHANGED
@@ -22512,7 +22512,7 @@ function writeIdentityFile(target, opts, overwrite = false) {
22512
22512
  }
22513
22513
 
22514
22514
  // src/index.ts
22515
- var VERSION = true ? "0.11.2" : "0.0.0-dev";
22515
+ var VERSION = true ? "0.11.4" : "0.0.0-dev";
22516
22516
  var CONFIG = loadConfig();
22517
22517
  var STATE_DIR = CONFIG.stateDir;
22518
22518
  var BROKER_URL = CONFIG.brokerUrl;
@@ -22962,7 +22962,7 @@ async function provisionIdentity(name, opts = { exposeLocal: true, localAutoAcce
22962
22962
  const seed = randomBytes(24).toString("hex");
22963
22963
  fs2.writeFileSync(seedPath(dir), seed, { mode: 384 });
22964
22964
  const id = await createPacket(name, seed, dir);
22965
- await mutatingTx(id, "::actor::set_my_name", { name });
22965
+ await mutatingTx(id, "::a2a_messaging::set_my_name", { name });
22966
22966
  await pinRegistrar(id);
22967
22967
  if (!opts.localAutoAccept) {
22968
22968
  await mutatingTx(id, "::actor::set_local_policy", { auto_accept: false });
@@ -23174,7 +23174,7 @@ function createMcpServer(getSessionId) {
23174
23174
  if (identities.has(name)) return textResult(`create_identity failed: an identity named "${name}" already exists.`, true);
23175
23175
  try {
23176
23176
  const id = await provisionIdentity(name, { exposeLocal: expose_local, localAutoAccept: local_auto_accept });
23177
- if (bio) await mutatingTx(id, "::actor::set_my_bio", { bio });
23177
+ if (bio) await mutatingTx(id, "::a2a_messaging::set_my_bio", { bio });
23178
23178
  let hierarchy = "";
23179
23179
  const root = rootName ? identities.get(rootName) : void 0;
23180
23180
  if (root) {
@@ -23216,7 +23216,7 @@ Ask the user: do you want to arm a message monitor for "${name}"? If yes, use: M
23216
23216
  }
23217
23217
  try {
23218
23218
  const id = await provisionIdentity(name, { exposeLocal: expose_local, localAutoAccept: local_auto_accept });
23219
- if (bio) await mutatingTx(id, "::actor::set_my_bio", { bio });
23219
+ if (bio) await mutatingTx(id, "::a2a_messaging::set_my_bio", { bio });
23220
23220
  rootName = name;
23221
23221
  writeRootMarker(name);
23222
23222
  const adopted = [];
@@ -23444,7 +23444,7 @@ ${inbox.map((m) => fmtMsg(m)).join("\n")}`;
23444
23444
  try {
23445
23445
  const targ = {};
23446
23446
  if (name) targ.name = name;
23447
- const data = await mutatingTx(id, "::actor::generate_invite", targ);
23447
+ const data = await mutatingTx(id, "::a2a_messaging::generate_invite", targ);
23448
23448
  const blob = packInvite(Buffer.from(data.Reduce("invite").GetBinary()));
23449
23449
  const heading = name ? `Invite for "${name}" created.` : "Invite created \u2014 the contact will be registered under the name the recipient announces when accepting.";
23450
23450
  return textResult(
@@ -23478,7 +23478,7 @@ ${blob}`
23478
23478
  const blobValue = id.pw.packet.NewBinaryFromBuffer(buf);
23479
23479
  const targ = { invite: blobValue };
23480
23480
  if (name) targ.name = name;
23481
- const data = await mutatingTx(id, "::actor::add_contact", targ);
23481
+ const data = await mutatingTx(id, "::a2a_messaging::add_contact", targ);
23482
23482
  const added = data.Reduce("added").Visualize();
23483
23483
  const cid = data.Reduce("container_id").Visualize();
23484
23484
  const roleId = data.Reduce("role_id").Visualize();
@@ -23499,9 +23499,9 @@ ${blob}`
23499
23499
  const { id, err } = boundOr();
23500
23500
  if (err) return err;
23501
23501
  try {
23502
- const contacts = renderContacts(readonlyTx(id, "::actor::list_contacts"));
23502
+ const contacts = renderContacts(readonlyTx(id, "::a2a_messaging::list_contacts"));
23503
23503
  const pending = renderPending(readonlyTx(id, "::actor::list_pending_introductions"));
23504
- const roots = renderContactRoots(readonlyTx(id, "::actor::list_contact_roots"));
23504
+ const roots = renderContactRoots(readonlyTx(id, "::a2a_messaging::list_contact_roots"));
23505
23505
  const lines = [];
23506
23506
  lines.push(
23507
23507
  contacts.length === 0 ? "No contacts yet." : `Contacts (${contacts.length}):
@@ -23576,7 +23576,7 @@ ${lines.join("\n")}`);
23576
23576
  const { id, err } = boundOr();
23577
23577
  if (err) return err;
23578
23578
  try {
23579
- await mutatingTx(id, "::actor::set_my_bio", { bio });
23579
+ await mutatingTx(id, "::a2a_messaging::set_my_bio", { bio });
23580
23580
  let refreshed = 0;
23581
23581
  if (id.name === rootName) {
23582
23582
  for (const other of identities.values()) {
@@ -23638,7 +23638,7 @@ ${lines.join("\n")}`);
23638
23638
  const { id, err } = boundOr();
23639
23639
  if (err) return err;
23640
23640
  try {
23641
- await mutatingTx(id, "::actor::send_message", { contact, text });
23641
+ await mutatingTx(id, "::a2a_messaging::send_message", { contact, text });
23642
23642
  return textResult(`Message sent to "${contact}".`);
23643
23643
  } catch (e) {
23644
23644
  if (!/Unknown contact/.test(String(e))) {
@@ -23662,7 +23662,7 @@ ${lines.join("\n")}`);
23662
23662
  const { id, err } = boundOr();
23663
23663
  if (err) return err;
23664
23664
  try {
23665
- const data = await mutatingTx(id, "::actor::remove_contact", { contact });
23665
+ const data = await mutatingTx(id, "::a2a_messaging::remove_contact", { contact });
23666
23666
  const name = data.Reduce("removed").Visualize();
23667
23667
  const cid = data.Reduce("container_id").Visualize();
23668
23668
  return textResult(`Removed contact "${name}" (${cid}).`);
@@ -5,14 +5,39 @@
5
5
  // encrypted; the key exchange is handled for us by the stdlib `encrypted_channel`
6
6
  // library — we only ever address peers by their container id.
7
7
  //
8
+ // The shared transaction logic lives in the a2adapt-mufl-core repo (checked
9
+ // out as the core/ subfolder of this directory): `a2a_protocol` (wire shapes +
10
+ // verification), `a2a_messaging` (contact + message wire path, addressed
11
+ // ::a2a_messaging::<name> by the host), and `version`. They are shared with
12
+ // the web messenger — change them there, bump core_version, and recompile
13
+ // every consumer.
14
+ //
15
+ // This packet keeps only what is agent-specific:
16
+ // - the inbox message store with its lifecycle (unread -> processed ->
17
+ // ready_to_delete -> deleted; see get_messages / gc / defer_messages)
18
+ // - the local contact book + identity hierarchy transactions (host-fired)
19
+ // - export_state / import_state wrappers composing the core helpers with
20
+ // the app-side fields (including the legacy-blob migrations)
21
+ // - ::actor:: compat shims for the network-visible inbound transactions
22
+ // (accept_contact, receive_message) — pre-migration peers send to these
23
+ // names, and the core keeps SENDING to them too (Option A: zero network
24
+ // break; drop both only when no old clients remain)
25
+ //
26
+ // Storage is wired into the core via a2a_messaging::init hooks (see the
27
+ // hidden block): on_message_received deposits into the inbox (or the
28
+ // pending-introduction queue for unknown senders); send/remove hooks are
29
+ // agent no-ops.
30
+ //
8
31
  // User transactions (each backs one MCP tool, except gc which the host fires):
9
- // set_my_name — set the display name peers see for me
10
- // set_my_bio — set my profile bio (free-text, self-asserted)
11
- // generate_invite — make a slim personal invite blob for a named peer
12
- // add_contact — join via an invite blob, reply to the inviter
13
- // send_message — send an e2e-encrypted message to a contact
14
- // remove_contact — forget a contact (drops it from my contacts)
15
- // list_contacts — (readonly) my contacts
32
+ // ::a2a_messaging::set_my_name — set the display name peers see for me
33
+ // ::a2a_messaging::set_my_bio — set my profile bio (free-text, self-asserted)
34
+ // ::a2a_messaging::generate_invite — make an invite blob for a named peer
35
+ // ::a2a_messaging::add_contact — join via an invite blob, reply to the inviter
36
+ // ::a2a_messaging::send_message — send an e2e-encrypted message to a contact
37
+ // ::a2a_messaging::remove_contact — forget a contact
38
+ // ::a2a_messaging::list_contacts — (readonly) my contacts
39
+ // ::a2a_messaging::list_contact_roots — (readonly) verified root linkage per contact
40
+ // ::a2a_messaging::get_version — (readonly) shared-core version
16
41
  // list_incoming_messages — (readonly) my inbox, with per-message id + status
17
42
  // get_messages — return unread messages + mark them processed (sole body egress)
18
43
  // defer_messages — flip processed/ready_to_delete messages back to unread
@@ -23,7 +48,6 @@
23
48
  // export_root_profile — root-only in practice: my self-signed root profile blob
24
49
  // set_delegation — role: store my verified delegation cert + root material
25
50
  // describe_identity — (readonly) name/bio/hierarchy position
26
- // list_contact_roots — (readonly) verified root linkage per contact
27
51
  // connect_sibling — register an intra-root peer + send sibling_introduce
28
52
  //
29
53
  // Local contact book (host-fired transactions; see the design notes above
@@ -39,17 +63,10 @@
39
63
  // list_pending_introductions — (readonly) pending introductions (names + queue sizes)
40
64
  //
41
65
  // External transactions (inbound, not exposed as tools):
42
- // accept_contact — inviter learns the joiner's identity + name
43
- // receive_message — store a decrypted inbound message
66
+ // accept_contact — inviter learns the joiner's identity + name (core shim)
67
+ // receive_message — store a decrypted inbound message (core shim)
44
68
  // local_introduce — same-host peer connects via the local contact book
45
69
  // sibling_introduce — intra-root peer connects, authorized by its delegation cert
46
- //
47
- // Naming model (personal invites): generate_invite('Bob') tags a pending invite
48
- // with the peer-name "Bob"; whoever redeems it is registered under "Bob" (the
49
- // inviter's assigned name wins). Generating WITHOUT a name tags the invite with
50
- // "" — then the joiner's self-announced name wins (falling back to its container
51
- // id if the joiner never set one). The joiner, in turn, sees the inviter under
52
- // the inviter's own self-name (set via set_my_name), unless the joiner overrides it.
53
70
 
54
71
  application actor loads libraries
55
72
  identity_proof_document,
@@ -62,25 +79,14 @@ application actor loads libraries
62
79
  key_storage,
63
80
  continuation,
64
81
  encrypted_channel,
65
- current_transaction_info
82
+ current_transaction_info,
83
+ a2a_protocol,
84
+ a2a_messaging,
85
+ version
66
86
  uses transactions
67
87
  {
68
88
  hidden
69
89
  {
70
- metadef contact_t: ($name -> str, $container_id -> global_id).
71
- // Slim invite. Short keys (no field-name bloat), no outer wrapper, and
72
- // only the cryptographically load-bearing parts travel: the inviter's
73
- // signed identity (public keys) + its self-signatures. inviter_id is NOT
74
- // carried separately — it is the identity's own container_id. version is
75
- // a constant reconstructed on the receiver. (See generate_invite /
76
- // add_contact: the embedded identity is kept byte-for-byte so its
77
- // _value_id — what the signatures are over — stays valid.)
78
- // $d invite_id $n inviter_name $c container_id
79
- // $k public keys $a authorizations
80
- // default_keys is NOT carried — the receiver rebuilds it from the keys
81
- // (each key knows its own function + id), so the reconstructed identity is
82
- // byte-identical to the signed one and the self-signatures still verify.
83
- metadef invite_t: ($d -> global_id, $n -> str, $c -> global_id, $k -> key_utils::t_publickey(,), $a -> crypto_signature(,)).
84
90
  // A received message carries a stable per-packet id and a lifecycle status:
85
91
  // "unread" (just arrived) -> "processed" (handed to the agent via
86
92
  // get_messages) -> "ready_to_delete" (first gc tick) -> deleted (next gc
@@ -92,71 +98,13 @@ application actor loads libraries
92
98
  // migrates blobs in this shape forward — see below.
93
99
  metadef legacy_message_t: ($sender_id -> global_id, $sender_name -> str, $text -> str, $date -> time).
94
100
 
95
- // ---- local contact book wire shapes ---------------------------------
96
- // Introduction credential, minted PER CONNECT ATTEMPT by the host's
97
- // registrar packet (never stored in the book). It binds the joiner's
98
- // identity AND address document to one target, with freshness + a nonce,
99
- // so possession of book material alone authorizes nothing: only the
100
- // registrar (whose key never leaves the host) can mint one, which is
101
- // what makes "local" a cryptographic property rather than a convention.
102
- metadef intro_t: (
103
- $version -> int,
104
- $joiner_cid -> global_id,
105
- $joiner_ad_hash -> hash_code,
106
- $target_cid -> global_id,
107
- $iat -> time,
108
- $nonce -> global_id
109
- ).
110
- metadef signed_intro_t: ($i -> intro_t, $s -> crypto_signature).
111
- // What the registrar signs for a contact-book entry (tamper-evidence for
112
- // the host-side book file; verified by the SENDER in connect_local).
113
- metadef book_entry_t: ($version -> int, $name -> str, $ad_hash -> hash_code).
114
101
  // A not-yet-approved local introduction, with its bounded message queue.
102
+ // (The local-book WIRE shapes — intro_t, signed_intro_t, book_entry_t —
103
+ // live in a2a_protocol; these three are packet-local state/view shapes.)
115
104
  metadef pending_msg_t: ($text -> str, $date -> time).
116
105
  metadef pending_intro_t: ($name -> str, $ad -> address_document_types::t_address_document, $messages -> pending_msg_t[]).
117
106
  metadef pending_view_t: ($name -> str, $queued -> int).
118
107
 
119
- // ---- identity hierarchy wire shapes ---------------------------------
120
- // Delegation certificate: "role X belongs to root Y, signed by Y". The
121
- // signature is over the core's _value_id, binding the role's container
122
- // id AND its full key material (the address-document hash) to one root.
123
- // An identity carrying NIL here is a root (or a legacy flat identity) —
124
- // detection is structural, not a flag. v1 revocation == delete the role.
125
- metadef delegation_core_t: (
126
- $version -> int,
127
- $role_cid -> global_id,
128
- $role_ad_hash -> hash_code,
129
- $role_id -> str,
130
- $root_cid -> global_id,
131
- $issued_at -> time
132
- ).
133
- metadef delegation_cert_t: ($c -> delegation_core_t, $s -> crypto_signature).
134
- // Self-signed root profile, carried in role invites so an external peer
135
- // learns WHO is behind the role. It includes the root's key list, so the
136
- // receiver can verify both this signature and the delegation cert with
137
- // no prior knowledge of the root.
138
- metadef root_profile_core_t: (
139
- $version -> int,
140
- $root_cid -> global_id,
141
- $name -> str,
142
- $bio -> str,
143
- $keys -> key_utils::t_publickey(,)
144
- ).
145
- metadef root_profile_t: ($p -> root_profile_core_t, $s -> crypto_signature).
146
- // Verified root linkage learned about a contact (from its role invite or
147
- // a sibling introduction). Kept beside `contacts` so old state blobs
148
- // (whose contact_t has no such fields) import unchanged.
149
- metadef contact_root_t: ($root_cid -> global_id, $root_name -> str, $role_id -> str).
150
- // Role invite: the slim invite plus the delegation chain and the role's
151
- // self-asserted bio. Roots and legacy identities keep emitting the old
152
- // invite_t shape byte-for-byte, so their invites stay redeemable by old
153
- // clients; only role invites require a hierarchy-aware receiver.
154
- metadef invite_role_t: (
155
- $d -> global_id, $n -> str, $c -> global_id,
156
- $k -> key_utils::t_publickey(,), $a -> crypto_signature(,),
157
- $b -> str, $dc -> delegation_cert_t, $rp -> root_profile_t
158
- ).
159
-
160
108
  // Acceptance window for an introduction credential (seconds since mint;
161
109
  // small negative slack for clock oddities) and the matching nonce-table
162
110
  // retention horizon (window + slack, so a nonce outlives its credential).
@@ -171,27 +119,19 @@ application actor loads libraries
171
119
  encrypted_channel::init ($_read_or_abort -> _read_or_abort).
172
120
 
173
121
  // ---- packet state ---------------------------------------------------
174
- // The display name peers see for me (set via set_my_name).
175
- my_name is str = "".
176
- // Known contacts, keyed by their container id.
177
- contacts is (global_id ->> contact_t) = (,).
178
- // Invites I generated, keyed by invite id -> the name I assigned the peer.
179
- pending_invites is (global_id ->> str) = (,).
122
+ // (The shared contact/profile/hierarchy state my_name, contacts,
123
+ // pending_invites, peer_ads, my_bio, delegation_cert, root_ad,
124
+ // root_profile, contact_roots lives in a2a_messaging and is read /
125
+ // assigned as a2a_messaging::<field> below.)
126
+ //
180
127
  // Received messages. Each carries its own lifecycle status (see
181
- // message_t / get_messages / mark_processed / defer_messages), so the
128
+ // message_t / get_messages / gc / defer_messages), so the
182
129
  // packet is the single authority on what has been read or processed —
183
130
  // no host-side cursor, safe across concurrent sessions.
184
131
  inbox is message_t[] = [].
185
132
  // Monotonic source of per-message ids (the stable handle the agent uses
186
133
  // to mark a message processed or defer it).
187
134
  next_msg_seq is int = 1.
188
- // Peer address documents, captured when a contact is established. These are
189
- // self-signed, code-independent, and seed-stable, so on a code upgrade the
190
- // host re-creates this packet from the same seed and import_state replays
191
- // them through address_document::process_address_document — re-registering
192
- // every peer's keys in key_storage so encrypted channels survive the upgrade
193
- // with no re-handshake. Only peer PUBLIC keys travel here, never secrets.
194
- peer_ads is (global_id ->> address_document_types::t_address_document) = (,).
195
135
  // The host registrar's address document (pinned once at identity
196
136
  // creation / injected on upgrade) — its $identity $key_list is what
197
137
  // introduction credentials are verified against. NIL means this identity
@@ -206,18 +146,6 @@ application actor loads libraries
206
146
  // Verified-but-unapproved local introductions (when auto-accept is off),
207
147
  // each with a bounded queue of messages awaiting approval.
208
148
  pending_introductions is (global_id ->> pending_intro_t) = (,).
209
- // ---- identity hierarchy state ----
210
- // My profile bio (free-text, self-asserted; carried in role invites).
211
- my_bio is str = "".
212
- // My delegation cert. NIL == I am a root or a legacy flat identity.
213
- delegation_cert is delegation_cert_t+ = NIL.
214
- // My root's address document (set with the cert; its key list is what
215
- // sibling introductions and my own cert are verified against).
216
- root_ad is address_document_types::t_address_document+ = NIL.
217
- // My root's self-signed profile, embedded in the invites I generate.
218
- root_profile is root_profile_t+ = NIL.
219
- // Verified root linkage per contact, keyed by the contact's container id.
220
- contact_roots is (global_id ->> contact_root_t) = (,).
221
149
 
222
150
  // Signal the host to persist the packet. Only emitted at the end of a
223
151
  // complete procedure — intermediate states (e.g. channel handshake) are
@@ -226,19 +154,6 @@ application actor loads libraries
226
154
  fn _return_data (payload: any) = (transaction::action::return_data ($kind -> $data, $payload -> payload)).
227
155
  fn _notify_agent (payload: any) = (transaction::action::return_data ($kind -> $notify_agent, $payload -> payload)).
228
156
 
229
- // Resolve a contact reference (a display name or stringified container id)
230
- // to a container id; aborts if no contact matches.
231
- fn resolve_contact (ref: str) -> global_id
232
- {
233
- found is global_id+ = NIL.
234
- sc contacts -- (cid -> c) ?? found == NIL && ((c $name) == ref || (_str cid) == ref)
235
- {
236
- found -> cid.
237
- }
238
- abort "Unknown contact: " + ref when found == NIL.
239
- return found?.
240
- }
241
-
242
157
  // Append a message to the inbox under a fresh id; returns the id.
243
158
  fn deposit_message (sender_id: global_id, sender_name: str, text: str, msg_date: time) -> int
244
159
  {
@@ -255,25 +170,6 @@ application actor loads libraries
255
170
  return mid.
256
171
  }
257
172
 
258
- // Verify a delegation chain presented by a peer: the root profile is
259
- // internally consistent and the cert binds the peer's container id AND
260
- // its address document to that root, both signed by the root's keys.
261
- // The chain is self-contained (the profile carries the root's key list),
262
- // so it proves "this role belongs to the root that signed it" — it does
263
- // NOT vouch for who the root is (root verification is deferred to v2).
264
- // Aborts on any mismatch; returns the linkage to record.
265
- fn verify_peer_delegation (peer_cid: global_id, peer_ad_hash: hash_code, cert: delegation_cert_t, rp: root_profile_t) -> contact_root_t
266
- {
267
- abort "Unsupported delegation certificate version." when (cert $c $version) != 1.
268
- abort "Unsupported root profile version." when (rp $p $version) != 1.
269
- abort "Delegation certificate was issued for a different identity." when (cert $c $role_cid) != peer_cid.
270
- abort "Delegation certificate does not match the peer's address document." when (cert $c $role_ad_hash) != peer_ad_hash.
271
- abort "Root profile does not match the delegation certificate's root." when (rp $p $root_cid) != (cert $c $root_cid).
272
- abort "Root profile signature is invalid." when key_storage::check_signature_new_container (_value_id (rp $p)) (rp $s) (rp $p $keys) != TRUE.
273
- abort "Delegation certificate was not signed by its root." when key_storage::check_signature_new_container (_value_id (cert $c)) (cert $s) (rp $p $keys) != TRUE.
274
- return ($root_cid -> cert $c $root_cid, $root_name -> rp $p $name, $role_id -> cert $c $role_id).
275
- }
276
-
277
173
  // Resolve a pending introduction by joiner name or stringified container
278
174
  // id; aborts when nothing matches.
279
175
  fn resolve_pending (ref: str) -> global_id
@@ -286,245 +182,52 @@ application actor loads libraries
286
182
  abort "No pending introduction matches: " + ref when found == NIL.
287
183
  return found?.
288
184
  }
289
- }
290
-
291
- // ---- user transactions --------------------------------------------------
292
-
293
- trn set_my_name _:($name -> name: str)
294
- {
295
- current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
296
- my_name -> name.
297
- return transaction::success [
298
- _return_data ($name -> name),
299
- _save_state NIL
300
- ].
301
- }
302
-
303
- trn set_my_bio _:($bio -> bio: str)
304
- {
305
- current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
306
- my_bio -> bio.
307
- return transaction::success [
308
- _return_data ($bio -> bio),
309
- _save_state NIL
310
- ].
311
- }
312
-
313
- trn generate_invite _:($name -> name: str+)
314
- {
315
- current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
316
-
317
- invite_id = _new_id "a2adapt invite".
318
- // Remember the name I'm assigning to whoever redeems this invite. An
319
- // empty string is the "no assigned name" sentinel: accept_contact then
320
- // registers the joiner under its self-announced name instead.
321
- assigned = (name == NIL ?? "" ; name?).
322
- pending_invites invite_id -> assigned.
323
-
324
- // Carry only my signed identity (public keys) + its self-signatures. The
325
- // joiner rebuilds my address document from these (version is constant, my
326
- // container_id is the identity's own field) and stores it so it can
327
- // re-register me after a code upgrade (see peer_ads / import_state). The
328
- // identity sub-record is passed through untouched so its _value_id — what
329
- // the authorizations sign over — survives the round trip unchanged.
330
- my_ad = address_document::get_my_address_document().
331
- my_identity = my_ad $identity.
332
185
 
333
- // A delegated role embeds its cert + the root's profile (Ring 3 of the
334
- // identity hierarchy: external peers verify the whole chain from the
335
- // invite alone). A root or legacy identity emits the original shape
336
- // byte-for-byte, so those invites stay redeemable by old clients.
337
- if delegation_cert != NIL && root_profile != NIL
338
- {
339
- role_invite is invite_role_t = (
340
- $d -> invite_id,
341
- $n -> my_name,
342
- $c -> my_identity $container_id,
343
- $k -> my_identity $key_list,
344
- $a -> my_ad $authorizations,
345
- $b -> my_bio,
346
- $dc -> delegation_cert?,
347
- $rp -> root_profile?
348
- ).
349
- return transaction::success [
350
- _return_data (
351
- $invite -> (_write role_invite),
352
- $invite_id -> invite_id,
353
- $peer_name -> assigned
354
- ),
355
- _save_state NIL
356
- ].
357
- }
358
-
359
- invite is invite_t = (
360
- $d -> invite_id,
361
- $n -> my_name,
362
- $c -> my_identity $container_id,
363
- $k -> my_identity $key_list,
364
- $a -> my_ad $authorizations
365
- ).
366
-
367
- return transaction::success [
368
- _return_data (
369
- $invite -> (_write invite),
370
- $invite_id -> invite_id,
371
- $peer_name -> assigned
372
- ),
373
- _save_state NIL
374
- ].
375
- }
376
-
377
- trn add_contact _:($invite -> invite_blob: bin, $name -> custom_name: str+)
378
- {
379
- current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
380
-
381
- // Parse field-by-field rather than via one `safe invite_t` cast: the
382
- // shared fields are identical between invite_t and invite_role_t, so
383
- // this one path redeems both the legacy shape and the role shape (the
384
- // hierarchy fields are simply NIL on a legacy invite).
385
- raw = _read_or_abort invite_blob.
386
- inviter_id = (raw $c) safe global_id.
387
- abort "This invite is your own — you cannot add yourself." when inviter_id == _get_container_id().
388
- invite_id = (raw $d) safe global_id.
389
- inviter_name = (raw $n) safe str.
390
- inviter_keys = (raw $k) safe (key_utils::t_publickey(,)).
391
- inviter_auths = (raw $a) safe (crypto_signature(,)).
392
-
393
- // Rebuild default_keys from the carried public keys: each key reports its
394
- // own function and id, so this reproduces the inviter's default-key map
395
- // exactly. With it we reconstruct the full identity (and hence its
396
- // _value_id, which the self-signatures sign over) byte-for-byte, then the
397
- // full address document. import_state later replays this reconstructed
398
- // document through process_address_document to re-register the inviter's
399
- // keys after a code upgrade — so it must validate, and it does.
400
- inviter_default_keys is (key_utils::t_function ->> key_utils::t_key_id) = (,).
401
- sc inviter_keys -- (key -> )
402
- {
403
- inviter_default_keys (key_utils::key_get_function key) -> (_crypto_get_key_id key).
404
- }
405
- inviter_identity is key_storage::t_container_identity = (
406
- $key_list -> inviter_keys,
407
- $default_keys -> inviter_default_keys,
408
- $container_id -> inviter_id
409
- ).
410
- inviter_ad is address_document_types::t_address_document = (
411
- $version -> 1,
412
- $identity -> inviter_identity,
413
- $authorizations -> inviter_auths
186
+ // Wire the agent's storage into the shared messaging core. The receive
187
+ // hook owns everything app-specific about an inbound message: known
188
+ // senders deposit into the inbox; an unknown sender may be a verified-
189
+ // but-unapproved local introduction messaging before approval queue
190
+ // (bounded) inside its pending entry, approval flushes the queue into
191
+ // the inbox in order; anything else from an unknown sender is rejected.
192
+ // The notification deliberately carries NO message body — only that a
193
+ // message arrived, from whom, and its id. The body stays in the packet
194
+ // and only leaves through get_messages.
195
+ a2a_messaging::init (
196
+ $_read_or_abort -> _read_or_abort,
197
+ $on_message_received -> fn (arg: any) -> transaction::action::type[]
198
+ {
199
+ sender_id = (arg $sender_id) safe global_id.
200
+ text = (arg $text) safe str.
201
+ msg_date = (arg $date) safe time.
202
+
203
+ if (arg $sender_name) == NIL
204
+ {
205
+ p = pending_introductions sender_id.
206
+ abort "Message from an unknown sender was rejected." when p == NIL.
207
+ entry = p?.
208
+ queued = entry $messages.
209
+ abort "Pending-introduction message queue is full; awaiting approval." when (_count queued|) >= pending_queue_cap.
210
+ queued (_count queued|) -> ($text -> text, $date -> msg_date).
211
+ pending_introductions sender_id -> ($name -> entry $name, $ad -> entry $ad, $messages -> queued).
212
+ return [
213
+ _notify_agent ($event -> $pending_message, $sender_name -> entry $name, $queued -> _count queued|),
214
+ _save_state NIL
215
+ ].
216
+ }
217
+
218
+ sender_name = (arg $sender_name) safe str.
219
+ mid = deposit_message sender_id sender_name text msg_date.
220
+ return [
221
+ _notify_agent ($event -> $message_received, $sender_name -> sender_name, $msg_id -> mid, $date -> msg_date),
222
+ _save_state NIL
223
+ ].
224
+ },
225
+ $on_message_sent -> fn (_: any) -> transaction::action::type[] { return []. },
226
+ $on_contact_removed -> fn (_: any) -> transaction::action::type[] { return []. }
414
227
  ).
415
-
416
- // A role invite carries a delegation chain — verify it BEFORE anything
417
- // is registered (an invalid chain rejects the whole invite), and record
418
- // the root linkage. A legacy/root invite has no chain; nothing to check.
419
- inviter_root is contact_root_t+ = NIL.
420
- inviter_bio is str = "".
421
- if (raw $dc) != NIL
422
- {
423
- cert = (raw $dc) safe delegation_cert_t.
424
- rp = (raw $rp) safe root_profile_t.
425
- inviter_root -> verify_peer_delegation inviter_id (_value_id inviter_ad) cert rp.
426
- inviter_bio -> (raw $b) safe str.
427
- }
428
-
429
- // Register the inviter under my chosen name, or the name they embedded.
430
- contact_name = (custom_name == NIL ?? inviter_name ; custom_name?).
431
- contacts inviter_id -> ($name -> contact_name, $container_id -> inviter_id).
432
- // Remember the inviter's address document so I can re-register them after
433
- // an upgrade (their keys are seed-stable, so this stays valid).
434
- peer_ads inviter_id -> inviter_ad.
435
- if inviter_root != NIL
436
- {
437
- contact_roots inviter_id -> inviter_root?.
438
- }
439
-
440
- my_self_name = my_name.
441
- my_ad = address_document::get_my_address_document().
442
- // If I am a delegated role myself, carry my own chain in the reply so
443
- // the inviter learns my root linkage symmetrically.
444
- my_cert_blob is bin+ = NIL.
445
- my_rp_blob is bin+ = NIL.
446
- if delegation_cert != NIL && root_profile != NIL
447
- {
448
- my_cert_blob -> (_write delegation_cert?).
449
- my_rp_blob -> (_write root_profile?).
450
- }
451
-
452
- root_name_out is str = "".
453
- role_id_out is str = "".
454
- if inviter_root != NIL
455
- {
456
- root_name_out -> inviter_root? $root_name.
457
- role_id_out -> inviter_root? $role_id.
458
- }
459
-
460
- // Establish the encrypted channel with the inviter (handshake happens
461
- // transparently if we haven't talked before), then tell them I redeemed
462
- // their invite, what my own name is, and my address document, so they can
463
- // finish the handshake and remember me for future upgrades.
464
- return encrypted_channel::execute_transaction inviter_id (fn (_) -> transaction::results::type {
465
- return transaction::success [
466
- encrypted_channel::send_encrypted_tx inviter_id (
467
- $name -> "::actor::accept_contact",
468
- $targ -> (
469
- $invite_id -> invite_id,
470
- $joiner_name -> my_self_name,
471
- $joiner_ad -> my_ad,
472
- $joiner_cert -> my_cert_blob,
473
- $joiner_root_profile -> my_rp_blob
474
- )
475
- ),
476
- _return_data (
477
- $added -> contact_name,
478
- $container_id -> inviter_id,
479
- $root_name -> root_name_out,
480
- $role_id -> role_id_out,
481
- $bio -> inviter_bio
482
- ),
483
- _save_state NIL
484
- ].
485
- }).
486
- }
487
-
488
- trn send_message _:($contact -> contact_ref: str, $text -> text: str)
489
- {
490
- current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
491
-
492
- target_id = resolve_contact contact_ref.
493
-
494
- return encrypted_channel::execute_transaction target_id (fn (_) -> transaction::results::type {
495
- return transaction::success [
496
- encrypted_channel::send_encrypted_tx target_id (
497
- $name -> "::actor::receive_message",
498
- $targ -> ($text -> text)
499
- ),
500
- _return_data ($sent_to -> target_id, $text -> text),
501
- _save_state NIL
502
- ].
503
- }).
504
- }
505
-
506
- trn remove_contact _:($contact -> contact_ref: str)
507
- {
508
- current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
509
-
510
- target_id = resolve_contact contact_ref.
511
- removed = contacts target_id.
512
- removed_name = removed? $name.
513
-
514
- delete contacts target_id.
515
- if peer_ads target_id != NIL { delete peer_ads target_id. }
516
- if contact_roots target_id != NIL { delete contact_roots target_id. }
517
-
518
- return transaction::success [
519
- _return_data ($removed -> removed_name, $container_id -> target_id),
520
- _save_state NIL
521
- ].
522
228
  }
523
229
 
524
- trn readonly list_contacts _
525
- {
526
- return contacts.
527
- }
230
+ // ---- message store --------------------------------------------------------
528
231
 
529
232
  trn readonly list_incoming_messages _
530
233
  {
@@ -720,7 +423,7 @@ application actor loads libraries
720
423
 
721
424
  joiner_ad = (_read_or_abort joiner_ad_blob) safe address_document_types::t_address_document.
722
425
  target_ad = (_read_or_abort target_ad_blob) safe address_document_types::t_address_document.
723
- intro is intro_t = (
426
+ intro is a2a_protocol::intro_t = (
724
427
  $version -> 1,
725
428
  $joiner_cid -> joiner_ad $identity $container_id,
726
429
  $joiner_ad_hash -> _value_id joiner_ad,
@@ -728,7 +431,7 @@ application actor loads libraries
728
431
  $iat -> (current_transaction_info::get_transaction_time())?,
729
432
  $nonce -> _new_id "a2adapt local introduction"
730
433
  ).
731
- signed is signed_intro_t = ($i -> intro, $s -> key_storage::default_sign (_value_id intro)).
434
+ signed is a2a_protocol::signed_intro_t = ($i -> intro, $s -> key_storage::default_sign (_value_id intro)).
732
435
  return transaction::success [
733
436
  _return_data ($intro -> (_write signed))
734
437
  ].
@@ -742,7 +445,7 @@ application actor loads libraries
742
445
  current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
743
446
 
744
447
  ad = (_read_or_abort ad_blob) safe address_document_types::t_address_document.
745
- entry is book_entry_t = ($version -> 1, $name -> name, $ad_hash -> _value_id ad).
448
+ entry is a2a_protocol::book_entry_t = ($version -> 1, $name -> name, $ad_hash -> _value_id ad).
746
449
  return transaction::success [
747
450
  _return_data ($sig -> (_write (key_storage::default_sign (_value_id entry))))
748
451
  ].
@@ -763,14 +466,14 @@ application actor loads libraries
763
466
  target_id = target_ad $identity $container_id.
764
467
  abort "This contact-book entry is your own identity." when target_id == _get_container_id().
765
468
 
766
- entry is book_entry_t = ($version -> 1, $name -> name, $ad_hash -> _value_id target_ad).
469
+ entry is a2a_protocol::book_entry_t = ($version -> 1, $name -> name, $ad_hash -> _value_id target_ad).
767
470
  entry_sig = (_read_or_abort entry_sig_blob) safe crypto_signature.
768
471
  abort "Contact-book entry failed registrar verification." when key_storage::check_signature_new_container (_value_id entry) entry_sig (registrar_ad? $identity $key_list) != TRUE.
769
472
 
770
- contacts target_id -> ($name -> name, $container_id -> target_id).
771
- peer_ads target_id -> target_ad.
473
+ a2a_messaging::contacts target_id -> ($name -> name, $container_id -> target_id).
474
+ a2a_messaging::peer_ads target_id -> target_ad.
772
475
 
773
- my_self_name = my_name.
476
+ my_self_name = a2a_messaging::my_name.
774
477
  my_ad = address_document::get_my_address_document().
775
478
  return encrypted_channel::execute_transaction target_id (fn (_) -> transaction::results::type {
776
479
  return transaction::success [
@@ -790,8 +493,8 @@ application actor loads libraries
790
493
 
791
494
  pid = resolve_pending ref.
792
495
  entry = (pending_introductions pid)?.
793
- contacts pid -> ($name -> entry $name, $container_id -> pid).
794
- peer_ads pid -> entry $ad.
496
+ a2a_messaging::contacts pid -> ($name -> entry $name, $container_id -> pid).
497
+ a2a_messaging::peer_ads pid -> entry $ad.
795
498
 
796
499
  queued = entry $messages.
797
500
  flushed is int = 0.
@@ -850,13 +553,13 @@ application actor loads libraries
850
553
  trn sign_delegation _:($role_ad -> role_ad_blob: bin, $role_id -> role_id: str)
851
554
  {
852
555
  current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
853
- abort "Only a root identity can sign delegation certificates." when delegation_cert != NIL.
556
+ abort "Only a root identity can sign delegation certificates." when a2a_messaging::delegation_cert != NIL.
854
557
 
855
558
  role_ad = (_read_or_abort role_ad_blob) safe address_document_types::t_address_document.
856
559
  role_cid = role_ad $identity $container_id.
857
560
  abort "Cannot issue a delegation certificate to myself." when role_cid == _get_container_id().
858
561
 
859
- core is delegation_core_t = (
562
+ core is a2a_protocol::delegation_core_t = (
860
563
  $version -> 1,
861
564
  $role_cid -> role_cid,
862
565
  $role_ad_hash -> _value_id role_ad,
@@ -864,7 +567,7 @@ application actor loads libraries
864
567
  $root_cid -> _get_container_id(),
865
568
  $issued_at -> (current_transaction_info::get_transaction_time())?
866
569
  ).
867
- cert is delegation_cert_t = ($c -> core, $s -> key_storage::default_sign (_value_id core)).
570
+ cert is a2a_protocol::delegation_cert_t = ($c -> core, $s -> key_storage::default_sign (_value_id core)).
868
571
  return transaction::success [
869
572
  _return_data ($cert -> (_write cert))
870
573
  ].
@@ -876,17 +579,17 @@ application actor loads libraries
876
579
  trn export_root_profile _
877
580
  {
878
581
  current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
879
- abort "Only a root identity can export a root profile." when delegation_cert != NIL.
582
+ abort "Only a root identity can export a root profile." when a2a_messaging::delegation_cert != NIL.
880
583
 
881
584
  my_ad = address_document::get_my_address_document().
882
- core is root_profile_core_t = (
585
+ core is a2a_protocol::root_profile_core_t = (
883
586
  $version -> 1,
884
587
  $root_cid -> _get_container_id(),
885
- $name -> my_name,
886
- $bio -> my_bio,
588
+ $name -> a2a_messaging::my_name,
589
+ $bio -> a2a_messaging::my_bio,
887
590
  $keys -> my_ad $identity $key_list
888
591
  ).
889
- profile is root_profile_t = ($p -> core, $s -> key_storage::default_sign (_value_id core)).
592
+ profile is a2a_protocol::root_profile_t = ($p -> core, $s -> key_storage::default_sign (_value_id core)).
890
593
  return transaction::success [
891
594
  _return_data ($profile -> (_write profile))
892
595
  ].
@@ -901,9 +604,9 @@ application actor loads libraries
901
604
  {
902
605
  current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
903
606
 
904
- cert = (_read_or_abort cert_blob) safe delegation_cert_t.
607
+ cert = (_read_or_abort cert_blob) safe a2a_protocol::delegation_cert_t.
905
608
  new_root_ad = (_read_or_abort root_ad_blob) safe address_document_types::t_address_document.
906
- rp = (_read_or_abort rp_blob) safe root_profile_t.
609
+ rp = (_read_or_abort rp_blob) safe a2a_protocol::root_profile_t.
907
610
 
908
611
  abort "Unsupported delegation certificate version." when (cert $c $version) != 1.
909
612
  abort "This delegation certificate was issued to a different identity." when (cert $c $role_cid) != _get_container_id().
@@ -916,9 +619,9 @@ application actor loads libraries
916
619
  abort "The root profile's key list does not match the root's address document." when (_value_id (rp $p $keys)) != (_value_id (new_root_ad $identity $key_list)).
917
620
  abort "The root profile signature is invalid." when key_storage::check_signature_new_container (_value_id (rp $p)) (rp $s) (new_root_ad $identity $key_list) != TRUE.
918
621
 
919
- delegation_cert -> cert.
920
- root_ad -> new_root_ad.
921
- root_profile -> rp.
622
+ a2a_messaging::delegation_cert -> cert.
623
+ a2a_messaging::root_ad -> new_root_ad.
624
+ a2a_messaging::root_profile -> rp.
922
625
 
923
626
  return transaction::success [
924
627
  _return_data ($delegated -> TRUE, $root_cid -> (_str (cert $c $root_cid)), $role_id -> cert $c $role_id),
@@ -928,16 +631,16 @@ application actor loads libraries
928
631
 
929
632
  trn readonly describe_identity _
930
633
  {
931
- if delegation_cert == NIL
634
+ if a2a_messaging::delegation_cert == NIL
932
635
  {
933
- return ($name -> my_name, $bio -> my_bio, $has_cert -> FALSE, $role_id -> "", $root_cid -> "", $root_name -> "").
636
+ return ($name -> a2a_messaging::my_name, $bio -> a2a_messaging::my_bio, $has_cert -> FALSE, $role_id -> "", $root_cid -> "", $root_name -> "").
934
637
  }
935
- cert = delegation_cert?.
638
+ cert = a2a_messaging::delegation_cert?.
936
639
  rname is str = "".
937
- if root_profile != NIL { rname -> root_profile? $p $name. }
640
+ if a2a_messaging::root_profile != NIL { rname -> a2a_messaging::root_profile? $p $name. }
938
641
  return (
939
- $name -> my_name,
940
- $bio -> my_bio,
642
+ $name -> a2a_messaging::my_name,
643
+ $bio -> a2a_messaging::my_bio,
941
644
  $has_cert -> TRUE,
942
645
  $role_id -> cert $c $role_id,
943
646
  $root_cid -> (_str (cert $c $root_cid)),
@@ -945,11 +648,6 @@ application actor loads libraries
945
648
  ).
946
649
  }
947
650
 
948
- trn readonly list_contact_roots _
949
- {
950
- return contact_roots.
951
- }
952
-
953
651
  // Connect to an intra-root sibling (Ring 1): register it as a contact and
954
652
  // introduce myself over the encrypted channel, presenting my delegation
955
653
  // cert (NIL when I am the root itself — the channel proves I control the
@@ -965,22 +663,22 @@ application actor loads libraries
965
663
  abort "This sibling is your own identity." when target_id == _get_container_id().
966
664
 
967
665
  cert_blob is bin+ = NIL.
968
- if delegation_cert != NIL { cert_blob -> (_write delegation_cert?). }
666
+ if a2a_messaging::delegation_cert != NIL { cert_blob -> (_write a2a_messaging::delegation_cert?). }
969
667
 
970
- contacts target_id -> ($name -> name, $container_id -> target_id).
971
- peer_ads target_id -> target_ad.
668
+ a2a_messaging::contacts target_id -> ($name -> name, $container_id -> target_id).
669
+ a2a_messaging::peer_ads target_id -> target_ad.
972
670
  // Record the target's root linkage: a sibling shares my root by
973
671
  // definition (the receiving side verifies the converse independently).
974
- if delegation_cert != NIL && root_profile != NIL
672
+ if a2a_messaging::delegation_cert != NIL && a2a_messaging::root_profile != NIL
975
673
  {
976
- contact_roots target_id -> ($root_cid -> delegation_cert? $c $root_cid, $root_name -> root_profile? $p $name, $role_id -> name).
674
+ a2a_messaging::contact_roots target_id -> ($root_cid -> a2a_messaging::delegation_cert? $c $root_cid, $root_name -> a2a_messaging::root_profile? $p $name, $role_id -> name).
977
675
  }
978
676
  else
979
677
  {
980
- contact_roots target_id -> ($root_cid -> _get_container_id(), $root_name -> my_name, $role_id -> name).
678
+ a2a_messaging::contact_roots target_id -> ($root_cid -> _get_container_id(), $root_name -> a2a_messaging::my_name, $role_id -> name).
981
679
  }
982
680
 
983
- my_self_name = my_name.
681
+ my_self_name = a2a_messaging::my_name.
984
682
  my_ad = address_document::get_my_address_document().
985
683
  return encrypted_channel::execute_transaction target_id (fn (_) -> transaction::results::type {
986
684
  return transaction::success [
@@ -1002,27 +700,32 @@ application actor loads libraries
1002
700
  //
1003
701
  // The packet-level snapshot is NOT used for upgrades: it is bound to the unit
1004
702
  // code hash, so a new .muflo cannot load an old snapshot. This data blob is.
703
+ //
704
+ // The blob stays FLAT with the historical field names — the core fields come
705
+ // from a2a_messaging::export_core_state / import_core_state, the app fields
706
+ // are composed in here — so a PRE-migration export imports unchanged.
1005
707
 
1006
708
  trn readonly export_state _
1007
709
  {
710
+ core_state = a2a_messaging::export_core_state NIL.
1008
711
  return (
1009
- $my_name -> my_name,
1010
- $contacts -> contacts,
1011
- $pending_invites -> pending_invites,
712
+ $my_name -> core_state $my_name,
713
+ $contacts -> core_state $contacts,
714
+ $pending_invites -> core_state $pending_invites,
1012
715
  $inbox -> inbox,
1013
716
  $next_msg_seq -> next_msg_seq,
1014
- $peer_ads -> peer_ads,
717
+ $peer_ads -> core_state $peer_ads,
1015
718
  $registrar_ad -> registrar_ad,
1016
719
  $local_auto_accept -> local_auto_accept,
1017
720
  // Nonces are exported so a restart does not reopen the replay window
1018
721
  // for still-fresh credentials; stale ones are purged lazily anyway.
1019
722
  $seen_nonces -> seen_nonces,
1020
723
  $pending_introductions -> pending_introductions,
1021
- $my_bio -> my_bio,
1022
- $delegation_cert -> delegation_cert,
1023
- $root_ad -> root_ad,
1024
- $root_profile -> root_profile,
1025
- $contact_roots -> contact_roots
724
+ $my_bio -> core_state $my_bio,
725
+ $delegation_cert -> core_state $delegation_cert,
726
+ $root_ad -> core_state $root_ad,
727
+ $root_profile -> core_state $root_profile,
728
+ $contact_roots -> core_state $contact_roots
1026
729
  ).
1027
730
  }
1028
731
 
@@ -1030,12 +733,11 @@ application actor loads libraries
1030
733
  {
1031
734
  current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
1032
735
 
1033
- // The fields that did not change across the schema bump are validated the
1034
- // same way for any version of the blob.
1035
- my_name -> (data $my_name) safe str.
1036
- contacts -> (data $contacts) safe (global_id ->> contact_t).
1037
- pending_invites -> (data $pending_invites) safe (global_id ->> str).
1038
- peer_ads -> (data $peer_ads) safe (global_id ->> address_document_types::t_address_document).
736
+ // Core fields (contacts/profile/hierarchy) validated and restored by
737
+ // the shared library, which also replays every peer's address document
738
+ // through process_address_document so encrypted channels keep working
739
+ // after the upgrade with no re-handshake.
740
+ a2a_messaging::import_core_state data.
1039
741
 
1040
742
  // The inbox + next_msg_seq are the only parts the message-lifecycle changes
1041
743
  // touched. A pre-lifecycle blob has no $next_msg_seq and inbox entries with
@@ -1106,36 +808,6 @@ application actor loads libraries
1106
808
  pending_introductions -> (data $pending_introductions) safe (global_id ->> pending_intro_t).
1107
809
  }
1108
810
 
1109
- // Identity-hierarchy state arrived after the local-contact-book schema —
1110
- // same pattern: every field is optional, defaults stay when absent.
1111
- if (data $my_bio) != NIL
1112
- {
1113
- my_bio -> (data $my_bio) safe str.
1114
- }
1115
- if (data $delegation_cert) != NIL
1116
- {
1117
- delegation_cert -> (data $delegation_cert) safe delegation_cert_t.
1118
- }
1119
- if (data $root_ad) != NIL
1120
- {
1121
- root_ad -> (data $root_ad) safe address_document_types::t_address_document.
1122
- }
1123
- if (data $root_profile) != NIL
1124
- {
1125
- root_profile -> (data $root_profile) safe root_profile_t.
1126
- }
1127
- if (data $contact_roots) != NIL
1128
- {
1129
- contact_roots -> (data $contact_roots) safe (global_id ->> contact_root_t).
1130
- }
1131
-
1132
- // Re-register every peer's keys so encrypted channels keep working after
1133
- // the upgrade — no handshake needed (my own keys are unchanged, and the
1134
- // peers' self-signed address documents re-authorize on this fresh packet).
1135
- sc peer_ads -- ( -> ad)
1136
- {
1137
- address_document::process_address_document ad TRUE.
1138
- }
1139
811
  // Pending introducers' keys too: their channel to me predates approval,
1140
812
  // so it must survive an upgrade exactly like an approved contact's.
1141
813
  sc pending_introductions -- ( -> p)
@@ -1144,103 +816,24 @@ application actor loads libraries
1144
816
  }
1145
817
 
1146
818
  return transaction::success [
1147
- _return_data ($imported -> TRUE, $contacts -> _count contacts|, $peers -> _count peer_ads|),
819
+ _return_data ($imported -> TRUE, $contacts -> _count a2a_messaging::contacts|, $peers -> _count a2a_messaging::peer_ads|),
1148
820
  _save_state NIL
1149
821
  ].
1150
822
  }
1151
823
 
1152
824
  // ---- external (inbound) transactions ------------------------------------
1153
825
 
1154
- // Args are taken as `any` (not a destructured shape) so old clients — whose
1155
- // accept_contact payload has no hierarchy fields keep working unchanged.
826
+ // Compat shims (Option A): pre-migration peers address these as ::actor::*,
827
+ // and the core keeps SENDING to the ::actor:: names too. Each delegates to
828
+ // the shared handler — remove only when no old clients remain.
1156
829
  trn accept_contact args: any
1157
830
  {
1158
- current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::external,).
1159
- encrypted_channel::check_encrypted_or_abort().
1160
-
1161
- sender_id = current_transaction_info::get_external_envelope_or_abort() $from.
1162
- invite_id = (args $invite_id) safe global_id.
1163
- joiner_ad = (args $joiner_ad) safe address_document_types::t_address_document.
1164
-
1165
- // Only an invite I generated (and have not yet consumed) authorizes a
1166
- // contact registration. Without this gate any invite blob would be a
1167
- // multi-use bearer credential: anyone who ever saw one could register
1168
- // themselves as my contact with a self-chosen name.
1169
- assigned_name = pending_invites invite_id.
1170
- abort "Unknown or already-redeemed invite." when assigned_name == NIL.
1171
- // An empty assigned name means the invite was generated without one:
1172
- // the joiner's self-announced name wins (container id as a last resort
1173
- // when the joiner never set a name either).
1174
- contact_name is str = assigned_name?.
1175
- if contact_name == ""
1176
- {
1177
- joiner_self = (args $joiner_name) safe str.
1178
- contact_name -> (joiner_self == "" ?? (_str sender_id) ; joiner_self).
1179
- }
1180
-
1181
- // A delegated-role joiner carries its chain so I learn its root linkage
1182
- // symmetrically; an invalid chain rejects the redemption outright.
1183
- joiner_root is contact_root_t+ = NIL.
1184
- if (args $joiner_cert) != NIL
1185
- {
1186
- cert = (_read_or_abort ((args $joiner_cert) safe bin)) safe delegation_cert_t.
1187
- rp = (_read_or_abort ((args $joiner_root_profile) safe bin)) safe root_profile_t.
1188
- joiner_root -> verify_peer_delegation sender_id (_value_id joiner_ad) cert rp.
1189
- }
1190
-
1191
- contacts sender_id -> ($name -> contact_name, $container_id -> sender_id).
1192
- // Remember the joiner's address document for upgrade-time re-registration.
1193
- peer_ads sender_id -> joiner_ad.
1194
- if joiner_root != NIL
1195
- {
1196
- contact_roots sender_id -> joiner_root?.
1197
- }
1198
- delete pending_invites invite_id.
1199
-
1200
- return transaction::success [
1201
- _notify_agent ($event -> $contact_accepted, $name -> contact_name, $container_id -> sender_id),
1202
- _save_state NIL
1203
- ].
831
+ return a2a_messaging::handle_accept_contact args.
1204
832
  }
1205
833
 
1206
834
  trn receive_message _:($text -> text: str)
1207
835
  {
1208
- current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::external,).
1209
- encrypted_channel::check_encrypted_or_abort().
1210
-
1211
- sender_id = current_transaction_info::get_external_envelope_or_abort() $from.
1212
- msg_date = (current_transaction_info::get_transaction_time())?.
1213
- sender = contacts sender_id.
1214
-
1215
- if sender == NIL
1216
- {
1217
- // A verified-but-unapproved local introduction may message me before
1218
- // approval: queue (bounded) inside its pending entry. Approval
1219
- // flushes the queue into the inbox in order; anything else from an
1220
- // unknown sender is rejected as before.
1221
- p = pending_introductions sender_id.
1222
- abort "Message from an unknown sender was rejected." when p == NIL.
1223
- entry = p?.
1224
- queued = entry $messages.
1225
- abort "Pending-introduction message queue is full; awaiting approval." when (_count queued|) >= pending_queue_cap.
1226
- queued (_count queued|) -> ($text -> text, $date -> msg_date).
1227
- pending_introductions sender_id -> ($name -> entry $name, $ad -> entry $ad, $messages -> queued).
1228
- return transaction::success [
1229
- _notify_agent ($event -> $pending_message, $sender_name -> entry $name, $queued -> _count queued|),
1230
- _save_state NIL
1231
- ].
1232
- }
1233
-
1234
- sender_name = sender? $name.
1235
- mid = deposit_message sender_id sender_name text msg_date.
1236
-
1237
- // The notification deliberately carries NO message body — only that a
1238
- // message arrived, from whom, and its id. The body stays in the packet
1239
- // and only leaves through get_messages.
1240
- return transaction::success [
1241
- _notify_agent ($event -> $message_received, $sender_name -> sender_name, $msg_id -> mid, $date -> msg_date),
1242
- _save_state NIL
1243
- ].
836
+ return a2a_messaging::handle_receive_message text.
1244
837
  }
1245
838
 
1246
839
  // A same-host peer connects via the local contact book. The credential must
@@ -1263,7 +856,7 @@ application actor loads libraries
1263
856
  sender_id = current_transaction_info::get_external_envelope_or_abort() $from.
1264
857
  abort "This identity does not accept local-contact-book introductions." when registrar_ad == NIL.
1265
858
 
1266
- signed = (_read_or_abort intro_blob) safe signed_intro_t.
859
+ signed = (_read_or_abort intro_blob) safe a2a_protocol::signed_intro_t.
1267
860
  intro = signed $i.
1268
861
  abort "Unsupported introduction credential version." when (intro $version) != 1.
1269
862
  abort "Introduction credential was not signed by this host's registrar." when key_storage::check_signature_new_container (_value_id intro) (signed $s) (registrar_ad? $identity $key_list) != TRUE.
@@ -1289,12 +882,12 @@ application actor loads libraries
1289
882
  abort "Too many concurrent introductions; try again shortly." when (_count seen_nonces|) >= seen_nonce_cap.
1290
883
  seen_nonces (intro $nonce) -> now.
1291
884
 
1292
- existing = contacts sender_id.
885
+ existing = a2a_messaging::contacts sender_id.
1293
886
  if existing != NIL
1294
887
  {
1295
888
  // Already a contact (idempotent re-introduction): keep my assigned
1296
889
  // name, refresh the stored address document, deliver any payload.
1297
- peer_ads sender_id -> joiner_ad.
890
+ a2a_messaging::peer_ads sender_id -> joiner_ad.
1298
891
  if text != NIL
1299
892
  {
1300
893
  mid = deposit_message sender_id (existing? $name) text? now.
@@ -1308,8 +901,8 @@ application actor loads libraries
1308
901
 
1309
902
  if local_auto_accept
1310
903
  {
1311
- contacts sender_id -> ($name -> joiner_name, $container_id -> sender_id).
1312
- peer_ads sender_id -> joiner_ad.
904
+ a2a_messaging::contacts sender_id -> ($name -> joiner_name, $container_id -> sender_id).
905
+ a2a_messaging::peer_ads sender_id -> joiner_ad.
1313
906
  if text != NIL
1314
907
  {
1315
908
  mid = deposit_message sender_id joiner_name text? now.
@@ -1356,30 +949,30 @@ application actor loads libraries
1356
949
  sender_id = current_transaction_info::get_external_envelope_or_abort() $from.
1357
950
  now = (current_transaction_info::get_transaction_time())?.
1358
951
 
1359
- link is contact_root_t+ = NIL.
952
+ link is a2a_protocol::contact_root_t+ = NIL.
1360
953
  if cert_blob == NIL
1361
954
  {
1362
955
  // Sender claims to be my root.
1363
- abort "A certificate-less sibling introduction is only accepted from my root." when delegation_cert == NIL.
1364
- abort "Sender is not my root." when sender_id != (delegation_cert? $c $root_cid).
956
+ abort "A certificate-less sibling introduction is only accepted from my root." when a2a_messaging::delegation_cert == NIL.
957
+ abort "Sender is not my root." when sender_id != (a2a_messaging::delegation_cert? $c $root_cid).
1365
958
  link -> ($root_cid -> sender_id, $root_name -> joiner_name, $role_id -> "").
1366
959
  }
1367
960
  else
1368
961
  {
1369
- cert = (_read_or_abort cert_blob?) safe delegation_cert_t.
962
+ cert = (_read_or_abort cert_blob?) safe a2a_protocol::delegation_cert_t.
1370
963
  abort "Unsupported delegation certificate version." when (cert $c $version) != 1.
1371
964
  abort "Sibling certificate was issued for a different sender." when (cert $c $role_cid) != sender_id.
1372
965
  abort "Sibling certificate does not match the sender's address document." when (cert $c $role_ad_hash) != (_value_id joiner_ad).
1373
966
 
1374
967
  root_name_known is str = "".
1375
- if delegation_cert != NIL
968
+ if a2a_messaging::delegation_cert != NIL
1376
969
  {
1377
970
  // I am a role: the cert must name MY root and verify against the
1378
971
  // root key material pinned at delegation time.
1379
- abort "My root material is missing — cannot verify sibling certificates." when root_ad == NIL.
1380
- abort "Sibling certificate names a different root." when (cert $c $root_cid) != (delegation_cert? $c $root_cid).
1381
- abort "Sibling certificate was not signed by my root." when key_storage::check_signature_new_container (_value_id (cert $c)) (cert $s) (root_ad? $identity $key_list) != TRUE.
1382
- if root_profile != NIL { root_name_known -> root_profile? $p $name. }
972
+ abort "My root material is missing — cannot verify sibling certificates." when a2a_messaging::root_ad == NIL.
973
+ abort "Sibling certificate names a different root." when (cert $c $root_cid) != (a2a_messaging::delegation_cert? $c $root_cid).
974
+ abort "Sibling certificate was not signed by my root." when key_storage::check_signature_new_container (_value_id (cert $c)) (cert $s) (a2a_messaging::root_ad? $identity $key_list) != TRUE.
975
+ if a2a_messaging::root_profile != NIL { root_name_known -> a2a_messaging::root_profile? $p $name. }
1383
976
  }
1384
977
  else
1385
978
  {
@@ -1388,18 +981,18 @@ application actor loads libraries
1388
981
  abort "Sibling certificate names a different root." when (cert $c $root_cid) != _get_container_id().
1389
982
  my_ad = address_document::get_my_address_document().
1390
983
  abort "Sibling certificate was not signed by me." when key_storage::check_signature_new_container (_value_id (cert $c)) (cert $s) (my_ad $identity $key_list) != TRUE.
1391
- root_name_known -> my_name.
984
+ root_name_known -> a2a_messaging::my_name.
1392
985
  }
1393
986
  link -> ($root_cid -> cert $c $root_cid, $root_name -> root_name_known, $role_id -> cert $c $role_id).
1394
987
  }
1395
988
 
1396
- existing = contacts sender_id.
989
+ existing = a2a_messaging::contacts sender_id.
1397
990
  if existing != NIL
1398
991
  {
1399
992
  // Already a contact (idempotent re-introduction): keep my assigned
1400
993
  // name, refresh the stored material, deliver any payload.
1401
- peer_ads sender_id -> joiner_ad.
1402
- contact_roots sender_id -> link?.
994
+ a2a_messaging::peer_ads sender_id -> joiner_ad.
995
+ a2a_messaging::contact_roots sender_id -> link?.
1403
996
  if text != NIL
1404
997
  {
1405
998
  mid = deposit_message sender_id (existing? $name) text? now.
@@ -1411,9 +1004,9 @@ application actor loads libraries
1411
1004
  return transaction::success [ _save_state NIL ].
1412
1005
  }
1413
1006
 
1414
- contacts sender_id -> ($name -> joiner_name, $container_id -> sender_id).
1415
- peer_ads sender_id -> joiner_ad.
1416
- contact_roots sender_id -> link?.
1007
+ a2a_messaging::contacts sender_id -> ($name -> joiner_name, $container_id -> sender_id).
1008
+ a2a_messaging::peer_ads sender_id -> joiner_ad.
1009
+ a2a_messaging::contact_roots sender_id -> link?.
1417
1010
  if text != NIL
1418
1011
  {
1419
1012
  mid = deposit_message sender_id joiner_name text? now.
@@ -1,20 +1,25 @@
1
1
  // a2adapt messenger packet — compile configuration.
2
2
  //
3
- // Pulls in the full ADAPT standard library so `actor.mu` can `loads libraries`
4
- // the crypto / identity / transport modules by name. No app-private libraries
5
- // (everything we need lives in the stdlib, including `encrypted_channel`, which
6
- // does the peer key-exchange for us).
3
+ // Pulls in the full ADAPT standard library plus the shared a2adapt mufl core
4
+ // (the a2adapt-mufl-core repo, checked out as the core/ subfolder) so
5
+ // `actor.mu` can `loads libraries` the crypto / identity / transport modules
6
+ // and the shared protocol libraries (`a2a_protocol`, `version`) by name.
7
7
  //
8
- // Compile:
9
- // MUFL_STDLIB_PATH=<adapt-toolkit>/mufl_stdlib \
10
- // mufl-compile -mp <adapt-toolkit>/meta -mp <adapt-toolkit>/transactions -d-c actor.mu
11
- // -> emits <content-hash>.muflo in the cwd.
8
+ // Compile: scripts/compile-mufl.sh (copies actor.mu, this file, and core/
9
+ // into a temp dir and runs mufl-compile there).
12
10
 
13
11
  config script
14
12
  {
13
+ stdlib_config = (config_load #$MUFL_STDLIB_PATH).
14
+ core_config = (config_load #"core").
15
+
15
16
  (
16
- $imports -> ((config_load #$MUFL_STDLIB_PATH) $exports),
17
- $exports -> (
17
+ $imports ->
18
+ (
19
+ $libraries -> (stdlib_config $exports $libraries)'(core_config $exports $libraries),
20
+ ),
21
+ $exports ->
22
+ (
18
23
  $libraries -> (,),
19
24
  $applications -> (,)
20
25
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adapt-toolkit/a2adapt",
3
- "version": "0.11.2",
3
+ "version": "0.11.4",
4
4
  "description": "MCP server daemon for a2adapt — one native ADAPT wrapper hosting N self-sovereign identities, exposing secure agent-to-agent messaging tools over HTTP (Streamable HTTP). Run `a2adapt-mcp start`.",
5
5
  "type": "module",
6
6
  "license": "MIT",