Skip to content

Commit 2fcb7fe

Browse files
committed
showcase(D6): reclassify NSF features as skipped-incapable in d6-all-pills driver
When an integration's manifest lists a feature in not_supported_features (NSF), the D6 e2e-full driver now partitions requestedFeatures into capable + incapable sets BEFORE script resolution. Incapable features get a green side-row with errorClass='skipped-incapable' and surface in the aggregate skipped[] list plus a new incapable[] field. They no longer count as red. Includes red-green test that asserts NSF features without a registered script emit state=green (was red prior to this change).
1 parent a14959b commit 2fcb7fe

2 files changed

Lines changed: 207 additions & 7 deletions

File tree

showcase/harness/src/probes/drivers/d6-all-pills.test.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,120 @@ describe("e2e-full driver", () => {
327327
});
328328
});
329329

330+
// NSF (not_supported_features) reclassification: when an integration's
331+
// manifest declares a feature in `not_supported_features` (framework
332+
// primitive gap, not a regression), the driver must NOT count a failing
333+
// probe on that feature as red. Instead, the feature is partitioned
334+
// out before script resolution and emitted as a green side row with
335+
// `errorClass: "skipped-incapable"`. The aggregate carries them in
336+
// `skipped[]` AND an explicit `incapable[]` subset.
337+
describe("NSF (not_supported_features) reclassification", () => {
338+
it("reclassifies an NSF feature with no registered script as skipped-incapable (green), NOT red", async () => {
339+
// No script registered for `gen-ui-interrupt`. Pre-NSF behaviour:
340+
// missingScript red. Post-NSF: feature is in notSupportedFeatures
341+
// → emitted as green side row, included in skipped[] + incapable[],
342+
// never reaches the failed[] list, aggregate stays green.
343+
const sideEmits: ProbeResult<unknown>[] = [];
344+
const writer: ProbeResultWriter = {
345+
write: async (r) => {
346+
sideEmits.push(r);
347+
},
348+
};
349+
350+
const driver = createE2eFullDriver({
351+
launcher: async () => makeBrowser(),
352+
scriptLoader: noopScriptLoader(),
353+
});
354+
const result = await driver.run(makeCtx({ writer }), {
355+
key: "d6-all-pills-e2e:showcase-test-slug",
356+
backendUrl: "https://test.example.com",
357+
features: ["gen-ui-interrupt"],
358+
notSupportedFeatures: ["gen-ui-interrupt"],
359+
});
360+
361+
// Aggregate must be green — the only requested feature is NSF.
362+
expect(result.state).toBe("green");
363+
364+
const signal = result.signal as E2eFullAggregateSignal;
365+
expect(signal.failed).toEqual([]);
366+
expect(signal.skipped).toContain("gen-ui-interrupt");
367+
expect(signal.incapable).toEqual(["gen-ui-interrupt"]);
368+
369+
// Aggregate side row honors the same reclassification.
370+
const aggRow = sideEmits.find((r) => r.key === "d6:test-slug");
371+
expect(aggRow).toBeDefined();
372+
expect(aggRow!.state).toBe("green");
373+
374+
// Per-feature side row is green with skipped-incapable class.
375+
const ftRow = sideEmits.find(
376+
(r) => r.key === "d6:test-slug/gen-ui-interrupt",
377+
);
378+
expect(ftRow).toBeDefined();
379+
expect(ftRow!.state).toBe("green");
380+
const ftSignal = ftRow!.signal as E2eFullFeatureSignal;
381+
expect(ftSignal.errorClass).toBe("skipped-incapable");
382+
expect(ftSignal.note).toContain("not supported");
383+
});
384+
385+
it("keeps capable features running while NSF feature is skipped", async () => {
386+
// agentic-chat (capable) passes, gen-ui-interrupt (NSF) is skipped.
387+
// Aggregate stays green; passed=1; skipped=[gen-ui-interrupt];
388+
// incapable=[gen-ui-interrupt]; failed=[].
389+
registerD5Script(makeScript(["agentic-chat"]));
390+
391+
const sideEmits: ProbeResult<unknown>[] = [];
392+
const writer: ProbeResultWriter = {
393+
write: async (r) => {
394+
sideEmits.push(r);
395+
},
396+
};
397+
398+
const driver = createE2eFullDriver({
399+
launcher: async () => makeBrowser(),
400+
scriptLoader: noopScriptLoader(),
401+
});
402+
const result = await driver.run(makeCtx({ writer }), {
403+
key: "d6-all-pills-e2e:showcase-test-slug",
404+
backendUrl: "https://test.example.com",
405+
features: ["agentic-chat", "gen-ui-interrupt"],
406+
notSupportedFeatures: ["gen-ui-interrupt"],
407+
});
408+
409+
expect(result.state).toBe("green");
410+
const signal = result.signal as E2eFullAggregateSignal;
411+
expect(signal.passed).toBe(1);
412+
expect(signal.failed).toEqual([]);
413+
expect(signal.skipped).toEqual(["gen-ui-interrupt"]);
414+
expect(signal.incapable).toEqual(["gen-ui-interrupt"]);
415+
});
416+
417+
it("does NOT affect features outside the NSF set", async () => {
418+
// agentic-chat is NOT in NSF but has no script — must still go red.
419+
// This guards against accidentally treating NSF as a global allow.
420+
const sideEmits: ProbeResult<unknown>[] = [];
421+
const writer: ProbeResultWriter = {
422+
write: async (r) => {
423+
sideEmits.push(r);
424+
},
425+
};
426+
427+
const driver = createE2eFullDriver({
428+
launcher: async () => makeBrowser(),
429+
scriptLoader: noopScriptLoader(),
430+
});
431+
const result = await driver.run(makeCtx({ writer }), {
432+
key: "d6-all-pills-e2e:showcase-test-slug",
433+
backendUrl: "https://test.example.com",
434+
features: ["agentic-chat"],
435+
notSupportedFeatures: ["gen-ui-interrupt"],
436+
});
437+
438+
expect(result.state).toBe("red");
439+
const signal = result.signal as E2eFullAggregateSignal;
440+
expect(signal.failed).toContain("agentic-chat");
441+
});
442+
});
443+
330444
describe("deploy-churn grace window", () => {
331445
it("skips with green when deploy is recent", async () => {
332446
registerD5Script(makeScript(["agentic-chat"]));

showcase/harness/src/probes/drivers/d6-all-pills.ts

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ const inputSchema = z
6666
name: z.string().optional(),
6767
features: z.array(z.string()).optional(),
6868
demos: z.array(z.string()).optional(),
69+
/**
70+
* Integration's manifest `not_supported_features` set. Features in
71+
* this list are architecturally incapable on the framework, NOT
72+
* regressions. The driver reclassifies them as `skipped-incapable`
73+
* (green side-row + `skipped[]` in the aggregate) instead of running
74+
* a probe that would always fail and report red.
75+
*/
76+
notSupportedFeatures: z.array(z.string()).optional(),
6977
shape: showcaseShapeSchema.optional(),
7078
deployedAt: z.string().optional(),
7179
})
@@ -100,6 +108,14 @@ export interface E2eFullFeatureSignal {
100108
/**
101109
* Aggregate signal carried on the primary `d6:<slug>` row.
102110
* Green only if ALL features pass.
111+
*
112+
* `skipped` is a union of three reasons: filtered-by-trigger (operator
113+
* intent), deploy-churn (transient deploy state), and incapable
114+
* (manifest `not_supported_features` — framework primitive gap). The
115+
* driver does NOT distinguish them in the aggregate count because they
116+
* all share the "not counted as red" semantic. `incapable` is broken
117+
* out separately so dashboard / operators can tell genuine architectural
118+
* skips apart from operational ones.
103119
*/
104120
export interface E2eFullAggregateSignal {
105121
shape: "package";
@@ -109,6 +125,14 @@ export interface E2eFullAggregateSignal {
109125
passed: number;
110126
failed: string[];
111127
skipped: string[];
128+
/**
129+
* Subset of `skipped` representing manifest `not_supported_features`
130+
* — features the integration's framework architecturally cannot
131+
* support. Distinct from operational skips (deploy-churn, trigger
132+
* filter). Empty when the manifest declares no NSF or no requested
133+
* feature intersects it.
134+
*/
135+
incapable?: string[];
112136
note?: string;
113137
errorDesc?: string;
114138
failureSummary?: string;
@@ -500,6 +524,26 @@ export function createE2eFullDriver(
500524

501525
const requestedFeatures = featureSource.filter(isKnownFeatureType);
502526

527+
// NSF reclassification: features the integration's manifest
528+
// declares in `not_supported_features` are architecturally
529+
// incapable on this framework. Partition them out BEFORE script
530+
// resolution / runnable filtering so they're never attempted —
531+
// a stub demo page would fail every assertion and report red,
532+
// but the framework gap is the cause, not a regression. Emit
533+
// them as green side-rows with `errorClass: "skipped-incapable"`
534+
// and surface in the aggregate via `incapable[]` (a subset of
535+
// `skipped[]`).
536+
const incapableSet = new Set<string>(input.notSupportedFeatures ?? []);
537+
const incapableFeatures: D5FeatureType[] = [];
538+
const capableRequestedFeatures: D5FeatureType[] = [];
539+
for (const ft of requestedFeatures) {
540+
if (incapableSet.has(ft)) {
541+
incapableFeatures.push(ft);
542+
} else {
543+
capableRequestedFeatures.push(ft);
544+
}
545+
}
546+
503547
if (requestedFeatures.length === 0) {
504548
const aggregateResult: ProbeResult<E2eFullAggregateSignal> = {
505549
key: input.key,
@@ -591,9 +635,11 @@ export function createE2eFullDriver(
591635
// D6 strict missing-script handling: features without a registered
592636
// script FAIL with red (unlike D5 which skips with green). Missing
593637
// scripts in D6 are coverage gaps that must surface immediately.
638+
// Incapable features (NSF) are excluded from this check entirely
639+
// — they're emitted as green side-rows below.
594640
const missingScript: string[] = [];
595641
let runnable: D5FeatureType[] = [];
596-
for (const ft of requestedFeatures) {
642+
for (const ft of capableRequestedFeatures) {
597643
if (D5_REGISTRY.has(ft)) {
598644
runnable.push(ft);
599645
} else {
@@ -658,7 +704,11 @@ export function createE2eFullDriver(
658704
total: requestedFeatures.length,
659705
passed: 0,
660706
failed: [],
661-
skipped: [],
707+
skipped: [...incapableFeatures.map(String)],
708+
incapable:
709+
incapableFeatures.length > 0
710+
? incapableFeatures.map(String)
711+
: undefined,
662712
errorDesc: "launcher-error",
663713
failureSummary: truncateUtf8(msg, 1200),
664714
},
@@ -699,6 +749,26 @@ export function createE2eFullDriver(
699749
});
700750
}
701751

752+
// Emit green side rows for NSF-incapable features. Distinct
753+
// `errorClass: "skipped-incapable"` so log scrapers can
754+
// distinguish manifest-declared framework gaps from operational
755+
// skips. State is green so the dashboard does NOT count these
756+
// as red, but the side-row carries the reason for auditability.
757+
for (const ft of incapableFeatures) {
758+
await sideEmit(ctx, {
759+
key: `d6:${slug}/${ft}`,
760+
state: "green",
761+
signal: {
762+
slug,
763+
featureType: ft,
764+
backendUrl,
765+
errorClass: "skipped-incapable",
766+
note: "skipped: not supported by integration (manifest.not_supported_features)",
767+
},
768+
observedAt: ctx.now().toISOString(),
769+
});
770+
}
771+
702772
// If nothing is runnable but we have missing scripts, that's a red.
703773
if (runnable.length === 0 && missingScript.length > 0) {
704774
const aggregateResult: ProbeResult<E2eFullAggregateSignal> = {
@@ -711,7 +781,11 @@ export function createE2eFullDriver(
711781
total: requestedFeatures.length,
712782
passed: 0,
713783
failed: missingScript,
714-
skipped: filteredByTrigger,
784+
skipped: [...filteredByTrigger, ...incapableFeatures],
785+
incapable:
786+
incapableFeatures.length > 0
787+
? incapableFeatures.map(String)
788+
: undefined,
715789
failureSummary: missingScript
716790
.map((ft) => `${ft}: no script registered`)
717791
.join("; "),
@@ -734,8 +808,15 @@ export function createE2eFullDriver(
734808
total: requestedFeatures.length,
735809
passed: 0,
736810
failed: [],
737-
skipped: filteredByTrigger,
738-
note: "all runnable features filtered by trigger",
811+
skipped: [...filteredByTrigger, ...incapableFeatures],
812+
incapable:
813+
incapableFeatures.length > 0
814+
? incapableFeatures.map(String)
815+
: undefined,
816+
note:
817+
filteredByTrigger.length > 0
818+
? "all runnable features filtered by trigger"
819+
: "all requested features are NSF-incapable",
739820
},
740821
observedAt,
741822
};
@@ -1006,7 +1087,8 @@ export function createE2eFullDriver(
10061087
slug,
10071088
passed,
10081089
failed: failed.length,
1009-
skipped: filteredByTrigger.length,
1090+
skipped: filteredByTrigger.length + incapableFeatures.length,
1091+
incapable: incapableFeatures.length,
10101092
total: requestedFeatures.length,
10111093
state: aggregateGreen ? "green" : "red",
10121094
durationMs: Date.now() - serviceStart,
@@ -1021,7 +1103,11 @@ export function createE2eFullDriver(
10211103
total: requestedFeatures.length,
10221104
passed,
10231105
failed,
1024-
skipped: filteredByTrigger,
1106+
skipped: [...filteredByTrigger, ...incapableFeatures],
1107+
incapable:
1108+
incapableFeatures.length > 0
1109+
? incapableFeatures.map(String)
1110+
: undefined,
10251111
failureSummary:
10261112
featureErrors.length > 0 ? featureErrors.join("; ") : undefined,
10271113
},

0 commit comments

Comments
 (0)