@postplus/cli 0.1.39 → 0.1.40

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/build/doctor.js CHANGED
@@ -25,6 +25,19 @@ function createFail(id, label, detail, fix, input = {}) {
25
25
  metadata: input.metadata,
26
26
  };
27
27
  }
28
+ // A check whose hosted route/key is released but has a known field-level coverage
29
+ // gap. It is not a required failure (does not break `requiredOk`); it is surfaced
30
+ // so the gap is visible instead of a blanket pass.
31
+ function createDegraded(id, label, detail, fix, input = {}) {
32
+ return {
33
+ id,
34
+ label,
35
+ status: 'degraded',
36
+ severity: input.severity ?? 'required',
37
+ detail,
38
+ fix,
39
+ };
40
+ }
28
41
  export async function generateDoctorReport(options = {}) {
29
42
  const hostedBaseUrl = await resolveHostedBaseUrl();
30
43
  const checks = [
@@ -109,9 +122,12 @@ async function checkLocalDependencies(skillScope) {
109
122
  }
110
123
  }
111
124
  function buildDoctorReport(checks, skillId) {
112
- const requiredOk = checks.every((check) => check.severity !== 'required' || check.status === 'pass');
125
+ // A degraded required check is a known coverage gap, not a failure: it keeps
126
+ // `requiredOk` true but `ok` false (doctor is not a clean pass while a gap
127
+ // exists), mirroring how task-specific warnings surface without failing.
128
+ const requiredOk = checks.every((check) => check.severity !== 'required' || check.status !== 'fail');
113
129
  return {
114
- schemaVersion: 1,
130
+ schemaVersion: 2,
115
131
  ok: checks.every((check) => check.status === 'pass'),
116
132
  requiredOk,
117
133
  checks,
@@ -191,6 +207,15 @@ async function checkHostedCapabilities(input, skillScope) {
191
207
  });
192
208
  }
193
209
  const subscription = readSubscriptionStatusField(payload).label;
210
+ const degradedLabels = relevantCapabilities
211
+ .map((value) => readCapabilityDegradedLabel(value, skillScope))
212
+ .filter((value) => value !== null);
213
+ if (degradedLabels.length > 0) {
214
+ const skillId = skillScope?.skill.skillId;
215
+ return createDegraded('hosted_capabilities', skillId ? `Hosted capabilities for ${skillId}` : 'Hosted capabilities', `Ready with field-level coverage gaps: ${degradedLabels.join(', ')}; subscription ${subscription}`, 'These hosted routes are released but have a known field-level contract gap. Track the readiness convergence plan before relying on field-level validation for these endpoints.', {
216
+ severity: skillId ? 'required' : 'task_specific',
217
+ });
218
+ }
194
219
  return createPass('hosted_capabilities', skillScope
195
220
  ? `Hosted capabilities for ${skillScope.skill.skillId}`
196
221
  : 'Hosted capabilities', `Ready (${relevantCapabilities.length} capability checks passed; subscription ${subscription})`);
@@ -244,6 +269,48 @@ function readReadinessCheckFailureLabel(value) {
244
269
  ? record.id
245
270
  : 'unknown check';
246
271
  }
272
+ // Surfaces a capability whose checks include a `degraded` field-level dimension.
273
+ // Degraded checks keep `ok` true (they do not fail the capability), so the
274
+ // failure reader skips them; this reader reports the gap separately.
275
+ function readCapabilityDegradedLabel(value, skillScope) {
276
+ if (!value || typeof value !== 'object') {
277
+ return null;
278
+ }
279
+ if (value.required === false) {
280
+ return null;
281
+ }
282
+ const degradedChecks = Array.isArray(value.checks)
283
+ ? value.checks
284
+ .map(readReadinessCheckDegradedLabel)
285
+ .filter((check) => check !== null)
286
+ : [];
287
+ if (degradedChecks.length === 0) {
288
+ return null;
289
+ }
290
+ const label = typeof value.label === 'string'
291
+ ? value.label
292
+ : typeof value.id === 'string'
293
+ ? value.id
294
+ : 'unknown capability';
295
+ const labelWithChecks = `${label} (${degradedChecks.join(', ')})`;
296
+ return skillScope
297
+ ? `${labelWithChecks} for ${skillScope.skill.skillId}`
298
+ : labelWithChecks;
299
+ }
300
+ function readReadinessCheckDegradedLabel(value) {
301
+ if (!value || typeof value !== 'object') {
302
+ return null;
303
+ }
304
+ const record = value;
305
+ if (record.status !== 'degraded') {
306
+ return null;
307
+ }
308
+ return typeof record.label === 'string'
309
+ ? record.label
310
+ : typeof record.id === 'string'
311
+ ? record.id
312
+ : 'unknown check';
313
+ }
247
314
  function filterCapabilitiesForSkill(capabilities, requirements) {
248
315
  if (!hasHostedRequirements(requirements)) {
249
316
  return [];
@@ -401,18 +468,23 @@ export function formatDoctorReport(report) {
401
468
  for (const check of report.checks) {
402
469
  const marker = check.status === 'pass'
403
470
  ? '[PASS]'
404
- : check.severity === 'task_specific'
405
- ? '[WARN]'
406
- : '[FAIL]';
471
+ : check.status === 'degraded'
472
+ ? '[DEGRADED]'
473
+ : check.severity === 'task_specific'
474
+ ? '[WARN]'
475
+ : '[FAIL]';
407
476
  lines.push(`${marker} ${check.label}: ${check.detail}`);
408
477
  if (check.fix) {
409
478
  lines.push(` Fix: ${check.fix}`);
410
479
  }
411
480
  }
481
+ const hasDegraded = report.checks.some((check) => check.status === 'degraded');
412
482
  lines.push('', report.ok
413
483
  ? 'Doctor passed.'
414
484
  : report.requiredOk
415
- ? 'Doctor incomplete: task-specific checks need attention.'
485
+ ? hasDegraded
486
+ ? 'Doctor incomplete: hosted readiness has known field-level coverage gaps.'
487
+ : 'Doctor incomplete: task-specific checks need attention.'
416
488
  : 'Doctor failed.');
417
489
  return lines.join('\n');
418
490
  }