@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.
Files changed (61) hide show
  1. package/README.md +57 -30
  2. package/assets/skill/SKILL.md +5 -0
  3. package/assets/skill/cli-reference.md +16 -6
  4. package/assets/skill/mcp-reference.md +22 -3
  5. package/package.json +2 -1
  6. package/src/app/constants.ts +43 -10
  7. package/src/app/index-name.ts +127 -0
  8. package/src/cli/commands/doctor-activation.ts +151 -0
  9. package/src/cli/commands/doctor.ts +41 -16
  10. package/src/cli/commands/get.ts +18 -0
  11. package/src/cli/commands/mcp/atomic-config-write.ts +118 -0
  12. package/src/cli/commands/mcp/config-discovery.ts +42 -0
  13. package/src/cli/commands/mcp/config-editors.ts +432 -0
  14. package/src/cli/commands/mcp/config.ts +63 -160
  15. package/src/cli/commands/mcp/install.ts +75 -37
  16. package/src/cli/commands/mcp/paths.ts +141 -136
  17. package/src/cli/commands/mcp/server-entry.ts +66 -0
  18. package/src/cli/commands/mcp/status.ts +189 -57
  19. package/src/cli/commands/mcp/target-display.ts +30 -0
  20. package/src/cli/commands/mcp/uninstall.ts +29 -31
  21. package/src/cli/commands/mcp/yaml-config-editor.ts +257 -0
  22. package/src/cli/commands/mcp/yaml-layout-scanner.ts +447 -0
  23. package/src/cli/commands/multi-get.ts +31 -6
  24. package/src/cli/commands/status.ts +107 -11
  25. package/src/cli/program.ts +66 -20
  26. package/src/core/activation-connector-health.ts +19 -0
  27. package/src/core/activation-probe-plan.ts +321 -0
  28. package/src/core/activation-probe.ts +138 -0
  29. package/src/core/activation-receipt-store.ts +39 -0
  30. package/src/core/activation-status.ts +513 -0
  31. package/src/core/activation-verifier.ts +416 -0
  32. package/src/core/connector-environment.ts +68 -0
  33. package/src/core/connector-policy.ts +233 -0
  34. package/src/core/connector-verification-target.ts +150 -0
  35. package/src/core/connector-verifier.ts +497 -0
  36. package/src/core/indexed-reference.ts +33 -8
  37. package/src/core/runtime-entrypoint.ts +24 -0
  38. package/src/mcp/activation-verification-mode.ts +4 -0
  39. package/src/mcp/server.ts +9 -2
  40. package/src/sdk/client.ts +7 -0
  41. package/src/sdk/types.ts +1 -0
  42. package/src/serve/activation-health.ts +91 -0
  43. package/src/serve/background-runtime.ts +11 -1
  44. package/src/serve/connectors.ts +164 -19
  45. package/src/serve/public/components/BootstrapStatus.tsx +94 -1
  46. package/src/serve/public/components/FirstRunWizard.tsx +13 -51
  47. package/src/serve/public/components/HealthCenter.tsx +8 -2
  48. package/src/serve/public/globals.built.css +1 -1
  49. package/src/serve/public/pages/Connectors.tsx +216 -55
  50. package/src/serve/public/pages/Dashboard.tsx +1 -0
  51. package/src/serve/routes/api.ts +152 -8
  52. package/src/serve/server.ts +44 -9
  53. package/src/serve/status-model.ts +4 -0
  54. package/src/serve/status.ts +79 -35
  55. package/src/store/activation-receipts.ts +390 -0
  56. package/src/store/index.ts +8 -0
  57. package/src/store/migrations/012-activation-receipts.ts +38 -0
  58. package/src/store/migrations/013-fts-sync-marker.ts +39 -0
  59. package/src/store/migrations/index.ts +4 -0
  60. package/src/store/sqlite/adapter.ts +313 -53
  61. package/src/store/types.ts +118 -0
@@ -11,6 +11,12 @@ import type { DocumentRow, StorePort, StoreResult } from "../../store/types";
11
11
  import type { ParsedRef } from "./ref-parser";
12
12
 
13
13
  import { decorateUriForIndex, parseUri } from "../../app/constants";
14
+ import {
15
+ canonicalizeIndexName,
16
+ INDEX_NAME_REQUIREMENTS,
17
+ indexNamesMatch,
18
+ isValidIndexName,
19
+ } from "../../app/index-name";
14
20
  import { isGlobPattern, parseRef, splitRefs } from "./ref-parser";
15
21
  import { initStore } from "./shared";
16
22
 
@@ -319,7 +325,13 @@ function resolveMultiGetIndex(
319
325
  refs: string[],
320
326
  globalIndexName?: string
321
327
  ): { ok: true; indexName?: string } | { ok: false; error: string } {
322
- const explicitIndexes = new Set<string>();
328
+ if (globalIndexName !== undefined && !isValidIndexName(globalIndexName)) {
329
+ return {
330
+ ok: false,
331
+ error: `Invalid index name: ${INDEX_NAME_REQUIREMENTS}.`,
332
+ };
333
+ }
334
+ const explicitIndexes = new Map<string, string>();
323
335
  let hasUnindexedRef = false;
324
336
 
325
337
  for (const ref of refs) {
@@ -329,8 +341,17 @@ function resolveMultiGetIndex(
329
341
  continue;
330
342
  }
331
343
  const indexName = parseUri(parsed.value)?.indexName;
332
- if (indexName) {
333
- explicitIndexes.add(indexName);
344
+ if (indexName !== undefined) {
345
+ if (!isValidIndexName(indexName)) {
346
+ return {
347
+ ok: false,
348
+ error: `Invalid index name: ${INDEX_NAME_REQUIREMENTS}.`,
349
+ };
350
+ }
351
+ const identity = canonicalizeIndexName(indexName);
352
+ if (!explicitIndexes.has(identity)) {
353
+ explicitIndexes.set(identity, indexName);
354
+ }
334
355
  } else {
335
356
  hasUnindexedRef = true;
336
357
  }
@@ -342,16 +363,20 @@ function resolveMultiGetIndex(
342
363
  if (explicitIndexes.size > 1) {
343
364
  return {
344
365
  ok: false,
345
- error: `multi-get cannot mix explicit indexes: ${[...explicitIndexes].sort().join(", ")}`,
366
+ error: `multi-get cannot mix explicit indexes: ${[
367
+ ...explicitIndexes.values(),
368
+ ]
369
+ .sort()
370
+ .join(", ")}`,
346
371
  };
347
372
  }
348
373
 
349
- const explicitIndex = [...explicitIndexes][0];
374
+ const explicitIndex = [...explicitIndexes.values()][0];
350
375
  if (
351
376
  hasUnindexedRef &&
352
377
  globalIndexName &&
353
378
  explicitIndex &&
354
- globalIndexName !== explicitIndex
379
+ !indexNamesMatch(globalIndexName, explicitIndex)
355
380
  ) {
356
381
  return {
357
382
  ok: false,
@@ -5,11 +5,16 @@
5
5
  * @module src/cli/commands/status
6
6
  */
7
7
 
8
+ import type { ActivationStatus } from "../../core/activation-status";
8
9
  import type { IndexStatus } from "../../store/types";
9
10
 
10
- import { getIndexDbPath } from "../../app/constants";
11
+ import { getIndexDbPath, getModelsCachePath } from "../../app/constants";
11
12
  import { getConfigPaths, isInitialized, loadConfig } from "../../config";
12
- import { resolveModelUri } from "../../llm/registry";
13
+ import { isConnectorActivationComplete } from "../../core/activation-connector-health";
14
+ import { buildActivationStatus } from "../../core/activation-status";
15
+ import { ModelCache } from "../../llm/cache";
16
+ import { getActivePreset, resolveModelUri } from "../../llm/registry";
17
+ import { getConnectorVerificationTargets } from "../../serve/connectors";
13
18
  import { SqliteAdapter } from "../../store/sqlite/adapter";
14
19
 
15
20
  /**
@@ -30,13 +35,35 @@ export interface StatusOptions {
30
35
  * Result of status command.
31
36
  */
32
37
  export type StatusResult =
33
- | { success: true; status: IndexStatus }
38
+ | { success: true; status: IndexStatus; activation: ActivationStatus }
34
39
  | { success: false; error: string };
35
40
 
41
+ function connectorProjectionLine(activation: ActivationStatus): string | null {
42
+ const { projected, total, truncated } = activation.connectorProjection;
43
+ if (!truncated) {
44
+ return null;
45
+ }
46
+ return `Connector projection: ${projected}/${total} target/collection checks shown; ${total - projected} omitted`;
47
+ }
48
+
49
+ function isStatusHealthy(
50
+ indexStatus: IndexStatus,
51
+ activation: ActivationStatus
52
+ ): boolean {
53
+ return (
54
+ indexStatus.healthy &&
55
+ activation.healthy &&
56
+ isConnectorActivationComplete(activation)
57
+ );
58
+ }
59
+
36
60
  /**
37
61
  * Format status as terminal output.
38
62
  */
39
- function formatTerminal(indexStatus: IndexStatus): string {
63
+ function formatTerminal(
64
+ indexStatus: IndexStatus,
65
+ activation: ActivationStatus
66
+ ): string {
40
67
  const lines: string[] = [];
41
68
 
42
69
  lines.push(`Index: ${indexStatus.indexName}`);
@@ -73,7 +100,34 @@ function formatTerminal(indexStatus: IndexStatus): string {
73
100
  lines.push(`Last updated: ${indexStatus.lastUpdatedAt}`);
74
101
  }
75
102
 
76
- lines.push(`Health: ${indexStatus.healthy ? "OK" : "DEGRADED"}`);
103
+ lines.push(
104
+ `Health: ${isStatusHealthy(indexStatus, activation) ? "OK" : "DEGRADED"}`
105
+ );
106
+ lines.push("");
107
+ lines.push(
108
+ `Lexical activation: ${activation.healthy ? "READY" : activation.usable ? "DEGRADED" : "BLOCKED"}`
109
+ );
110
+ for (const collection of activation.collections) {
111
+ const failedStage = collection.remediation?.stage;
112
+ const suffix = failedStage
113
+ ? ` (${failedStage}: ${collection.remediation?.code}; ${collection.remediation?.command})`
114
+ : ` (semantic: ${collection.semanticAvailability.code})`;
115
+ lines.push(
116
+ ` ${collection.collection}: ${collection.ready ? "lexical ready" : "not ready"}${suffix}`
117
+ );
118
+ }
119
+ if (activation.connectors.length > 0) {
120
+ lines.push("Connector proofs:");
121
+ for (const connector of activation.connectors) {
122
+ lines.push(
123
+ ` ${connector.target}/${connector.collection}: ${connector.status}${connector.code ? ` (${connector.code})` : ""}`
124
+ );
125
+ }
126
+ }
127
+ const projectionLine = connectorProjectionLine(activation);
128
+ if (projectionLine) {
129
+ lines.push(projectionLine);
130
+ }
77
131
 
78
132
  return lines.join("\n");
79
133
  }
@@ -81,14 +135,19 @@ function formatTerminal(indexStatus: IndexStatus): string {
81
135
  /**
82
136
  * Format status as Markdown.
83
137
  */
84
- function formatMarkdown(indexStatus: IndexStatus): string {
138
+ function formatMarkdown(
139
+ indexStatus: IndexStatus,
140
+ activation: ActivationStatus
141
+ ): string {
85
142
  const lines: string[] = [];
86
143
 
87
144
  lines.push(`# Index Status: ${indexStatus.indexName}`);
88
145
  lines.push("");
89
146
  lines.push(`- **Config**: ${indexStatus.configPath}`);
90
147
  lines.push(`- **Database**: ${indexStatus.dbPath}`);
91
- lines.push(`- **Health**: ${indexStatus.healthy ? "✓ OK" : "⚠ DEGRADED"}`);
148
+ lines.push(
149
+ `- **Health**: ${isStatusHealthy(indexStatus, activation) ? "✓ OK" : "⚠ DEGRADED"}`
150
+ );
92
151
  lines.push("");
93
152
 
94
153
  if (indexStatus.collections.length > 0) {
@@ -115,6 +174,26 @@ function formatMarkdown(indexStatus: IndexStatus): string {
115
174
  lines.push(`- **Last updated**: ${indexStatus.lastUpdatedAt}`);
116
175
  }
117
176
 
177
+ lines.push("");
178
+ lines.push("## Lexical activation");
179
+ lines.push("");
180
+ lines.push(`- **Usable**: ${activation.usable}`);
181
+ lines.push(`- **Lexically healthy**: ${activation.healthy}`);
182
+ for (const collection of activation.collections) {
183
+ lines.push(
184
+ `- **${collection.collection}**: ${collection.ready ? "lexical ready" : `${collection.remediation?.stage ?? "index"} ${collection.remediation?.code ?? "index_query_failed"}`}`
185
+ );
186
+ }
187
+ for (const connector of activation.connectors) {
188
+ lines.push(
189
+ `- **${connector.target}/${connector.collection}**: ${connector.status}${connector.code ? ` (${connector.code})` : ""}`
190
+ );
191
+ }
192
+ const projectionLine = connectorProjectionLine(activation);
193
+ if (projectionLine) {
194
+ lines.push(`- **${projectionLine}**`);
195
+ }
196
+
118
197
  return lines.join("\n");
119
198
  }
120
199
 
@@ -158,7 +237,23 @@ export async function status(
158
237
  return { success: false, error: statusResult.error.message };
159
238
  }
160
239
 
161
- return { success: true, status: statusResult.value };
240
+ const preset = getActivePreset(config);
241
+ const embedModelCached = await new ModelCache(
242
+ getModelsCachePath()
243
+ ).isCached(preset.embed);
244
+ const activation = await buildActivationStatus(
245
+ store,
246
+ config.collections.map(({ name }) => name),
247
+ {
248
+ semantic: {
249
+ modelsCached: embedModelCached,
250
+ embeddingBacklog: statusResult.value.embeddingBacklog,
251
+ },
252
+ connectorTargets: await getConnectorVerificationTargets(),
253
+ }
254
+ );
255
+
256
+ return { success: true, status: statusResult.value, activation };
162
257
  } finally {
163
258
  await store.close();
164
259
  }
@@ -196,7 +291,8 @@ export function formatStatus(
196
291
  totalChunks: s.totalChunks,
197
292
  embeddingBacklog: s.embeddingBacklog,
198
293
  lastUpdated: s.lastUpdatedAt,
199
- healthy: s.healthy,
294
+ healthy: isStatusHealthy(s, result.activation),
295
+ activation: result.activation,
200
296
  },
201
297
  null,
202
298
  2
@@ -204,8 +300,8 @@ export function formatStatus(
204
300
  }
205
301
 
206
302
  if (options.md) {
207
- return formatMarkdown(result.status);
303
+ return formatMarkdown(result.status, result.activation);
208
304
  }
209
305
 
210
- return formatTerminal(result.status);
306
+ return formatTerminal(result.status, result.activation);
211
307
  }
@@ -18,6 +18,7 @@ import {
18
18
  PRODUCT_NAME,
19
19
  VERSION,
20
20
  } from "../app/constants";
21
+ import { INDEX_NAME_REQUIREMENTS, isValidIndexName } from "../app/index-name";
21
22
  import { resolveDepthPolicy } from "../core/depth-policy";
22
23
  import { parseAndValidateTagFilter } from "../core/tags";
23
24
  import { setColorsEnabled } from "./colors";
@@ -240,6 +241,12 @@ export function createProgram(): Command {
240
241
  program.hook("preAction", (thisCommand) => {
241
242
  const rootOpts = thisCommand.optsWithGlobals();
242
243
  const globals = parseGlobalOptions(rootOpts);
244
+ if (!isValidIndexName(globals.index)) {
245
+ throw new CliError(
246
+ "VALIDATION",
247
+ `Invalid index name: ${INDEX_NAME_REQUIREMENTS}.`
248
+ );
249
+ }
243
250
  applyGlobalOptions(globals);
244
251
  globalState.current = globals;
245
252
  });
@@ -1045,13 +1052,23 @@ function wireOnboardingCommands(program: Command): void {
1045
1052
  .option("--json", "JSON output")
1046
1053
  .action(async (cmdOpts: Record<string, unknown>) => {
1047
1054
  const format = getFormat(cmdOpts);
1048
- const { doctor, formatDoctor } = await import("./commands/doctor");
1049
- const result = await doctor({ json: format === "json" });
1055
+ const { doctor, formatDoctor, hasCriticalDoctorErrors } =
1056
+ await import("./commands/doctor");
1057
+ const globals = getGlobals();
1058
+ const result = await doctor({
1059
+ configPath: globals.config,
1060
+ indexName: globals.index,
1061
+ json: format === "json",
1062
+ });
1050
1063
 
1051
- // Doctor always succeeds but may report issues
1052
1064
  process.stdout.write(
1053
1065
  `${formatDoctor(result, { json: format === "json" })}\n`
1054
1066
  );
1067
+ if (hasCriticalDoctorErrors(result.checks)) {
1068
+ throw new CliError("RUNTIME", "Critical health checks failed", {
1069
+ silent: true,
1070
+ });
1071
+ }
1055
1072
  });
1056
1073
  }
1057
1074
 
@@ -1308,8 +1325,7 @@ function wireMcpCommand(program: Command): void {
1308
1325
  )
1309
1326
  .option(
1310
1327
  "-s, --scope <scope>",
1311
- "scope (user, project) - project only for claude-code/codex/cursor/opencode",
1312
- "user"
1328
+ "scope (user, project) - defaults to project for LibreChat and user otherwise"
1313
1329
  )
1314
1330
  .option("-f, --force", "overwrite existing configuration")
1315
1331
  .option("--dry-run", "show what would be done without making changes")
@@ -1320,10 +1336,11 @@ function wireMcpCommand(program: Command): void {
1320
1336
  .option("--json", "JSON output")
1321
1337
  .action(async (cmdOpts: Record<string, unknown>) => {
1322
1338
  const target = cmdOpts.target as string;
1323
- const scope = cmdOpts.scope as string;
1339
+ const requestedScope = cmdOpts.scope;
1324
1340
 
1325
1341
  // Import MCP_TARGETS for validation
1326
- const { MCP_TARGETS } = await import("./commands/mcp/paths.js");
1342
+ const { getDefaultTargetScope, MCP_TARGETS } =
1343
+ await import("./commands/mcp/paths.js");
1327
1344
 
1328
1345
  // Validate target
1329
1346
  if (!(MCP_TARGETS as string[]).includes(target)) {
@@ -1332,15 +1349,26 @@ function wireMcpCommand(program: Command): void {
1332
1349
  `Invalid target: ${target}. Must be one of: ${MCP_TARGETS.join(", ")}.`
1333
1350
  );
1334
1351
  }
1335
- // Validate scope
1336
- if (!["user", "project"].includes(scope)) {
1352
+ // Validate an explicit scope, then let the target choose its default.
1353
+ if (
1354
+ requestedScope !== undefined &&
1355
+ (typeof requestedScope !== "string" ||
1356
+ !["user", "project"].includes(requestedScope))
1357
+ ) {
1337
1358
  throw new CliError(
1338
1359
  "VALIDATION",
1339
- `Invalid scope: ${scope}. Must be 'user' or 'project'.`
1360
+ `Invalid scope: ${JSON.stringify(requestedScope)}. Must be 'user' or 'project'.`
1340
1361
  );
1341
1362
  }
1363
+ const scope =
1364
+ typeof requestedScope === "string"
1365
+ ? requestedScope
1366
+ : getDefaultTargetScope(
1367
+ target as Parameters<typeof getDefaultTargetScope>[0]
1368
+ );
1342
1369
 
1343
1370
  const { installMcp } = await import("./commands/mcp/install.js");
1371
+ const globals = getGlobals();
1344
1372
  await installMcp({
1345
1373
  target: target as NonNullable<
1346
1374
  Parameters<typeof installMcp>[0]
@@ -1349,6 +1377,8 @@ function wireMcpCommand(program: Command): void {
1349
1377
  force: Boolean(cmdOpts.force),
1350
1378
  dryRun: Boolean(cmdOpts.dryRun),
1351
1379
  enableWrite: Boolean(cmdOpts.enableWrite),
1380
+ indexName: globals.index,
1381
+ configPath: globals.config,
1352
1382
  // Pass undefined if not set, so global --json can take effect
1353
1383
  json: cmdOpts.json === true ? true : undefined,
1354
1384
  });
@@ -1363,14 +1393,18 @@ function wireMcpCommand(program: Command): void {
1363
1393
  "target client (claude-desktop, cursor, zed, windsurf, opencode, amp, lmstudio, librechat, claude-code, codex)",
1364
1394
  "claude-desktop"
1365
1395
  )
1366
- .option("-s, --scope <scope>", "scope (user, project)", "user")
1396
+ .option(
1397
+ "-s, --scope <scope>",
1398
+ "scope (user, project) - defaults to project for LibreChat and user otherwise"
1399
+ )
1367
1400
  .option("--json", "JSON output")
1368
1401
  .action(async (cmdOpts: Record<string, unknown>) => {
1369
1402
  const target = cmdOpts.target as string;
1370
- const scope = cmdOpts.scope as string;
1403
+ const requestedScope = cmdOpts.scope;
1371
1404
 
1372
1405
  // Import MCP_TARGETS for validation
1373
- const { MCP_TARGETS } = await import("./commands/mcp/paths.js");
1406
+ const { getDefaultTargetScope, MCP_TARGETS } =
1407
+ await import("./commands/mcp/paths.js");
1374
1408
 
1375
1409
  // Validate target
1376
1410
  if (!(MCP_TARGETS as string[]).includes(target)) {
@@ -1379,13 +1413,23 @@ function wireMcpCommand(program: Command): void {
1379
1413
  `Invalid target: ${target}. Must be one of: ${MCP_TARGETS.join(", ")}.`
1380
1414
  );
1381
1415
  }
1382
- // Validate scope
1383
- if (!["user", "project"].includes(scope)) {
1416
+ // Validate an explicit scope, then let the target choose its default.
1417
+ if (
1418
+ requestedScope !== undefined &&
1419
+ (typeof requestedScope !== "string" ||
1420
+ !["user", "project"].includes(requestedScope))
1421
+ ) {
1384
1422
  throw new CliError(
1385
1423
  "VALIDATION",
1386
- `Invalid scope: ${scope}. Must be 'user' or 'project'.`
1424
+ `Invalid scope: ${JSON.stringify(requestedScope)}. Must be 'user' or 'project'.`
1387
1425
  );
1388
1426
  }
1427
+ const scope =
1428
+ typeof requestedScope === "string"
1429
+ ? requestedScope
1430
+ : getDefaultTargetScope(
1431
+ target as Parameters<typeof getDefaultTargetScope>[0]
1432
+ );
1389
1433
 
1390
1434
  const { uninstallMcp } = await import("./commands/mcp/uninstall.js");
1391
1435
  await uninstallMcp({
@@ -1418,7 +1462,7 @@ function wireMcpCommand(program: Command): void {
1418
1462
  const scope = cmdOpts.scope as string;
1419
1463
 
1420
1464
  // Import MCP_TARGETS for validation
1421
- const { MCP_TARGETS, TARGETS_WITH_PROJECT_SCOPE } =
1465
+ const { getTargetDisplayName, getTargetScopes, MCP_TARGETS } =
1422
1466
  await import("./commands/mcp/paths.js");
1423
1467
 
1424
1468
  // Validate target
@@ -1438,12 +1482,14 @@ function wireMcpCommand(program: Command): void {
1438
1482
  // Validate target/scope combination
1439
1483
  if (
1440
1484
  target !== "all" &&
1441
- scope === "project" &&
1442
- !(TARGETS_WITH_PROJECT_SCOPE as string[]).includes(target)
1485
+ scope !== "all" &&
1486
+ !getTargetScopes(
1487
+ target as Parameters<typeof getTargetScopes>[0]
1488
+ ).includes(scope as "user" | "project")
1443
1489
  ) {
1444
1490
  throw new CliError(
1445
1491
  "VALIDATION",
1446
- `${target} does not support project scope.`
1492
+ `${getTargetDisplayName(target as Parameters<typeof getTargetDisplayName>[0])} does not support ${scope} scope.`
1447
1493
  );
1448
1494
  }
1449
1495
 
@@ -0,0 +1,19 @@
1
+ import type { ActivationStatus } from "./activation-status";
2
+
3
+ const NON_RUNTIME_CODES = new Set([
4
+ "connector_not_configured",
5
+ "target_runtime_unverifiable",
6
+ ]);
7
+
8
+ /** True when every observable proof passed and no target/collection pair was omitted. */
9
+ export function isConnectorActivationComplete(
10
+ activation: ActivationStatus
11
+ ): boolean {
12
+ if (activation.connectorProjection.truncated) {
13
+ return false;
14
+ }
15
+ return activation.connectors.every(
16
+ ({ code, status }) =>
17
+ (code !== undefined && NON_RUNTIME_CODES.has(code)) || status === "passed"
18
+ );
19
+ }