@gmickel/gno 1.23.0 → 1.24.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/README.md +18 -12
- package/assets/skill/SKILL.md +26 -19
- package/package.json +1 -1
- package/spec/cli.md +91 -0
- package/spec/output-schemas/setup-activation-result.schema.json +456 -0
- package/spec/output-schemas/setup-command-result.schema.json +93 -0
- package/spec/output-schemas/setup-receipt.schema.json +258 -0
- package/spec/output-schemas/setup-semantic-receipt.schema.json +195 -0
- package/src/cli/commands/completion/scripts.ts +2 -0
- package/src/cli/commands/embed.ts +7 -2
- package/src/cli/commands/setup-activation.ts +324 -0
- package/src/cli/commands/setup-semantic.ts +591 -0
- package/src/cli/commands/setup.ts +410 -0
- package/src/cli/program.ts +64 -0
- package/src/cli/setup-semantic-worker.ts +177 -0
- package/src/core/config-mutation.ts +94 -64
- package/src/core/file-lock.ts +70 -31
- package/src/core/folder-setup-planning.ts +453 -0
- package/src/core/folder-setup.ts +490 -0
- package/src/core/setup-activation.ts +309 -0
- package/src/core/setup-receipt.ts +321 -0
- package/src/serve/connectors.ts +29 -2
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core-only verified folder setup orchestration.
|
|
3
|
+
*
|
|
4
|
+
* @module src/core/folder-setup
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Collection } from "../config";
|
|
8
|
+
import type { SyncService } from "../ingestion";
|
|
9
|
+
import type { SqliteAdapter } from "../store/sqlite/adapter";
|
|
10
|
+
|
|
11
|
+
import { DEFAULT_EXCLUDES, loadConfig } from "../config";
|
|
12
|
+
import { defaultSyncService, withContentTypeRules } from "../ingestion";
|
|
13
|
+
import { verifyLexicalActivation } from "./activation-verifier";
|
|
14
|
+
import { applyConfigChange } from "./config-mutation";
|
|
15
|
+
import {
|
|
16
|
+
type CollectionSelection,
|
|
17
|
+
type FolderSetupError,
|
|
18
|
+
type FolderSetupErrorCode,
|
|
19
|
+
normalizeSetupExcludes,
|
|
20
|
+
preflightFolder,
|
|
21
|
+
resolveSetupFolder,
|
|
22
|
+
resolveSetupStoreIndex,
|
|
23
|
+
selectFolderCollection,
|
|
24
|
+
setupError,
|
|
25
|
+
setupExcludesMatch,
|
|
26
|
+
setupFilterDisagreement,
|
|
27
|
+
setupInjectedFailure,
|
|
28
|
+
validateSetupOutputPaths,
|
|
29
|
+
} from "./folder-setup-planning";
|
|
30
|
+
import {
|
|
31
|
+
createSetupReceipt,
|
|
32
|
+
failSetupStage,
|
|
33
|
+
type FolderSetupReceipt,
|
|
34
|
+
getSetupReceiptPath,
|
|
35
|
+
passSetupStage,
|
|
36
|
+
persistSetupReceipt,
|
|
37
|
+
setupFingerprint,
|
|
38
|
+
type SetupFailure,
|
|
39
|
+
type SetupStageName,
|
|
40
|
+
startSetupStage,
|
|
41
|
+
} from "./setup-receipt";
|
|
42
|
+
|
|
43
|
+
export type {
|
|
44
|
+
FolderSetupError,
|
|
45
|
+
FolderSetupErrorCode,
|
|
46
|
+
} from "./folder-setup-planning";
|
|
47
|
+
|
|
48
|
+
export type FolderSetupFailurePoint =
|
|
49
|
+
| "after_config_save"
|
|
50
|
+
| "after_store_sync"
|
|
51
|
+
| "after_lexical_index"
|
|
52
|
+
| "after_lexical_proof";
|
|
53
|
+
|
|
54
|
+
export interface FolderSetupOptions {
|
|
55
|
+
folder: string;
|
|
56
|
+
store: SqliteAdapter;
|
|
57
|
+
configPath: string;
|
|
58
|
+
dataDir: string;
|
|
59
|
+
indexName?: string;
|
|
60
|
+
name?: string;
|
|
61
|
+
exclude?: string[];
|
|
62
|
+
secretRiskAuthorized?: boolean;
|
|
63
|
+
/** Test-only deterministic interruption hook. */
|
|
64
|
+
failureInjection?: FolderSetupFailurePoint;
|
|
65
|
+
/** Test-only concurrency seam before the serialized config boundary. */
|
|
66
|
+
beforeConfigBoundary?: () => Promise<void>;
|
|
67
|
+
/** Test-only receipt persistence seam. */
|
|
68
|
+
receiptWriter?: (receipt: FolderSetupReceipt) => Promise<void>;
|
|
69
|
+
syncService?: SyncService;
|
|
70
|
+
now?: () => Date;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type FolderSetupResult =
|
|
74
|
+
| { ok: true; receipt: FolderSetupReceipt }
|
|
75
|
+
| {
|
|
76
|
+
ok: false;
|
|
77
|
+
error: FolderSetupError;
|
|
78
|
+
receipt: FolderSetupReceipt | null;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
class SetupAbort extends Error {
|
|
82
|
+
readonly result: FolderSetupResult;
|
|
83
|
+
|
|
84
|
+
constructor(result: FolderSetupResult) {
|
|
85
|
+
super("Folder setup aborted");
|
|
86
|
+
this.result = result;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function nowIso(options: FolderSetupOptions): string {
|
|
91
|
+
return (options.now ?? (() => new Date()))().toISOString();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function stageFailure(
|
|
95
|
+
stage: SetupStageName,
|
|
96
|
+
error: FolderSetupError
|
|
97
|
+
): SetupFailure {
|
|
98
|
+
return { stage, ...error };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function writeReceipt(
|
|
102
|
+
receipt: FolderSetupReceipt,
|
|
103
|
+
stage: SetupStageName,
|
|
104
|
+
options: FolderSetupOptions
|
|
105
|
+
): Promise<FolderSetupResult | null> {
|
|
106
|
+
try {
|
|
107
|
+
await (options.receiptWriter ?? persistSetupReceipt)(receipt);
|
|
108
|
+
return null;
|
|
109
|
+
} catch {
|
|
110
|
+
const error = setupError(
|
|
111
|
+
"receipt_write_failed",
|
|
112
|
+
`Failed to persist setup receipt: ${receipt.paths.receipt}`,
|
|
113
|
+
"Check local data-directory permissions and retry."
|
|
114
|
+
);
|
|
115
|
+
failSetupStage(receipt, stageFailure(stage, error), nowIso(options));
|
|
116
|
+
return { ok: false, error, receipt };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function persistFailure(
|
|
121
|
+
receipt: FolderSetupReceipt,
|
|
122
|
+
stage: SetupStageName,
|
|
123
|
+
error: FolderSetupError,
|
|
124
|
+
options: FolderSetupOptions
|
|
125
|
+
): Promise<FolderSetupResult> {
|
|
126
|
+
failSetupStage(receipt, stageFailure(stage, error), nowIso(options));
|
|
127
|
+
const writeFailure = await writeReceipt(receipt, stage, options);
|
|
128
|
+
return writeFailure ?? { ok: false, error, receipt };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function setupFolder(
|
|
132
|
+
options: FolderSetupOptions
|
|
133
|
+
): Promise<FolderSetupResult> {
|
|
134
|
+
const resolved = await resolveSetupFolder(options.folder);
|
|
135
|
+
if ("error" in resolved) {
|
|
136
|
+
return { ok: false, error: resolved.error, receipt: null };
|
|
137
|
+
}
|
|
138
|
+
const folder = resolved.folder;
|
|
139
|
+
|
|
140
|
+
const storeIdentity = await resolveSetupStoreIndex({
|
|
141
|
+
store: options.store,
|
|
142
|
+
requestedIndexName: options.indexName,
|
|
143
|
+
});
|
|
144
|
+
if ("code" in storeIdentity) {
|
|
145
|
+
return { ok: false, error: storeIdentity, receipt: null };
|
|
146
|
+
}
|
|
147
|
+
const receiptPath = getSetupReceiptPath({
|
|
148
|
+
dataDir: options.dataDir,
|
|
149
|
+
indexName: storeIdentity.indexName,
|
|
150
|
+
folderRealpath: folder,
|
|
151
|
+
});
|
|
152
|
+
const configLockPath = `${options.configPath}.setup.lock`;
|
|
153
|
+
const unsafeOutput = await validateSetupOutputPaths(folder, [
|
|
154
|
+
{ label: "Data directory", path: options.dataDir },
|
|
155
|
+
{ label: "Setup receipt", path: receiptPath },
|
|
156
|
+
{ label: "Config", path: options.configPath },
|
|
157
|
+
{ label: "Config lock", path: configLockPath },
|
|
158
|
+
{ label: "Index database", path: storeIdentity.dbPath },
|
|
159
|
+
]);
|
|
160
|
+
if (unsafeOutput) {
|
|
161
|
+
return { ok: false, error: unsafeOutput, receipt: null };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const requestedExcludes = normalizeSetupExcludes(
|
|
165
|
+
options.exclude?.length ? options.exclude : DEFAULT_EXCLUDES
|
|
166
|
+
);
|
|
167
|
+
const loaded = await loadConfig(options.configPath);
|
|
168
|
+
let initialSelection: CollectionSelection | FolderSetupError | null = null;
|
|
169
|
+
let effectiveExcludes = requestedExcludes;
|
|
170
|
+
if (loaded.ok) {
|
|
171
|
+
initialSelection = await selectFolderCollection(
|
|
172
|
+
loaded.value,
|
|
173
|
+
folder,
|
|
174
|
+
options.name,
|
|
175
|
+
requestedExcludes
|
|
176
|
+
);
|
|
177
|
+
if (
|
|
178
|
+
!("code" in initialSelection) &&
|
|
179
|
+
initialSelection.disposition === "reused"
|
|
180
|
+
) {
|
|
181
|
+
if (
|
|
182
|
+
options.exclude !== undefined &&
|
|
183
|
+
!setupExcludesMatch(
|
|
184
|
+
requestedExcludes,
|
|
185
|
+
initialSelection.collection.exclude
|
|
186
|
+
)
|
|
187
|
+
) {
|
|
188
|
+
initialSelection = setupFilterDisagreement(initialSelection.collection);
|
|
189
|
+
} else {
|
|
190
|
+
effectiveExcludes = normalizeSetupExcludes(
|
|
191
|
+
initialSelection.collection.exclude
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const receipt = createSetupReceipt({
|
|
198
|
+
now: nowIso(options),
|
|
199
|
+
folder,
|
|
200
|
+
indexName: storeIdentity.indexName,
|
|
201
|
+
requestedName: options.name,
|
|
202
|
+
excludes: effectiveExcludes,
|
|
203
|
+
secretRiskAuthorized: options.secretRiskAuthorized ?? false,
|
|
204
|
+
configPath: options.configPath,
|
|
205
|
+
dataDir: options.dataDir,
|
|
206
|
+
});
|
|
207
|
+
startSetupStage(receipt, "preflight", nowIso(options));
|
|
208
|
+
let writeFailure = await writeReceipt(receipt, "preflight", options);
|
|
209
|
+
if (writeFailure) {
|
|
210
|
+
return writeFailure;
|
|
211
|
+
}
|
|
212
|
+
if (initialSelection && "code" in initialSelection) {
|
|
213
|
+
return persistFailure(receipt, "preflight", initialSelection, options);
|
|
214
|
+
}
|
|
215
|
+
const preflightError = await preflightFolder(
|
|
216
|
+
folder,
|
|
217
|
+
effectiveExcludes,
|
|
218
|
+
options.secretRiskAuthorized ?? false
|
|
219
|
+
);
|
|
220
|
+
if (preflightError) {
|
|
221
|
+
return persistFailure(receipt, "preflight", preflightError, options);
|
|
222
|
+
}
|
|
223
|
+
passSetupStage(receipt, "preflight", nowIso(options));
|
|
224
|
+
|
|
225
|
+
startSetupStage(receipt, "config_saved", nowIso(options));
|
|
226
|
+
writeFailure = await writeReceipt(receipt, "config_saved", options);
|
|
227
|
+
if (writeFailure) {
|
|
228
|
+
return writeFailure;
|
|
229
|
+
}
|
|
230
|
+
if (!loaded.ok) {
|
|
231
|
+
return persistFailure(
|
|
232
|
+
receipt,
|
|
233
|
+
"config_saved",
|
|
234
|
+
setupError(
|
|
235
|
+
"config_load_failed",
|
|
236
|
+
loaded.error.message,
|
|
237
|
+
"Initialize or repair the selected GNO config, then retry."
|
|
238
|
+
),
|
|
239
|
+
options
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
await options.beforeConfigBoundary?.();
|
|
244
|
+
let activeSelection: CollectionSelection | null = null;
|
|
245
|
+
let selectedCollection: Collection | undefined;
|
|
246
|
+
let activeConfig = loaded.value;
|
|
247
|
+
let boundaryError: FolderSetupError | null = null;
|
|
248
|
+
try {
|
|
249
|
+
const mutation = await applyConfigChange(
|
|
250
|
+
{
|
|
251
|
+
store: options.store,
|
|
252
|
+
configPath: options.configPath,
|
|
253
|
+
writeLockPath: configLockPath,
|
|
254
|
+
onConfigUpdated: (config) => {
|
|
255
|
+
activeConfig = config;
|
|
256
|
+
},
|
|
257
|
+
afterConfigSaved: async (config) => {
|
|
258
|
+
activeConfig = config;
|
|
259
|
+
const selected = activeSelection;
|
|
260
|
+
if (!selected) {
|
|
261
|
+
throw new Error("Setup collection selection was not established");
|
|
262
|
+
}
|
|
263
|
+
receipt.collection = {
|
|
264
|
+
name: selected.collection.name,
|
|
265
|
+
path: folder,
|
|
266
|
+
disposition: selected.disposition,
|
|
267
|
+
};
|
|
268
|
+
receipt.fingerprints.config = setupFingerprint({
|
|
269
|
+
version: config.version,
|
|
270
|
+
ftsTokenizer: config.ftsTokenizer,
|
|
271
|
+
collection: selected.collection,
|
|
272
|
+
});
|
|
273
|
+
passSetupStage(receipt, "config_saved", nowIso(options));
|
|
274
|
+
const configReceiptFailure = await writeReceipt(
|
|
275
|
+
receipt,
|
|
276
|
+
"config_saved",
|
|
277
|
+
options
|
|
278
|
+
);
|
|
279
|
+
if (configReceiptFailure) {
|
|
280
|
+
throw new SetupAbort(configReceiptFailure);
|
|
281
|
+
}
|
|
282
|
+
if (options.failureInjection === "after_config_save") {
|
|
283
|
+
throw new Error("INJECTED_AFTER_CONFIG_SAVE");
|
|
284
|
+
}
|
|
285
|
+
startSetupStage(receipt, "store_synced", nowIso(options));
|
|
286
|
+
const storeReceiptFailure = await writeReceipt(
|
|
287
|
+
receipt,
|
|
288
|
+
"store_synced",
|
|
289
|
+
options
|
|
290
|
+
);
|
|
291
|
+
if (storeReceiptFailure) {
|
|
292
|
+
throw new SetupAbort(storeReceiptFailure);
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
async (config) => {
|
|
297
|
+
const fresh = await selectFolderCollection(
|
|
298
|
+
config,
|
|
299
|
+
folder,
|
|
300
|
+
options.name,
|
|
301
|
+
effectiveExcludes
|
|
302
|
+
);
|
|
303
|
+
if ("code" in fresh) {
|
|
304
|
+
boundaryError = fresh;
|
|
305
|
+
return { ok: false, error: fresh.message, code: fresh.code };
|
|
306
|
+
}
|
|
307
|
+
if (
|
|
308
|
+
fresh.disposition === "reused" &&
|
|
309
|
+
!setupExcludesMatch(fresh.collection.exclude, effectiveExcludes)
|
|
310
|
+
) {
|
|
311
|
+
boundaryError = setupFilterDisagreement(fresh.collection);
|
|
312
|
+
return {
|
|
313
|
+
ok: false,
|
|
314
|
+
error: boundaryError.message,
|
|
315
|
+
code: boundaryError.code,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
activeSelection = fresh;
|
|
319
|
+
return {
|
|
320
|
+
ok: true,
|
|
321
|
+
config: fresh.config,
|
|
322
|
+
value: fresh.collection,
|
|
323
|
+
skipSave: fresh.disposition === "reused",
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
);
|
|
327
|
+
if (!mutation.ok) {
|
|
328
|
+
if (boundaryError) {
|
|
329
|
+
return persistFailure(receipt, "config_saved", boundaryError, options);
|
|
330
|
+
}
|
|
331
|
+
return persistFailure(
|
|
332
|
+
receipt,
|
|
333
|
+
receipt.stages.config_saved.status === "passed"
|
|
334
|
+
? "store_synced"
|
|
335
|
+
: "config_saved",
|
|
336
|
+
setupError(
|
|
337
|
+
mutation.code === "SYNC_ERROR"
|
|
338
|
+
? "store_sync_failed"
|
|
339
|
+
: "config_save_failed",
|
|
340
|
+
mutation.error,
|
|
341
|
+
"Fix config/data-directory permissions and rerun setup."
|
|
342
|
+
),
|
|
343
|
+
options
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
activeConfig = mutation.config;
|
|
347
|
+
selectedCollection = mutation.value;
|
|
348
|
+
} catch (error) {
|
|
349
|
+
if (error instanceof SetupAbort) {
|
|
350
|
+
return error.result;
|
|
351
|
+
}
|
|
352
|
+
if (
|
|
353
|
+
error instanceof Error &&
|
|
354
|
+
error.message === "INJECTED_AFTER_CONFIG_SAVE"
|
|
355
|
+
) {
|
|
356
|
+
return persistFailure(
|
|
357
|
+
receipt,
|
|
358
|
+
"config_saved",
|
|
359
|
+
setupInjectedFailure("after_config_save"),
|
|
360
|
+
options
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
return persistFailure(
|
|
364
|
+
receipt,
|
|
365
|
+
receipt.stages.config_saved.status === "passed"
|
|
366
|
+
? "store_synced"
|
|
367
|
+
: "config_saved",
|
|
368
|
+
setupError(
|
|
369
|
+
"config_save_failed",
|
|
370
|
+
error instanceof Error ? error.message : "Config mutation failed",
|
|
371
|
+
"Retry after the selected config write lock is available."
|
|
372
|
+
),
|
|
373
|
+
options
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const collection = selectedCollection;
|
|
378
|
+
if (!collection) {
|
|
379
|
+
return persistFailure(
|
|
380
|
+
receipt,
|
|
381
|
+
"store_synced",
|
|
382
|
+
setupError(
|
|
383
|
+
"store_sync_failed",
|
|
384
|
+
"Store projection completed without a selected collection",
|
|
385
|
+
"Retry setup against a healthy config and index store."
|
|
386
|
+
),
|
|
387
|
+
options
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
passSetupStage(receipt, "store_synced", nowIso(options));
|
|
391
|
+
writeFailure = await writeReceipt(receipt, "store_synced", options);
|
|
392
|
+
if (writeFailure) {
|
|
393
|
+
return writeFailure;
|
|
394
|
+
}
|
|
395
|
+
if (options.failureInjection === "after_store_sync") {
|
|
396
|
+
return persistFailure(
|
|
397
|
+
receipt,
|
|
398
|
+
"store_synced",
|
|
399
|
+
setupInjectedFailure("after_store_sync"),
|
|
400
|
+
options
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
startSetupStage(receipt, "lexical_indexed", nowIso(options));
|
|
405
|
+
writeFailure = await writeReceipt(receipt, "lexical_indexed", options);
|
|
406
|
+
if (writeFailure) {
|
|
407
|
+
return writeFailure;
|
|
408
|
+
}
|
|
409
|
+
const syncService = options.syncService ?? defaultSyncService;
|
|
410
|
+
const sync = await syncService.syncCollection(
|
|
411
|
+
collection,
|
|
412
|
+
options.store,
|
|
413
|
+
withContentTypeRules({ runUpdateCmd: false }, activeConfig)
|
|
414
|
+
);
|
|
415
|
+
const indexedCount =
|
|
416
|
+
sync.filesAdded + sync.filesUpdated + sync.filesUnchanged;
|
|
417
|
+
if (indexedCount === 0) {
|
|
418
|
+
return persistFailure(
|
|
419
|
+
receipt,
|
|
420
|
+
"lexical_indexed",
|
|
421
|
+
setupError(
|
|
422
|
+
"lexical_index_failed",
|
|
423
|
+
`No document reached the lexical index (${sync.filesErrored} errors, ${sync.filesSkipped} skipped)`,
|
|
424
|
+
"Inspect converter errors or add an indexable text document, then retry."
|
|
425
|
+
),
|
|
426
|
+
options
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
passSetupStage(receipt, "lexical_indexed", nowIso(options));
|
|
430
|
+
writeFailure = await writeReceipt(receipt, "lexical_indexed", options);
|
|
431
|
+
if (writeFailure) {
|
|
432
|
+
return writeFailure;
|
|
433
|
+
}
|
|
434
|
+
if (options.failureInjection === "after_lexical_index") {
|
|
435
|
+
return persistFailure(
|
|
436
|
+
receipt,
|
|
437
|
+
"lexical_indexed",
|
|
438
|
+
setupInjectedFailure("after_lexical_index"),
|
|
439
|
+
options
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
startSetupStage(receipt, "lexical_proved", nowIso(options));
|
|
444
|
+
writeFailure = await writeReceipt(receipt, "lexical_proved", options);
|
|
445
|
+
if (writeFailure) {
|
|
446
|
+
return writeFailure;
|
|
447
|
+
}
|
|
448
|
+
const proof = await verifyLexicalActivation(options.store, collection.name);
|
|
449
|
+
if (!proof.ok || !proof.value.ready) {
|
|
450
|
+
const message = proof.ok
|
|
451
|
+
? `Lexical activation was not proven (${proof.value.stages.lexical.code ?? proof.value.stages.index.code ?? "unknown"})`
|
|
452
|
+
: proof.error.message;
|
|
453
|
+
if (proof.ok) {
|
|
454
|
+
receipt.activation = proof.value;
|
|
455
|
+
receipt.fingerprints.index = proof.value.fingerprint;
|
|
456
|
+
}
|
|
457
|
+
return persistFailure(
|
|
458
|
+
receipt,
|
|
459
|
+
"lexical_proved",
|
|
460
|
+
setupError(
|
|
461
|
+
"lexical_proof_failed",
|
|
462
|
+
message,
|
|
463
|
+
"Fix the lexical corpus/index state, then rerun setup."
|
|
464
|
+
),
|
|
465
|
+
options
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
receipt.activation = proof.value;
|
|
469
|
+
receipt.fingerprints.index = proof.value.fingerprint;
|
|
470
|
+
passSetupStage(receipt, "lexical_proved", nowIso(options));
|
|
471
|
+
writeFailure = await writeReceipt(receipt, "lexical_proved", options);
|
|
472
|
+
if (writeFailure) {
|
|
473
|
+
return writeFailure;
|
|
474
|
+
}
|
|
475
|
+
if (options.failureInjection === "after_lexical_proof") {
|
|
476
|
+
return persistFailure(
|
|
477
|
+
receipt,
|
|
478
|
+
"lexical_proved",
|
|
479
|
+
setupInjectedFailure("after_lexical_proof"),
|
|
480
|
+
options
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
startSetupStage(receipt, "completed", nowIso(options));
|
|
485
|
+
passSetupStage(receipt, "completed", nowIso(options));
|
|
486
|
+
receipt.status = "completed";
|
|
487
|
+
receipt.pending = [];
|
|
488
|
+
writeFailure = await writeReceipt(receipt, "completed", options);
|
|
489
|
+
return writeFailure ?? { ok: true, receipt };
|
|
490
|
+
}
|