Skip to content

Commit 0cf57c6

Browse files
TheLarkInnCopilot
andcommitted
Add daemon-aligned major default-flip migration model (feature 28/28)
Encode the specification §8.1 phase 6 default flip as revertible data in a new migration module: the seven migration phases (each independently releasable and revertible), pre-flip and daemon-aligned major default sets (automatic selection on by default, legacy terminal APIs removed, incompatible plugins gated before apply, legacy renderer/aliases/sentinel bridge retained, RUSH_REPORTER=legacy emergency fallback), and a plugin apply gate that fails incompatible plugins with a structured RUSH_PLUGIN_API_INCOMPATIBLE diagnostic. Completes the Rush Reporter Overhaul (28/28). Assistant-model: GitHub Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 897dcf7e-e6e8-4a84-85ca-34b93fa29be3
1 parent be441b0 commit 0cf57c6

8 files changed

Lines changed: 713 additions & 1 deletion

File tree

common/reviews/api/reporter.api.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ export function createScopedReporter(options: ICreateScopedReporterOptions): ISc
9494
// @beta
9595
export function createTelemetryReporter(subscriber: TelemetrySubscriber): IReporter;
9696

97+
// @beta
98+
export const DAEMON_ALIGNED_MAJOR_REPORTER_DEFAULTS: IReporterMajorDefaults;
99+
97100
// @beta
98101
export const DEFAULT_FLUSH_TIMEOUT_MS: number;
99102

@@ -133,6 +136,9 @@ export function detectAgent(env: Record<string, string | undefined>, configuredV
133136
// @beta
134137
export function encodeNdjsonRecord(value: unknown, options?: INdjsonOptions): string;
135138

139+
// @beta
140+
export function evaluatePluginApplyGate(manifests: readonly IRushPluginManifest[], options?: IPluginApplyGateOptions): IPluginApplyDecision[];
141+
136142
// @beta
137143
export const EXIT_CODE_FAILURE: 1;
138144

@@ -161,6 +167,9 @@ export class FileReporter implements IReporter {
161167
// @beta
162168
export function filterEventsForLogLevel(logLevel: ReporterLogLevel, events: readonly IReporterEventEnvelope<unknown>[]): IReporterEventEnvelope<unknown>[];
163169

170+
// @beta
171+
export function getBlockedPlugins(decisions: readonly IPluginApplyDecision[]): IPluginApplyDecision[];
172+
164173
// @beta
165174
export function getEventMinimumLogLevel(event: IReporterEventEnvelope<unknown>): ReporterLogLevel;
166175

@@ -170,6 +179,9 @@ export function getLogLevelRank(level: ReporterLogLevel): number;
170179
// @beta
171180
export function getPrivacyClassificationRank(classification: ReporterPrivacyClassification): number;
172181

182+
// @beta
183+
export function getReporterMigrationPhase(id: ReporterMigrationPhaseId): IReporterMigrationPhase;
184+
173185
// @beta
174186
export function getSignalExitCode(signal: NodeJS.Signals): number;
175187

@@ -268,6 +280,13 @@ export interface IAutomaticReporterPlan {
268280
readonly stdoutOwner: 'machine' | 'human';
269281
}
270282

283+
// @beta
284+
export interface IAutomaticSelectionContext {
285+
readonly emergencyLegacyFallback?: boolean;
286+
readonly experimentalSettingEnabled?: boolean;
287+
readonly explicitOptIn?: boolean;
288+
}
289+
271290
// @beta
272291
export interface IBootstrapEventBufferOptions {
273292
readonly maxBytes?: number;
@@ -572,6 +591,19 @@ export interface IPlaintextReporterOptions {
572591
readonly write: (text: string) => void;
573592
}
574593

594+
// @beta
595+
export interface IPluginApplyDecision {
596+
readonly allowed: boolean;
597+
readonly diagnostic?: IRushDiagnostic;
598+
readonly manifest: IRushPluginManifest;
599+
}
600+
601+
// @beta
602+
export interface IPluginApplyGateOptions {
603+
readonly gateEnabled?: boolean;
604+
readonly supportedApiVersion?: string;
605+
}
606+
575607
// @beta
576608
export interface IProblemMatch {
577609
readonly code?: string;
@@ -729,6 +761,18 @@ export interface IReporterHostOptions {
729761
readonly retentionMs?: number;
730762
}
731763

764+
// @beta
765+
export interface IReporterMajorDefaults {
766+
readonly automaticSelectionEnabledByDefault: boolean;
767+
readonly emergencyFallbackEnvVar: string;
768+
readonly emergencyFallbackReporterName: string;
769+
readonly gateIncompatiblePluginsBeforeApply: boolean;
770+
readonly legacyRendererRetained: boolean;
771+
readonly removedTerminalApis: readonly string[];
772+
readonly sentinelBridgeRetained: boolean;
773+
readonly verbosityAliasesRetained: boolean;
774+
}
775+
732776
// @beta
733777
export interface IReporterManagerOptions {
734778
readonly coalesceThreshold?: number;
@@ -737,6 +781,16 @@ export interface IReporterManagerOptions {
737781
readonly protocolVersion?: IReporterProtocolVersion;
738782
}
739783

784+
// @beta
785+
export interface IReporterMigrationPhase {
786+
readonly id: ReporterMigrationPhaseId;
787+
readonly independentlyReleasable: boolean;
788+
readonly ordinal: number;
789+
readonly revertible: boolean;
790+
readonly summary: string;
791+
readonly title: string;
792+
}
793+
740794
// @beta
741795
export interface IReporterOutputTarget {
742796
readonly params: {
@@ -889,6 +943,9 @@ export function isAgentVariableActive(value: string | undefined): boolean;
889943
// @beta
890944
export function isAlreadyReportedSentinel(error: unknown): boolean;
891945

946+
// @beta
947+
export function isAutomaticSelectionEnabled(defaults: IReporterMajorDefaults, context?: IAutomaticSelectionContext): boolean;
948+
892949
// @beta
893950
export function isBootstrapHandoffFileName(fileName: string): boolean;
894951

@@ -917,6 +974,9 @@ export interface IScopedReporter {
917974
emitMessage(options: IScopedMessageOptions): string;
918975
}
919976

977+
// @beta
978+
export function isEmergencyLegacyFallback(env: Record<string, string | undefined>, defaults?: IReporterMajorDefaults): boolean;
979+
920980
// @beta
921981
export interface ISessionCompletedPayload {
922982
readonly durationMs?: number;
@@ -960,6 +1020,9 @@ export function isSupportedLogLevel(level: string): level is ReporterLogLevel;
9601020
// @beta
9611021
export function isSupportedReporterName(name: string): name is ReporterName;
9621022

1023+
// @beta
1024+
export function isTerminalApiRemoved(api: string, defaults?: IReporterMajorDefaults): boolean;
1025+
9631026
// @beta
9641027
export function isValidRushDiagnosticCode(code: string): boolean;
9651028

@@ -1151,6 +1214,9 @@ export type PlaintextVariant = 'detailed' | 'concise';
11511214
// @beta
11521215
export function planAutomaticReporters(selection: IReporterSelection): IAutomaticReporterPlan;
11531216

1217+
// @beta
1218+
export const PRE_FLIP_REPORTER_DEFAULTS: IReporterMajorDefaults;
1219+
11541220
// @beta
11551221
export class ProblemMatcherRegistry {
11561222
getMatchers(tool: string, options?: IGetMatchersOptions): IProblemMatcher[];
@@ -1166,6 +1232,9 @@ export function readChildDescriptorFd(env: Record<string, string | undefined>):
11661232
// @beta
11671233
export function regroupOperationOutput(events: readonly IReporterEventEnvelope<unknown>[]): Map<string, string>;
11681234

1235+
// @beta
1236+
export const REMOVED_TERMINAL_APIS: readonly string[];
1237+
11691238
// @beta
11701239
export function renderActiveProjectsRow(projects: readonly string[], width: number): string;
11711240

@@ -1175,6 +1244,9 @@ export function renderLiveRegion(state: ILiveRegionState, options: IRenderLiveRe
11751244
// @beta
11761245
export const REPORTER_EVENT_TYPES: readonly ReporterEventType[];
11771246

1247+
// @beta
1248+
export const REPORTER_MIGRATION_PHASES: readonly IReporterMigrationPhase[];
1249+
11781250
// @beta
11791251
export const REPORTER_PACKAGE_NAME: '@rushstack/reporter';
11801252

@@ -1232,6 +1304,9 @@ export class ReporterManager implements IReporterEventSink {
12321304
// @beta
12331305
export type ReporterMessageSeverity = 'debug' | 'info' | 'warning' | 'error';
12341306

1307+
// @beta
1308+
export type ReporterMigrationPhaseId = 'contractsAndBaselines' | 'bootstrapAndCompatAdapters' | 'shadowStructuredEmission' | 'optInReporters' | 'heftProtocolTrack' | 'daemonAlignedMajorFlip' | 'laterCleanupMajor';
1309+
12351310
// @beta
12361311
export class ReporterMultiplexer implements IReporter {
12371312
constructor(name: string, reporters: readonly IReporter[]);

libraries/reporter/src/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,3 +291,20 @@ export {
291291
isWithinWallTimeBudget,
292292
isWithinMemoryBudget
293293
} from './perf/PerformanceBudgets';
294+
295+
export type { ReporterMigrationPhaseId, IReporterMigrationPhase } from './migration/MigrationPhase';
296+
export { REPORTER_MIGRATION_PHASES, getReporterMigrationPhase } from './migration/MigrationPhase';
297+
export type {
298+
IReporterMajorDefaults,
299+
IAutomaticSelectionContext
300+
} from './migration/DaemonAlignedMajorDefaults';
301+
export {
302+
REMOVED_TERMINAL_APIS,
303+
PRE_FLIP_REPORTER_DEFAULTS,
304+
DAEMON_ALIGNED_MAJOR_REPORTER_DEFAULTS,
305+
isTerminalApiRemoved,
306+
isEmergencyLegacyFallback,
307+
isAutomaticSelectionEnabled
308+
} from './migration/DaemonAlignedMajorDefaults';
309+
export type { IPluginApplyGateOptions, IPluginApplyDecision } from './migration/PluginApplyGate';
310+
export { evaluatePluginApplyGate, getBlockedPlugins } from './migration/PluginApplyGate';
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
/**
5+
* The reporting defaults that change across a Rush major release boundary.
6+
*
7+
* @remarks
8+
* The daemon-aligned major flips the default behavior described in specification
9+
* §8.1 phase 6. Because every phase is revertible, both the pre-flip and
10+
* post-flip default sets are represented as data so the flip can be rolled back
11+
* to the previous phase's opt-in behavior by swapping the active default set.
12+
*
13+
* @beta
14+
*/
15+
export interface IReporterMajorDefaults {
16+
/**
17+
* Whether environment-based automatic reporter selection is active without an
18+
* explicit opt-in.
19+
*/
20+
readonly automaticSelectionEnabledByDefault: boolean;
21+
22+
/**
23+
* The legacy terminal APIs removed in this major, for example
24+
* `ILogger.terminal`.
25+
*/
26+
readonly removedTerminalApis: readonly string[];
27+
28+
/**
29+
* Whether an incompatible plugin fails before its `apply()` runs.
30+
*/
31+
readonly gateIncompatiblePluginsBeforeApply: boolean;
32+
33+
/**
34+
* Whether the legacy renderer is still available.
35+
*/
36+
readonly legacyRendererRetained: boolean;
37+
38+
/**
39+
* Whether the legacy verbosity aliases (`--quiet`, `--verbose`, `--debug`)
40+
* remain.
41+
*/
42+
readonly verbosityAliasesRetained: boolean;
43+
44+
/**
45+
* Whether the legacy `AlreadyReportedError` sentinel bridge remains.
46+
*/
47+
readonly sentinelBridgeRetained: boolean;
48+
49+
/**
50+
* The environment variable that forces the emergency legacy fallback.
51+
*/
52+
readonly emergencyFallbackEnvVar: string;
53+
54+
/**
55+
* The reporter name that the emergency fallback selects.
56+
*/
57+
readonly emergencyFallbackReporterName: string;
58+
}
59+
60+
/**
61+
* The legacy terminal APIs removed by the daemon-aligned major, per
62+
* specification §5.3.
63+
*
64+
* @beta
65+
*/
66+
export const REMOVED_TERMINAL_APIS: readonly string[] = ['ILogger.terminal', 'RushSession.terminalProvider'];
67+
68+
/**
69+
* The reporting defaults before the daemon-aligned major flip.
70+
*
71+
* @remarks
72+
* Automatic selection is opt-in only, the legacy terminal APIs still exist, and
73+
* incompatible plugins are not gated. The legacy renderer, verbosity aliases,
74+
* and sentinel bridge are retained. Reverting the flip restores these defaults.
75+
*
76+
* @beta
77+
*/
78+
export const PRE_FLIP_REPORTER_DEFAULTS: IReporterMajorDefaults = {
79+
automaticSelectionEnabledByDefault: false,
80+
removedTerminalApis: [],
81+
gateIncompatiblePluginsBeforeApply: false,
82+
legacyRendererRetained: true,
83+
verbosityAliasesRetained: true,
84+
sentinelBridgeRetained: true,
85+
emergencyFallbackEnvVar: 'RUSH_REPORTER',
86+
emergencyFallbackReporterName: 'legacy'
87+
};
88+
89+
/**
90+
* The reporting defaults in the daemon-aligned major release.
91+
*
92+
* @remarks
93+
* Automatic selection is enabled by default, the legacy terminal APIs are
94+
* removed, and incompatible plugins fail before `apply()`. The legacy renderer,
95+
* verbosity aliases, and sentinel bridge are still retained for this major; they
96+
* are removed only in the later cleanup major.
97+
*
98+
* @beta
99+
*/
100+
export const DAEMON_ALIGNED_MAJOR_REPORTER_DEFAULTS: IReporterMajorDefaults = {
101+
automaticSelectionEnabledByDefault: true,
102+
removedTerminalApis: REMOVED_TERMINAL_APIS,
103+
gateIncompatiblePluginsBeforeApply: true,
104+
legacyRendererRetained: true,
105+
verbosityAliasesRetained: true,
106+
sentinelBridgeRetained: true,
107+
emergencyFallbackEnvVar: 'RUSH_REPORTER',
108+
emergencyFallbackReporterName: 'legacy'
109+
};
110+
111+
/**
112+
* Returns `true` if the named legacy terminal API is removed under the given
113+
* defaults.
114+
*
115+
* @param api - the API identifier, for example `ILogger.terminal`
116+
* @param defaults - the defaults to check; defaults to
117+
* {@link DAEMON_ALIGNED_MAJOR_REPORTER_DEFAULTS}
118+
*
119+
* @beta
120+
*/
121+
export function isTerminalApiRemoved(
122+
api: string,
123+
defaults: IReporterMajorDefaults = DAEMON_ALIGNED_MAJOR_REPORTER_DEFAULTS
124+
): boolean {
125+
return defaults.removedTerminalApis.indexOf(api) >= 0;
126+
}
127+
128+
/**
129+
* Returns `true` if the environment requests the emergency legacy fallback,
130+
* for example `RUSH_REPORTER=legacy`.
131+
*
132+
* @param env - the environment variables
133+
* @param defaults - the defaults that name the fallback control; defaults to
134+
* {@link DAEMON_ALIGNED_MAJOR_REPORTER_DEFAULTS}
135+
*
136+
* @beta
137+
*/
138+
export function isEmergencyLegacyFallback(
139+
env: Record<string, string | undefined>,
140+
defaults: IReporterMajorDefaults = DAEMON_ALIGNED_MAJOR_REPORTER_DEFAULTS
141+
): boolean {
142+
return env[defaults.emergencyFallbackEnvVar] === defaults.emergencyFallbackReporterName;
143+
}
144+
145+
/**
146+
* The context used to decide whether automatic reporter selection runs.
147+
*
148+
* @beta
149+
*/
150+
export interface IAutomaticSelectionContext {
151+
/**
152+
* Whether the user explicitly opted in, for example via `--reporter` or
153+
* `RUSH_REPORTER`.
154+
*/
155+
readonly explicitOptIn?: boolean;
156+
157+
/**
158+
* Whether the experimental repository setting enabled the new reporter path.
159+
*/
160+
readonly experimentalSettingEnabled?: boolean;
161+
162+
/**
163+
* Whether the emergency legacy fallback is active.
164+
*/
165+
readonly emergencyLegacyFallback?: boolean;
166+
}
167+
168+
/**
169+
* Determines whether environment-based automatic reporter selection should run.
170+
*
171+
* @remarks
172+
* The emergency legacy fallback always wins. Otherwise, once the daemon-aligned
173+
* major has flipped the default, automatic selection runs unconditionally; before
174+
* the flip it runs only when the user opted in explicitly or through the
175+
* experimental setting.
176+
*
177+
* @param defaults - the active major defaults
178+
* @param context - the opt-in and fallback context
179+
*
180+
* @beta
181+
*/
182+
export function isAutomaticSelectionEnabled(
183+
defaults: IReporterMajorDefaults,
184+
context: IAutomaticSelectionContext = {}
185+
): boolean {
186+
if (context.emergencyLegacyFallback) {
187+
return false;
188+
}
189+
if (defaults.automaticSelectionEnabledByDefault) {
190+
return true;
191+
}
192+
return Boolean(context.explicitOptIn || context.experimentalSettingEnabled);
193+
}

0 commit comments

Comments
 (0)