@gajae-code/utils 0.15.5 → 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.
@@ -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.15.5",
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.15.5",
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",
@@ -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
- return { kind: "occurrence", fingerprint, fpv: body.fpv, recordId: body.id, at, errorName, messageClass };
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 { appendCrashEvent, appendFatalCrashEvent } from "./crash-journal";
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 written = writeCrashRecord(label, describeFatal(reason), options);
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,