@agoric/swing-store 0.9.1 → 0.9.2-calypso-dev-84eb287.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.
@@ -16,7 +16,7 @@ The SwingStore export protocol defines two stages (effectively two datasets). Th
16
16
 
17
17
  Each time a SwingStore API is used to modify the state somehow (e.g. adding/changing/deleting a `kvStore` entry, or pushing a new item on to a transcript), the contents of both datasets may change. New first-stage entries can be created, existing ones may be modified or deleted. And the set of second-stage artifacts may change.
18
18
 
19
- These export data/artifact changes can happen when calling into the kernel (e.g. invoking the external API of a device, causing the device code to change its own state or push messages onto the run-queue), or by normal kernel operations as it runs (any time `controller.run()` is executing). When the kernel is idle (after `controller.run()` has completed), the kernel will not make any changes to the SwingStore, and both datasets will be stable.
19
+ These export data/artifact changes can happen when calling into the kernel (e.g. invoking the external API of a device, causing the device code to change its own state or push messages onto the run-queue), or by normal kernel operations as it runs (any time `controller.run()` is executing). When the kernel is idle (after `controller.run()` has completed), and `hostStorage.commit()` is called, the kernel will not make any changes to the SwingStore, and both datasets will be stable.
20
20
 
21
21
  Among other things, the SwingStore records a transcript of deliveries for each vat. The collection of all deliveries to a particular vat since its last heap snapshot was written is called the "current span". For each vat, the first-stage export data will record a single record that remembers the extent and the hash of the current span. This record then refers to a second-stage export artifact that contains the actual transcript contents.
22
22
 
@@ -39,30 +39,41 @@ The exporter is created by calling `makeSwingStoreExporter(dirpath)`, passing it
39
39
  After calling `hostStorage.commit()`, the host application can extract the first-stage export data, and then the second-stage export artifacts:
40
40
 
41
41
  ```js
42
- import { buffer } from 'node:stream/consumers';
43
-
44
- const dirPath = '.../swing-store';
45
- const swingStore = openSwingStore(dirPath);
46
- ...
47
- await controller.run();
48
- hostStorage.commit();
49
- // spawn a child process
50
-
51
- // child process does:
52
- const exporter = makeSwingStoreExporter(dirPath);
53
- // exporter now has a txn, parent process is free to proceed forward
54
- const exportData = new Map();
55
- for (const [key, value] of exporter.getExportData()) {
56
- exportData.set(key, value);
57
- }
58
- const exportArtifacts = new Map();
59
- for (const name of exporter.getArtifactNames()) {
60
- const reader = exporter.getArtifact(name);
61
- // reader is an async iterable of Uint8Array, e.g. a stream
62
- const data = await buffer(reader);
63
- exportArtifacts.set(name, data);
42
+ const dirPath = '.../swing-store';
43
+ const swingStore = openSwingStore(dirPath);
44
+ ...
45
+ await controller.run();
46
+ hostStorage.commit();
47
+ // spawn a child process and wait for it to open a transaction
48
+ const started = makePromiseKit();
49
+ const child = fork(path, args);
50
+ child.on('error', started.reject);
51
+ child.on('exit', started.reject);
52
+ child.on('message', msg => {
53
+ if (msg?.type === 'started') {
54
+ started.resolve();
64
55
  }
65
- // export is 'exportData' and 'exportArtifacts'
56
+ });
57
+ await started.promise;
58
+ ...
59
+
60
+ // child process does:
61
+ import { buffer } from 'node:stream/consumers';
62
+ const exporter = makeSwingStoreExporter(dirPath);
63
+ // exporter now has a txn, so parent process is free to proceed forward
64
+ process.send({ type: 'started' });
65
+ const exportData = new Map();
66
+ for (const [key, value] of exporter.getExportData()) {
67
+ exportData.set(key, value);
68
+ }
69
+ const exportArtifacts = new Map();
70
+ for (const name of exporter.getArtifactNames()) {
71
+ const reader = exporter.getArtifact(name);
72
+ // reader is an async iterable of Uint8Array, e.g. a stream
73
+ const data = await buffer(reader);
74
+ exportArtifacts.set(name, data);
75
+ }
76
+ // export is the combination of 'exportData' and 'exportArtifacts'
66
77
  ```
67
78
 
68
79
  ![image 3](./images/data-export-3.jpg)
@@ -83,6 +94,8 @@ So, to include SwingStore data in this state-sync snapshot, we need a way to get
83
94
 
84
95
  To support this, SwingStore has an "incremental export" mode. This is activated when the host application supplies an "export callback" option to the SwingStore instance constructor. Instead of retrieving the entire first-stage export data at the end of the block, the host application will be continuously notified about changes to this data as the kernel executes. The host application can then incorporate those entries into an existing hashed Merkle tree (e.g. the cosmos-sdk IAVL tree), whose root hash is included in the consensus block hash. Every time the callback is given `(key, value)`, the host should add a new (or modify some existing) IAVL entry, using an IAVL key within some range dedicated to the SwingStore first-stage export data. When the callback receives `(key, undefined)` or `(key, null)`, it should delete the entry. In this way, the IAVL tree maintains a "shadow copy" of the first-stage export data at all times, making the contents both covered by the consensus hash, and automatically included in the cosmos-sdk IAVL tree where it will become available to the new validator as it begins to reconstruct the SwingStore.
85
96
 
97
+ The export callback must be established from the very beginning, so it includes all changes made during kernel initialization.
98
+
86
99
  All validator nodes use this export callback, even if they never perform the rest of the export process, to ensure that the consensus state includes the entire first-stage dataset. (Note that the first stage data is generally smaller than the full dataset, making this relatively inexpensive).
87
100
 
88
101
  Then, on the few occasions when the application needs to build a full state-sync snapshot, it can ask the SwingStore (after block commit) for the full set of artifacts that match the most recent commit.
@@ -90,54 +103,58 @@ Then, on the few occasions when the application needs to build a full state-sync
90
103
  ![image 4](./images/data-export-4.jpg)
91
104
 
92
105
  ```js
93
- const dirPath = '.../swing-store';
94
- const iavl = ...;
95
- function exportCallback(key, value) {
96
- const iavlKey = `ssed.${key}`; // 'ssed' is short for SwingStoreExportData
97
- if (value) {
98
- iavl.set(iavlKey, value);
99
- } else {
100
- iavl.delete(iavlKey); // value===undefined means delete
101
- }
102
- }
103
- const swingStore = openSwingStore(dirPath, { exportCallback });
104
- ...
105
- await controller.run();
106
- hostStorage.commit();
107
-
108
- // now, if the validator is configured to publish state-sync snapshots,
109
- // and if this block height is one of the publishing points,
110
- // do the following:
111
-
112
- // spawn a child process
113
-
114
- // child process does:
115
- const exporter = makeSwingStoreExporter(dirPath);
116
- // note: no exporter.getExportData(), the first-stage data is already in IAVL
117
- const artifacts = new Map();
118
- for (const name of exporter.getArtifactNames()) {
119
- artifacts.set(name, exporter.getArtifact(name));
106
+ const dirPath = '.../swing-store';
107
+ const iavl = ...;
108
+ function exportCallback(key, value) {
109
+ const iavlKey = `ssed.${key}`; // 'ssed' is short for SwingStoreExportData
110
+ if (value === undefined) {
111
+ iavl.delete(iavlKey);
112
+ } else {
113
+ iavl.set(iavlKey, value);
114
+ }
115
+ }
116
+ const swingStore = openSwingStore(dirPath, { exportCallback });
117
+ ...
118
+ await controller.run();
119
+ hostStorage.commit();
120
+
121
+ // now, if the validator is configured to publish state-sync snapshots,
122
+ // and if this block height is one of the publishing points,
123
+ // do the following:
124
+
125
+ // spawn a child process and wait for it to open a transaction
126
+ const started = makePromiseKit();
127
+ const child = fork(path, args);
128
+ child.on('error', started.reject);
129
+ child.on('exit', started.reject);
130
+ child.on('message', msg => {
131
+ if (msg?.type === 'started') {
132
+ started.resolve();
120
133
  }
121
- // instruct cosmos-sdk to include 'artifacts' in the state-sync snapshot
134
+ });
135
+ try {
136
+ await started.promise;
137
+ } catch (err) {
138
+ ...
139
+ }
140
+ ...
141
+
142
+ // child process does:
143
+ const exporter = makeSwingStoreExporter(dirPath);
144
+ process.send({ type: 'started' });
145
+ // note: no exporter.getExportData(), the first-stage data is already in IAVL
146
+ const artifacts = new Map();
147
+ for (const name of exporter.getArtifactNames()) {
148
+ artifacts.set(name, exporter.getArtifact(name));
149
+ }
150
+ // instruct cosmos-sdk to include 'artifacts' in the state-sync snapshot
122
151
  ```
123
152
 
124
153
  ## Import
125
154
 
126
- On other end of the export process is an importer. This is a new host application, which wants to start from the contents of the export, rather than initializing a brand new (empty) kernel state.
155
+ On the other end of the export process is an importer. This is used to restore kernel state, so that a new host application can simply continue mostly as if it had been previously executing. The expectation is that the import and the execution are 2 independent events, and the execution doesn't need to be aware it was imported.
127
156
 
128
- When starting a brand new instance, host applications would normally call `openSwingStore(dirPath)` to create a new (empty) SwingStore, then call SwingSet's `initializeSwingset(config, .., kernelStorage)` to let the kernel initialize the DB with a config-dependent starting state:
129
-
130
- ```js
131
- // this is done only the first time an instance is created:
132
-
133
- import { openSwingStore } from '@agoric/swing-store';
134
- import { initializeSwingset } from '@agoric/swingset-vat';
135
- const dirPath = './swing-store';
136
- const { hostStorage, kernelStorage } = openSwingStore(dirPath);
137
- await initializeSwingset(config, argv, kernelStorage);
138
- ```
139
-
140
- Once the initial state is created, each time the application is launched, it will build a controller around the existing state:
157
+ For reference, after the initial state is created, each time the application is launched, it builds a controller around the existing state:
141
158
 
142
159
  ```js
143
160
  import { openSwingStore } from '@agoric/swing-store';
@@ -148,7 +165,7 @@ const controller = await makeSwingsetController(kernelStorage);
148
165
  // ... now do things like controller.run(), etc
149
166
  ```
150
167
 
151
- When cloning an existing kernel, the initialization step is replaced with `importSwingStore`. The host application should feed the importer with the export data and artifacts, by passing an object that has the same API as the SwingStore's exporter:
168
+ When cloning an existing kernel, the host application first imports and commits the restored state using `importSwingStore`. The host application should feed the importer with the export data and artifacts, by passing an object that has the same API as the SwingStore's exporter:
152
169
 
153
170
  ```js
154
171
  import { importSwingStore } from '@agoric/swing-store';
@@ -159,11 +176,13 @@ const exporter = {
159
176
  getArtifact(name) { // return blob of artifact data },
160
177
  };
161
178
  const { hostStorage } = importSwingStore(exporter, dirPath);
162
- hostStorage.commit();
163
- // now the swingstore is fully populated
179
+ // Update any hostStorage as needed
180
+ await hostStorage.commit();
181
+ await hostStorage.close();
182
+ // now the populated swingstore can be re-opened using `openSwingStore``
164
183
  ```
165
184
 
166
- Once the new SwingStore is fully populated with the previously-exported data, the host application can use `makeSwingsetController()` to build a kernel that will start from the exported state.
185
+ Once the new SwingStore is fully populated with the previously-exported data, the host application can update any host specific state before committing and closing the SwingStore. `importSwingStore` returns only the host facet of the SwingStore instance, as it is not suitable for immediate execution.
167
186
 
168
187
  ## Optional / Historical Data
169
188
 
@@ -177,18 +196,41 @@ As a result, for each active vat, the first-stage Export Data contains a record
177
196
 
178
197
  The `openSwingStore()` function has an option named `keepTranscripts` (which defaults to `true`), which causes the transcriptStore to retain the old transcript items. A second option named `keepSnapshots` (which defaults to `false`) causes the snapStore to retain the old heap snapshots. Opening the swingStore with a `false` option does not necessarily delete the old items immediately, but they'll probably get deleted the next time the kernel triggers a heap snapshot or transcript-span rollover. Validators who care about minimizing their disk usage will want to set both to `false`. In the future, we will arrange the SwingStore SQLite tables to provide easy `sqlite3` CLI commands that will delete the old data, so validators can also periodically use the CLI command to prune it.
179
198
 
180
- The `getArtifactNames()` API includes an option named `includeHistorical`. If `true`, all available historical artifacts will be included in the export (limited by what the `openSwingStore` options have deleted). If `false`, none will be included. Note that the "export data" is necessarily unaffected: if we *ever* want to validate this optional data, the hashes are mandatory. But the `getArtifactNames()` list will be smaller if you set `includeHistorical = false`. Also, re-exporting from a pruned copy will lack the old data, even if the re-export uses `includeHistorical = true`, because the second SwingStore cannot magically reconstruct the missing data.
199
+ When exporting, the `makeSwingStoreExporter()` function takes an `artifactMode` option (in an options bag). This serves to both limit, and provide some minimal guarantees about, the set of artifacts that will be provided in the export. The defined values of `artifactMode` each build upon the previous one:
200
+
201
+ * `operational`: include only the current transcript span and current snapshot for each vat: just the minimum set necessary for current operations
202
+ * `replay`: add all transcript spans for the current incarnation
203
+ * `archival`: add all available transcript spans, even for old incarnations
204
+ * `debug`: add all available snapshots, giving you everything. The old snapshots are never necessary for normal operations, nor are they likely to be useful for extreme upgrade scenarios, but they might be useful for some unusual debugging operations or investigations
205
+
206
+ For each mode, the export will fail if the data necessary for those artifacts is not available (e.g. it was previously pruned). For example, an export with `artifactMode: 'replay'` will fail unless every vat has all transcript entries for each one's current incarnation. The `archival` mode will fail to export unless every vat has *every* transcript entry, back to the very first incarnation.
207
+
208
+ However the `debug` export mode will never fail: it merely dumps everything in the swingstore, without limits or completeness checks.
209
+
210
+ Note that `artifactMode` does not affect the Export Data generated by the exporter (because if we *ever* want to validate this optional data, the hashes are mandatory). It only affects the names returned by `getArtifactNames()`: `operational` returns a subset of `replay`, which returns a subset of `archival`. And re-exporting from a previously-pruned copy under `archival` mode will fail, because the second SwingStore cannot magically reconstruct the missing data.
211
+
212
+ Also note that when a vat is terminated, we delete all information about it, including transcript items and snapshots, both current and old. This will remove all the Export Data records, and well as the matching artifacts from `getArtifactNames`.
213
+
214
+ When importing, the `importSwingStore()` function's options bag takes a property named `artifactMode`, with the same meanings as for export. Importing with the `operational` mode will ignore any artifacts other than those needed for current operations, and will fail unless all such artifacts were available. Importing with `replay` will ignore spans from old incarnations, but will fail unless all spans from current incarnations are present. Importing with `archival` will fail unless all spans from all incarnations are present. There is no `debug` option during import.
215
+
216
+ While `importSwingStore()`'s options bag accepts the same options as `openSwingStore()`, since it returns only the host facet of a SwingStore, some of these options might not be meaningful, such as `keepTranscripts`.
217
+
218
+ So, to avoid pruning current-incarnation historical transcript spans when exporting from one swingstore to another, you must set (or avoid overriding) the following options along the way:
181
219
 
182
- Note that when a vat is terminated, we delete all information about it, including transcript items and snapshots, both current and old. This will remove all the Export Data records, and well as the matching artifacts from `getArtifactNames`.
220
+ * the original swingstore must not be opened with `{ keepTranscripts: false }`, otherwise the old spans will be pruned immediately
221
+ * the export must use `makeSwingStoreExporter(dirpath, { artifactMode: 'replay'})`, otherwise the export will omit the old spans
222
+ * the import must use `importSwingStore(exporter, dirPath, { artifactMode: 'replay'})`, otherwise the import will ignore the old spans
223
+ * subsequent `openSwingStore` calls must not use `keepTranscripts: false`, otherwise the new swingstore will prune historical spans as new ones are created (during `rolloverSpan`).
183
224
 
184
225
  ## Implementation Details
185
226
 
186
- SwingStore contains components to accomodate all the various kinds of state that the SwingSet kernel needs to store. This currently consists of three portions:
227
+ SwingStore contains components to accommodate all the various kinds of state that the SwingSet kernel needs to store. This currently consists of four portions:
187
228
 
188
229
  * `kvStore`, a general-purpose string/string key-value table
189
230
  * `transcriptStore`: append-only vat deliveries, broken into "spans", delimited by heap snapshot events
190
231
  * `snapshotStore`: binary blobs containing JS engine heap state, to limit transcript replay depth
232
+ * `bundleStore`: code bundles that can be imported with [@endo/import-bundle](https://www.npmjs.com/package/@endo/import-bundle)
191
233
 
192
- Currently, the SwingStore treats transcript spans and heap snapshots as export artifacts, with hashes recorded in the export data for validation (and to remember exactly which artifacts are necessary). The `kvStore` is copied one-to-one into the export data (i.e. we keep a full shadow copy in IAVL), because that is the fastest way to ensure the `kvStore` data is fully available and validated.
234
+ Currently, the SwingStore treats transcript spans, heap snapshots, and bundles as export artifacts, with hashes recorded in the export data for validation (and to remember exactly which artifacts are necessary). The `kvStore` is copied one-to-one into the export data (i.e. we keep a full shadow copy in IAVL), because that is the fastest way to ensure the `kvStore` data is fully available and validated.
193
235
 
194
236
  If some day we implement an IAVL-like Merkle tree inside SwingStore, and use it to automatically generate a root hash for the `kvStore` at the end of each block, we will replace this (large) shadow copy with a single `kvStoreRootHash` entry, and add a new export artifact to contain the full contents of the kvStore. This reduce the size of the IAVL tree, as well as the rate of IAVL updates during block execution, at the cost of increased CPU and complexity within SwingStore.
@@ -0,0 +1,52 @@
1
+ # SwingStore Data Model
2
+
3
+ The "SwingStore" provides a database to hold SwingSet kernel state, with an API crafted to help both the kernel and the host application mutate, commit, export, and import this state.
4
+
5
+ The state is broken up into several pieces, or "stores":
6
+
7
+ * `bundleStore`: a string-keyed Bundle-value table, holding source bundles which can be evaluated by `importBundle` to create vats, or new Compartments within a vat
8
+ * `transcriptStore`: records a linear sequence of deliveries and syscalls (with results), collectively known as "transcript entries", for each vat
9
+ * `snapStore`: records one or more XS heap snapshots for each vat, to rebuild a worker more efficiently than replaying all transcript entries from the beginning
10
+ * `kvStore`: a string-keyed string-valued table, which holds everything else. Currently, this holds each vat's c-list and vatstore data, as well as the kernel-wide object and promise tables, and run-queues.
11
+
12
+ ## Incarnations, Spans, Snapshots
13
+
14
+ The kernel tracks the state of one or more vats. Each vat's execution is split into "incarnations", which are separated by a "vat upgrade" (a call to `E(vatAdminFacet).upgrade(newBundleCap, options)`, see https://github.com/Agoric/agoric-sdk/blob/master/packages/SwingSet/docs/vat-upgrade.md for details). Each incarnation gets a new worker, which erases the heap state and only retains durable vatstore data across the upgrade. Every active vat has a "current incarnation", and zero or more "historic incarnations". Only the current incarnation is instantiated.
15
+
16
+ Within each incarnation, execution is broken into one or more "spans", with a "current span" and zero or more "historic spans". This breaks up the transcript into corresponding spans.
17
+
18
+ Each historic span ends with a `save-snapshot` entry which records the creation and saving of an XS heap snapshot. The initial span starts with a `start-worker` entry, while all non-initial spans start with a `load-snapshot` entry. The final span of historic incarnations each end with a `shutdown-worker` entry.
19
+
20
+ Each `save-snapshot` entry adds a new snapshot to the `snapStore`, so each vat has zero or more snapshots, of which the last one is called the "current" or "in-use" snapshot, and the earlier ones are called "historical snapshots".
21
+
22
+ (note: the `deliveryNum` counter is scoped to the vat and does not reset at incarnation or span boundaries)
23
+
24
+ ## Artifacts
25
+
26
+ The import/export process (using `makeSwingStoreExporter` and `importSwingStore`) defines some number of "artifacts" to contain much of the SwingStore data. Each bundle is a separate artifact, as is each heap snapshot. Each transcript span is a separate artifact (an aggregate of the individual transcript entries comprising that span).
27
+
28
+ During export, the `getArtifactNames()` method provides a list of all available artifacts, while `getArtifact(name)` is used to retrieve the actual data. The import function processes each artifact separately.
29
+
30
+ ## Populated vs Pruned
31
+
32
+ For normal operation, the kernel does not require historical incarnations, spans, or snapshots. It only needs the ability to reconstruct a worker for the current incarnation of each vat, which means loading the current snapshot (if any), and replaying the contents of the current transcript span.
33
+
34
+ For this reason, the swingstore must always contain the current transcript span, and the current snapshot (if any), for every vat.
35
+
36
+ However, to save space, historical spans/snapshots might be pruned, by deleting their contents from the database (but retaining the metadata, which includes a hash of the contents for later validation). Historical snapshots are pruned by default (unless `openSwingStore()` is given an options bag with `keepSnapshots: true`). Historical spans are not currently pruned (the `keepTranscripts` option defaults to `true`), but that may change.
37
+
38
+ In addition, `importSwingStore()` can be used to create a SwingStore from data exported out of some other SwingStore. The export-then-import process might result in a pruned DB in one of three ways:
39
+
40
+ * the import-time options might instruct the import process to ignore some of the available data
41
+ * the export-time options might have done the same
42
+ * the original DB was itself already pruned, so the data was not available in the first place
43
+
44
+ In the future, a separate SwingStore API will exist to allow previously-pruned artifacts to be repopulated. Every artifact has a metadata record which *is* included in the export (in the `exportData` section, but separate from the kvStore shadow table entries, see [data-export.md](./data-export.md)), regardless of pruning modes, to ensure that this API can check the integrity of these repopulated artifacts. This reduces the reliance set and trust burden of the repopulation process (we can safely use untrusted artifact providers).
45
+
46
+ When a snapshot is pruned, the `snapshots` SQL table row is modified, replacing its `compressedSnapshot` BLOB with a NULL. The other columns are left alone, especially the `hash` column, which retains the integrity-checking metadata to support a future repopulation.
47
+
48
+ When a transcript span is pruned, the `transcriptSpans` row is left alone, but the collection of `transcriptItems` rows are deleted. Any span for which all the `transcriptItems` rows are present is said to be "populated", while any span that is missing one or more `transcriptItems` rows is said to be "pruned". (There is no good reason for a span to be only partially pruned, but until we compress historical spans into a single row, in some new table, there remains the possibility of partial pruning).
49
+
50
+ During import, we create the metadata first (as the export-data is parsed), then later, we fill in the details as the artifacts are read.
51
+
52
+ Bundles are never pruned, however during import, the `bundles` table will temporarily contain rows whose `bundle` BLOB is NULL.
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@agoric/swing-store",
3
- "version": "0.9.1",
3
+ "version": "0.9.2-calypso-dev-84eb287.0+84eb287",
4
4
  "description": "Persistent storage for SwingSet",
5
5
  "type": "module",
6
- "main": "src/swingStore.js",
6
+ "main": "./src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js"
9
+ },
7
10
  "repository": "https://github.com/Agoric/agoric-sdk",
8
11
  "author": "Agoric",
9
12
  "license": "Apache-2.0",
@@ -14,23 +17,23 @@
14
17
  "test:xs": "exit 0",
15
18
  "lint-fix": "yarn lint:eslint --fix",
16
19
  "lint": "run-s --continue-on-error lint:*",
17
- "lint:types": "tsc -p jsconfig.json",
20
+ "lint:types": "tsc",
18
21
  "lint:eslint": "eslint ."
19
22
  },
20
23
  "dependencies": {
21
- "@agoric/assert": "^0.6.0",
22
- "@agoric/internal": "^0.3.2",
23
- "@endo/base64": "^0.2.31",
24
- "@endo/bundle-source": "^2.5.1",
25
- "@endo/check-bundle": "^0.2.18",
26
- "@endo/nat": "^4.1.27",
27
- "better-sqlite3": "^8.2.0"
24
+ "@agoric/assert": "0.6.1-calypso-dev-84eb287.0+84eb287",
25
+ "@agoric/internal": "0.3.3-calypso-dev-84eb287.0+84eb287",
26
+ "@endo/base64": "^1.0.5",
27
+ "@endo/bundle-source": "^3.2.3",
28
+ "@endo/check-bundle": "^1.0.7",
29
+ "@endo/nat": "^5.0.7",
30
+ "better-sqlite3": "^9.1.1"
28
31
  },
29
32
  "devDependencies": {
30
- "@endo/init": "^0.5.56",
31
- "@types/better-sqlite3": "^7.5.0",
32
- "ava": "^5.2.0",
33
- "c8": "^7.13.0",
33
+ "@endo/init": "^1.1.2",
34
+ "@types/better-sqlite3": "^7.6.9",
35
+ "ava": "^5.3.0",
36
+ "c8": "^9.1.0",
34
37
  "tmp": "^0.2.1"
35
38
  },
36
39
  "publishConfig": {
@@ -38,9 +41,15 @@
38
41
  },
39
42
  "ava": {
40
43
  "files": [
41
- "test/**/test-*.js"
44
+ "test/**/*.test.*"
45
+ ],
46
+ "require": [
47
+ "@endo/init/debug.js"
42
48
  ],
43
49
  "timeout": "2m"
44
50
  },
45
- "gitHead": "b66bf7c881ae462fcb617cd6e8d41b5c40ec58ef"
51
+ "typeCoverage": {
52
+ "atLeast": 76.31
53
+ },
54
+ "gitHead": "84eb287915c8255d68e845a9b6e10c790f0d54cb"
46
55
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @param {import('./internal.js').SwingStoreInternal} internal
3
+ * @param {Omit<import('./internal.js').ArtifactMode, 'debug'>} checkMode
4
+ * @returns {void}
5
+ */
6
+ export function assertComplete(internal, checkMode) {
7
+ // every bundle must be populated
8
+ internal.bundleStore.assertComplete(checkMode);
9
+
10
+ // every 'isCurrent' transcript span must have all items
11
+ // TODO: every vat with any data must have a isCurrent transcript
12
+ // span
13
+ internal.transcriptStore.assertComplete(checkMode);
14
+
15
+ // every 'inUse' snapshot must be populated
16
+ internal.snapStore.assertComplete(checkMode);
17
+
18
+ // TODO: every isCurrent span that starts with load-snapshot has a
19
+ // matching snapshot (counter-argument: swing-store should not know
20
+ // those details about transcript entries)
21
+ }