@descryy/runtime-adapter-php 0.0.0 → 0.1.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/LICENSE ADDED
@@ -0,0 +1,6 @@
1
+ Copyright (c) Descry
2
+
3
+ All rights reserved. This software is proprietary and confidential.
4
+ No license, express or implied, to reproduce, distribute, modify, or
5
+ create derivative works is granted except as explicitly agreed in
6
+ writing.
@@ -1,76 +1,35 @@
1
1
  /**
2
- * Laravel's exception-log grammar, which is **not PHP's** (RT-112).
3
- *
4
- * ## The measurement
5
- *
6
- * A real Laravel 12.67.0 application on PHP 8.4.4, spawned through the
7
- * managed lifecycle, failing through its own request pipeline with
8
- * `LOG_CHANNEL=stderr`:
9
- *
10
- * ```
11
- * 64 lines in -> 0 exceptions, 53 lines in a bare STACK_TRACE, 11 logs
12
- * ```
13
- *
14
- * The **mirror image of Rails' defect** (RT-111), which lost everything.
15
- * Laravel loses only the identity — and that is arguably worse to receive,
16
- * because the result looks like working evidence: fifty correct frames with
17
- * no exception class and no message attached to them.
18
- *
19
- * ## Why PHP's classifier half-sees it
20
- *
21
- * Laravel logs through Monolog, not through PHP's own
22
- * `Throwable::__toString()`:
23
- *
24
- * ```
25
- * [2026-08-19 03:45:19] production.ERROR: Trying to access array offset on null {"exception":"[object] (ErrorException(code: 0): Trying to access array offset on null at /app/routes/web.php:9)
26
- * [stacktrace]
27
- * #0 /app/vendor/.../HandleExceptions.php(262): Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError(2, '...')
28
- * #1 /app/routes/web.php(9): ...
29
- * #11 {main}
30
- * "}
31
- * ```
32
- *
33
- * Three differences, of which two are fatal and one is silent:
34
- *
35
- * 1. **The header says `at file:line)` where PHP says `in file:line`.**
36
- * PHP's `HEADER_PATTERN` is `$`-anchored on ` in <file>:<line>`, so
37
- * Laravel's — with the word `at` and a trailing `)` — matches nothing.
38
- * 2. **The bridge is `[stacktrace]`, not `Stack trace:`.**
39
- * 3. **Class names are JSON-escaped.** The trace is written inside a JSON
40
- * string, so `Illuminate\Routing\Router` arrives as
41
- * `Illuminate\\Routing\\Router`. The frames still match PHP's
42
- * `FRAME_PATTERN` — which is why the evidence is not lost — and every
43
- * class name in them is wrong by a backslash, which resolves to no graph
44
- * node while looking entirely correct. RT-103's file-path corruption,
45
- * one field over.
46
- *
47
- * ## Decoding, not rewriting
48
- *
49
- * `\\` is recovered to `\` because the text **is** JSON-escaped and decoding
50
- * it recovers what the process meant. That is the opposite of RT-105's
51
- * decision to leave an `eval` frame's synthetic path alone: there, no
52
- * decoding exists and any "fix" would invent a location; here, the encoding
53
- * is documented and reversible.
54
- *
55
- * Only the callee is decoded. POSIX file paths carry no backslashes, so
56
- * decoding them would be a no-op with a Windows-shaped hazard attached.
57
- *
58
- * ## Laravel's default is worse than any of this, and no parser can fix it
59
- *
60
- * `LOG_CHANNEL` defaults to `stack` -> `single`, which writes
61
- * `storage/logs/laravel.log`. **A collector watching process output sees
62
- * nothing at all** — not a parse failure, no evidence. Setting
63
- * `LOG_CHANNEL=stderr` is a first-class Laravel option and it is a
64
- * *configuration requirement this adapter cannot satisfy on the
65
- * application's behalf*. It belongs in the disclosure to the user, not in a
66
- * pattern.
2
+ * Laravel's exception-log grammar is not PHP's (RT-112).
3
+ *
4
+ * Measured on real Laravel 12.67.0 / PHP 8.4.4 with LOG_CHANNEL=stderr:
5
+ * 64 lines in -> 0 exceptions, 53 lines in a bare STACK_TRACE, 11 logs.
6
+ * Mirror image of Rails' defect (RT-111, which lost everything) — Laravel
7
+ * loses only the identity, arguably worse: fifty correct frames with no
8
+ * exception class or message attached.
9
+ *
10
+ * Laravel logs through Monolog, not Throwable::__toString(). Three
11
+ * differences, two fatal and one silent:
12
+ * 1. Header says `at file:line)` where PHP says `in file:line)` — PHP's
13
+ * `$`-anchored HEADER_PATTERN matches nothing.
14
+ * 2. Bridge is `[stacktrace]`, not `Stack trace:`.
15
+ * 3. Class names are JSON-escaped (`Illuminate\\Routing\\Router`). Frames
16
+ * still match PHP's FRAME_PATTERN so evidence isn't lost, but every
17
+ * class name is wrong by a backslash — resolves to no graph node while
18
+ * looking correct (RT-103's file-path corruption, one field over).
19
+ *
20
+ * `\\` is decoded to `\` because the text is JSON-escaped and decoding
21
+ * recovers what the process meant — opposite of RT-105's eval-frame
22
+ * decision, where no decoding exists and any "fix" would invent a location.
23
+ * Only the callee is decoded; POSIX file paths carry no backslashes.
24
+ *
25
+ * LOG_CHANNEL defaults to stack -> single (storage/logs/laravel.log): a
26
+ * collector watching process output sees nothing at all, not a parse
27
+ * failure. LOG_CHANNEL=stderr is a configuration requirement this adapter
28
+ * can't satisfy for the app — belongs in disclosure, not in a pattern.
67
29
  */
68
30
  /**
69
- * Recovers a JSON-escaped backslash.
70
- *
71
- * `Illuminate\\Routing\\Router` -> `Illuminate\Routing\Router`. Applied only
72
- * to a callee: this is a decode of a known encoding, not a guess, and the
73
- * file paths in the same line are not escaped.
31
+ * Recovers a JSON-escaped backslash: `Illuminate\\Routing\\Router` -> `Illuminate\Routing\Router`.
32
+ * Applied only to a callee — file paths in the same line are not escaped.
74
33
  */
75
34
  export declare function decodeLaravelCallee(callee: string): string;
76
35
  export declare function isLaravelHeaderLine(line: string): boolean;
@@ -82,28 +41,15 @@ export declare function parseLaravelHeader(line: string): {
82
41
  readonly line: number;
83
42
  } | null;
84
43
  /**
85
- * Laravel's classifier: its own header and bridge, PHP's own frames and
86
- * trailer.
87
- *
88
- * The frame and trailer halves are imported rather than restated, because
89
- * the `#N file(line): callee(args)` shape genuinely is PHP's — Monolog
90
- * writes `Throwable::getTraceAsString()` verbatim. Only the framing around
91
- * it is Laravel's. Restating them would be a second copy to keep in step
92
- * with RT-105's closure-name fix.
93
- *
94
- * `isTrailer` is PHP's `#N {main}`, which closes the block and leaves
95
- * Monolog's own `"}` terminator to fall through as a log line — the same
96
- * choice PHP's grammar already makes for its `thrown in ...` trailer, and
97
- * for the same reason: attach what identifies the exception, not everything
98
- * printed after it.
99
- *
100
- * **`isBridge` is mutation-inert, and that is recorded rather than hidden.**
101
- * Replacing `[stacktrace]` with PHP's `Stack trace:` kills no test: RT-082's
102
- * held-message absorption picks the line up instead, and the block still
103
- * forms. It is kept because the line genuinely *is* a bridge and absorbing
104
- * it spends one of `MAX_HELD_LINES` on transport punctuation — but no test
105
- * distinguishes the two, so "every guard is mutation-tested" would be the
106
- * wrong summary of this file.
44
+ * Laravel's classifier: its own header and bridge, PHP's own frames and trailer.
45
+ * Frame/trailer are imported rather than restated — Monolog writes
46
+ * Throwable::getTraceAsString() verbatim, only the framing is Laravel's.
47
+ * `isTrailer` is PHP's `#N {main}`; Monolog's `"}` terminator falls through as a log line.
48
+ *
49
+ * `isBridge` is mutation-inert (recorded, not hidden): replacing `[stacktrace]`
50
+ * with PHP's `Stack trace:` kills no test, since RT-082's held-message
51
+ * absorption picks it up instead. Kept anyway because the line genuinely is
52
+ * a bridge and absorbing it spends a `MAX_HELD_LINES` slot on punctuation.
107
53
  */
108
54
  export declare function createLaravelLineClassifier(): {
109
55
  isHeader(line: string): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"laravel-frame-parsing.d.ts","sourceRoot":"","sources":["../src/laravel-frame-parsing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AAqBH;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,+GAA+G;AAC/G,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,GACX;IAAE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAapH;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,2BAA2B,IAAI;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAClC,CAOA"}
1
+ {"version":3,"file":"laravel-frame-parsing.d.ts","sourceRoot":"","sources":["../src/laravel-frame-parsing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAgBH;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,+GAA+G;AAC/G,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,GACX;IAAE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAapH;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,IAAI;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAClC,CAOA"}
@@ -1,91 +1,45 @@
1
1
  /**
2
- * Laravel's exception-log grammar, which is **not PHP's** (RT-112).
3
- *
4
- * ## The measurement
5
- *
6
- * A real Laravel 12.67.0 application on PHP 8.4.4, spawned through the
7
- * managed lifecycle, failing through its own request pipeline with
8
- * `LOG_CHANNEL=stderr`:
9
- *
10
- * ```
11
- * 64 lines in -> 0 exceptions, 53 lines in a bare STACK_TRACE, 11 logs
12
- * ```
13
- *
14
- * The **mirror image of Rails' defect** (RT-111), which lost everything.
15
- * Laravel loses only the identity — and that is arguably worse to receive,
16
- * because the result looks like working evidence: fifty correct frames with
17
- * no exception class and no message attached to them.
18
- *
19
- * ## Why PHP's classifier half-sees it
20
- *
21
- * Laravel logs through Monolog, not through PHP's own
22
- * `Throwable::__toString()`:
23
- *
24
- * ```
25
- * [2026-08-19 03:45:19] production.ERROR: Trying to access array offset on null {"exception":"[object] (ErrorException(code: 0): Trying to access array offset on null at /app/routes/web.php:9)
26
- * [stacktrace]
27
- * #0 /app/vendor/.../HandleExceptions.php(262): Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError(2, '...')
28
- * #1 /app/routes/web.php(9): ...
29
- * #11 {main}
30
- * "}
31
- * ```
32
- *
33
- * Three differences, of which two are fatal and one is silent:
34
- *
35
- * 1. **The header says `at file:line)` where PHP says `in file:line`.**
36
- * PHP's `HEADER_PATTERN` is `$`-anchored on ` in <file>:<line>`, so
37
- * Laravel's — with the word `at` and a trailing `)` — matches nothing.
38
- * 2. **The bridge is `[stacktrace]`, not `Stack trace:`.**
39
- * 3. **Class names are JSON-escaped.** The trace is written inside a JSON
40
- * string, so `Illuminate\Routing\Router` arrives as
41
- * `Illuminate\\Routing\\Router`. The frames still match PHP's
42
- * `FRAME_PATTERN` — which is why the evidence is not lost — and every
43
- * class name in them is wrong by a backslash, which resolves to no graph
44
- * node while looking entirely correct. RT-103's file-path corruption,
45
- * one field over.
46
- *
47
- * ## Decoding, not rewriting
48
- *
49
- * `\\` is recovered to `\` because the text **is** JSON-escaped and decoding
50
- * it recovers what the process meant. That is the opposite of RT-105's
51
- * decision to leave an `eval` frame's synthetic path alone: there, no
52
- * decoding exists and any "fix" would invent a location; here, the encoding
53
- * is documented and reversible.
54
- *
55
- * Only the callee is decoded. POSIX file paths carry no backslashes, so
56
- * decoding them would be a no-op with a Windows-shaped hazard attached.
57
- *
58
- * ## Laravel's default is worse than any of this, and no parser can fix it
59
- *
60
- * `LOG_CHANNEL` defaults to `stack` -> `single`, which writes
61
- * `storage/logs/laravel.log`. **A collector watching process output sees
62
- * nothing at all** — not a parse failure, no evidence. Setting
63
- * `LOG_CHANNEL=stderr` is a first-class Laravel option and it is a
64
- * *configuration requirement this adapter cannot satisfy on the
65
- * application's behalf*. It belongs in the disclosure to the user, not in a
66
- * pattern.
2
+ * Laravel's exception-log grammar is not PHP's (RT-112).
3
+ *
4
+ * Measured on real Laravel 12.67.0 / PHP 8.4.4 with LOG_CHANNEL=stderr:
5
+ * 64 lines in -> 0 exceptions, 53 lines in a bare STACK_TRACE, 11 logs.
6
+ * Mirror image of Rails' defect (RT-111, which lost everything) — Laravel
7
+ * loses only the identity, arguably worse: fifty correct frames with no
8
+ * exception class or message attached.
9
+ *
10
+ * Laravel logs through Monolog, not Throwable::__toString(). Three
11
+ * differences, two fatal and one silent:
12
+ * 1. Header says `at file:line)` where PHP says `in file:line)` — PHP's
13
+ * `$`-anchored HEADER_PATTERN matches nothing.
14
+ * 2. Bridge is `[stacktrace]`, not `Stack trace:`.
15
+ * 3. Class names are JSON-escaped (`Illuminate\\Routing\\Router`). Frames
16
+ * still match PHP's FRAME_PATTERN so evidence isn't lost, but every
17
+ * class name is wrong by a backslash — resolves to no graph node while
18
+ * looking correct (RT-103's file-path corruption, one field over).
19
+ *
20
+ * `\\` is decoded to `\` because the text is JSON-escaped and decoding
21
+ * recovers what the process meant — opposite of RT-105's eval-frame
22
+ * decision, where no decoding exists and any "fix" would invent a location.
23
+ * Only the callee is decoded; POSIX file paths carry no backslashes.
24
+ *
25
+ * LOG_CHANNEL defaults to stack -> single (storage/logs/laravel.log): a
26
+ * collector watching process output sees nothing at all, not a parse
27
+ * failure. LOG_CHANNEL=stderr is a configuration requirement this adapter
28
+ * can't satisfy for the app — belongs in disclosure, not in a pattern.
67
29
  */
68
30
  import { isPhpFrameLine, isPhpTrailerLine } from "./php-frame-parsing.js";
69
31
  /**
70
32
  * `(ErrorException(code: 0): Trying to access array offset on null at /app/routes/web.php:9)`
71
- *
72
- * Composed by `Illuminate\Log\Logger`'s exception context. The class group
73
- * accepts a namespaced name (`Illuminate\Database\QueryException`) in either
74
- * escaping, since the same shape appears outside a JSON string in some
75
- * channels.
76
- *
77
- * Not anchored at the start: the Monolog line prefix (`[timestamp]
78
- * env.LEVEL: message `) always precedes it, and matching from anywhere is
79
- * how PHP's own header pattern already handles its SAPI prefix.
33
+ * Composed by Illuminate\Log\Logger's exception context. Class group accepts
34
+ * either escaping since the shape appears outside JSON in some channels.
35
+ * Not anchored at the start — the Monolog prefix (`[timestamp] env.LEVEL: message `)
36
+ * always precedes it, same as PHP's own header pattern handling its SAPI prefix.
80
37
  */
81
38
  const LARAVEL_HEADER = /\((?<class>[A-Za-z_\x7f-\xff][\w\\]*)\(code: -?\d+\): (?<message>.*?) at (?<file>[^\s]+):(?<line>\d+)\)/;
82
39
  const LARAVEL_BRIDGE = "[stacktrace]";
83
40
  /**
84
- * Recovers a JSON-escaped backslash.
85
- *
86
- * `Illuminate\\Routing\\Router` -> `Illuminate\Routing\Router`. Applied only
87
- * to a callee: this is a decode of a known encoding, not a guess, and the
88
- * file paths in the same line are not escaped.
41
+ * Recovers a JSON-escaped backslash: `Illuminate\\Routing\\Router` -> `Illuminate\Routing\Router`.
42
+ * Applied only to a callee — file paths in the same line are not escaped.
89
43
  */
90
44
  export function decodeLaravelCallee(callee) {
91
45
  return callee.replace(/\\\\/g, "\\");
@@ -111,28 +65,15 @@ export function parseLaravelHeader(line) {
111
65
  };
112
66
  }
113
67
  /**
114
- * Laravel's classifier: its own header and bridge, PHP's own frames and
115
- * trailer.
116
- *
117
- * The frame and trailer halves are imported rather than restated, because
118
- * the `#N file(line): callee(args)` shape genuinely is PHP's — Monolog
119
- * writes `Throwable::getTraceAsString()` verbatim. Only the framing around
120
- * it is Laravel's. Restating them would be a second copy to keep in step
121
- * with RT-105's closure-name fix.
122
- *
123
- * `isTrailer` is PHP's `#N {main}`, which closes the block and leaves
124
- * Monolog's own `"}` terminator to fall through as a log line — the same
125
- * choice PHP's grammar already makes for its `thrown in ...` trailer, and
126
- * for the same reason: attach what identifies the exception, not everything
127
- * printed after it.
128
- *
129
- * **`isBridge` is mutation-inert, and that is recorded rather than hidden.**
130
- * Replacing `[stacktrace]` with PHP's `Stack trace:` kills no test: RT-082's
131
- * held-message absorption picks the line up instead, and the block still
132
- * forms. It is kept because the line genuinely *is* a bridge and absorbing
133
- * it spends one of `MAX_HELD_LINES` on transport punctuation — but no test
134
- * distinguishes the two, so "every guard is mutation-tested" would be the
135
- * wrong summary of this file.
68
+ * Laravel's classifier: its own header and bridge, PHP's own frames and trailer.
69
+ * Frame/trailer are imported rather than restated — Monolog writes
70
+ * Throwable::getTraceAsString() verbatim, only the framing is Laravel's.
71
+ * `isTrailer` is PHP's `#N {main}`; Monolog's `"}` terminator falls through as a log line.
72
+ *
73
+ * `isBridge` is mutation-inert (recorded, not hidden): replacing `[stacktrace]`
74
+ * with PHP's `Stack trace:` kills no test, since RT-082's held-message
75
+ * absorption picks it up instead. Kept anyway because the line genuinely is
76
+ * a bridge and absorbing it spends a `MAX_HELD_LINES` slot on punctuation.
136
77
  */
137
78
  export function createLaravelLineClassifier() {
138
79
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"laravel-frame-parsing.js","sourceRoot":"","sources":["../src/laravel-frame-parsing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AAEH,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1E;;;;;;;;;;;GAWG;AACH,MAAM,cAAc,GAClB,yGAAyG,CAAC;AAE5G,MAAM,cAAc,GAAG,cAAc,CAAC;AAEtC;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,+GAA+G;AAC/G,MAAM,UAAU,kBAAkB,CAChC,IAAY;IAEZ,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,CAAC,KAAK,EAAE,MAAM;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,IAAI,KAAK,SAAS,IAAI,cAAc,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IACrG,OAAO;QACL,cAAc,EAAE,mBAAmB,CAAC,cAAc,CAAC;QACnD,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE;QACtC,IAAI;QACJ,IAAI,EAAE,UAAU;KACjB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,2BAA2B;IAMzC,OAAO;QACL,QAAQ,EAAE,mBAAmB;QAC7B,OAAO,EAAE,cAAc;QACvB,QAAQ,EAAE,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,cAAc;QACnE,SAAS,EAAE,gBAAgB;KAC5B,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"laravel-frame-parsing.js","sourceRoot":"","sources":["../src/laravel-frame-parsing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1E;;;;;;GAMG;AACH,MAAM,cAAc,GAClB,yGAAyG,CAAC;AAE5G,MAAM,cAAc,GAAG,cAAc,CAAC;AAEtC;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,+GAA+G;AAC/G,MAAM,UAAU,kBAAkB,CAChC,IAAY;IAEZ,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,CAAC,KAAK,EAAE,MAAM;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,IAAI,KAAK,SAAS,IAAI,cAAc,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IACrG,OAAO;QACL,cAAc,EAAE,mBAAmB,CAAC,cAAc,CAAC;QACnD,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE;QACtC,IAAI;QACJ,IAAI,EAAE,UAAU;KACjB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,2BAA2B;IAMzC,OAAO;QACL,QAAQ,EAAE,mBAAmB;QAC7B,OAAO,EAAE,cAAc;QACvB,QAAQ,EAAE,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,cAAc;QACnE,SAAS,EAAE,gBAAgB;KAC5B,CAAC;AACJ,CAAC"}
@@ -1,18 +1,13 @@
1
1
  /**
2
2
  * Composes Laravel's header with PHP's own frames (RT-112).
3
3
  *
4
- * ## Why this is a second parser and not a branch in the PHP one
4
+ * A second parser, not a branch in PHP's one — same reason as RT-111's Rails:
5
+ * the two grammars disagree about what `primaryFrameIndex` indexes, and
6
+ * happening to agree on the answer (both index 0 at the header-derived
7
+ * frame) is coincidence, which is exactly what RT-083 exists to not rely on.
5
8
  *
6
- * Same reason RT-111 gave for Rails: the two grammars disagree about the
7
- * shape of the thing `primaryFrameIndex` indexes. They happen to agree on
8
- * the *answer* — Laravel's header carries `at file:line`, the exception's
9
- * construction site, exactly as PHP's `in file:line` does, so index 0 is the
10
- * header-derived frame in both — and agreeing by coincidence is precisely
11
- * what RT-083 was about. Two parsers each state one answer.
12
- *
13
- * The frames themselves are PHP's, parsed by PHP's parser, because Monolog
14
- * writes `Throwable::getTraceAsString()` verbatim. What differs is the
15
- * framing and the JSON escaping of class names, and both are handled here.
9
+ * Frames themselves are PHP's, parsed by PHP's parser — Monolog writes
10
+ * Throwable::getTraceAsString() verbatim; only framing and JSON-escaping differ.
16
11
  */
17
12
  import type { SourceLocationResolver, StackTraceParser } from "@descryy/runtime-backend-observation";
18
13
  export declare function createLaravelStackTraceParser(resolver: SourceLocationResolver): StackTraceParser;
@@ -1 +1 @@
1
- {"version":3,"file":"laravel-stack-trace-parser.d.ts","sourceRoot":"","sources":["../src/laravel-stack-trace-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAKrG,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,sBAAsB,GAAG,gBAAgB,CAqChG"}
1
+ {"version":3,"file":"laravel-stack-trace-parser.d.ts","sourceRoot":"","sources":["../src/laravel-stack-trace-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAKrG,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,sBAAsB,GAAG,gBAAgB,CAgChG"}
@@ -1,18 +1,13 @@
1
1
  /**
2
2
  * Composes Laravel's header with PHP's own frames (RT-112).
3
3
  *
4
- * ## Why this is a second parser and not a branch in the PHP one
4
+ * A second parser, not a branch in PHP's one — same reason as RT-111's Rails:
5
+ * the two grammars disagree about what `primaryFrameIndex` indexes, and
6
+ * happening to agree on the answer (both index 0 at the header-derived
7
+ * frame) is coincidence, which is exactly what RT-083 exists to not rely on.
5
8
  *
6
- * Same reason RT-111 gave for Rails: the two grammars disagree about the
7
- * shape of the thing `primaryFrameIndex` indexes. They happen to agree on
8
- * the *answer* — Laravel's header carries `at file:line`, the exception's
9
- * construction site, exactly as PHP's `in file:line` does, so index 0 is the
10
- * header-derived frame in both — and agreeing by coincidence is precisely
11
- * what RT-083 was about. Two parsers each state one answer.
12
- *
13
- * The frames themselves are PHP's, parsed by PHP's parser, because Monolog
14
- * writes `Throwable::getTraceAsString()` verbatim. What differs is the
15
- * framing and the JSON escaping of class names, and both are handled here.
9
+ * Frames themselves are PHP's, parsed by PHP's parser — Monolog writes
10
+ * Throwable::getTraceAsString() verbatim; only framing and JSON-escaping differ.
16
11
  */
17
12
  import { decodeLaravelCallee, parseLaravelHeader } from "./laravel-frame-parsing.js";
18
13
  import { parsePhpFrame } from "./php-frame-parsing.js";
@@ -24,9 +19,7 @@ export function createLaravelStackTraceParser(resolver) {
24
19
  for (const line of lines) {
25
20
  const header = parseLaravelHeader(line);
26
21
  if (header !== null) {
27
- // The construction site. `functionName` is null because Laravel,
28
- // like PHP itself, names no function for it — not because it could
29
- // not be parsed.
22
+ // functionName null: Laravel, like PHP, names no function for the construction site.
30
23
  const location = await resolver.resolve({ file: header.file, line: header.line, column: null, functionName: null });
31
24
  frames.push({ location, raw: line });
32
25
  continue;
@@ -38,10 +31,7 @@ export function createLaravelStackTraceParser(resolver) {
38
31
  file: raw.file,
39
32
  line: raw.line,
40
33
  column: null,
41
- // Decoded, not rewritten: the trace lives inside a JSON string, so
42
- // `Illuminate\\Routing\\Router` IS `Illuminate\Routing\Router` and
43
- // recovering it is reading rather than guessing. Left encoded it
44
- // resolves to no graph node while looking correct.
34
+ // Decoded not rewritten: left JSON-escaped it resolves to no graph node while looking correct.
45
35
  functionName: raw.functionName === null ? null : decodeLaravelCallee(raw.functionName),
46
36
  });
47
37
  frames.push({ location, raw: line });
@@ -1 +1 @@
1
- {"version":3,"file":"laravel-stack-trace-parser.js","sourceRoot":"","sources":["../src/laravel-stack-trace-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrF,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,MAAM,UAAU,6BAA6B,CAAC,QAAgC;IAC5E,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,OAAe;YACzB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC1C,MAAM,MAAM,GAAiB,EAAE,CAAC;YAEhC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;gBACxC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;oBACpB,iEAAiE;oBACjE,mEAAmE;oBACnE,iBAAiB;oBACjB,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;oBACpH,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;oBACrC,SAAS;gBACX,CAAC;gBAED,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;gBAChC,IAAI,GAAG,KAAK,IAAI;oBAAE,SAAS;gBAE3B,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;oBACtC,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,MAAM,EAAE,IAAI;oBACZ,mEAAmE;oBACnE,mEAAmE;oBACnE,iEAAiE;oBACjE,mDAAmD;oBACnD,YAAY,EAAE,GAAG,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,GAAG,CAAC,YAAY,CAAC;iBACvF,CAAC,CAAC;gBACH,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YACvC,CAAC;YAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YACrC,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;QACnE,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"laravel-stack-trace-parser.js","sourceRoot":"","sources":["../src/laravel-stack-trace-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrF,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,MAAM,UAAU,6BAA6B,CAAC,QAAgC;IAC5E,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,OAAe;YACzB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC1C,MAAM,MAAM,GAAiB,EAAE,CAAC;YAEhC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;gBACxC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;oBACpB,qFAAqF;oBACrF,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;oBACpH,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;oBACrC,SAAS;gBACX,CAAC;gBAED,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;gBAChC,IAAI,GAAG,KAAK,IAAI;oBAAE,SAAS;gBAE3B,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;oBACtC,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,MAAM,EAAE,IAAI;oBACZ,+FAA+F;oBAC/F,YAAY,EAAE,GAAG,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,GAAG,CAAC,YAAY,CAAC;iBACvF,CAAC,CAAC;gBACH,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YACvC,CAAC;YAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YACrC,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;QACnE,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -1,74 +1,37 @@
1
1
  /**
2
- * PHP uncaught-`Throwable` trace line shapes — the `LineClassifier` for
3
- * `ExceptionBlockDetector` plus the raw-text-to-frame-parts extraction that
4
- * feeds `SourceLocationResolver`.
2
+ * PHP uncaught-Throwable trace line shapes — LineClassifier for
3
+ * ExceptionBlockDetector plus raw-text-to-frame-parts extraction feeding
4
+ * SourceLocationResolver.
5
5
  *
6
- * Grammar verified against `php-src`'s own `Zend/zend_exceptions.c` — the C
7
- * source that generates this text (`build_trace_string`,
8
- * `zend_trace_to_string`, and the default `Throwable::__toString`
9
- * composition around line 780) — and against `php-src`'s own `.phpt`
10
- * conformance suite, the same category of interpreter-verified source
11
- * `ruby/spec` was for RT-038. See `test/fixtures/PROVENANCE.md` for the
12
- * specific citations.
6
+ * Grammar verified against php-src's Zend/zend_exceptions.c (build_trace_string,
7
+ * zend_trace_to_string, Throwable::__toString) and its .phpt conformance suite.
8
+ * `php-call-shapes.test.ts` also spawns real php 8.4.4 and asserts against its
9
+ * stdout — that upgrade is what found RT-105 (older-grammar assumption below).
13
10
  *
14
- * **A real `php` runs against this now** — `php-call-shapes.test.ts` spawns
15
- * 8.4.4 and asserts against its stdout. That upgrade is what found RT-105:
16
- * the engine-source reading above was correct about the grammar it read and
17
- * was reading the *older* grammar, and PHP 8.4 had since put parentheses
18
- * inside a function name. A format read from the generator is only as
19
- * current as the generator you read.
11
+ * PHP's shape is distinct from V8/CPython/Ruby:
20
12
  *
21
- * PHP's shape is its own, distinct from V8, CPython and Ruby alike:
22
- *
23
- * 1. **The header carries a real location, but it is a separate line from
24
- * the first frame, with `Stack trace:` sitting between them.**
25
- * `Fatal error: Uncaught RuntimeException: invoice 3 is corrupt in
26
- * /app/InvoiceService.php:15` names the exception AND its construction
27
- * site (`Throwable::getFile()`/`getLine()` — the near-universal `throw
28
- * new X(...)` pattern makes this the throw site, though strictly it is
29
- * where the exception object was `new`-ed, which can differ if a caller
30
- * holds a `Throwable` across lines before throwing it). That makes it a
31
- * genuine, un-fabricated frame — same reasoning as RT-037's header-only
32
- * Rust panic, `functionName: null` because PHP names no function for
33
- * this specific location — but the literal `Stack trace:` line always
34
- * sits between the header and the first real `#N` frame.
35
- * `createPhpLineClassifier` reports this via `LineClassifier.isBridge`
36
- * (RT-048): a lone `Stack trace:` line is never even offered to
37
- * `isBridge` — `ExceptionBlockDetector` only consults it while a header
38
- * is already being held — so an application log line that happens to
39
- * print the same phrase for an unrelated reason cannot open anything on
40
- * its own, and this classifier no longer needs to track that itself.
41
- * 2. **A real frame carries a call-type marker PHP itself distinguishes:**
42
- * `Class->method()` for an instance call, `Class::method()` for a
43
- * static one (verified verbatim against `Zend/tests/bug65911.phpt`'s
44
- * `B->go()` and `Zend/tests/inheritance/bug44141.phpt`'s `Y::cheat(5)`).
45
- * Both are kept exactly as printed, never normalised to one form.
46
- * 3. **A frame can have no location at all.** `#N [internal function]:
47
- * Class::method(args)` — a call made through `array_map`,
48
- * `call_user_func` or similar, where PHP genuinely has no file/line to
49
- * report — confirmed against `Zend/tests/bug74164.phpt`. This is exactly
50
- * the case RT-037 widened `RawFrameLocation.file`/`.line` to `| null`
51
- * for; both are `null` here rather than a fabricated location.
52
- * 4. **`#N {main}` ends the trace and is a trailer, not a frame** — it has
53
- * no trailing `(args)`, so `FRAME_PATTERN` never matches it, and
54
- * `isTrailer` closes the block on it. What follows (` thrown in FILE
55
- * on line N`, present on the CLI/log-formatted output) is real but
56
- * entirely redundant with the header's own file:line, so it is left to
57
- * fall through as a plain log line rather than special-cased into the
58
- * block — same choice CPython's `isTrailer` design already established
59
- * for "attach only what identifies the exception, not everything after
60
- * it."
61
- * 5. **Chained exceptions (`Throwable::getPrevious()`) need no
62
- * connective-exclusion logic**, unlike CPython's. `#N {main}` closes the
63
- * first block cleanly; the next line is a blank line (an honest, empty
64
- * log event) and then `Next Class: message in file:line` — which
65
- * `HEADER_PATTERN`'s lack of start-of-line anchoring matches without
66
- * the literal word `Next` needing any special handling at all, the same
67
- * way V8's and Ruby's non-anchored header patterns handle a prefix they
68
- * were never written to expect. Verified against
69
- * `Zend/tests/throwable_001.phpt`, including the detail that only the
70
- * **outermost** exception's `thrown in ... on line N` trailer appears —
71
- * once, at the very end, not once per link.
13
+ * 1. Header carries a real location, but on a separate line from the first
14
+ * frame with `Stack trace:` between them: `Fatal error: Uncaught
15
+ * RuntimeException: ... in /app/InvoiceService.php:15` names the exception's
16
+ * construction site (functionName null, since PHP names no function there).
17
+ * `Stack trace:` is reported via LineClassifier.isBridge (RT-048), only
18
+ * consulted while a header is held — an unrelated log line printing the
19
+ * same phrase can't open anything on its own.
20
+ * 2. A real frame carries `Class->method()` (instance) or `Class::method()`
21
+ * (static), verified against Zend/tests/bug65911.phpt and
22
+ * inheritance/bug44141.phpt — kept exactly as printed, never normalised.
23
+ * 3. A frame can have no location: `#N [internal function]: Class::method(args)`
24
+ * (call through array_map/call_user_func), confirmed against
25
+ * Zend/tests/bug74164.phpt. file/line both null, never fabricated.
26
+ * 4. `#N {main}` ends the trace as a trailer, not a frame — no trailing `(args)`,
27
+ * so FRAME_PATTERN never matches it and isTrailer closes the block. The
28
+ * following `thrown in FILE on line N` is redundant with the header and
29
+ * left as a plain log line.
30
+ * 5. Chained exceptions need no connective-exclusion logic, unlike CPython's:
31
+ * `#N {main}` closes the block, then a blank line, then `Next Class: message
32
+ * in file:line` — matched by HEADER_PATTERN's lack of start-of-line
33
+ * anchoring with no special handling for `Next`. Verified against
34
+ * Zend/tests/throwable_001.phpt: only the outermost exception's trailer appears, once.
72
35
  */
73
36
  export interface RawPhpFrame {
74
37
  /** `Class->method`, `Class::method`, a bare function name, or `{closure:file:line}` — kept verbatim, never rewritten. */
@@ -82,19 +45,10 @@ export declare function isPhpHeaderLine(line: string): boolean;
82
45
  export declare function isPhpFrameLine(line: string): boolean;
83
46
  export declare function isPhpTrailerLine(line: string): boolean;
84
47
  /**
85
- * Stateless (RT-048): the single `Stack trace:` line that separates a
86
- * header from its first real frame is reported via `isBridge`, which
87
- * `ExceptionBlockDetector` only ever consults while a header is already
88
- * being held — never from idle. A `Stack trace:` line with no preceding
89
- * held header therefore can't reach this method at all, so recognising it
90
- * unconditionally here is exactly as safe as PHP's previous hand-gated
91
- * boolean, without the classifier having to track "did I just hold a
92
- * header" itself.
93
- *
94
- * Before RT-048, this correctness property depended on a boolean this
95
- * classifier tracked and reset itself (`awaitingStackTraceMarker`); it is
96
- * now a structural property of `ExceptionBlockDetector`'s own state
97
- * machine instead — see `LineClassifier.isBridge`'s doc comment.
48
+ * Stateless (RT-048): `isBridge` is only ever consulted while a header is
49
+ * already held, so an unrelated `Stack trace:` line can't reach this method —
50
+ * recognising it unconditionally here is as safe as PHP's previous hand-gated
51
+ * boolean, without the classifier tracking that state itself.
98
52
  */
99
53
  export declare function createPhpLineClassifier(): {
100
54
  isHeader(line: string): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"php-frame-parsing.d.ts","sourceRoot":"","sources":["../src/php-frame-parsing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AAEH,MAAM,WAAW,WAAW;IAC1B,yHAAyH;IACzH,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,4FAA4F;IAC5F,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,qGAAqG;IACrG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AA+DD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,IAAI;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAClC,CAkBA;AAED,gHAAgH;AAChH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAQpE;AAED,yHAAyH;AACzH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAe9D"}
1
+ {"version":3,"file":"php-frame-parsing.d.ts","sourceRoot":"","sources":["../src/php-frame-parsing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,MAAM,WAAW,WAAW;IAC1B,yHAAyH;IACzH,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,4FAA4F;IAC5F,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,qGAAqG;IACrG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAoCD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAClC,CAkBA;AAED,gHAAgH;AAChH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAQpE;AAED,yHAAyH;AACzH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAe9D"}
@@ -1,128 +1,64 @@
1
1
  /**
2
- * PHP uncaught-`Throwable` trace line shapes — the `LineClassifier` for
3
- * `ExceptionBlockDetector` plus the raw-text-to-frame-parts extraction that
4
- * feeds `SourceLocationResolver`.
2
+ * PHP uncaught-Throwable trace line shapes — LineClassifier for
3
+ * ExceptionBlockDetector plus raw-text-to-frame-parts extraction feeding
4
+ * SourceLocationResolver.
5
5
  *
6
- * Grammar verified against `php-src`'s own `Zend/zend_exceptions.c` — the C
7
- * source that generates this text (`build_trace_string`,
8
- * `zend_trace_to_string`, and the default `Throwable::__toString`
9
- * composition around line 780) — and against `php-src`'s own `.phpt`
10
- * conformance suite, the same category of interpreter-verified source
11
- * `ruby/spec` was for RT-038. See `test/fixtures/PROVENANCE.md` for the
12
- * specific citations.
6
+ * Grammar verified against php-src's Zend/zend_exceptions.c (build_trace_string,
7
+ * zend_trace_to_string, Throwable::__toString) and its .phpt conformance suite.
8
+ * `php-call-shapes.test.ts` also spawns real php 8.4.4 and asserts against its
9
+ * stdout — that upgrade is what found RT-105 (older-grammar assumption below).
13
10
  *
14
- * **A real `php` runs against this now** — `php-call-shapes.test.ts` spawns
15
- * 8.4.4 and asserts against its stdout. That upgrade is what found RT-105:
16
- * the engine-source reading above was correct about the grammar it read and
17
- * was reading the *older* grammar, and PHP 8.4 had since put parentheses
18
- * inside a function name. A format read from the generator is only as
19
- * current as the generator you read.
11
+ * PHP's shape is distinct from V8/CPython/Ruby:
20
12
  *
21
- * PHP's shape is its own, distinct from V8, CPython and Ruby alike:
22
- *
23
- * 1. **The header carries a real location, but it is a separate line from
24
- * the first frame, with `Stack trace:` sitting between them.**
25
- * `Fatal error: Uncaught RuntimeException: invoice 3 is corrupt in
26
- * /app/InvoiceService.php:15` names the exception AND its construction
27
- * site (`Throwable::getFile()`/`getLine()` — the near-universal `throw
28
- * new X(...)` pattern makes this the throw site, though strictly it is
29
- * where the exception object was `new`-ed, which can differ if a caller
30
- * holds a `Throwable` across lines before throwing it). That makes it a
31
- * genuine, un-fabricated frame — same reasoning as RT-037's header-only
32
- * Rust panic, `functionName: null` because PHP names no function for
33
- * this specific location — but the literal `Stack trace:` line always
34
- * sits between the header and the first real `#N` frame.
35
- * `createPhpLineClassifier` reports this via `LineClassifier.isBridge`
36
- * (RT-048): a lone `Stack trace:` line is never even offered to
37
- * `isBridge` — `ExceptionBlockDetector` only consults it while a header
38
- * is already being held — so an application log line that happens to
39
- * print the same phrase for an unrelated reason cannot open anything on
40
- * its own, and this classifier no longer needs to track that itself.
41
- * 2. **A real frame carries a call-type marker PHP itself distinguishes:**
42
- * `Class->method()` for an instance call, `Class::method()` for a
43
- * static one (verified verbatim against `Zend/tests/bug65911.phpt`'s
44
- * `B->go()` and `Zend/tests/inheritance/bug44141.phpt`'s `Y::cheat(5)`).
45
- * Both are kept exactly as printed, never normalised to one form.
46
- * 3. **A frame can have no location at all.** `#N [internal function]:
47
- * Class::method(args)` — a call made through `array_map`,
48
- * `call_user_func` or similar, where PHP genuinely has no file/line to
49
- * report — confirmed against `Zend/tests/bug74164.phpt`. This is exactly
50
- * the case RT-037 widened `RawFrameLocation.file`/`.line` to `| null`
51
- * for; both are `null` here rather than a fabricated location.
52
- * 4. **`#N {main}` ends the trace and is a trailer, not a frame** — it has
53
- * no trailing `(args)`, so `FRAME_PATTERN` never matches it, and
54
- * `isTrailer` closes the block on it. What follows (` thrown in FILE
55
- * on line N`, present on the CLI/log-formatted output) is real but
56
- * entirely redundant with the header's own file:line, so it is left to
57
- * fall through as a plain log line rather than special-cased into the
58
- * block — same choice CPython's `isTrailer` design already established
59
- * for "attach only what identifies the exception, not everything after
60
- * it."
61
- * 5. **Chained exceptions (`Throwable::getPrevious()`) need no
62
- * connective-exclusion logic**, unlike CPython's. `#N {main}` closes the
63
- * first block cleanly; the next line is a blank line (an honest, empty
64
- * log event) and then `Next Class: message in file:line` — which
65
- * `HEADER_PATTERN`'s lack of start-of-line anchoring matches without
66
- * the literal word `Next` needing any special handling at all, the same
67
- * way V8's and Ruby's non-anchored header patterns handle a prefix they
68
- * were never written to expect. Verified against
69
- * `Zend/tests/throwable_001.phpt`, including the detail that only the
70
- * **outermost** exception's `thrown in ... on line N` trailer appears —
71
- * once, at the very end, not once per link.
13
+ * 1. Header carries a real location, but on a separate line from the first
14
+ * frame with `Stack trace:` between them: `Fatal error: Uncaught
15
+ * RuntimeException: ... in /app/InvoiceService.php:15` names the exception's
16
+ * construction site (functionName null, since PHP names no function there).
17
+ * `Stack trace:` is reported via LineClassifier.isBridge (RT-048), only
18
+ * consulted while a header is held — an unrelated log line printing the
19
+ * same phrase can't open anything on its own.
20
+ * 2. A real frame carries `Class->method()` (instance) or `Class::method()`
21
+ * (static), verified against Zend/tests/bug65911.phpt and
22
+ * inheritance/bug44141.phpt — kept exactly as printed, never normalised.
23
+ * 3. A frame can have no location: `#N [internal function]: Class::method(args)`
24
+ * (call through array_map/call_user_func), confirmed against
25
+ * Zend/tests/bug74164.phpt. file/line both null, never fabricated.
26
+ * 4. `#N {main}` ends the trace as a trailer, not a frame — no trailing `(args)`,
27
+ * so FRAME_PATTERN never matches it and isTrailer closes the block. The
28
+ * following `thrown in FILE on line N` is redundant with the header and
29
+ * left as a plain log line.
30
+ * 5. Chained exceptions need no connective-exclusion logic, unlike CPython's:
31
+ * `#N {main}` closes the block, then a blank line, then `Next Class: message
32
+ * in file:line` — matched by HEADER_PATTERN's lack of start-of-line
33
+ * anchoring with no special handling for `Next`. Verified against
34
+ * Zend/tests/throwable_001.phpt: only the outermost exception's trailer appears, once.
72
35
  */
73
36
  const STACK_TRACE_MARKER = "Stack trace:";
74
37
  /**
75
- * Not anchored to the start of the line, for the same reason V8's and
76
- * Ruby's header patterns aren't: PHP's own "Fatal error: " / "PHP Fatal
77
- * error: " SAPI prefix, an application logger's own prefix, and the
78
- * chained-exception "Next " marker are all real prefixes this must see
79
- * through. The greedy `message` group combined with the `$`-anchored
80
- * `in file:line` suffix correctly resolves to the LAST such suffix on the
81
- * line even when the message text itself contains the substring " in ".
38
+ * Not anchored to the start of the line: PHP's "Fatal error: " SAPI prefix,
39
+ * a logger's own prefix, and the chained-exception "Next " marker must all
40
+ * be seen through. Greedy `message` plus the `$`-anchored `in file:line`
41
+ * suffix resolves to the LAST such suffix even if the message contains " in ".
82
42
  */
83
43
  const HEADER_PATTERN = /(?<class>[A-Za-z_\x7f-\xff][\w\\]*)(?::\s(?<message>.*))?\s+in\s+(?<file>.+):(?<line>\d+)$/;
84
44
  /**
85
45
  * `#N file(line): [Class-><or::>]function(args)` or
86
46
  * `#N [internal function]: [Class-><or::>]function(args)`. Requiring a
87
- * trailing `(...)` is what excludes `#N {main}` from this pattern, so no
88
- * separate exclusion is needed. Args can themselves contain parens
89
- * (`Object(Closure)`, a nested call's own printed form) and are not
90
- * otherwise parsed; they stay in `raw` for audit.
91
- *
92
- * ## Why the callee is not simply `[^(]+` (RT-105)
93
- *
94
- * It was, and **PHP 8.4 put parentheses inside a function name.** The
95
- * "Closures in stack traces" change (php-src #12178, shipped 2024-11-21)
96
- * renamed a closure declared inside a method from `{closure}` to
97
- * `{closure:Class::method():LINE}`. Measured against real `php 8.4.4`:
98
- *
99
- * ```
100
- * #0 /app/Repo.php(2): Repo->{closure:Repo::load():5}()
101
- * ```
47
+ * trailing `(...)` excludes `#N {main}` from this pattern. Args can contain
48
+ * parens (`Object(Closure)`) and are kept in `raw`, not parsed.
102
49
  *
103
- * `[^(]+` stops at the `(` in `load()`, so the callee parsed as
104
- * `Repo->{closure:Repo::load` — the line still matched `isFrame`, still
105
- * carried the right file and line, and reported a **truncated, malformed
106
- * function name with full confidence.** Not lost evidence: wrong evidence,
107
- * which is the class this adapter's cross-language controls exist to ban.
108
- * PHP < 8.4 printed a bare `{closure}` and could not reach it.
50
+ * Callee is not simply `[^(]+` (RT-105): PHP 8.4's closures-in-stack-traces
51
+ * change (php-src #12178) renamed `{closure}` to `{closure:Class::method():LINE}`,
52
+ * e.g. `Repo->{closure:Repo::load():5}()`. `[^(]+` stopped at `load()`'s paren
53
+ * and silently produced a truncated, malformed name with full confidence —
54
+ * wrong evidence, not lost evidence. Fix: `{closure:...}` is matched whole
55
+ * first in the alternation (must come first, or `[^(]` would consume the `{`).
56
+ * Greedy `.+` was rejected too — measured on `handler(Object(Closure))`, it
57
+ * backtracks to the last `(` and breaks the nested-args case.
109
58
  *
110
- * The fix is a grammar, not a looser pattern. A `{closure:...}` block is
111
- * the one place a *name* may legitimately contain a paren, so it is matched
112
- * whole and everything else stays `[^(]`. It must come first in the
113
- * alternation: `[^(]` would otherwise consume the `{` and leave the engine
114
- * mid-token when it reached the inner paren.
115
- *
116
- * Greedy `.+` was the obvious alternative and is wrong — measured, not
117
- * assumed: on `handler(Object(Closure))` it backtracks to the *last* `(`
118
- * and yields the callee `handler(Object`, breaking the nested-args case
119
- * `[^(]+` was written for in the first place.
120
- *
121
- * **Not changed, because it was measured and holds:** the greedy `file`
122
- * group looks like it should mis-split on a string argument containing
123
- * `(N): ` — `audit('order(12): x')` — and does not. The engine tries that
124
- * split first, fails to satisfy the callee-plus-args tail, and backtracks
125
- * to the real one. Recorded because the next reader will suspect it too.
59
+ * The greedy `file` group looks like it should mis-split on a string argument
60
+ * containing `(N): ` (`audit('order(12): x')`) — measured, it doesn't: the
61
+ * engine backtracks to the real split after failing the callee-plus-args tail.
126
62
  */
127
63
  const FRAME_PATTERN = /^#\d+\s+(?:(?<file>.+)\((?<line>\d+)\):\s|\[internal function\]:\s)(?<callee>(?:\{closure:[^}]*\}|[^(])+)\(.*\)\s*$/;
128
64
  const TRAILER_PATTERN = /^#\d+\s+\{main\}\s*$/;
@@ -136,19 +72,10 @@ export function isPhpTrailerLine(line) {
136
72
  return TRAILER_PATTERN.test(line);
137
73
  }
138
74
  /**
139
- * Stateless (RT-048): the single `Stack trace:` line that separates a
140
- * header from its first real frame is reported via `isBridge`, which
141
- * `ExceptionBlockDetector` only ever consults while a header is already
142
- * being held — never from idle. A `Stack trace:` line with no preceding
143
- * held header therefore can't reach this method at all, so recognising it
144
- * unconditionally here is exactly as safe as PHP's previous hand-gated
145
- * boolean, without the classifier having to track "did I just hold a
146
- * header" itself.
147
- *
148
- * Before RT-048, this correctness property depended on a boolean this
149
- * classifier tracked and reset itself (`awaitingStackTraceMarker`); it is
150
- * now a structural property of `ExceptionBlockDetector`'s own state
151
- * machine instead — see `LineClassifier.isBridge`'s doc comment.
75
+ * Stateless (RT-048): `isBridge` is only ever consulted while a header is
76
+ * already held, so an unrelated `Stack trace:` line can't reach this method —
77
+ * recognising it unconditionally here is as safe as PHP's previous hand-gated
78
+ * boolean, without the classifier tracking that state itself.
152
79
  */
153
80
  export function createPhpLineClassifier() {
154
81
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"php-frame-parsing.js","sourceRoot":"","sources":["../src/php-frame-parsing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AAWH,MAAM,kBAAkB,GAAG,cAAc,CAAC;AAE1C;;;;;;;;GAQG;AACH,MAAM,cAAc,GAAG,4FAA4F,CAAC;AAEpH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,aAAa,GACjB,qHAAqH,CAAC;AAExH,MAAM,eAAe,GAAG,sBAAsB,CAAC;AAE/C,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,uBAAuB;IAMrC,OAAO;QACL,QAAQ,CAAC,IAAY;YACnB,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;QAED,OAAO,CAAC,IAAY;YAClB,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;QAED,QAAQ,CAAC,IAAY;YACnB,OAAO,IAAI,CAAC,IAAI,EAAE,KAAK,kBAAkB,CAAC;QAC5C,CAAC;QAED,SAAS,CAAC,IAAY;YACpB,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,gHAAgH;AAChH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,CAAC,KAAK,EAAE,MAAM;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IACrE,mEAAmE;IACnE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AACxD,CAAC;AAED,yHAAyH;AACzH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,CAAC,KAAK,EAAE,MAAM;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAEnC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,4EAA4E;QAC5E,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAClD,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/C,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AAClD,CAAC"}
1
+ {"version":3,"file":"php-frame-parsing.js","sourceRoot":"","sources":["../src/php-frame-parsing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAWH,MAAM,kBAAkB,GAAG,cAAc,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,cAAc,GAAG,4FAA4F,CAAC;AAEpH;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,aAAa,GACjB,qHAAqH,CAAC;AAExH,MAAM,eAAe,GAAG,sBAAsB,CAAC;AAE/C,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACpC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB;IAMrC,OAAO;QACL,QAAQ,CAAC,IAAY;YACnB,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;QAED,OAAO,CAAC,IAAY;YAClB,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;QAED,QAAQ,CAAC,IAAY;YACnB,OAAO,IAAI,CAAC,IAAI,EAAE,KAAK,kBAAkB,CAAC;QAC5C,CAAC;QAED,SAAS,CAAC,IAAY;YACpB,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,gHAAgH;AAChH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,CAAC,KAAK,EAAE,MAAM;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IACrE,mEAAmE;IACnE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AACxD,CAAC;AAED,yHAAyH;AACzH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,CAAC,KAAK,EAAE,MAAM;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAEnC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,4EAA4E;QAC5E,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAClD,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/C,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AAClD,CAAC"}
@@ -1,33 +1,24 @@
1
1
  /**
2
- * The concrete `RuntimeAdapter` for PHP.
2
+ * The concrete RuntimeAdapter for PHP.
3
3
  *
4
- * PHP's grammar needed no seam changes beyond RT-037's already-nullable
5
- * `SourceLocation`/`RawFrameLocation` fields, which an `[internal function]`
6
- * frame needs but did not itself force — that widening already existed by
7
- * the time this adapter was built. See `php-frame-parsing.ts` for the one
8
- * behaviour this language DID need from the classifier: recognising the
9
- * single `Stack trace:` line that separates PHP's header from its first
10
- * real frame, and only the one that follows a header just held.
4
+ * Needed no seam changes beyond RT-037's already-nullable SourceLocation/
5
+ * RawFrameLocation fields (needed for `[internal function]` frames). See
6
+ * php-frame-parsing.ts for the one classifier behavior PHP does need:
7
+ * recognising the `Stack trace:` line that follows a just-held header.
11
8
  */
12
9
  import type { RuntimeAdapter } from "@descryy/runtime-backend-observation";
13
10
  export interface PhpRuntimeAdapterOptions {
14
11
  /** Attributed on every emitted Evidence's `service` field (plan §16.7). Omit when not applicable. */
15
12
  readonly service?: string;
16
13
  /**
17
- * The framework whose exception logging this process uses (§16, RT-112).
14
+ * The framework whose exception logging this process uses (RT-112).
15
+ * Declared, never sniffed, same rule as ASP.NET's/Rails' options. Laravel
16
+ * logs through Monolog, not Throwable::__toString() — different header,
17
+ * bridge (`[stacktrace]`), and JSON-escaped class names. Measured live:
18
+ * 0 exceptions, a bare 53-line STACK_TRACE with no identity attached.
18
19
  *
19
- * **Declared, never sniffed**, the same rule ASP.NET's and Rails' options
20
- * follow. Laravel logs through Monolog rather than through PHP's own
21
- * `Throwable::__toString()`: the header reads `(Class(code: 0): message at
22
- * file:line)` where PHP's reads `... in file:line`, the bridge is
23
- * `[stacktrace]` rather than `Stack trace:`, and class names arrive
24
- * JSON-escaped. Measured live: **0 exceptions, and a bare 53-line
25
- * STACK_TRACE with no identity attached to it.**
26
- *
27
- * **This option cannot fix Laravel's default log destination**, which is a
28
- * file (`storage/logs/laravel.log`). A collector watching process output
29
- * sees nothing at all unless the application sets `LOG_CHANNEL=stderr`.
30
- * That is a disclosure to the user, not something an adapter can decide.
20
+ * Cannot fix Laravel's default log destination (a file); the app must set
21
+ * LOG_CHANNEL=stderr or a collector sees nothing at all — disclosed to the user.
31
22
  */
32
23
  readonly framework?: "laravel";
33
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"php-runtime-adapter.d.ts","sourceRoot":"","sources":["../src/php-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,sCAAsC,CAAC;AAUhG,MAAM,WAAW,wBAAwB;IACvC,qGAAqG;IACrG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;CAChC;AAED,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,wBAA6B,GAAG,cAAc,CA2B9F"}
1
+ {"version":3,"file":"php-runtime-adapter.d.ts","sourceRoot":"","sources":["../src/php-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,sCAAsC,CAAC;AAUhG,MAAM,WAAW,wBAAwB;IACvC,qGAAqG;IACrG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;OASG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;CAChC;AAED,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,wBAA6B,GAAG,cAAc,CAyB9F"}
@@ -1,13 +1,10 @@
1
1
  /**
2
- * The concrete `RuntimeAdapter` for PHP.
2
+ * The concrete RuntimeAdapter for PHP.
3
3
  *
4
- * PHP's grammar needed no seam changes beyond RT-037's already-nullable
5
- * `SourceLocation`/`RawFrameLocation` fields, which an `[internal function]`
6
- * frame needs but did not itself force — that widening already existed by
7
- * the time this adapter was built. See `php-frame-parsing.ts` for the one
8
- * behaviour this language DID need from the classifier: recognising the
9
- * single `Stack trace:` line that separates PHP's header from its first
10
- * real frame, and only the one that follows a header just held.
4
+ * Needed no seam changes beyond RT-037's already-nullable SourceLocation/
5
+ * RawFrameLocation fields (needed for `[internal function]` frames). See
6
+ * php-frame-parsing.ts for the one classifier behavior PHP does need:
7
+ * recognising the `Stack trace:` line that follows a just-held header.
11
8
  */
12
9
  import { createLogCollector } from "@descryy/runtime-backend-observation";
13
10
  import { createLaravelLineClassifier } from "./laravel-frame-parsing.js";
@@ -24,9 +21,7 @@ export function createPhpRuntimeAdapter(options = {}) {
24
21
  function createBackendCollectors(sources) {
25
22
  return sources.map((source) => createLogCollector({
26
23
  source,
27
- // One classifier per collector, never shared: it carries the
28
- // "just saw a header, expecting Stack trace: next" bit, and two
29
- // processes' output must not prime each other.
24
+ // One classifier per collector: carries per-process state, must not be shared.
30
25
  classifier: isLaravel ? createLaravelLineClassifier() : createPhpLineClassifier(),
31
26
  stackTraceParser,
32
27
  ...(options.service !== undefined ? { service: options.service } : {}),
@@ -1 +1 @@
1
- {"version":3,"file":"php-runtime-adapter.js","sourceRoot":"","sources":["../src/php-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAE1E,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AAwBxE,MAAM,UAAU,uBAAuB,CAAC,UAAoC,EAAE;IAC5E,MAAM,sBAAsB,GAAG,+BAA+B,EAAE,CAAC;IACjE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;IAClD,MAAM,gBAAgB,GAAG,SAAS;QAChC,CAAC,CAAC,6BAA6B,CAAC,sBAAsB,CAAC;QACvD,CAAC,CAAC,yBAAyB,CAAC,sBAAsB,CAAC,CAAC;IAEtD,SAAS,uBAAuB,CAAC,OAAuC;QACtE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAC5B,kBAAkB,CAAC;YACjB,MAAM;YACN,6DAA6D;YAC7D,gEAAgE;YAChE,+CAA+C;YAC/C,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,2BAA2B,EAAE,CAAC,CAAC,CAAC,uBAAuB,EAAE;YACjF,gBAAgB;YAChB,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvE,CAAC,CACH,CAAC;IACJ,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,KAAK;QACf,gBAAgB;QAChB,sBAAsB;QACtB,uBAAuB;KACxB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"php-runtime-adapter.js","sourceRoot":"","sources":["../src/php-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAE1E,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AAkBxE,MAAM,UAAU,uBAAuB,CAAC,UAAoC,EAAE;IAC5E,MAAM,sBAAsB,GAAG,+BAA+B,EAAE,CAAC;IACjE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;IAClD,MAAM,gBAAgB,GAAG,SAAS;QAChC,CAAC,CAAC,6BAA6B,CAAC,sBAAsB,CAAC;QACvD,CAAC,CAAC,yBAAyB,CAAC,sBAAsB,CAAC,CAAC;IAEtD,SAAS,uBAAuB,CAAC,OAAuC;QACtE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAC5B,kBAAkB,CAAC;YACjB,MAAM;YACN,+EAA+E;YAC/E,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,2BAA2B,EAAE,CAAC,CAAC,CAAC,uBAAuB,EAAE;YACjF,gBAAgB;YAChB,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvE,CAAC,CACH,CAAC;IACJ,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,KAAK;QACf,gBAAgB;QAChB,sBAAsB;QACtB,uBAAuB;KACxB,CAAC;AACJ,CAAC"}
@@ -1,29 +1,19 @@
1
1
  /**
2
- * PHP `SourceLocationResolver`.
2
+ * PHP SourceLocationResolver.
3
3
  *
4
- * **Every location is `self-contained`, and that is a positive claim rather
5
- * than a fallback** — the same category RT-003 already assigns PHP traces
6
- * alongside Python tracebacks and Ruby backtraces. A real frame names the
7
- * `.php` file the engine was actually executing; there is no compilation
8
- * step and no separate map that can drift or go missing. The frame IS the
9
- * answer, when the engine printed one at all.
4
+ * Every location is `self-contained` (RT-003, same category as Python/Ruby):
5
+ * a real frame names the `.php` file the engine actually executed, no
6
+ * compile step or map to drift.
10
7
  *
11
- * **A frame can have no location.** `#N [internal function]: ...` means
12
- * PHP genuinely captured no file or line for that call — not that this
13
- * resolver failed to look. `reliability` stays `self-contained` even then:
14
- * this is not a side artifact that went missing (there was never a PDB or
15
- * source map to lose), it is the engine's own honest report that it has
16
- * nothing to offer for that frame. `file`/`line` pass through as the `null`
17
- * they arrived as, per RT-037's widening of `SourceLocation` for exactly
18
- * this shape (a stripped JVM frame first forced it; a PHP internal-function
19
- * frame is the same shape for a different reason).
8
+ * `#N [internal function]: ...` means PHP captured no file/line for that
9
+ * call — not that this resolver failed. `reliability` stays `self-contained`
10
+ * even then (no PDB/source map was ever there to go missing); file/line
11
+ * pass through as the null they arrived as (RT-037's SourceLocation widening).
20
12
  *
21
- * **What this deliberately does not do:** check that the file exists on
22
- * disk, or that its contents still match what ran. `self-contained` is a
23
- * statement about the mechanism, not about whether the working tree has
24
- * since been edited.
13
+ * Deliberately does not check the file exists on disk or still matches what
14
+ * ran — `self-contained` is a statement about mechanism, not the working tree.
25
15
  *
26
- * `column` is always null. PHP traces never print one.
16
+ * `column` is always null — PHP traces never print one.
27
17
  */
28
18
  import type { SourceLocationResolver } from "@descryy/runtime-backend-observation";
29
19
  export declare function createPhpSourceLocationResolver(): SourceLocationResolver;
@@ -1 +1 @@
1
- {"version":3,"file":"php-source-location-resolver.d.ts","sourceRoot":"","sources":["../src/php-source-location-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAoB,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAErG,wBAAgB,+BAA+B,IAAI,sBAAsB,CAgBxE"}
1
+ {"version":3,"file":"php-source-location-resolver.d.ts","sourceRoot":"","sources":["../src/php-source-location-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAoB,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAErG,wBAAgB,+BAA+B,IAAI,sBAAsB,CAcxE"}
@@ -1,35 +1,23 @@
1
1
  /**
2
- * PHP `SourceLocationResolver`.
2
+ * PHP SourceLocationResolver.
3
3
  *
4
- * **Every location is `self-contained`, and that is a positive claim rather
5
- * than a fallback** — the same category RT-003 already assigns PHP traces
6
- * alongside Python tracebacks and Ruby backtraces. A real frame names the
7
- * `.php` file the engine was actually executing; there is no compilation
8
- * step and no separate map that can drift or go missing. The frame IS the
9
- * answer, when the engine printed one at all.
4
+ * Every location is `self-contained` (RT-003, same category as Python/Ruby):
5
+ * a real frame names the `.php` file the engine actually executed, no
6
+ * compile step or map to drift.
10
7
  *
11
- * **A frame can have no location.** `#N [internal function]: ...` means
12
- * PHP genuinely captured no file or line for that call — not that this
13
- * resolver failed to look. `reliability` stays `self-contained` even then:
14
- * this is not a side artifact that went missing (there was never a PDB or
15
- * source map to lose), it is the engine's own honest report that it has
16
- * nothing to offer for that frame. `file`/`line` pass through as the `null`
17
- * they arrived as, per RT-037's widening of `SourceLocation` for exactly
18
- * this shape (a stripped JVM frame first forced it; a PHP internal-function
19
- * frame is the same shape for a different reason).
8
+ * `#N [internal function]: ...` means PHP captured no file/line for that
9
+ * call — not that this resolver failed. `reliability` stays `self-contained`
10
+ * even then (no PDB/source map was ever there to go missing); file/line
11
+ * pass through as the null they arrived as (RT-037's SourceLocation widening).
20
12
  *
21
- * **What this deliberately does not do:** check that the file exists on
22
- * disk, or that its contents still match what ran. `self-contained` is a
23
- * statement about the mechanism, not about whether the working tree has
24
- * since been edited.
13
+ * Deliberately does not check the file exists on disk or still matches what
14
+ * ran — `self-contained` is a statement about mechanism, not the working tree.
25
15
  *
26
- * `column` is always null. PHP traces never print one.
16
+ * `column` is always null — PHP traces never print one.
27
17
  */
28
18
  export function createPhpSourceLocationResolver() {
29
19
  return {
30
- // Async to satisfy the interface; PHP needs no I/O to answer, so this
31
- // never awaits — same as the Python and Ruby resolvers, for the same
32
- // reason.
20
+ // Async to satisfy the interface; PHP needs no I/O so this never awaits.
33
21
  resolve(frame) {
34
22
  return Promise.resolve({
35
23
  file: frame.file,
@@ -1 +1 @@
1
- {"version":3,"file":"php-source-location-resolver.js","sourceRoot":"","sources":["../src/php-source-location-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,MAAM,UAAU,+BAA+B;IAC7C,OAAO;QACL,sEAAsE;QACtE,qEAAqE;QACrE,UAAU;QACV,OAAO,CAAC,KAAuB;YAC7B,OAAO,OAAO,CAAC,OAAO,CAAC;gBACrB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,YAAY,EAAE,KAAK,CAAC,YAAY;gBAChC,WAAW,EAAE,gBAAgB;gBAC7B,WAAW,EAAE,IAAI;aAClB,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"php-source-location-resolver.js","sourceRoot":"","sources":["../src/php-source-location-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,MAAM,UAAU,+BAA+B;IAC7C,OAAO;QACL,yEAAyE;QACzE,OAAO,CAAC,KAAuB;YAC7B,OAAO,OAAO,CAAC,OAAO,CAAC;gBACrB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,YAAY,EAAE,KAAK,CAAC,YAAY;gBAChC,WAAW,EAAE,gBAAgB;gBAC7B,WAAW,EAAE,IAAI;aAClB,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -1,19 +1,14 @@
1
1
  /**
2
- * Composes the pure PHP line-recognition functions (`php-frame-parsing.ts`)
3
- * with a `SourceLocationResolver` to implement the full async
4
- * `StackTraceParser` contract.
2
+ * Composes the pure PHP line-recognition functions (php-frame-parsing.ts)
3
+ * with a SourceLocationResolver to implement the async StackTraceParser contract.
5
4
  *
6
- * **No reversal.** The header's own file:line is the exception's
7
- * construction site — the innermost real location — and each `#N` frame
8
- * walks outward toward `{main}`. `StackTrace.frames` is contractually
9
- * most-recent-first, and that is the order this text already prints in.
5
+ * No reversal — the header's file:line is the construction site (innermost),
6
+ * each `#N` frame walks outward toward `{main}`, and that's the order the
7
+ * text already prints in.
10
8
  *
11
- * `fidelity` is always reported `"synchronous"`. A PHP trace is built by
12
- * walking the actual call frames live at the moment the `Throwable` was
13
- * constructed — every location shown is a genuine link in the chain that
14
- * was executing. It cannot show a logical caller across a `Fiber` (PHP
15
- * 8.1+) suspension boundary, the same class of limit every other adapter
16
- * in this codebase shares and none claims to see through by text alone.
9
+ * `fidelity` is always `"synchronous"` — walks actual live call frames at
10
+ * construction time, but cannot show a caller across a Fiber (PHP 8.1+)
11
+ * suspension boundary, same limit every adapter here shares.
17
12
  */
18
13
  import type { SourceLocationResolver, StackTraceParser } from "@descryy/runtime-backend-observation";
19
14
  export declare function createPhpStackTraceParser(resolver: SourceLocationResolver): StackTraceParser;
@@ -1 +1 @@
1
- {"version":3,"file":"php-stack-trace-parser.d.ts","sourceRoot":"","sources":["../src/php-stack-trace-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAIrG,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,gBAAgB,CAiD5F"}
1
+ {"version":3,"file":"php-stack-trace-parser.d.ts","sourceRoot":"","sources":["../src/php-stack-trace-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAIrG,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,gBAAgB,CAkC5F"}
@@ -1,19 +1,14 @@
1
1
  /**
2
- * Composes the pure PHP line-recognition functions (`php-frame-parsing.ts`)
3
- * with a `SourceLocationResolver` to implement the full async
4
- * `StackTraceParser` contract.
2
+ * Composes the pure PHP line-recognition functions (php-frame-parsing.ts)
3
+ * with a SourceLocationResolver to implement the async StackTraceParser contract.
5
4
  *
6
- * **No reversal.** The header's own file:line is the exception's
7
- * construction site — the innermost real location — and each `#N` frame
8
- * walks outward toward `{main}`. `StackTrace.frames` is contractually
9
- * most-recent-first, and that is the order this text already prints in.
5
+ * No reversal — the header's file:line is the construction site (innermost),
6
+ * each `#N` frame walks outward toward `{main}`, and that's the order the
7
+ * text already prints in.
10
8
  *
11
- * `fidelity` is always reported `"synchronous"`. A PHP trace is built by
12
- * walking the actual call frames live at the moment the `Throwable` was
13
- * constructed — every location shown is a genuine link in the chain that
14
- * was executing. It cannot show a logical caller across a `Fiber` (PHP
15
- * 8.1+) suspension boundary, the same class of limit every other adapter
16
- * in this codebase shares and none claims to see through by text alone.
9
+ * `fidelity` is always `"synchronous"` — walks actual live call frames at
10
+ * construction time, but cannot show a caller across a Fiber (PHP 8.1+)
11
+ * suspension boundary, same limit every adapter here shares.
17
12
  */
18
13
  import { parsePhpHeaderFrame, parsePhpFrame } from "./php-frame-parsing.js";
19
14
  export function createPhpStackTraceParser(resolver) {
@@ -22,11 +17,7 @@ export function createPhpStackTraceParser(resolver) {
22
17
  const lines = rawText.split(/\r\n|\r|\n/);
23
18
  const frames = [];
24
19
  for (const line of lines) {
25
- // Structurally disjoint: a real frame line always starts with `#N`,
26
- // the header never does, so at most one of these ever matches a
27
- // given line. `Stack trace:` and the `#N {main}` trailer match
28
- // neither and are real parts of the block that are simply not
29
- // frames — nothing is fabricated for them.
20
+ // Structurally disjoint: frame lines start with `#N`, header never does, so at most one matches.
30
21
  const raw = parsePhpFrame(line) ?? parsePhpHeaderFrame(line);
31
22
  if (raw === null)
32
23
  continue;
@@ -39,26 +30,15 @@ export function createPhpStackTraceParser(resolver) {
39
30
  frames.push({ location, raw: line });
40
31
  }
41
32
  if (frames.length === 0) {
42
- // Nothing frame-shaped at all — not recognisable as a PHP
43
- // uncaught-Throwable trace, per this interface's contract: null,
44
- // never an empty-but-structured StackTrace standing in for
45
- // "couldn't parse".
33
+ // Nothing frame-shaped — null, never an empty StackTrace standing in for "couldn't parse".
46
34
  return null;
47
35
  }
48
- // `primaryFrameIndex` is required so that every adapter states an
49
- // answer rather than inheriting one (RT-083). PHP's is 0 because the
50
- // **header carries a location no `#N` frame does** — `getFile()` /
51
- // `getLine()`, the site where the `Throwable` was constructed — and
52
- // it is the first line of the block, so it parses to frame 0.
53
- //
54
- // This is emphatically not "index 0 happens to work". PHP's `#N`
55
- // frames name the **callee at the caller's location**, so `#0` is the
56
- // call site one level out; an adapter that pointed at `#0` would
57
- // report the caller's line as the failure site and look correct doing
58
- // it. See `php-frame-parsing.ts` for the one caveat this inherits:
59
- // strictly the header is where the exception was `new`-ed, which
60
- // differs from the throw site only if a caller holds a `Throwable`
61
- // across lines before throwing it.
36
+ // primaryFrameIndex 0 (RT-083): the header (getFile()/getLine(), the
37
+ // Throwable's construction site) is frame 0. Not "0 happens to work" —
38
+ // PHP's `#N` frames name the callee at the CALLER's location, so `#0`
39
+ // would report the caller's line as the failure site. See
40
+ // php-frame-parsing.ts for the one caveat: strictly this is where the
41
+ // exception was `new`-ed, which can differ from the throw site.
62
42
  return { fidelity: "synchronous", frames, primaryFrameIndex: 0 };
63
43
  },
64
44
  };
@@ -1 +1 @@
1
- {"version":3,"file":"php-stack-trace-parser.js","sourceRoot":"","sources":["../src/php-stack-trace-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5E,MAAM,UAAU,yBAAyB,CAAC,QAAgC;IACxE,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,OAAe;YACzB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAE1C,MAAM,MAAM,GAAiB,EAAE,CAAC;YAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,oEAAoE;gBACpE,gEAAgE;gBAChE,+DAA+D;gBAC/D,8DAA8D;gBAC9D,2CAA2C;gBAC3C,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC;gBAC7D,IAAI,GAAG,KAAK,IAAI;oBAAE,SAAS;gBAE3B,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;oBACtC,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,MAAM,EAAE,IAAI;oBACZ,YAAY,EAAE,GAAG,CAAC,YAAY;iBAC/B,CAAC,CAAC;gBACH,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YACvC,CAAC;YAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxB,0DAA0D;gBAC1D,iEAAiE;gBACjE,2DAA2D;gBAC3D,oBAAoB;gBACpB,OAAO,IAAI,CAAC;YACd,CAAC;YAED,kEAAkE;YAClE,qEAAqE;YACrE,mEAAmE;YACnE,oEAAoE;YACpE,8DAA8D;YAC9D,EAAE;YACF,iEAAiE;YACjE,sEAAsE;YACtE,iEAAiE;YACjE,sEAAsE;YACtE,mEAAmE;YACnE,iEAAiE;YACjE,mEAAmE;YACnE,mCAAmC;YACnC,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;QACnE,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"php-stack-trace-parser.js","sourceRoot":"","sources":["../src/php-stack-trace-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5E,MAAM,UAAU,yBAAyB,CAAC,QAAgC;IACxE,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,OAAe;YACzB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAE1C,MAAM,MAAM,GAAiB,EAAE,CAAC;YAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,iGAAiG;gBACjG,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC;gBAC7D,IAAI,GAAG,KAAK,IAAI;oBAAE,SAAS;gBAE3B,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;oBACtC,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,MAAM,EAAE,IAAI;oBACZ,YAAY,EAAE,GAAG,CAAC,YAAY;iBAC/B,CAAC,CAAC;gBACH,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YACvC,CAAC;YAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxB,2FAA2F;gBAC3F,OAAO,IAAI,CAAC;YACd,CAAC;YAED,qEAAqE;YACrE,uEAAuE;YACvE,sEAAsE;YACtE,0DAA0D;YAC1D,sEAAsE;YACtE,gEAAgE;YAChE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;QACnE,CAAC;KACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,9 +1,14 @@
1
1
  {
2
2
  "name": "@descryy/runtime-adapter-php",
3
- "version": "0.0.0",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "description": "PHP runtime adapter: uncaught-Throwable trace parsing, self-contained source locations, the concrete RuntimeAdapter for PHP.",
6
6
  "license": "UNLICENSED",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/descryhq-wq/descry-runtime.git",
10
+ "directory": "packages/adapter-php"
11
+ },
7
12
  "engines": {
8
13
  "node": ">=22.5"
9
14
  },
@@ -24,7 +29,7 @@
24
29
  "build": "tsc -b"
25
30
  },
26
31
  "dependencies": {
27
- "@descryy/runtime-contracts": "*",
28
- "@descryy/runtime-backend-observation": "*"
32
+ "@descryy/runtime-contracts": "0.3.0",
33
+ "@descryy/runtime-backend-observation": "0.3.0"
29
34
  }
30
35
  }