Skip to content

Commit 06418af

Browse files
committed
refactor: nest spec-version range under source: ScenarioSource union
The flat `introducedIn`/`removedIn`/`extension?` shape forced extension scenarios to set a placeholder `introducedIn` that was never read. Nesting the version-range fields inside a `source` property typed as a union lets the compiler enforce introducedIn XOR extensionId — no runtime invariant test needed. type ScenarioSource = | { introducedIn: SpecVersion; removedIn?: SpecVersion } | { extensionId: ExtensionId }; A class can't `implements` a union type directly, so the union lives inside a property and `Scenario` stays a plain interface — `class implements Scenario` keeps working everywhere. `EXTENSION_IDS` is a const array mirroring the `DATED_SPEC_VERSIONS` pattern. Also renames the cross-app-access scenario to enterprise-managed-auth to match the extension's canonical name: file, class, slug, context schema literal, and the everything-client runner.
1 parent 6b808f9 commit 06418af

32 files changed

Lines changed: 141 additions & 244 deletions

examples/clients/typescript/everything-client.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -375,20 +375,20 @@ registerScenario('auth/pre-registration', runPreRegistration);
375375
// ============================================================================
376376

377377
/**
378-
* Cross-app access: Complete Flow (SEP-990)
378+
* Enterprise Managed Auth (SEP-990)
379379
* Tests the complete flow: IDP ID token -> authorization grant -> access token -> MCP access.
380380
*/
381-
export async function runCrossAppAccessCompleteFlow(
381+
export async function runEnterpriseManagedAuth(
382382
serverUrl: string
383383
): Promise<void> {
384384
const ctx = parseContext();
385-
if (ctx.name !== 'auth/cross-app-access-complete-flow') {
385+
if (ctx.name !== 'auth/enterprise-managed-auth') {
386386
throw new Error(
387-
`Expected cross-app-access-complete-flow context, got ${ctx.name}`
387+
`Expected enterprise-managed-auth context, got ${ctx.name}`
388388
);
389389
}
390390

391-
logger.debug('Starting complete cross-app access flow...');
391+
logger.debug('Starting enterprise managed auth flow...');
392392
logger.debug('IDP Issuer:', ctx.idp_issuer);
393393
logger.debug('IDP Token Endpoint:', ctx.idp_token_endpoint);
394394

@@ -494,7 +494,7 @@ export async function runCrossAppAccessCompleteFlow(
494494
// Step 3: Use access token to access MCP server
495495
logger.debug('Step 3: Accessing MCP server with access token...');
496496
const client = new Client(
497-
{ name: 'conformance-cross-app-access', version: '1.0.0' },
497+
{ name: 'conformance-enterprise-managed-auth', version: '1.0.0' },
498498
{ capabilities: {} }
499499
);
500500

@@ -516,13 +516,10 @@ export async function runCrossAppAccessCompleteFlow(
516516
logger.debug('Successfully called tool');
517517

518518
await transport.close();
519-
logger.debug('Complete cross-app access flow completed successfully');
519+
logger.debug('Enterprise managed auth flow completed successfully');
520520
}
521521

522-
registerScenario(
523-
'auth/cross-app-access-complete-flow',
524-
runCrossAppAccessCompleteFlow
525-
);
522+
registerScenario('auth/enterprise-managed-auth', runEnterpriseManagedAuth);
526523

527524
// ============================================================================
528525
// Main entry point

src/scenarios/authorization-server/authorization-server-metadata.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@
33
*/
44
import {
55
ClientScenarioForAuthorizationServer,
6-
ConformanceCheck,
7-
DatedSpecVersion
6+
ConformanceCheck
87
} from '../../types';
98
import { request } from 'undici';
109

1110
type Status = 'SUCCESS' | 'FAILURE';
1211

1312
export class AuthorizationServerMetadataEndpointScenario implements ClientScenarioForAuthorizationServer {
1413
name = 'authorization-server-metadata-endpoint';
15-
introducedIn: DatedSpecVersion = '2025-03-26';
14+
readonly source = { introducedIn: '2025-03-26' } as const;
1615
description = `Test authorization server metadata endpoint.
1716
1817
**Authorization Server Implementation Requirements:**

src/scenarios/client/auth/basic-cimd.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Scenario, ConformanceCheck } from '../../../types';
2-
import { ScenarioUrls, DatedSpecVersion } from '../../../types';
2+
import { ScenarioUrls } from '../../../types';
33
import { createAuthServer } from './helpers/createAuthServer';
44
import { createServer } from './helpers/createServer';
55
import { ServerLifecycle } from './helpers/serverLifecycle';
@@ -22,7 +22,7 @@ export const CIMD_CLIENT_METADATA_URL =
2222
*/
2323
export class AuthBasicCIMDScenario implements Scenario {
2424
name = 'auth/basic-cimd';
25-
introducedIn: DatedSpecVersion = '2025-11-25';
25+
readonly source = { introducedIn: '2025-11-25' } as const;
2626
description =
2727
'Tests OAuth flow with Client ID Metadata Documents (SEP-991/URL-based client IDs). Server advertises client_id_metadata_document_supported=true and client should use URL as client_id instead of DCR.';
2828
private authServer = new ServerLifecycle();

src/scenarios/client/auth/client-credentials.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
import * as jose from 'jose';
22
import type { CryptoKey } from 'jose';
3-
import type {
4-
Scenario,
5-
ConformanceCheck,
6-
ScenarioUrls,
7-
SpecVersion
8-
} from '../../../types';
9-
import { LATEST_SPEC_VERSION } from '../../../types';
3+
import type { Scenario, ConformanceCheck, ScenarioUrls } from '../../../types';
104
import { createAuthServer } from './helpers/createAuthServer';
115
import { createServer } from './helpers/createServer';
126
import { ServerLifecycle } from './helpers/serverLifecycle';
@@ -38,8 +32,7 @@ async function generateTestKeypair(): Promise<{
3832
*/
3933
export class ClientCredentialsJwtScenario implements Scenario {
4034
name = 'auth/client-credentials-jwt';
41-
extension = true;
42-
introducedIn: SpecVersion = LATEST_SPEC_VERSION;
35+
readonly source = { extensionId: 'client-credentials' } as const;
4336
description =
4437
'Tests OAuth client_credentials flow with private_key_jwt authentication (SEP-1046)';
4538

@@ -258,8 +251,7 @@ export class ClientCredentialsJwtScenario implements Scenario {
258251
*/
259252
export class ClientCredentialsBasicScenario implements Scenario {
260253
name = 'auth/client-credentials-basic';
261-
extension = true;
262-
introducedIn: SpecVersion = LATEST_SPEC_VERSION;
254+
readonly source = { extensionId: 'client-credentials' } as const;
263255
description =
264256
'Tests OAuth client_credentials flow with client_secret_basic authentication';
265257

src/scenarios/client/auth/discovery-metadata.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import type { Scenario, ConformanceCheck } from '../../../types';
10-
import { ScenarioUrls, DatedSpecVersion } from '../../../types';
10+
import { ScenarioUrls } from '../../../types';
1111
import { createAuthServer } from './helpers/createAuthServer';
1212
import { createServer } from './helpers/createServer';
1313
import { ServerLifecycle } from './helpers/serverLifecycle';
@@ -87,7 +87,7 @@ function createMetadataScenario(config: MetadataScenarioConfig): Scenario {
8787

8888
return {
8989
name: `auth/${config.name}`,
90-
introducedIn: '2025-11-25' as DatedSpecVersion,
90+
source: { introducedIn: '2025-11-25' },
9191
description: `Tests Basic OAuth metadata discovery flow.
9292
9393
**PRM:** ${config.prmLocation}${config.inWwwAuth ? '' : ' (not in WWW-Authenticate)'}

src/scenarios/client/auth/cross-app-access.ts renamed to src/scenarios/client/auth/enterprise-managed-auth.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
import * as jose from 'jose';
22
import type { CryptoKey } from 'jose';
33
import express, { type Request, type Response } from 'express';
4-
import type {
5-
Scenario,
6-
ConformanceCheck,
7-
ScenarioUrls,
8-
SpecVersion
9-
} from '../../../types';
10-
import { LATEST_SPEC_VERSION } from '../../../types';
4+
import type { Scenario, ConformanceCheck, ScenarioUrls } from '../../../types';
115
import { createAuthServer } from './helpers/createAuthServer';
126
import { createServer } from './helpers/createServer';
137
import { MockTokenVerifier } from './helpers/mockTokenVerifier';
@@ -54,15 +48,14 @@ async function createIdpIdToken(
5448
}
5549

5650
/**
57-
* Scenario: Complete Cross-App Access Flow
51+
* Scenario: Enterprise Managed Auth (SEP-990)
5852
*
5953
* Tests the complete SEP-990 flow: IDP ID token -> authorization grant -> access token
6054
* This scenario combines both RFC 8693 token exchange and RFC 7523 JWT bearer grant.
6155
*/
62-
export class CrossAppAccessCompleteFlowScenario implements Scenario {
63-
name = 'auth/cross-app-access-complete-flow';
64-
extension = true;
65-
introducedIn: SpecVersion = LATEST_SPEC_VERSION;
56+
export class EnterpriseManagedAuthScenario implements Scenario {
57+
name = 'auth/enterprise-managed-auth';
58+
readonly source = { extensionId: 'enterprise-managed-auth' } as const;
6659
description =
6760
'Tests complete SEP-990 flow: token exchange + JWT bearer grant (Enterprise Managed OAuth)';
6861

src/scenarios/client/auth/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
} from './client-credentials';
2424
import { ResourceMismatchScenario } from './resource-mismatch';
2525
import { PreRegistrationScenario } from './pre-registration';
26-
import { CrossAppAccessCompleteFlowScenario } from './cross-app-access';
26+
import { EnterpriseManagedAuthScenario } from './enterprise-managed-auth';
2727
import {
2828
OfflineAccessScopeScenario,
2929
OfflineAccessNotSupportedScenario
@@ -54,7 +54,7 @@ export const backcompatScenariosList: Scenario[] = [
5454
export const extensionScenariosList: Scenario[] = [
5555
new ClientCredentialsJwtScenario(),
5656
new ClientCredentialsBasicScenario(),
57-
new CrossAppAccessCompleteFlowScenario()
57+
new EnterpriseManagedAuthScenario()
5858
];
5959

6060
// Draft scenarios (informational - not scored for tier assessment)

src/scenarios/client/auth/march-spec-backcompat.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Scenario, ConformanceCheck } from '../../../types';
2-
import { ScenarioUrls, DatedSpecVersion } from '../../../types';
2+
import { ScenarioUrls } from '../../../types';
33
import { createAuthServer } from './helpers/createAuthServer';
44
import { createServer } from './helpers/createServer';
55
import { ServerLifecycle } from './helpers/serverLifecycle';
@@ -8,8 +8,10 @@ import { SpecReferences } from './spec-references';
88

99
export class Auth20250326OAuthMetadataBackcompatScenario implements Scenario {
1010
name = 'auth/2025-03-26-oauth-metadata-backcompat';
11-
introducedIn: DatedSpecVersion = '2025-03-26';
12-
removedIn: DatedSpecVersion = '2025-06-18';
11+
readonly source = {
12+
introducedIn: '2025-03-26',
13+
removedIn: '2025-06-18'
14+
} as const;
1315
description =
1416
'Tests 2025-03-26 spec OAuth flow: no PRM (Protected Resource Metadata), OAuth metadata at root location';
1517
private server = new ServerLifecycle();
@@ -70,8 +72,10 @@ export class Auth20250326OAuthMetadataBackcompatScenario implements Scenario {
7072

7173
export class Auth20250326OEndpointFallbackScenario implements Scenario {
7274
name = 'auth/2025-03-26-oauth-endpoint-fallback';
73-
introducedIn: DatedSpecVersion = '2025-03-26';
74-
removedIn: DatedSpecVersion = '2025-06-18';
75+
readonly source = {
76+
introducedIn: '2025-03-26',
77+
removedIn: '2025-06-18'
78+
} as const;
7579
description =
7680
'Tests OAuth flow with no metadata endpoints, relying on fallback to standard OAuth endpoints at server root (2025-03-26 spec behavior)';
7781
private server = new ServerLifecycle();

src/scenarios/client/auth/offline-access.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import type { Scenario, ConformanceCheck } from '../../../types';
2-
import {
3-
ScenarioUrls,
4-
SpecVersion,
5-
DRAFT_PROTOCOL_VERSION
6-
} from '../../../types';
2+
import { ScenarioUrls, DRAFT_PROTOCOL_VERSION } from '../../../types';
73
import { createAuthServer } from './helpers/createAuthServer';
84
import { createServer } from './helpers/createServer';
95
import { ServerLifecycle } from './helpers/serverLifecycle';
@@ -27,7 +23,7 @@ import { MockTokenVerifier } from './helpers/mockTokenVerifier';
2723
*/
2824
export class OfflineAccessScopeScenario implements Scenario {
2925
name = 'auth/offline-access-scope';
30-
introducedIn: SpecVersion = DRAFT_PROTOCOL_VERSION;
26+
readonly source = { introducedIn: DRAFT_PROTOCOL_VERSION } as const;
3127
description =
3228
'Tests that a client that wants a refresh token handles offline_access scope and refresh_token grant type when AS supports them (SEP-2207)';
3329

@@ -231,7 +227,7 @@ export class OfflineAccessScopeScenario implements Scenario {
231227
*/
232228
export class OfflineAccessNotSupportedScenario implements Scenario {
233229
name = 'auth/offline-access-not-supported';
234-
introducedIn: SpecVersion = DRAFT_PROTOCOL_VERSION;
230+
readonly source = { introducedIn: DRAFT_PROTOCOL_VERSION } as const;
235231
description =
236232
'Tests that client does not request offline_access when AS does not list it in scopes_supported (SEP-2207)';
237233

src/scenarios/client/auth/pre-registration.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
import type {
2-
Scenario,
3-
ConformanceCheck,
4-
ScenarioUrls,
5-
DatedSpecVersion
6-
} from '../../../types';
1+
import type { Scenario, ConformanceCheck, ScenarioUrls } from '../../../types';
72
import { createAuthServer } from './helpers/createAuthServer';
83
import { createServer } from './helpers/createServer';
94
import { ServerLifecycle } from './helpers/serverLifecycle';
@@ -24,7 +19,7 @@ const PRE_REGISTERED_CLIENT_SECRET = 'pre-registered-secret';
2419
*/
2520
export class PreRegistrationScenario implements Scenario {
2621
name = 'auth/pre-registration';
27-
introducedIn: DatedSpecVersion = '2025-11-25';
22+
readonly source = { introducedIn: '2025-11-25' } as const;
2823
description =
2924
'Tests OAuth flow with pre-registered client credentials. Server does not support DCR.';
3025

0 commit comments

Comments
 (0)