@@ -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 */
104120export 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