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,27 @@
1
+ # Build a chatroom with live presence
2
+
3
+ Build a shared chatroom where visitors choose a name once, send messages, and see who is online. The starter includes the canonical reusable `ChatroomComponent`, its stylesheet, input validation and real-network tests. You do not write a browser WebSocket handler for every message or member change.
4
+
5
+ ## Explain it like I'm five
6
+
7
+ The room is a noticeboard managed by the server. Each visitor gets a little window onto it. The room keeps the recent messages and the online list; each visitor's component keeps their name and what their window should show. When the noticeboard changes, the server updates the windows.
8
+
9
+ ## Follow the design
10
+
11
+ 1. The [application entrypoint](../../recipes/chat/app.tsx) starts a page created by `createChatroomPage()`. The component source shown below is copied from the maintained example, not a separate implementation.
12
+ 2. `ChatRoom` owns shared message/member data. `ChatroomComponent` owns a participant's state, server-callable actions and view. Normal TypeScript conditions choose the join screen or conversation screen.
13
+ 3. Decorated join/send actions validate form values through the starter's Zod schemas. Redweb provides loading/error feedback; invalid input does not require custom browser glue to preserve the draft.
14
+ 4. `connected()` restores online participation when a retained participant reconnects. `disconnected()` removes online presence; later disposal releases retained identity. A name reserved briefly for reconnect does not mean the person is still online.
15
+ 5. State assignments and stable JSX keys update messages and members. A function that returns reusable markup alone would not replace this component's owned lifecycle and actions.
16
+
17
+ Display names are not authenticated identities. Use the [private dashboard guide](realtime-dashboard.md) and [authorization reference](../ROOM_AUTHORIZATION.md) when your application needs verified accounts and private data.
18
+
19
+ ## Check that it works
20
+
21
+ Open two tabs at `http://localhost:8181/`, choose different names and send a message. Both should see its sender and text. Close one tab and confirm its online presence disappears once the server observes the disconnect. Abrupt network loss is not necessarily detected immediately; heartbeat and network timing matter. Reconnect is not a promise of durable identity.
22
+
23
+ The [starter tests](../../recipes/chat/app.test.cjs) exercise actual pages, sockets, server actions, escaped message delivery and disconnect behavior. The package gate repeats them after source removal. Test invalid inputs, browser focus, unsent drafts and reconnects on your supported browsers as well.
24
+
25
+ ## Before promising durable chat
26
+
27
+ History is bounded to **100 messages in server memory**. It survives neither a process restart nor independent workers. Add application-owned persistence and a deliberate cross-process notification design before promising durable history or distributed rooms. Add authentication, membership authorization and abuse controls before exposing private or public rooms. The raw socket handler starter is a different abstraction; do not replace this component with a second browser message dispatcher merely to update its HTML.
@@ -0,0 +1,28 @@
1
+ # Serve HTTP and WebSockets on one port
2
+
3
+ Build an Express endpoint and a raw WebSocket route on the same Node listener. Use this when an existing HTTP application needs socket endpoints without a second port or a separate web framework. This guide demonstrates server composition, not a rendered chat interface.
4
+
5
+ ## Explain it like I'm five
6
+
7
+ Imagine one front door with two signs. Ordinary HTTP visitors ask for a page or JSON response. WebSocket visitors ask to keep a conversation open. Both use the same door, but different route and handler classes decide what happens inside. One owner is responsible for closing the building.
8
+
9
+ ## Follow the design
10
+
11
+ 1. `HttpServer({ listen: false })` builds the Express application and Node server without opening a port. `/health` answers ordinary HTTP requests; `publicPaths: []` avoids exposing an incidental working-directory folder.
12
+ 2. Pass that Node server to `SocketServer`, alongside the `/chat` route. `listen: true` explicitly starts the supplied listener; `closeServerOnShutdown: true` assigns its cleanup to the socket service.
13
+ 3. The URL selects `ChatRoute`. A raw JSON message with `type: "hello"` selects `Hello`; there is no secondary action dispatcher.
14
+ 4. `createApp()` returns the one cleanup owner. Its `shutdown()` processes route failures and still closes the shared HTTP peers. The generated entrypoint helper adds bounded process shutdown without another handwritten signal policy.
15
+
16
+ The framework ordinarily leaves supplied listeners caller-owned. These explicit flags are a choice made by this starter, not a change to that default. Use [migration and ownership guidance](../MIGRATION.md) when adapting an existing application; do not let two independent services compete to close the same listener.
17
+
18
+ ## Check that it works
19
+
20
+ Request `http://127.0.0.1:8181/health` and expect `{"ok":true}`. Open a WebSocket to `ws://127.0.0.1:8181/chat`, send `{"type":"hello"}`, and expect `{"type":"hello","message":"Hello from the server!"}`. An unknown socket path is rejected rather than sent to a catch-all handler.
21
+
22
+ The [shipped tests](../../recipes/http-ws/app.test.cjs) use real HTTP and WebSocket clients on one ephemeral port. They also leave an HTTP request incomplete, repeat shutdown, and deliberately fail an application route's cleanup to confirm the listener still closes. Shared lifecycle tests cover process-level shutdown failures; the package gate repeats the compiled application checks with source removed.
23
+
24
+ ## Before public deployment
25
+
26
+ The starter deliberately binds loopback. Configure the deployment bind address and HTTPS/WSS termination, trusted origins, identity, authorization and capacity limits before exposing it. `/health` proves liveness, not readiness to accept game traffic or completion of durable work. Forced shutdown closes transports; it does not guarantee delivery, transaction completion or storage.
27
+
28
+ For shared validation and inferred payloads, use the [typed WebSocket guide](typed-websockets.md). For UI updates driven by server-side components, use the [chatroom guide](chatroom.md). See [operations and deployment boundaries](../MULTIPLAYER_OPERATIONS.md) before adding proxies or multiple workers.
@@ -0,0 +1,26 @@
1
+ # Render JSX without React
2
+
3
+ Build a two-page TypeScript site with shared navigation, a stylesheet and per-page metadata. JSX is a markup syntax here: Redweb renders it on Node.js, without React hooks, hydration, or a browser component runtime. This is useful for documentation, content sites and server-rendered pages that do not need browser-side component execution.
4
+
5
+ ## Explain it like I'm five
6
+
7
+ The page class is a recipe and the server is the kitchen. `render()` prepares HTML before it reaches the browser. A shared layout adds the same navigation around each page, like putting different meals on the same kind of plate. The browser receives the finished document, not the kitchen.
8
+
9
+ ## Follow the design
10
+
11
+ 1. The initializer supplies `redweb/tsconfig.json` inheritance, TypeScript, the stylesheet and the entrypoint helper. Keep the file as `.tsx`; do not point its JSX settings at `react/jsx-runtime`.
12
+ 2. `defineSite()` supplies one layout and CSS declaration. Its page decorators register `/` and `/about`, with metadata beside each page.
13
+ 3. Each `render()` returns ordinary TSX. Function components can share presentation; page-specific data remains in your server code. Text and attribute values are escaped, and URL protocols are restricted.
14
+ 4. `createApp()` combines both pages on one listener. The standalone entrypoint owns bounded shutdown through the shared helper; importing the module starts nothing.
15
+
16
+ Keep CSS in external files. The [rendering reference](../LIVE_HTML.md) covers components, templates, assets and static export. For interactive pages, start from the [realtime counter](../../recipes/realtime/README.md): assignments to decorated state update the browser through Redweb's runtime. Non-live site pages do not acquire that behavior just because their markup is JSX.
17
+
18
+ ## Check that it works
19
+
20
+ Open `http://localhost:8181/`, follow About, and confirm the navigation and styling stay consistent while the title and content change. View the response source: it is server-rendered HTML, not an empty mount point. The [shipped test](../../recipes/site/app.test.cjs) checks actual HTTP responses, both pages, CSS and absence of the live-page runtime. The package gate repeats it with the source directory unavailable.
21
+
22
+ `npm run build` produces compiled code and copied assets; `npm start` serves that output. `site.export()` is a separate static-export workflow, not what the starter's default build does. Static export cannot replace protected or live application requests.
23
+
24
+ ## When to choose another approach
25
+
26
+ Redweb TSX is not React-compatible. Do not import React components or expect hooks, client effects, browser rendering or automatic support for browser-only libraries. Choose an appropriate browser framework when those are core requirements. Live Redweb applications require a Node host with long-lived listeners; exported static files have a different deployment model. See [compatibility and release verification](../RELEASE_TRUST.md).
@@ -0,0 +1,29 @@
1
+ # Build a private realtime dashboard
2
+
3
+ Build a page where signed-in users create cards, see their other tabs update, and find the same cards after a server restart. Use the dashboard starter on **Node 22.13 or newer**; this application uses native SQLite. It is a complete application recipe, not a new database or authentication framework inside Redweb.
4
+
5
+ ## Explain it like I'm five
6
+
7
+ Think of SQLite as a locked notebook and each browser tab as a window onto it. The server checks whose notebook you may open before reading or changing a card. After a successful change, it tells that account's other open windows to read their latest cards. The windows are not the notebook: closing them does not erase saved data.
8
+
9
+ ## Follow the design
10
+
11
+ 1. The setup command below provisions `alice` after installing dependencies. It prints a generated password once; save it privately. There is no default password. Open `http://127.0.0.1:8181/login` after development starts.
12
+ 2. `app.tsx` composes the store, session checks, protected page and shutdown. The `Cards` component below owns presentation and actions, not the database connection.
13
+ 3. `loading()` reads the current account's cards. `connected()` subscribes that browser connection to private updates; disconnect and disposal release the subscription.
14
+ 4. The add/remove actions validate form input before calling the store. The store rechecks the session and owner within each write transaction; a hidden input is not permission to delete someone else's card.
15
+ 5. `PrivateCards.publish()` refreshes only valid subscribers for that account. Assigning the new array to decorated state updates keyed TSX without a manual browser message handler.
16
+
17
+ See the complete [composition](../../recipes/dashboard/app.tsx), [store](../../recipes/dashboard/store.ts), [authentication](../../recipes/dashboard/auth.ts), and [acceptance tests](../../recipes/dashboard/app.test.cjs). The generated recipe supplies all of them together.
18
+
19
+ ## Check that it works
20
+
21
+ Sign in from two tabs, add a card in one, and confirm both show it. A different account must not see it. Restart the process with the same database path and confirm the card remains. Sign out all sessions and verify both tabs lose access. `npm test` exercises real HTTP, WebSockets, temporary SQLite data, account isolation, restart and expiry; it does not modify your application database. Run your own browser checks for the browsers you support.
22
+
23
+ ## Before deployment
24
+
25
+ Keep `DASHBOARD_DATABASE` on a writable persistent volume and out of public assets, source control and logs. Follow the [recipe's origin, cookie, account-provisioning and backup instructions](../../recipes/dashboard/README.md). Browser refresh is not persistence; successful storage and a retained database are what preserve cards.
26
+
27
+ On a compiled-only deployment, provision accounts with `node dist/admin.js alice` using the same database environment and volume, before starting the service. The development `npm run add-user` script rebuilds first and therefore needs development tooling; the compiled administrator command does not. Never copy a generated password into logs or deployment manifests.
28
+
29
+ This recipe uses **single-process** notifications and revocation. Multiple workers do not automatically exchange updates or logout events. It has no password reset, MFA or account recovery; use a dedicated identity integration when those are requirements. Do not replace server-side permission checks with a `shared: true` page containing private state. See [request and room authorization](../ROOM_AUTHORIZATION.md) and [production boundaries](../PRODUCTION_READINESS.md).
@@ -0,0 +1,26 @@
1
+ # Share typed WebSocket contracts
2
+
3
+ Build a `/match` service with independent join, move and resume handlers. A shared schema validates payloads and supplies TypeScript types to both sides. Use this when you need a raw socket protocol, such as a game client or a custom realtime client, rather than a server-rendered page.
4
+
5
+ ## Explain it like I'm five
6
+
7
+ The URL is the room's address. A message's `type` tells the receptionist which person should handle it. The shared contract is the form that says what information that person needs. Checking the form before handing it over prevents a movement handler from receiving a name where a coordinate should be.
8
+
9
+ ## Follow the design
10
+
11
+ 1. [The contract](../../recipes/socket/contract.ts) declares `join`, `move`, `resume` and `state` once. It is safe to import into a browser bundle because it does not import the server application.
12
+ 2. [The route](../../recipes/socket/app.tsx) binds `/match`, enables the contract protocol and registers `Join`, `Move` and `Resume`. There is no socket decorator layer and no inner `message.action` switch.
13
+ 3. The handlers below receive parsed payloads. `Join` creates an in-memory player session, `Move` changes its server-owned coordinates, and `Resume` reclaims it using a private bearer token.
14
+ 4. Each sends a validated `state` response. A client uses `match.client(socket)` to send and parse typed messages. That wrapper does **not** open or reconnect its transport; the application creates the WebSocket first.
15
+
16
+ Connect to `ws://localhost:8181/match?redwebVersion=1` during local development. Follow the [complete recipe's client example](../../recipes/socket/README.md) for opening a transport and handling responses; use WSS outside local development. The [contract reference](../SOCKET_CONTRACTS.md) documents wire envelopes, validation and failure behavior.
17
+
18
+ ## Check that it works
19
+
20
+ Join with two independent clients, move one, then disconnect and resume it with its session token. The other player's state must remain independent. Send invalid coordinates and a malformed raw message to verify both client and server checks. The [real-socket acceptance test](../../recipes/socket/app.test.cjs) covers those sequences, including server rejection that bypasses client validation.
21
+
22
+ ## This is not a complete game backend
23
+
24
+ The example bounds coordinate values; it does not prove a move obeys your game's speed, turn or collision rules. Add authoritative game rules and authentication. Keep session tokens private: possession permits resume and connection takeover. Sessions are capped at 100, expire 30 seconds after disconnect, and are lost on restart. They are not a durable or cross-worker identity store.
25
+
26
+ Per-connection ordering is not exactly-once delivery. After a disconnect, a client may not know whether its last action completed; reconcile state before retrying side effects. Choose transport limits from measured load and read [operations](../MULTIPLAYER_OPERATIONS.md) and [runtime retry boundaries](../RUNTIME_DIAGNOSTICS.md).