redweb 0.12.0 → 0.13.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.
Files changed (167) hide show
  1. package/CHANGELOG.md +166 -9
  2. package/README.md +177 -523
  3. package/bin/redweb.js +11 -20
  4. package/client.d.ts +7 -2
  5. package/config/tsconfig.json +14 -14
  6. package/contract.d.ts +45 -0
  7. package/contract.js +5 -0
  8. package/docs/ACTION_INPUT_VERIFICATION.md +96 -0
  9. package/docs/ADMISSION_TIMEOUT_VERIFICATION.md +69 -0
  10. package/docs/AGENT_ACCESS.md +35 -0
  11. package/docs/AGENT_EVALUATION.md +58 -0
  12. package/docs/AGENT_READY_ACCEPTANCE.md +763 -0
  13. package/docs/APPLICATION_RECORDER_VERIFICATION.md +50 -0
  14. package/docs/BENCHMARK_VERIFICATION.md +307 -0
  15. package/docs/BROWSER_OWNER_VERIFICATION.md +191 -0
  16. package/docs/CLI.md +116 -0
  17. package/docs/CLIENT_DEVELOPMENT.md +152 -0
  18. package/docs/CLIENT_POLISH_VERIFICATION.md +282 -0
  19. package/docs/COVERAGE_COUNTER_VALIDATION.md +109 -0
  20. package/docs/COVERAGE_SCOPE_AUDIT.md +1183 -0
  21. package/docs/DEVELOPMENT.md +79 -0
  22. package/docs/DIAGNOSTIC_COMPATIBILITY.md +76 -0
  23. package/docs/DOCUMENTATION.md +37 -0
  24. package/docs/FEEDBACK_COMMAND_VERIFICATION.md +228 -0
  25. package/docs/GETTING_STARTED.md +58 -0
  26. package/docs/JSX_PERFORMANCE_VERIFICATION.md +59 -0
  27. package/docs/LIVE_HTML.md +169 -21
  28. package/docs/LIVE_HTML_LOAD_VERIFICATION.md +98 -0
  29. package/docs/MIGRATION.md +28 -0
  30. package/docs/MULTIPLAYER_OPERATIONS.md +26 -4
  31. package/docs/ORIGINAL_RECOVERY_VERIFICATION.md +100 -0
  32. package/docs/PACKAGED_EXAMPLE_VERIFICATION.md +126 -0
  33. package/docs/POLISH_RELEASE_CHECKPOINT.md +91 -0
  34. package/docs/PROCESS_CLEANUP_OBSERVATION.md +61 -0
  35. package/docs/PROCESS_REAPING_VERIFICATION.md +30 -0
  36. package/docs/PRODUCTION_READINESS.md +11 -2
  37. package/docs/RECOVERY_CLIENT_HEAP.md +201 -0
  38. package/docs/RECOVERY_CODE_ATTRIBUTION.md +174 -0
  39. package/docs/RECOVERY_CODE_CENSUS.md +158 -0
  40. package/docs/RECOVERY_COMPARISON.md +103 -0
  41. package/docs/RECOVERY_DEOPTIMIZATION.md +169 -0
  42. package/docs/RECOVERY_FOLLOWUP_SPIKE.md +147 -0
  43. package/docs/RECOVERY_INVESTIGATION.md +229 -0
  44. package/docs/RECOVERY_RUNTIME_CONTROLS.md +181 -0
  45. package/docs/RELEASE_TRUST.md +58 -0
  46. package/docs/ROOM_AUTHORIZATION.md +49 -0
  47. package/docs/RUNTIME_DIAGNOSTICS.md +78 -0
  48. package/docs/SERVER_RECOVERY_CANDIDATE.md +185 -0
  49. package/docs/SOAK_ROTATION_OBSERVATION.md +160 -0
  50. package/docs/SOAK_VERIFICATION.md +154 -0
  51. package/docs/SOCKET_CONTRACTS.md +39 -0
  52. package/docs/SPLIT_RECOVERY_COVERAGE.md +83 -0
  53. package/docs/SPLIT_RECOVERY_ERROR_HANDLING.md +67 -0
  54. package/docs/STARTER_COORDINATOR_VERIFICATION.md +112 -0
  55. package/docs/STARTER_LIFECYCLE_VERIFICATION.md +75 -0
  56. package/docs/STARTER_REPORT_RETENTION.md +73 -0
  57. package/docs/VERIFICATION_EVIDENCE.md +2 -0
  58. package/docs/generated.json +2154 -0
  59. package/docs/guides/chatroom.md +27 -0
  60. package/docs/guides/http-websocket.md +28 -0
  61. package/docs/guides/jsx-without-react.md +26 -0
  62. package/docs/guides/realtime-dashboard.md +29 -0
  63. package/docs/guides/typed-websockets.md +26 -0
  64. package/docs/reference.json +1207 -0
  65. package/docs/snippets/room-access.tsx +51 -0
  66. package/docs/topics.json +21 -0
  67. package/examples/live-html/chatroom.js +207 -268
  68. package/examples/live-html/chatroom.tsx +167 -0
  69. package/examples/live-html/jsx-page.js +1 -1
  70. package/examples/live-html/jsx-page.tsx +1 -1
  71. package/examples/live-html/tsconfig.json +8 -7
  72. package/index.d.ts +170 -45
  73. package/index.js +2 -0
  74. package/jsx-dev-runtime.js +2 -2
  75. package/jsx-runtime.d.ts +7 -2
  76. package/package.json +88 -7
  77. package/recipes/add/artifact.test.cjs +57 -0
  78. package/recipes/add/live.tsx +18 -0
  79. package/recipes/add/socket-route.ts +24 -0
  80. package/recipes/chat/README.md +22 -0
  81. package/recipes/chat/app.test.cjs +105 -0
  82. package/recipes/chat/app.tsx +9 -0
  83. package/recipes/dashboard/README.md +43 -0
  84. package/recipes/dashboard/admin.ts +21 -0
  85. package/recipes/dashboard/app.css +16 -0
  86. package/recipes/dashboard/app.test.cjs +450 -0
  87. package/recipes/dashboard/app.tsx +86 -0
  88. package/recipes/dashboard/auth.ts +80 -0
  89. package/recipes/dashboard/cards.tsx +102 -0
  90. package/recipes/dashboard/rate-window.test.cjs +17 -0
  91. package/recipes/dashboard/store.ts +120 -0
  92. package/recipes/http-ws/README.md +11 -0
  93. package/recipes/http-ws/app.test.cjs +92 -0
  94. package/recipes/http-ws/app.tsx +36 -0
  95. package/recipes/realtime/README.md +8 -0
  96. package/recipes/realtime/app.test.cjs +15 -0
  97. package/recipes/realtime/app.tsx +28 -0
  98. package/recipes/shared/README.md +40 -0
  99. package/recipes/shared/app.css +8 -0
  100. package/recipes/shared/copy-assets.cjs +8 -0
  101. package/recipes/shared/network.cjs +59 -0
  102. package/recipes/shared/run-app.test.cjs +158 -0
  103. package/recipes/shared/run-app.ts +50 -0
  104. package/recipes/site/README.md +4 -0
  105. package/recipes/site/app.test.cjs +19 -0
  106. package/recipes/site/app.tsx +25 -0
  107. package/recipes/socket/README.md +39 -0
  108. package/recipes/socket/app.test.cjs +85 -0
  109. package/recipes/socket/app.tsx +30 -0
  110. package/recipes/socket/contract.ts +12 -0
  111. package/recipes/socket/handlers.ts +40 -0
  112. package/src/OwnedServerLifecycle.js +66 -0
  113. package/src/access/AccessPolicy.js +37 -0
  114. package/src/access/AuthenticationFailure.js +13 -0
  115. package/src/access/RequestFailure.js +33 -0
  116. package/src/access/failure-codes.json +25 -0
  117. package/src/async/BoundedOperation.js +62 -0
  118. package/src/cli/ActionReferences.js +193 -0
  119. package/src/cli/AdditionLayout.js +140 -0
  120. package/src/cli/FilePlan.js +94 -0
  121. package/src/cli/ProjectAddition.js +60 -0
  122. package/src/cli/ProjectConfig.js +26 -0
  123. package/src/cli/ProjectDoctor.js +112 -0
  124. package/src/cli/ProjectInitializer.js +20 -30
  125. package/src/cli/SourceInspector.js +207 -0
  126. package/src/cli/StaticSource.js +192 -0
  127. package/src/cli/arguments.js +62 -0
  128. package/src/cli/formatCommand.js +10 -0
  129. package/src/cli/run.js +57 -0
  130. package/src/cli/templates.js +86 -87
  131. package/src/context/RequestSnapshot.js +41 -0
  132. package/src/dataProperty.js +11 -0
  133. package/src/development/DevelopmentPageManager.js +48 -0
  134. package/src/development/Inspection.js +104 -0
  135. package/src/development/ObservedRenderer.js +42 -0
  136. package/src/development/description.js +35 -0
  137. package/src/development/loopbackRequest.js +27 -0
  138. package/src/development/refreshBrowser.js +96 -0
  139. package/src/development/refreshStyles.js +9 -0
  140. package/src/development/settings.js +17 -0
  141. package/src/docs/Documentation.js +182 -0
  142. package/src/htmx/ActionDefinition.js +44 -0
  143. package/src/htmx/Jsx.js +24 -8
  144. package/src/htmx/LiveHtmlServer.js +41 -19
  145. package/src/htmx/LivePage.js +63 -13
  146. package/src/htmx/PageIdentity.js +32 -0
  147. package/src/htmx/PageLifetime.js +37 -0
  148. package/src/htmx/PageManager.js +203 -74
  149. package/src/htmx/ReactiveRenderer.js +241 -0
  150. package/src/htmx/StaticExporter.js +1 -1
  151. package/src/htmx/TemplateRenderer.js +13 -7
  152. package/src/htmx/browserRuntime.js +2 -93
  153. package/src/htmx/metadata.js +19 -7
  154. package/src/validation/ActionInputError.js +12 -0
  155. package/src/validation/SchemaValidator.js +38 -0
  156. package/src/ws/AdmissionPolicy.js +24 -23
  157. package/src/ws/BaseSocketServer.js +53 -38
  158. package/src/ws/ContractValidationError.js +12 -0
  159. package/src/ws/HeartbeatMonitor.js +19 -7
  160. package/src/ws/ProtocolPolicy.js +1 -1
  161. package/src/ws/RoomAccess.js +82 -0
  162. package/src/ws/RoomRegistry.js +56 -6
  163. package/src/ws/RouteRuntime.js +56 -10
  164. package/src/ws/SocketContract.js +112 -0
  165. package/src/ws/SocketRoute.js +18 -0
  166. package/src/ws/protocol-schema.json +6 -1
  167. package/examples/live-html/chatroom.ts +0 -217
@@ -0,0 +1,174 @@
1
+ # Offline client code attribution
2
+
3
+ The existing private snapshots show a broader set of compiled code attached to
4
+ **preexisting functions and closures**, not an accumulating collection of detached
5
+ optimized versions. This is a narrower, supported conclusion about this diagnostic
6
+ client; it does not establish the entire cause of the original CI recovery failure.
7
+
8
+ An independent research agent began without inherited conversation or a proposed
9
+ cause. It subsequently encountered earlier investigation notes in the repository
10
+ and disclosed that exposure. Its structural findings were reproduced by the
11
+ tested offline analyzer and submitted to a separate senior critic.
12
+
13
+ No new measured workload or capture was performed for this milestone. Both inputs
14
+ are the unchanged Windows/Node 22.21.0, V8 12.4.254.21-node.33 snapshots documented
15
+ in [the capture report](RECOVERY_CLIENT_HEAP.md). The original capture command
16
+ failed its detailed-output size limit; its report and failure remain unchanged.
17
+ The new offline result is not a successful replacement recovery run.
18
+
19
+ ## Function identities, not names
20
+
21
+ | Observed structure | Warm | Final |
22
+ | --- | ---: | ---: |
23
+ | Code objects with deoptimization data | 96 | 261 |
24
+ | Associated SharedFunctionInfo identities | 95 | 260 |
25
+ | Identities with one such Code object | 94 | 259 |
26
+ | Identities with two such Code objects | 1 | 1 |
27
+ | Code objects referenced by an owning closure's current code field | 96 | 261 |
28
+ | Feedback vectors | 487 | 532 |
29
+
30
+ The final 261 Code objects comprise 57 surviving and 204 added identities; 39 warm
31
+ Code identities disappeared. All 261 have an owning function identity and a
32
+ matching owning closure already present in the warm snapshot. The only two-code
33
+ group contains exactly the same two Code identities in both snapshots, both
34
+ referenced by current closure fields. There were no unresolved, ambiguous or
35
+ conflicting code/feedback associations in this pair.
36
+
37
+ "Current" means a `closure.code` reference, **not proof that the code remains valid
38
+ or is executing**. V8 permits attached code to be marked for deoptimization; that
39
+ primitive status is not available in these snapshots. See the pinned
40
+ [JSFunction implementation](https://github.com/nodejs/node/blob/v22.21.0/deps/v8/src/objects/js-function.cc).
41
+ This analysis cannot match the exact invalidated versions in the separate earlier
42
+ logging run to these objects.
43
+
44
+ | Source of added Code objects | Objects | Code plus instruction-stream shallow bytes |
45
+ | --- | ---: | ---: |
46
+ | Node built-ins | 180 | 299,360 |
47
+ | ws | 22 | 57,296 |
48
+ | Realtime test harness | 2 | 8,112 |
49
+
50
+ These byte totals describe added Code objects plus their attached streams, not
51
+ exclusively newly allocated nodes: one added Node Code references a surviving
52
+ 2,752-byte instruction stream. There are 204 added Code objects but 203 added
53
+ instruction-stream identities.
54
+
55
+ Of 45 added feedback vectors, 44 belong to Node built-ins (9,744 shallow bytes)
56
+ and one to the snapshot observer (184 bytes). Observer allocations are explicitly
57
+ included, not silently attributed to workload code.
58
+
59
+ The deduplicated union of Code, instruction streams and immediate deoptimization,
60
+ relocation and source-position metadata grows from 201,752 to 861,664 shallow
61
+ bytes (+659,912). Feedback vectors are excluded from this union. These are
62
+ selected graph-node self-sizes, not exclusive retained sizes, all code-related
63
+ memory, or bytes that can be subtracted from the recovery gate. Per-function
64
+ metadata can overlap; adding those rows would double-count shared objects.
65
+
66
+ ## A concrete persistent path
67
+
68
+ `ws.initAsClient` has the same single closure and SharedFunctionInfo identity in
69
+ both snapshots. Its baseline code remains in `function_data`; one new
70
+ deoptimization-bearing Code occupies the closure's code field at the final
71
+ capture. That new Code and instruction stream occupy 20,056 shallow bytes, with
72
+ 15,304 bytes of associated immediate deoptimization data. There is no collection
73
+ of old detached optimized versions for this function in the final snapshot.
74
+
75
+ The analyzer finds this unique chain in both snapshots, with all eight node
76
+ identities unchanged:
77
+
78
+ ```text
79
+ process
80
+ -- property/_events --> events object
81
+ -- property/message --> worker IPC listener
82
+ -- internal/context --> listener context
83
+ -- context/WebSocket --> ws WebSocket constructor
84
+ -- internal/context --> constructor context
85
+ -- internal/previous --> enclosing context
86
+ -- context/initAsClient --> initAsClient closure
87
+ ```
88
+
89
+ The listener is sourced from `recovery-split-worker.cjs`; its context also holds
90
+ the harness's `closeClient` and `waitFor`. Two root-reachable module objects export
91
+ the same WebSocket constructor in each snapshot (`ws/index.js` and
92
+ `ws/lib/websocket.js`). This establishes persistent worker/module reachability,
93
+ not a unique owner or a leftover per-connection closure.
94
+
95
+ The three unchanged objects named `Socket` are also resolved structurally:
96
+
97
+ | Role | Shallow bytes | Identifying reference |
98
+ | --- | ---: | --- |
99
+ | Cached stderr stream | 320 | `getStderr` context's `stderr` binding |
100
+ | TLS socket prototype | 56 | `TLSSocket.prototype` |
101
+ | JS stream socket prototype | 24 | `JSStreamSocket.prototype` |
102
+
103
+ Classification requires the appropriate built-in source and edge structure, not
104
+ the displayed size or constructor name alone. All three retain the same identity
105
+ and role; none is identified as a leftover workload connection. Independent
106
+ inspection also found 50 of the 51 unchanged HTTPParser objects reachable through
107
+ the parser FreeList. One remaining parser's semantic role is unresolved despite
108
+ a path through global handles and a native context. The reusable analyzer does
109
+ not classify HTTPParser ownership.
110
+
111
+ ## Method and limits
112
+
113
+ `CodeAttribution.cjs` composes the existing validated graph reader. It identifies
114
+ SharedFunctionInfo structurally, follows Code/deoptimization-data associations,
115
+ cross-checks closure ownership and requires reciprocal instruction-stream links.
116
+ Identity groups remain separate even when function names coincide.
117
+
118
+ It does not assume that a snapshot's numerical hidden edge is a V8 field offset.
119
+ The [pinned snapshot generator](https://github.com/nodejs/node/blob/v22.21.0/deps/v8/src/profiler/heap-snapshot-generator.cc)
120
+ compacts hidden visitor indices. The outer function association is the unique
121
+ direct strong structural SFI reference, not nested inlined-function references
122
+ in the [deoptimization data](https://github.com/nodejs/node/blob/v22.21.0/deps/v8/src/objects/deoptimization-data.h).
123
+ Missing, conflicting, ambiguous and unsupported structures remain explicit.
124
+
125
+ Inputs retain the paired-process identity, exact runtime, phase, size and SHA-256
126
+ checks. Graphs are capped at 250,000 nodes and 1,500,000 edges; metadata association
127
+ work is capped at 2,000,000. Exceeding a bound fails instead of reporting partial
128
+ success. Detailed output contains at most 128 function groups (135 of 263 groups
129
+ are explicitly omitted here); global counts include every group. Output stays
130
+ within 1 MiB, uses narrow fixed source/function labels, and contains no raw object
131
+ IDs, arbitrary values, absolute source paths or code addresses.
132
+
133
+ To analyze this pinned capture format locally, without starting the workload:
134
+
135
+ ```powershell
136
+ node scripts/diagnostics/HeapCodeComparison.cjs <original-report.json> <private-snapshot-directory> <new-report.json> attribution
137
+ ```
138
+
139
+ Output creation is exclusive; an existing output is not overwritten. Raw
140
+ snapshots remain private and local.
141
+
142
+ ## Evidence and verification
143
+
144
+ - Implementation: `8a15569` on `codex/agent-ready`.
145
+ - Original report: `coverage/recovery-split-f7owK8/report.json`; SHA-256 `1b894777de9ec50f53faa7e66f144bfe4feb89793ae1bfe068365000833acbe7`.
146
+ - Separate offline attribution: `coverage/recovery-split-f7owK8/code-attribution.json`, 67,985 bytes; SHA-256 `af299ace748edc2c01eb02d18ab93cb256fc28a5883adb853b03e146e2ef3971`.
147
+ - The attribution report embeds original-report and analyzer hashes. Snapshot identities/hashes remain those in the capture report.
148
+ - Native verification: 29 unit and real-process/socket/snapshot tests passed in 8.304 seconds. All six snapshot modules have 100% statements, branches, functions and lines, with unchanged thresholds and no exclusions added. Integration tests use actual native snapshots, CLI processes and network traffic, without mocks.
149
+ - Native coverage: `coverage/code-attribution-native/coverage-final.json`; SHA-256 `fd9c8b997f5d40e7367d79c4ca793435f93e1594a8f4f91f58c3b09af47e535d`.
150
+ - Full regression at implementation `8a15569`: **853 tests in 82 suites passed** in 434.002 seconds, with a normal exit, pretest/generated-documentation checks and all three TypeScript configurations. The subsequently edited evidence prose is checked separately; no runtime or test inputs changed during the suite.
151
+ - Instrumented-library coverage: 100% across 5,445 statements, 4,044 branches, 978 functions and 4,464 lines. The owned-HTTP-shutdown test passed, without resolving its historical timeout.
152
+ - Full-suite JSON: `coverage/code-attribution-full-suite.json`; SHA-256 `7274abbb5915e7865f5e3082afaa32d9a08a624732e24664f916600876c961bc`.
153
+ - After updating the evidence prose and generated catalogue, pretest/type checks and 30 focused documentation/attribution/graph tests in four suites passed (1.241 seconds for tests).
154
+
155
+ The senior critic approved the scoped analyzer after a reciprocal-stream
156
+ double-counting risk was fixed and regression-tested, then independently reproduced
157
+ the saved attribution and approved this report's interpretation. This is not approval of
158
+ release acceptance or a claim that broader coordinator/worker coverage is 100%.
159
+
160
+ ## Remaining decision boundary
161
+
162
+ Existing snapshots answer which functions hold this code and disfavor detached
163
+ version accumulation as the explanation for this captured client. They cannot
164
+ answer whether the attached code is marked for invalidation, prove every retained
165
+ byte benign, or reproduce the original Ubuntu/Node 22.23.2 shared-process failure.
166
+ That original fourth-storm 110.218742% result remains above the unchanged 110%
167
+ limit even though the final storm declined. The historical shutdown timeout also
168
+ remains unexplained.
169
+
170
+ There is no supported Redweb production fix to apply from these findings alone.
171
+ Closing the original failure requires evidence under its comparable environment
172
+ and protocol, not another aggregate snapshot, code-byte subtraction or repeated
173
+ runs selected for a pass. No production runtime, dependency, npm link, workload,
174
+ acceptance limit, publication, deployment or merge changed in this milestone.
@@ -0,0 +1,158 @@
1
+ # Client code-creation census — 2026-08-30
2
+
3
+ Status: concrete compilation candidates identified; **original recovery failure
4
+ not resolved**. This follows the [JIT control](RECOVERY_RUNTIME_CONTROLS.md).
5
+
6
+ ## Method and boundaries
7
+
8
+ One predeclared run, executed once after unit/real-network tests and critic review:
9
+
10
+ ```sh
11
+ node scripts/diagnostics/recovery-split.cjs client-code
12
+ ```
13
+
14
+ The server retains normal flags. Only the client adds `--log-code`,
15
+ `--no-log-source-code`, `--no-log-source-position`, `--no-logfile-per-isolate`
16
+ and `--logfile=-` to `--expose-gc`. Inherited Node options/coverage do not enter
17
+ measured workers. No tests ran concurrently with measurement. Runtime is unchanged:
18
+ Windows, Node 22.21.0, V8 12.4.254.21-node.33, ws 8.21.3.
19
+
20
+ The pinned V8 [flag definitions](https://github.com/nodejs/node/blob/v22.21.0/deps/v8/src/flags/flag-definitions.h)
21
+ show code logging normally enables source logging; the explicit negative flags
22
+ prevent that. V8's [logger implementation](https://github.com/nodejs/node/blob/v22.21.0/deps/v8/src/logging/log.cc)
23
+ records instruction size, code kind, logger-relative microseconds, function name
24
+ and location. Numeric kinds are interpreted using the exact pinned
25
+ [code-kind enum](https://github.com/nodejs/node/blob/v22.21.0/deps/v8/src/objects/code-kind.h).
26
+ Other V8 versions are rejected by the parser, not silently decoded with this enum.
27
+
28
+ Two tiny diagnostic functions are first invoked **after** the warm and final
29
+ samples. Their native code-creation records bracket the interval in the same V8
30
+ stream; JavaScript stdout markers are not mixed into it. Both are interpreted
31
+ functions, two instruction bytes each. The parser requires exactly one of each,
32
+ in order, and monotonic code-event timestamps. It rejects missing/repeated/reversed
33
+ boundaries, malformed records, unknown kinds, private detail records and oversized
34
+ or incomplete input. The existing coordinator preserves bounded logs and hashes;
35
+ census failure invalidates the result. Summary names/locations are limited to
36
+ known code sources, with other entries explicitly unclassified.
37
+
38
+ Logging, native markers and their allocations perturb this run. The census window
39
+ is after-warm-sample to after-final-sample, not precisely the two heap-read
40
+ instants. This is not acceptance-equivalent. Creation events/instruction bytes
41
+ are **not retained objects, retained size, total heap size, or a deoptimization
42
+ diagnosis**. Raw local logs still include addresses, paths and code labels; they
43
+ are not published as sanitized reports. No heap snapshots were taken.
44
+
45
+ ## Results
46
+
47
+ All 7,400 exact reply IDs were acknowledged. Client tracked connections and server
48
+ client/room/session registries were empty at every sample. Both workers exited.
49
+ All source fingerprints remained unchanged through measurement and were rechecked
50
+ afterward. Every raw log's byte length, completeness and SHA-256 was independently
51
+ verified; reparsing its serialized census reproduced the report exactly.
52
+
53
+ The log has 4,360 code-creation records: 3,502 through the warm boundary,
54
+ **851 between boundaries**, and seven at/after the final boundary. Native boundary
55
+ timestamps are 2,873,133 and 12,657,842 microseconds. Of the interval events, 843
56
+ are TurboFan (1,620,872 instruction bytes) and eight baseline-tier (9,004 bytes).
57
+ These are cumulative creation sizes, not a memory-retention total.
58
+
59
+ | Source category | Interval creation events | Cumulative instruction bytes |
60
+ | --- | ---: | ---: |
61
+ | Node core | 653 | 1,170,660 |
62
+ | `ws` | 106 | 293,460 |
63
+ | Load-test harness and worker | 41 | 144,184 |
64
+ | Unclassified | 51 | 21,572 |
65
+
66
+ Largest named TurboFan groups:
67
+
68
+ | Function | Source | Events | Cumulative instruction bytes |
69
+ | --- | --- | ---: | ---: |
70
+ | `initAsClient` | `ws/lib/websocket.js:667` | 4 | 71,176 |
71
+ | `ClientRequest` | `node:_http_client:190` | 4 | 67,520 |
72
+ | `Socket._writeGeneric` | `node:net:935` | 5 | 29,520 |
73
+ | `onEvent` | `scripts/realtime-harness.js:13` | 5 | 28,004 |
74
+ | `Socket._destroy` | `node:net:808` | 4 | 27,888 |
75
+ | `parserOnHeadersComplete` | `node:_http_common:74` | 4 | 24,208 |
76
+
77
+ For `initAsClient`, all four events share one SharedFunctionInfo address but have
78
+ four distinct code addresses and instruction sizes of 16,144 / 18,860 / 17,220 /
79
+ 18,952 bytes, at 5,508,294 / 7,818,047 / 9,753,664 / 11,684,671 microseconds.
80
+ That is evidence of distinct optimized versions associated with the same function
81
+ in this run, not just four identical records. It does not say why new versions
82
+ were generated, whether earlier versions survived, or which objects retained them.
83
+
84
+ ### Memory observations (not a gate)
85
+
86
+ | Phase | Server heap bytes | Client heap bytes | Client code-and-metadata bytes |
87
+ | --- | ---: | ---: | ---: |
88
+ | Preconditioning | 10,190,376 | 7,030,952 | 2,860,536 |
89
+ | Warm | 10,190,592 | 7,005,648 | 2,834,870 |
90
+ | Storm 1 | 10,462,648 | 7,242,200 | 2,986,582 |
91
+ | Storm 2 | 10,791,136 | 7,693,424 | 3,283,972 |
92
+ | Storm 3 | 11,087,200 | 7,816,488 | 3,346,328 |
93
+ | Storm 4 | 10,964,832 | 7,853,856 | 3,355,785 |
94
+ | Storm 5 | 9,910,160 | 7,844,472 | 3,330,512 |
95
+
96
+ Client warm-to-final heap growth is 838,824 bytes; code-and-metadata growth is
97
+ 495,642 bytes. Peak/final heap ratios are 112.10748812957773% and
98
+ 111.97353906448055%. Client bytecode-and-metadata rises from 756,440 at warm to
99
+ 756,544 at storm 1 and stays there; marker overhead is not subtracted. External
100
+ memory (2,331,012) and array buffers (35,458) remain constant after warm-up.
101
+ Server peak/final ratios are 108.79838973044941% and 97.24812846986711%; its
102
+ bytecode-and-metadata again drops late, from 908,728 to 205,328 bytes. None of these
103
+ overlapping statistics is added to/subtracted from heap to manufacture a pass.
104
+
105
+ ## Evidence and verification
106
+
107
+ - Tooling commit: `200fb30` on `codex/agent-ready`.
108
+ - UTC run: 2026-08-30 19:46:02.966–19:46:15.721.
109
+ - Local directory: `coverage/recovery-split-4EG5qI` (report, samples and four logs).
110
+ - Report SHA-256: `8cc597cf22bdaef4b6af90943b36a4a541e49af03ec8bc43ef76fc95390c8cdb`.
111
+ - Client stdout: 419,734 bytes; SHA-256 `55a02d595bf57b26e39aac6cc609db49a4062df24f2876b5c2fd9766204709c4`.
112
+ - Other three logs: empty, complete, hashes verified.
113
+ - Parser source SHA-256: `759961f48151f8e74cbc6f9490bec640f9e42508023fa5e4082b866eb9c7acec`.
114
+
115
+ Fifty-nine focused unit/real-child/real-HTTP-WebSocket tests passed. The new parser
116
+ has **100% statements, branches, functions and lines**, in both Jest and native V8
117
+ coverage. The real-network test exercises logging isolation, boundaries, complete
118
+ hashes, source-text exclusion and listener cleanup. On other V8 versions it checks
119
+ explicit parser rejection, while still exercising traffic and capture; it does
120
+ not claim schema support for those versions.
121
+
122
+ The final-tree combined three diagnostic scripts remain below the unchanged native
123
+ 100% gate: **80% statements/lines, 97.02% branches and 93.54% functions**. That
124
+ coverage command exited nonzero despite all 59 behavior tests passing. The worker
125
+ is 100% statements/lines/functions but 98.14% branches (cleanup-barrier timeout
126
+ uncovered); coordinator CLI/orchestration/error paths remain uncovered. These
127
+ numbers supersede the earlier pre-finalization coverage check. No thresholds or
128
+ exclusions changed.
129
+
130
+ Full-suite verification passed **813 tests / 77 suites** in 411.353 seconds,
131
+ including pretest/generated-documentation checks and all three TypeScript
132
+ configurations. Instrumented-library coverage is 100% for statements, branches,
133
+ functions and lines (5,445 statements, 4,044 branches and 978 functions, none
134
+ uncovered). This excludes the separately measured diagnostic scripts above.
135
+ Result: `coverage/recovery-code-full-suite.json`, SHA-256
136
+ `562e3d65c12ae5073fa51a5f8cedc25a3e0adf705fb91b22235c0e54b7603999`.
137
+ The previous milestone's 777-pass/one-shutdown-timeout result remains recorded;
138
+ the same test passing now does not explain that failure or waive it as harmless.
139
+
140
+ The senior critic independently verified source/report/log hashes, serialized
141
+ census reproduction, all phase measurements, grouped events and function identity.
142
+ Both the preflight and findings write-up were approved with no outstanding finding.
143
+ After updating the evidence, generated-documentation and TypeScript checks passed
144
+ again, followed by 63 focused documentation/diagnostic tests across four suites.
145
+
146
+ ## Next causal question
147
+
148
+ The shortlist is now specific: the HTTP/WebSocket handshake and stream lifecycle,
149
+ plus the load-test event helpers. Investigate why these functions acquire multiple
150
+ optimized versions (deoptimization reasons and dependency invalidation), then
151
+ verify surviving code/feedback objects and retaining paths if needed. Do not
152
+ rewrite Redweb or its client based only on these creation counts: this native
153
+ load generator does not import either runtime. No acceptance workload, warm-up,
154
+ memory limit, production source, dependency, frozen helper, npm link, publication,
155
+ deployment or merge changed. Release gates remain open.
156
+
157
+ Follow-up: [client deoptimization investigation](RECOVERY_DEOPTIMIZATION.md)
158
+ identifies the observed invalidation mechanism without claiming retained-size proof.
@@ -0,0 +1,103 @@
1
+ # Bounded Ubuntu recovery comparison
2
+
3
+ Status: the declared pair is complete; acceptance decision pending. No retries,
4
+ new tracing, threshold changes or replacement of the original gate followed.
5
+
6
+ ## Exact execution
7
+
8
+ [Run 33346305798](https://github.com/lakam99/redweb/actions/runs/33346305798)
9
+ executed revision `41915b0c0ef5906eca851aaef64d80b9c198cd08` once on Ubuntu
10
+ 24.04.4, image `20260823.283.1`, kernel `6.17.0-1022-azure`, Node 22.23.2,
11
+ V8 `12.4.254.21-node.56`, systemd `255.4-1ubuntu8.17`. The OS/image match the
12
+ historical failing runner; its kernel was not independently recorded. Hardware
13
+ identity or exclusive underlying hardware is not asserted.
14
+
15
+ The temporary workflow's real service probes returned exactly 0 and 7 with
16
+ confirmed cleanup before either measured command. It then ran, sequentially:
17
+
18
+ 1. `node --expose-gc scripts/verify-recovery.js` — exit 0, 8.002-second service lifetime.
19
+ 2. `node scripts/diagnostics/recovery-split.cjs baseline` — exit 0, 8.068-second service lifetime.
20
+
21
+ Both services became inactive with zero main/control PIDs and removed cgroups.
22
+ Source fingerprints before and after match, including the diagnostic report's
23
+ own fingerprint. All four installed client bundles match published client 0.2.0.
24
+ No diagnostic/workload/coverage overrides entered the measured environments.
25
+ The comparison used systemd descendant containment and a minimal environment,
26
+ unlike the historical direct-shell runner. Those differences are not concealed.
27
+
28
+ The temporary trigger was removed after collection. Its implementation remains
29
+ auditable in commit `41915b0`; do not restore it merely to obtain another pass.
30
+
31
+ ## Heap results
32
+
33
+ Bytes measured after each phase's unchanged settling/collection protocol:
34
+
35
+ | Phase | Original combined process | Split server | Split load generator |
36
+ | --- | ---: | ---: | ---: |
37
+ | Preconditioning | 10,388,616 | 9,991,120 | 6,410,728 |
38
+ | Warm | 10,379,216 | 10,023,960 | 6,360,512 |
39
+ | Storm 1 | 10,823,136 | 10,303,776 | 6,611,032 |
40
+ | Storm 2 | 11,207,736 | 10,627,552 | 7,000,048 |
41
+ | Storm 3 | 11,390,320 | 10,801,856 | 7,119,072 |
42
+ | Storm 4 | 11,330,136 | 10,875,592 | 7,156,480 |
43
+ | Storm 5 | 10,118,552 | 9,654,040 | 7,194,432 |
44
+
45
+ The original peaked at **109.741622%** of its fixed warm baseline and finished
46
+ at 97.488596%, satisfying its existing 110% bound in this one run. The isolated
47
+ server peaked at **108.495964%** and finished at 96.309642%. The split load
48
+ generator reached **113.110894%**; this is not a claim that every process satisfied
49
+ 110%. Systemd's service-accounting numbers are not substituted for Node's heap
50
+ samples or treated as equivalent process-memory measurements.
51
+
52
+ Both original and split retained empty server client/room/session registries.
53
+ The original waits for a reply per connection but does not assert each reply's
54
+ payload identity. The split verifier additionally checked **7,400 exact replies**:
55
+ client sent, client received and server received agree at every phase, client
56
+ registries are empty, and all recorded worker output is complete and hash-verified.
57
+ Its successful exit proves its stated delivery/cleanup conditions; the heap
58
+ percentages above were independently calculated from the actual samples.
59
+
60
+ ## Contrary CI evidence remains a failure
61
+
62
+ The ordinary Node 22 job at prior source head `69ea1fb` also ran during this
63
+ collection, on a separate hosted VM. Its 83 suites passed (854 tests, five
64
+ platform-specific skips), but [job 99349863069](https://github.com/lakam99/redweb/actions/runs/33345905870/job/99349863069)
65
+ failed the original recovery gate: warm 10,395,344 bytes, fourth storm 11,540,472
66
+ bytes (**111.015778%**), final 10,069,496 bytes (96.865443%). Final recovery does
67
+ not erase the failed intermediate limit. The measured production runtime and
68
+ original recovery script did not change between those heads.
69
+
70
+ The same run's Node 20 core job separately failed the trace-mode diagnostic
71
+ integration test with `write EPIPE` during worker startup. Its cause is not yet
72
+ established; it is neither a demonstrated production socket defect nor a passing
73
+ test. Node 24 and lifecycle/browser/package jobs passed. These results must not
74
+ be condensed into a green cross-runtime release claim.
75
+
76
+ ## Decision and preserved evidence
77
+
78
+ The bounded comparison supports reviewing a server-focused acceptance protocol,
79
+ not automatically adopting one. The proposed next step is a separately reviewed
80
+ server gate with explicit delivery, cleanup, shutdown and fixed memory budgets,
81
+ while retaining the original combined-process result as visible diagnostic
82
+ evidence. Maintainer approval is required before replacing any existing CI gate.
83
+ Until then the current gate and release blockers remain unchanged, and no merge
84
+ or publication is authorized by this report.
85
+
86
+ The hosted artifact `bounded-recovery-33346305798` is retained for 30 days. A local
87
+ copy remains under `coverage/bounded-recovery-33346305798`; no snapshots were
88
+ collected or uploaded. Key SHA-256 receipts:
89
+
90
+ - `recovery-comparison/inputs-before.json` (identical to `inputs-after.json`):
91
+ `26354cecf712c22a66a91e759b45c939a3a27f456ec9f3e72349eb11b3aeecb4`.
92
+ - `recovery-comparison/original.log`:
93
+ `af8b438e00ef0e901b4dcb598f942e50ce49c9a3fab021b5f82353ee517fc16f`.
94
+ - `recovery-split-8bVO1w/report.json`:
95
+ `106dee62dbc0dc8667e57bc07dd653034516ba7dd8927c499fedff5cabc6411b`.
96
+
97
+ The senior critic independently reproduced the ratios, delivery reconciliation,
98
+ sample/log hashes, input identity and recorded cleanup checks without rerunning
99
+ either workload. That is scoped evidence approval, not merge approval.
100
+
101
+ The original historical failure and deferred research remain described in
102
+ [the follow-up spike](RECOVERY_FOLLOWUP_SPIKE.md). One successful comparison is
103
+ not repeatability evidence and does not explain the underlying V8 cause.
@@ -0,0 +1,169 @@
1
+ # Client deoptimization investigation — 2026-08-30
2
+
3
+ Status: GC-dependent code invalidation identified in the measured client;
4
+ **original recovery acceptance remains open**.
5
+ This continues the [client compilation census](RECOVERY_CODE_CENSUS.md).
6
+
7
+ ## Predeclared method
8
+
9
+ One measured run, after tests and senior critic preflight:
10
+
11
+ ```sh
12
+ node scripts/diagnostics/recovery-split.cjs client-deopt
13
+ ```
14
+
15
+ This adds only `--log-deopt` to the earlier client-code mode. The worker source,
16
+ seven phases, 7,400 connections, batch size, settling, explicit collections,
17
+ server policies and original recovery limits remain unchanged. Only the client
18
+ is logged; the server retains normal flags. No concurrent tests or coverage
19
+ instrumentation enter measurement. Source-text logging remains disabled.
20
+
21
+ The pinned [V8 logger](https://github.com/nodejs/node/blob/v22.21.0/deps/v8/src/logging/log.cc)
22
+ distinguishes `dependency-change` from `deopt-eager` and `deopt-lazy`. A dependency
23
+ invalidation record does not prove an active stack bailed out. The logged code
24
+ address is correlated with earlier creations, following native `code-move` events.
25
+ Unknown moves clear stale destination associations; unmatched events stay counted
26
+ as unmatched. Raw deoptimization locations are not published. Reasons outside a
27
+ small known-label allowlist stay counted as unclassified. Native warm/final
28
+ boundaries determine the interval by log order, not by adjacent JavaScript output.
29
+ Zero record counts in earlier `client-code` logs cannot establish absence of
30
+ deoptimization: those runs did not enable `--log-deopt`. This run cannot
31
+ retroactively identify why every compiled version changed in earlier runs.
32
+
33
+ The [collector implementation](https://github.com/nodejs/node/blob/v22.21.0/deps/v8/src/heap/mark-compact.cc)
34
+ marks code with the reason `weak objects` when an embedded weak object is unmarked,
35
+ then clears embedded objects from that code. A separate
36
+ [dependency mechanism](https://github.com/nodejs/node/blob/v22.21.0/deps/v8/src/objects/dependent-code.cc)
37
+ uses `code dependencies`. Neither reason identifies a particular retained user
38
+ object. The [deoptimizer](https://github.com/nodejs/node/blob/v22.21.0/deps/v8/src/deoptimizer/deoptimizer.cc)
39
+ routes these invalidation records to the native logger. These details apply to
40
+ the pinned Node 22.21.0 / V8 12.4.254.21-node.33 runtime, not every Node release.
41
+
42
+ The census does not model code deletion, prove simultaneous code survival, or
43
+ calculate retained size. Deoptimization records report instruction-stream object
44
+ size; creation records report instruction size. Those values are intentionally
45
+ not added, equated or subtracted from the heap measurements.
46
+
47
+ ## Results
48
+
49
+ The single run acknowledged all 7,400 exact reply IDs and left client tracked
50
+ connections and server clients/rooms/sessions empty at every sample. Both workers
51
+ exited. Source fingerprints, output lengths and hashes were independently
52
+ rechecked; reparsing the serialized census exactly reproduced the report.
53
+
54
+ Within the native after-warm/after-final window (2,950,836–12,789,228 microseconds),
55
+ V8 logged 856 code creations and **703 deoptimization/invalidation records**:
56
+
57
+ | Record kind | Reason | Records |
58
+ | --- | --- | ---: |
59
+ | Dependency invalidation | `weak objects` | 698 |
60
+ | Eager bailout | `wrong map` | 4 |
61
+ | Eager bailout | `not a Smi` | 1 |
62
+
63
+ There were 813 such records over the whole log: 109 before the warm boundary,
64
+ 703 in the window and one at/after the final boundary. All interval code addresses
65
+ matched known optimized creations, including through 116 logged code moves;
66
+ there were no unmatched moves or unclassified reason labels. Fifty-three interval
67
+ records have unclassified source/function labels: matching a code address is not
68
+ the same as classifying its printable name. No records were dropped to make the
69
+ named-source totals look complete.
70
+
71
+ ### The handshake function's replacement sequence
72
+
73
+ `ws.initAsClient` has four optimized code creations for one SharedFunctionInfo
74
+ identity. Each specific code address is subsequently invalidated with the reason
75
+ `weak objects`, before the next version is created:
76
+
77
+ | Version | Created at (V8 microseconds) | Instruction bytes created | Invalidated at | Reason |
78
+ | --- | ---: | ---: | ---: | --- |
79
+ | 1 | 5,581,865 | 16,212 | 6,867,589 | `weak objects` |
80
+ | 2 | 7,886,535 | 15,612 | 8,876,052 | `weak objects` |
81
+ | 3 | 9,846,472 | 14,576 | 10,821,681 | `weak objects` |
82
+ | 4 | 11,807,985 | 15,228 | 12,784,300 | `weak objects` |
83
+
84
+ Together with the pinned collector code above, this identifies garbage-collector
85
+ dependency invalidation as the mechanism behind these observed optimized-version
86
+ replacements. It is materially more specific than merely noticing code growth.
87
+ It does **not** identify the precise weak referents, prove which collection was
88
+ forced versus natural, prove simultaneous survival of the versions, or establish
89
+ that all heap growth comes from code. No new GC trace or heap snapshot was taken.
90
+
91
+ ### Heap observations, not an acceptance result
92
+
93
+ | Phase | Server heap bytes | Client heap bytes | Client code-and-metadata bytes |
94
+ | --- | ---: | ---: | ---: |
95
+ | Preconditioning | 10,174,352 | 7,030,968 | 2,860,489 |
96
+ | Warm | 10,193,960 | 7,005,152 | 2,834,367 |
97
+ | Storm 1 | 10,475,080 | 7,241,416 | 2,985,665 |
98
+ | Storm 2 | 10,859,504 | 7,685,000 | 3,276,065 |
99
+ | Storm 3 | 10,854,912 | 7,800,448 | 3,336,345 |
100
+ | Storm 4 | 11,058,200 | 7,840,416 | 3,346,105 |
101
+ | Storm 5 | 9,818,424 | 7,843,832 | 3,330,883 |
102
+
103
+ Client heap grew 838,680 bytes and code-and-metadata grew 496,516 bytes after
104
+ warm-up. Peak/final client heap was 111.97233122136393% of warm. Client bytecode
105
+ was 756,440 at warm and 756,544 thereafter; external memory (2,331,012 bytes) and
106
+ array buffers (35,458 bytes) stayed constant after warm-up. Server peak/final
107
+ ratios were 108.47796145953093% and 96.31609305902711%. Logging and the existing
108
+ native boundaries perturb execution: these observations do not replace the
109
+ original shared-process acceptance gate. No statistics were subtracted or limits
110
+ changed to produce a pass.
111
+
112
+ ### Retained evidence
113
+
114
+ - Tooling commit: `8b4694c`, branch `codex/agent-ready`.
115
+ - UTC run: 2026-08-30 20:15:13.595–20:15:26.483, Windows/Node 22.21.0/ws 8.21.3.
116
+ - Local directory: `coverage/recovery-split-xeBAYE` (report, samples and four logs).
117
+ - Report SHA-256: `fdb0626a8fb87425eb0d9384f74066fe3fbbf045c67a4b02a94bc2012262f6e2`.
118
+ - Client stdout: 521,311 bytes; SHA-256 `bbba488633697b70c7ac35e7c0b1821d1008f542af63005710947244f1732bf8`.
119
+ - Other three logs: empty and complete, hashes verified.
120
+ - Code parser SHA-256: `44dae45083bc6a55eed71cb5840dd6ecca4e2c5a2582b0b90bb35e2908af1d5b`.
121
+ - Deoptimization parser SHA-256: `a244196245fb32c3c4ff30a9d70e15fc20b946910e7012334de0d68af99b465b`.
122
+
123
+ The raw local logs contain addresses, paths and code labels; they are not public
124
+ sanitized artifacts. The version-controlled report contains selected known-code
125
+ labels and numeric evidence instead.
126
+
127
+ ## Verification
128
+
129
+ Seventy-nine focused tests across five suites passed before measurement, including
130
+ unit tests and real HTTP/WebSocket/process integration. A separate actual-V8
131
+ fixture deterministically produces a field-dependency invalidation and a wrong-map
132
+ eager bailout. Native intrinsics and forced optimization are **test-only**, never
133
+ part of the measured workload. Other runtime versions still exercise real traffic
134
+ and capture, but explicitly reject the unsupported parser schema.
135
+
136
+ Both parser modules have 100% statement/branch/function/line coverage in Jest and
137
+ native V8 coverage. Combined diagnostic-tool coverage remains below its unchanged
138
+ 100% gate on the final tree: 83.01% statements/lines, 97.64% branches and 94.87% functions. All 79
139
+ behavior tests pass, but that aggregate coverage command correctly exits nonzero.
140
+ The old coordinator CLI/error paths and worker cleanup-barrier timeout remain
141
+ uncovered. No exclusions or weakened thresholds were introduced.
142
+
143
+ Full-suite verification passed **833 tests / 79 suites** in 418.458 seconds,
144
+ including generated-documentation and all three TypeScript pretest configurations.
145
+ Instrumented-library coverage is 100% statements/branches/functions/lines: 5,445
146
+ statements, 4,044 branches and 978 functions, none uncovered. This library
147
+ denominator does not include the diagnostic-tool gaps above. Saved result:
148
+ `coverage/recovery-deopt-full-suite.json`, SHA-256
149
+ `96ab9e8b4ee219b8682e04229e3843edbd3d133ffadfadccd089f1d1f716411b`.
150
+ The earlier shutdown timeout did not recur, but its historical failure remains
151
+ unexplained; this pass is not proof that it was harmless.
152
+
153
+ The senior critic approved preflight, independently verified every source/output
154
+ hash, census counts and all four code-address sequences, and approved the report's
155
+ wording and inference limits. There were no outstanding findings. No additional
156
+ measured run or acceptance retry was made.
157
+ After the evidence update, generated-documentation and TypeScript checks passed
158
+ again, followed by 83 focused documentation/diagnostic tests across six suites.
159
+
160
+ ## Remaining causal boundary
161
+
162
+ We now know why the observed handshake versions are replaced. The next distinct
163
+ question is what remains live after those invalidations: surviving code, feedback
164
+ structures and their retaining paths. That requires retention evidence, not more
165
+ counts of creation or invalidation events. Nothing here justifies a speculative
166
+ Redweb/runtime rewrite, suppressing GC, relaxing the recovery budget, or claiming
167
+ that no leak exists. The client load generator does not import Redweb or
168
+ `redweb-client`. Production/library source, dependencies, npm links, frozen
169
+ helpers, publication, deployment and merge state remain unchanged.