@gajae-code/utils 0.15.6 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/types/crash-journal.d.ts +10 -0
- package/dist/types/postmortem.d.ts +2 -0
- package/package.json +2 -2
- package/src/crash-journal.ts +34 -1
- package/src/postmortem.ts +13 -3
|
@@ -4,6 +4,14 @@ export declare const CRASH_EVENT_MAX_BYTES = 512;
|
|
|
4
4
|
export declare const CRASH_EVENT_KIND = "gjc-crash-event.v1";
|
|
5
5
|
/** Preview cap for the message class carried by an event. */
|
|
6
6
|
export declare const CRASH_EVENT_MESSAGE_MAX_BYTES = 256;
|
|
7
|
+
/** Bounded execution provenance carried only on newly written occurrences. */
|
|
8
|
+
export type CrashProvenance = "product" | "eval" | "bun_test";
|
|
9
|
+
/**
|
|
10
|
+
* Classify only process-level harness modes that are explicit and stable.
|
|
11
|
+
* Everything else is product provenance, including source checkouts, CLI
|
|
12
|
+
* invocations, SDK/ACP hosts, and native-loader failures.
|
|
13
|
+
*/
|
|
14
|
+
export declare function detectCrashProvenance(argv?: readonly string[], env?: NodeJS.ProcessEnv, execArgv?: readonly string[]): CrashProvenance;
|
|
7
15
|
export type CrashEvent = CrashOccurrenceEvent | CrashRefusedEvent | CrashReportedEvent | CrashRelayedEvent | CrashAcknowledgedEvent | CrashNudgedEvent;
|
|
8
16
|
export interface CrashOccurrenceEvent {
|
|
9
17
|
readonly kind: "occurrence";
|
|
@@ -13,6 +21,8 @@ export interface CrashOccurrenceEvent {
|
|
|
13
21
|
readonly at: number;
|
|
14
22
|
readonly errorName: string;
|
|
15
23
|
readonly messageClass: string;
|
|
24
|
+
/** Legacy events omit this and are treated as product crashes. */
|
|
25
|
+
readonly provenance?: CrashProvenance;
|
|
16
26
|
}
|
|
17
27
|
export interface CrashRefusedEvent {
|
|
18
28
|
readonly kind: "refused";
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* in response to process exit, signals, or fatal exceptions. It is intended to
|
|
6
6
|
* allow reliably releasing resources or shutting down subprocesses, files, sockets, etc.
|
|
7
7
|
*/
|
|
8
|
+
import type { CrashProvenance } from "./crash-journal";
|
|
8
9
|
import { redactCrashSecrets } from "./crash-redaction";
|
|
9
10
|
export declare enum Reason {
|
|
10
11
|
PRE_EXIT = "pre_exit",// Pre-exit phase (not used by default)
|
|
@@ -61,6 +62,7 @@ export declare function resetHandledErrorDedupeForTest(): void;
|
|
|
61
62
|
interface CrashRecordOptions {
|
|
62
63
|
path?: string;
|
|
63
64
|
now?: Date;
|
|
65
|
+
provenance?: CrashProvenance;
|
|
64
66
|
}
|
|
65
67
|
/**
|
|
66
68
|
* Register a process cleanup callback, to be run on shutdown, signal, or fatal error.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/utils",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.16.0",
|
|
5
5
|
"description": "Shared utilities for pi packages",
|
|
6
6
|
"homepage": "https://gajae-code.com",
|
|
7
7
|
"author": "Yeachan-Heo",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"fmt": "biome format --write ."
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@gajae-code/natives": "0.
|
|
34
|
+
"@gajae-code/natives": "0.16.0",
|
|
35
35
|
"beautiful-mermaid": "^1.1.3",
|
|
36
36
|
"handlebars": "^4.7.9",
|
|
37
37
|
"winston": "^3.19.0",
|
package/src/crash-journal.ts
CHANGED
|
@@ -22,6 +22,24 @@ export const CRASH_EVENT_KIND = "gjc-crash-event.v1";
|
|
|
22
22
|
/** Preview cap for the message class carried by an event. */
|
|
23
23
|
export const CRASH_EVENT_MESSAGE_MAX_BYTES = 256;
|
|
24
24
|
|
|
25
|
+
/** Bounded execution provenance carried only on newly written occurrences. */
|
|
26
|
+
export type CrashProvenance = "product" | "eval" | "bun_test";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Classify only process-level harness modes that are explicit and stable.
|
|
30
|
+
* Everything else is product provenance, including source checkouts, CLI
|
|
31
|
+
* invocations, SDK/ACP hosts, and native-loader failures.
|
|
32
|
+
*/
|
|
33
|
+
export function detectCrashProvenance(
|
|
34
|
+
argv: readonly string[] = process.argv,
|
|
35
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
36
|
+
execArgv: readonly string[] = process.execArgv,
|
|
37
|
+
): CrashProvenance {
|
|
38
|
+
if (env.BUN_TEST !== undefined) return "bun_test";
|
|
39
|
+
if (execArgv[0] === "-e" || execArgv[0] === "--eval" || argv[0] === "-e" || argv[0] === "--eval") return "eval";
|
|
40
|
+
return "product";
|
|
41
|
+
}
|
|
42
|
+
|
|
25
43
|
export type CrashEvent =
|
|
26
44
|
| CrashOccurrenceEvent
|
|
27
45
|
| CrashRefusedEvent
|
|
@@ -38,6 +56,8 @@ export interface CrashOccurrenceEvent {
|
|
|
38
56
|
readonly at: number;
|
|
39
57
|
readonly errorName: string;
|
|
40
58
|
readonly messageClass: string;
|
|
59
|
+
/** Legacy events omit this and are treated as product crashes. */
|
|
60
|
+
readonly provenance?: CrashProvenance;
|
|
41
61
|
}
|
|
42
62
|
|
|
43
63
|
export interface CrashRefusedEvent {
|
|
@@ -109,6 +129,7 @@ export function formatCrashEventLine(event: CrashEvent): string {
|
|
|
109
129
|
id: event.recordId,
|
|
110
130
|
at: event.at,
|
|
111
131
|
n: sanitizeEventText(truncateUtf8(event.errorName, 64)),
|
|
132
|
+
...(event.provenance && event.provenance !== "product" ? { p: event.provenance } : {}),
|
|
112
133
|
...(messageClass === undefined ? {} : { m: messageClass }),
|
|
113
134
|
}
|
|
114
135
|
: event.kind === "reported"
|
|
@@ -181,7 +202,19 @@ export function parseCrashEventLine(line: string): CrashEvent | undefined {
|
|
|
181
202
|
if (typeof body.fpv !== "number" || !Number.isSafeInteger(body.fpv) || body.fpv < 1) return undefined;
|
|
182
203
|
const errorName = typeof body.n === "string" ? sanitizeEventText(body.n) : "Error";
|
|
183
204
|
const messageClass = typeof body.m === "string" ? sanitizeEventText(body.m) : "";
|
|
184
|
-
|
|
205
|
+
const provenance = body.p === undefined ? undefined : body.p;
|
|
206
|
+
if (provenance !== undefined && provenance !== "product" && provenance !== "eval" && provenance !== "bun_test")
|
|
207
|
+
return undefined;
|
|
208
|
+
return {
|
|
209
|
+
kind: "occurrence",
|
|
210
|
+
fingerprint,
|
|
211
|
+
fpv: body.fpv,
|
|
212
|
+
recordId: body.id,
|
|
213
|
+
at,
|
|
214
|
+
errorName,
|
|
215
|
+
messageClass,
|
|
216
|
+
...(provenance === undefined ? {} : { provenance }),
|
|
217
|
+
};
|
|
185
218
|
}
|
|
186
219
|
case "reported": {
|
|
187
220
|
if (!fingerprint) return undefined;
|
package/src/postmortem.ts
CHANGED
|
@@ -13,7 +13,8 @@ import * as path from "node:path";
|
|
|
13
13
|
import { isMainThread } from "node:worker_threads";
|
|
14
14
|
import { BROKEN_PIPE_EXIT_CODE, createProcessStdoutEpipeClassifier } from "./broken-pipe";
|
|
15
15
|
import { type CrashFingerprint, computeCrashFingerprint, formatCrashRecordMarker } from "./crash-fingerprint";
|
|
16
|
-
import {
|
|
16
|
+
import type { CrashProvenance } from "./crash-journal";
|
|
17
|
+
import { appendCrashEvent, appendFatalCrashEvent, detectCrashProvenance } from "./crash-journal";
|
|
17
18
|
import { redactCrashSecrets } from "./crash-redaction";
|
|
18
19
|
import { getCrashEventsPath, getCrashLogPath, getHandledErrorEventsPath, getHandledErrorLogPath } from "./dirs";
|
|
19
20
|
import * as logger from "./logger";
|
|
@@ -460,7 +461,8 @@ function boundCrashRecord(report: string, maxBytes: number = CRASH_RECORD_MAX_BY
|
|
|
460
461
|
* `process.exit`. Returns the path written, or `undefined` on failure.
|
|
461
462
|
*/
|
|
462
463
|
export function recordFatalCrash(label: string, reason: unknown, options: CrashRecordOptions = {}): string | undefined {
|
|
463
|
-
const
|
|
464
|
+
const provenance = detectCrashProvenance();
|
|
465
|
+
const written = writeCrashRecord(label, describeFatal(reason), { ...options, provenance });
|
|
464
466
|
if (!written) return undefined;
|
|
465
467
|
appendFatalCrashEvent(
|
|
466
468
|
{
|
|
@@ -471,12 +473,19 @@ export function recordFatalCrash(label: string, reason: unknown, options: CrashR
|
|
|
471
473
|
at: written.now.getTime(),
|
|
472
474
|
errorName: written.fingerprint.errorName,
|
|
473
475
|
messageClass: written.fingerprint.messageClass,
|
|
476
|
+
provenance,
|
|
474
477
|
},
|
|
475
478
|
getCrashEventsTarget(written.target, options.path),
|
|
476
479
|
);
|
|
477
480
|
return written.target;
|
|
478
481
|
}
|
|
479
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Classify only process-level harness modes that are explicit and stable.
|
|
485
|
+
* Everything else is product provenance, including source checkouts, CLI
|
|
486
|
+
* invocations, SDK/ACP hosts, and native-loader failures.
|
|
487
|
+
*/
|
|
488
|
+
|
|
480
489
|
const handledErrorFingerprints = new Set<string>();
|
|
481
490
|
const HANDLED_ERROR_FINGERPRINT_LIMIT = 256;
|
|
482
491
|
|
|
@@ -552,6 +561,7 @@ export function resetHandledErrorDedupeForTest(): void {
|
|
|
552
561
|
interface CrashRecordOptions {
|
|
553
562
|
path?: string;
|
|
554
563
|
now?: Date;
|
|
564
|
+
provenance?: CrashProvenance;
|
|
555
565
|
}
|
|
556
566
|
|
|
557
567
|
interface WrittenCrashRecord {
|
|
@@ -591,7 +601,7 @@ function writeCrashRecord(
|
|
|
591
601
|
// The marker is the record's identity, so it is budgeted first and appended
|
|
592
602
|
// after truncation: an oversized body can never evict it.
|
|
593
603
|
const body = boundCrashRecord(
|
|
594
|
-
`${now.toISOString()} pid=${process.pid} [${label}] ` +
|
|
604
|
+
`${now.toISOString()} pid=${process.pid} [${label}${options.provenance && options.provenance !== "product" ? `;provenance=${options.provenance}` : ""}] ` +
|
|
595
605
|
`${redactCrashSecrets(fatal.name)}: ${redactCrashSecrets(fatal.message)}\n` +
|
|
596
606
|
`${stack}${payload}`,
|
|
597
607
|
CRASH_RECORD_MAX_BYTES - Buffer.byteLength(markerLine, "utf8") - 1,
|