@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,601 @@
|
|
|
1
|
+
// Shared ours cluster library (core.cluster capability handler).
|
|
2
|
+
//
|
|
3
|
+
// Implements CLUSTER_API.md (FROZEN v1 + refinement R1): the core.cluster verb
|
|
4
|
+
// handler dispatched via a2a_capabilities, the root-packet cluster_members
|
|
5
|
+
// registry, the async create/remove workflow (host-primitive notify-actions +
|
|
6
|
+
// host callbacks), and reconcile (host-truth ⨝ registry). The control plane and
|
|
7
|
+
// every root-managing app speak ONE envelope; this library owns the cluster
|
|
8
|
+
// verb logic so no app hand-codes it.
|
|
9
|
+
//
|
|
10
|
+
// Layering: loads a2a_messaging (composes its trns' helper fns: resolve_contact,
|
|
11
|
+
// emit_pair, introduce_name) + a2a_capabilities (build_response, the cap id).
|
|
12
|
+
// The authz chokepoint is in a2a_capabilities::dispatch (RR-9); handlers here
|
|
13
|
+
// run AFTER it, so they may assume the sender is the authorized controller.
|
|
14
|
+
//
|
|
15
|
+
// Response model (R1): a handler RETURNS the response_envelope as transaction
|
|
16
|
+
// return_data (a NATIVE record); the daemon marshals it to JSON + ships via the
|
|
17
|
+
// generic sendControl. Core emits NO send action for responses. MUFL has no JSON
|
|
18
|
+
// codec, so $args (request) and $result/$err (response) are native both ways —
|
|
19
|
+
// the daemon adapts JSON<->native at the boundary, generically.
|
|
20
|
+
library a2a_cluster loads libraries
|
|
21
|
+
current_transaction_info,
|
|
22
|
+
address_document_types,
|
|
23
|
+
a2a_protocol,
|
|
24
|
+
a2a_capabilities,
|
|
25
|
+
a2a_messaging,
|
|
26
|
+
version
|
|
27
|
+
uses transactions
|
|
28
|
+
{
|
|
29
|
+
// ---- host-primitive notify-action event tags (CLUSTER_API.md §7) ----------
|
|
30
|
+
// Emitted to the local daemon (host executor) as $notify_agent actions; never
|
|
31
|
+
// cross the network. The daemon maps each to a runtime op + a host callback.
|
|
32
|
+
ev_provision = "host_provision_child".
|
|
33
|
+
ev_destroy = "host_destroy_child".
|
|
34
|
+
ev_enumerate = "host_enumerate_children".
|
|
35
|
+
// R2: minting a child's invite is irreducibly CROSS-PACKET (only the child's
|
|
36
|
+
// packet can sign its own AD) — so it is the 4th host primitive. The daemon
|
|
37
|
+
// runs generate_invite IN the child's packet and calls back register_child_invite.
|
|
38
|
+
ev_mint_invite = "host_mint_child_invite".
|
|
39
|
+
// Per-child monitoring (single-gate, host-mediated): the daemon binds the CHILD's
|
|
40
|
+
// monitoring_proxy to the root's ceremony-pinned cluster CP (enable: host-run
|
|
41
|
+
// set_proxy_pending+verify_proxy_code on the child; disable: host_clear_child_monitoring).
|
|
42
|
+
ev_set_monitoring = "host_set_child_monitoring".
|
|
43
|
+
|
|
44
|
+
// core 3.2: CP-side inbound name for a pushed roster snapshot (literal — this lib
|
|
45
|
+
// keeps no code dep on a2a_monitoring, which loads us).
|
|
46
|
+
roster_update_tx = "::a2a_monitoring::receive_roster_update".
|
|
47
|
+
|
|
48
|
+
// ---- registry (RR-3: host-truth-backed projection) ------------------------
|
|
49
|
+
// member = the control-plane row for one hosted child. $monitoring in
|
|
50
|
+
// "off"|"pending"|"on"; $caps is the child's advertised capability ids.
|
|
51
|
+
metadef member_t: ($cid -> global_id, $role_id -> str, $name -> str,
|
|
52
|
+
$bio -> str, $persona -> str, $monitoring -> str, $caps -> str[]).
|
|
53
|
+
cluster_members is (global_id ->> member_t) = (,).
|
|
54
|
+
|
|
55
|
+
// A child as enumerated from host truth (RR-4: carries caps + bio so a
|
|
56
|
+
// backfilled member is introduce-capable, not hard-blocked). $child_ad is the
|
|
57
|
+
// child's native address document (the daemon passes it parsed — no in-MUFL
|
|
58
|
+
// deserialization, consistent with R1).
|
|
59
|
+
metadef child_rec_t: ($cid -> global_id, $role_id -> str, $name -> str,
|
|
60
|
+
$bio -> str, $persona -> str, $caps -> str[],
|
|
61
|
+
$child_ad -> address_document_types::t_address_document).
|
|
62
|
+
|
|
63
|
+
// ---- async pending / dedup state (CLUSTER_API.md §8) ----------------------
|
|
64
|
+
// pending_reqs: keyed by the host-unique $pending_handle, holds the full
|
|
65
|
+
// (sender,$req_id) so a callback resolves the right sender (RR-2).
|
|
66
|
+
metadef pending_req_t: ($handle -> str, $sender_id -> global_id, $req_id -> str,
|
|
67
|
+
$verb -> str, $op_key -> str, $bio -> str, $date -> time).
|
|
68
|
+
pending_reqs is (str ->> pending_req_t) = (,).
|
|
69
|
+
// GLOBAL (not per-sender) pending-create op-keys by name (RR-3): blocks a
|
|
70
|
+
// proxy-handoff re-create of a name already mid-provision.
|
|
71
|
+
pending_create_names is (str ->> bool) = (,).
|
|
72
|
+
// Monotonic handle counter (MUFL has no RNG): handle = my_cid + "-" + seq.
|
|
73
|
+
next_handle_seq is int = 0.
|
|
74
|
+
// §8 pending-req TTL: a pending-req older than this is swept (settled) by
|
|
75
|
+
// sweep_and_settle, which WS-B calls AFTER a fresh reconcile (so an adopted
|
|
76
|
+
// orphan is already in the registry before we decide success vs timeout).
|
|
77
|
+
pending_req_ttl_seconds = 120.
|
|
78
|
+
|
|
79
|
+
// ---- local action helpers -------------------------------------------------
|
|
80
|
+
fn _save (_) = (transaction::action::return_data ($kind -> $save_state)).
|
|
81
|
+
fn _ret (payload: any) = (transaction::action::return_data ($kind -> $data, $payload -> payload)).
|
|
82
|
+
fn _notify (payload: any) = (transaction::action::return_data ($kind -> $notify_agent, $payload -> payload)).
|
|
83
|
+
|
|
84
|
+
fn _ok_action (req_id: str, result_val: any) -> transaction::action::type
|
|
85
|
+
{
|
|
86
|
+
return _ret (a2a_capabilities::build_response ($req_id -> req_id, $ok -> TRUE, $result -> result_val, $err -> (,))).
|
|
87
|
+
}
|
|
88
|
+
fn _err_action (req_id: str, code: str, message: str) -> transaction::action::type
|
|
89
|
+
{
|
|
90
|
+
return a2a_capabilities::deny_action ($req_id -> req_id, $code -> code, $message -> message).
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ASYNC reply (CLUSTER_API.md §2 R1 routing): a host-fired callback runs at
|
|
94
|
+
// origin::user, so its ctx sender is the DAEMON/self — the async response must
|
|
95
|
+
// route to the ORIGINAL controller stored in the pending-req, never to the
|
|
96
|
+
// callback's own sender. So the callback's return_data is a routing wrapper
|
|
97
|
+
// ($target -> the stored sender cid, $response -> the response_envelope); the
|
|
98
|
+
// daemon ships $response to $target. Routing is always by sender, never by verb.
|
|
99
|
+
fn _async_reply (target: global_id, response: a2a_capabilities::response_envelope_t) -> transaction::action::type
|
|
100
|
+
{
|
|
101
|
+
return _ret ($target -> (_str target), $response -> response).
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fn _mint_handle (_) -> str
|
|
105
|
+
{
|
|
106
|
+
next_handle_seq -> next_handle_seq + 1.
|
|
107
|
+
return (_str (_get_container_id())) + "-" + (_str next_handle_seq).
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// member -> the native $result record the daemon marshals to JSON.
|
|
111
|
+
fn _member_view (m: member_t) -> any
|
|
112
|
+
{
|
|
113
|
+
return (
|
|
114
|
+
$cid -> (_str (m $cid)),
|
|
115
|
+
$role_id -> (m $role_id),
|
|
116
|
+
$name -> (m $name),
|
|
117
|
+
$bio -> (m $bio),
|
|
118
|
+
$persona -> (m $persona),
|
|
119
|
+
$monitoring -> (m $monitoring),
|
|
120
|
+
$caps -> (m $caps)
|
|
121
|
+
).
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fn _member_views (_) -> any[]
|
|
125
|
+
{
|
|
126
|
+
views is any[] = [].
|
|
127
|
+
sc cluster_members -- ( -> m) { views (_count views|) -> (_member_view m). }
|
|
128
|
+
return views.
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// core 3.2: build the roster-push actions IFF a control plane is bound. Bumps the
|
|
132
|
+
// seq so each push is ordered; carries the full member view (rosters are small,
|
|
133
|
+
// push-on-change). Returns [] when unbound (push_to_cp_actions also self-gates).
|
|
134
|
+
fn _push_roster_actions (_) -> transaction::action::type[]
|
|
135
|
+
{
|
|
136
|
+
if (a2a_messaging::bound_cp_cid NIL) == NIL { return []. }
|
|
137
|
+
seq = a2a_messaging::next_roster_seq NIL.
|
|
138
|
+
return a2a_messaging::push_to_cp_actions roster_update_tx ($version -> seq, $members -> (_member_views NIL)).
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// registry lookups for the sweep (the registry is keyed by global_id; create's
|
|
142
|
+
// op-key is the name, remove's is the stringified cid).
|
|
143
|
+
fn _find_by_name (name: str) -> member_t+
|
|
144
|
+
{
|
|
145
|
+
found is member_t+ = NIL.
|
|
146
|
+
sc cluster_members -- ( -> m) ?? found == NIL { if (m $name) == name { found -> m. } }
|
|
147
|
+
return found.
|
|
148
|
+
}
|
|
149
|
+
fn _present_cid_str (cidstr: str) -> bool
|
|
150
|
+
{
|
|
151
|
+
present is bool = FALSE.
|
|
152
|
+
sc cluster_members -- (cid -> _) ?? present == FALSE { if (_str cid) == cidstr { present -> TRUE. } }
|
|
153
|
+
return present.
|
|
154
|
+
}
|
|
155
|
+
fn _timeout_resp (req_id: str, message: str) -> a2a_capabilities::response_envelope_t
|
|
156
|
+
{
|
|
157
|
+
return a2a_capabilities::build_response ($req_id -> req_id, $ok -> FALSE, $result -> (,), $err -> ($code -> "timeout", $message -> message)).
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ---- verb handlers (each returns transaction::action::type[]) --------------
|
|
161
|
+
// list: read the registry (post-reconcile). Controller-gated upstream.
|
|
162
|
+
fn _h_list (ctx: any) -> transaction::action::type[]
|
|
163
|
+
{
|
|
164
|
+
return [ _ok_action ((ctx $req_id) safe str) ($members -> (_member_views NIL), $version -> (a2a_messaging::current_roster_seq NIL)) ].
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// set_bio: registry-only in v1 (§13-Q3). Updates the member's bio.
|
|
168
|
+
fn _h_set_bio (ctx: any) -> transaction::action::type[]
|
|
169
|
+
{
|
|
170
|
+
req_id = (ctx $req_id) safe str.
|
|
171
|
+
args = ctx $args.
|
|
172
|
+
cid = (args $cid) safe global_id.
|
|
173
|
+
m = cluster_members cid.
|
|
174
|
+
if m == NIL { return [ _err_action req_id "not_found" "no such child" ]. }
|
|
175
|
+
bio = (args $bio) safe str.
|
|
176
|
+
cluster_members cid -> ($cid -> (m $cid), $role_id -> (m $role_id), $name -> (m $name),
|
|
177
|
+
$bio -> bio, $persona -> (m $persona), $monitoring -> (m $monitoring), $caps -> (m $caps)).
|
|
178
|
+
out is transaction::action::type[] = [ _ok_action req_id ($cid -> (_str cid), $bio -> bio) ].
|
|
179
|
+
sc (_push_roster_actions NIL) -- ( -> a) { out (_count out|) -> a. }
|
|
180
|
+
out (_count out|) -> _save NIL.
|
|
181
|
+
return out.
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// set_persona: registry-only, host-authoritative (mirrors set_bio, §13-Q3).
|
|
185
|
+
fn _h_set_persona (ctx: any) -> transaction::action::type[]
|
|
186
|
+
{
|
|
187
|
+
req_id = (ctx $req_id) safe str.
|
|
188
|
+
args = ctx $args.
|
|
189
|
+
cid = (args $cid) safe global_id.
|
|
190
|
+
m = cluster_members cid.
|
|
191
|
+
if m == NIL { return [ _err_action req_id "not_found" "no such child" ]. }
|
|
192
|
+
persona = (args $persona) safe str.
|
|
193
|
+
cluster_members cid -> ($cid -> (m $cid), $role_id -> (m $role_id), $name -> (m $name),
|
|
194
|
+
$bio -> (m $bio), $persona -> persona, $monitoring -> (m $monitoring), $caps -> (m $caps)).
|
|
195
|
+
out is transaction::action::type[] = [ _ok_action req_id ($cid -> (_str cid), $persona -> persona) ].
|
|
196
|
+
sc (_push_roster_actions NIL) -- ( -> a) { out (_count out|) -> a. }
|
|
197
|
+
out (_count out|) -> _save NIL.
|
|
198
|
+
return out.
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// set_monitoring (ASYNC, single-gate host-mediated, C5): real per-child monitoring.
|
|
202
|
+
// The CP a child is bound to is DERIVED from the root's OWN ceremony-pinned
|
|
203
|
+
// monitoring_proxy (bound_cp_cid) — NEVER from $args (critic load-bearing condition:
|
|
204
|
+
// a child can only ever be bound to the root's ceremonied CP, so no bypass). Emits
|
|
205
|
+
// host_set_child_monitoring; the daemon host-binds (enable) or host-clears (disable)
|
|
206
|
+
// the CHILD's monitoring_proxy, then calls back confirm_child_monitoring. Enable
|
|
207
|
+
// requires the root to be bound to a cluster CP. (e) disable genuinely clears the
|
|
208
|
+
// child's proxy → forwarding stops. (i) child-visible notice = conscious deferred
|
|
209
|
+
// carry (mcp host children are non-human-facing) — see CLUSTER_API.md §9.
|
|
210
|
+
fn _h_set_monitoring (ctx: any) -> transaction::action::type[]
|
|
211
|
+
{
|
|
212
|
+
req_id = (ctx $req_id) safe str.
|
|
213
|
+
cid = ((ctx $args) $cid) safe global_id.
|
|
214
|
+
if (cluster_members cid) == NIL { return [ _err_action req_id "not_found" "no such child" ]. }
|
|
215
|
+
enabled = ((ctx $args) $enabled) safe bool.
|
|
216
|
+
sender_id = (ctx $sender_id) safe global_id.
|
|
217
|
+
date = (ctx $date) safe time.
|
|
218
|
+
handle = _mint_handle NIL.
|
|
219
|
+
pr = ($handle -> handle, $sender_id -> sender_id, $req_id -> req_id,
|
|
220
|
+
$verb -> "set_monitoring", $op_key -> (_str cid), $bio -> "", $date -> date).
|
|
221
|
+
ack = _ret (a2a_capabilities::build_response ($req_id -> req_id, $ok -> TRUE, $result -> ($pending -> TRUE), $err -> (,))).
|
|
222
|
+
|
|
223
|
+
if enabled == TRUE
|
|
224
|
+
{
|
|
225
|
+
// ENABLE: derive the CP from the root's OWN ceremony-pinned proxy, and carry
|
|
226
|
+
// the CP's verified AD so the daemon can HOST-INJECT it into the child as a
|
|
227
|
+
// contact (host_register_monitoring_cp) BEFORE host-running the ceremony — a
|
|
228
|
+
// network introduce is rejected by the child's CP-only acceptance gate and
|
|
229
|
+
// would race the ceremony anyway (§9).
|
|
230
|
+
cp = a2a_messaging::bound_cp_cid NIL.
|
|
231
|
+
if cp == NIL { return [ _err_action req_id "not_bound" "no bound cluster CP to monitor under" ]. }
|
|
232
|
+
cp_ad = a2a_messaging::peer_ads cp?.
|
|
233
|
+
if cp_ad == NIL { return [ _err_action req_id "internal" "bound CP has no stored address document" ]. }
|
|
234
|
+
pending_reqs handle -> pr.
|
|
235
|
+
return [
|
|
236
|
+
_notify ($event -> ev_set_monitoring, $cid -> cid, $cp_cid -> cp?, $cp_ad -> cp_ad?, $enabled -> TRUE, $pending_handle -> handle),
|
|
237
|
+
ack, _save NIL
|
|
238
|
+
].
|
|
239
|
+
}
|
|
240
|
+
// DISABLE: no CP needed — the daemon host-clears the child's monitoring_proxy.
|
|
241
|
+
pending_reqs handle -> pr.
|
|
242
|
+
return [
|
|
243
|
+
_notify ($event -> ev_set_monitoring, $cid -> cid, $enabled -> FALSE, $pending_handle -> handle),
|
|
244
|
+
ack, _save NIL
|
|
245
|
+
].
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// create (async): op-key = name (global). Short-circuit if the name already
|
|
249
|
+
// exists as a member or a pending create (RR-3 registry+pending check; the
|
|
250
|
+
// host-packet (d) check is enforced by the daemon enumerable-before-ack +
|
|
251
|
+
// reconcile). Emits host_provision_child + persists the pending-req.
|
|
252
|
+
fn _h_create (ctx: any) -> transaction::action::type[]
|
|
253
|
+
{
|
|
254
|
+
req_id = (ctx $req_id) safe str.
|
|
255
|
+
args = ctx $args.
|
|
256
|
+
name = (args $name) safe str.
|
|
257
|
+
bio = ((args $bio) == NIL ?? "" ; (args $bio) safe str).
|
|
258
|
+
|
|
259
|
+
dup is bool = FALSE.
|
|
260
|
+
sc cluster_members -- ( -> m) ?? dup == FALSE { if (m $name) == name { dup -> TRUE. } }
|
|
261
|
+
if dup == TRUE || (pending_create_names name) != NIL
|
|
262
|
+
{
|
|
263
|
+
return [ _err_action req_id "duplicate" "a child with that name already exists or is being created" ].
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
handle = _mint_handle NIL.
|
|
267
|
+
pending_reqs handle -> ($handle -> handle, $sender_id -> ((ctx $sender_id) safe global_id), $req_id -> req_id,
|
|
268
|
+
$verb -> "create", $op_key -> name, $bio -> bio, $date -> ((ctx $date) safe time)).
|
|
269
|
+
pending_create_names name -> TRUE.
|
|
270
|
+
return [
|
|
271
|
+
_notify ($event -> ev_provision, $name -> name, $bio -> bio, $pending_handle -> handle),
|
|
272
|
+
_ret (a2a_capabilities::build_response ($req_id -> req_id, $ok -> TRUE, $result -> ($pending -> TRUE), $err -> (,))),
|
|
273
|
+
_save NIL
|
|
274
|
+
].
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// remove (async): op-key = cid. Idempotent if already absent. Emits
|
|
278
|
+
// host_destroy_child + pending-req.
|
|
279
|
+
fn _h_remove (ctx: any) -> transaction::action::type[]
|
|
280
|
+
{
|
|
281
|
+
req_id = (ctx $req_id) safe str.
|
|
282
|
+
args = ctx $args.
|
|
283
|
+
cid = (args $cid) safe global_id.
|
|
284
|
+
if (cluster_members cid) == NIL
|
|
285
|
+
{
|
|
286
|
+
return [ _ok_action req_id ($cid -> (_str cid), $removed -> TRUE) ].
|
|
287
|
+
}
|
|
288
|
+
handle = _mint_handle NIL.
|
|
289
|
+
pending_reqs handle -> ($handle -> handle, $sender_id -> ((ctx $sender_id) safe global_id), $req_id -> req_id,
|
|
290
|
+
$verb -> "remove", $op_key -> (_str cid), $bio -> "", $date -> ((ctx $date) safe time)).
|
|
291
|
+
return [
|
|
292
|
+
_notify ($event -> ev_destroy, $cid -> cid, $pending_handle -> handle),
|
|
293
|
+
_ret (a2a_capabilities::build_response ($req_id -> req_id, $ok -> TRUE, $result -> ($pending -> TRUE), $err -> (,))),
|
|
294
|
+
_save NIL
|
|
295
|
+
].
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// contact (ASYNC, R2): the controller must become a DIRECT contact of the CHILD
|
|
299
|
+
// (chatWithAgent keys on childCid), so the invite must carry the CHILD's identity
|
|
300
|
+
// — and only the child's packet can sign its own AD. The root handler therefore
|
|
301
|
+
// CANNOT mint it (a root-minted invite = confused deputy → contacts the root).
|
|
302
|
+
// So: validate the cid is a real hosted child (criterion 5), persist a pending-
|
|
303
|
+
// req, and emit host_mint_child_invite; the daemon runs generate_invite IN the
|
|
304
|
+
// child packet and calls back register_child_invite with the child's invite blob.
|
|
305
|
+
// $result.invite shape is UNCHANGED (base64url of an invite bin) — WS-C unaffected.
|
|
306
|
+
fn _h_contact (ctx: any) -> transaction::action::type[]
|
|
307
|
+
{
|
|
308
|
+
req_id = (ctx $req_id) safe str.
|
|
309
|
+
cid = ((ctx $args) $cid) safe global_id.
|
|
310
|
+
if (cluster_members cid) == NIL { return [ _err_action req_id "not_found" "no such child" ]. }
|
|
311
|
+
handle = _mint_handle NIL.
|
|
312
|
+
pending_reqs handle -> ($handle -> handle, $sender_id -> ((ctx $sender_id) safe global_id), $req_id -> req_id,
|
|
313
|
+
$verb -> "contact", $op_key -> (_str cid), $bio -> "", $date -> ((ctx $date) safe time)).
|
|
314
|
+
return [
|
|
315
|
+
_notify ($event -> ev_mint_invite, $cid -> cid, $pending_handle -> handle),
|
|
316
|
+
_ret (a2a_capabilities::build_response ($req_id -> req_id, $ok -> TRUE, $result -> ($pending -> TRUE), $err -> (,))),
|
|
317
|
+
_save NIL
|
|
318
|
+
].
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// introduce: compose a2a_messaging::emit_pair (1:1 $peer_a/$peer_b) — both
|
|
322
|
+
// parties must be established contacts (peer_ads). Cluster-scoped child<->contact.
|
|
323
|
+
// resolve_contact aborts on an unknown ref (matches the existing introduce trn);
|
|
324
|
+
// a missing stored AD returns a clean not_found.
|
|
325
|
+
fn _h_introduce (ctx: any) -> transaction::action::type[]
|
|
326
|
+
{
|
|
327
|
+
req_id = (ctx $req_id) safe str.
|
|
328
|
+
args = ctx $args.
|
|
329
|
+
a_id = a2a_messaging::resolve_contact ((args $peer_a) safe str).
|
|
330
|
+
b_id = a2a_messaging::resolve_contact ((args $peer_b) safe str).
|
|
331
|
+
if a_id == b_id { return [ _err_action req_id "bad_args" "cannot introduce a node to itself" ]. }
|
|
332
|
+
ad_a = a2a_messaging::peer_ads a_id.
|
|
333
|
+
ad_b = a2a_messaging::peer_ads b_id.
|
|
334
|
+
if ad_a == NIL || ad_b == NIL { return [ _err_action req_id "not_found" "a peer is not an established contact" ]. }
|
|
335
|
+
actions is transaction::action::type[] =
|
|
336
|
+
a2a_messaging::emit_pair a_id ad_a? (a2a_messaging::introduce_name a_id)
|
|
337
|
+
b_id ad_b? (a2a_messaging::introduce_name b_id).
|
|
338
|
+
actions (_count actions|) -> _ok_action req_id ($ok -> TRUE).
|
|
339
|
+
return actions.
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ---- core.monitoring + core.connect handlers (cutover: ALL caps via dispatch) --
|
|
343
|
+
// core.monitoring: bind (auth-class BOOTSTRAP — runs pre-bind; THE envelope-path
|
|
344
|
+
// bootstrap) wraps the shared 6-digit ceremony and on success returns
|
|
345
|
+
// {manifest, members, config} one-shot (WS-C Q3); disable clears the binding.
|
|
346
|
+
fn monitoring_handler (ctx: any) -> transaction::action::type[]
|
|
347
|
+
{
|
|
348
|
+
verb = (ctx $verb) safe str.
|
|
349
|
+
req_id = (ctx $req_id) safe str.
|
|
350
|
+
sender_id = (ctx $sender_id) safe global_id.
|
|
351
|
+
if verb == "bind"
|
|
352
|
+
{
|
|
353
|
+
code = ((ctx $args) $code) safe str.
|
|
354
|
+
r = a2a_messaging::do_verify_proxy_code code sender_id.
|
|
355
|
+
if (r $verified) != TRUE
|
|
356
|
+
{
|
|
357
|
+
return [ _err_action req_id "bind_failed" (r $reason), _save NIL ].
|
|
358
|
+
}
|
|
359
|
+
mf = a2a_capabilities::current_manifest NIL.
|
|
360
|
+
cfg_cap = (mf $capabilities) a2a_capabilities::cap_configuration.
|
|
361
|
+
config_params = ((cfg_cap == NIL) ?? "" ; cfg_cap? $params).
|
|
362
|
+
return [
|
|
363
|
+
_ok_action req_id ($manifest -> mf, $members -> (_member_views NIL), $config -> config_params, $version -> (a2a_messaging::current_roster_seq NIL)),
|
|
364
|
+
_save NIL
|
|
365
|
+
].
|
|
366
|
+
}
|
|
367
|
+
if verb == "disable"
|
|
368
|
+
{
|
|
369
|
+
if (a2a_messaging::do_disable_monitoring sender_id) != TRUE
|
|
370
|
+
{
|
|
371
|
+
return [ _err_action req_id "not_bound" "sender is not the bound control plane" ].
|
|
372
|
+
}
|
|
373
|
+
return [ _ok_action req_id ($ok -> TRUE), _save NIL ].
|
|
374
|
+
}
|
|
375
|
+
return [ _err_action req_id "unknown_verb" ("core.monitoring has no verb: " + verb) ].
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// core.connect: introduce (generic peer<->peer; reuses the emit_pair composition).
|
|
379
|
+
fn connect_handler (ctx: any) -> transaction::action::type[]
|
|
380
|
+
{
|
|
381
|
+
verb = (ctx $verb) safe str.
|
|
382
|
+
if verb == "introduce" { return _h_introduce ctx. }
|
|
383
|
+
return [ _err_action ((ctx $req_id) safe str) "unknown_verb" ("core.connect has no verb: " + verb) ].
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ---- the capability handler (wired into a2a_capabilities::init $handlers) --
|
|
387
|
+
// Switches on ctx.$verb. Unknown verb -> unknown_verb error (no permissive
|
|
388
|
+
// fall-through, §3). contact/introduce compose a2a_messaging and land next.
|
|
389
|
+
fn cluster_handler (ctx: any) -> transaction::action::type[]
|
|
390
|
+
{
|
|
391
|
+
verb = (ctx $verb) safe str.
|
|
392
|
+
if verb == "list" { return _h_list ctx. }
|
|
393
|
+
if verb == "set_bio" { return _h_set_bio ctx. }
|
|
394
|
+
if verb == "set_persona" { return _h_set_persona ctx. }
|
|
395
|
+
if verb == "set_monitoring" { return _h_set_monitoring ctx. }
|
|
396
|
+
if verb == "create" { return _h_create ctx. }
|
|
397
|
+
if verb == "remove" { return _h_remove ctx. }
|
|
398
|
+
if verb == "contact" { return _h_contact ctx. }
|
|
399
|
+
if verb == "introduce" { return _h_introduce ctx. }
|
|
400
|
+
return [ _err_action ((ctx $req_id) safe str) "unknown_verb" ("core.cluster has no verb: " + verb) ].
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// ---- host callbacks (CLUSTER_API.md §7) ------------------------------------
|
|
404
|
+
// HOST-ONLY: origin::user (a remote peer is origin::external and cannot claim
|
|
405
|
+
// it) + MUST match an outstanding pending-req by $pending_handle (else abort);
|
|
406
|
+
// the pending-req is consumed atomically on first callback (RR-1 correctness /
|
|
407
|
+
// RR-3 idempotency). Not in any $handlers map, so dispatch cannot reach them.
|
|
408
|
+
trn register_provisioned_child _:($pending_handle -> handle: str,
|
|
409
|
+
$role_id -> role_id: str,
|
|
410
|
+
$child_ad -> child_ad: address_document_types::t_address_document)
|
|
411
|
+
{
|
|
412
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
413
|
+
pr = pending_reqs handle.
|
|
414
|
+
abort "register_provisioned_child: no matching pending-req (unsolicited callback rejected)." when pr == NIL || (pr $verb) != "create".
|
|
415
|
+
|
|
416
|
+
name = pr $op_key.
|
|
417
|
+
cid = child_ad $identity $container_id.
|
|
418
|
+
// C2 (RR-4 on the create path): populate caps so the new child is NOT
|
|
419
|
+
// introduce-hard-blocked (caps=[] reads connectKnown=true,connect=false)
|
|
420
|
+
// for the ≤300s until the next reconcile. Every child of this app
|
|
421
|
+
// advertises the SAME describe() capability set, so seed from my own live
|
|
422
|
+
// manifest; bio from the create's pending-req; role_id from the daemon
|
|
423
|
+
// (it delegated the role). reconcile later refreshes from host truth.
|
|
424
|
+
mf = a2a_capabilities::current_manifest NIL.
|
|
425
|
+
caps_list is str[] = [].
|
|
426
|
+
sc (mf $capabilities) -- (cap_id -> _) { caps_list (_count caps_list|) -> cap_id. }
|
|
427
|
+
cluster_members cid -> ($cid -> cid, $role_id -> role_id, $name -> name, $bio -> (pr $bio), $persona -> "",
|
|
428
|
+
$monitoring -> "off", $caps -> caps_list).
|
|
429
|
+
// consume the pending-req + its global op-key atomically.
|
|
430
|
+
pending_reqs handle -> NIL.
|
|
431
|
+
pending_create_names name -> NIL.
|
|
432
|
+
out is transaction::action::type[] = [
|
|
433
|
+
_async_reply (pr $sender_id) (a2a_capabilities::build_response (
|
|
434
|
+
$req_id -> (pr $req_id), $ok -> TRUE,
|
|
435
|
+
$result -> ($cid -> (_str cid), $name -> name, $monitoring -> "off"),
|
|
436
|
+
$err -> (,)
|
|
437
|
+
))
|
|
438
|
+
].
|
|
439
|
+
sc (_push_roster_actions NIL) -- ( -> a) { out (_count out|) -> a. }
|
|
440
|
+
out (_count out|) -> _save NIL.
|
|
441
|
+
return transaction::success out.
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
trn confirm_child_destroyed _:($pending_handle -> handle: str, $cid -> cid: global_id)
|
|
445
|
+
{
|
|
446
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
447
|
+
pr = pending_reqs handle.
|
|
448
|
+
abort "confirm_child_destroyed: no matching pending-req (unsolicited callback rejected)." when pr == NIL || (pr $verb) != "remove".
|
|
449
|
+
|
|
450
|
+
cluster_members cid -> NIL.
|
|
451
|
+
pending_reqs handle -> NIL.
|
|
452
|
+
out is transaction::action::type[] = [
|
|
453
|
+
_async_reply (pr $sender_id) (a2a_capabilities::build_response (
|
|
454
|
+
$req_id -> (pr $req_id), $ok -> TRUE,
|
|
455
|
+
$result -> ($cid -> (_str cid), $removed -> TRUE), $err -> (,)
|
|
456
|
+
))
|
|
457
|
+
].
|
|
458
|
+
sc (_push_roster_actions NIL) -- ( -> a) { out (_count out|) -> a. }
|
|
459
|
+
out (_count out|) -> _save NIL.
|
|
460
|
+
return transaction::success out.
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// register_child_invite (R2 contact callback): the daemon ran generate_invite IN
|
|
464
|
+
// the CHILD's packet (so $invite carries the child's identity/keys/cert — the
|
|
465
|
+
// redeemer reaches the CHILD, not the root) and hands the blob back here. SAME
|
|
466
|
+
// RR-1 guards as the other callbacks: origin::user FIRST + pending-handle match
|
|
467
|
+
// (verb=="contact") + atomic consume — no forged child-invite injection. Routes
|
|
468
|
+
// the invite to the STORED controller via $target (not the daemon/self).
|
|
469
|
+
trn register_child_invite _:($pending_handle -> handle: str, $invite -> invite_blob: bin)
|
|
470
|
+
{
|
|
471
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
472
|
+
pr = pending_reqs handle.
|
|
473
|
+
abort "register_child_invite: no matching pending-req (unsolicited callback rejected)." when pr == NIL || (pr $verb) != "contact".
|
|
474
|
+
pending_reqs handle -> NIL.
|
|
475
|
+
return transaction::success [
|
|
476
|
+
_async_reply (pr $sender_id) (a2a_capabilities::build_response (
|
|
477
|
+
$req_id -> (pr $req_id), $ok -> TRUE,
|
|
478
|
+
$result -> ($invite -> invite_blob), $err -> (,)
|
|
479
|
+
)),
|
|
480
|
+
_save NIL
|
|
481
|
+
].
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// confirm_child_monitoring (C5 set_monitoring callback): the daemon host-bound (or
|
|
485
|
+
// host-cleared) the CHILD's monitoring_proxy and reports the resulting state. SAME
|
|
486
|
+
// RR-1 guards (origin::user FIRST + pending-handle match verb==set_monitoring +
|
|
487
|
+
// atomic consume). Updates the registry $monitoring to host truth, routes the reply
|
|
488
|
+
// to the STORED controller via $target.
|
|
489
|
+
trn confirm_child_monitoring _:($pending_handle -> handle: str, $cid -> cid: global_id, $enabled -> enabled: bool)
|
|
490
|
+
{
|
|
491
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
492
|
+
pr = pending_reqs handle.
|
|
493
|
+
abort "confirm_child_monitoring: no matching pending-req (unsolicited callback rejected)." when pr == NIL || (pr $verb) != "set_monitoring".
|
|
494
|
+
pending_reqs handle -> NIL.
|
|
495
|
+
mon = (enabled == TRUE ?? "on" ; "off").
|
|
496
|
+
m = cluster_members cid.
|
|
497
|
+
if m != NIL { cluster_members cid -> ($cid -> (m $cid), $role_id -> (m $role_id), $name -> (m $name), $bio -> (m $bio), $persona -> (m $persona), $monitoring -> mon, $caps -> (m $caps)). }
|
|
498
|
+
out is transaction::action::type[] = [
|
|
499
|
+
_async_reply (pr $sender_id) (a2a_capabilities::build_response (
|
|
500
|
+
$req_id -> (pr $req_id), $ok -> TRUE,
|
|
501
|
+
$result -> ($cid -> (_str cid), $monitoring -> mon), $err -> (,)
|
|
502
|
+
))
|
|
503
|
+
].
|
|
504
|
+
sc (_push_roster_actions NIL) -- ( -> a) { out (_count out|) -> a. }
|
|
505
|
+
out (_count out|) -> _save NIL.
|
|
506
|
+
return transaction::success out.
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// reconcile: host truth ⨝ registry (RR-3). Adds host children missing from the
|
|
510
|
+
// registry (backfills out-of-band/CLI creates + the upgrade migration, RR-5),
|
|
511
|
+
// drops members absent from host truth. $pending_handle="" for timer/boot runs.
|
|
512
|
+
trn reconcile _:($pending_handle -> handle: str, $children -> children: child_rec_t[])
|
|
513
|
+
{
|
|
514
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
515
|
+
|
|
516
|
+
// host cids present this round (for the drop pass).
|
|
517
|
+
seen is (global_id ->> bool) = (,).
|
|
518
|
+
added is int = 0.
|
|
519
|
+
changed is int = 0.
|
|
520
|
+
sc children -- ( -> c)
|
|
521
|
+
{
|
|
522
|
+
cid = c $cid.
|
|
523
|
+
seen cid -> TRUE.
|
|
524
|
+
existing = cluster_members cid.
|
|
525
|
+
if existing == NIL { added -> added + 1. }
|
|
526
|
+
// Preserve CP-authoritative registry fields for an EXISTING member:
|
|
527
|
+
// $monitoring AND $bio (set_bio is registry-only v1, §13-Q3 — host
|
|
528
|
+
// truth must not revert it, C1). For a NEW (backfilled) child, seed
|
|
529
|
+
// both from host truth.
|
|
530
|
+
mon = ((existing == NIL) ?? "off" ; existing? $monitoring).
|
|
531
|
+
bio = ((existing == NIL) ?? (c $bio) ; existing? $bio).
|
|
532
|
+
persona = ((existing == NIL) ?? (c $persona) ; existing? $persona).
|
|
533
|
+
if existing != NIL && ((existing? $name) != (c $name) || (existing? $role_id) != (c $role_id) || (_count (existing? $caps)|) != (_count (c $caps)|)) { changed -> changed + 1. }
|
|
534
|
+
cluster_members cid -> ($cid -> cid, $role_id -> (c $role_id), $name -> (c $name),
|
|
535
|
+
$bio -> bio, $persona -> persona, $monitoring -> mon, $caps -> (c $caps)).
|
|
536
|
+
}
|
|
537
|
+
// drop registry members no longer hosted.
|
|
538
|
+
stale is global_id[] = [].
|
|
539
|
+
sc cluster_members -- (cid -> _) { if (seen cid) == NIL { stale (_count stale|) -> cid. } }
|
|
540
|
+
sc stale -- ( -> cid) { cluster_members cid -> NIL. }
|
|
541
|
+
|
|
542
|
+
// Return a status record (count) so the daemon's mutatingTx await RESOLVES
|
|
543
|
+
// immediately — a save-only trn emits no $data, so the await would block to
|
|
544
|
+
// timeout while holding the root lock (serializing against inbound control,
|
|
545
|
+
// WS-B E2E). $added/$dropped also give the daemon reconcile observability.
|
|
546
|
+
out is transaction::action::type[] = [
|
|
547
|
+
_ret ($ok -> TRUE, $added -> added, $dropped -> (_count stale|), $total -> (_count cluster_members|))
|
|
548
|
+
].
|
|
549
|
+
if (added > 0 || (_count stale|) > 0 || changed > 0) { sc (_push_roster_actions NIL) -- ( -> a) { out (_count out|) -> a. } }
|
|
550
|
+
out (_count out|) -> _save NIL.
|
|
551
|
+
return transaction::success out.
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// sweep_and_settle (§8 RR-3/R8-4): WS-B calls this AFTER a fresh reconcile (Option
|
|
555
|
+
// 2), so the registry already reflects host truth. Settles every pending-req aged
|
|
556
|
+
// past pending_req_ttl_seconds: create → adopt if the name now exists (a spawned-
|
|
557
|
+
// but-register-lost child, just adopted by reconcile) else timeout; remove → success
|
|
558
|
+
// if the cid is gone else timeout; contact → timeout (no invite arrived). Returns
|
|
559
|
+
// [($target,$response)…] for the daemon to ship to each STORED controller. C7: an
|
|
560
|
+
// expired create clears BOTH pending_reqs[handle] AND pending_create_names[name], so
|
|
561
|
+
// a lost register can never leave the name permanently un-creatable.
|
|
562
|
+
trn sweep_and_settle _
|
|
563
|
+
{
|
|
564
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
565
|
+
now = (current_transaction_info::get_transaction_time())?.
|
|
566
|
+
|
|
567
|
+
replies is any[] = [].
|
|
568
|
+
expired is str[] = [].
|
|
569
|
+
sc pending_reqs -- (handle -> pr)
|
|
570
|
+
{
|
|
571
|
+
if (_substract_seconds now (pr $date)) > pending_req_ttl_seconds
|
|
572
|
+
{
|
|
573
|
+
expired (_count expired|) -> handle.
|
|
574
|
+
req_id = pr $req_id.
|
|
575
|
+
verb = pr $verb.
|
|
576
|
+
resp is a2a_capabilities::response_envelope_t = (_timeout_resp req_id "request timed out").
|
|
577
|
+
if verb == "create"
|
|
578
|
+
{
|
|
579
|
+
m = _find_by_name (pr $op_key).
|
|
580
|
+
if m != NIL { resp -> a2a_capabilities::build_response ($req_id -> req_id, $ok -> TRUE, $result -> (_member_view m?), $err -> (,)). }
|
|
581
|
+
else { resp -> (_timeout_resp req_id "provision timed out"). }
|
|
582
|
+
}
|
|
583
|
+
if verb == "remove"
|
|
584
|
+
{
|
|
585
|
+
if (_present_cid_str (pr $op_key)) != TRUE { resp -> a2a_capabilities::build_response ($req_id -> req_id, $ok -> TRUE, $result -> ($removed -> TRUE), $err -> (,)). }
|
|
586
|
+
else { resp -> (_timeout_resp req_id "destroy timed out"). }
|
|
587
|
+
}
|
|
588
|
+
if verb == "contact" { resp -> (_timeout_resp req_id "invite mint timed out"). }
|
|
589
|
+
replies (_count replies|) -> ($target -> (_str (pr $sender_id)), $response -> resp).
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
// C7: clear expired pending-reqs AND, for creates, their global name op-key.
|
|
593
|
+
sc expired -- ( -> h)
|
|
594
|
+
{
|
|
595
|
+
ph = pending_reqs h.
|
|
596
|
+
if ph != NIL && (ph $verb) == "create" { pending_create_names (ph $op_key) -> NIL. }
|
|
597
|
+
pending_reqs h -> NIL.
|
|
598
|
+
}
|
|
599
|
+
return transaction::success [ _ret ($settled -> replies), _save NIL ].
|
|
600
|
+
}
|
|
601
|
+
}
|