@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,564 @@
|
|
|
1
|
+
// Self-contained test actor for the core-3.0 ephemeral-invite suite
|
|
2
|
+
// (T/V/RC-series; the a2a_notifications N-series moved to notif_actor.mu).
|
|
3
|
+
//
|
|
4
|
+
// DERIVED locally for the core repo — it does NOT vendor any consumer/daemon
|
|
5
|
+
// source. It loads only the shared core libraries this suite exercises
|
|
6
|
+
// (a2a_protocol + a2a_messaging + their stdlib deps) and provides the MINIMUM
|
|
7
|
+
// host wiring a packet needs: the storage hooks a2a_messaging::init requires, a
|
|
8
|
+
// tiny inbox, the identity-hierarchy helper trns (so the role scenario can mint
|
|
9
|
+
// a delegation chain), export/import wrappers (migration scenario), and the
|
|
10
|
+
// `qa_*` probe trns the driver uses to inject adversarial inputs and read state.
|
|
11
|
+
//
|
|
12
|
+
// It does NOT load a2a_notifications: the compiler bounds meta-stage type-level
|
|
13
|
+
// reduction PER COMPILED UNIT (adapt src/eval/meta_reduction_fuel.h, 1M steps).
|
|
14
|
+
// Loading BOTH a2a_messaging AND a2a_notifications, after the 0.9.0 migration
|
|
15
|
+
// surface landed in a2a_messaging, tips this unit OVER the ceiling. a2a_messaging
|
|
16
|
+
// has ZERO references to a2a_notifications (the dep is one-way), so the
|
|
17
|
+
// notification tests split cleanly into notif_actor.mu (which loads both libs and
|
|
18
|
+
// keeps the COMBINED core+notify export/import round-trip). This unit keeps only
|
|
19
|
+
// the core-half export/import round-trip.
|
|
20
|
+
//
|
|
21
|
+
// It does NOT load a2a_control / a2a_cluster / a2a_monitoring / a2a_capabilities-
|
|
22
|
+
// init: the invite redeem flow, send_message, the hierarchy chain, and
|
|
23
|
+
// export/import do not depend on them. The redeem transactions themselves
|
|
24
|
+
// (generate_invite / add_contact / submit_invite_response / complete_invite) live
|
|
25
|
+
// in a2a_messaging and are library-routed, so no ::actor:: shim is needed.
|
|
26
|
+
|
|
27
|
+
application actor loads libraries
|
|
28
|
+
identity_proof_document,
|
|
29
|
+
attestation_document,
|
|
30
|
+
native_attestation_document,
|
|
31
|
+
transaction_message_decoder,
|
|
32
|
+
address_document,
|
|
33
|
+
address_document_types,
|
|
34
|
+
key_utils,
|
|
35
|
+
key_storage,
|
|
36
|
+
continuation,
|
|
37
|
+
encrypted_channel,
|
|
38
|
+
a2a_versions,
|
|
39
|
+
a2a_capabilities,
|
|
40
|
+
a2a_protocol,
|
|
41
|
+
a2a_messaging,
|
|
42
|
+
current_transaction_info,
|
|
43
|
+
protocol_container,
|
|
44
|
+
version
|
|
45
|
+
uses transactions
|
|
46
|
+
{
|
|
47
|
+
hidden
|
|
48
|
+
{
|
|
49
|
+
// Minimal inbox (the app owns message storage; the core calls the hook).
|
|
50
|
+
metadef msg_t: ($sender -> global_id, $text -> str, $wire_id -> str, $reply_wire -> str).
|
|
51
|
+
inbox is msg_t[] = [].
|
|
52
|
+
|
|
53
|
+
// Minimal file store (the app owns file storage; the core calls the hook).
|
|
54
|
+
metadef file_t: ($sender -> global_id, $filename -> str, $mime -> str, $wire_id -> str, $reply_wire -> str).
|
|
55
|
+
files is file_t[] = [].
|
|
56
|
+
|
|
57
|
+
// Wire the deserialization primitive into the libraries that need it.
|
|
58
|
+
_read_or_abort = grab( _read_or_abort ).
|
|
59
|
+
key_storage::init ($_read_or_abort -> _read_or_abort).
|
|
60
|
+
encrypted_channel::init ($_read_or_abort -> _read_or_abort).
|
|
61
|
+
|
|
62
|
+
// Host-protocol action helpers (the driver resolves on kind "data").
|
|
63
|
+
fn _save_state (_) = (transaction::action::return_data ($kind -> $save_state)).
|
|
64
|
+
fn _return_data (payload: any) = (transaction::action::return_data ($kind -> $data, $payload -> payload)).
|
|
65
|
+
fn _notify_agent (payload: any) = (transaction::action::return_data ($kind -> $notify_agent, $payload -> payload)).
|
|
66
|
+
|
|
67
|
+
// Receipt consumer log (core 0.7.0) — the driver's RC-series probe.
|
|
68
|
+
receipts_log is any[] = [].
|
|
69
|
+
|
|
70
|
+
// Storage hooks: deposit inbound messages; send/remove are no-ops.
|
|
71
|
+
a2a_messaging::init (
|
|
72
|
+
$_read_or_abort -> _read_or_abort,
|
|
73
|
+
$on_message_received -> fn (arg: any) -> transaction::action::type[]
|
|
74
|
+
{
|
|
75
|
+
sid = (arg $sender_id) safe global_id.
|
|
76
|
+
txt = (arg $text) safe str.
|
|
77
|
+
wid is str = "".
|
|
78
|
+
if (arg $wire_id) != NIL { wid -> (arg $wire_id) safe str. }
|
|
79
|
+
rw is str = "".
|
|
80
|
+
if (arg $reply_to) != NIL { rw -> ((arg $reply_to) $wire_id) safe str. }
|
|
81
|
+
inbox (_count inbox|) -> ($sender -> sid, $text -> txt, $wire_id -> wid, $reply_wire -> rw).
|
|
82
|
+
return [ _notify_agent ($event -> $message_received), _save_state NIL ].
|
|
83
|
+
},
|
|
84
|
+
$on_message_sent -> fn (_: any) -> transaction::action::type[] { return []. },
|
|
85
|
+
$on_contact_removed -> fn (_: any) -> transaction::action::type[] { return []. },
|
|
86
|
+
$on_file_received -> fn (arg: any) -> transaction::action::type[]
|
|
87
|
+
{
|
|
88
|
+
sid = (arg $sender_id) safe global_id.
|
|
89
|
+
fname = (arg $filename) safe str.
|
|
90
|
+
mt = (arg $mime) safe str.
|
|
91
|
+
wid is str = "".
|
|
92
|
+
if (arg $wire_id) != NIL { wid -> (arg $wire_id) safe str. }
|
|
93
|
+
rw is str = "".
|
|
94
|
+
if (arg $reply_to) != NIL { rw -> ((arg $reply_to) $wire_id) safe str. }
|
|
95
|
+
files (_count files|) -> ($sender -> sid, $filename -> fname, $mime -> mt, $wire_id -> wid, $reply_wire -> rw).
|
|
96
|
+
return [ _notify_agent ($event -> $file_received), _save_state NIL ].
|
|
97
|
+
},
|
|
98
|
+
$on_file_sent -> fn (_: any) -> transaction::action::type[] { return []. },
|
|
99
|
+
$on_receipt_received -> fn (arg: any) -> transaction::action::type[]
|
|
100
|
+
{
|
|
101
|
+
receipts_log (_count receipts_log|) -> arg.
|
|
102
|
+
return [ _notify_agent ($event -> $receipt_received), _save_state NIL ].
|
|
103
|
+
}
|
|
104
|
+
).
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ---- minimal host surface used by the driver ----
|
|
109
|
+
// The core's send_message delivers to the legacy ::actor::receive_message name;
|
|
110
|
+
// this shim routes it into the core receive handler (→ on_message_received hook).
|
|
111
|
+
trn receive_message args: any { return a2a_messaging::handle_receive_message args. }
|
|
112
|
+
trn readonly list_incoming_messages _ { return ($inbox -> inbox). }
|
|
113
|
+
trn readonly list_incoming_files _ { return ($files -> files). }
|
|
114
|
+
|
|
115
|
+
// Exercises the metadata-only file monitoring summary directly (the loopback has
|
|
116
|
+
// no bound control plane, so the format + byte-secrecy are asserted on the helper).
|
|
117
|
+
trn qa_file_summary _:($filename -> f: str, $mime -> m: str, $data -> d: bin)
|
|
118
|
+
{
|
|
119
|
+
return transaction::success [ _return_data ($summary -> (a2a_messaging::file_monitor_summary f m d)) ].
|
|
120
|
+
}
|
|
121
|
+
trn readonly export_address_document _ { return (_write address_document::get_my_address_document()). }
|
|
122
|
+
|
|
123
|
+
// Identity-hierarchy helpers (derived equivalents of the host's; used only by
|
|
124
|
+
// the role scenario). sign_delegation/export_root_profile are root-side;
|
|
125
|
+
// set_delegation is role-side.
|
|
126
|
+
trn sign_delegation _:($role_ad -> role_ad_blob: bin, $role_id -> role_id: str)
|
|
127
|
+
{
|
|
128
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
129
|
+
abort "Only a root can sign delegation certs." when a2a_messaging::delegation_cert != NIL.
|
|
130
|
+
role_ad = (_read_or_abort role_ad_blob) safe address_document_types::t_address_document.
|
|
131
|
+
role_cid = role_ad $identity $container_id.
|
|
132
|
+
abort "Cannot delegate to myself." when role_cid == _get_container_id().
|
|
133
|
+
core is a2a_protocol::delegation_core_t = (
|
|
134
|
+
$version -> 1, $role_cid -> role_cid, $role_ad_hash -> (_value_id role_ad),
|
|
135
|
+
$role_id -> role_id, $root_cid -> _get_container_id(),
|
|
136
|
+
$issued_at -> (current_transaction_info::get_transaction_time())?
|
|
137
|
+
).
|
|
138
|
+
cert is a2a_protocol::delegation_cert_t = ($c -> core, $s -> key_storage::default_sign (_value_id core)).
|
|
139
|
+
return transaction::success [ _return_data ($cert -> (_write cert)) ].
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
trn export_root_profile _
|
|
143
|
+
{
|
|
144
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
145
|
+
abort "Only a root can export a root profile." when a2a_messaging::delegation_cert != NIL.
|
|
146
|
+
my_ad = address_document::get_my_address_document().
|
|
147
|
+
core is a2a_protocol::root_profile_core_t = (
|
|
148
|
+
$version -> 1, $root_cid -> _get_container_id(),
|
|
149
|
+
$name -> a2a_messaging::my_name, $bio -> a2a_messaging::my_bio,
|
|
150
|
+
$keys -> my_ad $identity $key_list
|
|
151
|
+
).
|
|
152
|
+
profile is a2a_protocol::root_profile_t = ($p -> core, $s -> key_storage::default_sign (_value_id core)).
|
|
153
|
+
return transaction::success [ _return_data ($profile -> (_write profile)) ].
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// $cert_v1 (NULLABLE): the SAME chain minted over my v1 (bundle-less) AD, so a
|
|
157
|
+
// down-level to a pre-E2E peer carries a cert whose $role_ad_hash matches the v1
|
|
158
|
+
// AD that peer receives. Verified against _value_id(produce_v1_address_document()).
|
|
159
|
+
trn set_delegation _:($cert -> cert_blob: bin, $root_ad -> root_ad_blob: bin, $root_profile -> rp_blob: bin, $cert_v1 -> cert_v1_blob: bin+)
|
|
160
|
+
{
|
|
161
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
162
|
+
cert = (_read_or_abort cert_blob) safe a2a_protocol::delegation_cert_t.
|
|
163
|
+
new_root_ad = (_read_or_abort root_ad_blob) safe address_document_types::t_address_document.
|
|
164
|
+
rp = (_read_or_abort rp_blob) safe a2a_protocol::root_profile_t.
|
|
165
|
+
abort "cert not for me." when (cert $c $role_cid) != _get_container_id().
|
|
166
|
+
my_ad = address_document::get_my_address_document().
|
|
167
|
+
abort "cert AD mismatch." when (cert $c $role_ad_hash) != (_value_id my_ad).
|
|
168
|
+
abort "root AD mismatch." when (new_root_ad $identity $container_id) != (cert $c $root_cid).
|
|
169
|
+
abort "cert not root-signed." when key_storage::check_signature_new_container (_value_id (cert $c)) (cert $s) (new_root_ad $identity $key_list) != TRUE.
|
|
170
|
+
abort "profile not root-signed." when key_storage::check_signature_new_container (_value_id (rp $p)) (rp $s) (new_root_ad $identity $key_list) != TRUE.
|
|
171
|
+
a2a_messaging::delegation_cert -> cert.
|
|
172
|
+
a2a_messaging::root_ad -> new_root_ad.
|
|
173
|
+
a2a_messaging::root_profile -> rp.
|
|
174
|
+
if cert_v1_blob != NIL
|
|
175
|
+
{
|
|
176
|
+
cert_v1 = (_read_or_abort cert_v1_blob?) safe a2a_protocol::delegation_cert_t.
|
|
177
|
+
v1_ad = address_document::get_my_address_document_versioned(TRUE).
|
|
178
|
+
abort "v1 cert not for me." when (cert_v1 $c $role_cid) != _get_container_id().
|
|
179
|
+
abort "v1 cert AD mismatch." when (cert_v1 $c $role_ad_hash) != (_value_id v1_ad).
|
|
180
|
+
abort "v1 cert root mismatch." when (cert_v1 $c $root_cid) != (cert $c $root_cid).
|
|
181
|
+
abort "v1 cert not root-signed." when key_storage::check_signature_new_container (_value_id (cert_v1 $c)) (cert_v1 $s) (new_root_ad $identity $key_list) != TRUE.
|
|
182
|
+
a2a_messaging::delegation_cert_v1 -> cert_v1.
|
|
183
|
+
}
|
|
184
|
+
return transaction::success [ _return_data ($delegated -> TRUE), _save_state NIL ].
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Expose my v1 (bundle-less) AD so the root can sign a v1-bound delegation cert
|
|
188
|
+
// (the daemon mints this by calling sign_delegation with this blob).
|
|
189
|
+
trn export_v1_address_document _
|
|
190
|
+
{
|
|
191
|
+
v1_ad = address_document::get_my_address_document_versioned(TRUE).
|
|
192
|
+
return transaction::success [ _return_data ($ad -> (_write v1_ad)) ].
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// export/import wrappers (migration scenario): the core state under $core, as a
|
|
196
|
+
// host would compose it. (The app inbox is not part of this suite's migration
|
|
197
|
+
// assertions, so it is omitted to keep the fixture minimal.)
|
|
198
|
+
// CORE-ONLY (this unit no longer loads a2a_notifications): the COMBINED
|
|
199
|
+
// core+notify round-trip moved to notif_actor.mu/notif.mjs (the unit that
|
|
200
|
+
// loads both libraries). test_actor keeps only the core-half round-trip.
|
|
201
|
+
trn readonly export_state _ { return ($core -> (a2a_messaging::export_core_state NIL)). }
|
|
202
|
+
trn import_state data: any
|
|
203
|
+
{
|
|
204
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
205
|
+
a2a_messaging::import_core_state (data $core).
|
|
206
|
+
return transaction::success [ _return_data ($imported -> TRUE), _save_state NIL ].
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ================= TEST PROBES =================
|
|
210
|
+
trn readonly qa_my_cid _ { return ($cid -> _get_container_id()). }
|
|
211
|
+
trn readonly qa_export_ad _ { return ($ad -> (_write address_document::get_my_address_document())). }
|
|
212
|
+
|
|
213
|
+
// Counts over the (non-hidden) core state — for receiver-side assertions.
|
|
214
|
+
trn readonly qa_state _
|
|
215
|
+
{
|
|
216
|
+
return (
|
|
217
|
+
$n_contacts -> (_count a2a_messaging::contacts),
|
|
218
|
+
$n_peer_ads -> (_count a2a_messaging::peer_ads),
|
|
219
|
+
$n_pending_invites -> (_count a2a_messaging::pending_invites),
|
|
220
|
+
$n_pending_redemptions -> (_count a2a_messaging::pending_redemptions),
|
|
221
|
+
$n_contact_roots -> (_count a2a_messaging::contact_roots),
|
|
222
|
+
$n_pending_restores -> (_count a2a_messaging::pending_restores),
|
|
223
|
+
$n_restore_replies -> (_count a2a_messaging::pending_restore_replies),
|
|
224
|
+
$n_deferred -> (_count a2a_messaging::deferred_msgs)
|
|
225
|
+
).
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// export-secrecy: hand back export_core_state so the driver confirms neither
|
|
229
|
+
// ephemeral secret store appears in the portable export.
|
|
230
|
+
trn readonly qa_export_core _ { return ($core -> (a2a_messaging::export_core_state NIL)). }
|
|
231
|
+
|
|
232
|
+
// Simulate a breaking-change migration that carried contacts but dropped the
|
|
233
|
+
// address documents (the spec's "degraded contact" state).
|
|
234
|
+
trn qa_strip_peer_ads _
|
|
235
|
+
{
|
|
236
|
+
a2a_messaging::peer_ads -> (,).
|
|
237
|
+
return transaction::success [ _return_data ($stripped -> TRUE) ].
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ---- adversarial leg-1 senders (bare-send a crafted submit_invite_response) ----
|
|
241
|
+
trn qa_leg1_badbox _:($invite -> blob: bin)
|
|
242
|
+
{
|
|
243
|
+
inv = (_read_or_abort blob) safe a2a_protocol::invite_eph_t.
|
|
244
|
+
kpr = _crypto_construct_encryption_keypair (inv $v).
|
|
245
|
+
wrong = _crypto_construct_encryption_keypair (inv $v).
|
|
246
|
+
payload = _write ($ad -> (address_document::get_my_address_document()), $cert -> NIL, $root_profile -> NIL, $cp_binding -> NIL, $invite_id -> (inv $d)).
|
|
247
|
+
data = _crypto_encrypt_message (kpr $secret_key) (wrong $public_key) payload.
|
|
248
|
+
return transaction::success [
|
|
249
|
+
transaction::action::send (inv $c) ($name -> "::a2a_messaging::submit_invite_response", $targ -> ($invite_id -> (inv $d), $epk -> (kpr $public_key), $v -> (inv $v), $data -> data)),
|
|
250
|
+
_return_data ($sent -> TRUE)
|
|
251
|
+
].
|
|
252
|
+
}
|
|
253
|
+
trn qa_leg1_foreign_ad _:($invite -> blob: bin, $foreign_ad -> fad: bin)
|
|
254
|
+
{
|
|
255
|
+
inv = (_read_or_abort blob) safe a2a_protocol::invite_eph_t.
|
|
256
|
+
kpr = _crypto_construct_encryption_keypair (inv $v).
|
|
257
|
+
// Bound `any`, not `safe t_address_document`: the AD is embedded as the
|
|
258
|
+
// `$ad -> any` payload field and re-verified downstream; casting to the
|
|
259
|
+
// full AD type here needlessly deepens meta-stage type reduction (AD-v2
|
|
260
|
+
// embeds t_e2e_bundle) — the per-unit fuel budget is scarce (corpus split).
|
|
261
|
+
foreign = _read_or_abort fad.
|
|
262
|
+
payload = _write ($ad -> foreign, $cert -> NIL, $root_profile -> NIL, $cp_binding -> NIL, $invite_id -> (inv $d)).
|
|
263
|
+
data = _crypto_encrypt_message (kpr $secret_key) (inv $k) payload.
|
|
264
|
+
return transaction::success [
|
|
265
|
+
transaction::action::send (inv $c) ($name -> "::a2a_messaging::submit_invite_response", $targ -> ($invite_id -> (inv $d), $epk -> (kpr $public_key), $v -> (inv $v), $data -> data)),
|
|
266
|
+
_return_data ($sent -> TRUE)
|
|
267
|
+
].
|
|
268
|
+
}
|
|
269
|
+
trn qa_leg1_forged_ad _:($invite -> blob: bin)
|
|
270
|
+
{
|
|
271
|
+
inv = (_read_or_abort blob) safe a2a_protocol::invite_eph_t.
|
|
272
|
+
kpr = _crypto_construct_encryption_keypair (inv $v).
|
|
273
|
+
my_ad = address_document::get_my_address_document().
|
|
274
|
+
forged = ($version -> (my_ad $version), $identity -> (my_ad $identity), $authorizations -> (,)). // untyped: embedded as $ad -> any, verified downstream (fuel budget)
|
|
275
|
+
payload = _write ($ad -> forged, $cert -> NIL, $root_profile -> NIL, $cp_binding -> NIL, $invite_id -> (inv $d)).
|
|
276
|
+
data = _crypto_encrypt_message (kpr $secret_key) (inv $k) payload.
|
|
277
|
+
return transaction::success [
|
|
278
|
+
transaction::action::send (inv $c) ($name -> "::a2a_messaging::submit_invite_response", $targ -> ($invite_id -> (inv $d), $epk -> (kpr $public_key), $v -> (inv $v), $data -> data)),
|
|
279
|
+
_return_data ($sent -> TRUE)
|
|
280
|
+
].
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Fire a leg-0 restore request at an arbitrary target (bypassing the
|
|
284
|
+
// degraded-contact trigger) — used to prove the responder's contacts gate.
|
|
285
|
+
trn qa_send_restore_request _:($target -> tgt: global_id)
|
|
286
|
+
{
|
|
287
|
+
actions is transaction::action::type[] = a2a_messaging::begin_contact_restore tgt.
|
|
288
|
+
actions (_count actions|) -> _return_data ($ok -> TRUE).
|
|
289
|
+
return transaction::success actions.
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Craft an unsolicited leg-1 (no matching pending_restores at the target).
|
|
293
|
+
trn qa_send_fake_restore_response _:($target -> tgt: global_id)
|
|
294
|
+
{
|
|
295
|
+
scheme = _crypto_default_scheme_id().
|
|
296
|
+
kp = _crypto_construct_encryption_keypair scheme.
|
|
297
|
+
payload = _write ($junk -> "x").
|
|
298
|
+
data = _crypto_encrypt_message (kp $secret_key) (kp $public_key) payload.
|
|
299
|
+
return transaction::success [
|
|
300
|
+
transaction::action::send tgt (
|
|
301
|
+
$name -> "::a2a_messaging::submit_restore_response",
|
|
302
|
+
$targ -> ($rid -> (_new_id "fake restore"), $epk -> (kp $public_key), $v -> scheme, $data -> data)
|
|
303
|
+
),
|
|
304
|
+
_return_data ($ok -> TRUE)
|
|
305
|
+
].
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ---- cross-version leg-1 senders (PLAN Step 4.1) ----
|
|
309
|
+
// Emit a submit_invite_response whose BOXED payload is the EXACT wire shape
|
|
310
|
+
// of a given core version's sender — the 0.2.0 ($shape "v2": no $name, no
|
|
311
|
+
// $pv), 0.3.0 ("v3": +$name), 0.5.0 ("v5": +$pv/$caps), or a BELOW-FLOOR
|
|
312
|
+
// dialect ("too_old": v2 fields + $pv -> 1, the Addition A/B injection).
|
|
313
|
+
// Sender-side emulation only: the responder-side completion stores are
|
|
314
|
+
// hidden (INV-4), so the inviter-side outcome + the leg-3 ARRIVAL at this
|
|
315
|
+
// packet (visible as its gate abort) are what the driver asserts.
|
|
316
|
+
trn qa_send_versioned_leg1 _:($invite -> blob: bin, $shape -> shape: str, $name -> name: str)
|
|
317
|
+
{
|
|
318
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
319
|
+
inv = (_read_or_abort blob) safe a2a_protocol::invite_eph_t.
|
|
320
|
+
kpr = _crypto_construct_encryption_keypair (inv $v).
|
|
321
|
+
my_ad = address_document::get_my_address_document().
|
|
322
|
+
|
|
323
|
+
payload is bin+ = NIL.
|
|
324
|
+
if shape == "v2"
|
|
325
|
+
{
|
|
326
|
+
payload -> (_write ($ad -> my_ad, $cert -> NIL, $root_profile -> NIL, $cp_binding -> NIL, $invite_id -> (inv $d))).
|
|
327
|
+
}
|
|
328
|
+
if shape == "v3"
|
|
329
|
+
{
|
|
330
|
+
payload -> (_write ($ad -> my_ad, $cert -> NIL, $root_profile -> NIL, $cp_binding -> NIL, $invite_id -> (inv $d), $name -> name)).
|
|
331
|
+
}
|
|
332
|
+
if shape == "v5"
|
|
333
|
+
{
|
|
334
|
+
// literal 5: this shape emulates a 0.5.0 sender (wire_version moved on).
|
|
335
|
+
payload -> (_write ($ad -> my_ad, $cert -> NIL, $root_profile -> NIL, $cp_binding -> NIL, $invite_id -> (inv $d), $name -> name, $pv -> 5, $caps -> ["core.notifications"])).
|
|
336
|
+
}
|
|
337
|
+
if shape == "too_old"
|
|
338
|
+
{
|
|
339
|
+
payload -> (_write ($ad -> my_ad, $cert -> NIL, $root_profile -> NIL, $cp_binding -> NIL, $invite_id -> (inv $d), $pv -> 1)).
|
|
340
|
+
}
|
|
341
|
+
abort "qa_send_versioned_leg1: unknown shape " + shape when payload == NIL.
|
|
342
|
+
|
|
343
|
+
data = _crypto_encrypt_message (kpr $secret_key) (inv $k) payload?.
|
|
344
|
+
return transaction::success [
|
|
345
|
+
transaction::action::send (inv $c) (
|
|
346
|
+
$name -> "::a2a_messaging::submit_invite_response",
|
|
347
|
+
$targ -> ($invite_id -> (inv $d), $epk -> (kpr $public_key), $v -> (inv $v), $data -> data)
|
|
348
|
+
),
|
|
349
|
+
_return_data ($sent -> TRUE, $shape -> shape)
|
|
350
|
+
].
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Passive version learning probe: the contact_pv map (cid -> learned wire
|
|
354
|
+
// dialect; absent = nothing learned yet, 0 = pre-0.5 peer).
|
|
355
|
+
trn readonly qa_contact_pv _
|
|
356
|
+
{
|
|
357
|
+
return ($contact_pv -> a2a_messaging::contact_pv, $contact_caps -> a2a_messaging::contact_caps).
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Per-cid probes (precise driver assertions, no map-dump parsing):
|
|
361
|
+
// learned dialect (-1 = nothing learned), advertised caps, contact name.
|
|
362
|
+
trn readonly qa_contact_pv_of _:($cid -> cid: global_id)
|
|
363
|
+
{
|
|
364
|
+
p = a2a_messaging::contact_pv cid.
|
|
365
|
+
caps = a2a_messaging::contact_caps cid.
|
|
366
|
+
empty is str[] = [].
|
|
367
|
+
return (
|
|
368
|
+
$pv -> (p == NIL ?? 0 - 1 ; p?),
|
|
369
|
+
$caps -> (caps == NIL ?? empty ; caps?)
|
|
370
|
+
).
|
|
371
|
+
}
|
|
372
|
+
trn readonly qa_contact_name _:($cid -> cid: global_id)
|
|
373
|
+
{
|
|
374
|
+
c = a2a_messaging::contacts cid.
|
|
375
|
+
return ($name -> (c == NIL ?? "" ; (c? $name))).
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Inject a learned capability set for a contact — drives the CAP-1 gate
|
|
379
|
+
// tests (positive-evidence denial vs unknown/empty pass-through).
|
|
380
|
+
trn qa_set_contact_caps _:($cid -> cid: global_id, $caps -> caps: str[])
|
|
381
|
+
{
|
|
382
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
383
|
+
a2a_messaging::contact_caps cid -> caps.
|
|
384
|
+
return transaction::success [ _return_data ($set -> TRUE), _save_state NIL ].
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Inject a learned dialect for a contact — arranges the "peer was v2 at
|
|
388
|
+
// invite time" precondition of the upgrade scenario (V7) on a pair that
|
|
389
|
+
// has a live encrypted channel (V1 proves the real v2 leg-1 learns 2).
|
|
390
|
+
trn qa_set_contact_pv _:($cid -> cid: global_id, $pv -> pv: int)
|
|
391
|
+
{
|
|
392
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
393
|
+
a2a_messaging::contact_pv cid -> pv.
|
|
394
|
+
return transaction::success [ _return_data ($set -> TRUE), _save_state NIL ].
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Emit the EXACT pre-0.5 legacy receive_message $targ — only $text, no
|
|
398
|
+
// $wire_id / $reply_to / $pv — over the established encrypted channel
|
|
399
|
+
// (byte-shape of the deployed 0.2-line sender). Drives the V7 monotonicity
|
|
400
|
+
// assertion: unstamped legacy traffic must never downgrade learned state.
|
|
401
|
+
trn qa_send_legacy_message _:($target -> tgt: global_id, $text -> text: str)
|
|
402
|
+
{
|
|
403
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
404
|
+
return encrypted_channel::execute_transaction tgt (fn (_) -> transaction::results::type {
|
|
405
|
+
return transaction::success [
|
|
406
|
+
encrypted_channel::send_encrypted_tx tgt (
|
|
407
|
+
$name -> "::actor::receive_message",
|
|
408
|
+
$targ -> ($text -> text)
|
|
409
|
+
),
|
|
410
|
+
_return_data ($sent -> TRUE)
|
|
411
|
+
].
|
|
412
|
+
}).
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// ---- core 0.7.0 receipts QA (RC-series) ----
|
|
416
|
+
trn readonly qa_receipts_log _ { return ($log -> receipts_log). }
|
|
417
|
+
trn readonly qa_receipt_expectation _:($cid -> cid: global_id)
|
|
418
|
+
{
|
|
419
|
+
return ($state -> (a2a_messaging::receipt_expectation cid)).
|
|
420
|
+
}
|
|
421
|
+
// (Re)declare this node's advertised protocol caps — drives the emit gate's
|
|
422
|
+
// self side (a2a_capabilities::init is re-callable; empty handlers, stub
|
|
423
|
+
// describe: receipts ids are $advertise-class, no control verbs).
|
|
424
|
+
trn qa_init_caps _:($advertise -> adv: str[])
|
|
425
|
+
{
|
|
426
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
427
|
+
a2a_capabilities::init (
|
|
428
|
+
$describe -> fn (_: any) -> a2a_capabilities::app_manifest_t
|
|
429
|
+
{
|
|
430
|
+
return ($version -> 1, $app_id -> "test.actor", $name -> "actor",
|
|
431
|
+
$description -> "", $monitoring_status -> "off", $capabilities -> (,)).
|
|
432
|
+
},
|
|
433
|
+
$supported -> [],
|
|
434
|
+
$handlers -> (,),
|
|
435
|
+
$on_unknown -> fn (_: any) -> transaction::action::type[] { return []. },
|
|
436
|
+
$authorizer -> NIL,
|
|
437
|
+
$advertise -> adv
|
|
438
|
+
).
|
|
439
|
+
return transaction::success [ _return_data ($set -> TRUE) ].
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Contact-naming regression plumbing: advertise core.connect on the receiver,
|
|
443
|
+
// then let its already-bound CP relay a real signed peer AD + display label.
|
|
444
|
+
trn qa_init_connect _
|
|
445
|
+
{
|
|
446
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
447
|
+
hs is (str ->> (any -> transaction::action::type[])) = (,).
|
|
448
|
+
hs a2a_capabilities::cap_connect -> fn (_: any) -> transaction::action::type[] { return []. }.
|
|
449
|
+
a2a_capabilities::init (
|
|
450
|
+
$describe -> fn (_: any) -> a2a_capabilities::app_manifest_t
|
|
451
|
+
{
|
|
452
|
+
caps is (str ->> a2a_capabilities::capability_t) = (,).
|
|
453
|
+
caps a2a_capabilities::cap_connect -> (
|
|
454
|
+
$cap -> a2a_capabilities::cap_connect,
|
|
455
|
+
$version -> 1,
|
|
456
|
+
$params -> "",
|
|
457
|
+
$secrets -> (,)
|
|
458
|
+
).
|
|
459
|
+
return ($version -> 1, $app_id -> "test.actor", $name -> "actor",
|
|
460
|
+
$description -> "", $monitoring_status -> "off", $capabilities -> caps).
|
|
461
|
+
},
|
|
462
|
+
$supported -> [a2a_capabilities::cap_connect],
|
|
463
|
+
$handlers -> hs,
|
|
464
|
+
$on_unknown -> fn (_: any) -> transaction::action::type[] { return []. },
|
|
465
|
+
$authorizer -> NIL,
|
|
466
|
+
$advertise -> []
|
|
467
|
+
).
|
|
468
|
+
return transaction::success [ _return_data ($set -> TRUE) ].
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
trn qa_send_ingest_descriptor _:($target -> tgt: global_id, $peer_ad -> ad_blob: bin, $peer_name -> peer_name: str)
|
|
472
|
+
{
|
|
473
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
474
|
+
ad = (_read_or_abort ad_blob) safe address_document_types::t_address_document.
|
|
475
|
+
return encrypted_channel::execute_transaction tgt (fn (_) -> transaction::results::type {
|
|
476
|
+
return transaction::success [
|
|
477
|
+
encrypted_channel::send_encrypted_tx tgt (
|
|
478
|
+
$name -> "::a2a_messaging::ingest_connect_descriptor",
|
|
479
|
+
$targ -> ($peer_ad -> ad, $peer_name -> peer_name, $pv -> a2a_versions::wire_version)
|
|
480
|
+
),
|
|
481
|
+
_return_data ($sent -> TRUE)
|
|
482
|
+
].
|
|
483
|
+
}).
|
|
484
|
+
}
|
|
485
|
+
// Consumer read-path emission (the get/mark-read moment): appends the core
|
|
486
|
+
// read_receipt_actions for ids just transitioned unread->read.
|
|
487
|
+
trn qa_mark_read _:($contact -> cid: global_id, $wire_ids -> wids: str[])
|
|
488
|
+
{
|
|
489
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
490
|
+
actions is transaction::action::type[] = [].
|
|
491
|
+
sc a2a_messaging::read_receipt_actions cid wids -- ( -> a)
|
|
492
|
+
{
|
|
493
|
+
actions (_count actions|) -> a.
|
|
494
|
+
}
|
|
495
|
+
actions (_count actions|) -> _return_data ($sent -> ((_count actions) > 0)).
|
|
496
|
+
return transaction::success actions.
|
|
497
|
+
}
|
|
498
|
+
// Raw receipt injector (forward-compat / shape-tolerance cells).
|
|
499
|
+
trn qa_send_raw_receipt _:($target -> tgt: global_id, $kind -> kind: any, $wire_ids -> wids: any)
|
|
500
|
+
{
|
|
501
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
502
|
+
return encrypted_channel::execute_transaction tgt (fn (_) -> transaction::results::type {
|
|
503
|
+
return transaction::success [
|
|
504
|
+
encrypted_channel::send_encrypted_tx tgt (
|
|
505
|
+
$name -> "::a2a_messaging::receive_receipt",
|
|
506
|
+
$targ -> ($kind -> kind, $wire_ids -> wids, $pv -> a2a_versions::wire_version)
|
|
507
|
+
),
|
|
508
|
+
_return_data ($sent -> TRUE)
|
|
509
|
+
].
|
|
510
|
+
}).
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Emulate an OLD-dialect sender: a stamped message with an arbitrary $pv
|
|
514
|
+
// (+ wire_id so the receiver's delivered-emission path is actually
|
|
515
|
+
// evaluated, not short-circuited). Drives the RC10 old-peer-silence cell:
|
|
516
|
+
// the receiver LEARNS the stamped pv from this very message (learning
|
|
517
|
+
// precedes the gate — the self-heal mechanism), so presetting maps isn't
|
|
518
|
+
// enough; the message itself must carry the old dialect.
|
|
519
|
+
trn qa_send_stamped_message _:($target -> tgt: global_id, $text -> text: str, $pv -> pv: int, $wire_id -> wid: str)
|
|
520
|
+
{
|
|
521
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
522
|
+
return encrypted_channel::execute_transaction tgt (fn (_) -> transaction::results::type {
|
|
523
|
+
return transaction::success [
|
|
524
|
+
encrypted_channel::send_encrypted_tx tgt (
|
|
525
|
+
$name -> "::actor::receive_message",
|
|
526
|
+
$targ -> ($text -> text, $wire_id -> wid, $pv -> pv)
|
|
527
|
+
),
|
|
528
|
+
_return_data ($sent -> TRUE)
|
|
529
|
+
].
|
|
530
|
+
}).
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
// ---- leg-3 isolation helpers ----
|
|
535
|
+
// A fake invite carrying a chosen inviter cid; the named cid never minted it, so
|
|
536
|
+
// its leg-2 aborts (unknown invite) and sends no real leg-3 — leaving the
|
|
537
|
+
// responder with a LIVE redemption + kept eph priv to target.
|
|
538
|
+
trn qa_mint_fake_invite _:($inviter_cid -> icid: global_id)
|
|
539
|
+
{
|
|
540
|
+
scheme = _crypto_default_scheme_id().
|
|
541
|
+
kp = _crypto_construct_encryption_keypair scheme.
|
|
542
|
+
iid = _new_id "fake invite".
|
|
543
|
+
inv is a2a_protocol::invite_eph_t = ($d -> iid, $c -> icid, $n -> "Fake", $k -> (kp $public_key), $v -> scheme).
|
|
544
|
+
return transaction::success [ _return_data ($blob -> (_write inv), $invite_id -> iid) ].
|
|
545
|
+
}
|
|
546
|
+
// Crafted leg-3 as a BARE BOXED send to the responder's kept eph pubkey.
|
|
547
|
+
// mode: "real" (sender-pin) | "foreign" (cid-bind leg-3) | "forged" (PoP leg-3).
|
|
548
|
+
trn qa_send_complete _:($target -> tgt: global_id, $invite_id -> iid: global_id, $resp_eph_pub -> rpk: publickey_encrypt, $mode -> mode: str, $foreign_ad -> fad: bin)
|
|
549
|
+
{
|
|
550
|
+
scheme = _crypto_default_scheme_id().
|
|
551
|
+
kpi = _crypto_construct_encryption_keypair scheme.
|
|
552
|
+
my_ad = address_document::get_my_address_document().
|
|
553
|
+
ad = my_ad as any. // any, not typed AD: embedded as $ad -> any downstream (meta-stage fuel budget)
|
|
554
|
+
if mode == "foreign" { ad -> (_read_or_abort fad). }
|
|
555
|
+
if mode == "forged" { ad -> ($version -> (my_ad $version), $identity -> (my_ad $identity), $authorizations -> (,)). }
|
|
556
|
+
payload = _write ($ad -> ad, $cert -> NIL, $root_profile -> NIL, $cp_binding -> NIL, $invite_id -> iid).
|
|
557
|
+
data = _crypto_encrypt_message (kpi $secret_key) rpk payload.
|
|
558
|
+
return transaction::success [
|
|
559
|
+
transaction::action::send tgt ($name -> "::a2a_messaging::complete_invite", $targ -> ($invite_id -> iid, $epk -> (kpi $public_key), $v -> scheme, $data -> data)),
|
|
560
|
+
_return_data ($sent -> TRUE)
|
|
561
|
+
].
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Shared driver harness for the loopback test suites.
|
|
2
|
+
//
|
|
3
|
+
// Extracted from test.mjs so both test.mjs (T/V/RC-series, messaging-only unit)
|
|
4
|
+
// and notif.mjs (N-series, notifications unit) import ONE copy of the wrapper /
|
|
5
|
+
// packet bootstrap + the mk/wire/mutate/ro/binv/mkPacket/sleep/log helpers
|
|
6
|
+
// instead of duplicating them. Each driver keeps its own tiny scorecard/ok/CUR
|
|
7
|
+
// local (they differ in prose and are trivially cheap).
|
|
8
|
+
//
|
|
9
|
+
// The `wire` handler preserves the protocol_error/notify_agent error-as-data
|
|
10
|
+
// capture verbatim (the V-series Additions A/B assertions read code/message/
|
|
11
|
+
// context/surface off id.errEvents).
|
|
12
|
+
import { resolve } from 'node:path';
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
import { adapt_wrapper } from '@adapt-toolkit/sdk/executables';
|
|
15
|
+
import { PacketWrapperConfigurator } from '@adapt-toolkit/sdk/wrappers';
|
|
16
|
+
import { object_to_adapt_value } from '@adapt-toolkit/sdk/wrapper';
|
|
17
|
+
|
|
18
|
+
export const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
19
|
+
|
|
20
|
+
// Pure per-node state record.
|
|
21
|
+
export function mk(name) {
|
|
22
|
+
return { name, pw: null, cid: '', pending: [], rejects: [], events: [], errEvents: [] };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Build a harness bound to one broker + unit dir. Returns the driver helpers,
|
|
26
|
+
// each of which closes over the shared `wrapper` and unit metadata.
|
|
27
|
+
export async function createHarness({ brokerUrl, unitDir, logTag = 't' }) {
|
|
28
|
+
const UNIT_DIR = resolve(unitDir);
|
|
29
|
+
const unitHash = fs.readdirSync(UNIT_DIR).find((f) => f.endsWith('.muflo')).slice(0, -'.muflo'.length);
|
|
30
|
+
const UNIT = new Uint8Array(fs.readFileSync(resolve(UNIT_DIR, `${unitHash}.muflo`)));
|
|
31
|
+
const log = (...a) => process.stderr.write(`[${logTag}] ${a.join(' ')}\n`);
|
|
32
|
+
|
|
33
|
+
function wire(id) {
|
|
34
|
+
id.pw.on_return_data = (d) => {
|
|
35
|
+
const kind = d.Reduce('kind').Visualize();
|
|
36
|
+
if (kind === 'notify_agent') {
|
|
37
|
+
const ev = d.Reduce('payload').Reduce('event').Visualize();
|
|
38
|
+
id.events.push(ev);
|
|
39
|
+
// Capture the full error-as-data payload for $protocol_error events
|
|
40
|
+
// (Additions A/B assertions read code/message/context off it).
|
|
41
|
+
if (ev === 'protocol_error') {
|
|
42
|
+
const p = d.Reduce('payload');
|
|
43
|
+
id.errEvents.push({
|
|
44
|
+
context: p.Reduce('context').Visualize(),
|
|
45
|
+
message: p.Reduce('message').Visualize(),
|
|
46
|
+
code: p.Reduce('error').Reduce('code').Visualize(),
|
|
47
|
+
errMessage: p.Reduce('error').Reduce('message').Visualize(),
|
|
48
|
+
peerVersion: p.Reduce('error').Reduce('peer_version').Visualize(),
|
|
49
|
+
minSupported: p.Reduce('error').Reduce('min_supported').Visualize(),
|
|
50
|
+
surface: p.Reduce('error').Reduce('surface').Visualize(),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (kind === 'save_state') return;
|
|
56
|
+
const p = id.pending.shift(); if (!p) return;
|
|
57
|
+
clearTimeout(p.timer); p.resolve(d.Reduce('payload'));
|
|
58
|
+
};
|
|
59
|
+
id.pw.on_transaction_failure = (msg) => {
|
|
60
|
+
const p = id.pending.shift();
|
|
61
|
+
if (p) { clearTimeout(p.timer); p.reject(new Error(msg)); }
|
|
62
|
+
else { id.rejects.push(String(msg)); log(`${id.name} inbound rejected:`, String(msg).split('\n')[0]); }
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const wrapper = await adapt_wrapper.start(['--broker_address', brokerUrl, '--test_mode',
|
|
67
|
+
'--logger_config', '--level', 'WARNING', '--stdout', 'stderr', '--logger_config_end']);
|
|
68
|
+
wrapper.start();
|
|
69
|
+
await sleep(1500);
|
|
70
|
+
|
|
71
|
+
function mutate(id, name, targ) {
|
|
72
|
+
return new Promise((res, rej) => {
|
|
73
|
+
const timer = setTimeout(() => rej(new Error(`${id.name}.${name} timed out`)), 20000);
|
|
74
|
+
id.pending.push({ resolve: res, reject: rej, timer });
|
|
75
|
+
id.pw.add_client_message(object_to_adapt_value({ name, targ }));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const ro = (id, name, targ) => id.pw.packet.ExecuteTransaction(object_to_adapt_value({ name, targ }));
|
|
79
|
+
const binv = (id, buf) => id.pw.packet.NewBinaryFromBuffer(Buffer.from(buf));
|
|
80
|
+
async function mkPacket(id, seed) {
|
|
81
|
+
const cfg = new PacketWrapperConfigurator();
|
|
82
|
+
cfg.process_arguments(['--unit_hash', unitHash, '--seed_phrase', seed, '--unit_dir_path', UNIT_DIR]);
|
|
83
|
+
await new Promise((res, rej) => {
|
|
84
|
+
const t = setTimeout(() => rej(new Error(`${id.name} create timeout`)), 30000);
|
|
85
|
+
wrapper.packet_manager.create_packet(cfg, (pw) => {
|
|
86
|
+
clearTimeout(t); id.pw = pw; id.cid = pw.packet.GetContainerID().Visualize(); wire(id);
|
|
87
|
+
log(`${id.name} cid ${id.cid.slice(0, 12)}…`); res();
|
|
88
|
+
}, UNIT);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { wrapper, UNIT_DIR, unitHash, mk, wire, mutate, ro, binv, mkPacket, sleep, log };
|
|
93
|
+
}
|