@gmickel/gno 1.12.4 → 1.13.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 +57 -30
- package/assets/skill/SKILL.md +5 -0
- package/assets/skill/cli-reference.md +16 -6
- package/assets/skill/mcp-reference.md +22 -3
- package/package.json +2 -1
- package/src/app/constants.ts +43 -10
- package/src/app/index-name.ts +127 -0
- package/src/cli/commands/doctor-activation.ts +151 -0
- package/src/cli/commands/doctor.ts +41 -16
- package/src/cli/commands/get.ts +18 -0
- package/src/cli/commands/mcp/atomic-config-write.ts +118 -0
- package/src/cli/commands/mcp/config-discovery.ts +42 -0
- package/src/cli/commands/mcp/config-editors.ts +432 -0
- package/src/cli/commands/mcp/config.ts +63 -160
- package/src/cli/commands/mcp/install.ts +75 -37
- package/src/cli/commands/mcp/paths.ts +141 -136
- package/src/cli/commands/mcp/server-entry.ts +66 -0
- package/src/cli/commands/mcp/status.ts +189 -57
- package/src/cli/commands/mcp/target-display.ts +30 -0
- package/src/cli/commands/mcp/uninstall.ts +29 -31
- package/src/cli/commands/mcp/yaml-config-editor.ts +257 -0
- package/src/cli/commands/mcp/yaml-layout-scanner.ts +447 -0
- package/src/cli/commands/multi-get.ts +31 -6
- package/src/cli/commands/status.ts +107 -11
- package/src/cli/program.ts +66 -20
- package/src/core/activation-connector-health.ts +19 -0
- package/src/core/activation-probe-plan.ts +321 -0
- package/src/core/activation-probe.ts +138 -0
- package/src/core/activation-receipt-store.ts +39 -0
- package/src/core/activation-status.ts +513 -0
- package/src/core/activation-verifier.ts +416 -0
- package/src/core/connector-environment.ts +68 -0
- package/src/core/connector-policy.ts +233 -0
- package/src/core/connector-verification-target.ts +150 -0
- package/src/core/connector-verifier.ts +497 -0
- package/src/core/indexed-reference.ts +33 -8
- package/src/core/runtime-entrypoint.ts +24 -0
- package/src/mcp/activation-verification-mode.ts +4 -0
- package/src/mcp/server.ts +9 -2
- package/src/sdk/client.ts +7 -0
- package/src/sdk/types.ts +1 -0
- package/src/serve/activation-health.ts +91 -0
- package/src/serve/background-runtime.ts +11 -1
- package/src/serve/connectors.ts +164 -19
- package/src/serve/public/components/BootstrapStatus.tsx +94 -1
- package/src/serve/public/components/FirstRunWizard.tsx +13 -51
- package/src/serve/public/components/HealthCenter.tsx +8 -2
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/pages/Connectors.tsx +216 -55
- package/src/serve/public/pages/Dashboard.tsx +1 -0
- package/src/serve/routes/api.ts +152 -8
- package/src/serve/server.ts +44 -9
- package/src/serve/status-model.ts +4 -0
- package/src/serve/status.ts +79 -35
- package/src/store/activation-receipts.ts +390 -0
- package/src/store/index.ts +8 -0
- package/src/store/migrations/012-activation-receipts.ts +38 -0
- package/src/store/migrations/013-fts-sync-marker.ts +39 -0
- package/src/store/migrations/index.ts +4 -0
- package/src/store/sqlite/adapter.ts +313 -53
- package/src/store/types.ts +118 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/** Shared, passive activation status for CLI, REST, and UI surfaces. */
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ActivationStageName,
|
|
5
|
+
ActivationStageReceipt,
|
|
6
|
+
ActivationVerificationCode,
|
|
7
|
+
ActivationVerificationReceipt,
|
|
8
|
+
StorePort,
|
|
9
|
+
StoreResult,
|
|
10
|
+
} from "../store/types";
|
|
11
|
+
import type { EphemeralActivationProbePlan } from "./activation-probe-plan";
|
|
12
|
+
import type {
|
|
13
|
+
ConnectorVerificationCode,
|
|
14
|
+
ConnectorVerificationTarget,
|
|
15
|
+
} from "./connector-verifier";
|
|
16
|
+
|
|
17
|
+
import { createEphemeralActivationProbePlan } from "./activation-probe-plan";
|
|
18
|
+
import { verifyLexicalActivation } from "./activation-verifier";
|
|
19
|
+
import {
|
|
20
|
+
getConnectorActivationReceiptLookup,
|
|
21
|
+
getConnectorVerificationRemediation,
|
|
22
|
+
} from "./connector-verifier";
|
|
23
|
+
|
|
24
|
+
const DEFAULT_CONCURRENCY = 4;
|
|
25
|
+
const MAX_CONNECTOR_TARGETS = 16;
|
|
26
|
+
const MAX_CONNECTOR_PROJECTIONS = 64;
|
|
27
|
+
const CONNECTOR_CODES = new Set<ActivationVerificationCode>([
|
|
28
|
+
"connector_not_configured",
|
|
29
|
+
"connector_probe_unavailable",
|
|
30
|
+
"connector_unsupported_config",
|
|
31
|
+
"connector_start_failed",
|
|
32
|
+
"connector_timeout",
|
|
33
|
+
"connector_missing_tools",
|
|
34
|
+
"connector_status_failed",
|
|
35
|
+
"connector_search_failed",
|
|
36
|
+
"connector_result_mismatch",
|
|
37
|
+
"target_runtime_unverifiable",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
export type SemanticAvailabilityCode =
|
|
41
|
+
| "models_missing"
|
|
42
|
+
| "embeddings_pending"
|
|
43
|
+
| "vector_unavailable"
|
|
44
|
+
| "semantic_not_checked";
|
|
45
|
+
|
|
46
|
+
export interface ActivationRemediation {
|
|
47
|
+
stage: ActivationStageName;
|
|
48
|
+
code: ActivationVerificationCode;
|
|
49
|
+
command: string;
|
|
50
|
+
message: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ActivationCollectionStatus {
|
|
54
|
+
collection: string;
|
|
55
|
+
ready: boolean;
|
|
56
|
+
generatedAt: string | null;
|
|
57
|
+
stages: Record<ActivationStageName, ActivationStageReceipt>;
|
|
58
|
+
semanticAvailability: {
|
|
59
|
+
status: "pending" | "skipped";
|
|
60
|
+
code: SemanticAvailabilityCode;
|
|
61
|
+
command: string;
|
|
62
|
+
};
|
|
63
|
+
remediation: ActivationRemediation | null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ActivationConnectorStatus {
|
|
67
|
+
collection: string;
|
|
68
|
+
target: string;
|
|
69
|
+
status: ActivationStageReceipt["status"];
|
|
70
|
+
code?: ActivationVerificationCode;
|
|
71
|
+
remediation: string | null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ActivationStatus {
|
|
75
|
+
schemaVersion: "1.0";
|
|
76
|
+
usable: boolean;
|
|
77
|
+
healthy: boolean;
|
|
78
|
+
collections: ActivationCollectionStatus[];
|
|
79
|
+
/** Only fingerprint-current persisted connector receipts may appear here. */
|
|
80
|
+
connectors: ActivationConnectorStatus[];
|
|
81
|
+
connectorProjection: {
|
|
82
|
+
total: number;
|
|
83
|
+
projected: number;
|
|
84
|
+
truncated: boolean;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ActivationStatusOptions {
|
|
89
|
+
concurrency?: number;
|
|
90
|
+
semantic?: {
|
|
91
|
+
modelsCached: boolean;
|
|
92
|
+
embeddingBacklog: number;
|
|
93
|
+
/** Omit when the passive caller has not initialized the vector runtime. */
|
|
94
|
+
vectorAvailable?: boolean;
|
|
95
|
+
};
|
|
96
|
+
/** Current target configs, inspected without starting their runtimes. */
|
|
97
|
+
connectorTargets?: readonly ConnectorVerificationTarget[];
|
|
98
|
+
/** Test seam. Production callers use the shipped local lexical verifier. */
|
|
99
|
+
verifyCollection?: (
|
|
100
|
+
store: StorePort,
|
|
101
|
+
collection: string,
|
|
102
|
+
plan?: EphemeralActivationProbePlan
|
|
103
|
+
) => Promise<StoreResult<ActivationVerificationReceipt>>;
|
|
104
|
+
/** Test seam for proving fingerprint-scoped coalescing with a stub verifier. */
|
|
105
|
+
prepareCollection?: (
|
|
106
|
+
store: StorePort,
|
|
107
|
+
collection: string
|
|
108
|
+
) => Promise<StoreResult<EphemeralActivationProbePlan>>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface VerifiedCollection {
|
|
112
|
+
projected: ActivationCollectionStatus;
|
|
113
|
+
receipt: ActivationVerificationReceipt | null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const inflightByStore = new WeakMap<
|
|
117
|
+
StorePort,
|
|
118
|
+
Map<string, Promise<StoreResult<ActivationVerificationReceipt>>>
|
|
119
|
+
>();
|
|
120
|
+
|
|
121
|
+
function pendingStage(
|
|
122
|
+
code: ActivationVerificationCode
|
|
123
|
+
): ActivationStageReceipt {
|
|
124
|
+
return {
|
|
125
|
+
status: "pending",
|
|
126
|
+
startedAt: null,
|
|
127
|
+
completedAt: null,
|
|
128
|
+
latencyMs: null,
|
|
129
|
+
code,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function failedStage(code: ActivationVerificationCode): ActivationStageReceipt {
|
|
134
|
+
return {
|
|
135
|
+
status: "failed",
|
|
136
|
+
startedAt: null,
|
|
137
|
+
completedAt: null,
|
|
138
|
+
latencyMs: null,
|
|
139
|
+
code,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function skippedStage(
|
|
144
|
+
code: ActivationVerificationCode
|
|
145
|
+
): ActivationStageReceipt {
|
|
146
|
+
return {
|
|
147
|
+
status: "skipped",
|
|
148
|
+
startedAt: null,
|
|
149
|
+
completedAt: null,
|
|
150
|
+
latencyMs: null,
|
|
151
|
+
code,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function failureReceipt(collection: string): ActivationCollectionStatus {
|
|
156
|
+
const code = "index_query_failed" as const;
|
|
157
|
+
return {
|
|
158
|
+
collection,
|
|
159
|
+
ready: false,
|
|
160
|
+
generatedAt: null,
|
|
161
|
+
stages: {
|
|
162
|
+
index: failedStage(code),
|
|
163
|
+
lexical: skippedStage(code),
|
|
164
|
+
semantic: pendingStage("semantic_not_checked"),
|
|
165
|
+
connector: skippedStage("connector_not_requested"),
|
|
166
|
+
},
|
|
167
|
+
semanticAvailability: {
|
|
168
|
+
status: "pending",
|
|
169
|
+
code: "semantic_not_checked",
|
|
170
|
+
command: "gno status",
|
|
171
|
+
},
|
|
172
|
+
remediation: remediationFor(collection, "index", code),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function remediationFor(
|
|
177
|
+
collection: string,
|
|
178
|
+
stage: ActivationStageName,
|
|
179
|
+
code: ActivationVerificationCode
|
|
180
|
+
): ActivationRemediation {
|
|
181
|
+
const command = `gno index ${collection} --no-embed`;
|
|
182
|
+
const messages: Partial<Record<ActivationVerificationCode, string>> = {
|
|
183
|
+
no_documents:
|
|
184
|
+
"Index at least one supported text document in this collection.",
|
|
185
|
+
no_probe_term:
|
|
186
|
+
"Add searchable text or adjust the collection filters, then reindex.",
|
|
187
|
+
index_query_failed:
|
|
188
|
+
"Repair the local index and rerun the collection-scoped lexical proof.",
|
|
189
|
+
index_out_of_sync:
|
|
190
|
+
"Rebuild this collection's lexical index so its FTS rows match the current mirrors.",
|
|
191
|
+
retrieval_mismatch:
|
|
192
|
+
"Rebuild the collection index so lexical results match the current source.",
|
|
193
|
+
};
|
|
194
|
+
return {
|
|
195
|
+
stage,
|
|
196
|
+
code,
|
|
197
|
+
command,
|
|
198
|
+
message:
|
|
199
|
+
messages[code] ??
|
|
200
|
+
"Repair the collection-scoped lexical proof, then check activation again.",
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function semanticAvailability(
|
|
205
|
+
options: ActivationStatusOptions["semantic"]
|
|
206
|
+
): ActivationCollectionStatus["semanticAvailability"] {
|
|
207
|
+
if (!options) {
|
|
208
|
+
return {
|
|
209
|
+
status: "pending",
|
|
210
|
+
code: "semantic_not_checked",
|
|
211
|
+
command: "gno status",
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (!options.modelsCached) {
|
|
215
|
+
return {
|
|
216
|
+
status: "pending",
|
|
217
|
+
code: "models_missing",
|
|
218
|
+
command: "gno models pull --embed",
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (options.embeddingBacklog > 0) {
|
|
222
|
+
return {
|
|
223
|
+
status: "pending",
|
|
224
|
+
code: "embeddings_pending",
|
|
225
|
+
command: "gno embed",
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
if (options.vectorAvailable === false) {
|
|
229
|
+
return {
|
|
230
|
+
status: "skipped",
|
|
231
|
+
code: "vector_unavailable",
|
|
232
|
+
command: "gno doctor",
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
status: "pending",
|
|
237
|
+
code: "semantic_not_checked",
|
|
238
|
+
command: "gno status",
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function projectReceipt(
|
|
243
|
+
receipt: ActivationVerificationReceipt,
|
|
244
|
+
semantic: ActivationCollectionStatus["semanticAvailability"]
|
|
245
|
+
): ActivationCollectionStatus {
|
|
246
|
+
const failedStageEntry = (["index", "lexical"] as const).find(
|
|
247
|
+
(stage) => receipt.stages[stage].status !== "passed"
|
|
248
|
+
);
|
|
249
|
+
const failedStageReceipt = failedStageEntry
|
|
250
|
+
? receipt.stages[failedStageEntry]
|
|
251
|
+
: undefined;
|
|
252
|
+
return {
|
|
253
|
+
collection: receipt.collection,
|
|
254
|
+
ready: receipt.ready,
|
|
255
|
+
generatedAt: receipt.generatedAt,
|
|
256
|
+
stages: receipt.stages,
|
|
257
|
+
semanticAvailability: semantic,
|
|
258
|
+
remediation:
|
|
259
|
+
failedStageEntry && failedStageReceipt?.code
|
|
260
|
+
? remediationFor(
|
|
261
|
+
receipt.collection,
|
|
262
|
+
failedStageEntry,
|
|
263
|
+
failedStageReceipt.code
|
|
264
|
+
)
|
|
265
|
+
: null,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function connectorFallback(
|
|
270
|
+
collection: string,
|
|
271
|
+
target: ConnectorVerificationTarget,
|
|
272
|
+
lexicalReady: boolean
|
|
273
|
+
): ActivationConnectorStatus {
|
|
274
|
+
let status: ActivationStageReceipt["status"] = "pending";
|
|
275
|
+
let code: ActivationVerificationCode = "connector_not_requested";
|
|
276
|
+
if (!lexicalReady) {
|
|
277
|
+
status = "skipped";
|
|
278
|
+
code = "connector_probe_unavailable";
|
|
279
|
+
} else if (target.configError) {
|
|
280
|
+
status = "failed";
|
|
281
|
+
code = "connector_unsupported_config";
|
|
282
|
+
} else if (target.kind === "skill") {
|
|
283
|
+
status = "skipped";
|
|
284
|
+
code = target.installed
|
|
285
|
+
? "target_runtime_unverifiable"
|
|
286
|
+
: "connector_not_configured";
|
|
287
|
+
} else if (!target.configured) {
|
|
288
|
+
status = target.configError ? "failed" : "skipped";
|
|
289
|
+
code = target.configError
|
|
290
|
+
? "connector_unsupported_config"
|
|
291
|
+
: "connector_not_configured";
|
|
292
|
+
}
|
|
293
|
+
const remediation =
|
|
294
|
+
code === "connector_not_requested"
|
|
295
|
+
? `Run explicit read-only verification for ${target.id} from Connectors.`
|
|
296
|
+
: connectorRemediation(code, target.id);
|
|
297
|
+
return {
|
|
298
|
+
collection,
|
|
299
|
+
target: target.id,
|
|
300
|
+
status,
|
|
301
|
+
code,
|
|
302
|
+
remediation,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function connectorRemediation(
|
|
307
|
+
code: ActivationVerificationCode,
|
|
308
|
+
target: string
|
|
309
|
+
): string {
|
|
310
|
+
return CONNECTOR_CODES.has(code)
|
|
311
|
+
? getConnectorVerificationRemediation(
|
|
312
|
+
code as ConnectorVerificationCode,
|
|
313
|
+
target
|
|
314
|
+
)
|
|
315
|
+
: `Repeat explicit read-only verification for ${target}.`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function buildConnectorStatuses(
|
|
319
|
+
store: StorePort,
|
|
320
|
+
collections: VerifiedCollection[],
|
|
321
|
+
targets: readonly ConnectorVerificationTarget[]
|
|
322
|
+
): Promise<{ items: ActivationConnectorStatus[]; total: number }> {
|
|
323
|
+
const sortedTargets = [...targets].sort((a, b) => a.id.localeCompare(b.id));
|
|
324
|
+
const boundedTargets = sortedTargets.slice(0, MAX_CONNECTOR_TARGETS);
|
|
325
|
+
const allPairs = collections.flatMap((collection) =>
|
|
326
|
+
boundedTargets.map((target) => ({ collection, target }))
|
|
327
|
+
);
|
|
328
|
+
const items = await mapBounded(
|
|
329
|
+
allPairs.slice(0, MAX_CONNECTOR_PROJECTIONS),
|
|
330
|
+
DEFAULT_CONCURRENCY,
|
|
331
|
+
async ({ collection, target }) => {
|
|
332
|
+
const fallback = connectorFallback(
|
|
333
|
+
collection.projected.collection,
|
|
334
|
+
target,
|
|
335
|
+
collection.projected.ready
|
|
336
|
+
);
|
|
337
|
+
if (!collection.receipt || fallback.code !== "connector_not_requested") {
|
|
338
|
+
return fallback;
|
|
339
|
+
}
|
|
340
|
+
const lookup = getConnectorActivationReceiptLookup(
|
|
341
|
+
collection.receipt.fingerprint,
|
|
342
|
+
target
|
|
343
|
+
);
|
|
344
|
+
const cached = await store.getActivationReceipt(
|
|
345
|
+
collection.projected.collection,
|
|
346
|
+
lookup.fingerprint,
|
|
347
|
+
lookup.connectorTarget
|
|
348
|
+
);
|
|
349
|
+
if (!cached.ok || !cached.value) {
|
|
350
|
+
return fallback;
|
|
351
|
+
}
|
|
352
|
+
const stage = cached.value.stages.connector;
|
|
353
|
+
const code = stage.code;
|
|
354
|
+
return {
|
|
355
|
+
collection: collection.projected.collection,
|
|
356
|
+
target: target.id,
|
|
357
|
+
status: stage.status,
|
|
358
|
+
...(code ? { code } : {}),
|
|
359
|
+
remediation:
|
|
360
|
+
code && code !== "connector_not_requested"
|
|
361
|
+
? connectorRemediation(code, target.id)
|
|
362
|
+
: null,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
return { items, total: collections.length * sortedTargets.length };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function verifyCoalesced(
|
|
370
|
+
store: StorePort,
|
|
371
|
+
collection: string,
|
|
372
|
+
fingerprint: string,
|
|
373
|
+
verifyCollection: NonNullable<ActivationStatusOptions["verifyCollection"]>
|
|
374
|
+
): Promise<StoreResult<ActivationVerificationReceipt>> {
|
|
375
|
+
let storeInflight = inflightByStore.get(store);
|
|
376
|
+
if (!storeInflight) {
|
|
377
|
+
storeInflight = new Map();
|
|
378
|
+
inflightByStore.set(store, storeInflight);
|
|
379
|
+
}
|
|
380
|
+
const key = `${collection}\0${fingerprint}`;
|
|
381
|
+
const existing = storeInflight.get(key);
|
|
382
|
+
if (existing) {
|
|
383
|
+
return existing;
|
|
384
|
+
}
|
|
385
|
+
const verification = verifyCollection(store, collection);
|
|
386
|
+
storeInflight.set(key, verification);
|
|
387
|
+
try {
|
|
388
|
+
return await verification;
|
|
389
|
+
} finally {
|
|
390
|
+
if (storeInflight.get(key) === verification) {
|
|
391
|
+
storeInflight.delete(key);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async function mapBounded<T, R>(
|
|
397
|
+
values: T[],
|
|
398
|
+
concurrency: number,
|
|
399
|
+
mapper: (value: T) => Promise<R>
|
|
400
|
+
): Promise<R[]> {
|
|
401
|
+
const results: R[] = [];
|
|
402
|
+
results.length = values.length;
|
|
403
|
+
let cursor = 0;
|
|
404
|
+
const workers = Array.from(
|
|
405
|
+
{ length: Math.min(concurrency, values.length) },
|
|
406
|
+
async () => {
|
|
407
|
+
while (cursor < values.length) {
|
|
408
|
+
const index = cursor;
|
|
409
|
+
cursor += 1;
|
|
410
|
+
const value = values[index];
|
|
411
|
+
if (value !== undefined) {
|
|
412
|
+
results[index] = await mapper(value);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
await Promise.all(workers);
|
|
418
|
+
return results;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Build deterministic activation status without loading models, starting a
|
|
423
|
+
* connector, making a remote call, or retaining a settled cache.
|
|
424
|
+
*/
|
|
425
|
+
export async function buildActivationStatus(
|
|
426
|
+
store: StorePort,
|
|
427
|
+
configuredCollections: readonly string[],
|
|
428
|
+
options: ActivationStatusOptions = {}
|
|
429
|
+
): Promise<ActivationStatus> {
|
|
430
|
+
const collections = [...new Set(configuredCollections)].sort((a, b) =>
|
|
431
|
+
a.localeCompare(b)
|
|
432
|
+
);
|
|
433
|
+
const verifyCollection = options.verifyCollection;
|
|
434
|
+
const prepareCollection =
|
|
435
|
+
options.prepareCollection ??
|
|
436
|
+
(verifyCollection
|
|
437
|
+
? null
|
|
438
|
+
: (targetStore: StorePort, collection: string) =>
|
|
439
|
+
createEphemeralActivationProbePlan(targetStore, collection, {
|
|
440
|
+
collectCandidates: false,
|
|
441
|
+
}));
|
|
442
|
+
const semantic = semanticAvailability(options.semantic);
|
|
443
|
+
const verified = await mapBounded(
|
|
444
|
+
collections,
|
|
445
|
+
Math.max(1, Math.floor(options.concurrency ?? DEFAULT_CONCURRENCY)),
|
|
446
|
+
async (collection) => {
|
|
447
|
+
try {
|
|
448
|
+
if (!prepareCollection && verifyCollection) {
|
|
449
|
+
const receipt = await verifyCoalesced(
|
|
450
|
+
store,
|
|
451
|
+
collection,
|
|
452
|
+
collection,
|
|
453
|
+
verifyCollection
|
|
454
|
+
);
|
|
455
|
+
return receipt.ok
|
|
456
|
+
? {
|
|
457
|
+
projected: projectReceipt(receipt.value, semantic),
|
|
458
|
+
receipt: receipt.value,
|
|
459
|
+
}
|
|
460
|
+
: { projected: failureReceipt(collection), receipt: null };
|
|
461
|
+
}
|
|
462
|
+
if (!prepareCollection) {
|
|
463
|
+
return { projected: failureReceipt(collection), receipt: null };
|
|
464
|
+
}
|
|
465
|
+
const prepared = await prepareCollection(store, collection);
|
|
466
|
+
if (!prepared.ok) {
|
|
467
|
+
return { projected: failureReceipt(collection), receipt: null };
|
|
468
|
+
}
|
|
469
|
+
const runVerification = verifyCollection
|
|
470
|
+
? (targetStore: StorePort, targetCollection: string) =>
|
|
471
|
+
verifyCollection(targetStore, targetCollection, prepared.value)
|
|
472
|
+
: (targetStore: StorePort, targetCollection: string) =>
|
|
473
|
+
verifyLexicalActivation(targetStore, targetCollection, {
|
|
474
|
+
plan: prepared.value,
|
|
475
|
+
});
|
|
476
|
+
const receipt = await verifyCoalesced(
|
|
477
|
+
store,
|
|
478
|
+
collection,
|
|
479
|
+
prepared.value.fingerprint,
|
|
480
|
+
runVerification
|
|
481
|
+
);
|
|
482
|
+
return receipt.ok
|
|
483
|
+
? {
|
|
484
|
+
projected: projectReceipt(receipt.value, semantic),
|
|
485
|
+
receipt: receipt.value,
|
|
486
|
+
}
|
|
487
|
+
: { projected: failureReceipt(collection), receipt: null };
|
|
488
|
+
} catch {
|
|
489
|
+
return { projected: failureReceipt(collection), receipt: null };
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
);
|
|
493
|
+
const projected = verified.map(({ projected: collection }) => collection);
|
|
494
|
+
const connectorStatuses = await buildConnectorStatuses(
|
|
495
|
+
store,
|
|
496
|
+
verified,
|
|
497
|
+
options.connectorTargets ?? []
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
return {
|
|
501
|
+
schemaVersion: "1.0",
|
|
502
|
+
usable: projected.some((collection) => collection.ready),
|
|
503
|
+
healthy:
|
|
504
|
+
projected.length > 0 && projected.every((collection) => collection.ready),
|
|
505
|
+
collections: projected,
|
|
506
|
+
connectors: connectorStatuses.items,
|
|
507
|
+
connectorProjection: {
|
|
508
|
+
total: connectorStatuses.total,
|
|
509
|
+
projected: connectorStatuses.items.length,
|
|
510
|
+
truncated: connectorStatuses.total > connectorStatuses.items.length,
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
}
|