@volter/twin-tunnel 0.1.37 → 1.0.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 (87) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +13 -121
  3. package/dist/generated/pack-facts.json +54 -0
  4. package/dist/journeys/coverage.json +109 -0
  5. package/dist/journeys/customer-life.json +588 -0
  6. package/dist/journeys/customer-life.outline.md +34 -0
  7. package/dist/journeys/customer-life.outline.verdict.json +42 -0
  8. package/dist/journeys/customer-life.verdict.json +51 -0
  9. package/dist/journeys/decisions.json +23 -0
  10. package/dist/journeys/demand.json +24 -0
  11. package/dist/journeys/score.json +134 -0
  12. package/dist/journeys/unreachable.json +12 -0
  13. package/dist/journeys/vendor-examples.json +7 -0
  14. package/dist/journeys/vendor-examples.verdict.json +18 -0
  15. package/dist/spec/SOURCE.md +30 -0
  16. package/dist/spec/openapi.json +50 -0
  17. package/dist/spec/sources.json +1 -0
  18. package/dist/src/cli.js +15 -53
  19. package/dist/src/fetch.d.ts +2 -0
  20. package/dist/src/fetch.js +19 -0
  21. package/dist/src/generated/surface.gen.json +1 -0
  22. package/dist/src/generated/ui.gen.json +1 -0
  23. package/dist/src/index.d.ts +5 -7
  24. package/dist/src/index.js +9 -42
  25. package/dist/src/manifest.d.ts +2 -0
  26. package/dist/src/manifest.js +49 -0
  27. package/dist/src/semantics/around.d.ts +2 -0
  28. package/dist/src/semantics/around.js +72 -0
  29. package/dist/src/semantics/doors.d.ts +3 -0
  30. package/dist/src/semantics/doors.js +18 -0
  31. package/dist/src/semantics/index.d.ts +2 -0
  32. package/dist/src/semantics/index.js +1 -0
  33. package/dist/src/semantics/shared.d.ts +22 -0
  34. package/dist/src/semantics/shared.js +73 -0
  35. package/dist/src/semantics/sockets.d.ts +2 -0
  36. package/dist/src/semantics/sockets.js +161 -0
  37. package/dist/src/semantics/states.d.ts +1 -0
  38. package/dist/src/semantics/states.js +3 -0
  39. package/dist/src/server.d.ts +7 -0
  40. package/dist/src/server.js +9 -0
  41. package/generated/pack-facts.json +54 -0
  42. package/journeys/coverage.json +109 -0
  43. package/journeys/customer-life.json +588 -0
  44. package/journeys/customer-life.outline.md +34 -0
  45. package/journeys/customer-life.outline.verdict.json +42 -0
  46. package/journeys/customer-life.verdict.json +51 -0
  47. package/journeys/decisions.json +23 -0
  48. package/journeys/demand.json +24 -0
  49. package/journeys/score.json +134 -0
  50. package/journeys/unreachable.json +12 -0
  51. package/journeys/vendor-examples.json +7 -0
  52. package/journeys/vendor-examples.verdict.json +18 -0
  53. package/package.json +17 -32
  54. package/spec/SOURCE.md +30 -0
  55. package/spec/openapi.json +50 -0
  56. package/spec/sources.json +1 -0
  57. package/src/cli.ts +15 -41
  58. package/src/fetch.ts +20 -0
  59. package/src/generated/surface.gen.json +1 -0
  60. package/src/generated/ui.gen.json +1 -0
  61. package/src/index.ts +10 -52
  62. package/src/manifest.ts +54 -0
  63. package/src/semantics/around.ts +68 -0
  64. package/src/semantics/doors.ts +21 -0
  65. package/src/semantics/index.ts +6 -0
  66. package/src/semantics/shared.ts +78 -0
  67. package/src/semantics/sockets.ts +127 -0
  68. package/src/semantics/states.ts +3 -0
  69. package/src/server.ts +10 -0
  70. package/dist/src/tunnel-budget.d.ts +0 -21
  71. package/dist/src/tunnel-budget.js +0 -26
  72. package/dist/src/tunnel-capabilities.d.ts +0 -4
  73. package/dist/src/tunnel-capabilities.js +0 -2091
  74. package/dist/src/tunnel-conformance.d.ts +0 -10
  75. package/dist/src/tunnel-conformance.js +0 -63
  76. package/dist/src/tunnel-connector.d.ts +0 -21
  77. package/dist/src/tunnel-connector.js +0 -68
  78. package/dist/src/tunnel-server.d.ts +0 -109
  79. package/dist/src/tunnel-server.js +0 -1197
  80. package/dist/src/tunnel-twin.d.ts +0 -175
  81. package/dist/src/tunnel-twin.js +0 -236
  82. package/src/tunnel-budget.ts +0 -34
  83. package/src/tunnel-capabilities.ts +0 -1770
  84. package/src/tunnel-conformance.ts +0 -62
  85. package/src/tunnel-connector.ts +0 -64
  86. package/src/tunnel-server.ts +0 -1194
  87. package/src/tunnel-twin.ts +0 -339
@@ -0,0 +1,588 @@
1
+ {
2
+ "vendor": "tunnel",
3
+ "author": "Contributor 71",
4
+ "host": "voltertest.xyz",
5
+ "about": "Ada demonstrates Harness remote access to Bob; the local client answers buffered page requests and streams command output. Reconnect, copied configuration, a truncated token and shutdown supply the refusal causes.",
6
+ "steps": [
7
+ {
8
+ "name": "Ada creates her relay account for the shared Harness demo",
9
+ "method": "POST",
10
+ "path": "/_twin/accounts",
11
+ "body": {
12
+ "slug": "ada"
13
+ },
14
+ "status": 201,
15
+ "capture": {
16
+ "ada": "token"
17
+ },
18
+ "expect": [
19
+ {
20
+ "path": "token",
21
+ "matches": "^vt_[A-Za-z0-9]{40}$"
22
+ },
23
+ {
24
+ "path": "server",
25
+ "equals": "https://voltertest.xyz"
26
+ }
27
+ ]
28
+ },
29
+ {
30
+ "name": "Harness connects its named remote-access tunnel",
31
+ "socket": "h1",
32
+ "open": "/ws?id=ada-harness"
33
+ },
34
+ {
35
+ "name": "Harness registers with Ada's token and saves the returned visitor URL",
36
+ "socket": "h1",
37
+ "send": {
38
+ "type": "register",
39
+ "secret": "{ada}",
40
+ "authRequired": false,
41
+ "tunnelId": "ada-harness"
42
+ },
43
+ "capture": {
44
+ "public": "last.url"
45
+ },
46
+ "expect": [
47
+ {
48
+ "path": "last.type",
49
+ "equals": "registered"
50
+ },
51
+ {
52
+ "path": "last.tunnelId",
53
+ "equals": "ada-harness"
54
+ },
55
+ {
56
+ "path": "last.url",
57
+ "equals": "https://ada-harness.voltertest.xyz"
58
+ }
59
+ ]
60
+ },
61
+ {
62
+ "name": "Bob mistypes the tunnel name from Ada's invitation",
63
+ "method": "GET",
64
+ "path": "https://ada-hanress.voltertest.xyz/demo",
65
+ "status": 502,
66
+ "expect": [
67
+ {
68
+ "path": "",
69
+ "equals": "Tunnel not connected"
70
+ }
71
+ ]
72
+ },
73
+ {
74
+ "name": "Bob opens the shared demo page",
75
+ "method": "GET",
76
+ "path": "{public}/demo?view=remote",
77
+ "defer": "page"
78
+ },
79
+ {
80
+ "name": "Harness receives Bob's page request",
81
+ "socket": "h1",
82
+ "capture": {
83
+ "page": "last.reqId"
84
+ },
85
+ "expect": [
86
+ {
87
+ "path": "last.type",
88
+ "equals": "request"
89
+ },
90
+ {
91
+ "path": "last.method",
92
+ "equals": "GET"
93
+ },
94
+ {
95
+ "path": "last.path",
96
+ "equals": "/demo?view=remote"
97
+ },
98
+ {
99
+ "path": "last.body",
100
+ "equals": null
101
+ },
102
+ {
103
+ "path": "last.headers.x-forwarded-for",
104
+ "equals": "0.0.0.0"
105
+ }
106
+ ]
107
+ },
108
+ {
109
+ "name": "The local server returns the remote-access page",
110
+ "socket": "h1",
111
+ "send": {
112
+ "type": "response",
113
+ "reqId": "{page}",
114
+ "status": 200,
115
+ "headers": {
116
+ "content-type": "text/html",
117
+ "x-frame-options": "DENY",
118
+ "content-security-policy": "default-src 'self'; frame-ancestors 'none'"
119
+ },
120
+ "body": "PGgxPkFkYSdzIHJlbW90ZSBkZW1vPC9oMT4="
121
+ }
122
+ },
123
+ {
124
+ "name": "Bob sees the page supplied by Ada's local server",
125
+ "method": "GET",
126
+ "path": "{public}/demo?view=remote",
127
+ "await": "page",
128
+ "status": 200,
129
+ "expect": [
130
+ {
131
+ "path": "",
132
+ "equals": "<h1>Ada's remote demo</h1>"
133
+ },
134
+ {
135
+ "header": "content-type",
136
+ "contains": "text/html"
137
+ },
138
+ {
139
+ "header": "x-frame-options",
140
+ "exists": false
141
+ },
142
+ {
143
+ "header": "content-security-policy",
144
+ "equals": "default-src 'self'"
145
+ },
146
+ {
147
+ "header": "cache-control",
148
+ "equals": "private, no-store, max-age=0"
149
+ }
150
+ ]
151
+ },
152
+ {
153
+ "name": "Bob's remote viewer requests permission to send its command headers",
154
+ "method": "OPTIONS",
155
+ "path": "{public}/command",
156
+ "headers": {
157
+ "origin": "https://viewer.example",
158
+ "access-control-request-headers": "Content-Type, X-Volter-App"
159
+ },
160
+ "status": 204,
161
+ "expect": [
162
+ {
163
+ "header": "access-control-allow-origin",
164
+ "equals": "https://viewer.example"
165
+ },
166
+ {
167
+ "header": "access-control-allow-headers",
168
+ "equals": "Content-Type, X-Volter-App"
169
+ }
170
+ ]
171
+ },
172
+ {
173
+ "name": "Bob asks Harness to run a demo command",
174
+ "method": "POST",
175
+ "path": "{public}/command?__volter_token=visitor&mode=demo",
176
+ "body": "run demo",
177
+ "headers": {
178
+ "cookie": "session=bob; __volter_auth=relay",
179
+ "x-forwarded-for": "127.0.0.1",
180
+ "origin": "https://viewer.example",
181
+ "x-volter-app": "demo"
182
+ },
183
+ "defer": "command"
184
+ },
185
+ {
186
+ "name": "Harness receives the command without relay authentication data",
187
+ "socket": "h1",
188
+ "capture": {
189
+ "command": "last.reqId"
190
+ },
191
+ "expect": [
192
+ {
193
+ "path": "last.method",
194
+ "equals": "POST"
195
+ },
196
+ {
197
+ "path": "last.path",
198
+ "equals": "/command?mode=demo"
199
+ },
200
+ {
201
+ "path": "last.body",
202
+ "equals": "cnVuIGRlbW8="
203
+ },
204
+ {
205
+ "path": "last.headers.cookie",
206
+ "equals": "session=bob"
207
+ },
208
+ {
209
+ "path": "last.headers.x-forwarded-for",
210
+ "equals": "0.0.0.0"
211
+ },
212
+ {
213
+ "path": "last.headers.x-volter-app",
214
+ "equals": "demo"
215
+ }
216
+ ]
217
+ },
218
+ {
219
+ "name": "Harness starts streaming the command's output",
220
+ "socket": "h1",
221
+ "send": {
222
+ "type": "response-start",
223
+ "reqId": "{command}",
224
+ "status": 200,
225
+ "headers": {
226
+ "content-type": "text/plain",
227
+ "x-demo": "Ada"
228
+ }
229
+ }
230
+ },
231
+ {
232
+ "name": "The running command reports its progress",
233
+ "socket": "h1",
234
+ "send": {
235
+ "type": "response-chunk",
236
+ "reqId": "{command}",
237
+ "data": "U3RhcnRpbmcgZGVtbwo="
238
+ }
239
+ },
240
+ {
241
+ "name": "The command finishes and the stream ends",
242
+ "socket": "h1",
243
+ "send": [
244
+ {
245
+ "type": "response-chunk",
246
+ "reqId": "{command}",
247
+ "data": "RGVtbyBjb21wbGV0ZQo="
248
+ },
249
+ {
250
+ "type": "response-end",
251
+ "reqId": "{command}"
252
+ }
253
+ ]
254
+ },
255
+ {
256
+ "name": "Bob reads both chunks from the completed command",
257
+ "method": "POST",
258
+ "path": "{public}/command?__volter_token=visitor&mode=demo",
259
+ "await": "command",
260
+ "status": 200,
261
+ "expect": [
262
+ {
263
+ "path": "",
264
+ "equals": "Starting demo\nDemo complete\n"
265
+ },
266
+ {
267
+ "header": "x-demo",
268
+ "equals": "Ada"
269
+ },
270
+ {
271
+ "header": "access-control-allow-origin",
272
+ "equals": "https://viewer.example"
273
+ }
274
+ ]
275
+ },
276
+ {
277
+ "name": "Ada opens a separate control-protocol diagnostic while setting up the second Harness connector",
278
+ "socket": "h2",
279
+ "open": "/ws?id=ada-harness"
280
+ },
281
+ {
282
+ "name": "Ada's diagnostic omits replacement and receives the relay's live-connector collision refusal",
283
+ "socket": "h2",
284
+ "send": {
285
+ "type": "register",
286
+ "secret": "{ada}",
287
+ "authRequired": false,
288
+ "tunnelId": "ada-harness"
289
+ },
290
+ "status": 4002,
291
+ "expect": [
292
+ {
293
+ "path": "last.type",
294
+ "equals": "error"
295
+ },
296
+ {
297
+ "path": "last.message",
298
+ "contains": "already in use"
299
+ },
300
+ {
301
+ "path": "closed.reason",
302
+ "equals": "Tunnel ID already in use"
303
+ }
304
+ ]
305
+ },
306
+ {
307
+ "name": "Ada starts the actual Harness connector whose SDK requests replacement automatically",
308
+ "socket": "h3",
309
+ "open": "/ws?id=ada-harness"
310
+ },
311
+ {
312
+ "name": "Harness's automatic SDK replacement keeps the existing demo URL",
313
+ "socket": "h3",
314
+ "send": {
315
+ "type": "register",
316
+ "secret": "{ada}",
317
+ "authRequired": false,
318
+ "replace": true,
319
+ "tunnelId": "ada-harness"
320
+ },
321
+ "expect": [
322
+ {
323
+ "path": "last.type",
324
+ "equals": "registered"
325
+ },
326
+ {
327
+ "path": "last.url",
328
+ "equals": "{public}"
329
+ }
330
+ ]
331
+ },
332
+ {
333
+ "name": "Harness releases the obsolete connector after the relay replaced it",
334
+ "socket": "h1",
335
+ "status": 4001,
336
+ "expect": [
337
+ {
338
+ "path": "closed.reason",
339
+ "equals": "Replaced by new client"
340
+ }
341
+ ],
342
+ "close": true
343
+ },
344
+ {
345
+ "name": "Bob checks that the restarted demo still answers",
346
+ "method": "HEAD",
347
+ "path": "{public}/ready",
348
+ "defer": "ready"
349
+ },
350
+ {
351
+ "name": "The replacement receives the readiness request",
352
+ "socket": "h3",
353
+ "capture": {
354
+ "ready": "last.reqId"
355
+ },
356
+ "expect": [
357
+ {
358
+ "path": "last.type",
359
+ "equals": "request"
360
+ },
361
+ {
362
+ "path": "last.path",
363
+ "equals": "/ready"
364
+ }
365
+ ]
366
+ },
367
+ {
368
+ "name": "The local readiness endpoint replies",
369
+ "socket": "h3",
370
+ "send": [
371
+ {
372
+ "type": "response-start",
373
+ "reqId": "{ready}",
374
+ "status": 200,
375
+ "headers": {
376
+ "x-ready": "Ready"
377
+ }
378
+ },
379
+ {
380
+ "type": "response-end",
381
+ "reqId": "{ready}"
382
+ }
383
+ ]
384
+ },
385
+ {
386
+ "name": "Bob gets the readiness response through the replacement",
387
+ "method": "HEAD",
388
+ "path": "{public}/ready",
389
+ "await": "ready",
390
+ "status": 200,
391
+ "expect": [
392
+ {
393
+ "path": "",
394
+ "equals": ""
395
+ },
396
+ {
397
+ "header": "x-ready",
398
+ "equals": "Ready"
399
+ }
400
+ ]
401
+ },
402
+ {
403
+ "name": "Bob creates an account to host his own remote session",
404
+ "method": "POST",
405
+ "path": "/_twin/accounts",
406
+ "body": {
407
+ "slug": "bob"
408
+ },
409
+ "status": 201,
410
+ "capture": {
411
+ "bob": "token"
412
+ }
413
+ },
414
+ {
415
+ "name": "Bob copied Ada's tunnel name into his connector configuration",
416
+ "socket": "b1",
417
+ "open": "/ws?id=ada-harness"
418
+ },
419
+ {
420
+ "name": "Bob's account cannot claim Ada's reserved name",
421
+ "socket": "b1",
422
+ "send": {
423
+ "type": "register",
424
+ "secret": "{bob}",
425
+ "authRequired": false,
426
+ "tunnelId": "ada-harness",
427
+ "replace": true
428
+ },
429
+ "status": 4002,
430
+ "expect": [
431
+ {
432
+ "path": "last.message",
433
+ "contains": "reserved by another account"
434
+ },
435
+ {
436
+ "path": "closed.reason",
437
+ "equals": "Tunnel ID reserved"
438
+ }
439
+ ]
440
+ },
441
+ {
442
+ "name": "Bob corrects the name but pastes an incomplete account token",
443
+ "socket": "b2",
444
+ "open": "/ws?id=bob-harness"
445
+ },
446
+ {
447
+ "name": "The incomplete token is rejected before any reservation is made",
448
+ "socket": "b2",
449
+ "send": {
450
+ "type": "register",
451
+ "secret": "vt_incomplete",
452
+ "authRequired": false,
453
+ "tunnelId": "bob-harness"
454
+ },
455
+ "status": 4003,
456
+ "expect": [
457
+ {
458
+ "path": "last.message",
459
+ "equals": "Invalid tunnel secret"
460
+ },
461
+ {
462
+ "path": "closed.reason",
463
+ "equals": "Invalid tunnel secret"
464
+ }
465
+ ]
466
+ },
467
+ {
468
+ "name": "Bob reconnects with the token his account actually returned",
469
+ "socket": "b3",
470
+ "open": "/ws?id=bob-harness"
471
+ },
472
+ {
473
+ "name": "Bob now has his own public demo URL",
474
+ "socket": "b3",
475
+ "send": {
476
+ "type": "register",
477
+ "secret": "{bob}",
478
+ "authRequired": false,
479
+ "tunnelId": "bob-harness"
480
+ },
481
+ "capture": {
482
+ "bobPublic": "last.url"
483
+ },
484
+ "expect": [
485
+ {
486
+ "path": "last.type",
487
+ "equals": "registered"
488
+ },
489
+ {
490
+ "path": "last.url",
491
+ "equals": "https://bob-harness.voltertest.xyz"
492
+ }
493
+ ]
494
+ },
495
+ {
496
+ "name": "Bob requests one last local command while Ada is ending the demo",
497
+ "method": "GET",
498
+ "path": "{public}/last-command",
499
+ "defer": "last-command"
500
+ },
501
+ {
502
+ "name": "Harness receives the last command before the laptop disconnects",
503
+ "socket": "h3",
504
+ "expect": [
505
+ {
506
+ "path": "last.type",
507
+ "equals": "request"
508
+ },
509
+ {
510
+ "path": "last.path",
511
+ "equals": "/last-command"
512
+ }
513
+ ]
514
+ },
515
+ {
516
+ "name": "Ada ends her demo and closes its connector",
517
+ "socket": "h3",
518
+ "close": true,
519
+ "status": 1000
520
+ },
521
+ {
522
+ "name": "Bob's pending command reports the disconnected connector",
523
+ "method": "GET",
524
+ "path": "{public}/last-command",
525
+ "await": "last-command",
526
+ "status": 502,
527
+ "expect": [
528
+ {
529
+ "path": "",
530
+ "equals": "Tunnel disconnected"
531
+ }
532
+ ]
533
+ },
534
+ {
535
+ "name": "Bob checks his own local relay connection before closing it",
536
+ "method": "GET",
537
+ "path": "http://localhost/check?__tunnel=bob-harness",
538
+ "defer": "bob-check"
539
+ },
540
+ {
541
+ "name": "Bob's connector receives the direct-host diagnostic with the routing parameter removed",
542
+ "socket": "b3",
543
+ "capture": {
544
+ "bobCheck": "last.reqId"
545
+ },
546
+ "expect": [
547
+ {
548
+ "path": "last.path",
549
+ "equals": "/check"
550
+ },
551
+ {
552
+ "path": "last.body",
553
+ "equals": null
554
+ }
555
+ ]
556
+ },
557
+ {
558
+ "name": "Bob's local server answers the diagnostic",
559
+ "socket": "b3",
560
+ "send": {
561
+ "type": "response",
562
+ "reqId": "{bobCheck}",
563
+ "status": 200,
564
+ "headers": {},
565
+ "body": "Qm9iIGNvbm5lY3RlZA=="
566
+ }
567
+ },
568
+ {
569
+ "name": "Bob confirms his separate tunnel still works",
570
+ "method": "GET",
571
+ "path": "http://localhost/check?__tunnel=bob-harness",
572
+ "await": "bob-check",
573
+ "status": 200,
574
+ "expect": [
575
+ {
576
+ "path": "",
577
+ "equals": "Bob connected"
578
+ }
579
+ ]
580
+ },
581
+ {
582
+ "name": "Bob closes his own connector after the demo",
583
+ "socket": "b3",
584
+ "close": true,
585
+ "status": 1000
586
+ }
587
+ ]
588
+ }
@@ -0,0 +1,34 @@
1
+ # A shared Harness demo
2
+
3
+ Ada runs Harness on her laptop. Her relay account's token grants her connector
4
+ the right to register her demo tunnel. Bob first visits its public URL without a
5
+ relay credential: Ada deliberately shares an unauthenticated remote-access demo.
6
+ Later Bob creates his own account, whose token can register his own tunnel but
7
+ cannot take Ada's reserved name. The story takes place in one sitting; it uses
8
+ neither metered quotas nor a credential with a limited grant.
9
+
10
+ 1. Ada signs up and gives Harness the account token she was shown. Harness
11
+ connects its named tunnel and shares the visitor URL returned by the relay.
12
+ 2. Bob mistypes the tunnel name from Ada's invitation and cannot reach a
13
+ connector there. He follows the actual shared URL to correct the mistake. Harness receives the page request and answers it with
14
+ the page from Ada's local server. Bob's remote viewer asks permission to send its command headers and runs a demo command;
15
+ Harness streams progress and completion, and Bob receives the complete output.
16
+ 3. While setting up a second Harness connector for the demo, Ada first uses a
17
+ separate diagnostic client speaking the relay's documented control protocol.
18
+ It registers the existing name without requesting replacement and is refused.
19
+ She then starts the actual Harness connector, whose SDK requests replacement
20
+ automatically; the previous connector closes, the URL stays stable, and Bob
21
+ checks that the restarted local server answers.
22
+ 4. Bob creates his own relay account. He copied Ada's tunnel name and discovers
23
+ that his account cannot claim it. He corrects the name but pastes an incomplete
24
+ token. After that is rejected, he reconnects using the token actually returned
25
+ by his account and receives his own demo URL.
26
+ 5. Bob requests a final command just as Ada closes the connector at the end of
27
+ the demonstration. The pending command reports the disconnect. Bob uses the local relay's direct
28
+ addressing diagnostic to check his separate connector still answers, then
29
+ closes his own connector.
30
+
31
+ The account act uses the World account door. Registration and reconnection use
32
+ the control socket. A visitor's request is started while the local client answers
33
+ its control frame, then its completed HTTP response is checked. Both buffered
34
+ and streamed replies therefore occur in the executable story.
@@ -0,0 +1,42 @@
1
+ {
2
+ "sha256": "fd151bd1155b27b8cf5a0503e5dd0ce08fdcc442264417ce2e95fa2c49c783de",
3
+ "plausible": true,
4
+ "bar": "829132787d94a2d4",
5
+ "judge": {
6
+ "name": "Contributor 44",
7
+ "agent": "Contributor 20"
8
+ },
9
+ "authors": [
10
+ "Contributor 43",
11
+ "Contributor 1"
12
+ ],
13
+ "rounds": 1,
14
+ "rulings": [
15
+ {
16
+ "id": "R1",
17
+ "acts": [
18
+ 3
19
+ ],
20
+ "required": "Identify separate protocol diagnostic and SDK automatic replacement"
21
+ }
22
+ ],
23
+ "doubts": [
24
+ "See independent exhaustive report in the retained independent review"
25
+ ],
26
+ "applied": {
27
+ "from": "e68d21e7aa7caceefff49ebbd2b1b7d7be2130188cc92b3e4fe63083ff0a0d83",
28
+ "wasPlausible": false,
29
+ "steps": [
30
+ "3"
31
+ ],
32
+ "rulings": [
33
+ {
34
+ "id": "R1",
35
+ "acts": [
36
+ 3
37
+ ],
38
+ "required": "Identify separate protocol diagnostic and SDK automatic replacement"
39
+ }
40
+ ]
41
+ }
42
+ }