@aiwg/cli 2026.10.9 → 2026.10.11

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 (116) hide show
  1. package/agentic/code/providers/model-capabilities.v1.json +275 -17
  2. package/bin/aiwg.mjs +8 -0
  3. package/dist/src/cli/handlers/mc-admission.js +39 -0
  4. package/dist/src/cli/handlers/mc.js +2 -31
  5. package/dist/src/cli/handlers/models-help.js +52 -0
  6. package/dist/src/cli/handlers/models.js +11 -81
  7. package/dist/src/cli/handlers/sessions.js +198 -15
  8. package/dist/src/cli/handlers/use-addon-profile.js +72 -0
  9. package/dist/src/cli/handlers/use.js +203 -234
  10. package/dist/src/cli/ui.js +1 -0
  11. package/dist/src/config/aiwg-config.js +38 -184
  12. package/dist/src/config/cli-help.js +54 -0
  13. package/dist/src/config/cli.js +5 -69
  14. package/dist/src/config/config-normalization.js +10 -0
  15. package/dist/src/config/worker-activation-gate.js +26 -0
  16. package/dist/src/config/worker-adoption-cli.js +102 -0
  17. package/dist/src/config/worker-adoption.js +368 -0
  18. package/dist/src/config/worker-config-digest.js +14 -0
  19. package/dist/src/config/worker-context.js +337 -0
  20. package/dist/src/config/worker-group-templates.js +150 -0
  21. package/dist/src/config/worker-guidance-warning.js +28 -0
  22. package/dist/src/config/worker-owner-lock.js +152 -0
  23. package/dist/src/config/worker-parallelism.js +154 -0
  24. package/dist/src/config/worker-write.js +75 -0
  25. package/dist/src/decision/telemetry/exporter.js +2 -1
  26. package/dist/src/decision/telemetry/index.js +1 -0
  27. package/dist/src/decision/telemetry/mapping.js +43 -0
  28. package/dist/src/decision/telemetry/otlp-http.js +5 -2
  29. package/dist/src/decision/telemetry/redaction.js +6 -1
  30. package/dist/src/decision/telemetry/types.js +3 -0
  31. package/dist/src/decision/telemetry/worker.js +69 -0
  32. package/dist/src/lint/rule-activation.js +15 -7
  33. package/dist/src/lint/runner.js +2 -2
  34. package/dist/src/mcp/server.mjs +2 -11
  35. package/dist/src/mcp/stdio-lifecycle.mjs +27 -0
  36. package/dist/src/mcp/tools/worker-failure-diagnostics.mjs +9 -0
  37. package/dist/src/mcp/tools/worker-invoke.mjs +114 -16
  38. package/dist/src/mission-protocol/codecs.js +8 -38
  39. package/dist/src/mission-protocol/worker-contract-diagnostics.js +62 -0
  40. package/dist/src/mission-protocol/worker-invocation-types.js +2 -0
  41. package/dist/src/models/conductor-data-policy.d.mts +8 -0
  42. package/dist/src/models/conductor-data-policy.mjs +190 -0
  43. package/dist/src/models/config-loader.js +19 -3
  44. package/dist/src/models/config-writer.js +27 -0
  45. package/dist/src/models/model-capabilities.v1.json +275 -17
  46. package/dist/src/models/provider-policy.js +14 -155
  47. package/dist/src/models/resolver.js +2 -293
  48. package/dist/src/models/worker-artifact-binding.mjs +12 -0
  49. package/dist/src/models/worker-direct-readiness-policy.js +155 -0
  50. package/dist/src/models/worker-direct-readiness.js +9 -218
  51. package/dist/src/models/worker-health-readiness.js +105 -26
  52. package/dist/src/models/worker-installation.mjs +12 -14
  53. package/dist/src/models/worker-member-references.js +64 -0
  54. package/dist/src/models/worker-plan.js +317 -0
  55. package/dist/src/models/worker-selection-policy.js +217 -0
  56. package/dist/src/models/wrapper-deployment.js +1 -1
  57. package/dist/src/policy/authorization.js +29 -1
  58. package/dist/src/process/aiwg-probe-command.d.mts +15 -0
  59. package/dist/src/process/aiwg-probe-command.mjs +37 -0
  60. package/dist/src/serve/shared-host-admission-checks.js +116 -0
  61. package/dist/src/serve/shared-host-scheduler.js +25 -107
  62. package/dist/src/serve/shared-host-starvation.js +73 -0
  63. package/dist/src/serve/worker-artifacts.d.mts +4 -0
  64. package/dist/src/serve/worker-artifacts.mjs +56 -0
  65. package/dist/src/serve/worker-command-request.js +68 -0
  66. package/dist/src/serve/worker-invocation-paths.js +144 -0
  67. package/dist/src/serve/worker-invocation-preparation.js +169 -0
  68. package/dist/src/serve/worker-invocation.js +24 -291
  69. package/dist/src/serve/worker-stdin-frame.d.mts +2 -2
  70. package/dist/src/serve/worker-stdin-frame.mjs +6 -6
  71. package/dist/src/sessions/adapters/grokbot.js +7 -0
  72. package/dist/src/sessions/batch-import.js +34 -6
  73. package/dist/src/sessions/contracts.js +19 -0
  74. package/dist/src/sessions/coverage-preflight.js +7 -1
  75. package/dist/src/sessions/fortemi-export-mapping.js +33 -3
  76. package/dist/src/sessions/host-catalog.js +157 -0
  77. package/dist/src/sessions/image-blobs.js +141 -0
  78. package/dist/src/sessions/importer.js +23 -3
  79. package/dist/src/sessions/index.js +2 -0
  80. package/dist/src/sessions/refresh.js +25 -2
  81. package/dist/src/sessions/repository.js +164 -22
  82. package/dist/src/sessions/workspace-discovery.js +5 -2
  83. package/dist/src/smiths/context-pipeline/parallelism-section.js +19 -7
  84. package/package.json +15 -1
  85. package/schemas/mission-v1.schema.json +20 -19
  86. package/schemas/models/model-config-compatibility.v1.schema.json +16 -0
  87. package/tools/agents/providers/claude-post-deploy.mjs +35 -0
  88. package/tools/agents/providers/claude.mjs +3 -30
  89. package/tools/agents/providers/codex-post-deploy.mjs +13 -0
  90. package/tools/agents/providers/codex.mjs +2 -8
  91. package/tools/agents/providers/conductor-data-guard.mjs +61 -0
  92. package/tools/agents/providers/rule-delivery.mjs +3 -16
  93. package/tools/agents/providers/rule-presentation.mjs +38 -0
  94. package/tools/plugin/package-plugins.mjs +1 -1
  95. package/tools/ralph-external/lib/claude-adapter.mjs +2 -2
  96. package/tools/ralph-external/lib/codex-adapter.mjs +3 -3
  97. package/tools/ralph-external/lib/managed-worker-attempt.mjs +86 -0
  98. package/tools/ralph-external/lib/muse-adapter.mjs +10 -0
  99. package/tools/ralph-external/lib/provider-adapter.mjs +11 -260
  100. package/tools/ralph-external/lib/session-attempt.mjs +85 -0
  101. package/tools/ralph-external/lib/strict-worker-json.mjs +190 -0
  102. package/tools/ralph-external/lib/worker-artifacts.mjs +209 -0
  103. package/tools/ralph-external/lib/worker-context.mjs +237 -0
  104. package/tools/ralph-external/lib/worker-final.mjs +143 -0
  105. package/tools/ralph-external/lib/worker-inputs.mjs +55 -0
  106. package/tools/ralph-external/lib/worker-invocation-ancestry.mjs +132 -0
  107. package/tools/ralph-external/lib/worker-invocation-processes.mjs +123 -0
  108. package/tools/ralph-external/lib/worker-isolation-status.mjs +48 -0
  109. package/tools/ralph-external/lib/worker-isolation.mjs +19 -73
  110. package/tools/ralph-external/lib/worker-launch-cli.mjs +49 -0
  111. package/tools/ralph-external/lib/worker-native-sandbox.mjs +83 -0
  112. package/tools/ralph-external/lib/worker-overflow.mjs +48 -13
  113. package/tools/ralph-external/lib/worker-retry-evidence.mjs +103 -0
  114. package/tools/ralph-external/lib/worker-termination.mjs +82 -0
  115. package/tools/ralph-external/session-launcher.mjs +124 -313
  116. package/vscode-extension/schemas/aiwg.config.v1.json +161 -2
@@ -0,0 +1,155 @@
1
+ // Resolves direct-worker bindings, member context and offline dispatch eligibility.
2
+ import { resolveWorkerGroupInstallation } from '../config/aiwg-config.js';
3
+ import { resolveWorkerPlan } from './resolver.js';
4
+ export { memberReferences, memberLabels, describeConfigContext, allConfiguredCliIds, isCliRequired, advisoryStatus } from './worker-member-references.js';
5
+ export const SMOKE_NOTE = 'A version probe alone leaves profiles unqualified. To record direct CLI operator approval, run aiwg doctor --probe-workers --qualify; a version probe is not an authenticated smoke run (see #3005).';
6
+ const ADAPTER_REFERENCE = /^(providers|workerAdapters):(.+)$/;
7
+ /**
8
+ * Resolve the configured binding by exact, case-sensitive namespace:id
9
+ * lookup — the same shape `resolveWorkerAdapter` (provider-policy.ts) uses
10
+ * for dispatch and `validateParallelismConfig` uses for config validation.
11
+ * No alias normalization happens for either namespace, so a binding this
12
+ * accepts is also a binding the public config validator accepts, and a
13
+ * custom `workerAdapters:<id>` reference resolves against
14
+ * `registry.workerAdapters`, never against the provider-alias registry.
15
+ *
16
+ * For the `providers` namespace this falls back from `workerAdapter` to
17
+ * `workerHealthAdapter` when no direct-dispatch binding is pinned yet — the
18
+ * install/version metadata is identical either way, and that is exactly
19
+ * what lets install readiness and dispatch eligibility disagree honestly
20
+ * (installed and in range, but the resolver still has no workerAdapter to
21
+ * dispatch through).
22
+ */
23
+ export function resolveDirectBinding(registry, configuredAdapter, cli) {
24
+ let namespace;
25
+ let id;
26
+ if (configuredAdapter !== undefined) {
27
+ const match = ADAPTER_REFERENCE.exec(configuredAdapter.trim());
28
+ if (!match)
29
+ return { error: `Invalid worker adapter reference '${configuredAdapter}'` };
30
+ [, namespace, id] = match;
31
+ }
32
+ else {
33
+ namespace = 'providers';
34
+ id = cli;
35
+ }
36
+ if (namespace === 'providers') {
37
+ const entry = registry.providers[id];
38
+ const adapter = entry?.workerAdapter ?? entry?.workerHealthAdapter;
39
+ if (!adapter)
40
+ return { error: `No direct worker profile is registered for binding 'providers:${id}'` };
41
+ return { adapter, adapterRef: `providers:${id}` };
42
+ }
43
+ const adapter = registry.workerAdapters?.[id];
44
+ if (!adapter)
45
+ return { error: `Unknown worker adapter reference 'workerAdapters:${id}'` };
46
+ return { adapter, adapterRef: `workerAdapters:${id}` };
47
+ }
48
+ const offlineDirectContext = () => ({ estimator: 'doctor-offline-direct-context-v1', briefTokens: 0, inputTokens: 0, toolOutputAllowance: 0, reservedOutput: 0 });
49
+ /**
50
+ * Dispatch eligibility from the actual offline dispatch resolver
51
+ * (`resolveWorkerPlan`): called once per role for every group member
52
+ * referencing this cli, never re-derived. A resolvable binding alone is not
53
+ * reported as eligible — eligibility requires the resolver to actually place
54
+ * at least one referencing member in its eligible/alternatives set. Refusals
55
+ * are member-specific and name the resolver's own missing-evidence or policy
56
+ * diagnostics.
57
+ */
58
+ function resolveEligibilityFromDispatchResolver(resolverSnapshot, registry, cli, refs) {
59
+ const snapshot = {
60
+ config: resolverSnapshot.config, models: resolverSnapshot.models, modelDigests: resolverSnapshot.modelDigests,
61
+ catalog: resolverSnapshot.catalog, registry, installations: {},
62
+ };
63
+ const diagnostics = new Map();
64
+ const uncovered = [];
65
+ for (const ref of refs) {
66
+ if (!ref.enabled)
67
+ continue;
68
+ const matches = (tuple) => tuple.group === ref.groupId && tuple.member === ref.memberId;
69
+ const seen = [];
70
+ let eligibleHere = false;
71
+ for (const role of ref.member.roles) {
72
+ const plan = resolveWorkerPlan(snapshot, {
73
+ role: role, group: ref.groupId, member: ref.memberId,
74
+ now: resolverSnapshot.now, context: offlineDirectContext(),
75
+ });
76
+ if ([plan.selected, ...plan.alternatives].some(tuple => tuple && matches(tuple))) {
77
+ eligibleHere = true;
78
+ break;
79
+ }
80
+ const exclusion = plan.exclusions.find(matches);
81
+ for (const diagnostic of exclusion?.diagnostics ?? plan.diagnostics) {
82
+ // Only an exact member pointer is generalized; other config pointers
83
+ // remain distinct even when their diagnostic text happens to match.
84
+ const memberPointer = `/parallelism/groups/${ref.groupId}/members/${ref.memberId}`;
85
+ const pointer = diagnostic.pointer === memberPointer || diagnostic.pointer.startsWith(`${memberPointer}/`)
86
+ ? `/parallelism/groups/${ref.groupId}/members${diagnostic.pointer.slice(memberPointer.length)}`
87
+ : diagnostic.pointer;
88
+ seen.push({ ...diagnostic, pointer, members: [`${ref.groupId}/${ref.memberId}`],
89
+ roles: diagnostic.roles ?? [role] });
90
+ }
91
+ }
92
+ if (eligibleHere) {
93
+ return { eligibility: 'eligible',
94
+ detail: `Dispatch resolver accepts cli '${cli}' for group '${ref.groupId}' member '${ref.memberId}'. `
95
+ + `Dispatch eligibility: eligible. ${SMOKE_NOTE}` };
96
+ }
97
+ if (!seen.length)
98
+ uncovered.push(`${ref.groupId}/${ref.memberId}`);
99
+ for (const diagnostic of seen) {
100
+ const key = JSON.stringify([diagnostic.code, diagnostic.pointer, diagnostic.message,
101
+ diagnostic.blocking, diagnostic.origins]);
102
+ const shared = diagnostics.get(key);
103
+ if (shared) {
104
+ shared.members = [...new Set([...shared.members, ...diagnostic.members])];
105
+ shared.roles = [...new Set([...shared.roles, ...diagnostic.roles])];
106
+ }
107
+ else
108
+ diagnostics.set(key, diagnostic);
109
+ }
110
+ }
111
+ if (!diagnostics.size && !uncovered.length) {
112
+ return { eligibility: 'blocked',
113
+ detail: `Dispatch resolver has no enabled group member to evaluate for cli '${cli}'. `
114
+ + `Dispatch eligibility: blocked. ${SMOKE_NOTE}` };
115
+ }
116
+ const distinct = [...diagnostics.values()];
117
+ const reasons = distinct.map(diagnostic => `${diagnostic.code} ${diagnostic.pointer}: ${diagnostic.message}`
118
+ + ` [roles: ${diagnostic.roles.join(', ')}; members: ${diagnostic.members.join(', ')}]`);
119
+ if (uncovered.length)
120
+ reasons.push(`No qualified route [members: ${uncovered.join(', ')}]`);
121
+ return { eligibility: 'blocked', diagnostics: distinct,
122
+ detail: `Dispatch resolver refuses cli '${cli}': ${reasons.join('; ')}. `
123
+ + `Dispatch eligibility: blocked. ${SMOKE_NOTE}` };
124
+ }
125
+ /**
126
+ * Binding-only fallback used only when the caller has not supplied a
127
+ * `resolverSnapshot` (for example a direct unit-level call that never
128
+ * resolved the wider model config/catalog). States plainly that this is a
129
+ * binding-level result, not the dispatch resolver's verdict.
130
+ */
131
+ function resolveEligibilityFromBindingOnly(parallelism, registry, cli, installReady) {
132
+ try {
133
+ const resolved = resolveWorkerGroupInstallation(parallelism, cli, registry);
134
+ return { eligibility: 'eligible',
135
+ detail: `Resolver accepts cli '${cli}' (binding '${resolved.installation.adapter}'). Dispatch eligibility: eligible at binding level only — `
136
+ + `no resolver snapshot was supplied, so group/member qualification was not evaluated. `
137
+ + `Launch additionally requires current installation evidence and model observation under the dispatch resolver's gates. `
138
+ + `${SMOKE_NOTE}` };
139
+ }
140
+ catch (error) {
141
+ const reason = error instanceof Error ? error.message : String(error);
142
+ const readyNote = installReady
143
+ ? ' The CLI is installed with an in-range version, but dispatch stays refused for the reason above; '
144
+ + `an authenticated smoke run (see #3005) alone does not satisfy the resolver gates. ${SMOKE_NOTE}`
145
+ : ` ${SMOKE_NOTE}`;
146
+ return { eligibility: 'blocked',
147
+ detail: `Resolver refuses cli '${cli}': ${reason}. Dispatch eligibility: blocked.${readyNote}` };
148
+ }
149
+ }
150
+ export function checkDispatchEligibility(parallelism, registry, cli, installReady, resolverSnapshot, refs) {
151
+ if (resolverSnapshot && refs.length)
152
+ return resolveEligibilityFromDispatchResolver(resolverSnapshot, registry, cli, refs);
153
+ return resolveEligibilityFromBindingOnly(parallelism, registry, cli, installReady);
154
+ }
155
+ //# sourceMappingURL=worker-direct-readiness-policy.js.map
@@ -57,11 +57,9 @@
57
57
  * performs no live-qualification recording and requires no evidence files.
58
58
  */
59
59
  import { tmpdir } from 'node:os';
60
+ import { advisoryStatus, allConfiguredCliIds, checkDispatchEligibility, describeConfigContext, isCliRequired, memberLabels, memberReferences, resolveDirectBinding, SMOKE_NOTE, } from './worker-direct-readiness-policy.js';
60
61
  import { isAbsolute, resolve as resolvePath } from 'node:path';
61
62
  import { boundedVersionProbe, normalizeWorkerVersionBanner, workerVersionProbeDefaults, workerVersionProbeEnvironment, workerVersionRangeMismatch, } from './worker-installation.mjs';
62
- import { resolveWorkerGroupInstallation } from '../config/aiwg-config.js';
63
- import { resolveWorkerPlan } from './resolver.js';
64
- const SMOKE_NOTE = 'A version probe alone leaves profiles unqualified. To record direct CLI operator approval, run aiwg doctor --probe-workers --qualify; a version probe is not an authenticated smoke run (see #3005).';
65
63
  function truncateBanner(banner, limit = 200) {
66
64
  const singleLine = banner.replace(/[\r\n]+/g, '\\n');
67
65
  return singleLine.length > limit ? `${singleLine.slice(0, limit)}…` : singleLine;
@@ -100,214 +98,6 @@ export async function probeDirectWorkerVersion(executable, versionArgv, options
100
98
  return { ok: false, reason: error?.code === 'ENOENT' ? 'not-installed' : 'probe-failed' };
101
99
  }
102
100
  }
103
- const ADAPTER_REFERENCE = /^(providers|workerAdapters):(.+)$/;
104
- /**
105
- * Resolve the configured binding by exact, case-sensitive namespace:id
106
- * lookup — the same shape `resolveWorkerAdapter` (provider-policy.ts) uses
107
- * for dispatch and `validateParallelismConfig` uses for config validation.
108
- * No alias normalization happens for either namespace, so a binding this
109
- * accepts is also a binding the public config validator accepts, and a
110
- * custom `workerAdapters:<id>` reference resolves against
111
- * `registry.workerAdapters`, never against the provider-alias registry.
112
- *
113
- * For the `providers` namespace this falls back from `workerAdapter` to
114
- * `workerHealthAdapter` when no direct-dispatch binding is pinned yet — the
115
- * install/version metadata is identical either way, and that is exactly
116
- * what lets install readiness and dispatch eligibility disagree honestly
117
- * (installed and in range, but the resolver still has no workerAdapter to
118
- * dispatch through).
119
- */
120
- function resolveDirectBinding(registry, configuredAdapter, cli) {
121
- let namespace;
122
- let id;
123
- if (configuredAdapter !== undefined) {
124
- const match = ADAPTER_REFERENCE.exec(configuredAdapter.trim());
125
- if (!match)
126
- return { error: `Invalid worker adapter reference '${configuredAdapter}'` };
127
- [, namespace, id] = match;
128
- }
129
- else {
130
- namespace = 'providers';
131
- id = cli;
132
- }
133
- if (namespace === 'providers') {
134
- const entry = registry.providers[id];
135
- const adapter = entry?.workerAdapter ?? entry?.workerHealthAdapter;
136
- if (!adapter)
137
- return { error: `No direct worker profile is registered for binding 'providers:${id}'` };
138
- return { adapter, adapterRef: `providers:${id}` };
139
- }
140
- const adapter = registry.workerAdapters?.[id];
141
- if (!adapter)
142
- return { error: `Unknown worker adapter reference 'workerAdapters:${id}'` };
143
- return { adapter, adapterRef: `workerAdapters:${id}` };
144
- }
145
- /** Every group member referencing this cli, including disabled ones (for context). */
146
- function memberReferences(parallelism, cli) {
147
- const refs = [];
148
- for (const [groupId, group] of Object.entries(parallelism.groups ?? {})) {
149
- for (const member of group?.members ?? []) {
150
- if (member.cli !== cli)
151
- continue;
152
- refs.push({ groupId, memberId: member.id, member,
153
- enabled: member.enabled !== false && parallelism.clis?.[cli]?.enabled !== false });
154
- }
155
- }
156
- return refs.sort((a, b) => `${a.groupId}/${a.memberId}`.localeCompare(`${b.groupId}/${b.memberId}`));
157
- }
158
- function memberLabels(refs) {
159
- return refs.map(ref => `${ref.groupId}/${ref.memberId}${ref.enabled ? '' : ' (disabled)'}`);
160
- }
161
- function describeConfigContext(members) {
162
- return members.length ? `members: ${members.join(', ')}` : 'members: no group member references this cli';
163
- }
164
- /**
165
- * Union of every cli id a readiness pass must consider: explicit
166
- * `parallelism.clis` entries plus any cli id referenced only implicitly by a
167
- * group member (canonical default binding, no explicit installation entry).
168
- */
169
- function allConfiguredCliIds(parallelism, cli) {
170
- const ids = new Set(Object.keys(parallelism.clis ?? {}));
171
- for (const group of Object.values(parallelism.groups ?? {})) {
172
- for (const member of group?.members ?? [])
173
- ids.add(member.cli);
174
- }
175
- return [...ids].filter(id => cli === undefined || id === cli).sort();
176
- }
177
- /**
178
- * A cli is "required" only when an enabled group member referencing it also
179
- * covers a role its group lists in `requiredRoles`. Every other configured
180
- * cli is optional: installation failures stay advisory (never 'error', so
181
- * they never flip doctor's exit code) until something actually requires it.
182
- */
183
- function isCliRequired(parallelism, cli) {
184
- for (const group of Object.values(parallelism.groups ?? {})) {
185
- const required = new Set(group?.requiredRoles ?? []);
186
- if (!required.size)
187
- continue;
188
- for (const member of group?.members ?? []) {
189
- if (member.cli !== cli || member.enabled === false)
190
- continue;
191
- if (parallelism.clis?.[cli]?.enabled === false)
192
- continue;
193
- if (member.roles.some(role => required.has(role)))
194
- return true;
195
- }
196
- }
197
- return false;
198
- }
199
- /** Cap a status at 'warn' for optional (non-required) clis so an unreferenced
200
- * or not-yet-wired profile never flips doctor's exit code. */
201
- function advisoryStatus(status, required) {
202
- return required || status !== 'error' ? status : 'warn';
203
- }
204
- const offlineDirectContext = () => ({ estimator: 'doctor-offline-direct-context-v1', briefTokens: 0, inputTokens: 0, toolOutputAllowance: 0, reservedOutput: 0 });
205
- /**
206
- * Dispatch eligibility from the actual offline dispatch resolver
207
- * (`resolveWorkerPlan`): called once per role for every group member
208
- * referencing this cli, never re-derived. A resolvable binding alone is not
209
- * reported as eligible — eligibility requires the resolver to actually place
210
- * at least one referencing member in its eligible/alternatives set. Refusals
211
- * are member-specific and name the resolver's own missing-evidence or policy
212
- * diagnostics.
213
- */
214
- function resolveEligibilityFromDispatchResolver(resolverSnapshot, registry, cli, refs) {
215
- const snapshot = {
216
- config: resolverSnapshot.config, models: resolverSnapshot.models, modelDigests: resolverSnapshot.modelDigests,
217
- catalog: resolverSnapshot.catalog, registry, installations: {},
218
- };
219
- const diagnostics = new Map();
220
- const uncovered = [];
221
- for (const ref of refs) {
222
- if (!ref.enabled)
223
- continue;
224
- const matches = (tuple) => tuple.group === ref.groupId && tuple.member === ref.memberId;
225
- const seen = [];
226
- let eligibleHere = false;
227
- for (const role of ref.member.roles) {
228
- const plan = resolveWorkerPlan(snapshot, {
229
- role: role, group: ref.groupId, member: ref.memberId,
230
- now: resolverSnapshot.now, context: offlineDirectContext(),
231
- });
232
- if ([plan.selected, ...plan.alternatives].some(tuple => tuple && matches(tuple))) {
233
- eligibleHere = true;
234
- break;
235
- }
236
- const exclusion = plan.exclusions.find(matches);
237
- for (const diagnostic of exclusion?.diagnostics ?? plan.diagnostics) {
238
- // Only an exact member pointer is generalized; other config pointers
239
- // remain distinct even when their diagnostic text happens to match.
240
- const memberPointer = `/parallelism/groups/${ref.groupId}/members/${ref.memberId}`;
241
- const pointer = diagnostic.pointer === memberPointer || diagnostic.pointer.startsWith(`${memberPointer}/`)
242
- ? `/parallelism/groups/${ref.groupId}/members${diagnostic.pointer.slice(memberPointer.length)}`
243
- : diagnostic.pointer;
244
- seen.push({ ...diagnostic, pointer, members: [`${ref.groupId}/${ref.memberId}`],
245
- roles: diagnostic.roles ?? [role] });
246
- }
247
- }
248
- if (eligibleHere) {
249
- return { eligibility: 'eligible',
250
- detail: `Dispatch resolver accepts cli '${cli}' for group '${ref.groupId}' member '${ref.memberId}'. `
251
- + `Dispatch eligibility: eligible. ${SMOKE_NOTE}` };
252
- }
253
- if (!seen.length)
254
- uncovered.push(`${ref.groupId}/${ref.memberId}`);
255
- for (const diagnostic of seen) {
256
- const key = JSON.stringify([diagnostic.code, diagnostic.pointer, diagnostic.message,
257
- diagnostic.blocking, diagnostic.origins]);
258
- const shared = diagnostics.get(key);
259
- if (shared) {
260
- shared.members = [...new Set([...shared.members, ...diagnostic.members])];
261
- shared.roles = [...new Set([...shared.roles, ...diagnostic.roles])];
262
- }
263
- else
264
- diagnostics.set(key, diagnostic);
265
- }
266
- }
267
- if (!diagnostics.size && !uncovered.length) {
268
- return { eligibility: 'blocked',
269
- detail: `Dispatch resolver has no enabled group member to evaluate for cli '${cli}'. `
270
- + `Dispatch eligibility: blocked. ${SMOKE_NOTE}` };
271
- }
272
- const distinct = [...diagnostics.values()];
273
- const reasons = distinct.map(diagnostic => `${diagnostic.code} ${diagnostic.pointer}: ${diagnostic.message}`
274
- + ` [roles: ${diagnostic.roles.join(', ')}; members: ${diagnostic.members.join(', ')}]`);
275
- if (uncovered.length)
276
- reasons.push(`No qualified route [members: ${uncovered.join(', ')}]`);
277
- return { eligibility: 'blocked', diagnostics: distinct,
278
- detail: `Dispatch resolver refuses cli '${cli}': ${reasons.join('; ')}. `
279
- + `Dispatch eligibility: blocked. ${SMOKE_NOTE}` };
280
- }
281
- /**
282
- * Binding-only fallback used only when the caller has not supplied a
283
- * `resolverSnapshot` (for example a direct unit-level call that never
284
- * resolved the wider model config/catalog). States plainly that this is a
285
- * binding-level result, not the dispatch resolver's verdict.
286
- */
287
- function resolveEligibilityFromBindingOnly(parallelism, registry, cli, installReady) {
288
- try {
289
- const resolved = resolveWorkerGroupInstallation(parallelism, cli, registry);
290
- return { eligibility: 'eligible',
291
- detail: `Resolver accepts cli '${cli}' (binding '${resolved.installation.adapter}'). Dispatch eligibility: eligible at binding level only — `
292
- + `no resolver snapshot was supplied, so group/member qualification was not evaluated. `
293
- + `Launch additionally requires current installation evidence and model observation under the dispatch resolver's gates. `
294
- + `${SMOKE_NOTE}` };
295
- }
296
- catch (error) {
297
- const reason = error instanceof Error ? error.message : String(error);
298
- const readyNote = installReady
299
- ? ' The CLI is installed with an in-range version, but dispatch stays refused for the reason above; '
300
- + `an authenticated smoke run (see #3005) alone does not satisfy the resolver gates. ${SMOKE_NOTE}`
301
- : ` ${SMOKE_NOTE}`;
302
- return { eligibility: 'blocked',
303
- detail: `Resolver refuses cli '${cli}': ${reason}. Dispatch eligibility: blocked.${readyNote}` };
304
- }
305
- }
306
- function checkDispatchEligibility(parallelism, registry, cli, installReady, resolverSnapshot, refs) {
307
- if (resolverSnapshot && refs.length)
308
- return resolveEligibilityFromDispatchResolver(resolverSnapshot, registry, cli, refs);
309
- return resolveEligibilityFromBindingOnly(parallelism, registry, cli, installReady);
310
- }
311
101
  /**
312
102
  * Read-only readiness for every configured worker. Starts no process unless
313
103
  * `probe` is supplied; without a prober each cli gets a config-only install
@@ -325,14 +115,15 @@ export async function diagnoseDirectWorkerReadiness(input) {
325
115
  findings: [],
326
116
  };
327
117
  if (!report.enabled) {
118
+ const members = allConfiguredCliIds(parallelism, input.cli).flatMap(cli => memberLabels(memberReferences(parallelism, cli)));
328
119
  report.findings.push({ cli: '*', kind: 'dispatch', profile: null, executable: null, installedVersion: null,
329
120
  status: 'info', code: 'WORKER_DIRECT_DISABLED',
330
- message: `Group dispatch is disabled (config ${configFile} key /parallelism/worker_dispatch); install/version checks do not apply and onboarding cannot proceed.`,
121
+ message: `Group dispatch is disabled (config ${configFile} key /parallelism/worker_dispatch); install/version checks do not apply and onboarding cannot proceed. (${describeConfigContext(members)})`,
331
122
  nextAction: 'enable-worker-dispatch-to-onboard',
332
123
  configFile, configKey: '/parallelism/worker_dispatch',
333
124
  readiness: null, eligibility: 'blocked',
334
125
  eligibilityDetail: 'Dispatch eligibility: blocked — group dispatch is disabled.',
335
- members: [] });
126
+ members });
336
127
  return report;
337
128
  }
338
129
  const cliIds = allConfiguredCliIds(parallelism, input.cli);
@@ -363,9 +154,9 @@ export async function diagnoseDirectWorkerReadiness(input) {
363
154
  status: 'info', code: 'WORKER_DIRECT_CLI_DISABLED',
364
155
  message: `Dispatch is disabled for cli '${cli}' (config ${configFile} key ${configKey}/enabled); the version probe was skipped. Installation readiness: skipped. (${describeConfigContext(members)})`,
365
156
  nextAction: 'enable-cli-to-onboard',
366
- configFile, configKey, readiness: 'skipped', members });
157
+ configFile, configKey: `${configKey}/enabled`, readiness: 'skipped', members });
367
158
  const { eligibility, detail, diagnostics } = checkDispatchEligibility(parallelism, registry, cli, false, resolverSnapshot, refs);
368
- pushDispatch(cli, configured.invocationProfile ?? null, 'info', 'WORKER_DIRECT_DISPATCH_BLOCKED', eligibility, detail, configKey, members, 'enable-cli-to-onboard', diagnostics);
159
+ pushDispatch(cli, configured.invocationProfile ?? null, 'info', 'WORKER_DIRECT_DISPATCH_BLOCKED', eligibility, detail, `${configKey}/enabled`, members, 'enable-cli-to-onboard', diagnostics);
369
160
  return;
370
161
  }
371
162
  const resolved = resolveDirectBinding(registry, configured?.adapter, cli);
@@ -399,9 +190,9 @@ export async function diagnoseDirectWorkerReadiness(input) {
399
190
  status: advisoryStatus('error', required), code: 'WORKER_DIRECT_PROFILE_UNKNOWN',
400
191
  message: configured?.invocationProfile
401
192
  ? `Configured invocation profile '${configured.invocationProfile}' is not registered for binding '${adapterRef}' (config ${configFile} key ${configKey}/invocationProfile). Installation readiness: not-ready. (${describeConfigContext(members)})`
402
- : `Cli '${cli}' binding '${adapterRef}' has no single direct profile to check (config ${configFile} key ${configKey}); configure invocationProfile explicitly. Installation readiness: not-ready. (${describeConfigContext(members)})`,
193
+ : `Cli '${cli}' binding '${adapterRef}' has no single direct profile to check (config ${configFile} key ${configKey}/invocationProfile); configure invocationProfile explicitly. Installation readiness: not-ready. (${describeConfigContext(members)})`,
403
194
  nextAction: 'review-worker-configuration',
404
- configFile, configKey, readiness: 'not-ready', members });
195
+ configFile, configKey: `${configKey}/invocationProfile`, readiness: 'not-ready', members });
405
196
  const { eligibility, detail, diagnostics } = checkDispatchEligibility(parallelism, registry, cli, false, resolverSnapshot, refs);
406
197
  pushDispatch(cli, configured?.invocationProfile ?? null, 'warn', 'WORKER_DIRECT_DISPATCH_BLOCKED', eligibility, detail, configKey, members, 'review-worker-configuration', diagnostics);
407
198
  return;
@@ -435,7 +226,7 @@ export async function diagnoseDirectWorkerReadiness(input) {
435
226
  ? `CLI '${executable}' is not installed (version probe failed to start; config ${configFile} key ${configKey}/executable); install it before onboarding cli '${cli}'. Installation readiness: not-ready. (${describeConfigContext(members)})`
436
227
  : `Version probe for cli '${cli}' ('${executable}', config ${configFile} key ${configKey}) failed; retry once, then check PATH and execute permission. Installation readiness: not-ready. (${describeConfigContext(members)})`,
437
228
  nextAction: notInstalled ? 'install-cli' : 'retry-version-probe',
438
- configFile, configKey, readiness: 'not-ready', members });
229
+ configFile, configKey: notInstalled ? `${configKey}/executable` : configKey, readiness: 'not-ready', members });
439
230
  const { eligibility, detail, diagnostics } = checkDispatchEligibility(parallelism, registry, cli, false, resolverSnapshot, refs);
440
231
  pushDispatch(cli, profileRef, 'warn', 'WORKER_DIRECT_DISPATCH_BLOCKED', eligibility, detail, configKey, members, notInstalled ? 'install-cli' : 'retry-version-probe', diagnostics);
441
232
  return;
@@ -1,5 +1,8 @@
1
1
  /** Offline S1 health readiness projection; never launch authority or a probe. */
2
- import { validateParallelismConfig, resolveWorkerGroupInstallation, workerConfigDigest } from '../config/aiwg-config.js';
2
+ import { homedir } from 'node:os';
3
+ import { collectLocalOnlyPaths } from './conductor-data-policy.mjs';
4
+ import { isAbsolute, join, resolve } from 'node:path';
5
+ import { resolveParallelism, validateParallelismConfig, resolveWorkerGroupInstallation, workerConfigDigest } from '../config/aiwg-config.js';
3
6
  import { assertWorkerHealthQualification } from './provider-policy.js';
4
7
  import { resolveWorkerPlan } from './resolver.js';
5
8
  import { resolveWorkerEffectiveHealth } from '../serve/worker-invocation.js';
@@ -18,11 +21,86 @@ const repairAction = (code, pointer) => {
18
21
  return 'qualify-installation-and-profile';
19
22
  return 'review-worker-configuration';
20
23
  };
21
- export function diagnoseWorkerHealthReadiness(snapshot, request) {
22
- const p = snapshot.config.parallelism;
24
+ /** Preserve the same repair context in human-readable doctor output and JSON. */
25
+ export function formatWorkerHealthReadinessFinding(finding) {
26
+ return `${finding.code}: ${finding.message} (config ${finding.configFile} key ${finding.configKey}; members: ${finding.members.join(', ') || 'none'}) [next: ${finding.nextAction}]`;
27
+ }
28
+ /** Build offline configuration repair findings before model/catalog loading is possible. */
29
+ export function workerHealthConfigurationFindings(parallelism, errors, paths = {}) {
30
+ return errors.map(message => {
31
+ const pointer = message.match(/^OG_CONFIG_REFERENCE (\/[^:]*):/)?.[1] ?? '/parallelism';
32
+ const parts = pointer.split('/');
33
+ const members = Object.entries(parallelism?.groups ?? {}).flatMap(([group, value]) => (Array.isArray(value?.members) ? value.members : []).filter((member, index) => {
34
+ if (!member || typeof member.id !== 'string')
35
+ return false;
36
+ if (parts[2] === 'groups')
37
+ return group === parts[3]
38
+ && (parts[4] !== 'members' || member.id === parts[5] || String(index) === parts[5]);
39
+ return parts[2] !== 'clis' || member.cli === parts[3];
40
+ }).map(member => `${group}/${member.id}`));
41
+ return { code: 'OG_CONFIG_REFERENCE', status: 'error', pointer,
42
+ configFile: paths.configFile ?? resolve('.aiwg/aiwg.config'), configKey: pointer,
43
+ members, message, nextAction: 'review-worker-configuration', role: null };
44
+ });
45
+ }
46
+ export function diagnoseWorkerHealthReadiness(snapshot, request, paths = {}) {
47
+ let p = snapshot.config.parallelism;
23
48
  const report = { enabled: p?.worker_dispatch === true,
24
49
  ready: true, contextDigest: null, findings: [], thresholds: [] };
25
- const add = (finding) => { report.findings.push(finding); };
50
+ const configFile = paths.configFile ?? resolve('.aiwg/aiwg.config');
51
+ const modelsFile = paths.modelsFile ?? resolve('models.json');
52
+ const catalogFile = paths.catalogFile ?? join(homedir(), '.cache/aiwg/model-catalog.v1.json');
53
+ const errors = validateParallelismConfig(p);
54
+ if (!errors.length && p?.worker_dispatch)
55
+ p = resolveParallelism(p, snapshot.config.providers[0]);
56
+ const allMembers = Object.entries(p?.groups ?? {}).flatMap(([group, value]) => (value.members ?? []).map(member => ({ group, member, label: `${group}/${member.id}` })));
57
+ const membersFor = (pointer, role) => {
58
+ const parts = pointer.split('/');
59
+ const groups = role ? snapshot.models.defaults.workerSelection?.roleGuidance?.[role]?.groups : undefined;
60
+ return allMembers.filter(row => {
61
+ if (parts[1] === 'parallelism' && parts[2] === 'groups')
62
+ return row.group === parts[3]
63
+ && (parts[4] !== 'members' || row.member.id === parts[5])
64
+ && (parts[4] !== 'requiredRoles' || !role || row.member.roles.includes(role));
65
+ if (parts[1] === 'parallelism' && parts[2] === 'clis')
66
+ return row.member.cli === parts[3];
67
+ return !groups || groups.includes(row.group);
68
+ }).map(row => row.label);
69
+ };
70
+ const contextualize = (finding, members) => {
71
+ let file = configFile;
72
+ let key = finding.pointer;
73
+ if (key.startsWith('/defaults/') || key.startsWith('/agentOverrides')) {
74
+ const origins = Object.entries(snapshot.models.workerPolicyOrigins ?? {})
75
+ .filter(([pointer]) => pointer === key || pointer.startsWith(`${key}/`))
76
+ .flatMap(([, values]) => values).filter(origin => isAbsolute(origin.source))
77
+ .sort((a, b) => ['builtin', 'user', 'project'].indexOf(a.authority)
78
+ - ['builtin', 'user', 'project'].indexOf(b.authority));
79
+ file = origins.at(-1)?.source ?? modelsFile;
80
+ }
81
+ else if (key.startsWith('/catalog/')) {
82
+ file = catalogFile;
83
+ key = key.slice('/catalog'.length);
84
+ }
85
+ else if (key === '/worker' || key.startsWith('/worker/')) {
86
+ file = catalogFile;
87
+ const affected = allMembers.find(row => row.label === members[0]);
88
+ const cli = affected?.member.cli;
89
+ const index = cli ? snapshot.catalog.discovery?.instances?.[cli]?.models.findIndex(row => row.id === affected.member.model || row.apiModelId === affected.member.model) : undefined;
90
+ if (cli && index !== undefined && index >= 0)
91
+ key = `/discovery/instances/${cli}/models/${index}${key}`;
92
+ }
93
+ return { ...finding, configFile: file, configKey: key, members: [...new Set(members)] };
94
+ };
95
+ const add = (finding, members = membersFor(finding.pointer, finding.role)) => {
96
+ report.findings.push(contextualize(finding, members));
97
+ };
98
+ const localOnlyPaths = collectLocalOnlyPaths(snapshot.models.defaults.workerSelection, p);
99
+ if (localOnlyPaths.length)
100
+ add({ code: 'CONDUCTOR_LOCAL_ONLY_PATHS', status: 'info',
101
+ pointer: '/defaults/workerSelection/dataPolicy/localOnlyPaths', role: null,
102
+ message: `Declared local-only conductor inputs: ${localOnlyPaths.join(', ')}. Deploy provider guards before opening task inputs; shell coverage is limited.`,
103
+ nextAction: 'deploy-provider-data-guards' });
26
104
  if (!report.enabled) {
27
105
  add({ code: 'WORKER_HEALTH_DISABLED', status: 'info', pointer: '/parallelism/worker_dispatch',
28
106
  message: 'Group dispatch is disabled; legacy execution has no worker health readiness requirement.',
@@ -30,12 +108,9 @@ export function diagnoseWorkerHealthReadiness(snapshot, request) {
30
108
  return report;
31
109
  }
32
110
  report.contextDigest = workerConfigDigest({ context: request.context, modelDigests: snapshot.modelDigests });
33
- const errors = validateParallelismConfig(p);
34
111
  if (errors.length) {
35
112
  report.ready = false;
36
- for (const message of errors)
37
- add({ code: 'OG_CONFIG_REFERENCE', status: 'error',
38
- pointer: '/parallelism', message, nextAction: 'review-worker-configuration', role: null });
113
+ report.findings.push(...workerHealthConfigurationFindings(p, errors, { configFile }));
39
114
  return report;
40
115
  }
41
116
  if (p?.worker_mode === 'managed') {
@@ -53,7 +128,7 @@ export function diagnoseWorkerHealthReadiness(snapshot, request) {
53
128
  message: 'No worker role guidance is configured; onboarding cannot offer a qualified route.',
54
129
  nextAction: 'configure-role-guidance', role: null });
55
130
  }
56
- const installations = new Set(enabledGroups.flatMap(group => group.members.filter(member => member.enabled !== false).map(member => member.cli)));
131
+ const installations = new Set(enabledGroups.flatMap(group => (group.members ?? []).filter(member => member.enabled !== false).map(member => member.cli)));
57
132
  for (const cli of installations) {
58
133
  if (!p?.clis?.[cli]?.quotaScope)
59
134
  add({ code: 'WORKER_QUOTA_SCOPE_UNKNOWN', status: 'warn', role: null,
@@ -65,26 +140,30 @@ export function diagnoseWorkerHealthReadiness(snapshot, request) {
65
140
  for (const role of roles) {
66
141
  const plan = resolveWorkerPlan(snapshot, { ...request, role: role });
67
142
  const isRequired = required.has(role);
68
- const diagnostics = [...plan.diagnostics, ...plan.exclusions.flatMap(item => item.diagnostics)];
69
- const seen = new Set();
70
- for (const diagnostic of diagnostics) {
143
+ const diagnostics = [
144
+ ...plan.diagnostics.map(diagnostic => ({ diagnostic, members: membersFor(diagnostic.pointer, role) })),
145
+ ...plan.exclusions.flatMap(item => item.diagnostics.map(diagnostic => ({ diagnostic,
146
+ members: [`${item.group}/${item.member}`] }))),
147
+ ];
148
+ for (const { diagnostic, members } of diagnostics) {
71
149
  if (diagnostic.roles && !diagnostic.roles.includes(role))
72
150
  continue;
73
- const key = JSON.stringify([diagnostic.code, diagnostic.pointer, diagnostic.message, diagnostic.blocking, diagnostic.origins]);
74
- if (seen.has(key))
75
- continue;
76
- seen.add(key);
77
- const finding = { ...diagnostic,
151
+ const finding = contextualize({ ...diagnostic,
78
152
  status: diagnostic.blocking === false ? 'info' : 'warn', role,
79
- nextAction: repairAction(diagnostic.code, diagnostic.pointer) };
153
+ nextAction: repairAction(diagnostic.code, diagnostic.pointer) }, members);
154
+ const key = JSON.stringify([diagnostic.code, diagnostic.pointer, diagnostic.message, diagnostic.blocking,
155
+ diagnostic.origins, finding.configFile, finding.configKey]);
80
156
  const shared = sharedDiagnostics.get(key);
81
157
  if (shared) {
82
- shared.roles = [...new Set([...(shared.roles ?? [shared.role]), role])];
83
- shared.role = null;
158
+ shared.members = [...new Set([...shared.members, ...finding.members])];
159
+ if (shared.role !== role) {
160
+ shared.roles = [...new Set([...(shared.roles ?? [shared.role]), role])];
161
+ shared.role = null;
162
+ }
84
163
  }
85
164
  else {
86
165
  sharedDiagnostics.set(key, finding);
87
- add(finding);
166
+ report.findings.push(finding);
88
167
  }
89
168
  }
90
169
  if (!plan.selected) {
@@ -111,11 +190,11 @@ export function diagnoseWorkerHealthReadiness(snapshot, request) {
111
190
  add({ code: 'WORKER_STEP_CAP_NOT_READY', status: isRequired ? 'error' : 'warn', role,
112
191
  pointer: '/parallelism/health/requireStepCap',
113
192
  message: 'Required step cap lacks native enforcement or qualified runtime pre-step admission evidence.',
114
- nextAction: 'qualify-step-enforcement' });
193
+ nextAction: 'qualify-step-enforcement' }, [`${tuple.group}/${tuple.member}`]);
115
194
  continue;
116
195
  }
117
196
  // External admission needs runtime gate evidence; a config declaration alone cannot prove it.
118
- const health = resolveWorkerEffectiveHealth(p?.health, { role, effort: tuple.effort,
197
+ const health = resolveWorkerEffectiveHealth(p?.health, { role: plan.policy?.roleGuidance?.[role]?.baseRole ?? role, effort: tuple.effort,
119
198
  modelKey: tuple.modelKey, profileRef: tuple.profile, wallSeconds: plan.ceilings.wallSeconds ?? 1800,
120
199
  stepEnforcement: nativeSteps ? 'native' : 'unavailable' });
121
200
  report.thresholds.push({ role, group: tuple.group, member: tuple.member, health });
@@ -123,12 +202,12 @@ export function diagnoseWorkerHealthReadiness(snapshot, request) {
123
202
  add({ code: 'WORKER_HEALTH_DEGRADED', status: 'warn', role,
124
203
  pointer: `/parallelism/clis/${tuple.cli}/invocationProfile`,
125
204
  message: 'Profile has degraded or unobserved health coverage; progress and stop evidence may be unavailable.',
126
- nextAction: 'qualify-health-signals' });
205
+ nextAction: 'qualify-health-signals' }, [`${tuple.group}/${tuple.member}`]);
127
206
  }
128
207
  add({ code: 'WORKER_ROLE_ELIGIBLE', status: 'ok', role,
129
208
  pointer: `/defaults/workerSelection/roleGuidance/${role}`,
130
209
  message: 'Policy route is eligible; launch still requires current installation and task qualification.',
131
- nextAction: 'resolve-current-task' });
210
+ nextAction: 'resolve-current-task' }, [`${tuple.group}/${tuple.member}`]);
132
211
  }
133
212
  catch {
134
213
  if (isRequired)
@@ -136,7 +215,7 @@ export function diagnoseWorkerHealthReadiness(snapshot, request) {
136
215
  add({ code: 'WORKER_HEALTH_UNQUALIFIED', status: isRequired ? 'error' : 'warn', role,
137
216
  pointer: `/parallelism/clis/${tuple.cli}/invocationProfile`,
138
217
  message: 'Health profile scope or effective settings are invalid; requalify the exact version/account/model/effort.',
139
- nextAction: 'qualify-health-signals' });
218
+ nextAction: 'qualify-health-signals' }, [`${tuple.group}/${tuple.member}`]);
140
219
  }
141
220
  }
142
221
  return report;