@agoric/swingset-liveslots 0.10.3-other-dev-8f8782b.0 → 0.10.3-other-dev-fbe72e7.0.fbe72e7

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 (141) hide show
  1. package/README.md +2 -0
  2. package/package.json +34 -26
  3. package/src/boyd-gc.d.ts +12 -0
  4. package/src/boyd-gc.d.ts.map +1 -0
  5. package/src/boyd-gc.js +598 -0
  6. package/src/cache.d.ts +71 -0
  7. package/src/cache.d.ts.map +1 -0
  8. package/src/cache.js +3 -2
  9. package/src/capdata.d.ts +16 -0
  10. package/src/capdata.d.ts.map +1 -0
  11. package/src/capdata.js +17 -10
  12. package/src/collectionManager.d.ts +47 -0
  13. package/src/collectionManager.d.ts.map +1 -0
  14. package/src/collectionManager.js +220 -103
  15. package/src/facetiousness.d.ts +25 -0
  16. package/src/facetiousness.d.ts.map +1 -0
  17. package/src/facetiousness.js +1 -1
  18. package/src/index.d.ts +4 -0
  19. package/src/index.d.ts.map +1 -0
  20. package/src/index.js +4 -2
  21. package/src/kdebug.d.ts +7 -0
  22. package/src/kdebug.d.ts.map +1 -0
  23. package/src/liveslots.d.ts +42 -0
  24. package/src/liveslots.d.ts.map +1 -0
  25. package/src/liveslots.js +137 -305
  26. package/src/message.d.ts +49 -0
  27. package/src/message.d.ts.map +1 -0
  28. package/src/message.js +9 -5
  29. package/src/parseVatSlots.d.ts +125 -0
  30. package/src/parseVatSlots.d.ts.map +1 -0
  31. package/src/parseVatSlots.js +1 -1
  32. package/src/types-index.d.ts +4 -0
  33. package/src/types-index.js +2 -0
  34. package/src/types.d.ts +81 -0
  35. package/src/types.d.ts.map +1 -0
  36. package/src/types.js +14 -7
  37. package/src/vatDataTypes.d.ts +170 -0
  38. package/src/vatDataTypes.d.ts.map +1 -0
  39. package/src/vatDataTypes.ts +272 -0
  40. package/src/vatstore-iterators.d.ts +4 -0
  41. package/src/vatstore-iterators.d.ts.map +1 -0
  42. package/src/vatstore-iterators.js +2 -0
  43. package/src/vatstore-usage.md +198 -0
  44. package/src/virtualObjectManager.d.ts +44 -0
  45. package/src/virtualObjectManager.d.ts.map +1 -0
  46. package/src/virtualObjectManager.js +254 -84
  47. package/src/virtualReferences.d.ts +61 -0
  48. package/src/virtualReferences.d.ts.map +1 -0
  49. package/src/virtualReferences.js +135 -26
  50. package/src/vpid-tracking.md +92 -0
  51. package/src/watchedPromises.d.ts +31 -0
  52. package/src/watchedPromises.d.ts.map +1 -0
  53. package/src/watchedPromises.js +81 -24
  54. package/test/{test-baggage.js → baggage.test.js} +1 -2
  55. package/test/{test-cache.js → cache.test.js} +0 -1
  56. package/test/clear-collection.test.js +586 -0
  57. package/test/{test-collection-schema-refcount.js → collection-schema-refcount.test.js} +1 -2
  58. package/test/{test-collection-upgrade.js → collection-upgrade.test.js} +1 -3
  59. package/test/{test-collections.js → collections.test.js} +183 -18
  60. package/test/{test-dropped-collection-weakrefs.js → dropped-collection-weakrefs.test.js} +1 -2
  61. package/test/dropped-weakset-9939.test.js +80 -0
  62. package/test/dummyMeterControl.d.ts +2 -0
  63. package/test/dummyMeterControl.d.ts.map +1 -0
  64. package/test/dummyMeterControl.js +1 -1
  65. package/test/{test-durabilityChecks.js → durabilityChecks.test.js} +4 -4
  66. package/test/engine-gc.d.ts +3 -0
  67. package/test/engine-gc.d.ts.map +1 -0
  68. package/test/exo-utils.js +70 -0
  69. package/test/{test-facetiousness.js → facetiousness.test.js} +1 -2
  70. package/test/gc-and-finalize.d.ts +5 -0
  71. package/test/gc-and-finalize.d.ts.map +1 -0
  72. package/test/gc-and-finalize.js +30 -1
  73. package/test/gc-before-finalizer.test.js +230 -0
  74. package/test/gc-helpers.js +4 -5
  75. package/test/{test-gc-sensitivity.js → gc-sensitivity.test.js} +2 -2
  76. package/test/handled-promises.test.js +872 -0
  77. package/test/{test-initial-vrefs.js → initial-vrefs.test.js} +13 -20
  78. package/test/liveslots-helpers.d.ts +64 -0
  79. package/test/liveslots-helpers.d.ts.map +1 -0
  80. package/test/liveslots-helpers.js +13 -7
  81. package/test/{test-liveslots-mock-gc.js → liveslots-mock-gc.test.js} +101 -2
  82. package/test/{test-liveslots-real-gc.js → liveslots-real-gc.test.js} +73 -46
  83. package/test/{test-liveslots.js → liveslots.test.js} +17 -18
  84. package/test/mock-gc.js +1 -0
  85. package/test/storeGC/{test-lifecycle.js → lifecycle.test.js} +15 -14
  86. package/test/storeGC/{test-refcount-management.js → refcount-management.test.js} +1 -2
  87. package/test/storeGC/{test-scalar-store-kind.js → scalar-store-kind.test.js} +0 -1
  88. package/test/storeGC/{test-weak-key.js → weak-key.test.js} +1 -2
  89. package/test/strict-test-env-upgrade.test.js +94 -0
  90. package/test/util.d.ts +25 -0
  91. package/test/util.d.ts.map +1 -0
  92. package/test/util.js +4 -4
  93. package/test/vat-environment.test.js +65 -0
  94. package/test/vat-util.d.ts +9 -0
  95. package/test/vat-util.d.ts.map +1 -0
  96. package/test/vat-util.js +2 -2
  97. package/test/virtual-objects/{test-cease-recognition.js → cease-recognition.test.js} +2 -2
  98. package/test/virtual-objects/{test-cross-facet.js → cross-facet.test.js} +5 -4
  99. package/test/virtual-objects/{test-empty-data.js → empty-data.test.js} +1 -2
  100. package/test/virtual-objects/{test-facets.js → facets.test.js} +1 -2
  101. package/test/virtual-objects/{test-kind-changes.js → kind-changes.test.js} +2 -2
  102. package/test/virtual-objects/{test-reachable-vrefs.js → reachable-vrefs.test.js} +2 -2
  103. package/test/virtual-objects/{test-rep-tostring.js → rep-tostring.test.js} +3 -5
  104. package/test/virtual-objects/{test-retain-remotable.js → retain-remotable.test.js} +25 -24
  105. package/test/virtual-objects/set-debug-label-instances.js +1 -1
  106. package/test/virtual-objects/state-shape.test.js +389 -0
  107. package/test/virtual-objects/{test-virtualObjectGC.js → virtualObjectGC.test.js} +39 -38
  108. package/test/virtual-objects/{test-virtualObjectManager.js → virtualObjectManager.test.js} +104 -8
  109. package/test/virtual-objects/{test-vo-real-gc.js → vo-real-gc.test.js} +8 -8
  110. package/test/virtual-objects/{test-weakcollections-vref-handling.js → weakcollections-vref-handling.test.js} +1 -2
  111. package/test/{test-vo-test-harness.js → vo-test-harness.test.js} +13 -10
  112. package/test/{test-vpid-liveslots.js → vpid-liveslots.test.js} +105 -5
  113. package/test/waitUntilQuiescent.d.ts +3 -0
  114. package/test/waitUntilQuiescent.d.ts.map +1 -0
  115. package/test/waitUntilQuiescent.js +2 -1
  116. package/test/weakset-dropped-remotable.test.js +50 -0
  117. package/tools/fakeCollectionManager.d.ts +14 -0
  118. package/tools/fakeCollectionManager.d.ts.map +1 -0
  119. package/tools/fakeCollectionManager.js +44 -0
  120. package/tools/fakeVirtualObjectManager.d.ts +32 -0
  121. package/tools/fakeVirtualObjectManager.d.ts.map +1 -0
  122. package/tools/fakeVirtualObjectManager.js +62 -0
  123. package/tools/fakeVirtualSupport.d.ts +278 -0
  124. package/tools/fakeVirtualSupport.d.ts.map +1 -0
  125. package/tools/fakeVirtualSupport.js +389 -0
  126. package/tools/prepare-strict-test-env.d.ts +37 -0
  127. package/tools/prepare-strict-test-env.d.ts.map +1 -0
  128. package/tools/prepare-strict-test-env.js +124 -0
  129. package/tools/prepare-test-env.d.ts +2 -0
  130. package/tools/prepare-test-env.d.ts.map +1 -0
  131. package/tools/prepare-test-env.js +13 -0
  132. package/tools/setup-vat-data.d.ts +9 -0
  133. package/tools/setup-vat-data.d.ts.map +1 -0
  134. package/tools/setup-vat-data.js +95 -0
  135. package/tools/vo-test-harness.d.ts +33 -0
  136. package/tools/vo-test-harness.d.ts.map +1 -0
  137. package/tools/vo-test-harness.js +164 -0
  138. package/CHANGELOG.md +0 -61
  139. package/test/kmarshal.js +0 -79
  140. package/test/test-handled-promises.js +0 -360
  141. package/test/virtual-objects/test-state-shape.js +0 -298
@@ -1,56 +1,57 @@
1
- /* global WeakRef */
1
+ // @ts-nocheck
2
2
  import test from 'ava';
3
- import '@endo/init/debug.js';
4
3
 
5
4
  import { Far } from '@endo/marshal';
6
5
  import { initEmpty } from '@agoric/store';
7
6
 
8
7
  import engineGC from '../engine-gc.js';
9
- import { makeGcAndFinalize } from '../gc-and-finalize.js';
8
+ import { makeGcAndFinalize, watchCollected } from '../gc-and-finalize.js';
10
9
  import { makeFakeVirtualStuff } from '../../tools/fakeVirtualSupport.js';
11
10
 
12
- function makeHeld() {
13
- const held = Far('held');
14
- const wr = new WeakRef(held);
11
+ function makeStashKit(name = 'held') {
12
+ const held = Far(name);
13
+ const collected = watchCollected(held);
15
14
  const ws = new WeakSet(); // note: real WeakSet, not vref-aware
16
15
  ws.add(held);
17
16
  function isHeld(obj) {
18
17
  return ws.has(obj);
19
18
  }
20
- return { held, wr, isHeld };
19
+ function isCollected() {
20
+ return collected.result;
21
+ }
22
+ return { held, isCollected, isHeld };
21
23
  }
22
24
 
23
25
  function prepareEphemeral(vom) {
24
- const ephemeral = Far('ephemeral');
25
- vom.registerEntry('o+12345', ephemeral);
26
- const wr = new WeakRef(ephemeral);
27
- return { wr };
26
+ const { held, isCollected } = makeStashKit('ephemeral');
27
+ vom.registerEntry('o+12345', held);
28
+ return { isCollected };
28
29
  }
29
30
 
30
31
  function stashRemotableOne(weakStore, key1) {
31
- const { held, wr, isHeld } = makeHeld();
32
+ const { held, isCollected, isHeld } = makeStashKit();
32
33
  weakStore.init(key1, held);
33
- return { wr, isHeld };
34
+ return { isCollected, isHeld };
34
35
  }
35
36
 
36
37
  function stashRemotableTwo(weakStore, key1) {
37
- const { held, wr, isHeld } = makeHeld();
38
+ const { held, isCollected, isHeld } = makeStashKit();
38
39
  weakStore.init(key1, 'initial');
39
40
  weakStore.set(key1, held);
40
- return { wr, isHeld };
41
+ return { isCollected, isHeld };
41
42
  }
42
43
 
43
44
  function stashRemotableThree(holderMaker) {
44
- const { held, wr, isHeld } = makeHeld();
45
+ const { held, isCollected, isHeld } = makeStashKit();
45
46
  const holder = holderMaker(held);
46
- return { wr, isHeld, holder };
47
+ return { isCollected, isHeld, holder };
47
48
  }
48
49
 
49
50
  function stashRemotableFour(holderMaker) {
50
- const { held, wr, isHeld } = makeHeld();
51
+ const { held, isCollected, isHeld } = makeStashKit();
51
52
  const holder = holderMaker('initial');
52
53
  holder.setHeld(held);
53
- return { wr, isHeld, holder };
54
+ return { isCollected, isHeld, holder };
54
55
  }
55
56
 
56
57
  test('remotables retained by virtualized data', async t => {
@@ -74,7 +75,7 @@ test('remotables retained by virtualized data', async t => {
74
75
  // false positive in the subsequent test
75
76
  const stash0 = prepareEphemeral(vom);
76
77
  await gcAndFinalize();
77
- t.falsy(stash0.wr.deref(), `caution: fake VOM didn't release Remotable`);
78
+ t.true(stash0.isCollected(), `caution: fake VOM didn't release Remotable`);
78
79
 
79
80
  // stash a Remotable in the value of a weakStore
80
81
  const key1 = makeKey();
@@ -84,14 +85,14 @@ test('remotables retained by virtualized data', async t => {
84
85
  // Representatives or Presences, so the value is not holding a strong
85
86
  // reference to the Remotable. The VOM is supposed to keep it alive, via
86
87
  // reachableRemotables.
87
- t.truthy(stash1.wr.deref());
88
+ t.false(stash1.isCollected());
88
89
  t.truthy(stash1.isHeld(weakStore.get(key1)));
89
90
 
90
91
  // do the same, but exercise weakStore.set instead of .init
91
92
  const key2 = makeKey();
92
93
  const stash2 = stashRemotableTwo(weakStore, key2);
93
94
  await gcAndFinalize();
94
- t.truthy(stash2.wr.deref());
95
+ t.false(stash2.isCollected());
95
96
  t.truthy(stash2.isHeld(weakStore.get(key2)));
96
97
 
97
98
  // now stash a Remotable in the state of a virtual object during init()
@@ -100,12 +101,12 @@ test('remotables retained by virtualized data', async t => {
100
101
  // Each state property is virtualized upon write (via the generated
101
102
  // setters). So again we rely on the VOM to keep the Remotable alive in
102
103
  // case someone retrieves it again.
103
- t.truthy(stash3.wr.deref());
104
+ t.false(stash3.isCollected());
104
105
  t.truthy(stash3.isHeld(stash3.holder.getHeld()));
105
106
 
106
107
  // same, but stash after init()
107
108
  const stash4 = stashRemotableFour(makeHolder);
108
109
  await gcAndFinalize();
109
- t.truthy(stash4.wr.deref());
110
+ t.false(stash4.isCollected());
110
111
  t.truthy(stash4.isHeld(stash4.holder.getHeld()));
111
112
  });
@@ -1,4 +1,4 @@
1
- /* global process */
1
+ /* eslint-env node */
2
2
 
3
3
  // put this in a separate module, so we can make it happen before
4
4
  // importing virtualObjectManager.js
@@ -0,0 +1,389 @@
1
+ // @ts-nocheck
2
+ import test from 'ava';
3
+
4
+ import { Fail } from '@endo/errors';
5
+ import { Far } from '@endo/marshal';
6
+ import { kser, kslot, kunser } from '@agoric/kmarshal';
7
+ import { M } from '@agoric/store';
8
+ import { makeLiveSlots } from '../../src/liveslots.js';
9
+ import { buildSyscall } from '../liveslots-helpers.js';
10
+ import { makeStartVat, makeMessage } from '../util.js';
11
+ import { makeMockGC } from '../mock-gc.js';
12
+ import { makeFakeVirtualStuff } from '../../tools/fakeVirtualSupport.js';
13
+
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
+ };
43
+
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
+ },
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 });
70
+
71
+ // virtual/durable Kinds can specify a 'stateShape', which should be
72
+ // enforced at both initialization and subsequent state changes
73
+ const testStateShape = test.macro((t, valueShape, config) => {
74
+ const { goodValues, badValues, throwsExpectation } = config;
75
+ const { vom } = makeFakeVirtualStuff();
76
+ const { defineKind } = vom;
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/ },
110
+ });
111
+
112
+ // durable Kinds serialize and store their stateShape, which must
113
+ // itself be durable
114
+ test('durable state shape', t => {
115
+ // note: relaxDurabilityRules defaults to true in fake tools
116
+ const { vom } = makeFakeVirtualStuff({ relaxDurabilityRules: false });
117
+ const { makeKindHandle, defineDurableKind } = vom;
118
+
119
+ let kindNumber = 0;
120
+ const defineNextKind = valueShape => {
121
+ kindNumber += 1;
122
+ const kh = makeKindHandle(`kind${kindNumber}`);
123
+ return defineHolder(defineDurableKind, kh, { value: valueShape });
124
+ };
125
+
126
+ const makeKind1 = defineNextKind();
127
+ makeKind1({ value: undefined });
128
+
129
+ const makeKind2 = defineNextKind();
130
+ makeKind2();
131
+
132
+ const makeKind3 = defineNextKind(M.any());
133
+ const obj3 = makeKind3();
134
+
135
+ const makeKind4 = defineNextKind(M.string());
136
+ const obj4 = makeKind4({ value: 'string' });
137
+
138
+ const makeKind5 = defineNextKind(M.remotable());
139
+ const durableValueFail = { message: /value for "value" is not durable/ };
140
+ t.throws(() => makeKind5({ value: eph1 }), durableValueFail);
141
+
142
+ const durableShapeFail = { message: /stateShape.*is not durable: slot 0 of/ };
143
+ t.throws(() => defineNextKind(eph1), durableShapeFail);
144
+
145
+ const makeKind7 = defineNextKind(obj4); // obj4 is durable
146
+ makeKind7({ value: obj4 });
147
+ const specificRemotableFail = { message: /kind3.*Must be:.*kind4/ };
148
+ t.throws(() => makeKind7({ value: obj3 }), specificRemotableFail);
149
+ });
150
+
151
+ // durable Kinds maintain refcounts on their serialized stateShape
152
+ test('durable stateShape refcounts', async t => {
153
+ const kvStore = new Map();
154
+ const { syscall: sc1 } = buildSyscall({ kvStore });
155
+ const gcTools = makeMockGC();
156
+
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';
175
+
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);
180
+
181
+ // ...but using it in stateShape does
182
+ await ls1.dispatch(makeMessage(root, 'defineDurableKind', [kslot(vref1)]));
183
+ t.is(ls1.testHooks.getReachableRefCount(vref1), 1);
184
+
185
+ // ------
186
+
187
+ // Simulate upgrade by starting from the non-empty kvStore.
188
+ const clonedStore = new Map(kvStore);
189
+ const { syscall: sc2 } = buildSyscall({ kvStore: clonedStore });
190
+
191
+ // to test refcount increment/decrement, we need to override the
192
+ // usual rule that the new version must exactly match the original
193
+ // stateShape
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
+ });
207
+
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);
216
+ });
217
+
218
+ test('durable stateShape must match or extend', async t => {
219
+ const store1 = new Map();
220
+ const { syscall: sc1, log: log1 } = buildSyscall({ kvStore: store1 });
221
+ const gcTools = makeMockGC();
222
+
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';
242
+
243
+ const vref1 = 'o-1';
244
+ const vref2 = 'o-2';
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) }),
259
+ );
260
+
261
+ // ------
262
+
263
+ // Simulate upgrade by starting from the non-empty kvStore.
264
+ const store2 = new Map(store1);
265
+ const { syscall: sc2, log: log2 } = buildSyscall({ kvStore: store2 });
266
+
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', {});
293
+ };
294
+ return { buildRootObject };
295
+ });
296
+
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));
302
+
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
+ }
389
+ });