@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.
Files changed (107) hide show
  1. package/LICENSE +98 -0
  2. package/README.md +231 -0
  3. package/dist/cli.js +446 -0
  4. package/dist/config.js +104 -0
  5. package/dist/daemon.js +353 -0
  6. package/dist/http.js +31 -0
  7. package/dist/log-store.js +48 -0
  8. package/dist/presence.js +157 -0
  9. package/dist/provision.js +29 -0
  10. package/dist/push.js +6 -0
  11. package/dist/web-push-sender.js +41 -0
  12. package/mufl_code/20E39399689579D5AFA5132C59DEDDD328C3BCA1A74D546F2193B36585DDD283.muflo +0 -0
  13. package/mufl_code/config.mufl +21 -0
  14. package/mufl_code/core/.github/FUNDING.yml +3 -0
  15. package/mufl_code/core/.github/workflows/docs.yml +39 -0
  16. package/mufl_code/core/.github/workflows/tests.yml +66 -0
  17. package/mufl_code/core/COMMERCIAL-LICENCE.md +25 -0
  18. package/mufl_code/core/COMPATIBILITY.md +221 -0
  19. package/mufl_code/core/CONTRIBUTING.md +37 -0
  20. package/mufl_code/core/LICENSE +98 -0
  21. package/mufl_code/core/NOTIFICATIONS_CONTRACT.md +290 -0
  22. package/mufl_code/core/README.md +91 -0
  23. package/mufl_code/core/SECURITY.md +40 -0
  24. package/mufl_code/core/TRADEMARKS.md +24 -0
  25. package/mufl_code/core/a2a_capabilities.mm +449 -0
  26. package/mufl_code/core/a2a_cluster.mm +601 -0
  27. package/mufl_code/core/a2a_control.mm +90 -0
  28. package/mufl_code/core/a2a_messaging.mm +4817 -0
  29. package/mufl_code/core/a2a_monitoring.mm +94 -0
  30. package/mufl_code/core/a2a_notification_integration.mm +144 -0
  31. package/mufl_code/core/a2a_notifications.mm +1127 -0
  32. package/mufl_code/core/a2a_protocol.mm +246 -0
  33. package/mufl_code/core/a2a_versions.mm +801 -0
  34. package/mufl_code/core/config.mufl +29 -0
  35. package/mufl_code/core/docs/.vitepress/config.mts +58 -0
  36. package/mufl_code/core/docs/citations.json +688 -0
  37. package/mufl_code/core/docs/guide/01-vendor-the-core.md +50 -0
  38. package/mufl_code/core/docs/guide/02-configure-and-compile.md +100 -0
  39. package/mufl_code/core/docs/guide/03-wire-the-host.md +154 -0
  40. package/mufl_code/core/docs/guide/04-connect-and-message.md +156 -0
  41. package/mufl_code/core/docs/guide/05-test-your-app.md +75 -0
  42. package/mufl_code/core/docs/guide/index.md +63 -0
  43. package/mufl_code/core/docs/how-it-works/capabilities-and-control.md +77 -0
  44. package/mufl_code/core/docs/how-it-works/cluster.md +65 -0
  45. package/mufl_code/core/docs/how-it-works/identity.md +56 -0
  46. package/mufl_code/core/docs/how-it-works/invites-and-contacts.md +76 -0
  47. package/mufl_code/core/docs/how-it-works/messaging.md +55 -0
  48. package/mufl_code/core/docs/how-it-works/monitoring-and-config.md +72 -0
  49. package/mufl_code/core/docs/how-it-works/overview.md +45 -0
  50. package/mufl_code/core/docs/how-it-works/versioning.md +116 -0
  51. package/mufl_code/core/docs/index.md +27 -0
  52. package/mufl_code/core/docs/package-lock.json +3467 -0
  53. package/mufl_code/core/docs/package.json +15 -0
  54. package/mufl_code/core/docs/reference/contributing.md +26 -0
  55. package/mufl_code/core/docs/reference/glossary.md +108 -0
  56. package/mufl_code/core/docs/reference/implementations.md +23 -0
  57. package/mufl_code/core/docs/reference/modules.md +19 -0
  58. package/mufl_code/core/docs/scripts/check-citations.mjs +36 -0
  59. package/mufl_code/core/docs/scripts/check-denylist.mjs +30 -0
  60. package/mufl_code/core/docs/scripts/gen-llms.mjs +32 -0
  61. package/mufl_code/core/docs/workflows/cluster.md +89 -0
  62. package/mufl_code/core/docs/workflows/contact-exchange.md +60 -0
  63. package/mufl_code/core/docs/workflows/contact-restore.md +60 -0
  64. package/mufl_code/core/docs/workflows/control-verbs.md +70 -0
  65. package/mufl_code/core/docs/workflows/index.md +57 -0
  66. package/mufl_code/core/docs/workflows/introductions.md +60 -0
  67. package/mufl_code/core/docs/workflows/messaging.md +75 -0
  68. package/mufl_code/core/docs/workflows/monitoring.md +84 -0
  69. package/mufl_code/core/docs/workflows/notifications.md +109 -0
  70. package/mufl_code/core/release-notes/0.1.md +60 -0
  71. package/mufl_code/core/release-notes/0.3.md +22 -0
  72. package/mufl_code/core/release-notes/0.4.md +97 -0
  73. package/mufl_code/core/tests/README.md +83 -0
  74. package/mufl_code/core/tests/compat/README.md +82 -0
  75. package/mufl_code/core/tests/compat/compat.mjs +245 -0
  76. package/mufl_code/core/tests/compat/compat_peer.mjs +146 -0
  77. package/mufl_code/core/tests/compat/run_compat.sh +96 -0
  78. package/mufl_code/core/tests/corpus.mjs +139 -0
  79. package/mufl_code/core/tests/corpus_actor.mu +242 -0
  80. package/mufl_code/core/tests/fatcompile_actor.mu +52 -0
  81. package/mufl_code/core/tests/mig.mjs +805 -0
  82. package/mufl_code/core/tests/mig_actor.mu +518 -0
  83. package/mufl_code/core/tests/migapp.mjs +431 -0
  84. package/mufl_code/core/tests/migapp_actor.mu +294 -0
  85. package/mufl_code/core/tests/mufl_semantics/run.sh +69 -0
  86. package/mufl_code/core/tests/mufl_semantics/t11_three_version.mufl +47 -0
  87. package/mufl_code/core/tests/mufl_semantics/t2_extra_fields.mufl +11 -0
  88. package/mufl_code/core/tests/mufl_semantics/t7_runtime.mufl +30 -0
  89. package/mufl_code/core/tests/mufl_semantics/t9_passthrough.mufl +19 -0
  90. package/mufl_code/core/tests/mufl_semantics/t_str_order.mufl +19 -0
  91. package/mufl_code/core/tests/notif.mjs +755 -0
  92. package/mufl_code/core/tests/notif_actor.mu +450 -0
  93. package/mufl_code/core/tests/protocol_container.mm +18 -0
  94. package/mufl_code/core/tests/run.sh +65 -0
  95. package/mufl_code/core/tests/run_corpus.sh +51 -0
  96. package/mufl_code/core/tests/run_fatcompile.sh +40 -0
  97. package/mufl_code/core/tests/run_mig.sh +51 -0
  98. package/mufl_code/core/tests/run_migapp.sh +51 -0
  99. package/mufl_code/core/tests/run_notif.sh +57 -0
  100. package/mufl_code/core/tests/test.mjs +750 -0
  101. package/mufl_code/core/tests/test_actor.mu +564 -0
  102. package/mufl_code/core/tests/test_common.mjs +93 -0
  103. package/mufl_code/core/version.mm +24 -0
  104. package/mufl_code/packet.mu +170 -0
  105. package/mufl_code/protocol_container.mm +18 -0
  106. package/package.json +43 -0
  107. package/scripts/gen-vapid.mjs +16 -0
@@ -0,0 +1,801 @@
1
+ // Shared ours versioned-type-registry library (core 0.5.0).
2
+ //
3
+ // THE backward-compatibility mechanism (SPEC v2 §1, COMPATIBILITY.md): for every
4
+ // wire input surface whose shape ever changed, this library declares — in one
5
+ // place, in MUFL types — one metadef PER SHIPPED WIRE VERSION of its payload,
6
+ // the accepted UNION (the handler's visible contract), the ordered version
7
+ // vector, the DISCRIMINATOR (the $pv wire-version tag, with a per-registry
8
+ // shape-inference rule for pre-$pv peers), and the dispatch fns:
9
+ //
10
+ // try_narrow_<r> (raw) — dispatch on the discriminator, exact-cast to the
11
+ // matched version's type; a below-floor or unrecognized payload returns
12
+ // an ERROR VALUE AS DATA (version_error_t), never an abort. Handlers on
13
+ // async inbound surfaces use THIS and surface the error to the local
14
+ // client via _notify_agent — the "old peers never crash" invariant plus
15
+ // the owner's "too-old versions return error-as-data" rule (Addition A).
16
+ // narrow_<r> (raw) — the strict form: same dispatch, aborts with the error
17
+ // message on failure. For corpus tests and callers where abort is right.
18
+ //
19
+ // Rules (REG-1…6, COMPATIBILITY.md): shipped v*_t metadefs are FROZEN — a wire
20
+ // shape change registers a NEW versioned type beside them and adds one branch;
21
+ // dispatch reads the discriminator off the RAW value and exact-casts (never
22
+ // cast-to-union as the selector — disjunction casts pick alternatives in
23
+ // canonical order and strip, see tests/mufl_semantics); a $pv NEWER than the
24
+ // newest registered version narrows as the newest (class-A changes are
25
+ // additive by taxonomy, extra fields strip safely); $pv is peer-asserted
26
+ // metadata — it gates parsing branches and diagnostics, NEVER authz.
27
+ //
28
+ // Self-contained: no library deps (base types only), loadable from anywhere.
29
+ library a2a_versions
30
+ {
31
+ // ---- this build's wire dialect id -------------------------------------
32
+ // Stamped as $pv on every 0.5.0+ core-originated send (cleartext $targ
33
+ // envelopes AND inside the boxed identity-bundle payloads). Minor-version
34
+ // ints: core 0.5.0 -> 5, 0.7.0 -> 7. Monotone; bump ONLY when a wire
35
+ // surface registers a new versioned type (SPEC Q2), not on every release.
36
+ // 7: the rcp surface (receive_receipt) registered in 0.7 — a peer whose
37
+ // learned dialect is >= 7 is KNOWN to parse receipts-era transactions,
38
+ // which the receipts gate uses to self-heal stale-caps contacts (see
39
+ // a2a_messaging::receipt_gate). Correcting an under-bump: 0.7.0 initially
40
+ // shipped still stamping 5, which left pre-receipts contacts permanently
41
+ // gated (caps only re-learn on invite/restore legs — the owner-reported
42
+ // single-tick bug).
43
+ // 8: the e2e surface (e2e_signed_message) registered in 0.8 — a peer whose
44
+ // learned dialect is >= 8 is KNOWN to parse the E2E signed-message envelope,
45
+ // the belt to the core.e2e-cap suspenders for send-side routing (mirrors the
46
+ // receipts -> 7 precedent). Additive; no existing >= 7 gate changes (8 >= 7).
47
+ wire_version = 8.
48
+ // The version floor: OSP (oldest supported peer) = core 0.2.0 -> 2.
49
+ // Raising this = an owner decision recorded in COMPATIBILITY.md (drop the
50
+ // v2 types from the unions + prune the corpus — a visible, reviewed act).
51
+ min_wire_version = 2.
52
+
53
+ // ---- safety-net reads (COMPAT-1, the layer UNDER the registry, REG-5) --
54
+ // For display/UX-class optional fields ONLY: defense in depth so a bare
55
+ // NIL is never again the only thing between an old peer and an EVAL_ERROR.
56
+ // The registry dispatch above is the documented mechanism; any new `safe`
57
+ // on wire data belongs inside a registry narrow/branch or one of these.
58
+ fn opt_str (v: any, dflt: str) -> str { return (v == NIL ?? dflt ; v safe str). }
59
+ fn opt_int (v: any, dflt: int) -> int { return (v == NIL ?? dflt ; v safe int). }
60
+ fn opt_bool (v: any, dflt: bool) -> bool { return (v == NIL ?? dflt ; v safe bool). }
61
+
62
+ // ---- runtime type-domain guards (M1) -----------------------------------
63
+ // `x safe T` ABORTS on a present-but-wrong-DOMAIN field (mufl has no
64
+ // try/catch), so presence checks alone cannot make try_narrow abort-free.
65
+ // Every NON-nullable field a registry cast reads is pre-checked against its
66
+ // _typeof runtime domain (probed on the vendored toolchain, mufl 0.8.0):
67
+ // str -> "STRING", int -> "INTEGER", bin -> "BINARY",
68
+ // global_id -> "STRING" (ids are hex strings at runtime).
69
+ // Residual (documented, accepted): `safe global_id` additionally
70
+ // hex-validates, so a STRING that is not valid hex still aborts inside the
71
+ // exact cast. No shipped version's sender can produce that (senders stamp
72
+ // real ids); reaching it requires a hostile hand-crafted box, which is the
73
+ // malformed/tamper class where a hard abort is the correct outcome.
74
+ td_str = "STRING".
75
+ td_int = "INTEGER".
76
+
77
+ fn is_str (v: any) -> bool { return v != NIL && (_typeof v) == td_str. }
78
+ fn is_int (v: any) -> bool { return v != NIL && (_typeof v) == td_int. }
79
+
80
+ // $pv as sent by the peer; 0 = pre-0.5 peer (never stamped it). Tolerant
81
+ // of a mistyped $pv (non-int => treated as unstamped, shape inference
82
+ // applies) so reading the discriminator itself can never abort (M1).
83
+ fn peer_pv (raw: any) -> int
84
+ {
85
+ p = raw $pv.
86
+ if is_int p != TRUE { return 0. }
87
+ return p safe int.
88
+ }
89
+
90
+ // ---- error-as-data (owner Addition A) ----------------------------------
91
+ // The first-class protocol return type for a version-incompatible input:
92
+ // when a peer's wire version is below the floor, or its payload matches no
93
+ // registered version, the handler RETURNS this value as data (via a
94
+ // _notify_agent $protocol_error event on async inbound surfaces) instead
95
+ // of aborting — the MCP server and every client resolve this type and
96
+ // render $message cleanly. Codes are STABLE wire contract:
97
+ // "peer_version_unsupported" — peer dialect below min_wire_version
98
+ // "payload_shape_unrecognized" — matches no registered version >= floor
99
+ metadef version_error_t: (
100
+ $code -> str,
101
+ $surface -> str, // registry id: "sir" | "cin" | "rst" | "acc" | ...
102
+ $message -> str, // human-readable, render-ready
103
+ $peer_version -> int, // as read/inferred by the discriminator
104
+ $min_supported -> int,
105
+ $max_supported -> int
106
+ ).
107
+
108
+ fn mk_version_error (surface: str, code: str, message: str, peer_version: int, max_supported: int) -> version_error_t
109
+ {
110
+ return (
111
+ $code -> code, $surface -> surface, $message -> message,
112
+ $peer_version -> peer_version,
113
+ $min_supported -> min_wire_version, $max_supported -> max_supported
114
+ ).
115
+ }
116
+
117
+ fn too_old_error (surface: str, peer_version: int, max_supported: int) -> version_error_t
118
+ {
119
+ return mk_version_error surface "peer_version_unsupported"
120
+ ("Peer speaks unsupported (too old) protocol version v" + (_str peer_version)
121
+ + "; minimum supported is v" + (_str min_wire_version) + ". Ask the peer to update.")
122
+ peer_version max_supported.
123
+ }
124
+
125
+ fn shape_error (surface: str, peer_version: int, max_supported: int) -> version_error_t
126
+ {
127
+ return mk_version_error surface "payload_shape_unrecognized"
128
+ ("Payload matches no supported wire shape of surface '" + surface
129
+ + "' (peer dialect v" + (_str peer_version) + ", supported v"
130
+ + (_str min_wire_version) + "..v" + (_str max_supported) + ").")
131
+ peer_version max_supported.
132
+ }
133
+
134
+ // ---- reusable registry scaffold (type-level, via type reductions) ------
135
+ // Generic union/vector pair; a registry may write these out directly (as
136
+ // the concrete registries below do — 2 lines each, SPEC Q10) or
137
+ // instantiate: `module sir instantiates versioned_input_3 with v5, v3, v2.`
138
+ generic module versioned_input_2 takes Vnewest, Voldest
139
+ {
140
+ metadef union_t: Vnewest || Voldest.
141
+ metadef versions_t: [Voldest, Vnewest].
142
+ }
143
+ generic module versioned_input_3 takes Vnewest, Vmid, Voldest
144
+ {
145
+ metadef union_t: Vnewest || Vmid || Voldest.
146
+ metadef versions_t: [Voldest, Vmid, Vnewest].
147
+ }
148
+
149
+ // ========================================================================
150
+ // REGISTRY "sir" — submit_invite_response, leg-1 boxed identity bundle.
151
+ // The incident surface: 0.3.0 added $name; 0.2.0 peers omit it.
152
+ // ========================================================================
153
+
154
+ // v0.2.0 — the deployed OSP shape (ours-mcp). NO $name. $ad stays `any`:
155
+ // the AD is verified downstream by verify_identity_bundle on the RAW
156
+ // payload, never by this cast. bin+ = nullable: absent reads NIL, passes.
157
+ metadef sir_payload_v2_t: (
158
+ $ad -> any,
159
+ $cert -> bin+,
160
+ $root_profile -> bin+,
161
+ $cp_binding -> bin+,
162
+ $invite_id -> global_id
163
+ ).
164
+ // v0.3.0 — added $name (the field whose strict read caused the incident).
165
+ metadef sir_payload_v3_t: (
166
+ $ad -> any,
167
+ $cert -> bin+,
168
+ $root_profile -> bin+,
169
+ $cp_binding -> bin+,
170
+ $invite_id -> global_id,
171
+ $name -> str
172
+ ).
173
+ // v0.5.0 — adds the wire-version tag + capability piggyback (SPEC §4).
174
+ metadef sir_payload_v5_t: (
175
+ $ad -> any,
176
+ $cert -> bin+,
177
+ $root_profile -> bin+,
178
+ $cp_binding -> bin+,
179
+ $invite_id -> global_id,
180
+ $name -> str,
181
+ $pv -> int,
182
+ $caps -> str[]+
183
+ ).
184
+
185
+ // The handler-visible contract: union of every version >= OSP (REG-3).
186
+ metadef sir_payload_t: sir_payload_v5_t || sir_payload_v3_t || sir_payload_v2_t.
187
+ // Ordered registry listing — the reviewable "what do we accept" artifact.
188
+ metadef sir_versions_t: [sir_payload_v2_t, sir_payload_v3_t, sir_payload_v5_t].
189
+ sir_max_version = 5.
190
+
191
+ // Discriminator: $pv when stamped (0.5.0+); pre-$pv shapes are inferred:
192
+ // $name present => v3, else v2. Registry-local rule, documented here only.
193
+ fn sir_version_of (raw: any) -> int
194
+ {
195
+ pv = peer_pv raw.
196
+ if pv != 0 { return pv. }
197
+ return ((raw $name) != NIL ?? 3 ; 2).
198
+ }
199
+
200
+ // Field presence + runtime-domain check for the REGISTERED version `reg`
201
+ // (2|3|5): the abort-free "does this payload actually carry its version's
202
+ // required fields, with the right domains" probe (M1) try_narrow uses to
203
+ // make no-match an ERROR VALUE instead of a cast abort. Nullable fields
204
+ // ($cert/$root_profile/$cp_binding bin+, $caps str[]+) are excluded: absent
205
+ // reads NIL and passes; present-but-mistyped is the malformed/hostile class
206
+ // (aborts in the cast, correctly). $ad is `any` — verified downstream.
207
+ fn sir_shape_ok (raw: any, reg: int) -> bool
208
+ {
209
+ if (raw $ad) == NIL || is_str (raw $invite_id) != TRUE { return FALSE. }
210
+ if reg >= 3 && is_str (raw $name) != TRUE { return FALSE. }
211
+ if reg >= 5 && is_int (raw $pv) != TRUE { return FALSE. }
212
+ return TRUE.
213
+ }
214
+
215
+ // try-narrow result: error-as-data union ($ok TRUE => $payload, else $err).
216
+ metadef sir_narrowed_t: ($ok -> bool, $payload -> sir_payload_t+, $err -> version_error_t+).
217
+
218
+ // REG-4 dispatch-then-narrow, error-as-data form (Addition A). $pv NEWER
219
+ // than we know narrows as our newest (class-A forward compat).
220
+ fn try_narrow_sir (raw: any) -> sir_narrowed_t
221
+ {
222
+ v = sir_version_of raw.
223
+ if v < min_wire_version
224
+ {
225
+ return ($ok -> FALSE, $payload -> NIL, $err -> too_old_error "sir" v sir_max_version).
226
+ }
227
+ // Registered-version mapping: 5+ -> v5, 3..4 -> v3 (0.4.x never shipped
228
+ // and its wire on this surface is byte-identical to 0.3's; a synthetic
229
+ // $pv=4 therefore narrows as v3 — requires $name, else shape error),
230
+ // else v2.
231
+ reg = (v >= 5 ?? 5 ; (v >= 3 ?? 3 ; 2)).
232
+ if sir_shape_ok raw reg != TRUE
233
+ {
234
+ return ($ok -> FALSE, $payload -> NIL, $err -> shape_error "sir" v sir_max_version).
235
+ }
236
+ if reg == 5 { return ($ok -> TRUE, $payload -> raw safe sir_payload_v5_t, $err -> NIL). }
237
+ if reg == 3 { return ($ok -> TRUE, $payload -> raw safe sir_payload_v3_t, $err -> NIL). }
238
+ return ($ok -> TRUE, $payload -> raw safe sir_payload_v2_t, $err -> NIL).
239
+ }
240
+
241
+ // Strict form: same dispatch, aborts with the stable message naming the
242
+ // surface — never an EVAL_ERROR from a NIL cast deeper in.
243
+ fn narrow_sir (raw: any) -> sir_payload_t
244
+ {
245
+ r = try_narrow_sir raw.
246
+ if (r $ok) != TRUE { abort ((r $err)? $message) when TRUE. }
247
+ return (r $payload)?.
248
+ }
249
+
250
+ // Versioned accessor — the per-branch "old way / new way" for the $name
251
+ // gap: v0.3/v0.5 carry $name in their registered type; v0.2 has no such
252
+ // field, so the v2 branch RETURNS the empty sentinel (caller falls back
253
+ // to the sender cid — explicit, typed degradation).
254
+ fn sir_joiner_name (input: sir_payload_t) -> str
255
+ {
256
+ raw = input as any.
257
+ v = sir_version_of raw.
258
+ if v >= 3 { return ((raw safe sir_payload_v3_t) $name). }
259
+ return "".
260
+ }
261
+
262
+ // Versioned $caps accessor (M2 pattern: re-cast from raw per version —
263
+ // never read version fields off the union binding): v5 carries $caps
264
+ // (nullable); every earlier version has no such field — empty list.
265
+ fn sir_caps (input: sir_payload_t) -> str[]
266
+ {
267
+ raw = input as any.
268
+ if (sir_version_of raw) >= 5
269
+ {
270
+ c = (raw safe sir_payload_v5_t) $caps.
271
+ if c != NIL { return c?. }
272
+ }
273
+ return [].
274
+ }
275
+
276
+ // ========================================================================
277
+ // REGISTRY "cin" — complete_invite, leg-3 boxed identity bundle.
278
+ // Never gained $name; v5 adds $pv/$caps only.
279
+ // ========================================================================
280
+ metadef cin_payload_v2_t: (
281
+ $ad -> any,
282
+ $cert -> bin+,
283
+ $root_profile -> bin+,
284
+ $cp_binding -> bin+,
285
+ $invite_id -> global_id
286
+ ).
287
+ metadef cin_payload_v5_t: (
288
+ $ad -> any,
289
+ $cert -> bin+,
290
+ $root_profile -> bin+,
291
+ $cp_binding -> bin+,
292
+ $invite_id -> global_id,
293
+ $pv -> int,
294
+ $caps -> str[]+
295
+ ).
296
+ metadef cin_payload_t: cin_payload_v5_t || cin_payload_v2_t.
297
+ metadef cin_versions_t: [cin_payload_v2_t, cin_payload_v5_t].
298
+ cin_max_version = 5.
299
+
300
+ // Pre-$pv cin shapes are all v2 (no inferable v3 delta on this surface).
301
+ fn cin_version_of (raw: any) -> int
302
+ {
303
+ pv = peer_pv raw.
304
+ return (pv != 0 ?? pv ; 2).
305
+ }
306
+
307
+ fn cin_shape_ok (raw: any, reg: int) -> bool
308
+ {
309
+ if (raw $ad) == NIL || is_str (raw $invite_id) != TRUE { return FALSE. }
310
+ if reg >= 5 && is_int (raw $pv) != TRUE { return FALSE. }
311
+ return TRUE.
312
+ }
313
+
314
+ metadef cin_narrowed_t: ($ok -> bool, $payload -> cin_payload_t+, $err -> version_error_t+).
315
+
316
+ fn try_narrow_cin (raw: any) -> cin_narrowed_t
317
+ {
318
+ v = cin_version_of raw.
319
+ if v < min_wire_version
320
+ {
321
+ return ($ok -> FALSE, $payload -> NIL, $err -> too_old_error "cin" v cin_max_version).
322
+ }
323
+ reg = (v >= 5 ?? 5 ; 2).
324
+ if cin_shape_ok raw reg != TRUE
325
+ {
326
+ return ($ok -> FALSE, $payload -> NIL, $err -> shape_error "cin" v cin_max_version).
327
+ }
328
+ if reg >= 5 { return ($ok -> TRUE, $payload -> raw safe cin_payload_v5_t, $err -> NIL). }
329
+ return ($ok -> TRUE, $payload -> raw safe cin_payload_v2_t, $err -> NIL).
330
+ }
331
+
332
+ fn narrow_cin (raw: any) -> cin_payload_t
333
+ {
334
+ r = try_narrow_cin raw.
335
+ if (r $ok) != TRUE { abort ((r $err)? $message) when TRUE. }
336
+ return (r $payload)?.
337
+ }
338
+
339
+ fn cin_caps (input: cin_payload_t) -> str[]
340
+ {
341
+ raw = input as any.
342
+ if (cin_version_of raw) >= 5
343
+ {
344
+ c = (raw safe cin_payload_v5_t) $caps.
345
+ if c != NIL { return c?. }
346
+ }
347
+ return [].
348
+ }
349
+
350
+ // ========================================================================
351
+ // REGISTRY "rst" — contact-restore boxed identity bundles (legs 1 and 2,
352
+ // submit_restore_response / complete_restore). Same bundle as cin with
353
+ // $rid instead of $invite_id; v5 adds $pv/$caps.
354
+ // ========================================================================
355
+ metadef rst_payload_v2_t: (
356
+ $ad -> any,
357
+ $cert -> bin+,
358
+ $root_profile -> bin+,
359
+ $cp_binding -> bin+,
360
+ $rid -> global_id
361
+ ).
362
+ metadef rst_payload_v5_t: (
363
+ $ad -> any,
364
+ $cert -> bin+,
365
+ $root_profile -> bin+,
366
+ $cp_binding -> bin+,
367
+ $rid -> global_id,
368
+ $pv -> int,
369
+ $caps -> str[]+
370
+ ).
371
+ metadef rst_payload_t: rst_payload_v5_t || rst_payload_v2_t.
372
+ metadef rst_versions_t: [rst_payload_v2_t, rst_payload_v5_t].
373
+ rst_max_version = 5.
374
+
375
+ fn rst_version_of (raw: any) -> int
376
+ {
377
+ pv = peer_pv raw.
378
+ return (pv != 0 ?? pv ; 2).
379
+ }
380
+
381
+ fn rst_shape_ok (raw: any, reg: int) -> bool
382
+ {
383
+ if (raw $ad) == NIL || is_str (raw $rid) != TRUE { return FALSE. }
384
+ if reg >= 5 && is_int (raw $pv) != TRUE { return FALSE. }
385
+ return TRUE.
386
+ }
387
+
388
+ metadef rst_narrowed_t: ($ok -> bool, $payload -> rst_payload_t+, $err -> version_error_t+).
389
+
390
+ fn try_narrow_rst (raw: any) -> rst_narrowed_t
391
+ {
392
+ v = rst_version_of raw.
393
+ if v < min_wire_version
394
+ {
395
+ return ($ok -> FALSE, $payload -> NIL, $err -> too_old_error "rst" v rst_max_version).
396
+ }
397
+ reg = (v >= 5 ?? 5 ; 2).
398
+ if rst_shape_ok raw reg != TRUE
399
+ {
400
+ return ($ok -> FALSE, $payload -> NIL, $err -> shape_error "rst" v rst_max_version).
401
+ }
402
+ if reg >= 5 { return ($ok -> TRUE, $payload -> raw safe rst_payload_v5_t, $err -> NIL). }
403
+ return ($ok -> TRUE, $payload -> raw safe rst_payload_v2_t, $err -> NIL).
404
+ }
405
+
406
+ fn narrow_rst (raw: any) -> rst_payload_t
407
+ {
408
+ r = try_narrow_rst raw.
409
+ if (r $ok) != TRUE { abort ((r $err)? $message) when TRUE. }
410
+ return (r $payload)?.
411
+ }
412
+
413
+ fn rst_caps (input: rst_payload_t) -> str[]
414
+ {
415
+ raw = input as any.
416
+ if (rst_version_of raw) >= 5
417
+ {
418
+ c = (raw safe rst_payload_v5_t) $caps.
419
+ if c != NIL { return c?. }
420
+ }
421
+ return [].
422
+ }
423
+
424
+ // ========================================================================
425
+ // REGISTRY "acc" — legacy accept_contact $targ args (encrypted channel).
426
+ // Hygiene sibling of sir: 0.3.0 added $joiner_name; 0.2.0 omits it. The
427
+ // path itself is slated for class-C removal at the next OSP raise. Never
428
+ // gained $pv (0.5.0 does not send it), so v3 is the newest registered.
429
+ // ========================================================================
430
+ metadef acc_args_v2_t: (
431
+ $invite_id -> global_id,
432
+ $joiner_ad -> any,
433
+ $joiner_cert -> bin+,
434
+ $joiner_root_profile -> bin+,
435
+ $joiner_cp_binding -> bin+
436
+ ).
437
+ metadef acc_args_v3_t: (
438
+ $invite_id -> global_id,
439
+ $joiner_ad -> any,
440
+ $joiner_cert -> bin+,
441
+ $joiner_root_profile -> bin+,
442
+ $joiner_cp_binding -> bin+,
443
+ $joiner_name -> str
444
+ ).
445
+ metadef acc_args_t: acc_args_v3_t || acc_args_v2_t.
446
+ metadef acc_versions_t: [acc_args_v2_t, acc_args_v3_t].
447
+ acc_max_version = 3.
448
+
449
+ fn acc_version_of (raw: any) -> int
450
+ {
451
+ pv = peer_pv raw.
452
+ if pv != 0 { return pv. }
453
+ return ((raw $joiner_name) != NIL ?? 3 ; 2).
454
+ }
455
+
456
+ fn acc_shape_ok (raw: any, reg: int) -> bool
457
+ {
458
+ if is_str (raw $invite_id) != TRUE || (raw $joiner_ad) == NIL { return FALSE. }
459
+ if reg >= 3 && is_str (raw $joiner_name) != TRUE { return FALSE. }
460
+ return TRUE.
461
+ }
462
+
463
+ metadef acc_narrowed_t: ($ok -> bool, $payload -> acc_args_t+, $err -> version_error_t+).
464
+
465
+ fn try_narrow_acc (raw: any) -> acc_narrowed_t
466
+ {
467
+ v = acc_version_of raw.
468
+ if v < min_wire_version
469
+ {
470
+ return ($ok -> FALSE, $payload -> NIL, $err -> too_old_error "acc" v acc_max_version).
471
+ }
472
+ reg = (v >= 3 ?? 3 ; 2).
473
+ if acc_shape_ok raw reg != TRUE
474
+ {
475
+ return ($ok -> FALSE, $payload -> NIL, $err -> shape_error "acc" v acc_max_version).
476
+ }
477
+ if reg >= 3 { return ($ok -> TRUE, $payload -> raw safe acc_args_v3_t, $err -> NIL). }
478
+ return ($ok -> TRUE, $payload -> raw safe acc_args_v2_t, $err -> NIL).
479
+ }
480
+
481
+ fn narrow_acc (raw: any) -> acc_args_t
482
+ {
483
+ r = try_narrow_acc raw.
484
+ if (r $ok) != TRUE { abort ((r $err)? $message) when TRUE. }
485
+ return (r $payload)?.
486
+ }
487
+
488
+ // acc analogue of sir_joiner_name: v3 carries $joiner_name; v2 has no such
489
+ // field — return the empty sentinel (caller falls back to the sender cid).
490
+ fn acc_joiner_name (input: acc_args_t) -> str
491
+ {
492
+ raw = input as any.
493
+ v = acc_version_of raw.
494
+ if v >= 3 { return ((raw safe acc_args_v3_t) $joiner_name). }
495
+ return "".
496
+ }
497
+
498
+ // ========================================================================
499
+ // REGISTRY "e2e" — e2e_signed_message, the end-to-end-encrypted message
500
+ // VARIANT (core 0.8.0, class-B NEW surface). The decode branch (adapt) keys
501
+ // on presence of the $e2e_envelope MARKER field, NOT a tx name; ours
502
+ // registers the wire variant so an old/hostile unit narrows it as
503
+ // error-as-data instead of a SAFE-cast crash (the fix-3 class). Single
504
+ // registered version, LOAD-BEARING (returns $payload via an exact `safe`
505
+ // cast, `sir`-style — not the best-effort `rcp` ignore shape). $ciphertext is
506
+ // an OPAQUE Olm blob the core never parses; all crypto + session state live
507
+ // in the adapt `e2e` library, which also owns the receive-side decrypt+verify.
508
+ // ========================================================================
509
+ // The INNER opaque body (contract `t_e2e_envelope`). $pv rides HERE — it is
510
+ // the version discriminator for the whole signed message.
511
+ metadef e2e_env_v1_t: (
512
+ $session_id -> any, // adapt-derived session id: BINARY on the SHIPPED wire
513
+ // (session_id() bytes, e2e.mm); a hex string is also accepted
514
+ // (§5.9-1 dual-accept). `any` here + the flat cast target below
515
+ // (e2e_signed_message keeps $e2e_envelope `any`) mean NO frozen
516
+ // type mutates; e2e_env_shape_ok validates the domain.
517
+ $olm_type -> int, // 0 = PRE_KEY (establishment inside $ciphertext), 1 = normal ratchet
518
+ $ciphertext -> bin, // opaque Olm blob
519
+ $pv -> int+ // wire dialect stamp (= wire_version at send); nullable in the cast so an
520
+ // unstamped body narrows (shape_ok guards present-non-int -> shape_error)
521
+ ).
522
+ // The registered WIRE MESSAGE VARIANT (contract `e2e_signed_message`). Both
523
+ // fields are `any`: the inner envelope is fully validated by e2e_env_shape_ok
524
+ // BEFORE the cast (exactly as `sir` validates then leaves $ad `any`), and
525
+ // $emsignature — the outer sender-auth (default_sign over _value_id(env),
526
+ // a crypto_signature on the wire) — is verified by adapt's decode hook, not
527
+ // ours. Keeping both `any` makes the cast a flat abort-free pass-through and
528
+ // avoids a nested-metadef type-level reduction. e2e_env_v1_t above documents
529
+ // the inner shape (used by the send-side builder + e2e_env_shape_ok's reads).
530
+ metadef e2e_signed_message_v1_t: (
531
+ $e2e_envelope -> any,
532
+ $emsignature -> any
533
+ ).
534
+ metadef e2e_msg_t: e2e_signed_message_v1_t.
535
+ metadef e2e_versions_t: [e2e_signed_message_v1_t].
536
+ e2e_max_version = 8.
537
+
538
+ // Discriminator lives INSIDE $e2e_envelope. Unstamped defaults to e2e's
539
+ // introduction dialect (8) — an e2e envelope only exists 0.8.0+ (cf. rcp -> 7).
540
+ fn e2e_version_of (raw: any) -> int
541
+ {
542
+ env = raw $e2e_envelope.
543
+ if env == NIL { return 8. } // no $e2e_envelope marker -> default; shape_ok rejects it
544
+ pv = peer_pv env.
545
+ return (pv != 0 ?? pv ; 8).
546
+ }
547
+
548
+ // Abort-free INNER-envelope probe (M1): every NON-nullable field checked
549
+ // against its runtime domain before the cast. The cast target keeps
550
+ // $e2e_envelope `any` (flat pass-through), so this probe is the ONLY domain
551
+ // gate — no residual `safe global_id` hex-validation happens on $session_id.
552
+ fn e2e_env_shape_ok (env: any) -> bool
553
+ {
554
+ ct = env $ciphertext.
555
+ pv = env $pv.
556
+ sid = env $session_id.
557
+ // $session_id DUAL-ACCEPT (§5.9-1): the SHIPPED adapt wire is BINARY
558
+ // (session_id() bytes); the historically-documented form was a hex STRING.
559
+ // Accept BOTH — an additive relaxation that rejects strictly less (a present-
560
+ // but-mistyped session_id, e.g. an int, still fails -> shape_error). All 0.9
561
+ // stores/payloads carry canonical BINARY and compare bytewise.
562
+ sid_ok = is_str sid || (sid != NIL && (_typeof sid) == "BINARY").
563
+ // $pv: absent is tolerated (defaults to 8 in e2e_version_of); present MUST be an int.
564
+ // Every 0.8.0+ e2e sender stamps a real int $pv, so a present-non-int is malformed ->
565
+ // shape_error (error-as-data), which also keeps the `$pv -> int+` cast abort-free.
566
+ return sid_ok && is_int (env $olm_type)
567
+ && ct != NIL && (_typeof ct) == "BINARY"
568
+ && (pv == NIL || is_int pv).
569
+ }
570
+
571
+ // Whole-variant shape: the $e2e_envelope MARKER present + a valid inner
572
+ // envelope + an $emsignature present (its crypto domain is adapt's to verify).
573
+ fn e2e_msg_shape_ok (raw: any) -> bool
574
+ {
575
+ env = raw $e2e_envelope.
576
+ if env == NIL { return FALSE. }
577
+ return e2e_env_shape_ok env && (raw $emsignature) != NIL.
578
+ }
579
+
580
+ metadef e2e_narrowed_t: ($ok -> bool, $payload -> e2e_msg_t+, $err -> version_error_t+).
581
+
582
+ // REG-4 dispatch-then-narrow, error-as-data (Addition A). Single registered
583
+ // version: any $pv >= floor with a valid shape narrows as v1 (class-A forward
584
+ // compat); a future dialect is accepted, not rejected.
585
+ fn try_narrow_e2e (raw: any) -> e2e_narrowed_t
586
+ {
587
+ v = e2e_version_of raw.
588
+ if v < min_wire_version
589
+ {
590
+ return ($ok -> FALSE, $payload -> NIL, $err -> too_old_error "e2e" v e2e_max_version).
591
+ }
592
+ if e2e_msg_shape_ok raw != TRUE
593
+ {
594
+ return ($ok -> FALSE, $payload -> NIL, $err -> shape_error "e2e" v e2e_max_version).
595
+ }
596
+ return ($ok -> TRUE, $payload -> raw safe e2e_signed_message_v1_t, $err -> NIL).
597
+ }
598
+
599
+ // Strict form — same dispatch, aborts with the stable surface-named message.
600
+ fn narrow_e2e (raw: any) -> e2e_msg_t
601
+ {
602
+ r = try_narrow_e2e raw.
603
+ if (r $ok) != TRUE { abort ((r $err)? $message) when TRUE. }
604
+ return (r $payload)?.
605
+ }
606
+
607
+ // ========================================================================
608
+ // Single-version registrations (REG-1) — surfaces whose shape never
609
+ // changed since OSP. Registering them pre-wires the change procedure: a
610
+ // future shape change adds a v<next>_t + one union branch here instead of
611
+ // an ad-hoc read. Their handlers keep field-by-field tolerant reads (the
612
+ // shapes are all-optional beyond the required core); the entries below are
613
+ // the registry index + the $pv learning hook. Remaining envelope surfaces
614
+ // (invite/restore cleartext envelopes, enroll/ingest/control/monitoring,
615
+ // notify_*) are catalogued in COMPATIBILITY.md.
616
+ // ========================================================================
617
+
618
+ // receive_message $targ (v5 adds only the $pv stamp — same shape).
619
+ metadef rmsg_targ_v2_t: (
620
+ $text -> str,
621
+ $wire_id -> str+,
622
+ $reply_to -> any,
623
+ $pv -> int+
624
+ ).
625
+ metadef rmsg_targ_t: rmsg_targ_v2_t.
626
+ fn rmsg_version_of (raw: any) -> int
627
+ {
628
+ pv = peer_pv raw.
629
+ return (pv != 0 ?? pv ; 2).
630
+ }
631
+
632
+ // receive_receipt $targ (core 0.7.0, class-B new surface — single version;
633
+ // reachable only behind positive core.receipts.* caps, so no $pv bump).
634
+ metadef rcp_targ_v1_t: (
635
+ $kind -> str, // "delivered" | "read" (frozen id strings)
636
+ $wire_ids -> any, // str[] — 1..N stable wire ids (shared msg+file namespace)
637
+ $date -> time+,
638
+ $pv -> int+
639
+ ).
640
+ metadef rcp_targ_t: rcp_targ_v1_t.
641
+ fn rcp_version_of (raw: any) -> int
642
+ {
643
+ pv = peer_pv raw.
644
+ return (pv != 0 ?? pv ; 7).
645
+ }
646
+ // Abort-free classification (M1): the handler IGNORES (success, no-op) any
647
+ // payload failing this — receipts are best-effort UX, never load-bearing.
648
+ // $wire_ids must be a real list (lists ride IMMUTABLE_DICTIONARY at
649
+ // runtime): iterating a scalar would char-walk strings / abort on ints.
650
+ fn rcp_shape_ok (raw: any) -> bool
651
+ {
652
+ ids = raw $wire_ids.
653
+ return is_str (raw $kind) && ids != NIL && (_typeof ids) == "IMMUTABLE_DICTIONARY".
654
+ }
655
+
656
+ // receive_file $targ (v5 adds only the $pv stamp — same shape).
657
+ metadef rfil_targ_v2_t: (
658
+ $filename -> str,
659
+ $data -> bin,
660
+ $mime -> str+,
661
+ $wire_id -> str+,
662
+ $reply_to -> any,
663
+ $pv -> int+
664
+ ).
665
+ metadef rfil_targ_t: rfil_targ_v2_t.
666
+ fn rfil_version_of (raw: any) -> int
667
+ {
668
+ pv = peer_pv raw.
669
+ return (pv != 0 ?? pv ; 2).
670
+ }
671
+
672
+ // ========================================================================
673
+ // REGISTRY "mgb" — e2e-migration bundle (core 0.9.0, class-B NEW surface,
674
+ // wire 9). The offer ($name e2e_migrate_offer) and ack (e2e_migrate_ack)
675
+ // legs SHARE this one registry, exactly as "rst" serves both restore legs.
676
+ // The payload IS an identity bundle (sir-shape: $ad `any`, verified
677
+ // downstream by verify_identity_bundle on the RAW value, never by this cast)
678
+ // PLUS the agreement fields ($nonce, ack-only $peer_nonce). Introduction
679
+ // dialect 9 — this surface cannot predate 0.9.0, so an unstamped body is
680
+ // malformed (a real sender always stamps $pv, cf. the offer/ack builders).
681
+ // ========================================================================
682
+ metadef mgb_targ_v1_t: (
683
+ $ad -> any, // FRESH signed ADv2 (must carry $e2e_bundle — checked downstream)
684
+ $cert -> bin+,
685
+ $root_profile -> bin+,
686
+ $cp_binding -> bin+,
687
+ $nonce -> bin, // sender's proposal nonce (agreement uniquifier, not key material)
688
+ $peer_nonce -> bin+, // ack only: echo of the offer nonce (pairs the exchange); absent on offer
689
+ $pv -> int, // wire dialect stamp (= wire_version at send); REQUIRED on this surface
690
+ $caps -> str[]+ // capability piggyback (the missing caps/AD refresh, spec §5.4 step 2)
691
+ ).
692
+ metadef mgb_targ_t: mgb_targ_v1_t.
693
+ metadef mgb_versions_t: [mgb_targ_v1_t].
694
+ mgb_max_version = 9.
695
+
696
+ // Discriminator: $pv when stamped; unstamped defaults to the introduction
697
+ // dialect 9 (cf. e2e -> 8). shape_ok then rejects the unstamped body since
698
+ // every real mgb sender stamps an int $pv.
699
+ fn mgb_version_of (raw: any) -> int
700
+ {
701
+ pv = peer_pv raw.
702
+ return (pv != 0 ?? pv ; 9).
703
+ }
704
+
705
+ // Abort-free classification (M1): the load-bearing NON-nullable fields
706
+ // ($ad present, $nonce a real BINARY, $pv a real int) checked before the
707
+ // cast so no-match is error-as-data, not a SAFE-cast abort. Nullable fields
708
+ // ($cert/$root_profile/$cp_binding bin+, $peer_nonce bin+, $caps str[]+) are
709
+ // excluded: absent reads NIL and passes; present-but-mistyped is the
710
+ // hostile/malformed class (aborts in the cast, correctly). $ad is `any`.
711
+ fn mgb_shape_ok (raw: any) -> bool
712
+ {
713
+ n = raw $nonce.
714
+ return (raw $ad) != NIL && n != NIL && (_typeof n) == "BINARY" && is_int (raw $pv).
715
+ }
716
+
717
+ metadef mgb_narrowed_t: ($ok -> bool, $payload -> mgb_targ_t+, $err -> version_error_t+).
718
+
719
+ // REG-4 dispatch-then-narrow, error-as-data. Single registered version: any
720
+ // $pv >= floor with a valid shape narrows as v1 (class-A forward compat).
721
+ fn try_narrow_mgb (raw: any) -> mgb_narrowed_t
722
+ {
723
+ v = mgb_version_of raw.
724
+ if v < min_wire_version
725
+ {
726
+ return ($ok -> FALSE, $payload -> NIL, $err -> too_old_error "mgb" v mgb_max_version).
727
+ }
728
+ if mgb_shape_ok raw != TRUE
729
+ {
730
+ return ($ok -> FALSE, $payload -> NIL, $err -> shape_error "mgb" v mgb_max_version).
731
+ }
732
+ return ($ok -> TRUE, $payload -> raw safe mgb_targ_v1_t, $err -> NIL).
733
+ }
734
+
735
+ // Strict form — same dispatch, aborts with the stable surface-named message.
736
+ fn narrow_mgb (raw: any) -> mgb_targ_t
737
+ {
738
+ r = try_narrow_mgb raw.
739
+ if (r $ok) != TRUE { abort ((r $err)? $message) when TRUE. }
740
+ return (r $payload)?.
741
+ }
742
+
743
+ // ========================================================================
744
+ // REGISTRY "mgc" — e2e-migration commit/confirm inner $targ (core 0.9.0,
745
+ // class-B NEW surface, wire 9). Both the commit (e2e_migrate_commit) and
746
+ // confirm (e2e_migrate_confirm) inner transactions SHARE this registry.
747
+ // These ride INSIDE the fresh Olm session (the commit is a PRE_KEY on the
748
+ // staged session; the confirm rides the new session), so $pv is nullable
749
+ // here — the E2E transcript already authenticates the sender. $epoch binds
750
+ // the agreement; $session_id (commit only) must byte-equal the CARRIER
751
+ // envelope's session id, binding the epoch to the concrete fresh session.
752
+ // Both are CANONICAL BYTES (adapt session_id() output) — every comparison in
753
+ // the FSM path is byte-equality on this form, no hex re-encoding anywhere.
754
+ // ========================================================================
755
+ metadef mgc_targ_v1_t: (
756
+ $epoch -> bin, // the agreement epoch id (32 bytes, mig_epoch)
757
+ $session_id -> bin+, // present on COMMIT, absent on CONFIRM
758
+ $pv -> int+ // nullable: the E2E session authenticates; unstamped defaults to 9
759
+ ).
760
+ metadef mgc_targ_t: mgc_targ_v1_t.
761
+ metadef mgc_versions_t: [mgc_targ_v1_t].
762
+ mgc_max_version = 9.
763
+
764
+ fn mgc_version_of (raw: any) -> int
765
+ {
766
+ pv = peer_pv raw.
767
+ return (pv != 0 ?? pv ; 9).
768
+ }
769
+
770
+ // Abort-free classification (M1): $epoch is the only required field (a real
771
+ // BINARY); $session_id, when present, MUST be BINARY (commit form) — absent
772
+ // is the confirm form and passes. $pv is nullable (int+), tolerated absent.
773
+ fn mgc_shape_ok (raw: any) -> bool
774
+ {
775
+ e = raw $epoch. s = raw $session_id.
776
+ return e != NIL && (_typeof e) == "BINARY" && (s == NIL || (_typeof s) == "BINARY").
777
+ }
778
+
779
+ metadef mgc_narrowed_t: ($ok -> bool, $payload -> mgc_targ_t+, $err -> version_error_t+).
780
+
781
+ fn try_narrow_mgc (raw: any) -> mgc_narrowed_t
782
+ {
783
+ v = mgc_version_of raw.
784
+ if v < min_wire_version
785
+ {
786
+ return ($ok -> FALSE, $payload -> NIL, $err -> too_old_error "mgc" v mgc_max_version).
787
+ }
788
+ if mgc_shape_ok raw != TRUE
789
+ {
790
+ return ($ok -> FALSE, $payload -> NIL, $err -> shape_error "mgc" v mgc_max_version).
791
+ }
792
+ return ($ok -> TRUE, $payload -> raw safe mgc_targ_v1_t, $err -> NIL).
793
+ }
794
+
795
+ fn narrow_mgc (raw: any) -> mgc_targ_t
796
+ {
797
+ r = try_narrow_mgc raw.
798
+ if (r $ok) != TRUE { abort ((r $err)? $message) when TRUE. }
799
+ return (r $payload)?.
800
+ }
801
+ }