@agoric/swingset-liveslots 0.10.3-u19.2 → 0.10.3-u21.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 (41) hide show
  1. package/package.json +21 -21
  2. package/src/capdata.d.ts +7 -3
  3. package/src/capdata.d.ts.map +1 -1
  4. package/src/capdata.js +16 -8
  5. package/src/collectionManager.d.ts +1 -0
  6. package/src/collectionManager.d.ts.map +1 -1
  7. package/src/collectionManager.js +1 -0
  8. package/src/liveslots.js +2 -2
  9. package/src/message.d.ts +10 -6
  10. package/src/message.d.ts.map +1 -1
  11. package/src/message.js +7 -3
  12. package/src/types.d.ts +8 -3
  13. package/src/types.d.ts.map +1 -1
  14. package/src/types.js +6 -5
  15. package/src/virtualObjectManager.d.ts.map +1 -1
  16. package/src/virtualObjectManager.js +70 -14
  17. package/src/watchedPromises.d.ts.map +1 -1
  18. package/src/watchedPromises.js +10 -13
  19. package/test/gc-helpers.js +2 -2
  20. package/test/handled-promises.test.js +529 -163
  21. package/test/initial-vrefs.test.js +12 -18
  22. package/test/liveslots-helpers.d.ts +1 -0
  23. package/test/liveslots-helpers.d.ts.map +1 -1
  24. package/test/liveslots-helpers.js +1 -0
  25. package/test/liveslots-real-gc.test.js +2 -2
  26. package/test/liveslots.test.js +3 -3
  27. package/test/storeGC/lifecycle.test.js +13 -12
  28. package/test/util.d.ts +1 -1
  29. package/test/util.d.ts.map +1 -1
  30. package/test/util.js +2 -2
  31. package/test/virtual-objects/state-shape.test.js +312 -221
  32. package/test/virtual-objects/virtualObjectGC.test.js +37 -36
  33. package/test/virtual-objects/virtualObjectManager.test.js +41 -63
  34. package/test/vo-test-harness.test.js +13 -9
  35. package/tools/fakeVirtualSupport.d.ts.map +1 -1
  36. package/tools/setup-vat-data.d.ts.map +1 -1
  37. package/tools/setup-vat-data.js +0 -1
  38. package/tools/vo-test-harness.d.ts +31 -0
  39. package/tools/vo-test-harness.d.ts.map +1 -1
  40. package/tools/vo-test-harness.js +21 -0
  41. package/test/watch-promise.test.js +0 -42
@@ -1,8 +1,9 @@
1
1
  // @ts-nocheck
2
2
  import test from 'ava';
3
3
 
4
+ import { Fail } from '@endo/errors';
4
5
  import { Far } from '@endo/marshal';
5
- import { kser, kslot } from '@agoric/kmarshal';
6
+ import { kser, kslot, kunser } from '@agoric/kmarshal';
6
7
  import { M } from '@agoric/store';
7
8
  import { makeLiveSlots } from '../../src/liveslots.js';
8
9
  import { buildSyscall } from '../liveslots-helpers.js';
@@ -10,160 +11,176 @@ import { makeStartVat, makeMessage } from '../util.js';
10
11
  import { makeMockGC } from '../mock-gc.js';
11
12
  import { makeFakeVirtualStuff } from '../../tools/fakeVirtualSupport.js';
12
13
 
13
- function makeGenericRemotable(typeName) {
14
- return Far(typeName, {
15
- aMethod() {
16
- return 'whatever';
17
- },
18
- });
19
- }
20
- const eph1 = makeGenericRemotable('ephemeral1');
21
- const eph2 = makeGenericRemotable('ephemeral2');
14
+ /** @import {VatOneResolution} from '../../src/types.js'; */
15
+ /** @import {VatData} from '../../src/vatDataTypes.js'; */
16
+
17
+ let lastPnum = 100;
18
+ /**
19
+ * Send a "message" syscall into a liveslots instance and return the
20
+ * unserialized result, or an error if the result is unsettled or rejected.
21
+ *
22
+ * @param {ReturnType<typeof makeLiveSlots>} ls
23
+ * @param {ReturnType<typeof buildSyscall>['log']} syscallLog
24
+ * @param {[target: unknown, method: string | symbol, args?: unknown[]]} message
25
+ */
26
+ const dispatchForResult = async (ls, syscallLog, message) => {
27
+ const oldSyscallCount = syscallLog.length;
28
+ lastPnum += 1;
29
+ const resultVpid = `p-${lastPnum}`;
30
+ const vdo = makeMessage(...message.concat(undefined).slice(0, 3), resultVpid);
31
+ await ls.dispatch(vdo);
32
+ const newSyscalls = syscallLog.slice(oldSyscallCount);
33
+ /** @type {VatOneResolution[]} */
34
+ const newResolutions = newSyscalls.flatMap(vso =>
35
+ vso.type === 'resolve' ? vso.resolutions : [],
36
+ );
37
+ const [_vpid, isRejection, capdata] =
38
+ newResolutions.find(resolution => resolution[0] === resultVpid) ||
39
+ Fail`unsettled by syscalls ${syscallLog.slice(oldSyscallCount)}`;
40
+ if (isRejection) throw Error('rejected', { cause: kunser(capdata) });
41
+ return kunser(capdata);
42
+ };
22
43
 
23
- const init = value => ({ value });
24
- const behavior = {
25
- set: ({ state }, value) => (state.value = value),
44
+ const eph1 = Far('ephemeral1');
45
+ const eph2 = Far('ephemeral2');
46
+
47
+ const initHolder = fields => ({ ...fields });
48
+ const holderMethods = {
49
+ get: ({ state }, ...fields) =>
50
+ // We require fields to be explicit because they are currently defined on
51
+ // the state *prototype*.
52
+ Object.fromEntries(
53
+ fields.flatMap(key => (key in state ? [[key, state[key]]] : [])),
54
+ ),
55
+ set: ({ state }, fields) => {
56
+ Object.assign(state, fields);
57
+ },
26
58
  };
59
+ /**
60
+ * Define a virtual or durable kind for getting and setting state constrained
61
+ * by the provided shape.
62
+ *
63
+ * @template {VatData.defineKind | VatData.defineDurableKind} D
64
+ * @param {D} defineKind
65
+ * @param {Parameters<D>[0]} kindIdentifier
66
+ * @param {import('@endo/patterns').Pattern} stateShape
67
+ */
68
+ const defineHolder = (defineKind, kindIdentifier, stateShape) =>
69
+ defineKind(kindIdentifier, initHolder, holderMethods, { stateShape });
27
70
 
28
71
  // virtual/durable Kinds can specify a 'stateShape', which should be
29
- // enforced, both during initialization and subsequent state changes
30
-
31
- test('constrain state shape', t => {
72
+ // enforced at both initialization and subsequent state changes
73
+ const testStateShape = test.macro((t, valueShape, config) => {
74
+ const { goodValues, badValues, throwsExpectation } = config;
32
75
  const { vom } = makeFakeVirtualStuff();
33
76
  const { defineKind } = vom;
34
- const any = { value: M.any() };
35
- const number = { value: M.number() };
36
- const string = { value: M.string() };
37
- const remotable = { value: M.remotable() };
38
- const eph = { value: eph1 };
39
-
40
- // M.any() allows anything
41
- const makeA = defineKind('kindA', init, behavior, { stateShape: any });
42
- makeA(eph1);
43
- makeA(1);
44
- makeA('string');
45
- const a = makeA(1);
46
- a.set(eph1);
47
- a.set(2);
48
- a.set('other string');
49
-
50
- // M.number() requires a number
51
- const numberFail = { message: /Must be a number/ };
52
- const makeB = defineKind('kindB', init, behavior, { stateShape: number });
53
- t.throws(() => makeB(eph1), numberFail);
54
- const b = makeB(1);
55
- t.throws(() => makeB('string'), numberFail);
56
- t.throws(() => b.set(eph1), numberFail);
57
- t.throws(() => b.set('string'), numberFail);
58
-
59
- // M.string() requires a string
60
- const stringFail = { message: /Must be a string/ };
61
- const makeC = defineKind('kindC', init, behavior, { stateShape: string });
62
- t.throws(() => makeC(eph1), stringFail);
63
- const c = makeC('string');
64
- t.throws(() => makeC(1), stringFail);
65
- t.throws(() => c.set(eph1), stringFail);
66
- t.throws(() => c.set(2), stringFail);
67
-
68
- // M.remotable() requires any Remotable
69
- const remotableFail = { message: /Must be a remotable/ };
70
- const makeD = defineKind('kindD', init, behavior, { stateShape: remotable });
71
- const d = makeD(eph1);
72
- makeD(eph2);
73
- t.throws(() => makeD(1), remotableFail);
74
- t.throws(() => makeD('string'), remotableFail);
75
- d.set(eph2);
76
- t.throws(() => d.set(2), remotableFail);
77
- t.throws(() => d.set('string'), remotableFail);
78
-
79
- // using a specific Remotable object requires that exact object
80
- const eph1Fail = { message: /Must be:.*Alleged: ephemeral1/ };
81
- const makeE = defineKind('kindE', init, behavior, { stateShape: eph });
82
- const e = makeE(eph1);
83
- t.throws(() => makeE(eph2), eph1Fail);
84
- t.throws(() => makeE(1), eph1Fail);
85
- t.throws(() => makeE('string'), eph1Fail);
86
- e.set(eph1);
87
- t.throws(() => e.set(eph2), eph1Fail);
88
- t.throws(() => e.set(2), eph1Fail);
89
- t.throws(() => e.set('string'), eph1Fail);
77
+ const makeHolder = defineHolder(defineKind, 'kindTag', { value: valueShape });
78
+ const instance = goodValues.map(value => makeHolder({ value })).at(-1);
79
+ for (const value of goodValues) {
80
+ instance.set({ value });
81
+ }
82
+ for (const value of badValues || []) {
83
+ t.throws(() => makeHolder({ value }), throwsExpectation);
84
+ t.throws(() => instance.set({ value }), throwsExpectation);
85
+ }
86
+ t.pass();
87
+ });
88
+ test('constrain state shape - M.any()', testStateShape, M.any(), {
89
+ goodValues: [eph1, eph2, 1, 2, 'string'],
90
+ });
91
+ test('constrain state shape - M.number()', testStateShape, M.number(), {
92
+ goodValues: [1],
93
+ badValues: [eph1, 'string'],
94
+ throwsExpectation: { message: /Must be a number/ },
95
+ });
96
+ test('constrain state shape - M.string()', testStateShape, M.string(), {
97
+ goodValues: ['string'],
98
+ badValues: [eph1, 1, 2],
99
+ throwsExpectation: { message: /Must be a string/ },
100
+ });
101
+ test('constrain state shape - M.remotable()', testStateShape, M.remotable(), {
102
+ goodValues: [eph1, eph2],
103
+ badValues: ['string', 1, 2],
104
+ throwsExpectation: { message: /Must be a remotable/ },
105
+ });
106
+ test('constrain state shape - specific remotable', testStateShape, eph1, {
107
+ goodValues: [eph1],
108
+ badValues: ['string', 1, 2],
109
+ throwsExpectation: { message: /Must be:.*Alleged: ephemeral1/ },
90
110
  });
91
111
 
92
112
  // durable Kinds serialize and store their stateShape, which must
93
113
  // itself be durable
94
-
95
114
  test('durable state shape', t => {
96
115
  // note: relaxDurabilityRules defaults to true in fake tools
97
116
  const { vom } = makeFakeVirtualStuff({ relaxDurabilityRules: false });
98
117
  const { makeKindHandle, defineDurableKind } = vom;
99
118
 
100
- const make = (which, stateShape) => {
101
- const kh = makeKindHandle(`kind${which}`);
102
- return defineDurableKind(kh, init, behavior, { stateShape });
119
+ let kindNumber = 0;
120
+ const defineNextKind = valueShape => {
121
+ kindNumber += 1;
122
+ const kh = makeKindHandle(`kind${kindNumber}`);
123
+ return defineHolder(defineDurableKind, kh, { value: valueShape });
103
124
  };
104
125
 
105
- const makeKind1 = make(1);
106
- makeKind1();
126
+ const makeKind1 = defineNextKind();
127
+ makeKind1({ value: undefined });
107
128
 
108
- const makeKind2 = make(2);
129
+ const makeKind2 = defineNextKind();
109
130
  makeKind2();
110
131
 
111
- const makeKind3 = make(3, { value: M.any() });
132
+ const makeKind3 = defineNextKind(M.any());
112
133
  const obj3 = makeKind3();
113
134
 
114
- const makeKind4 = make(4, { value: M.string() });
115
- const obj4 = makeKind4('string');
135
+ const makeKind4 = defineNextKind(M.string());
136
+ const obj4 = makeKind4({ value: 'string' });
116
137
 
117
- const makeKind5 = make(5, { value: M.remotable() });
138
+ const makeKind5 = defineNextKind(M.remotable());
118
139
  const durableValueFail = { message: /value for "value" is not durable/ };
119
- t.throws(() => makeKind5(eph1), durableValueFail);
140
+ t.throws(() => makeKind5({ value: eph1 }), durableValueFail);
120
141
 
121
142
  const durableShapeFail = { message: /stateShape.*is not durable: slot 0 of/ };
122
- t.throws(() => make(6, { value: eph1 }), durableShapeFail);
143
+ t.throws(() => defineNextKind(eph1), durableShapeFail);
123
144
 
124
- const makeKind7 = make(7, { value: obj4 }); // obj4 is durable
125
- makeKind7(obj4);
145
+ const makeKind7 = defineNextKind(obj4); // obj4 is durable
146
+ makeKind7({ value: obj4 });
126
147
  const specificRemotableFail = { message: /kind3.*Must be:.*kind4/ };
127
- t.throws(() => makeKind7(obj3), specificRemotableFail);
148
+ t.throws(() => makeKind7({ value: obj3 }), specificRemotableFail);
128
149
  });
129
150
 
130
151
  // durable Kinds maintain refcounts on their serialized stateShape
131
-
132
152
  test('durable stateShape refcounts', async t => {
133
153
  const kvStore = new Map();
134
154
  const { syscall: sc1 } = buildSyscall({ kvStore });
135
155
  const gcTools = makeMockGC();
136
156
 
137
- function build1(vatPowers, _vp, baggage) {
138
- const { VatData } = vatPowers;
139
- const { makeKindHandle, defineDurableKind } = VatData;
140
-
141
- return Far('root', {
142
- accept: _standard1 => 0, // assign it a vref
143
- create: standard1 => {
144
- const kh = makeKindHandle('shaped');
145
- baggage.init('kh', kh);
146
- const stateShape = { value: standard1 };
147
- defineDurableKind(kh, init, behavior, { stateShape });
148
- },
149
- });
150
- }
151
-
152
- const makeNS1 = () => ({ buildRootObject: build1 });
153
- const ls1 = makeLiveSlots(sc1, 'vatA', {}, {}, gcTools, undefined, makeNS1);
154
- const startVat1 = makeStartVat(kser());
155
- await ls1.dispatch(startVat1);
156
- const rootA = 'o+0';
157
-
158
- const standard1Vref = 'o-1';
159
- await ls1.dispatch(makeMessage(rootA, 'accept', []));
160
- t.falsy(ls1.testHooks.getReachableRefCount(standard1Vref));
157
+ const ls1 = makeLiveSlots(sc1, 'vatA', {}, {}, gcTools, undefined, () => {
158
+ const buildRootObject = (vatPowers, _vatParameters, baggage) => {
159
+ const { VatData } = vatPowers;
160
+ const { makeKindHandle, defineDurableKind } = VatData;
161
+
162
+ return Far('root', {
163
+ acceptRef: _ref => {}, // assigns a vref
164
+ defineDurableKind: valueShape => {
165
+ const kh = makeKindHandle('shaped');
166
+ baggage.init('kh', kh);
167
+ defineHolder(defineDurableKind, kh, { value: valueShape });
168
+ },
169
+ });
170
+ };
171
+ return { buildRootObject };
172
+ });
173
+ await ls1.dispatch(makeStartVat());
174
+ const root = 'o+0';
161
175
 
162
- await ls1.dispatch(makeMessage(rootA, 'create', [kslot(standard1Vref)]));
176
+ // accepting a ref but doing nothing with it does not increment its refcount
177
+ const vref1 = 'o-1';
178
+ await ls1.dispatch(makeMessage(root, 'acceptRef', [kslot(vref1)]));
179
+ t.is(ls1.testHooks.getReachableRefCount(vref1), 0);
163
180
 
164
- // using our 'standard1' object in stateShape causes its refcount to
165
- // be incremented
166
- t.is(ls1.testHooks.getReachableRefCount(standard1Vref), 1);
181
+ // ...but using it in stateShape does
182
+ await ls1.dispatch(makeMessage(root, 'defineDurableKind', [kslot(vref1)]));
183
+ t.is(ls1.testHooks.getReachableRefCount(vref1), 1);
167
184
 
168
185
  // ------
169
186
 
@@ -171,128 +188,202 @@ test('durable stateShape refcounts', async t => {
171
188
  const clonedStore = new Map(kvStore);
172
189
  const { syscall: sc2 } = buildSyscall({ kvStore: clonedStore });
173
190
 
174
- function build2(vatPowers, vatParameters, baggage) {
175
- const { VatData } = vatPowers;
176
- const { defineDurableKind } = VatData;
177
- const { standard2 } = vatParameters;
178
- const kh = baggage.get('kh');
179
- const stateShape = { value: standard2 };
180
- defineDurableKind(kh, init, behavior, { stateShape });
181
-
182
- return Far('root', {});
183
- }
184
-
185
191
  // to test refcount increment/decrement, we need to override the
186
192
  // usual rule that the new version must exactly match the original
187
193
  // stateShape
188
- const options = { allowStateShapeChanges: true };
189
- const makeNS2 = () => ({ buildRootObject: build2 });
190
- const ls2 = makeLiveSlots(
191
- sc2,
192
- 'vatA',
193
- {},
194
- options,
195
- gcTools,
196
- undefined,
197
- makeNS2,
198
- );
199
-
200
- const standard2Vref = 'o-2';
201
- const vp = { standard2: kslot(standard2Vref) };
202
- const startVat2 = makeStartVat(kser(vp));
203
- await ls2.dispatch(startVat2);
204
-
205
- // redefining the durable kind, with a different 'standard' object,
206
- // will decrement the standard1 refcount, and increment that of
207
- // standard2
194
+ const opts = { allowStateShapeChanges: true };
195
+ const ls2 = makeLiveSlots(sc2, 'vatA', {}, opts, gcTools, undefined, () => {
196
+ const buildRootObject = (vatPowers, vatParameters, baggage) => {
197
+ const { VatData } = vatPowers;
198
+ const { defineDurableKind } = VatData;
199
+ const { valueShape } = vatParameters;
200
+ const kh = baggage.get('kh');
201
+ defineHolder(defineDurableKind, kh, { value: valueShape });
202
+
203
+ return Far('root', {});
204
+ };
205
+ return { buildRootObject };
206
+ });
208
207
 
209
- t.falsy(ls2.testHooks.getReachableRefCount(standard1Vref));
210
- t.is(ls2.testHooks.getReachableRefCount(standard2Vref), 1);
208
+ // redefining the durable kind's stateShape to replace its vref1 reference
209
+ // with a vref2 reference will decrement the former refcount and increment the
210
+ // latter
211
+ const vref2 = 'o-2';
212
+ const vatParameters = { valueShape: kslot(vref2) };
213
+ await ls2.dispatch(makeStartVat(kser(vatParameters)));
214
+ t.is(ls2.testHooks.getReachableRefCount(vref1), 0);
215
+ t.is(ls2.testHooks.getReachableRefCount(vref2), 1);
211
216
  });
212
217
 
213
- test('durable stateShape must match', async t => {
214
- const kvStore = new Map();
215
- const { syscall: sc1 } = buildSyscall({ kvStore });
218
+ test('durable stateShape must match or extend', async t => {
219
+ const store1 = new Map();
220
+ const { syscall: sc1, log: log1 } = buildSyscall({ kvStore: store1 });
216
221
  const gcTools = makeMockGC();
217
222
 
218
- function build1(vatPowers, _vp, baggage) {
219
- const { VatData } = vatPowers;
220
- const { makeKindHandle, defineDurableKind } = VatData;
221
-
222
- return Far('root', {
223
- create: (obj1, obj2) => {
224
- const kh = makeKindHandle('shaped');
225
- baggage.init('kh', kh);
226
- const stateShape = { x: obj1, y: obj2 };
227
- defineDurableKind(kh, init, behavior, { stateShape });
228
- },
229
- });
230
- }
231
-
232
- const makeNS1 = () => ({ buildRootObject: build1 });
233
- const ls1 = makeLiveSlots(sc1, 'vatA', {}, {}, gcTools, undefined, makeNS1);
234
- const startVat1 = makeStartVat(kser());
235
- await ls1.dispatch(startVat1);
236
- const rootA = 'o+0';
223
+ const fields = ['x', 'x2', 'y']; // but "x2" is not initially present
224
+ const ls1 = makeLiveSlots(sc1, 'vatA', {}, {}, gcTools, undefined, () => {
225
+ const buildRootObject = (vatPowers, _vatParameters, baggage) => {
226
+ const { VatData } = vatPowers;
227
+ const { makeKindHandle, defineDurableKind } = VatData;
228
+
229
+ return Far('root', {
230
+ makeShapedDurable: (x, y) => {
231
+ const kh = makeKindHandle('shaped');
232
+ baggage.init('kh', kh);
233
+ const makeInstance = defineHolder(defineDurableKind, kh, { x, y });
234
+ return makeInstance(harden({ x, y }));
235
+ },
236
+ });
237
+ };
238
+ return { buildRootObject };
239
+ });
240
+ await ls1.dispatch(makeStartVat());
241
+ const root = 'o+0';
237
242
 
238
243
  const vref1 = 'o-1';
239
244
  const vref2 = 'o-2';
240
- await ls1.dispatch(
241
- makeMessage(rootA, 'create', [kslot(vref1), kslot(vref2)]),
245
+ const passables = new Map([
246
+ [vref1, kslot(vref1, vref1)],
247
+ [vref2, kslot(vref2, vref2)],
248
+ ]);
249
+ const instance1 = await dispatchForResult(ls1, log1, [
250
+ root,
251
+ 'makeShapedDurable',
252
+ [passables.get(vref1), passables.get(vref2)],
253
+ ]);
254
+ const objRef = instance1.getKref();
255
+ const state1 = await dispatchForResult(ls1, log1, [objRef, 'get', fields]);
256
+ t.deepEqual(
257
+ kser(state1),
258
+ kser({ x: passables.get(vref1), y: passables.get(vref2) }),
242
259
  );
243
260
 
244
- // the first version's state is { x: vref1, y: vref2 }
245
-
246
261
  // ------
247
262
 
248
263
  // Simulate upgrade by starting from the non-empty kvStore.
249
- const clonedStore = new Map(kvStore);
250
- const { syscall: sc2 } = buildSyscall({ kvStore: clonedStore });
264
+ const store2 = new Map(store1);
265
+ const { syscall: sc2, log: log2 } = buildSyscall({ kvStore: store2 });
251
266
 
252
- function build2(vatPowers, vatParameters, baggage) {
253
- const { VatData } = vatPowers;
254
- const { defineDurableKind } = VatData;
255
- const { obj1, obj2 } = vatParameters;
256
- const kh = baggage.get('kh');
257
- // several shapes that are not compatible
258
- const shape1 = { x: obj1, y: M.any() };
259
- const shape2 = { x: obj1 };
260
- const shape3 = { x: obj1, y: obj2, z: M.string() };
261
- const shape4 = { x: M.or(obj1, M.string()), y: obj2 };
262
- const shape5 = { x: obj2, y: obj1 }; // wrong slots
263
- const trial = shape => {
264
- t.throws(
265
- () => defineDurableKind(kh, init, behavior, { stateShape: shape }),
266
- { message: /durable Kind stateShape mismatch/ },
267
- );
267
+ // we do *not* override allowStateShapeChanges
268
+ // const opts = { allowStateShapeChanges: true };
269
+ const opts = undefined;
270
+ const stateShapeMismatch = { message: /durable Kind stateShape mismatch/ };
271
+ const ls2 = makeLiveSlots(sc2, 'vatA', {}, opts, gcTools, undefined, () => {
272
+ const buildRootObject = (vatPowers, vatParameters, baggage) => {
273
+ const { VatData } = vatPowers;
274
+ const { x, y } = vatParameters;
275
+ const kh = baggage.get('kh');
276
+ const redefineDurableKind = stateShape =>
277
+ defineHolder(VatData.defineDurableKind, kh, stateShape);
278
+ // several shapes that are not compatible
279
+ const badShapes = [
280
+ { x, y: M.any() },
281
+ { x },
282
+ { x, y, z: M.string() },
283
+ { x: M.or(x, M.string()), y },
284
+ { x: y, y: x }, // wrong slots
285
+ ];
286
+ for (const stateShape of badShapes) {
287
+ t.throws(() => redefineDurableKind(stateShape), stateShapeMismatch);
288
+ }
289
+ // the correct shape
290
+ redefineDurableKind({ x, y });
291
+
292
+ return Far('root', {});
268
293
  };
269
- trial(shape1);
270
- trial(shape2);
271
- trial(shape3);
272
- trial(shape4);
273
- trial(shape5);
274
- const stateShape = { x: obj1, y: obj2 }; // the correct shape
275
- defineDurableKind(kh, init, behavior, { stateShape });
276
- t.pass();
277
-
278
- return Far('root', {});
279
- }
294
+ return { buildRootObject };
295
+ });
280
296
 
281
- // we do *not* override allowStateShapeChanges
282
- // const options = { allowStateShapeChanges: true };
283
- const options = undefined;
284
- const makeNS2 = () => ({ buildRootObject: build2 });
285
- const ls2 = makeLiveSlots(
286
- sc2,
287
- 'vatA',
288
- {},
289
- options,
290
- gcTools,
291
- undefined,
292
- makeNS2,
293
- );
297
+ const vatParameters2 = { x: passables.get(vref1), y: passables.get(vref2) };
298
+ await ls2.dispatch(makeStartVat(kser(vatParameters2)));
299
+
300
+ const state2 = await dispatchForResult(ls2, log2, [objRef, 'get', fields]);
301
+ t.deepEqual(kser(state2), kser(state1));
294
302
 
295
- const vp = { obj1: kslot(vref1), obj2: kslot(vref2) };
296
- const startVat2 = makeStartVat(kser(vp));
297
- await ls2.dispatch(startVat2);
303
+ // ------
304
+
305
+ // Now upgrade again, first to add a new optional stateShape field x2 and then
306
+ // to preserve the new shape.
307
+ const store3 = new Map(store2);
308
+ const { syscall: sc3, log: log3 } = buildSyscall({ kvStore: store3 });
309
+ const ls3 = makeLiveSlots(sc3, 'vatA', {}, {}, gcTools, undefined, () => {
310
+ const buildRootObject = ({ VatData }, vatParameters, baggage) => {
311
+ const { x, y } = vatParameters;
312
+ const kh = baggage.get('kh');
313
+ const redefineDurableKind = stateShape =>
314
+ defineHolder(VatData.defineDurableKind, kh, stateShape);
315
+ const badShapes = [{ x, x2: x, y }];
316
+ for (const stateShape of badShapes) {
317
+ t.throws(() => redefineDurableKind(stateShape), stateShapeMismatch);
318
+ }
319
+ redefineDurableKind({ x, x2: M.or(M.undefined(), x), y });
320
+ return Far('root', {});
321
+ };
322
+ return { buildRootObject };
323
+ });
324
+ const vatParameters3 = { x: passables.get(vref1), y: passables.get(vref2) };
325
+ await ls3.dispatch(makeStartVat(kser(vatParameters3)));
326
+
327
+ const state3 = await dispatchForResult(ls3, log3, [objRef, 'get', fields]);
328
+ t.deepEqual(kser(state3), kser({ ...state1, x2: undefined }));
329
+ const makeVerificationCase = (x, x2, y, throwsMessage) => [
330
+ `{ x: ${x}, x2: ${x2}, y: ${y} }`,
331
+ { x: passables.get(x), x2: passables.get(x2), y: passables.get(y) },
332
+ throwsMessage,
333
+ ];
334
+ const verifications = [
335
+ makeVerificationCase(vref1, vref2, vref2, 'rejected'),
336
+ makeVerificationCase(vref1, vref1, vref2),
337
+ makeVerificationCase(vref1, undefined, vref2),
338
+ ];
339
+ let lastState = state3;
340
+ for (const [label, value, message] of verifications) {
341
+ const tryUpdate = () =>
342
+ dispatchForResult(ls3, log3, [objRef, 'set', [value]]);
343
+ await (message
344
+ ? t.throwsAsync(tryUpdate, { message }, `${label} ${message}`)
345
+ : tryUpdate());
346
+ const state = await dispatchForResult(ls3, log3, [objRef, 'get', fields]);
347
+ const expectState = message ? lastState : value;
348
+ t.deepEqual(kser(state), kser(expectState), `${label}`);
349
+ lastState = state;
350
+ }
351
+
352
+ const store4 = new Map(store3);
353
+ const { syscall: sc4, log: log4 } = buildSyscall({ kvStore: store4 });
354
+ const ls4 = makeLiveSlots(sc4, 'vatA', {}, {}, gcTools, undefined, () => {
355
+ const buildRootObject = ({ VatData }, vatParameters, baggage) => {
356
+ const { x, y } = vatParameters;
357
+ const kh = baggage.get('kh');
358
+ const redefineDurableKind = stateShape =>
359
+ defineHolder(VatData.defineDurableKind, kh, stateShape);
360
+ const badShapes = [
361
+ // Removing optionality from x2.
362
+ { x, x2: x, y },
363
+ // Equivalent but not *equal* to M.or(M.undefined(), x).
364
+ { x, x2: M.or(undefined, x), y },
365
+ { x, x2: M.or(x, M.undefined()), y },
366
+ ];
367
+ for (const stateShape of badShapes) {
368
+ t.throws(() => redefineDurableKind(stateShape), stateShapeMismatch);
369
+ }
370
+ redefineDurableKind({ x, x2: M.or(M.undefined(), x), y });
371
+ return Far('root', {});
372
+ };
373
+ return { buildRootObject };
374
+ });
375
+ const vatParameters4 = { x: passables.get(vref1), y: passables.get(vref2) };
376
+ await ls4.dispatch(makeStartVat(kser(vatParameters4)));
377
+
378
+ for (const [label, value, message] of verifications) {
379
+ const tryUpdate = () =>
380
+ dispatchForResult(ls4, log4, [objRef, 'set', [value]]);
381
+ await (message
382
+ ? t.throwsAsync(tryUpdate, { message }, `${label} ${message}`)
383
+ : tryUpdate());
384
+ const state = await dispatchForResult(ls4, log4, [objRef, 'get', fields]);
385
+ const expectState = message ? lastState : value;
386
+ t.deepEqual(kser(state), kser(expectState), `${label}`);
387
+ lastState = state;
388
+ }
298
389
  });