@moltzap/protocol 2026.408.0 → 2026.425.0

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 (288) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/rpc-registry.d.ts +446 -0
  6. package/dist/rpc-registry.d.ts.map +1 -0
  7. package/dist/rpc-registry.js +67 -0
  8. package/dist/rpc-registry.js.map +1 -0
  9. package/dist/rpc.d.ts +42 -0
  10. package/dist/rpc.d.ts.map +1 -0
  11. package/dist/rpc.js +29 -0
  12. package/dist/rpc.js.map +1 -0
  13. package/dist/schema/apps.d.ts +86 -0
  14. package/dist/schema/apps.d.ts.map +1 -0
  15. package/dist/schema/apps.js +77 -0
  16. package/dist/schema/apps.js.map +1 -0
  17. package/dist/schema/contacts.d.ts +8 -18
  18. package/dist/schema/contacts.d.ts.map +1 -1
  19. package/dist/schema/contacts.js +9 -13
  20. package/dist/schema/contacts.js.map +1 -1
  21. package/dist/schema/conversations.d.ts +15 -8
  22. package/dist/schema/conversations.d.ts.map +1 -1
  23. package/dist/schema/conversations.js +18 -7
  24. package/dist/schema/conversations.js.map +1 -1
  25. package/dist/schema/delivery.d.ts +1 -4
  26. package/dist/schema/delivery.d.ts.map +1 -1
  27. package/dist/schema/delivery.js +2 -3
  28. package/dist/schema/delivery.js.map +1 -1
  29. package/dist/schema/errors.d.ts +13 -0
  30. package/dist/schema/errors.d.ts.map +1 -1
  31. package/dist/schema/errors.js +14 -0
  32. package/dist/schema/errors.js.map +1 -1
  33. package/dist/schema/events.d.ts +112 -93
  34. package/dist/schema/events.d.ts.map +1 -1
  35. package/dist/schema/events.js +84 -25
  36. package/dist/schema/events.js.map +1 -1
  37. package/dist/schema/identity.d.ts +19 -17
  38. package/dist/schema/identity.d.ts.map +1 -1
  39. package/dist/schema/identity.js +7 -14
  40. package/dist/schema/identity.js.map +1 -1
  41. package/dist/schema/index.d.ts +4 -2
  42. package/dist/schema/index.d.ts.map +1 -1
  43. package/dist/schema/index.js +4 -2
  44. package/dist/schema/index.js.map +1 -1
  45. package/dist/schema/messages.d.ts +3 -7
  46. package/dist/schema/messages.d.ts.map +1 -1
  47. package/dist/schema/messages.js +4 -6
  48. package/dist/schema/messages.js.map +1 -1
  49. package/dist/schema/methods/apps.d.ts +123 -0
  50. package/dist/schema/methods/apps.d.ts.map +1 -0
  51. package/dist/schema/methods/apps.js +93 -0
  52. package/dist/schema/methods/apps.js.map +1 -0
  53. package/dist/schema/methods/auth.d.ts +102 -90
  54. package/dist/schema/methods/auth.d.ts.map +1 -1
  55. package/dist/schema/methods/auth.js +86 -74
  56. package/dist/schema/methods/auth.js.map +1 -1
  57. package/dist/schema/methods/contacts.d.ts +39 -74
  58. package/dist/schema/methods/contacts.d.ts.map +1 -1
  59. package/dist/schema/methods/contacts.js +35 -29
  60. package/dist/schema/methods/contacts.js.map +1 -1
  61. package/dist/schema/methods/conversations.d.ts +48 -52
  62. package/dist/schema/methods/conversations.d.ts.map +1 -1
  63. package/dist/schema/methods/conversations.js +89 -38
  64. package/dist/schema/methods/conversations.js.map +1 -1
  65. package/dist/schema/methods/invites.d.ts +1 -3
  66. package/dist/schema/methods/invites.d.ts.map +1 -1
  67. package/dist/schema/methods/invites.js +8 -1
  68. package/dist/schema/methods/invites.js.map +1 -1
  69. package/dist/schema/methods/messages.d.ts +12 -44
  70. package/dist/schema/methods/messages.d.ts.map +1 -1
  71. package/dist/schema/methods/messages.js +22 -28
  72. package/dist/schema/methods/messages.js.map +1 -1
  73. package/dist/schema/methods/presence.d.ts +7 -22
  74. package/dist/schema/methods/presence.d.ts.map +1 -1
  75. package/dist/schema/methods/presence.js +12 -6
  76. package/dist/schema/methods/presence.js.map +1 -1
  77. package/dist/schema/methods/push.d.ts +8 -6
  78. package/dist/schema/methods/push.d.ts.map +1 -1
  79. package/dist/schema/methods/push.js +20 -7
  80. package/dist/schema/methods/push.js.map +1 -1
  81. package/dist/schema/methods/system.d.ts +4 -0
  82. package/dist/schema/methods/system.d.ts.map +1 -0
  83. package/dist/schema/methods/system.js +11 -0
  84. package/dist/schema/methods/system.js.map +1 -0
  85. package/dist/schema/presence.d.ts +1 -12
  86. package/dist/schema/presence.d.ts.map +1 -1
  87. package/dist/schema/presence.js +2 -7
  88. package/dist/schema/presence.js.map +1 -1
  89. package/dist/schema/surfaces.d.ts +31 -25
  90. package/dist/schema/surfaces.d.ts.map +1 -1
  91. package/dist/schema/surfaces.js +40 -21
  92. package/dist/schema/surfaces.js.map +1 -1
  93. package/dist/testing/agent-registration.d.ts +49 -0
  94. package/dist/testing/agent-registration.d.ts.map +1 -0
  95. package/dist/testing/agent-registration.js +77 -0
  96. package/dist/testing/agent-registration.js.map +1 -0
  97. package/dist/testing/arbitraries/frames.d.ts +24 -0
  98. package/dist/testing/arbitraries/frames.d.ts.map +1 -0
  99. package/dist/testing/arbitraries/frames.js +36 -0
  100. package/dist/testing/arbitraries/frames.js.map +1 -0
  101. package/dist/testing/arbitraries/from-typebox.d.ts +37 -0
  102. package/dist/testing/arbitraries/from-typebox.d.ts.map +1 -0
  103. package/dist/testing/arbitraries/from-typebox.js +131 -0
  104. package/dist/testing/arbitraries/from-typebox.js.map +1 -0
  105. package/dist/testing/arbitraries/index.d.ts +4 -0
  106. package/dist/testing/arbitraries/index.d.ts.map +1 -0
  107. package/dist/testing/arbitraries/index.js +4 -0
  108. package/dist/testing/arbitraries/index.js.map +1 -0
  109. package/dist/testing/arbitraries/rpc.d.ts +66 -0
  110. package/dist/testing/arbitraries/rpc.d.ts.map +1 -0
  111. package/dist/testing/arbitraries/rpc.js +98 -0
  112. package/dist/testing/arbitraries/rpc.js.map +1 -0
  113. package/dist/testing/canonicalize.d.ts +38 -0
  114. package/dist/testing/canonicalize.d.ts.map +1 -0
  115. package/dist/testing/canonicalize.js +55 -0
  116. package/dist/testing/canonicalize.js.map +1 -0
  117. package/dist/testing/captures.d.ts +61 -0
  118. package/dist/testing/captures.d.ts.map +1 -0
  119. package/dist/testing/captures.js +99 -0
  120. package/dist/testing/captures.js.map +1 -0
  121. package/dist/testing/codec.d.ts +44 -0
  122. package/dist/testing/codec.d.ts.map +1 -0
  123. package/dist/testing/codec.js +170 -0
  124. package/dist/testing/codec.js.map +1 -0
  125. package/dist/testing/conformance/__divergence_proofs__/executable-proof-helpers.d.ts +6 -0
  126. package/dist/testing/conformance/__divergence_proofs__/executable-proof-helpers.d.ts.map +1 -0
  127. package/dist/testing/conformance/__divergence_proofs__/executable-proof-helpers.js +33 -0
  128. package/dist/testing/conformance/__divergence_proofs__/executable-proof-helpers.js.map +1 -0
  129. package/dist/testing/conformance/adversity.d.ts +36 -0
  130. package/dist/testing/conformance/adversity.d.ts.map +1 -0
  131. package/dist/testing/conformance/adversity.js +360 -0
  132. package/dist/testing/conformance/adversity.js.map +1 -0
  133. package/dist/testing/conformance/boundary.d.ts +56 -0
  134. package/dist/testing/conformance/boundary.d.ts.map +1 -0
  135. package/dist/testing/conformance/boundary.js +129 -0
  136. package/dist/testing/conformance/boundary.js.map +1 -0
  137. package/dist/testing/conformance/client/_fixtures.d.ts +71 -0
  138. package/dist/testing/conformance/client/_fixtures.d.ts.map +1 -0
  139. package/dist/testing/conformance/client/_fixtures.js +102 -0
  140. package/dist/testing/conformance/client/_fixtures.js.map +1 -0
  141. package/dist/testing/conformance/client/adversity.d.ts +39 -0
  142. package/dist/testing/conformance/client/adversity.d.ts.map +1 -0
  143. package/dist/testing/conformance/client/adversity.js +162 -0
  144. package/dist/testing/conformance/client/adversity.js.map +1 -0
  145. package/dist/testing/conformance/client/boundary.d.ts +12 -0
  146. package/dist/testing/conformance/client/boundary.d.ts.map +1 -0
  147. package/dist/testing/conformance/client/boundary.js +68 -0
  148. package/dist/testing/conformance/client/boundary.js.map +1 -0
  149. package/dist/testing/conformance/client/delivery.d.ts +38 -0
  150. package/dist/testing/conformance/client/delivery.d.ts.map +1 -0
  151. package/dist/testing/conformance/client/delivery.js +202 -0
  152. package/dist/testing/conformance/client/delivery.js.map +1 -0
  153. package/dist/testing/conformance/client/index.d.ts +16 -0
  154. package/dist/testing/conformance/client/index.d.ts.map +1 -0
  155. package/dist/testing/conformance/client/index.js +16 -0
  156. package/dist/testing/conformance/client/index.js.map +1 -0
  157. package/dist/testing/conformance/client/rpc-semantics.d.ts +26 -0
  158. package/dist/testing/conformance/client/rpc-semantics.d.ts.map +1 -0
  159. package/dist/testing/conformance/client/rpc-semantics.js +145 -0
  160. package/dist/testing/conformance/client/rpc-semantics.js.map +1 -0
  161. package/dist/testing/conformance/client/runner.d.ts +258 -0
  162. package/dist/testing/conformance/client/runner.d.ts.map +1 -0
  163. package/dist/testing/conformance/client/runner.js +228 -0
  164. package/dist/testing/conformance/client/runner.js.map +1 -0
  165. package/dist/testing/conformance/client/schema-conformance.d.ts +25 -0
  166. package/dist/testing/conformance/client/schema-conformance.d.ts.map +1 -0
  167. package/dist/testing/conformance/client/schema-conformance.js +123 -0
  168. package/dist/testing/conformance/client/schema-conformance.js.map +1 -0
  169. package/dist/testing/conformance/client/suite.d.ts +90 -0
  170. package/dist/testing/conformance/client/suite.d.ts.map +1 -0
  171. package/dist/testing/conformance/client/suite.js +209 -0
  172. package/dist/testing/conformance/client/suite.js.map +1 -0
  173. package/dist/testing/conformance/coverage-policy.d.ts +8 -0
  174. package/dist/testing/conformance/coverage-policy.d.ts.map +1 -0
  175. package/dist/testing/conformance/coverage-policy.js +10 -0
  176. package/dist/testing/conformance/coverage-policy.js.map +1 -0
  177. package/dist/testing/conformance/delivery.d.ts +40 -0
  178. package/dist/testing/conformance/delivery.d.ts.map +1 -0
  179. package/dist/testing/conformance/delivery.js +231 -0
  180. package/dist/testing/conformance/delivery.js.map +1 -0
  181. package/dist/testing/conformance/env.d.ts +3 -0
  182. package/dist/testing/conformance/env.d.ts.map +1 -0
  183. package/dist/testing/conformance/env.js +14 -0
  184. package/dist/testing/conformance/env.js.map +1 -0
  185. package/dist/testing/conformance/index.d.ts +10 -0
  186. package/dist/testing/conformance/index.d.ts.map +1 -0
  187. package/dist/testing/conformance/index.js +10 -0
  188. package/dist/testing/conformance/index.js.map +1 -0
  189. package/dist/testing/conformance/registry.d.ts +93 -0
  190. package/dist/testing/conformance/registry.d.ts.map +1 -0
  191. package/dist/testing/conformance/registry.js +62 -0
  192. package/dist/testing/conformance/registry.js.map +1 -0
  193. package/dist/testing/conformance/rpc-semantics.d.ts +67 -0
  194. package/dist/testing/conformance/rpc-semantics.d.ts.map +1 -0
  195. package/dist/testing/conformance/rpc-semantics.js +394 -0
  196. package/dist/testing/conformance/rpc-semantics.js.map +1 -0
  197. package/dist/testing/conformance/runner.d.ts +78 -0
  198. package/dist/testing/conformance/runner.d.ts.map +1 -0
  199. package/dist/testing/conformance/runner.js +65 -0
  200. package/dist/testing/conformance/runner.js.map +1 -0
  201. package/dist/testing/conformance/schema-conformance.d.ts +30 -0
  202. package/dist/testing/conformance/schema-conformance.d.ts.map +1 -0
  203. package/dist/testing/conformance/schema-conformance.js +229 -0
  204. package/dist/testing/conformance/schema-conformance.js.map +1 -0
  205. package/dist/testing/conformance/suite.d.ts +92 -0
  206. package/dist/testing/conformance/suite.d.ts.map +1 -0
  207. package/dist/testing/conformance/suite.js +233 -0
  208. package/dist/testing/conformance/suite.js.map +1 -0
  209. package/dist/testing/errors.d.ts +78 -0
  210. package/dist/testing/errors.d.ts.map +1 -0
  211. package/dist/testing/errors.js +34 -0
  212. package/dist/testing/errors.js.map +1 -0
  213. package/dist/testing/index.d.ts +25 -0
  214. package/dist/testing/index.d.ts.map +1 -0
  215. package/dist/testing/index.js +37 -0
  216. package/dist/testing/index.js.map +1 -0
  217. package/dist/testing/models/dispatch.d.ts +61 -0
  218. package/dist/testing/models/dispatch.d.ts.map +1 -0
  219. package/dist/testing/models/dispatch.js +197 -0
  220. package/dist/testing/models/dispatch.js.map +1 -0
  221. package/dist/testing/models/index.d.ts +3 -0
  222. package/dist/testing/models/index.d.ts.map +1 -0
  223. package/dist/testing/models/index.js +3 -0
  224. package/dist/testing/models/index.js.map +1 -0
  225. package/dist/testing/models/state.d.ts +37 -0
  226. package/dist/testing/models/state.d.ts.map +1 -0
  227. package/dist/testing/models/state.js +14 -0
  228. package/dist/testing/models/state.js.map +1 -0
  229. package/dist/testing/test-client.d.ts +70 -0
  230. package/dist/testing/test-client.d.ts.map +1 -0
  231. package/dist/testing/test-client.js +266 -0
  232. package/dist/testing/test-client.js.map +1 -0
  233. package/dist/testing/test-server.d.ts +62 -0
  234. package/dist/testing/test-server.d.ts.map +1 -0
  235. package/dist/testing/test-server.js +134 -0
  236. package/dist/testing/test-server.js.map +1 -0
  237. package/dist/testing/toxics/client.d.ts +52 -0
  238. package/dist/testing/toxics/client.d.ts.map +1 -0
  239. package/dist/testing/toxics/client.js +120 -0
  240. package/dist/testing/toxics/client.js.map +1 -0
  241. package/dist/testing/toxics/defaults.d.ts +43 -0
  242. package/dist/testing/toxics/defaults.d.ts.map +1 -0
  243. package/dist/testing/toxics/defaults.js +9 -0
  244. package/dist/testing/toxics/defaults.js.map +1 -0
  245. package/dist/testing/toxics/index.d.ts +4 -0
  246. package/dist/testing/toxics/index.d.ts.map +1 -0
  247. package/dist/testing/toxics/index.js +4 -0
  248. package/dist/testing/toxics/index.js.map +1 -0
  249. package/dist/testing/toxics/profile.d.ts +69 -0
  250. package/dist/testing/toxics/profile.d.ts.map +1 -0
  251. package/dist/testing/toxics/profile.js +57 -0
  252. package/dist/testing/toxics/profile.js.map +1 -0
  253. package/dist/types.d.ts +7 -11
  254. package/dist/types.d.ts.map +1 -1
  255. package/dist/types.js +3 -0
  256. package/dist/types.js.map +1 -1
  257. package/dist/validators.d.ts +49 -177
  258. package/dist/validators.d.ts.map +1 -1
  259. package/dist/validators.js +77 -48
  260. package/dist/validators.js.map +1 -1
  261. package/dist/version.d.ts +1 -1
  262. package/dist/version.js +1 -1
  263. package/package.json +18 -40
  264. package/LICENSE +0 -21
  265. package/dist/optional/contact-events.d.ts +0 -3
  266. package/dist/optional/contact-events.d.ts.map +0 -1
  267. package/dist/optional/contact-events.js +0 -5
  268. package/dist/optional/contact-events.js.map +0 -1
  269. package/dist/optional/contact-methods.d.ts +0 -5
  270. package/dist/optional/contact-methods.d.ts.map +0 -1
  271. package/dist/optional/contact-methods.js +0 -5
  272. package/dist/optional/contact-methods.js.map +0 -1
  273. package/dist/phone-hash.d.ts +0 -10
  274. package/dist/phone-hash.d.ts.map +0 -1
  275. package/dist/phone-hash.js +0 -17
  276. package/dist/phone-hash.js.map +0 -1
  277. package/dist/schema/methods/phone-contacts.d.ts +0 -30
  278. package/dist/schema/methods/phone-contacts.d.ts.map +0 -1
  279. package/dist/schema/methods/phone-contacts.js +0 -10
  280. package/dist/schema/methods/phone-contacts.js.map +0 -1
  281. package/dist/test-client.d.ts +0 -34
  282. package/dist/test-client.d.ts.map +0 -1
  283. package/dist/test-client.js +0 -176
  284. package/dist/test-client.js.map +0 -1
  285. package/dist/test-fixtures/phone-hashes.d.ts +0 -18
  286. package/dist/test-fixtures/phone-hashes.d.ts.map +0 -1
  287. package/dist/test-fixtures/phone-hashes.js +0 -24
  288. package/dist/test-fixtures/phone-hashes.js.map +0 -1
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Client-side conformance runner — acquires a TestServer substrate plus the
3
+ * consumer-provided real-client factory under a single Scope, pinned to an
4
+ * FC seed.
5
+ *
6
+ * Parallel to `conformance/runner.ts` (server-side: real server + TestClient).
7
+ * This module's input is `realClient: () => Effect<RealClientHandle, E,
8
+ * Scope>`; scope teardown closes the real client, drains the handshake-noise
9
+ * guard (see `ClientHandshakeWindow` below), and releases the TestServer.
10
+ *
11
+ * Architect O5 decision: the factory returns an `Effect` that owns the real
12
+ * client's lifetime via `Scope`. Consumers that already ship an Effect-native
13
+ * construction path (`packages/client/MoltZapWsClient` via its internal
14
+ * `ManagedRuntime`) wrap it in `Effect.acquireRelease`. Channel packages
15
+ * (`openclaw-channel`, `nanoclaw-channel`) add a narrow test-support subpath
16
+ * export (see §4 O5 resolution in the design doc) that returns the same
17
+ * factory shape.
18
+ */
19
+ import { Context, Effect, Ref, type Scope } from "effect";
20
+ import type { EventFrame, ResponseFrame } from "../../../schema/frames.js";
21
+ import { type TestServer, type TestServerConnection } from "../../test-server.js";
22
+ import { type ToxiproxyClient } from "../../toxics/client.js";
23
+ import { RealServerAcquireError, TransportIoError, type ToxicControlError } from "../../errors.js";
24
+ import type { ConformanceArtifact } from "../runner.js";
25
+ /**
26
+ * Opaque handle to a live real MoltZap client connected to `TestServer`.
27
+ * The consumer's factory returns this under a `Scope`; scope release runs
28
+ * `close()`.
29
+ *
30
+ * Invariant I9: every field below is a **public** observable surface on
31
+ * the real client — no private reads, no monkey-patching, no log
32
+ * scraping. When a channel package's client is private, the consumer
33
+ * exposes it via a test-support subpath export (O5 resolution).
34
+ */
35
+ export interface RealClientHandle {
36
+ /**
37
+ * Stable identifier emitted in the connect frame's `agentId` field.
38
+ * Used to correlate TestServer-observed inbound frames to this client.
39
+ */
40
+ readonly agentId: string;
41
+ /**
42
+ * Fully-connected promise — resolves after the handshake completes and
43
+ * the client is ready to receive events. Property bodies await this
44
+ * before scripting TestServer emissions so the handshake-noise guard
45
+ * window is closed (see `ClientHandshakeWindow`).
46
+ */
47
+ readonly ready: Effect.Effect<void, RealClientLifecycleError>;
48
+ /**
49
+ * Real client's public event-subscriber surface. Every captured event
50
+ * is tagged with the property-authored `emissionId` when the property
51
+ * uses `ClientHandshakeWindow.emitTaggedEvent`; predicates filter by
52
+ * that tag to exclude handshake-noise frames.
53
+ */
54
+ readonly events: RealClientEventSubscriber;
55
+ /**
56
+ * Real client's documented RPC caller. B1 / B4 / D5 predicates invoke
57
+ * this and assert on the returned promise's resolution / rejection.
58
+ */
59
+ readonly call: RealClientRpcCaller;
60
+ /**
61
+ * Real client's documented close / disconnect lifecycle signal. D6
62
+ * predicate awaits this on slow-close and asserts it resolves within
63
+ * the reap deadline.
64
+ */
65
+ readonly closeSignal: Effect.Effect<RealClientCloseEvent>;
66
+ /**
67
+ * Scope-release hook. The runner's Scope calls this on teardown; a
68
+ * close that throws surfaces as `RealClientLifecycleError`.
69
+ */
70
+ readonly close: Effect.Effect<void, RealClientLifecycleError>;
71
+ }
72
+ /**
73
+ * Real client's public event-subscriber surface. Property bodies `subscribe`
74
+ * once per fixture and drain via `snapshot`. Concrete shape is per-consumer
75
+ * (packages/client's `waitForEvent` + `onEvent`, channel packages' native
76
+ * event pipe); the wrapper adapts it to this interface.
77
+ */
78
+ export interface RealClientEventSubscriber {
79
+ readonly subscribe: (filter: RealClientEventFilter) => Effect.Effect<RealClientSubscription, RealClientLifecycleError>;
80
+ readonly snapshot: Effect.Effect<ReadonlyArray<ObservedEvent>>;
81
+ }
82
+ export interface RealClientSubscription {
83
+ readonly id: string;
84
+ readonly unsubscribe: Effect.Effect<void>;
85
+ }
86
+ export interface RealClientEventFilter {
87
+ /**
88
+ * Property-authored emission tag. The real client surfaces only
89
+ * events whose payload carries this tag, excluding handshake-noise.
90
+ * Implementations match on the event payload's `__emissionId` field
91
+ * (set by `ClientHandshakeWindow.emitTaggedEvent`).
92
+ */
93
+ readonly emissionTag?: string;
94
+ /** Restrict to a specific conversation / task. */
95
+ readonly conversationId?: string;
96
+ /** Restrict to a specific event-name family. */
97
+ readonly eventNamePrefix?: string;
98
+ }
99
+ /**
100
+ * Observed event after the real client has surfaced it on its public
101
+ * subscriber API. `rawBytes` carries the payload byte-for-byte (C3);
102
+ * `decoded` is the schema-decoded frame (A2 validation target).
103
+ */
104
+ export interface ObservedEvent {
105
+ readonly emissionTag: string | null;
106
+ readonly decoded: EventFrame;
107
+ readonly rawBytes: Uint8Array;
108
+ readonly observedAtMs: number;
109
+ }
110
+ /**
111
+ * Real client's RPC caller. Takes the raw JSON-RPC method + params;
112
+ * returns the decoded response or a typed error. Contract: the real
113
+ * client itself generates request IDs — the property does not mint
114
+ * them — and records the outbound ID via `outboundIdFeed` so the
115
+ * property can assert ID-set equality (B4, O7 idempotence).
116
+ */
117
+ export interface RealClientRpcCaller {
118
+ readonly call: (method: string, params: unknown) => Effect.Effect<ResponseFrame, RealClientRpcError>;
119
+ /** Stream of outbound request IDs the real client has minted. */
120
+ readonly outboundIdFeed: Effect.Effect<ReadonlyArray<string>>;
121
+ }
122
+ /**
123
+ * Real-client lifecycle error tag. All three cover the Principle 3 error
124
+ * channel; no raw throws escape the factory's Scope.
125
+ */
126
+ export declare class RealClientLifecycleError {
127
+ readonly cause: unknown;
128
+ readonly _tag = "RealClientLifecycleError";
129
+ constructor(cause: unknown);
130
+ }
131
+ /** Typed error surface for real-client RPC calls (D5 predicate target). */
132
+ export declare class RealClientRpcError {
133
+ readonly kind: "timeout" | "server-error" | "malformed-response" | "disconnected";
134
+ readonly method: string;
135
+ readonly documentedErrorTag: string | null;
136
+ readonly cause: unknown;
137
+ readonly _tag = "RealClientRpcError";
138
+ constructor(kind: "timeout" | "server-error" | "malformed-response" | "disconnected", method: string, documentedErrorTag: string | null, cause: unknown);
139
+ }
140
+ /** Close-event shape surfaced by `RealClientHandle.closeSignal`. */
141
+ export interface RealClientCloseEvent {
142
+ readonly code: number;
143
+ readonly reason: string;
144
+ readonly observedAtMs: number;
145
+ }
146
+ /**
147
+ * Handshake-noise guard window (O7 resolution).
148
+ *
149
+ * When a real client connects to TestServer, `packages/client` and the
150
+ * channel packages emit hello + subscribe + presence frames **before**
151
+ * the property's first scripted emission. Those frames must not be
152
+ * accepted as satisfying a later sampled predicate.
153
+ *
154
+ * Every client-side property that observes frames requests a
155
+ * `ClientHandshakeWindow` on its fixture and emits via
156
+ * `emitTaggedEvent` / `emitTaggedResponse`. The window stamps each
157
+ * emission with a property-authored `emissionTag`; the
158
+ * `RealClientEventSubscriber` filter drops untagged events.
159
+ *
160
+ * D6 is the only client-side property exempt (observes lifecycle
161
+ * signals, not frames).
162
+ */
163
+ export interface ClientHandshakeWindow {
164
+ readonly freshEmissionTag: Effect.Effect<string>;
165
+ readonly emitTaggedEvent: (opts: {
166
+ readonly connection: TestServerConnection;
167
+ readonly base: EventFrame;
168
+ readonly emissionTag: string;
169
+ }) => Effect.Effect<string>;
170
+ readonly emitTaggedResponse: (opts: {
171
+ readonly connection: TestServerConnection;
172
+ readonly base: ResponseFrame;
173
+ readonly emissionTag: string;
174
+ }) => Effect.Effect<string>;
175
+ readonly awaitHandshakeComplete: Effect.Effect<void, RealClientLifecycleError>;
176
+ }
177
+ export declare const ClientHandshakeWindow: Context.Tag<ClientHandshakeWindow, ClientHandshakeWindow>;
178
+ /**
179
+ * Context a client-side property receives. Parallel to server-side
180
+ * `ConformanceRunContext` — same `seed`, `toxiproxy`, `artifacts`
181
+ * plumbing; different factory pair.
182
+ */
183
+ /**
184
+ * Factory arguments the suite passes to every `realClient()` invocation.
185
+ * The factory uses `testServerUrl` to point its WS client at the bound
186
+ * TestServer substrate.
187
+ */
188
+ export interface RealClientFactoryArgs {
189
+ readonly testServerUrl: string;
190
+ }
191
+ export interface ClientConformanceRunContext {
192
+ readonly testServer: TestServer;
193
+ readonly realClientFactory: (args: RealClientFactoryArgs) => Effect.Effect<RealClientHandle, RealClientLifecycleError, Scope.Scope>;
194
+ readonly handshakeWindow: ClientHandshakeWindow;
195
+ readonly toxiproxy: ToxiproxyClient | null;
196
+ readonly opts: ClientConformanceRunOptions;
197
+ readonly seed: number;
198
+ readonly artifacts: Ref.Ref<ReadonlyArray<ConformanceArtifact>>;
199
+ }
200
+ export interface ClientConformanceRunOptions {
201
+ readonly tiers: ReadonlyArray<"A" | "B" | "C" | "D" | "E">;
202
+ readonly realClient: (args: RealClientFactoryArgs) => Effect.Effect<RealClientHandle, RealClientLifecycleError, Scope.Scope>;
203
+ readonly replaySeed?: number;
204
+ readonly numRuns?: number;
205
+ readonly manageToxiproxy?: boolean;
206
+ readonly toxiproxyUrl?: string;
207
+ readonly artifactDir?: string;
208
+ /**
209
+ * If `true`, TestServer binds behind a Toxiproxy upstream matching the
210
+ * adversity-tier `downstream` port; otherwise a direct bind. Default:
211
+ * `true` when `tiers` includes `"D"`.
212
+ */
213
+ readonly bindThroughToxiproxy?: boolean;
214
+ }
215
+ /**
216
+ * Acquire the full client-side context under one Scope. Returns a
217
+ * live TestServer, a real-client factory ready to call, and a
218
+ * handshake-noise guard window.
219
+ *
220
+ * The TestServer binds on an ephemeral port. Optional Toxiproxy is
221
+ * acquired when `manageToxiproxy` is set or `toxiproxyUrl` is provided
222
+ * alongside tier "D".
223
+ *
224
+ * Errors are typed; no raw throws.
225
+ */
226
+ export declare function acquireClientRunContext(opts: ClientConformanceRunOptions): Effect.Effect<ClientConformanceRunContext, ToxicControlError | RealServerAcquireError | RealClientLifecycleError, Scope.Scope>;
227
+ /**
228
+ * Build a `ClientHandshakeWindow` from a real-client handle. Returns a
229
+ * window whose `awaitHandshakeComplete` resolves when `handle.ready`
230
+ * does; emissions are passed through to the connection the property
231
+ * body chooses (TestServer may have multiple connections).
232
+ */
233
+ export declare function makeClientHandshakeWindow(handle: RealClientHandle): Effect.Effect<ClientHandshakeWindow, never, Scope.Scope>;
234
+ /**
235
+ * Auto-handshake responder. Spawned as a background fiber by property
236
+ * bodies; watches a TestServer connection's inbound capture buffer for
237
+ * `auth/connect` RPC requests and responds with a minimal valid
238
+ * `HelloOkSchema`. Required because `MoltZapWsClient.connect()` blocks
239
+ * on the auth/connect response before `ready` resolves.
240
+ *
241
+ * Exposed as a helper so each property body can choose whether to run
242
+ * the auto-responder or assert directly against the raw inbound stream
243
+ * (e.g., B4 spurious-id test wants to observe the inbound ids).
244
+ */
245
+ export declare function runAutoHandshakeResponder(connection: TestServerConnection, agentId: string): Effect.Effect<void, never, Scope.Scope>;
246
+ /**
247
+ * Utility: resolve a fresh tag from a handshake window in synchronous-
248
+ * friendly Effect code. Property bodies call this at the top of each
249
+ * fast-check iteration.
250
+ */
251
+ export declare function freshTag(window: ClientHandshakeWindow): Effect.Effect<string>;
252
+ /**
253
+ * Fiber-safe helper to await a TestServer connection. Times out so a
254
+ * never-connecting real client doesn't block the property body
255
+ * indefinitely.
256
+ */
257
+ export declare function awaitConnection(testServer: TestServer, timeoutMs?: number): Effect.Effect<TestServerConnection, TransportIoError>;
258
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../../src/testing/conformance/client/runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Client-side conformance runner — acquires a TestServer substrate plus the
3
+ * consumer-provided real-client factory under a single Scope, pinned to an
4
+ * FC seed.
5
+ *
6
+ * Parallel to `conformance/runner.ts` (server-side: real server + TestClient).
7
+ * This module's input is `realClient: () => Effect<RealClientHandle, E,
8
+ * Scope>`; scope teardown closes the real client, drains the handshake-noise
9
+ * guard (see `ClientHandshakeWindow` below), and releases the TestServer.
10
+ *
11
+ * Architect O5 decision: the factory returns an `Effect` that owns the real
12
+ * client's lifetime via `Scope`. Consumers that already ship an Effect-native
13
+ * construction path (`packages/client/MoltZapWsClient` via its internal
14
+ * `ManagedRuntime`) wrap it in `Effect.acquireRelease`. Channel packages
15
+ * (`openclaw-channel`, `nanoclaw-channel`) add a narrow test-support subpath
16
+ * export (see §4 O5 resolution in the design doc) that returns the same
17
+ * factory shape.
18
+ */
19
+ import { Context, Effect, Ref } from "effect";
20
+ import { makeTestServer, } from "../../test-server.js";
21
+ import { makeToxiproxyClient, } from "../../toxics/client.js";
22
+ import { RealServerAcquireError, TransportIoError, } from "../../errors.js";
23
+ import { conformanceNumRunsFromEnv } from "../env.js";
24
+ import { PROTOCOL_VERSION } from "../../../version.js";
25
+ /**
26
+ * Real-client lifecycle error tag. All three cover the Principle 3 error
27
+ * channel; no raw throws escape the factory's Scope.
28
+ */
29
+ export class RealClientLifecycleError {
30
+ cause;
31
+ _tag = "RealClientLifecycleError";
32
+ constructor(cause) {
33
+ this.cause = cause;
34
+ }
35
+ }
36
+ /** Typed error surface for real-client RPC calls (D5 predicate target). */
37
+ export class RealClientRpcError {
38
+ kind;
39
+ method;
40
+ documentedErrorTag;
41
+ cause;
42
+ _tag = "RealClientRpcError";
43
+ constructor(kind, method, documentedErrorTag, cause) {
44
+ this.kind = kind;
45
+ this.method = method;
46
+ this.documentedErrorTag = documentedErrorTag;
47
+ this.cause = cause;
48
+ }
49
+ }
50
+ export const ClientHandshakeWindow = Context.GenericTag("@moltzap/protocol/testing/ClientHandshakeWindow");
51
+ /**
52
+ * Acquire the full client-side context under one Scope. Returns a
53
+ * live TestServer, a real-client factory ready to call, and a
54
+ * handshake-noise guard window.
55
+ *
56
+ * The TestServer binds on an ephemeral port. Optional Toxiproxy is
57
+ * acquired when `manageToxiproxy` is set or `toxiproxyUrl` is provided
58
+ * alongside tier "D".
59
+ *
60
+ * Errors are typed; no raw throws.
61
+ */
62
+ export function acquireClientRunContext(opts) {
63
+ return Effect.gen(function* () {
64
+ const effectiveOpts = {
65
+ ...opts,
66
+ numRuns: opts.numRuns ?? conformanceNumRunsFromEnv(),
67
+ };
68
+ const seed = effectiveOpts.replaySeed ??
69
+ Number(process.env.FC_SEED ?? Date.now() & 0x7fffffff);
70
+ const artifacts = yield* Ref.make([]);
71
+ // Bind the TestServer under the ambient Scope. Server-close on teardown.
72
+ const testServer = yield* makeTestServer({
73
+ port: 0,
74
+ host: "127.0.0.1",
75
+ captureCapacity: 256,
76
+ }).pipe(Effect.mapError((err) => new RealServerAcquireError({
77
+ cause: new Error(`TestServer bind failed: ${String(err)}`),
78
+ })));
79
+ // Optional Toxiproxy acquisition — matches the server-side runner's
80
+ // contract (only allocate when tier "D" is present).
81
+ let toxiproxy = null;
82
+ if (effectiveOpts.tiers.includes("D") &&
83
+ effectiveOpts.toxiproxyUrl !== undefined) {
84
+ const tp = yield* makeToxiproxyClient({
85
+ apiUrl: effectiveOpts.toxiproxyUrl,
86
+ });
87
+ yield* tp.ping.pipe(Effect.orElseSucceed(() => undefined));
88
+ toxiproxy = tp;
89
+ }
90
+ // Build a placeholder handshake window; property bodies overwrite it
91
+ // via `makeClientHandshakeWindow(handle)` once they have a handle.
92
+ // The context carries the initial no-op shape so type-system contracts
93
+ // hold; each property body still binds a per-handle window.
94
+ const handshakeWindow = {
95
+ freshEmissionTag: Effect.sync(() => `tag-${Math.random().toString(36).slice(2, 10)}`),
96
+ emitTaggedEvent: ({ connection, base, emissionTag }) => emitTaggedEventDefault(connection, base, emissionTag),
97
+ emitTaggedResponse: ({ connection, base, emissionTag }) => emitTaggedResponseDefault(connection, base, emissionTag),
98
+ awaitHandshakeComplete: Effect.void,
99
+ };
100
+ return {
101
+ testServer,
102
+ realClientFactory: effectiveOpts.realClient,
103
+ handshakeWindow,
104
+ toxiproxy,
105
+ opts: effectiveOpts,
106
+ seed,
107
+ artifacts,
108
+ };
109
+ });
110
+ }
111
+ /**
112
+ * Default tagged-event emission: stamp the event payload with the
113
+ * caller's `emissionTag` under the reserved `__emissionTag` key, then
114
+ * forward to the connection's real `emitEvent`. Returns the tag so the
115
+ * caller can filter subscriber observations by the same string.
116
+ *
117
+ * `EventFrame.data` is `Type.Optional(Type.Unknown())`; injecting an
118
+ * object field is schema-valid. The real clients under test are
119
+ * payload-opaque (C3 predicate), so the extra field round-trips cleanly.
120
+ */
121
+ function emitTaggedEventDefault(connection, base, emissionTag) {
122
+ const base_data = (base.data ?? {}); // #ignore-sloppy-code[record-cast]: EventFrame.data is Type.Optional(Type.Unknown()); opaque-payload merge, not a Kysely row
123
+ const tagged = {
124
+ ...base,
125
+ data: { ...base_data, __emissionTag: emissionTag },
126
+ };
127
+ return connection.emitEvent(tagged).pipe(Effect.orElseSucceed(() => undefined), Effect.as(emissionTag));
128
+ }
129
+ function emitTaggedResponseDefault(connection, base, _emissionTag) {
130
+ // Response frames don't carry a free-form `data` field; responses are
131
+ // correlated by `id` instead — the response's `id` IS its emission tag
132
+ // from the property's perspective (see B1 / B4 / D5 predicates).
133
+ return connection.emitResponse(base).pipe(Effect.orElseSucceed(() => undefined), Effect.as(base.id));
134
+ }
135
+ /**
136
+ * Build a `ClientHandshakeWindow` from a real-client handle. Returns a
137
+ * window whose `awaitHandshakeComplete` resolves when `handle.ready`
138
+ * does; emissions are passed through to the connection the property
139
+ * body chooses (TestServer may have multiple connections).
140
+ */
141
+ export function makeClientHandshakeWindow(handle) {
142
+ return Effect.gen(function* () {
143
+ const tagCounter = yield* Ref.make(0);
144
+ return {
145
+ freshEmissionTag: Ref.updateAndGet(tagCounter, (n) => n + 1).pipe(Effect.map((n) => `emit-${handle.agentId}-${n}`)),
146
+ emitTaggedEvent: ({ connection, base, emissionTag }) => emitTaggedEventDefault(connection, base, emissionTag),
147
+ emitTaggedResponse: ({ connection, base, emissionTag }) => emitTaggedResponseDefault(connection, base, emissionTag),
148
+ awaitHandshakeComplete: handle.ready,
149
+ };
150
+ });
151
+ }
152
+ /**
153
+ * Auto-handshake responder. Spawned as a background fiber by property
154
+ * bodies; watches a TestServer connection's inbound capture buffer for
155
+ * `auth/connect` RPC requests and responds with a minimal valid
156
+ * `HelloOkSchema`. Required because `MoltZapWsClient.connect()` blocks
157
+ * on the auth/connect response before `ready` resolves.
158
+ *
159
+ * Exposed as a helper so each property body can choose whether to run
160
+ * the auto-responder or assert directly against the raw inbound stream
161
+ * (e.g., B4 spurious-id test wants to observe the inbound ids).
162
+ */
163
+ export function runAutoHandshakeResponder(connection, agentId) {
164
+ return Effect.forkScoped(Effect.gen(function* () {
165
+ let handshakeHandled = false;
166
+ while (!handshakeHandled) {
167
+ yield* Effect.sleep("10 millis");
168
+ const snap = yield* connection.inbound.snapshot;
169
+ for (const entry of snap) {
170
+ if (entry.kind === "inbound" &&
171
+ entry.frame !== null &&
172
+ entry.frame.type === "request" &&
173
+ entry.frame.method === "auth/connect") {
174
+ const helloOk = {
175
+ protocolVersion: PROTOCOL_VERSION,
176
+ agentId,
177
+ conversations: [],
178
+ unreadCounts: {},
179
+ policy: {
180
+ maxMessageBytes: 65536,
181
+ maxPartsPerMessage: 32,
182
+ maxTextLength: 4096,
183
+ maxGroupParticipants: 64,
184
+ heartbeatIntervalMs: 30_000,
185
+ rateLimits: {
186
+ messagesPerMinute: 60,
187
+ requestsPerMinute: 300,
188
+ },
189
+ },
190
+ };
191
+ yield* connection
192
+ .emitResponse({
193
+ jsonrpc: "2.0",
194
+ type: "response",
195
+ id: entry.frame.id,
196
+ result: helloOk,
197
+ })
198
+ .pipe(Effect.orElseSucceed(() => undefined));
199
+ handshakeHandled = true;
200
+ break;
201
+ }
202
+ }
203
+ }
204
+ })).pipe(Effect.asVoid);
205
+ }
206
+ /**
207
+ * Utility: resolve a fresh tag from a handshake window in synchronous-
208
+ * friendly Effect code. Property bodies call this at the top of each
209
+ * fast-check iteration.
210
+ */
211
+ export function freshTag(window) {
212
+ return window.freshEmissionTag;
213
+ }
214
+ /**
215
+ * Fiber-safe helper to await a TestServer connection. Times out so a
216
+ * never-connecting real client doesn't block the property body
217
+ * indefinitely.
218
+ */
219
+ export function awaitConnection(testServer, timeoutMs = 5000) {
220
+ return testServer.accept.pipe(Effect.timeoutFail({
221
+ duration: `${timeoutMs} millis`,
222
+ onTimeout: () => new TransportIoError({
223
+ direction: "inbound",
224
+ cause: new Error(`TestServer accept timeout after ${timeoutMs}ms (no real client connected)`),
225
+ }),
226
+ }));
227
+ }
228
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../../src/testing/conformance/client/runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { ClientConformanceRunContext } from "./runner.js";
2
+ /**
3
+ * A2 client-side — TestServer emits a property-sampled valid
4
+ * `EventFrame` with a property-authored `emissionTag`; real client's
5
+ * subscriber surfaces an event whose payload schema-matches within
6
+ * deadline.
7
+ *
8
+ * Predicate: `Value.Check(EventFrameSchema, observed.decoded)` passes
9
+ * AND `data.__emissionTag === emissionTag`.
10
+ *
11
+ * Discriminates: a client that strips or reorders required schema
12
+ * fields when surfacing events fails.
13
+ */
14
+ export declare function registerEventWellFormednessClient(ctx: ClientConformanceRunContext): void;
15
+ /**
16
+ * A4 client half — TestServer emits a bit-flipped / truncated /
17
+ * oversized frame; real client drops it silently. A subsequent tagged
18
+ * valid event still surfaces (liveness proof, mirrors #187 round-5
19
+ * guard). A client that crashes on the malformed frame disconnects,
20
+ * preventing the liveness probe from surfacing within the deadline.
21
+ *
22
+ * Predicate: liveness — next tagged event surfaces within deadline.
23
+ */
24
+ export declare function registerMalformedFrameHandlingClient(ctx: ClientConformanceRunContext): void;
25
+ //# sourceMappingURL=schema-conformance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-conformance.d.ts","sourceRoot":"","sources":["../../../../src/testing/conformance/client/schema-conformance.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAY/D;;;;;;;;;;;GAWG;AACH,wBAAgB,iCAAiC,CAC/C,GAAG,EAAE,2BAA2B,GAC/B,IAAI,CAiEN;AAED;;;;;;;;GAQG;AACH,wBAAgB,oCAAoC,CAClD,GAAG,EAAE,2BAA2B,GAC/B,IAAI,CA0DN"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Client-side schema-conformance properties.
3
+ *
4
+ * Covers spec-amendment #200 §5:
5
+ * A2 — event-well-formedness (client-side new)
6
+ * A4 — malformed-frame-handling (client half of both-sides)
7
+ *
8
+ * Predicate-authoring discipline:
9
+ * - P1 (#195): every predicate names a client-realistic misbehaviour.
10
+ * Here it's "real client surfaces a malformed or dropped event."
11
+ * - P2 (#195): executable divergence proofs live under
12
+ * `__divergence_proofs__/*-executable.proofs.test.ts`.
13
+ * - O7 (#200): every observation filters by property-authored
14
+ * `emissionTag` via `ClientHandshakeWindow.emitTaggedEvent` — auto-
15
+ * subscribe / hello / resume frames never satisfy a predicate.
16
+ * - O6 (#200): when spec names a typed error, assert exact match.
17
+ * A4 client half: `MalformedFrameError` is documented; the adapter
18
+ * exposes no typed error channel, so the predicate checks liveness
19
+ * only — a client that crashes on a malformed frame will disconnect,
20
+ * preventing the subsequent liveness probe from surfacing.
21
+ */
22
+ import { Effect } from "effect";
23
+ import { Value } from "@sinclair/typebox/value";
24
+ import { EventFrameSchema } from "../../../schema/frames.js";
25
+ import { arbitraryEventFrame } from "../../arbitraries/frames.js";
26
+ import * as fc from "fast-check";
27
+ import { registerProperty } from "../registry.js";
28
+ import { acquireFixture, collectTagged, invariant, subscribeAll, } from "./_fixtures.js";
29
+ const CATEGORY = "schema-conformance";
30
+ const PROPERTY_BUDGET_MS = 8_000;
31
+ /**
32
+ * A2 client-side — TestServer emits a property-sampled valid
33
+ * `EventFrame` with a property-authored `emissionTag`; real client's
34
+ * subscriber surfaces an event whose payload schema-matches within
35
+ * deadline.
36
+ *
37
+ * Predicate: `Value.Check(EventFrameSchema, observed.decoded)` passes
38
+ * AND `data.__emissionTag === emissionTag`.
39
+ *
40
+ * Discriminates: a client that strips or reorders required schema
41
+ * fields when surfacing events fails.
42
+ */
43
+ export function registerEventWellFormednessClient(ctx) {
44
+ registerProperty(ctx, CATEGORY, "event-well-formedness-client", "valid EventFrame emitted by TestServer surfaces schema-clean on real client", Effect.scoped(Effect.gen(function* () {
45
+ const fx = yield* acquireFixture(ctx, CATEGORY, "event-well-formedness-client");
46
+ yield* subscribeAll(fx.handle);
47
+ const sampled = fc.sample(arbitraryEventFrame(), {
48
+ numRuns: 1,
49
+ seed: ctx.seed,
50
+ })[0];
51
+ if (sampled === undefined) {
52
+ return yield* Effect.fail(invariant(CATEGORY, "event-well-formedness-client", "failed to sample EventFrame"));
53
+ }
54
+ const tag = yield* fx.window.freshEmissionTag;
55
+ yield* fx.window.emitTaggedEvent({
56
+ connection: fx.connection,
57
+ base: sampled,
58
+ emissionTag: tag,
59
+ });
60
+ const observed = yield* collectTagged(fx.handle, (t) => t === tag, {
61
+ expected: 1,
62
+ budgetMs: PROPERTY_BUDGET_MS,
63
+ });
64
+ if (observed.length === 0) {
65
+ return yield* Effect.fail(invariant(CATEGORY, "event-well-formedness-client", `tagged event ${tag} not surfaced within ${PROPERTY_BUDGET_MS}ms`));
66
+ }
67
+ // Reconstruct the expected event shape and re-check schema.
68
+ const reconstructed = {
69
+ jsonrpc: "2.0",
70
+ type: "event",
71
+ event: observed[0].eventName,
72
+ data: observed[0].data,
73
+ };
74
+ if (!Value.Check(EventFrameSchema, reconstructed)) {
75
+ return yield* Effect.fail(invariant(CATEGORY, "event-well-formedness-client", "real client surfaced event that fails EventFrameSchema"));
76
+ }
77
+ })));
78
+ }
79
+ /**
80
+ * A4 client half — TestServer emits a bit-flipped / truncated /
81
+ * oversized frame; real client drops it silently. A subsequent tagged
82
+ * valid event still surfaces (liveness proof, mirrors #187 round-5
83
+ * guard). A client that crashes on the malformed frame disconnects,
84
+ * preventing the liveness probe from surfacing within the deadline.
85
+ *
86
+ * Predicate: liveness — next tagged event surfaces within deadline.
87
+ */
88
+ export function registerMalformedFrameHandlingClient(ctx) {
89
+ registerProperty(ctx, CATEGORY, "malformed-frame-handling-client", "malformed TestServer emission absorbed silently; liveness intact", Effect.scoped(Effect.gen(function* () {
90
+ const fx = yield* acquireFixture(ctx, CATEGORY, "malformed-frame-handling-client");
91
+ yield* subscribeAll(fx.handle);
92
+ const baseEvent = fc.sample(arbitraryEventFrame(), {
93
+ numRuns: 1,
94
+ seed: ctx.seed,
95
+ })[0];
96
+ if (baseEvent === undefined) {
97
+ return yield* Effect.fail(invariant(CATEGORY, "malformed-frame-handling-client", "failed to sample base EventFrame"));
98
+ }
99
+ // Emit a malformed frame — the real client must absorb it.
100
+ yield* fx.connection
101
+ .emitMalformed({
102
+ baseEvent,
103
+ kind: "bit-flip",
104
+ seed: ctx.seed,
105
+ })
106
+ .pipe(Effect.orElseSucceed(() => undefined));
107
+ // Liveness probe: emit a valid tagged event after the malformed one.
108
+ const tag = yield* fx.window.freshEmissionTag;
109
+ yield* fx.window.emitTaggedEvent({
110
+ connection: fx.connection,
111
+ base: baseEvent,
112
+ emissionTag: tag,
113
+ });
114
+ const observed = yield* collectTagged(fx.handle, (t) => t === tag, {
115
+ expected: 1,
116
+ budgetMs: PROPERTY_BUDGET_MS,
117
+ });
118
+ if (observed.length === 0) {
119
+ return yield* Effect.fail(invariant(CATEGORY, "malformed-frame-handling-client", "liveness failed: no tagged event after malformed emission"));
120
+ }
121
+ })));
122
+ }
123
+ //# sourceMappingURL=schema-conformance.js.map