Skip to content

Commit 0c0b2bf

Browse files
TheLarkInnCopilot
andauthored
Reporter overhaul (5/6): Heft protocol track (negotiated child descriptors) (#5869)
* Add Heft child descriptor integration with raw fallback Integrate Heft as a cross-process reporter producer for @rushstack/reporter (#5858). - Add allocateChildDescriptor and readChildDescriptorFd so Rush passes a dynamically allocated inherited descriptor to the child through a private environment variable while stdout and stderr stay normal streams - Add HeftChildEmitter, which emits structured NDJSON over the descriptor or falls back to raw stdout and stderr when negotiation is unavailable - Add HeftDescriptorHost, which negotiates the child hello and correlates each child event with the parent session and operation ids, surfacing an update-global-Rush diagnostic on rejection - Keep the raw-stream and problem-matcher path for older Heft versions - Cover both the new descriptor and old raw-stream paths with tests Assistant-model: GitHub Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 897dcf7e-e6e8-4a84-85ca-34b93fa29be3 * Add rush change file for Heft descriptor integration Assistant-model: GitHub Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 897dcf7e-e6e8-4a84-85ca-34b93fa29be3 * Add a streaming parent drain for the Heft descriptor channel HeftDescriptorHost gains incremental record processing (processChildRecord) and createStreamProcessor(), which decodes NDJSON chunks as they arrive and forwards accepted events in receipt order. This continuously drains the child pipe (a chatty child no longer blocks on a full OS pipe buffer) and surfaces child progress live, per the design-review realignment. The batch processChildNdjson path is retained for completed streams and tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Harden Heft reporter descriptor protocol Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address Heft descriptor review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 48b21772-7262-40a9-9524-c2b21582d201 * Fix Heft reporter change file package name Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 48b21772-7262-40a9-9524-c2b21582d201 * Document Heft child output streams Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 48b21772-7262-40a9-9524-c2b21582d201 --------- Co-authored-by: Sean Larkin <TheLarkInn@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 897dcf7e-e6e8-4a84-85ca-34b93fa29be3 Copilot-Session: 48b21772-7262-40a9-9524-c2b21582d201
1 parent d0129d8 commit 0c0b2bf

11 files changed

Lines changed: 1485 additions & 3 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@rushstack/rush-reporter",
5+
"comment": "Add Heft integration over a negotiated inherited descriptor: HeftChildEmitter and HeftDescriptorHost with parent/child event correlation, a raw-stream fallback for older Heft, and descriptor allocation helpers",
6+
"type": "minor"
7+
}
8+
],
9+
"packageName": "@rushstack/rush-reporter",
10+
"email": "TheLarkInn@users.noreply.github.com"
11+
}

common/reviews/api/rush-reporter.api.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
55
```ts
66

7+
import type { Readable } from 'node:stream';
8+
import type { Writable } from 'node:stream';
9+
710
// @beta
811
export class AiReporter implements IReporter {
912
constructor(options: IAiReporterOptions);
@@ -19,6 +22,9 @@ export class AiReporter implements IReporter {
1922
report(event: IReporterEventEnvelope<unknown>): void;
2023
}
2124

25+
// @beta
26+
export function allocateChildDescriptor(fdNumber?: number): IChildDescriptorPlan;
27+
2228
// @beta
2329
export const ALREADY_REPORTED_ERROR_NAME: 'AlreadyReportedError';
2430

@@ -167,6 +173,30 @@ export function getPrivacyClassificationRank(classification: ReporterPrivacyClas
167173
// @beta
168174
export function getSignalExitCode(signal: NodeJS.Signals): number;
169175

176+
// @beta
177+
export class HeftChildEmitter {
178+
constructor(options: IHeftChildEmitterOptions);
179+
emitEvent(input: IHeftChildEventInput): string | undefined;
180+
readonly mode: HeftChildReporterMode;
181+
sendHello(): boolean;
182+
writeRaw(stream: 'stdout' | 'stderr', text: string): void;
183+
}
184+
185+
// @beta
186+
export type HeftChildReporterMode = 'structured' | 'raw-fallback';
187+
188+
// @beta
189+
export class HeftDescriptorHost {
190+
constructor(options: IHeftDescriptorHostOptions);
191+
createStreamProcessor(): {
192+
write(chunk: string): void;
193+
flush(): IHeftChildResult;
194+
};
195+
processChildNdjson(ndjson: string): IHeftChildResult;
196+
processChildRecord(record: unknown): boolean;
197+
processChildRecords(records: readonly unknown[]): IHeftChildResult;
198+
}
199+
170200
// @beta
171201
export interface IAiDiagnostic {
172202
// (undocumented)
@@ -297,6 +327,13 @@ export interface IBootstrapTruncation {
297327
readonly truncated: boolean;
298328
}
299329

330+
// @beta
331+
export interface IChildDescriptorPlan {
332+
readonly env: Record<string, string>;
333+
readonly fdNumber: number;
334+
readonly stdio: (string | number)[];
335+
}
336+
300337
// @beta
301338
export interface IClassifiedDiagnosticValue {
302339
readonly privacy: ReporterPrivacyClassification;
@@ -418,6 +455,63 @@ export interface IGetMatchersOptions {
418455
readonly version?: string;
419456
}
420457

458+
// @beta
459+
export interface IHeftChildEmitterOptions {
460+
readonly capabilities?: readonly string[];
461+
readonly childSessionId: string;
462+
readonly env: Record<string, string | undefined>;
463+
readonly now?: () => string;
464+
readonly producerVersion: string;
465+
readonly protocolVersion?: IReporterProtocolVersion;
466+
readonly requiredFeatures?: readonly string[];
467+
readonly source: IReporterEventSource;
468+
readonly writeDescriptor?: (text: string) => void;
469+
readonly writeStderr?: (text: string) => void;
470+
readonly writeStdout?: (text: string) => void;
471+
}
472+
473+
// @beta
474+
export interface IHeftChildEventInput {
475+
// (undocumented)
476+
readonly payload?: unknown;
477+
// (undocumented)
478+
readonly privacy?: 'public' | 'local-sensitive' | 'secret';
479+
// (undocumented)
480+
readonly scope?: IReporterEventScope;
481+
// (undocumented)
482+
readonly type: ReporterEventType;
483+
}
484+
485+
// @beta
486+
export interface IHeftChildOutputStreams {
487+
readonly stderr: Readable | null;
488+
readonly stdout: Readable | null;
489+
}
490+
491+
// @beta
492+
export interface IHeftChildOutputTargets {
493+
readonly stderr: Writable;
494+
readonly stdout: Writable;
495+
}
496+
497+
// @beta
498+
export interface IHeftChildResult {
499+
readonly accepted: boolean;
500+
readonly ack?: IReporterHelloAck;
501+
readonly diagnostic?: IRushDiagnostic;
502+
readonly eventCount: number;
503+
}
504+
505+
// @beta
506+
export interface IHeftDescriptorHostOptions {
507+
readonly forwardEnvelope: (envelope: IReporterEventEnvelope<unknown>) => void;
508+
readonly onNegotiation?: (result: IReporterHandshakeResult) => void;
509+
readonly parentOperationId?: string;
510+
readonly parentSessionId: string;
511+
readonly supportedCapabilities?: readonly string[];
512+
readonly supportedProtocolVersion: IReporterProtocolVersion;
513+
}
514+
421515
// @beta
422516
export interface IInteractiveTerminal {
423517
readonly columns: number;
@@ -1131,9 +1225,15 @@ export function readBootstrapHandoffFileAsync(filePath: string): Promise<{
11311225
discardedRecordCount: number;
11321226
}>;
11331227

1228+
// @beta
1229+
export function readChildDescriptorFd(env: Record<string, string | undefined>): number | undefined;
1230+
11341231
// @beta
11351232
export function regroupOperationOutput(events: readonly IReporterEventEnvelope<unknown>[]): Map<string, string>;
11361233

1234+
// @beta
1235+
export function relayHeftChildOutput(child: IHeftChildOutputStreams, targets?: IHeftChildOutputTargets): void;
1236+
11371237
// @beta
11381238
export function renderActiveProjectsRow(projects: readonly string[], width: number): string;
11391239

@@ -1285,6 +1385,12 @@ export const RUSH_DIAGNOSTIC_CODE_DEFINITIONS: readonly [{
12851385
readonly defaultSeverity: "error";
12861386
readonly summaryKey: "diagnostic.RUSH_PROTOCOL_UPDATE_REQUIRED.summary";
12871387
readonly detailKey: "diagnostic.RUSH_PROTOCOL_UPDATE_REQUIRED.detail";
1388+
}, {
1389+
readonly code: "RUSH_PROTOCOL_INVALID_CHILD_STREAM";
1390+
readonly category: "environment";
1391+
readonly defaultSeverity: "error";
1392+
readonly summaryKey: "diagnostic.RUSH_PROTOCOL_INVALID_CHILD_STREAM.summary";
1393+
readonly detailKey: "diagnostic.RUSH_PROTOCOL_INVALID_CHILD_STREAM.detail";
12881394
}, {
12891395
readonly code: "RUSH_INTERNAL_UNEXPECTED";
12901396
readonly category: "internal";
@@ -1326,6 +1432,9 @@ export const RUSH_REPORTER_BOOTSTRAP_HANDOFF_ENV_VAR: '_RUSH_REPORTER_BOOTSTRAP_
13261432
// @beta
13271433
export const RUSH_REPORTER_BOOTSTRAP_NONCE_ENV_VAR: '_RUSH_REPORTER_BOOTSTRAP_NONCE';
13281434

1435+
// @beta
1436+
export const RUSH_REPORTER_CHILD_FD_ENV_VAR: '_RUSH_REPORTER_CHILD_FD';
1437+
13291438
// @beta
13301439
export const RUSH_REPORTER_ENV_VAR: 'RUSH_REPORTER';
13311440

libraries/reporter/src/diagnostics/RushDiagnosticCodeRegistry.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,13 @@ export const RUSH_DIAGNOSTIC_CODE_DEFINITIONS = defineRushDiagnosticCodeDefiniti
206206
summaryKey: 'diagnostic.RUSH_PROTOCOL_UPDATE_REQUIRED.summary',
207207
detailKey: 'diagnostic.RUSH_PROTOCOL_UPDATE_REQUIRED.detail'
208208
},
209+
{
210+
code: 'RUSH_PROTOCOL_INVALID_CHILD_STREAM',
211+
category: 'environment',
212+
defaultSeverity: 'error',
213+
summaryKey: 'diagnostic.RUSH_PROTOCOL_INVALID_CHILD_STREAM.summary',
214+
detailKey: 'diagnostic.RUSH_PROTOCOL_INVALID_CHILD_STREAM.detail'
215+
},
209216
{
210217
code: RUSH_INTERNAL_ERROR_CODE,
211218
category: 'internal',

libraries/reporter/src/diagnostics/templates/environment.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,8 @@ export const ENVIRONMENT_DIAGNOSTIC_TEMPLATES = {
1515
'diagnostic.RUSH_PROTOCOL_UPDATE_REQUIRED.summary':
1616
'A reporter protocol feature required by {producerVersion} is not supported by this Rush.',
1717
'diagnostic.RUSH_PROTOCOL_UPDATE_REQUIRED.detail':
18-
'The producer advertised protocol major {producerProtocolMajor}. Update your global Rush installation to a version that supports it.'
18+
'The producer advertised protocol major {producerProtocolMajor}. Update your global Rush installation to a version that supports it.',
19+
'diagnostic.RUSH_PROTOCOL_INVALID_CHILD_STREAM.summary':
20+
'A child process sent an invalid reporter protocol stream.',
21+
'diagnostic.RUSH_PROTOCOL_INVALID_CHILD_STREAM.detail': 'The child reporter stream was rejected because {reason}.'
1922
} as const;

0 commit comments

Comments
 (0)