@agoric/async-flow 0.1.1-dev-f0f1a3e.0 → 0.1.1-dev-4adf64f.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/async-flow",
3
- "version": "0.1.1-dev-f0f1a3e.0+f0f1a3e",
3
+ "version": "0.1.1-dev-4adf64f.0+4adf64f",
4
4
  "description": "Upgrade async functions at await points by replay",
5
5
  "type": "module",
6
6
  "repository": "https://github.com/Agoric/agoric-sdk",
@@ -24,10 +24,10 @@
24
24
  "author": "Agoric",
25
25
  "license": "Apache-2.0",
26
26
  "dependencies": {
27
- "@agoric/base-zone": "0.1.1-dev-f0f1a3e.0+f0f1a3e",
28
- "@agoric/internal": "0.3.3-dev-f0f1a3e.0+f0f1a3e",
29
- "@agoric/store": "0.9.3-dev-f0f1a3e.0+f0f1a3e",
30
- "@agoric/vow": "0.1.1-dev-f0f1a3e.0+f0f1a3e",
27
+ "@agoric/base-zone": "0.1.1-dev-4adf64f.0+4adf64f",
28
+ "@agoric/internal": "0.3.3-dev-4adf64f.0+4adf64f",
29
+ "@agoric/store": "0.9.3-dev-4adf64f.0+4adf64f",
30
+ "@agoric/vow": "0.1.1-dev-4adf64f.0+4adf64f",
31
31
  "@endo/common": "^1.2.2",
32
32
  "@endo/errors": "^1.2.2",
33
33
  "@endo/eventual-send": "^1.2.2",
@@ -37,8 +37,8 @@
37
37
  "@endo/promise-kit": "^1.1.2"
38
38
  },
39
39
  "devDependencies": {
40
- "@agoric/swingset-liveslots": "0.10.3-dev-f0f1a3e.0+f0f1a3e",
41
- "@agoric/zone": "0.2.3-dev-f0f1a3e.0+f0f1a3e",
40
+ "@agoric/swingset-liveslots": "0.10.3-dev-4adf64f.0+4adf64f",
41
+ "@agoric/zone": "0.2.3-dev-4adf64f.0+4adf64f",
42
42
  "@endo/env-options": "^1.1.4",
43
43
  "@endo/ses-ava": "^1.2.2",
44
44
  "ava": "^5.3.0"
@@ -62,5 +62,5 @@
62
62
  "typeCoverage": {
63
63
  "atLeast": 77.83
64
64
  },
65
- "gitHead": "f0f1a3e0a10cae58f85cf41061950101230bc9fe"
65
+ "gitHead": "4adf64fd53a1a3c68ca52728710830201c9a4418"
66
66
  }
@@ -16,7 +16,7 @@ export function prepareAsyncFlowTools(outerZone: Zone, outerOptions?: Preparatio
16
16
  restart(eager?: boolean | undefined): void;
17
17
  wake(): void;
18
18
  getOutcome(): import("@agoric/vow").Vow<any>;
19
- dump(): ([op: "doFulfill", vow: import("@agoric/vow").Vow<import("@endo/pass-style").Passable>, fulfillment: import("@endo/pass-style").Passable] | [op: "doReject", vow: import("@agoric/vow").Vow<import("@endo/pass-style").Passable>, reason: import("@endo/pass-style").Passable] | [op: "doReturn", callIndex: number, result: import("@endo/pass-style").Passable] | [op: "doThrow", callIndex: number, problem: import("@endo/pass-style").Passable] | [op: "checkCall", target: import("@endo/pass-style").Passable, optVerb: PropertyKey | undefined, args: import("@endo/pass-style").Passable[], callIndex: number])[];
19
+ dump(): ([op: "doFulfill", vow: import("@agoric/vow").Vow<import("@endo/pass-style").Passable>, fulfillment: import("@endo/pass-style").Passable] | [op: "doReject", vow: import("@agoric/vow").Vow<import("@endo/pass-style").Passable>, reason: import("@endo/pass-style").Passable] | [op: "doReturn", callIndex: number, result: import("@endo/pass-style").Passable] | [op: "doThrow", callIndex: number, problem: import("@endo/pass-style").Passable] | [op: "checkCall", target: import("@endo/pass-style").Passable, optVerb: PropertyKey | undefined, args: import("@endo/pass-style").Passable[], callIndex: number] | [op: "checkSendOnly", target: import("@endo/pass-style").Passable, optVerb: PropertyKey | undefined, args: import("@endo/pass-style").Passable[], callIndex: number] | [op: "checkSend", target: import("@endo/pass-style").Passable, optVerb: PropertyKey | undefined, args: import("@endo/pass-style").Passable[], callIndex: number])[];
20
20
  getOptFatalProblem(): any;
21
21
  };
22
22
  admin: {
@@ -58,7 +58,7 @@ export type AsyncFlowTools = ReturnType<(outerZone: Zone, outerOptions?: Prepara
58
58
  restart(eager?: boolean | undefined): void;
59
59
  wake(): void;
60
60
  getOutcome(): import("@agoric/vow").Vow<any>;
61
- dump(): ([op: "doFulfill", vow: import("@agoric/vow").Vow<import("@endo/pass-style").Passable>, fulfillment: import("@endo/pass-style").Passable] | [op: "doReject", vow: import("@agoric/vow").Vow<import("@endo/pass-style").Passable>, reason: import("@endo/pass-style").Passable] | [op: "doReturn", callIndex: number, result: import("@endo/pass-style").Passable] | [op: "doThrow", callIndex: number, problem: import("@endo/pass-style").Passable] | [op: "checkCall", target: import("@endo/pass-style").Passable, optVerb: PropertyKey | undefined, args: import("@endo/pass-style").Passable[], callIndex: number])[];
61
+ dump(): ([op: "doFulfill", vow: import("@agoric/vow").Vow<import("@endo/pass-style").Passable>, fulfillment: import("@endo/pass-style").Passable] | [op: "doReject", vow: import("@agoric/vow").Vow<import("@endo/pass-style").Passable>, reason: import("@endo/pass-style").Passable] | [op: "doReturn", callIndex: number, result: import("@endo/pass-style").Passable] | [op: "doThrow", callIndex: number, problem: import("@endo/pass-style").Passable] | [op: "checkCall", target: import("@endo/pass-style").Passable, optVerb: PropertyKey | undefined, args: import("@endo/pass-style").Passable[], callIndex: number] | [op: "checkSendOnly", target: import("@endo/pass-style").Passable, optVerb: PropertyKey | undefined, args: import("@endo/pass-style").Passable[], callIndex: number] | [op: "checkSend", target: import("@endo/pass-style").Passable, optVerb: PropertyKey | undefined, args: import("@endo/pass-style").Passable[], callIndex: number])[];
62
62
  getOptFatalProblem(): any;
63
63
  };
64
64
  admin: {
@@ -7,7 +7,7 @@ export function prepareLogStore(zone: Zone): () => import("@endo/exo").Guarded<{
7
7
  peekEntry(): LogEntry;
8
8
  nextEntry(): LogEntry;
9
9
  pushEntry(entry: any): number;
10
- dump(): ([op: "doFulfill", vow: import("@agoric/vow").Vow<import("@endo/pass-style").Passable>, fulfillment: import("@endo/pass-style").Passable] | [op: "doReject", vow: import("@agoric/vow").Vow<import("@endo/pass-style").Passable>, reason: import("@endo/pass-style").Passable] | [op: "doReturn", callIndex: number, result: import("@endo/pass-style").Passable] | [op: "doThrow", callIndex: number, problem: import("@endo/pass-style").Passable] | [op: "checkCall", target: import("@endo/pass-style").Passable, optVerb: PropertyKey | undefined, args: import("@endo/pass-style").Passable[], callIndex: number])[];
10
+ dump(): ([op: "doFulfill", vow: import("@agoric/vow").Vow<import("@endo/pass-style").Passable>, fulfillment: import("@endo/pass-style").Passable] | [op: "doReject", vow: import("@agoric/vow").Vow<import("@endo/pass-style").Passable>, reason: import("@endo/pass-style").Passable] | [op: "doReturn", callIndex: number, result: import("@endo/pass-style").Passable] | [op: "doThrow", callIndex: number, problem: import("@endo/pass-style").Passable] | [op: "checkCall", target: import("@endo/pass-style").Passable, optVerb: PropertyKey | undefined, args: import("@endo/pass-style").Passable[], callIndex: number] | [op: "checkSendOnly", target: import("@endo/pass-style").Passable, optVerb: PropertyKey | undefined, args: import("@endo/pass-style").Passable[], callIndex: number] | [op: "checkSend", target: import("@endo/pass-style").Passable, optVerb: PropertyKey | undefined, args: import("@endo/pass-style").Passable[], callIndex: number])[];
11
11
  promiseReplayDone(): Promise<undefined>;
12
12
  }>;
13
13
  export type LogStore = ReturnType<ReturnType<(zone: Zone) => () => import("@endo/exo").Guarded<{
@@ -19,7 +19,7 @@ export type LogStore = ReturnType<ReturnType<(zone: Zone) => () => import("@endo
19
19
  peekEntry(): LogEntry;
20
20
  nextEntry(): LogEntry;
21
21
  pushEntry(entry: any): number;
22
- dump(): ([op: "doFulfill", vow: import("@agoric/vow").Vow<import("@endo/pass-style").Passable>, fulfillment: import("@endo/pass-style").Passable] | [op: "doReject", vow: import("@agoric/vow").Vow<import("@endo/pass-style").Passable>, reason: import("@endo/pass-style").Passable] | [op: "doReturn", callIndex: number, result: import("@endo/pass-style").Passable] | [op: "doThrow", callIndex: number, problem: import("@endo/pass-style").Passable] | [op: "checkCall", target: import("@endo/pass-style").Passable, optVerb: PropertyKey | undefined, args: import("@endo/pass-style").Passable[], callIndex: number])[];
22
+ dump(): ([op: "doFulfill", vow: import("@agoric/vow").Vow<import("@endo/pass-style").Passable>, fulfillment: import("@endo/pass-style").Passable] | [op: "doReject", vow: import("@agoric/vow").Vow<import("@endo/pass-style").Passable>, reason: import("@endo/pass-style").Passable] | [op: "doReturn", callIndex: number, result: import("@endo/pass-style").Passable] | [op: "doThrow", callIndex: number, problem: import("@endo/pass-style").Passable] | [op: "checkCall", target: import("@endo/pass-style").Passable, optVerb: PropertyKey | undefined, args: import("@endo/pass-style").Passable[], callIndex: number] | [op: "checkSendOnly", target: import("@endo/pass-style").Passable, optVerb: PropertyKey | undefined, args: import("@endo/pass-style").Passable[], callIndex: number] | [op: "checkSend", target: import("@endo/pass-style").Passable, optVerb: PropertyKey | undefined, args: import("@endo/pass-style").Passable[], callIndex: number])[];
23
23
  promiseReplayDone(): Promise<undefined>;
24
24
  }>>>;
25
25
  import type { Zone } from '@agoric/base-zone';
@@ -5,13 +5,13 @@ export function makeReplayMembrane({ log, bijection, vowTools, watchWake, panic,
5
5
  watchWake: (vowish: Promise<any> | Vow) => void;
6
6
  panic: (problem: Error) => never;
7
7
  }): {
8
- hostToGuest: (specimen: import("@endo/pass-style").Passable, label?: string | undefined) => any;
9
- guestToHost: (specimen: import("@endo/pass-style").Passable, label?: string | undefined) => any;
8
+ hostToGuest: (specimen: Passable, label?: string | undefined) => any;
9
+ guestToHost: (specimen: Passable, label?: string | undefined) => any;
10
10
  wake: () => void;
11
11
  stop: () => void;
12
12
  } & import("@endo/pass-style").RemotableObject<`Alleged: ${string}`> & import("@endo/eventual-send").RemotableBrand<{}, {
13
- hostToGuest: (specimen: import("@endo/pass-style").Passable, label?: string | undefined) => any;
14
- guestToHost: (specimen: import("@endo/pass-style").Passable, label?: string | undefined) => any;
13
+ hostToGuest: (specimen: Passable, label?: string | undefined) => any;
14
+ guestToHost: (specimen: Passable, label?: string | undefined) => any;
15
15
  wake: () => void;
16
16
  stop: () => void;
17
17
  }>;
@@ -22,13 +22,13 @@ export type ReplayMembrane = ReturnType<({ log, bijection, vowTools, watchWake,
22
22
  watchWake: (vowish: Promise<any> | Vow) => void;
23
23
  panic: (problem: Error) => never;
24
24
  }) => {
25
- hostToGuest: (specimen: import("@endo/pass-style").Passable, label?: string | undefined) => any;
26
- guestToHost: (specimen: import("@endo/pass-style").Passable, label?: string | undefined) => any;
25
+ hostToGuest: (specimen: Passable, label?: string | undefined) => any;
26
+ guestToHost: (specimen: Passable, label?: string | undefined) => any;
27
27
  wake: () => void;
28
28
  stop: () => void;
29
29
  } & import("@endo/pass-style").RemotableObject<`Alleged: ${string}`> & import("@endo/eventual-send").RemotableBrand<{}, {
30
- hostToGuest: (specimen: import("@endo/pass-style").Passable, label?: string | undefined) => any;
31
- guestToHost: (specimen: import("@endo/pass-style").Passable, label?: string | undefined) => any;
30
+ hostToGuest: (specimen: Passable, label?: string | undefined) => any;
31
+ guestToHost: (specimen: Passable, label?: string | undefined) => any;
32
32
  wake: () => void;
33
33
  stop: () => void;
34
34
  }>>;
@@ -36,4 +36,5 @@ import type { LogStore } from '../src/log-store.js';
36
36
  import type { Bijection } from '../src/bijection.js';
37
37
  import type { VowTools } from '@agoric/vow';
38
38
  import type { Vow } from '@agoric/vow';
39
+ import type { Passable } from '@endo/pass-style';
39
40
  //# sourceMappingURL=replay-membrane.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"replay-membrane.d.ts","sourceRoot":"","sources":["replay-membrane.js"],"names":[],"mappings":"AA4BO,oFANJ;IAAsB,GAAG,EAAjB,QAAQ;IACO,SAAS,EAAxB,SAAS;IACK,QAAQ,EAAtB,QAAQ;IAC6B,SAAS,EAA9C,CAAC,MAAM,EAAE,eAAU,GAAG,KAAK,IAAI;IACA,KAAK,EAApC,CAAC,OAAO,EAAE,KAAK,KAAK,KAAK;CACnC;;;;;;;;;;GAweA;6BAGa,UAAU,mDAhfrB;IAAsB,GAAG,EAAjB,QAAQ;IACO,SAAS,EAAxB,SAAS;IACK,QAAQ,EAAtB,QAAQ;IAC6B,SAAS,EAA9C,CAAC,MAAM,EAAE,eAAU,GAAG,KAAK,IAAI;IACA,KAAK,EAApC,CAAC,OAAO,EAAE,KAAK,KAAK,KAAK;CACnC;;;;;;;;;;GA2e2C;8BAzfjB,qBAAqB;+BACpB,qBAAqB;8BAFjB,aAAa;yBAAb,aAAa"}
1
+ {"version":3,"file":"replay-membrane.d.ts","sourceRoot":"","sources":["replay-membrane.js"],"names":[],"mappings":"AAsCO,oFANJ;IAAsB,GAAG,EAAjB,QAAQ;IACO,SAAS,EAAxB,SAAS;IACK,QAAQ,EAAtB,QAAQ;IAC6B,SAAS,EAA9C,CAAC,MAAM,EAAE,eAAU,GAAG,KAAK,IAAI;IACA,KAAK,EAApC,CAAC,OAAO,EAAE,KAAK,KAAK,KAAK;CACnC;;;;;;;;;;GAusBA;6BAGa,UAAU,mDA/sBrB;IAAsB,GAAG,EAAjB,QAAQ;IACO,SAAS,EAAxB,SAAS;IACK,QAAQ,EAAtB,QAAQ;IAC6B,SAAS,EAA9C,CAAC,MAAM,EAAE,eAAU,GAAG,KAAK,IAAI;IACA,KAAK,EAApC,CAAC,OAAO,EAAE,KAAK,KAAK,KAAK;CACnC;;;;;;;;;;GA0sB2C;8BAxtBjB,qBAAqB;+BACpB,qBAAqB;8BAFT,aAAa;yBAAb,aAAa;8BADD,kBAAkB"}
@@ -1,16 +1,26 @@
1
1
  /* eslint-disable no-use-before-define */
2
2
  import { Fail, X, b, makeError, q } from '@endo/errors';
3
- import { isPromise } from '@endo/promise-kit';
4
- import { Far, Remotable, getInterfaceOf } from '@endo/pass-style';
3
+ import {
4
+ Far,
5
+ Remotable,
6
+ getInterfaceOf,
7
+ getTag,
8
+ makeTagged,
9
+ passStyleOf,
10
+ } from '@endo/pass-style';
5
11
  import { E } from '@endo/eventual-send';
12
+ import { throwLabeled } from '@endo/common/throw-labeled.js';
13
+ import { heapVowE } from '@agoric/vow/vat.js';
6
14
  import { getMethodNames } from '@endo/eventual-send/utils.js';
15
+ import { objectMap } from '@endo/common/object-map.js';
7
16
  import { isVow } from '@agoric/vow/src/vow-utils.js';
8
17
  import { makeEquate } from './equate.js';
9
18
  import { makeConvertKit } from './convert.js';
10
19
 
11
20
  /**
12
21
  * @import {PromiseKit} from '@endo/promise-kit'
13
- * @import {Vow, VowTools} from '@agoric/vow'
22
+ * @import {Passable, PassableCap, CopyTagged} from '@endo/pass-style'
23
+ * @import {Vow, VowTools, VowKit} from '@agoric/vow'
14
24
  * @import {LogStore} from '../src/log-store.js';
15
25
  * @import {Bijection} from '../src/bijection.js';
16
26
  * @import {Host, HostVow, LogEntry, Outcome} from '../src/types.js';
@@ -33,7 +43,7 @@ export const makeReplayMembrane = ({
33
43
  watchWake,
34
44
  panic,
35
45
  }) => {
36
- const { when, watch } = vowTools;
46
+ const { when, watch, makeVowKit } = vowTools;
37
47
 
38
48
  const equate = makeEquate(bijection);
39
49
 
@@ -127,18 +137,50 @@ export const makeReplayMembrane = ({
127
137
 
128
138
  // ///////////// Guest to Host or consume log ////////////////////////////////
129
139
 
140
+ /**
141
+ * The host is not supposed to expose host-side promises to the membrane,
142
+ * since they cannot be stored durably or survive upgrade. We cannot just
143
+ * automatically wrap any such host promises with host vows, because that
144
+ * would mask upgrade hazards if an upgrade happens before the vow settles.
145
+ * However, during the transition, the current host APIs called by
146
+ * orchestration still return many promises. We want to generate diagnostics
147
+ * when we encounter them, but for now, automatically convert them to
148
+ * host vow anyway, just so integration testing can proceed to reveal
149
+ * additional problems beyond these.
150
+ *
151
+ * @param {Passable} h
152
+ */
130
153
  const tolerateHostPromiseToVow = h => {
131
- if (isPromise(h)) {
132
- const e = Error('where warning happened');
133
- console.log('Warning for now: vow expected, not promise', h, e);
134
- // TODO remove this stopgap. Here for now because host-side
135
- // promises are everywhere!
136
- // Note: A good place to set a breakpoint, or to uncomment the
137
- // `debugger;` line, to work around bundling.
138
- // debugger;
139
- return watch(h);
140
- } else {
141
- return h;
154
+ const passStyle = passStyleOf(h);
155
+ switch (passStyle) {
156
+ case 'promise': {
157
+ const e = Error('where warning happened');
158
+ console.log('Warning for now: vow expected, not promise', h, e);
159
+ // TODO remove this stopgap. Here for now because host-side
160
+ // promises are everywhere!
161
+ // Note: A good place to set a breakpoint, or to uncomment the
162
+ // `debugger;` line, to work around bundling.
163
+ // debugger;
164
+ return watch(h);
165
+ }
166
+ case 'copyRecord': {
167
+ const o = /** @type {object} */ (h);
168
+ return objectMap(o, tolerateHostPromiseToVow);
169
+ }
170
+ case 'copyArray': {
171
+ const a = /** @type {Array} */ (h);
172
+ return harden(a.map(tolerateHostPromiseToVow));
173
+ }
174
+ case 'tagged': {
175
+ const t = /** @type {CopyTagged} */ (h);
176
+ if (isVow(t)) {
177
+ return h;
178
+ }
179
+ return makeTagged(getTag(t), tolerateHostPromiseToVow(t.payload));
180
+ }
181
+ default: {
182
+ return h;
183
+ }
142
184
  }
143
185
  };
144
186
 
@@ -150,6 +192,7 @@ export const makeReplayMembrane = ({
150
192
  : hostTarget(...hostArgs);
151
193
  // This is a temporary kludge anyway. But note that it only
152
194
  // catches the case where the promise is at the top of hostResult.
195
+ harden(hostResult);
153
196
  hostResult = tolerateHostPromiseToVow(hostResult);
154
197
  // Try converting here just to route the error correctly
155
198
  hostToGuest(hostResult, `converting ${optVerb || 'host'} result`);
@@ -233,14 +276,193 @@ export const makeReplayMembrane = ({
233
276
 
234
277
  // //////////////// Eventual Send ////////////////////////////////////////////
235
278
 
279
+ /**
280
+ * @param {PassableCap} hostTarget
281
+ * @param {string | undefined} optVerb
282
+ * @param {Passable[]} hostArgs
283
+ */
284
+ const performSendOnly = (hostTarget, optVerb, hostArgs) => {
285
+ try {
286
+ optVerb
287
+ ? heapVowE.sendOnly(hostTarget)[optVerb](...hostArgs)
288
+ : // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
289
+ // @ts-ignore once we changed this from E to heapVowE,
290
+ // typescript started complaining that heapVowE(hostTarget)
291
+ // is not callable. I'm not sure if this is a just a typing bug
292
+ // in heapVowE or also reflects a runtime deficiency. But this
293
+ // case it not used yet anyway. We disable it
294
+ // with at-ts-ignore rather than at-ts-expect-error because
295
+ // the dependency-graph tests complains that the latter is unused.
296
+ heapVowE.sendOnly(hostTarget)(...hostArgs);
297
+ } catch (hostProblem) {
298
+ throw Panic`internal: eventual sendOnly synchrously failed ${hostProblem}`;
299
+ }
300
+ };
301
+
302
+ /**
303
+ * @param {PassableCap} hostTarget
304
+ * @param {string | undefined} optVerb
305
+ * @param {Passable[]} hostArgs
306
+ * @param {number} callIndex
307
+ * @param {VowKit} hostResultKit
308
+ * @param {Promise} guestReturnedP
309
+ * @returns {Outcome}
310
+ */
311
+ const performSend = (
312
+ hostTarget,
313
+ optVerb,
314
+ hostArgs,
315
+ callIndex,
316
+ hostResultKit,
317
+ guestReturnedP,
318
+ ) => {
319
+ const { vow, resolver } = hostResultKit;
320
+ try {
321
+ const hostPromise = optVerb
322
+ ? heapVowE(hostTarget)[optVerb](...hostArgs)
323
+ : // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
324
+ // @ts-ignore once we changed this from E to heapVowE,
325
+ // typescript started complaining that heapVowE(hostTarget)
326
+ // is not callable. I'm not sure if this is a just a typing bug
327
+ // in heapVowE or also reflects a runtime deficiency. But this
328
+ // case it not used yet anyway. We disable it
329
+ // with at-ts-ignore rather than at-ts-expect-error because
330
+ // the dependency-graph tests complains that the latter is unused.
331
+ heapVowE(hostTarget)(...hostArgs);
332
+ resolver.resolve(hostPromise); // TODO does this always work?
333
+ } catch (hostProblem) {
334
+ throw Panic`internal: eventual send synchrously failed ${hostProblem}`;
335
+ }
336
+ try {
337
+ const entry = harden(['doReturn', callIndex, vow]);
338
+ log.pushEntry(entry);
339
+ const guestPromise = makeGuestForHostVow(vow, guestReturnedP);
340
+ // Note that `guestPromise` is not registered in the bijection since
341
+ // guestReturnedP is already the guest for vow. Rather, the handler
342
+ // returns guestPromise to resolve guestReturnedP to guestPromise.
343
+ doReturn(callIndex, vow);
344
+ return harden({
345
+ kind: 'return',
346
+ result: guestPromise,
347
+ });
348
+ } catch (problem) {
349
+ throw panic(problem);
350
+ }
351
+ };
352
+
236
353
  const guestHandler = harden({
354
+ applyMethodSendOnly(guestTarget, optVerb, guestArgs) {
355
+ const callIndex = log.getIndex();
356
+ if (stopped || !bijection.hasGuest(guestTarget)) {
357
+ Fail`Sent from a previous run: ${guestTarget}`;
358
+ }
359
+ try {
360
+ const guestEntry = harden([
361
+ 'checkSendOnly',
362
+ guestTarget,
363
+ optVerb,
364
+ guestArgs,
365
+ callIndex,
366
+ ]);
367
+ if (log.isReplaying()) {
368
+ const entry = log.nextEntry();
369
+ try {
370
+ equate(guestEntry, entry);
371
+ } catch (equateErr) {
372
+ // TODO consider Richard Gibson's suggestion for a better way
373
+ // to keep track of the error labeling.
374
+ throwLabeled(
375
+ equateErr,
376
+ `replay ${callIndex}:
377
+ ${q(guestEntry)}
378
+ vs ${q(entry)}
379
+ `,
380
+ );
381
+ }
382
+ } else {
383
+ const entry = guestToHost(guestEntry);
384
+ log.pushEntry(entry);
385
+ const [_op, hostTarget, _optVerb, hostArgs, _callIndex] = entry;
386
+ performSendOnly(hostTarget, optVerb, hostArgs);
387
+ }
388
+ } catch (fatalError) {
389
+ throw panic(fatalError);
390
+ }
391
+ },
237
392
  applyMethod(guestTarget, optVerb, guestArgs, guestReturnedP) {
238
- if (optVerb === undefined) {
239
- throw Panic`guest eventual call not yet supported: ${guestTarget}(${b(guestArgs)}) -> ${b(guestReturnedP)}`;
240
- } else {
241
- throw Panic`guest eventual send not yet supported: ${guestTarget}.${b(optVerb)}(${b(guestArgs)}) -> ${b(guestReturnedP)}`;
393
+ const callIndex = log.getIndex();
394
+ if (stopped || !bijection.hasGuest(guestTarget)) {
395
+ Fail`Sent from a previous run: ${guestTarget}`;
396
+ }
397
+ const hostResultKit = makeVowKit();
398
+ const g = bijection.unwrapInit(guestReturnedP, hostResultKit.vow);
399
+ g === guestReturnedP ||
400
+ Fail`internal: guestReturnedP should not unwrap: ${g} vs ${guestReturnedP}`;
401
+ /** @type {Outcome} */
402
+ let outcome;
403
+ try {
404
+ const guestEntry = harden([
405
+ 'checkSend',
406
+ guestTarget,
407
+ optVerb,
408
+ guestArgs,
409
+ callIndex,
410
+ ]);
411
+ if (log.isReplaying()) {
412
+ const entry = log.nextEntry();
413
+ try {
414
+ equate(guestEntry, entry);
415
+ } catch (equateErr) {
416
+ // TODO consider Richard Gibson's suggestion for a better way
417
+ // to keep track of the error labeling.
418
+ throwLabeled(
419
+ equateErr,
420
+ `replay ${callIndex}:
421
+ ${q(guestEntry)}
422
+ vs ${q(entry)}
423
+ `,
424
+ );
425
+ }
426
+ outcome = /** @type {Outcome} */ (nestInterpreter(callIndex));
427
+ } else {
428
+ const entry = guestToHost(guestEntry);
429
+ log.pushEntry(entry);
430
+ const [_op, hostTarget, _optVerb, hostArgs, _callIndex] = entry;
431
+ nestInterpreter(callIndex);
432
+ outcome = performSend(
433
+ hostTarget,
434
+ optVerb,
435
+ hostArgs,
436
+ callIndex,
437
+ hostResultKit,
438
+ guestReturnedP,
439
+ );
440
+ }
441
+ } catch (fatalError) {
442
+ throw panic(fatalError);
443
+ }
444
+
445
+ switch (outcome.kind) {
446
+ case 'return': {
447
+ return outcome.result;
448
+ }
449
+ case 'throw': {
450
+ throw outcome.problem;
451
+ }
452
+ default: {
453
+ // @ts-expect-error TS correctly knows this case would be outside
454
+ // the type. But that's what we want to check.
455
+ throw Panic`unexpected outcome kind ${q(outcome.kind)}`;
456
+ }
242
457
  }
243
458
  },
459
+ applyFunctionSendOnly(guestTarget, guestArgs) {
460
+ return guestHandler.applyMethodSendOnly(
461
+ guestTarget,
462
+ undefined,
463
+ guestArgs,
464
+ );
465
+ },
244
466
  applyFunction(guestTarget, guestArgs, guestReturnedP) {
245
467
  return guestHandler.applyMethod(
246
468
  guestTarget,
@@ -249,6 +471,9 @@ export const makeReplayMembrane = ({
249
471
  guestReturnedP,
250
472
  );
251
473
  },
474
+ getSendOnly(guestTarget, prop) {
475
+ throw Panic`guest eventual getSendOnly not yet supported: ${guestTarget}.${b(prop)}`;
476
+ },
252
477
  get(guestTarget, prop, guestReturnedP) {
253
478
  throw Panic`guest eventual get not yet supported: ${guestTarget}.${b(prop)} -> ${b(guestReturnedP)}`;
254
479
  },
@@ -340,13 +565,21 @@ export const makeReplayMembrane = ({
340
565
 
341
566
  /**
342
567
  * @param {Vow} hVow
343
- * @returns {unknown}
568
+ * @param {Promise} [promiseKey]
569
+ * If provided, use this promise as the key in the guestPromiseMap
570
+ * rather than the returned promise. This only happens when the
571
+ * promiseKey ends up forwarded to the returned promise anyway, so
572
+ * associating it with this resolve/reject pair is not incorrect.
573
+ * It is needed when `promiseKey` is also entered into the bijection
574
+ * paired with hVow.
575
+ * @returns {Promise}
344
576
  */
345
- const makeGuestForHostVow = hVow => {
577
+ const makeGuestForHostVow = (hVow, promiseKey = undefined) => {
346
578
  hVow = tolerateHostPromiseToVow(hVow);
347
579
  isVow(hVow) || Fail`vow expected ${hVow}`;
348
580
  const { promise, resolve, reject } = makeGuestPromiseKit();
349
- guestPromiseMap.set(promise, harden({ resolve, reject }));
581
+ promiseKey ??= promise;
582
+ guestPromiseMap.set(promiseKey, harden({ resolve, reject }));
350
583
 
351
584
  watchWake(hVow);
352
585
 
@@ -370,7 +603,7 @@ export const makeReplayMembrane = ({
370
603
  hVow,
371
604
  async hostFulfillment => {
372
605
  await log.promiseReplayDone(); // should never reject
373
- if (!stopped && guestPromiseMap.get(promise) !== 'settled') {
606
+ if (!stopped && guestPromiseMap.get(promiseKey) !== 'settled') {
374
607
  /** @type {LogEntry} */
375
608
  const entry = harden(['doFulfill', hVow, hostFulfillment]);
376
609
  log.pushEntry(entry);
@@ -385,7 +618,7 @@ export const makeReplayMembrane = ({
385
618
  },
386
619
  async hostReason => {
387
620
  await log.promiseReplayDone(); // should never reject
388
- if (!stopped && guestPromiseMap.get(promise) !== 'settled') {
621
+ if (!stopped && guestPromiseMap.get(promiseKey) !== 'settled') {
389
622
  /** @type {LogEntry} */
390
623
  const entry = harden(['doReject', hVow, hostReason]);
391
624
  log.pushEntry(entry);
@@ -1 +1 @@
1
- {"version":3,"file":"type-guards.d.ts","sourceRoot":"","sources":["type-guards.js"],"names":[],"mappings":"AAGA,8DAME;AAEF,gEAA6D;AAE7D,6DAwCE"}
1
+ {"version":3,"file":"type-guards.d.ts","sourceRoot":"","sources":["type-guards.js"],"names":[],"mappings":"AAGA,8DAME;AAEF,gEAA6D;AAE7D,6DAsDE"}
@@ -23,6 +23,13 @@ export const LogEntryShape = M.or(
23
23
  // M.number(),
24
24
  // ],
25
25
  // [
26
+ // 'doSendOnly',
27
+ // M.or(M.remotable('host wrapper of guest target'), VowShape),
28
+ // M.opt(PropertyKeyShape),
29
+ // M.arrayOf(M.any()),
30
+ // M.number(),
31
+ // ],
32
+ // [
26
33
  // 'doSend',
27
34
  // M.or(M.remotable('host wrapper of guest target'), VowShape),
28
35
  // M.opt(PropertyKeyShape),
@@ -42,13 +49,20 @@ export const LogEntryShape = M.or(
42
49
  M.arrayOf(M.any()),
43
50
  M.number(),
44
51
  ],
45
- // [
46
- // 'checkSend',
47
- // M.or(M.remotable('host target'), VowShape),
48
- // M.opt(PropertyKeyShape),
49
- // M.arrayOf(M.any()),
50
- // M.number(),
51
- // ],
52
+ [
53
+ 'checkSendOnly',
54
+ M.or(M.remotable('host target'), VowShape),
55
+ M.opt(PropertyKeyShape),
56
+ M.arrayOf(M.any()),
57
+ M.number(),
58
+ ],
59
+ [
60
+ 'checkSend',
61
+ M.or(M.remotable('host target'), VowShape),
62
+ M.opt(PropertyKeyShape),
63
+ M.arrayOf(M.any()),
64
+ M.number(),
65
+ ],
52
66
  // ['checkReturn', M.number(), M.any()],
53
67
  // ['checkThrow', M.number(), M.any()],
54
68
  );
package/src/types.d.ts CHANGED
@@ -49,7 +49,7 @@ export type Ephemera<S extends WeakKey = WeakKey, V extends unknown = any> = {
49
49
  */
50
50
  export type LogEntry = [// ///////////////// From Host to Guest /////////////////////////
51
51
  op: "doFulfill", vow: HostVow, fulfillment: Host] | [op: "doReject", vow: HostVow, reason: Host] | [op: "doReturn", callIndex: number, result: Host] | [op: "doThrow", callIndex: number, problem: Host] | [// ///////////////////// From Guest to Host /////////////////////////
52
- op: "checkCall", target: Host, optVerb: PropertyKey | undefined, args: Host[], callIndex: number];
52
+ op: "checkCall", target: Host, optVerb: PropertyKey | undefined, args: Host[], callIndex: number] | [op: "checkSendOnly", target: Host, optVerb: PropertyKey | undefined, args: Host[], callIndex: number] | [op: "checkSend", target: Host, optVerb: PropertyKey | undefined, args: Host[], callIndex: number];
53
53
  import type { Passable } from '@endo/pass-style';
54
54
  import type { Vow } from '@agoric/vow';
55
55
  import type { LogStore } from './log-store.js';
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.js"],"names":[],"mappings":"wBAYa,SAAS,GACrB,UAAsB,GACtB,WAAuB,GACvB,QAAoB,GACpB,MAAkB;;;;;;;;kBAWC,CAAC,0BACR,CAAC;iBAIW,CAAC,SAAb,QAAU,eACV,CAAC;;;;;oBAOW,CAAC,SAAb,QAAU,eACV,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;6BAIZ,CAAC,GAAG,cAAc,EAAE,KAAK,EAAE,KAAK,KAAK,cAAS;mCAI9C,CAAC,GAAG,cAAc,EAAE,IAAI,EAAE,KAAK,OAAO;;;;;;;wCA2HnD,GAhEG;;0BArDiB,QAAQ;2BACR,SAAS;;;;;;0BAKhB,QAAQ,GAAC,OAAO;sBAIhB;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAC,GAC7B;IAAC,IAAI,EAAE,OAAO,CAAC;IAAE,OAAO,EAAE,GAAG,CAAA;CAAC;qBAKnB,CAAC,SAAX,OAAQ,YACF,CAAC;SAEP,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;cACd,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI;;;;;;;uBAQlB,CAAE,iEAAiE;AAC/E,EAAQ,EAAE,WAAW,EACrB,GAAS,EAAE,OAAO,EAClB,WAAiB,EAAE,IAAI,CAClB,GAAG,CACR,EAAQ,EAAE,UAAU,EACpB,GAAS,EAAE,OAAO,EAClB,MAAY,EAAE,IAAI,CACb,GAAG,CACR,EAAQ,EAAE,UAAU,EACpB,SAAe,EAAE,MAAM,EACvB,MAAY,EAAE,IAAI,CACb,GAAG,CACR,EAAQ,EAAE,SAAS,EACnB,SAAe,EAAE,MAAM,EACvB,OAAa,EAAE,IAAI,CACd,GAAG,CAAE,qEAAqE;AAC/E,EAAQ,EAAE,WAAW,EACrB,MAAY,EAAE,IAAI,EAClB,OAAa,EAAE,WAAW,GAAC,SAAS,EACpC,IAAU,EAAE,IAAI,EAAE,EAClB,SAAe,EAAE,MAAM,CAClB;8BArGqB,kBAAkB;yBACb,aAAa;8BAClB,gBAAgB;+BACf,gBAAgB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.js"],"names":[],"mappings":"wBAYa,SAAS,GACrB,UAAsB,GACtB,WAAuB,GACvB,QAAoB,GACpB,MAAkB;;;;;;;;kBAWC,CAAC,0BACR,CAAC;iBAIW,CAAC,SAAb,QAAU,eACV,CAAC;;;;;oBAOW,CAAC,SAAb,QAAU,eACV,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;6BAIZ,CAAC,GAAG,cAAc,EAAE,KAAK,EAAE,KAAK,KAAK,cAAS;mCAI9C,CAAC,GAAG,cAAc,EAAE,IAAI,EAAE,KAAK,OAAO;;;;;;;wCAyDxC,GAAG;;0BAnDM,QAAQ;2BACR,SAAS;;;;;;0BAKhB,QAAQ,GAAC,OAAO;sBAIhB;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAC,GAC7B;IAAC,IAAI,EAAE,OAAO,CAAC;IAAE,OAAO,EAAE,GAAG,CAAA;CAAC;qBAKnB,CAAC,SAAX,OAAQ,YACF,CAAC;SAEP,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;cACd,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI;;;;;;;uBAQlB,CAAE,iEAAiE;AAC/E,EAAQ,EAAE,WAAW,EACrB,GAAS,EAAE,OAAO,EAClB,WAAiB,EAAE,IAAI,CAClB,GAAG,CACR,EAAQ,EAAE,UAAU,EACpB,GAAS,EAAE,OAAO,EAClB,MAAY,EAAE,IAAI,CACb,GAAG,CACR,EAAQ,EAAE,UAAU,EACpB,SAAe,EAAE,MAAM,EACvB,MAAY,EAAE,IAAI,CACb,GAAG,CACR,EAAQ,EAAE,SAAS,EACnB,SAAe,EAAE,MAAM,EACvB,OAAa,EAAE,IAAI,CACd,GAAG,CAAE,qEAAqE;AAC/E,EAAQ,EAAE,WAAW,EACrB,MAAY,EAAE,IAAI,EAClB,OAAa,EAAE,WAAW,GAAC,SAAS,EACpC,IAAU,EAAE,IAAI,EAAE,EAClB,SAAe,EAAE,MAAM,CAClB,GAAG,CACR,EAAQ,EAAE,eAAe,EACzB,MAAY,EAAE,IAAI,EAClB,OAAa,EAAE,WAAW,GAAC,SAAS,EACpC,IAAU,EAAE,IAAI,EAAE,EAClB,SAAe,EAAE,MAAM,CAClB,GAAG,CACR,EAAQ,EAAE,WAAW,EACrB,MAAY,EAAE,IAAI,EAClB,OAAa,EAAE,WAAW,GAAC,SAAS,EACpC,IAAU,EAAE,IAAI,EAAE,EAClB,SAAe,EAAE,MAAM,CAClB;8BAjHqB,kBAAkB;yBACb,aAAa;8BAClB,gBAAgB;+BACf,gBAAgB"}
package/src/types.js CHANGED
@@ -103,6 +103,18 @@ export {};
103
103
  * optVerb: PropertyKey|undefined,
104
104
  * args: Host[],
105
105
  * callIndex: number
106
+ * ] | [
107
+ * op: 'checkSendOnly',
108
+ * target: Host,
109
+ * optVerb: PropertyKey|undefined,
110
+ * args: Host[],
111
+ * callIndex: number
112
+ * ] | [
113
+ * op: 'checkSend',
114
+ * target: Host,
115
+ * optVerb: PropertyKey|undefined,
116
+ * args: Host[],
117
+ * callIndex: number
106
118
  * ]} LogEntry
107
119
  */
108
120
 
@@ -127,6 +139,12 @@ export {};
127
139
  * args: Host[],
128
140
  * callIndex: number
129
141
  * ] | [
142
+ * op: 'doSendOnly',
143
+ * target: Host,
144
+ * optVerb: PropertyKey|undefined,
145
+ * args: Host[],
146
+ * callIndex: number
147
+ * ] | [
130
148
  * op: 'doSend',
131
149
  * target: Host,
132
150
  * optVerb: PropertyKey|undefined,
@@ -155,6 +173,12 @@ export {};
155
173
  * args: Host[],
156
174
  * callIndex: number
157
175
  * ] | [
176
+ * op: 'checkSendOnly',
177
+ * target: Host,
178
+ * optVerb: PropertyKey|undefined,
179
+ * args: Host[],
180
+ * callIndex: number
181
+ * ] | [
158
182
  * op: 'checkSend',
159
183
  * target: Host,
160
184
  * optVerb: PropertyKey|undefined,
@@ -141,7 +141,7 @@ const testBadHostReplay1 = async (t, zone) => {
141
141
  },
142
142
  {
143
143
  message:
144
- 'converting badMethod result: Remotables must be explicitly declared: "[Function nonPassableFunc]"',
144
+ 'Remotables must be explicitly declared: "[Function nonPassableFunc]"',
145
145
  },
146
146
  );
147
147
  t.log(' badHost replay1 guest error caused by host error', gErr);
@@ -177,7 +177,7 @@ const testBadHostReplay1 = async (t, zone) => {
177
177
  'doThrow',
178
178
  2,
179
179
  Error(
180
- 'converting badMethod result: Remotables must be explicitly declared: "[Function nonPassableFunc]"',
180
+ 'Remotables must be explicitly declared: "[Function nonPassableFunc]"',
181
181
  ),
182
182
  ],
183
183
  ['checkCall', badHost, 'badMethod', [], 4],
@@ -7,6 +7,7 @@ import {
7
7
  } from './prepare-test-env-ava.js';
8
8
 
9
9
  import { Fail } from '@endo/errors';
10
+ import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
10
11
  import { prepareVowTools } from '@agoric/vow';
11
12
  import { E } from '@endo/eventual-send';
12
13
  // import E from '@agoric/vow/src/E.js';
@@ -46,9 +47,12 @@ const preparePingee = zone =>
46
47
  */
47
48
  const testFirstPlay = async (t, zone) => {
48
49
  const vowTools = prepareVowTools(zone);
50
+ const { makeVowKit } = vowTools;
49
51
  const makeLogStore = prepareLogStore(zone);
50
52
  const makeBijection = prepareBijection(zone);
51
53
  const makePingee = preparePingee(zone);
54
+ const { vow: v1, resolver: r1 } = zone.makeOnce('v1', () => makeVowKit());
55
+ const { vow: _v2, resolver: _r2 } = zone.makeOnce('v2', () => makeVowKit());
52
56
 
53
57
  const log = zone.makeOnce('log', () => makeLogStore());
54
58
  const bijection = zone.makeOnce('bij', makeBijection);
@@ -61,6 +65,7 @@ const testFirstPlay = async (t, zone) => {
61
65
  panic,
62
66
  });
63
67
 
68
+ const p1 = mem.hostToGuest(v1);
64
69
  t.deepEqual(log.dump(), []);
65
70
 
66
71
  /** @type {Pingee} */
@@ -69,18 +74,122 @@ const testFirstPlay = async (t, zone) => {
69
74
  const guestPingee = mem.hostToGuest(pingee);
70
75
  t.deepEqual(log.dump(), []);
71
76
 
72
- const pingTestSendResult = t.throwsAsync(() => E(guestPingee).ping('send'), {
73
- message:
74
- 'panic over "[Error: guest eventual send not yet supported: \\"[Alleged: Pingee guest wrapper]\\".ping([\\"send\\"]) -> \\"[Promise]\\"]"',
77
+ const p = E(guestPingee).ping('send');
78
+ const pOnly = E.sendOnly(guestPingee).ping('sendOnly');
79
+ t.is(pOnly, undefined);
80
+
81
+ guestPingee.ping('call');
82
+
83
+ t.is(await p, undefined);
84
+ const dump = log.dump();
85
+ const v3 = dump[3][2];
86
+ t.deepEqual(dump, [
87
+ ['checkCall', pingee, 'ping', ['call'], 0],
88
+ ['doReturn', 0, undefined],
89
+ ['checkSend', pingee, 'ping', ['send'], 2],
90
+ ['doReturn', 2, v3],
91
+ ['checkSendOnly', pingee, 'ping', ['sendOnly'], 4],
92
+ ['doFulfill', v3, undefined],
93
+ ]);
94
+
95
+ r1.resolve('x');
96
+ t.is(await p1, 'x');
97
+
98
+ t.deepEqual(log.dump(), [
99
+ ['checkCall', pingee, 'ping', ['call'], 0],
100
+ ['doReturn', 0, undefined],
101
+ ['checkSend', pingee, 'ping', ['send'], 2],
102
+ ['doReturn', 2, v3],
103
+ ['checkSendOnly', pingee, 'ping', ['sendOnly'], 4],
104
+ ['doFulfill', v3, undefined],
105
+ ['doFulfill', v1, 'x'],
106
+ ]);
107
+ };
108
+
109
+ /**
110
+ * @param {any} t
111
+ * @param {Zone} zone
112
+ */
113
+ const testReplay = async (t, zone) => {
114
+ const vowTools = prepareVowTools(zone);
115
+ prepareLogStore(zone);
116
+ prepareBijection(zone);
117
+ preparePingee(zone);
118
+ const { vow: v1 } = zone.makeOnce('v1', () => Fail`need v1`);
119
+ const { vow: v2, resolver: r2 } = zone.makeOnce('v2', () => Fail`need v2`);
120
+
121
+ const log = /** @type {LogStore} */ (
122
+ zone.makeOnce('log', () => Fail`need log`)
123
+ );
124
+ const bijection = /** @type {Bijection} */ (
125
+ zone.makeOnce('bij', () => Fail`need bij`)
126
+ );
127
+
128
+ const pingee = zone.makeOnce('pingee', () => Fail`need pingee`);
129
+
130
+ const dump = log.dump();
131
+ const v3 = dump[3][2];
132
+ t.deepEqual(dump, [
133
+ ['checkCall', pingee, 'ping', ['call'], 0],
134
+ ['doReturn', 0, undefined],
135
+ ['checkSend', pingee, 'ping', ['send'], 2],
136
+ ['doReturn', 2, v3],
137
+ ['checkSendOnly', pingee, 'ping', ['sendOnly'], 4],
138
+ ['doFulfill', v3, undefined],
139
+ ['doFulfill', v1, 'x'],
140
+ ]);
141
+
142
+ const mem = makeReplayMembrane({
143
+ log,
144
+ bijection,
145
+ vowTools,
146
+ watchWake,
147
+ panic,
75
148
  });
149
+ t.true(log.isReplaying());
150
+ t.is(log.getIndex(), 0);
151
+
152
+ const guestPingee = mem.hostToGuest(pingee);
153
+ const p2 = mem.hostToGuest(v2);
154
+ // @ts-expect-error TS doesn't know that r2 is a resolver
155
+ r2.resolve('y');
156
+ await eventLoopIteration();
157
+
158
+ const p1 = mem.hostToGuest(v1);
159
+ mem.wake();
160
+ t.true(log.isReplaying());
161
+ t.is(log.getIndex(), 0);
162
+ t.deepEqual(log.dump(), [
163
+ ['checkCall', pingee, 'ping', ['call'], 0],
164
+ ['doReturn', 0, undefined],
165
+ ['checkSend', pingee, 'ping', ['send'], 2],
166
+ ['doReturn', 2, v3],
167
+ ['checkSendOnly', pingee, 'ping', ['sendOnly'], 4],
168
+ ['doFulfill', v3, undefined],
169
+ ['doFulfill', v1, 'x'],
170
+ ]);
171
+
172
+ E(guestPingee).ping('send');
173
+ // TODO Once https://github.com/endojs/endo/issues/2336 is fixed,
174
+ // the following `void` should not be needed. But strangely, TS isn't
175
+ // telling me a `void` is needed above, which is also incorrect.
176
+ void E.sendOnly(guestPingee).ping('sendOnly');
76
177
 
77
178
  guestPingee.ping('call');
78
179
 
79
- await pingTestSendResult;
180
+ t.is(await p1, 'x');
181
+ t.is(await p2, 'y');
182
+ t.false(log.isReplaying());
80
183
 
81
184
  t.deepEqual(log.dump(), [
82
185
  ['checkCall', pingee, 'ping', ['call'], 0],
83
186
  ['doReturn', 0, undefined],
187
+ ['checkSend', pingee, 'ping', ['send'], 2],
188
+ ['doReturn', 2, v3],
189
+ ['checkSendOnly', pingee, 'ping', ['sendOnly'], 4],
190
+ ['doFulfill', v3, undefined],
191
+ ['doFulfill', v1, 'x'],
192
+ ['doFulfill', v2, 'y'],
84
193
  ]);
85
194
  };
86
195
 
@@ -100,5 +209,9 @@ test.serial('test durable replay-membrane settlement', async t => {
100
209
 
101
210
  nextLife();
102
211
  const zone1 = makeDurableZone(getBaggage(), 'durableRoot');
103
- return testFirstPlay(t, zone1);
212
+ await testFirstPlay(t, zone1);
213
+
214
+ nextLife();
215
+ const zone3 = makeDurableZone(getBaggage(), 'durableRoot');
216
+ return testReplay(t, zone3);
104
217
  });