@powerhousedao/switchboard 6.2.3-dev.3 → 6.2.3-dev.31

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 (96) hide show
  1. package/.tsbuild/test/attachments/byte-grants.test.d.ts +2 -0
  2. package/.tsbuild/test/attachments/byte-grants.test.d.ts.map +1 -0
  3. package/.tsbuild/test/attachments/byte-routes-read-gate.integration.test.d.ts +2 -0
  4. package/.tsbuild/test/attachments/byte-routes-read-gate.integration.test.d.ts.map +1 -0
  5. package/.tsbuild/test/attachments/url-signer.test.d.ts +2 -0
  6. package/.tsbuild/test/attachments/url-signer.test.d.ts.map +1 -0
  7. package/.tsbuild/test/boot-create-signature-policy.test.d.ts +2 -0
  8. package/.tsbuild/test/boot-create-signature-policy.test.d.ts.map +1 -0
  9. package/.tsbuild/test/boot-without-renown.test.d.ts +2 -0
  10. package/.tsbuild/test/boot-without-renown.test.d.ts.map +1 -0
  11. package/.tsbuild/test/boot-without-require-signatures.test.d.ts +2 -0
  12. package/.tsbuild/test/boot-without-require-signatures.test.d.ts.map +1 -0
  13. package/.tsbuild/test/boot-worker-trust-policy.test.d.ts +2 -0
  14. package/.tsbuild/test/boot-worker-trust-policy.test.d.ts.map +1 -0
  15. package/.tsbuild/test/builder-defaults.test.d.ts +2 -0
  16. package/.tsbuild/test/builder-defaults.test.d.ts.map +1 -0
  17. package/.tsbuild/test/create-signature-policy.test.d.ts +2 -0
  18. package/.tsbuild/test/create-signature-policy.test.d.ts.map +1 -0
  19. package/.tsbuild/test/fatal-shutdown.test.d.ts +2 -0
  20. package/.tsbuild/test/fatal-shutdown.test.d.ts.map +1 -0
  21. package/.tsbuild/test/renown-trust-policy.test.d.ts +2 -0
  22. package/.tsbuild/test/renown-trust-policy.test.d.ts.map +1 -0
  23. package/.tsbuild/test/workflow/harness.d.ts +56 -0
  24. package/.tsbuild/test/workflow/harness.d.ts.map +1 -0
  25. package/.tsbuild/test/workflow/oauth.test.d.ts +2 -0
  26. package/.tsbuild/test/workflow/oauth.test.d.ts.map +1 -0
  27. package/.tsbuild/test/workflow/resolvers.test.d.ts +2 -0
  28. package/.tsbuild/test/workflow/resolvers.test.d.ts.map +1 -0
  29. package/.tsbuild/test/workflow/webhook-dedupe.test.d.ts +2 -0
  30. package/.tsbuild/test/workflow/webhook-dedupe.test.d.ts.map +1 -0
  31. package/.tsbuild/test/workflow/webhook-signed.test.d.ts +2 -0
  32. package/.tsbuild/test/workflow/webhook-signed.test.d.ts.map +1 -0
  33. package/.tsbuild/test/workflow/webhook-trigger-test.test.d.ts +2 -0
  34. package/.tsbuild/test/workflow/webhook-trigger-test.test.d.ts.map +1 -0
  35. package/.tsbuild/test/workflow/webhook-unsigned.test.d.ts +2 -0
  36. package/.tsbuild/test/workflow/webhook-unsigned.test.d.ts.map +1 -0
  37. package/.tsbuild/test/workflow-read-gate.test.d.ts +2 -0
  38. package/.tsbuild/test/workflow-read-gate.test.d.ts.map +1 -0
  39. package/.tsbuild/test/workflow-runtime.test.d.ts +2 -0
  40. package/.tsbuild/test/workflow-runtime.test.d.ts.map +1 -0
  41. package/.tsbuild/tsconfig.tsbuildinfo +1 -1
  42. package/.tsbuild/tsdown.config.d.ts.map +1 -1
  43. package/.tsbuild/vitest.config.d.ts.map +1 -1
  44. package/Auth.md +94 -0
  45. package/CHANGELOG.md +299 -0
  46. package/README.md +14 -0
  47. package/dist/index.d.mts +2 -1
  48. package/dist/index.mjs +6 -6
  49. package/dist/index.mjs.map +1 -1
  50. package/dist/install-packages.d.mts +2 -1
  51. package/dist/install-packages.mjs +2 -2
  52. package/dist/install-packages.mjs.map +1 -1
  53. package/dist/migrate.d.mts +2 -1
  54. package/dist/migrate.mjs +4 -3
  55. package/dist/migrate.mjs.map +1 -1
  56. package/dist/{server-B2r6AQ4Q.mjs → server-CKKn0aXV.mjs} +1394 -65
  57. package/dist/server-CKKn0aXV.mjs.map +1 -0
  58. package/dist/server.d.mts +76 -28
  59. package/dist/server.d.mts.map +1 -1
  60. package/dist/server.mjs +3 -4
  61. package/dist/{utils-Baw7rThP.mjs → utils-uaBlzyjS.mjs} +15 -9
  62. package/dist/utils-uaBlzyjS.mjs.map +1 -0
  63. package/dist/utils.d.mts +4 -5
  64. package/dist/utils.d.mts.map +1 -1
  65. package/dist/utils.mjs +3 -3
  66. package/package.json +21 -17
  67. package/test/attachments/auth.test.ts +85 -0
  68. package/test/attachments/byte-grants.test.ts +287 -0
  69. package/test/attachments/byte-routes-read-gate.integration.test.ts +314 -0
  70. package/test/attachments/download-target.test.ts +194 -31
  71. package/test/attachments/index.test.ts +34 -3
  72. package/test/attachments/routes-integration.test.ts +59 -4
  73. package/test/attachments/routes.test.ts +165 -23
  74. package/test/attachments/service-config.test.ts +13 -3
  75. package/test/attachments/url-signer.test.ts +105 -0
  76. package/test/boot-create-signature-policy.test.ts +108 -0
  77. package/test/boot-without-renown.test.ts +81 -0
  78. package/test/boot-without-require-signatures.test.ts +74 -0
  79. package/test/boot-worker-trust-policy.test.ts +90 -0
  80. package/test/builder-defaults.test.ts +80 -0
  81. package/test/create-signature-policy.test.ts +69 -0
  82. package/test/fatal-shutdown.test.ts +102 -0
  83. package/test/renown-trust-policy.test.ts +315 -0
  84. package/test/workflow/harness.ts +289 -0
  85. package/test/workflow/oauth.test.ts +214 -0
  86. package/test/workflow/resolvers.test.ts +296 -0
  87. package/test/workflow/webhook-dedupe.test.ts +295 -0
  88. package/test/workflow/webhook-signed.test.ts +509 -0
  89. package/test/workflow/webhook-trigger-test.test.ts +82 -0
  90. package/test/workflow/webhook-unsigned.test.ts +345 -0
  91. package/test/workflow-read-gate.test.ts +145 -0
  92. package/test/workflow-runtime.test.ts +486 -0
  93. package/tsconfig.json +6 -0
  94. package/tsdown.config.ts +3 -1
  95. package/dist/server-B2r6AQ4Q.mjs.map +0 -1
  96. package/dist/utils-Baw7rThP.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"tsdown.config.d.ts","sourceRoot":"","sources":["../tsdown.config.ts"],"names":[],"mappings":";AAEA,wBAaG"}
1
+ {"version":3,"file":"tsdown.config.d.ts","sourceRoot":"","sources":["../tsdown.config.ts"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"vitest.config.d.ts","sourceRoot":"","sources":["../vitest.config.ts"],"names":[],"mappings":";AAEA,wBAQG"}
1
+ {"version":3,"file":"vitest.config.d.ts","sourceRoot":"","sources":["../vitest.config.ts"],"names":[],"mappings":""}
package/Auth.md CHANGED
@@ -133,6 +133,57 @@ interface AuthConfig {
133
133
  - **Document Owners**: implicit ADMIN on documents they create
134
134
  - **Per-Document Grants**: READ / WRITE / ADMIN granted to users or groups, inherited from protected ancestors
135
135
 
136
+ #### Attachments
137
+
138
+ An attachment is readable by whoever may read the document that references it.
139
+ `GET /attachments/:hash/download-target` decides that twice over: the caller
140
+ must be able to read the document's `global` scope, and the reference index
141
+ must confirm that the document really does reference this hash. A denial is a
142
+ single generic `404` either way, so it never tells the caller which of the two
143
+ failed.
144
+
145
+ Which model answers the first half is the deployment's choice, and by default it
146
+ is the permission tables above — unchanged.
147
+
148
+ ```bash
149
+ # Decide an attachment read with the referencing document's own policy
150
+ export ATTACHMENT_READS_FOLLOW_DOCUMENT_POLICY=true
151
+ ```
152
+
153
+ With it on, the document's **own policy** decides, evaluated for the caller's
154
+ subject: their address *and* the `did:key` of the app instance whose token
155
+ authenticated them, because a grant can name either and a document's creator is
156
+ recorded by key. It needs a policy model to evaluate, which auth enforcement is
157
+ what supplies, and refuses to boot without one rather than leave the tables
158
+ deciding while the configuration says otherwise.
159
+
160
+ Why a deployment would want it: one whose documents carry policies keeps no rows
161
+ in the permission tables, so asking those tables about such a document returns
162
+ whatever the host-wide policy says — under `OPEN`, `true`, for every caller
163
+ including an anonymous one. Handing out bytes on that answer gives the file to
164
+ anyone who learns its hash, which the document's own state may well have told
165
+ them before their access was taken away.
166
+
167
+ Two things to know before turning it on. A document that carries **no** policy
168
+ stays readable, because an uninitialized policy is not a denial — unless the
169
+ host also sets `DEFAULT_PROTECTION`, which tells the gate to withhold what it
170
+ cannot decide, and then attachments on unpolicied documents are refused along
171
+ with everything else about them. And every non-browser client that downloads
172
+ bytes — an extraction worker, an indexer — needs a read grant of its own on the
173
+ documents it fetches, or it stops working the moment this is enabled.
174
+
175
+ The presigned URL a target carries is short-lived by design: the authorization
176
+ behind it is decided once, when it is issued, and the URL keeps working until it
177
+ expires however the policy changes in between. The default ceiling is **300
178
+ seconds**, and it applies whether or not the caller asked for a lifetime —
179
+ otherwise omitting `expiresIn` would be a way to opt out of it.
180
+
181
+ ```bash
182
+ # Raise it if a deployment genuinely needs longer; still bounded by the
183
+ # 7-day maximum a SigV4 signature can carry.
184
+ export ATTACHMENT_DOWNLOAD_TARGET_MAX_TTL_SECONDS=1800
185
+ ```
186
+
136
187
  ### 6. **Session Management**
137
188
 
138
189
  Advanced session handling with multiple active sessions:
@@ -221,6 +272,49 @@ It requires a caller to be resolvable at all, so it refuses to boot without
221
272
  resolution off, no bearer is ever read and it would reject every caller,
222
273
  including authenticated ones.
223
274
 
275
+ It also covers the attachment routes, which are mounted on the HTTP adapter and
276
+ never pass the GraphQL fetch chain. Their own 401 keys on `AUTH_ENABLED`, so
277
+ without this switch a deployment running `OPEN` serves them to anyone; with it,
278
+ every attachment route refuses a caller it cannot name — `download-target` and
279
+ the `GET`/`HEAD /attachments/:hash` byte routes included, even though they
280
+ decide per document (or by a signed URL) on their own. A filesystem signed
281
+ download URL therefore needs a bearer too while this switch is on.
282
+
283
+ ##### Serving one path anonymously
284
+
285
+ Some products have a flow that runs *before* sign-in: previewing an invitation
286
+ from the code in its e-mail, for instance, so the screen can say who the
287
+ invitation is for. That flow has no caller by definition, and the floor above
288
+ would answer it a `401`.
289
+
290
+ ```bash
291
+ export REQUIRE_AUTHENTICATED_CALLER=true
292
+ export REQUIRE_AUTHENTICATED_CALLER_EXEMPT_PATHS=/graphql/public
293
+ ```
294
+
295
+ Comma-separated, so several paths are one variable:
296
+
297
+ ```bash
298
+ export REQUIRE_AUTHENTICATED_CALLER_EXEMPT_PATHS=/graphql/public,/graphql/invites
299
+ ```
300
+
301
+ Each entry is matched against the request's **pathname, in full** — never as a
302
+ prefix, so `/graphql/public` does not also exempt `/graphql/public-admin`. A
303
+ trailing slash on either side is ignored; a leading slash is required, and a
304
+ path without one is refused at boot rather than left as an exemption that
305
+ silently never applies. Configuring exemptions while the floor is off is
306
+ refused too: it would describe a protection the server is not applying.
307
+
308
+ The exempt paths are named in the boot log, because the one question worth
309
+ asking about this floor is what is still open.
310
+
311
+ Every entry is a hole in it. Only ever name a path that serves operations which
312
+ are safe without a caller, and prefer a route mounted for exactly that purpose
313
+ over exempting one that also serves something else. Note what this switch does
314
+ **not** reach, in either configuration: routes registered directly on the HTTP
315
+ adapter (`/health`, `/ready`, `/explorer`, `/d/:drive`) and package HTTP routes,
316
+ which declare their own `auth` per route.
317
+
224
318
  #### Configuration File Method
225
319
 
226
320
  ```json
package/CHANGELOG.md CHANGED
@@ -1,3 +1,302 @@
1
+ ## 6.2.3-dev.31 (2026-09-29)
2
+
3
+ ### 🚀 Features
4
+
5
+ - **ph-cli:** seed a Workflows drive in vetra when workflows are on ([6e855808e](https://github.com/powerhouse-inc/powerhouse/commit/6e855808e))
6
+ - **workflow:** sign connections in with OAuth2 using their own app ([b03b33bf0](https://github.com/powerhouse-inc/powerhouse/commit/b03b33bf0))
7
+ - **reactor-workflow:** keyset-paginate the run listing through the subgraph and runs views ([d22a4eea5](https://github.com/powerhouse-inc/powerhouse/commit/d22a4eea5))
8
+ - **reactor-workflow:** renew webhook trigger subscriptions ([4fc6bfea6](https://github.com/powerhouse-inc/powerhouse/commit/4fc6bfea6))
9
+ - ⚠️ **switchboard:** block inputs, step and trigger tests, and version resolutions in the workflow API ([58b43b249](https://github.com/powerhouse-inc/powerhouse/commit/58b43b249))
10
+
11
+ ### 🩹 Fixes
12
+
13
+ - **reactor-workflow:** order runs by a key that doesn't change when they start ([f15425dd8](https://github.com/powerhouse-inc/powerhouse/commit/f15425dd8))
14
+ - **reactor-workflow:** disarm a workflow's trigger when its document is deleted ([866427bc6](https://github.com/powerhouse-inc/powerhouse/commit/866427bc6))
15
+
16
+ ### 🔥 Performance
17
+
18
+ - **reactor-workflow:** batch run listing step and document reads ([c33a42f7d](https://github.com/powerhouse-inc/powerhouse/commit/c33a42f7d))
19
+
20
+ ### ⚠️ Breaking Changes
21
+
22
+ - **switchboard:** block inputs, step and trigger tests, and version resolutions in the workflow API ([58b43b249](https://github.com/powerhouse-inc/powerhouse/commit/58b43b249))
23
+
24
+ ### ❤️ Thank You
25
+
26
+ - acaldas
27
+
28
+ ## 6.2.3-dev.30 (2026-09-29)
29
+
30
+ ### 🩹 Fixes
31
+
32
+ - **deps:** load cmd-ts's ESM build under bun, which fails to require chalk from its CJS build ([749528c71](https://github.com/powerhouse-inc/powerhouse/commit/749528c71))
33
+
34
+ ### ❤️ Thank You
35
+
36
+ - acaldas
37
+
38
+ ## 6.2.3-dev.29 (2026-09-28)
39
+
40
+ ### 🩹 Fixes
41
+
42
+ - **reactor-workflow:** refuse a secrets master key other than the one secrets were stored with ([87cfde0f5](https://github.com/powerhouse-inc/powerhouse/commit/87cfde0f5))
43
+
44
+ ### ❤️ Thank You
45
+
46
+ - acaldas
47
+
48
+ ## 6.2.3-dev.28 (2026-09-28)
49
+
50
+ ### 🚀 Features
51
+
52
+ - **reactor-workflow:** carry Activepieces layout hints, property groups and search to the editor ([4eca1e1bd](https://github.com/powerhouse-inc/powerhouse/commit/4eca1e1bd))
53
+
54
+ ### ❤️ Thank You
55
+
56
+ - acaldas
57
+
58
+ ## 6.2.3-dev.27 (2026-09-28)
59
+
60
+ ### 🚀 Features
61
+
62
+ - ⚠️ **switchboard:** serve attachment bytes only under a document grant ([275e88da0](https://github.com/powerhouse-inc/powerhouse/commit/275e88da0))
63
+ - **switchboard:** sign short-lived attachment byte-route URLs ([18e09cdb5](https://github.com/powerhouse-inc/powerhouse/commit/18e09cdb5))
64
+
65
+ ### 🩹 Fixes
66
+
67
+ - **switchboard:** sign byte-route URLs under main's download-target TTL cap ([d503b4f67](https://github.com/powerhouse-inc/powerhouse/commit/d503b4f67))
68
+ - **reactor-workflow:** serve workflow reads and runs as the caller ([b2ddd126a](https://github.com/powerhouse-inc/powerhouse/commit/b2ddd126a))
69
+ - **reactor-drive:** gate the reactor-drive subgraph as the caller ([7041635f4](https://github.com/powerhouse-inc/powerhouse/commit/7041635f4))
70
+ - **reactor-api:** gate attachment reads through the reactor's read gate ([d6c3b4df1](https://github.com/powerhouse-inc/powerhouse/commit/d6c3b4df1))
71
+
72
+ ### ⚠️ Breaking Changes
73
+
74
+ - **switchboard:** serve attachment bytes only under a document grant ([275e88da0](https://github.com/powerhouse-inc/powerhouse/commit/275e88da0))
75
+
76
+ ### ❤️ Thank You
77
+
78
+ - Benjamin Jordan
79
+ - Claude Opus 5.5
80
+
81
+ ## 6.2.3-dev.26 (2026-09-26)
82
+
83
+ ### 🚀 Features
84
+
85
+ - ⚠️ **switchboard:** serve attachment bytes only under a document grant ([275e88da0](https://github.com/powerhouse-inc/powerhouse/commit/275e88da0))
86
+ - **switchboard:** sign short-lived attachment byte-route URLs ([18e09cdb5](https://github.com/powerhouse-inc/powerhouse/commit/18e09cdb5))
87
+
88
+ ### 🩹 Fixes
89
+
90
+ - **switchboard:** sign byte-route URLs under main's download-target TTL cap ([d503b4f67](https://github.com/powerhouse-inc/powerhouse/commit/d503b4f67))
91
+ - **reactor-workflow:** serve workflow reads and runs as the caller ([b2ddd126a](https://github.com/powerhouse-inc/powerhouse/commit/b2ddd126a))
92
+ - **reactor-drive:** gate the reactor-drive subgraph as the caller ([7041635f4](https://github.com/powerhouse-inc/powerhouse/commit/7041635f4))
93
+ - **reactor-api:** gate attachment reads through the reactor's read gate ([d6c3b4df1](https://github.com/powerhouse-inc/powerhouse/commit/d6c3b4df1))
94
+
95
+ ### ⚠️ Breaking Changes
96
+
97
+ - **switchboard:** serve attachment bytes only under a document grant ([275e88da0](https://github.com/powerhouse-inc/powerhouse/commit/275e88da0))
98
+
99
+ ### ❤️ Thank You
100
+
101
+ - Benjamin Jordan
102
+ - Claude Opus 5.5
103
+
104
+ ## 6.2.3-dev.25 (2026-09-25)
105
+
106
+ ### 🚀 Features
107
+
108
+ - **workflow:** show run data in a lazy-loaded tree with copyable references, and scope the connection picker to the drive ([1d286f4a6](https://github.com/powerhouse-inc/powerhouse/commit/1d286f4a6))
109
+ - **reactor-workflow:** record when each step starts and ends ([6f6d8a233](https://github.com/powerhouse-inc/powerhouse/commit/6f6d8a233))
110
+ - **reactor-api:** serve an attachment only to callers who may read its document ([#3109](https://github.com/powerhouse-inc/powerhouse/pull/3109))
111
+
112
+ ### 🩹 Fixes
113
+
114
+ - **ci:** let the release skip the transpiler TypeScript 7 broke ([#36306](https://github.com/powerhouse-inc/powerhouse/issues/36306))
115
+
116
+ ### ❤️ Thank You
117
+
118
+ - acaldas
119
+ - Guillermo Puente
120
+ - Guillermo Puente Sandoval
121
+
122
+ ## 6.2.3-dev.24 (2026-09-25)
123
+
124
+ ### 🚀 Features
125
+
126
+ - **reactor-api:** exempt named paths from the authenticated-caller floor, and carry it to the attachment routes ([#3108](https://github.com/powerhouse-inc/powerhouse/pull/3108))
127
+ - **reactor:** action signature integrity ([#3088](https://github.com/powerhouse-inc/powerhouse/pull/3088), [#2894](https://github.com/powerhouse-inc/powerhouse/issues/2894), [#7](https://github.com/powerhouse-inc/powerhouse/issues/7))
128
+ - **reactor-workflow:** refuse the auth and trigger features the engine can't run ([7ce03d28f](https://github.com/powerhouse-inc/powerhouse/commit/7ce03d28f))
129
+ - type-safe piece authoring, upstream connection hooks, and a ph build that typechecks first ([#3082](https://github.com/powerhouse-inc/powerhouse/pull/3082))
130
+
131
+ ### 🩹 Fixes
132
+
133
+ - **workflow:** build after powerhouse-vetra-packages so their tsc --build runs never overlap ([#3101](https://github.com/powerhouse-inc/powerhouse/pull/3101))
134
+
135
+ ### ❤️ Thank You
136
+
137
+ - acaldas
138
+ - Benjamin Jordan
139
+ - Claude Fable 5.1
140
+ - Guillermo Puente Sandoval @gpuente
141
+
142
+ ## 6.2.3-dev.23 (2026-09-24)
143
+
144
+ ### 🩹 Fixes
145
+
146
+ - **switchboard:** leave unhandled rejections to Sentry when it listens ([c6a40108e](https://github.com/powerhouse-inc/powerhouse/commit/c6a40108e))
147
+ - **switchboard:** flush PGlite before exiting on an uncaught error ([#3077](https://github.com/powerhouse-inc/powerhouse/issues/3077))
148
+ - **switchboard:** escape non-ASCII file names in attachment metadata headers ([#3077](https://github.com/powerhouse-inc/powerhouse/issues/3077))
149
+
150
+ ### ❤️ Thank You
151
+
152
+ - Benjamin Jordan
153
+ - Claude Opus 5.5
154
+
155
+ ## 6.2.3-dev.22 (2026-09-23)
156
+
157
+ ### 🩹 Fixes
158
+
159
+ - **ci:** slice the duration-watch baseline in jq, not through head ([73db497e0](https://github.com/powerhouse-inc/powerhouse/commit/73db497e0))
160
+
161
+ ### ❤️ Thank You
162
+
163
+ - acaldas
164
+
165
+ ## 6.2.3-dev.21 (2026-09-22)
166
+
167
+ This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
168
+
169
+ ## 6.2.3-dev.20 (2026-09-22)
170
+
171
+ This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
172
+
173
+ ## 6.2.3-dev.19 (2026-09-21)
174
+
175
+ This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
176
+
177
+ ## 6.2.3-dev.18 (2026-09-21)
178
+
179
+ ### 🚀 Features
180
+
181
+ - **workflow:** read pieces from a Powerhouse registry ([f1467e0b0](https://github.com/powerhouse-inc/powerhouse/commit/f1467e0b0))
182
+
183
+ ### 🩹 Fixes
184
+
185
+ - **switchboard:** load the workflow package like any other package ([7629bf71f](https://github.com/powerhouse-inc/powerhouse/commit/7629bf71f))
186
+
187
+ ### ❤️ Thank You
188
+
189
+ - acaldas
190
+
191
+ ## 6.2.3-dev.17 (2026-09-21)
192
+
193
+ This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
194
+
195
+ ## 6.2.3-dev.16 (2026-09-21)
196
+
197
+ This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
198
+
199
+ ## 6.2.3-dev.15 (2026-09-20)
200
+
201
+ This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
202
+
203
+ ## 6.2.3-dev.14 (2026-09-19)
204
+
205
+ ### 🚀 Features
206
+
207
+ - **ph-cli:** build pieces on ph build ([3913b0796](https://github.com/powerhouse-inc/powerhouse/commit/3913b0796))
208
+ - **doc-harness:** serve rendered reports and transcripts from mastra studio ([fd64a4661](https://github.com/powerhouse-inc/powerhouse/commit/fd64a4661))
209
+ - **doc-harness:** workflows, steps, and the run/resume/inspect commands ([0e116d226](https://github.com/powerhouse-inc/powerhouse/commit/0e116d226))
210
+ - **doc-harness:** pilot task catalog with pinned recipe inputs ([74bc26129](https://github.com/powerhouse-inc/powerhouse/commit/74bc26129))
211
+ - **doc-harness:** workspace wiring and core schemas for the docs-validation harness ([5c591ac69](https://github.com/powerhouse-inc/powerhouse/commit/5c591ac69))
212
+
213
+ ### ❤️ Thank You
214
+
215
+ - acaldas
216
+ - Benjamin Jordan
217
+ - Claude Fable 5.1
218
+
219
+ ## 6.2.3-dev.13 (2026-09-18)
220
+
221
+ This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
222
+
223
+ ## 6.2.3-dev.12 (2026-09-17)
224
+
225
+ ### 🚀 Features
226
+
227
+ - **switchboard:** own the workflow runtime, feed it through a read model, and take workflows out of reactor-api ([#3042](https://github.com/powerhouse-inc/powerhouse/issues/3042))
228
+ - **reactor-workflow:** the workflow engine, composed in reactor-api behind the workflows flag ([cb807bf5d](https://github.com/powerhouse-inc/powerhouse/commit/cb807bf5d))
229
+ - **workflow:** the Connect-loaded workflow package, and the workflows flag in reactor-api ([0953fc254](https://github.com/powerhouse-inc/powerhouse/commit/0953fc254))
230
+ - **pieces-framework:** vendor the Activepieces piece framework as a Powerhouse package ([3da5dbea8](https://github.com/powerhouse-inc/powerhouse/commit/3da5dbea8))
231
+
232
+ ### ❤️ Thank You
233
+
234
+ - acaldas
235
+
236
+ ## 6.2.3-dev.11 (2026-09-17)
237
+
238
+ ### 🚀 Features
239
+
240
+ - **reactor-browser,design-system:** keep the legacy useDocumentOperations and RevisionHistory shapes as deprecated aliases ([6de05e632](https://github.com/powerhouse-inc/powerhouse/commit/6de05e632))
241
+
242
+ ### 🩹 Fixes
243
+
244
+ - **design-system,connect:** end the history walk on a failed page and show what loaded ([95f815b46](https://github.com/powerhouse-inc/powerhouse/commit/95f815b46))
245
+ - hold the timeline revision until the global history is loaded, default to the global scope, hide paging mid-load ([80c3b1289](https://github.com/powerhouse-inc/powerhouse/commit/80c3b1289))
246
+ - **reactor-browser:** page operations through nextCursor and invalidate alias keys ([ccb965869](https://github.com/powerhouse-inc/powerhouse/commit/ccb965869))
247
+
248
+ ### 🔥 Performance
249
+
250
+ - **design-system,connect:** wait for the whole history before rendering the timeline, yield between pages, fetch 500 per page ([86435721c](https://github.com/powerhouse-inc/powerhouse/commit/86435721c))
251
+
252
+ ### ❤️ Thank You
253
+
254
+ - Frank Pfeift
255
+
256
+ ## 6.2.3-dev.10 (2026-09-16)
257
+
258
+ This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
259
+
260
+ ## 6.2.3-dev.9 (2026-09-15)
261
+
262
+ This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
263
+
264
+ ## 6.2.3-dev.8 (2026-09-15)
265
+
266
+ This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
267
+
268
+ ## 6.2.3-dev.7 (2026-09-14)
269
+
270
+ This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
271
+
272
+ ## 6.2.3-dev.6 (2026-09-14)
273
+
274
+ This was a version bump only for @powerhousedao/switchboard to align it with other projects, there were no code changes.
275
+
276
+ ## 6.2.3-dev.5 (2026-09-13)
277
+
278
+ ### 🩹 Fixes
279
+
280
+ - **reactor-api:** warn at boot when auth is on but anonymous is still admitted ([84605330c](https://github.com/powerhouse-inc/powerhouse/commit/84605330c))
281
+ - **document-model:** make ConsoleLogger methods safe to detach ([686032a9a](https://github.com/powerhouse-inc/powerhouse/commit/686032a9a))
282
+
283
+ ### ❤️ Thank You
284
+
285
+ - Benjamin Jordan
286
+ - Claude Opus 5
287
+
288
+ ## 6.2.3-dev.4 (2026-09-12)
289
+
290
+ ### 🩹 Fixes
291
+
292
+ - **reactor-api:** warn at boot when auth is on but anonymous is still admitted ([84605330c](https://github.com/powerhouse-inc/powerhouse/commit/84605330c))
293
+ - **document-model:** make ConsoleLogger methods safe to detach ([686032a9a](https://github.com/powerhouse-inc/powerhouse/commit/686032a9a))
294
+
295
+ ### ❤️ Thank You
296
+
297
+ - Benjamin Jordan
298
+ - Claude Opus 5
299
+
1
300
  ## 6.2.3-dev.3 (2026-09-11)
2
301
 
3
302
  ### 🚀 Features
package/README.md CHANGED
@@ -186,6 +186,20 @@ exits non-zero instead of waiting out the init timeout.
186
186
 
187
187
  Programmatically: `startSwitchboard({ projectionWorker: { enabled, dbPoolSize } })`.
188
188
 
189
+ ### Creation Signature Policy
190
+
191
+ | Variable | Description | Default |
192
+ | ------------------------- | ------------------------------------------------------------ | ------------- |
193
+ | `CREATE_SIGNATURE_POLICY` | What new documents are created as: `legacy` or `v2-required` | `v2-required` |
194
+
195
+ A v2-required document accepts only v2 action signatures and takes a
196
+ content-addressed id. Set `legacy` while any reactor that syncs with this one
197
+ predates v2-required documents. It only decides what new documents are born
198
+ as; existing documents keep their policy, and it does not change what the
199
+ switchboard accepts. Without a Renown identity the switchboard has no signer,
200
+ so it creates legacy documents and logs a warning. A default drive configured
201
+ with a fixed `id` is always created legacy.
202
+
189
203
  ### Reactor Enforcement Flags
190
204
 
191
205
  | Variable | Description | Default |
package/dist/index.d.mts CHANGED
@@ -1,2 +1,3 @@
1
1
  import { NodeSDK } from "@opentelemetry/sdk-node";
2
- import { MeterProvider } from "@opentelemetry/sdk-metrics";
2
+ import { MeterProvider } from "@opentelemetry/sdk-metrics";
3
+ export {};
package/dist/index.mjs CHANGED
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="c6c10af1-f4cc-56a5-abf6-fa35cf0de956")}catch(e){}}();
4
- import { a as parseForcePgVersion, r as startSwitchboard } from "./server-B2r6AQ4Q.mjs";
5
- import "./utils-Baw7rThP.mjs";
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="f6ffead6-4742-59d9-948c-1c651716e65b")}catch(e){}}();
4
+ import { a as parseForcePgVersion, r as startSwitchboard } from "./server-CKKn0aXV.mjs";
6
5
  import { metrics } from "@opentelemetry/api";
7
6
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
8
7
  import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
@@ -201,7 +200,7 @@ async function initProfilerFromEnv(env) {
201
200
  collectCpuTime: true
202
201
  },
203
202
  heap: {
204
- samplingIntervalBytes: 512 * 1024,
203
+ samplingIntervalBytes: 524288,
205
204
  stackDepth: 64
206
205
  }
207
206
  }, {
@@ -246,7 +245,8 @@ try {
246
245
  await startSwitchboard({
247
246
  ...config,
248
247
  migratePglite: cliMigratePglite || config.migratePglite,
249
- forcePgVersion: config.forcePgVersion ?? void 0
248
+ forcePgVersion: config.forcePgVersion ?? void 0,
249
+ fatalErrorShutdown: true
250
250
  });
251
251
  } catch (e) {
252
252
  Sentry.captureException(e);
@@ -258,4 +258,4 @@ try {
258
258
  export {};
259
259
 
260
260
  //# sourceMappingURL=index.mjs.map
261
- //# debugId=c6c10af1-f4cc-56a5-abf6-fa35cf0de956
261
+ //# debugId=f6ffead6-4742-59d9-948c-1c651716e65b
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../src/metrics.ts","../src/redact-webhook-path.ts","../src/observability.mts","../src/config.ts","../src/profiler.ts","../src/index.mts"],"sourcesContent":["import { OTLPMetricExporter } from \"@opentelemetry/exporter-metrics-otlp-http\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport {\n MeterProvider,\n PeriodicExportingMetricReader,\n} from \"@opentelemetry/sdk-metrics\";\nimport { childLogger } from \"document-model\";\n\nconst logger = childLogger([\"switchboard\", \"metrics\"]);\n\nexport function createMeterProviderFromEnv(env: {\n OTEL_EXPORTER_OTLP_ENDPOINT?: string;\n OTEL_METRIC_EXPORT_INTERVAL?: string;\n OTEL_SERVICE_NAME?: string;\n}): MeterProvider | undefined {\n const endpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT;\n if (!endpoint) return undefined;\n\n const parsed = parseInt(env.OTEL_METRIC_EXPORT_INTERVAL ?? \"\", 10);\n const exportIntervalMillis =\n Number.isFinite(parsed) && parsed > 0 ? parsed : 5_000;\n\n const base = endpoint.replace(/\\/$/, \"\");\n const exporterUrl = base.endsWith(\"/v1/metrics\")\n ? base\n : `${base}/v1/metrics`;\n\n logger.info(`Initializing OpenTelemetry metrics exporter at: ${endpoint}`);\n const meterProvider = new MeterProvider({\n resource: resourceFromAttributes({\n \"service.name\": env.OTEL_SERVICE_NAME ?? \"switchboard\",\n }),\n readers: [\n new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({\n url: exporterUrl,\n }),\n exportIntervalMillis,\n exportTimeoutMillis: Math.max(exportIntervalMillis - 250, 1),\n }),\n ],\n });\n logger.info(`Metrics export enabled (interval: ${exportIntervalMillis}ms)`);\n return meterProvider;\n}\n","const WEBHOOK_SEGMENT = \"/webhooks/\";\n\n/**\n * Strips a webhook's delivery token out of a request path.\n *\n * The token is the endpoint's whole credential and it sits in the path, so it\n * must not reach a log line or a span attribute that ships off-box. The family\n * mounts under the host's base path, so the segment can sit at any depth.\n */\nexport function redactWebhookPath(url: string | undefined): string {\n if (!url) return \"\";\n // Dropped with the token: `http.route` is meant to be a template, and a\n // query string is another place a sender can put a credential.\n const path = url.split(\"?\")[0] ?? url;\n const at = path.indexOf(WEBHOOK_SEGMENT);\n if (at === -1) return url;\n // Nothing after the segment is nothing to redact.\n if (path.length === at + WEBHOOK_SEGMENT.length) return url;\n return `${path.slice(0, at)}${WEBHOOK_SEGMENT}[redacted]`;\n}\n","// Single observability bootstrap: Sentry + OpenTelemetry (tracing + metrics).\n//\n// MUST be imported as the very first module in apps/switchboard/src/index.mts.\n// OpenTelemetry instrumentations register require-time hooks at module load,\n// so http/express/pg/graphql must not be imported (transitively) before this\n// file runs.\n//\n// Replaces three legacy bootstrap sites:\n// - apps/switchboard/src/server.mts top-level Sentry.init\n// - apps/switchboard/src/metrics.ts standalone MeterProvider (still exported\n// here via createMeterProviderFromEnv so its tests keep passing)\n// - packages/reactor-api/src/tracing.ts side-effect NodeSDK\nimport { metrics } from \"@opentelemetry/api\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport { ExpressInstrumentation } from \"@opentelemetry/instrumentation-express\";\nimport { GraphQLInstrumentation } from \"@opentelemetry/instrumentation-graphql\";\nimport { HttpInstrumentation } from \"@opentelemetry/instrumentation-http\";\nimport { PgInstrumentation } from \"@opentelemetry/instrumentation-pg\";\nimport { UndiciInstrumentation } from \"@opentelemetry/instrumentation-undici\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport type { MeterProvider } from \"@opentelemetry/sdk-metrics\";\nimport { NodeSDK } from \"@opentelemetry/sdk-node\";\nimport {\n BatchSpanProcessor,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n} from \"@opentelemetry/semantic-conventions\";\nimport {\n registerEventLoopInstrumentation,\n type EventLoopInstrumentation,\n} from \"@powerhousedao/opentelemetry-instrumentation-reactor\";\nimport * as Sentry from \"@sentry/node\";\nimport { SentryPropagator, SentrySpanProcessor } from \"@sentry/opentelemetry\";\nimport { childLogger } from \"document-model\";\nimport type { IncomingMessage } from \"node:http\";\nimport { createMeterProviderFromEnv } from \"./metrics.js\";\nimport { redactWebhookPath } from \"./redact-webhook-path.js\";\n\nconst logger = childLogger([\"switchboard\", \"observability\"]);\n\nconst SERVICE_NAME = process.env.OTEL_SERVICE_NAME || \"switchboard\";\nconst SERVICE_VERSION = process.env.npm_package_version || \"unknown\";\nconst TENANT_ID = process.env.TENANT_ID || \"default\";\nconst DEPLOY_ENV = process.env.NODE_ENV || \"development\";\n\nconst TEMPO_ENDPOINT = process.env.TEMPO_ENDPOINT;\nconst SENTRY_DSN = process.env.SENTRY_DSN;\n\n// Whether to forward TRANSACTIONS (spans) to Sentry. Errors are always sent\n// when SENTRY_DSN is set — this flag only gates APM/tracing into Sentry.\n// Default on (back-compat). Set SENTRY_TRACING_ENABLED=false for an\n// \"errors-only\" deployment: errors still go to Sentry, traces still go to\n// Tempo, but no transactions hit Sentry (no Kafka/ClickHouse/nodestore\n// cost). This is the recommended mode for tenant workloads at scale —\n// Sentry's value there is error grouping; traces live in Tempo.\nconst SENTRY_TRACING_TO_SENTRY =\n Boolean(SENTRY_DSN) && process.env.SENTRY_TRACING_ENABLED !== \"false\";\n\nconst TRACING_REQUESTED =\n process.env.ENABLE_TRACING === \"true\" ||\n process.env.NODE_ENV === \"production\";\nconst HAS_TRACE_DESTINATION = Boolean(TEMPO_ENDPOINT) || Boolean(SENTRY_DSN);\nconst TRACING_ENABLED = TRACING_REQUESTED && HAS_TRACE_DESTINATION;\n\nif (TRACING_REQUESTED && !HAS_TRACE_DESTINATION) {\n logger.warn(\n \"Tracing was requested (NODE_ENV=production or ENABLE_TRACING=true) but \" +\n \"no destination is configured — instrumentation will not run. Set \" +\n \"TEMPO_ENDPOINT (e.g. http://tempo.monitoring.svc.cluster.local:4318/v1/traces) \" +\n \"to export OTLP spans, and/or SENTRY_DSN to forward spans to Sentry.\",\n );\n}\n\n// APM sampling for the Sentry-SDK-managed path (i.e. when TRACING is OFF and\n// @sentry/node runs its own bundled OTel — see skipOpenTelemetrySetup below).\n//\n// IMPORTANT: when TRACING_ENABLED, this value does NOT govern span volume.\n// Our NodeSDK (below) owns the pipeline and is constructed with no explicit\n// `sampler`, so @opentelemetry/sdk-node falls back to buildSamplerFromEnv()\n// and the REAL head-sampling knob is the standard OTEL_TRACES_SAMPLER /\n// OTEL_TRACES_SAMPLER_ARG env (set per-deploy in the k8s chart). That head\n// decision gates spans before any processor runs, so it bounds BOTH the\n// SentrySpanProcessor (→ Sentry transactions) and the Tempo OTLP export.\n// (Wiring @sentry/opentelemetry's SentrySampler here would let this rate\n// drive both backends and make DSC/sample_rand propagation spec-correct, but\n// that only matters for Sentry server-side dynamic sampling — a SaaS feature\n// our self-hosted install doesn't run — so it's intentionally deferred.)\nconst SENTRY_TRACES_SAMPLE_RATE = parseFloat(\n process.env.SENTRY_TRACES_SAMPLE_RATE ?? \"0.1\",\n);\n\nif (SENTRY_DSN) {\n logger.info(\"Initialized Sentry with env: @env\", process.env.SENTRY_ENV);\n Sentry.init({\n dsn: SENTRY_DSN,\n environment: process.env.SENTRY_ENV,\n // Match the version tag uploaded by release-branch.yml so source maps\n // resolve. Populated by the CI (WORKSPACE_VERSION) or npm at runtime.\n release:\n process.env.SENTRY_RELEASE ||\n (process.env.npm_package_version\n ? `v${process.env.npm_package_version}`\n : undefined),\n // 0 in errors-only mode so even @sentry/node's bundled auto-OTel path\n // (used when TRACING_ENABLED is false) produces no transactions.\n tracesSampleRate: SENTRY_TRACING_TO_SENTRY ? SENTRY_TRACES_SAMPLE_RATE : 0,\n // When tracing is on, our NodeSDK below owns the OTel globals and Sentry\n // receives spans via SentrySpanProcessor. Skipping Sentry's bundled OTel\n // setup avoids two TracerProviders fighting over setGlobalTracerProvider.\n // When tracing is off, leave the flag unset so @sentry/node's default\n // auto-OTel still records HTTP transactions for the APM dashboard.\n skipOpenTelemetrySetup: TRACING_ENABLED,\n });\n}\n\nconst meterProvider: MeterProvider | undefined = createMeterProviderFromEnv({\n OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,\n OTEL_METRIC_EXPORT_INTERVAL: process.env.OTEL_METRIC_EXPORT_INTERVAL,\n OTEL_SERVICE_NAME: process.env.OTEL_SERVICE_NAME,\n});\nlet eventLoopInstrumentation: EventLoopInstrumentation | undefined;\nif (meterProvider) {\n // One-way door: must register before any code calls metrics.getMeter() —\n // most notably ReactorInstrumentation inside the reactor module.\n metrics.setGlobalMeterProvider(meterProvider);\n // Host event-loop and CPU gauges (reactor.host.eventloop.*,\n // reactor.host.cpu.utilization). The bench sweep identified the host event\n // loop, not the executor, as the binding constraint on write throughput\n // (bench/test/integration/BASELINE.md, Run 10) — these are the production\n // counterpart of the metrics that finding was made from. Must come after\n // setGlobalMeterProvider or the gauges bind to a no-op meter.\n eventLoopInstrumentation = registerEventLoopInstrumentation();\n}\n\nlet sdk: NodeSDK | undefined;\n\nif (TRACING_ENABLED) {\n logger.info(`Initializing OpenTelemetry tracing for ${SERVICE_NAME}`);\n if (TEMPO_ENDPOINT) logger.info(` Tempo endpoint: ${TEMPO_ENDPOINT}`);\n if (SENTRY_DSN) logger.info(` Sentry span forwarding: enabled`);\n logger.info(` Tenant: ${TENANT_ID}`);\n\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: SERVICE_NAME,\n [ATTR_SERVICE_VERSION]: SERVICE_VERSION,\n \"tenant.id\": TENANT_ID,\n \"deployment.environment\": DEPLOY_ENV,\n });\n\n const spanProcessors: SpanProcessor[] = [];\n if (TEMPO_ENDPOINT) {\n spanProcessors.push(\n new BatchSpanProcessor(new OTLPTraceExporter({ url: TEMPO_ENDPOINT })),\n );\n }\n if (SENTRY_TRACING_TO_SENTRY) {\n // Fan the same OTel spans into Sentry — same trace IDs as Tempo, so\n // Sentry transactions cross-link to traces in Grafana. Skipped in\n // errors-only mode (SENTRY_TRACING_ENABLED=false): spans still flow to\n // Tempo via the BatchSpanProcessor above, just not to Sentry.\n spanProcessors.push(new SentrySpanProcessor());\n }\n\n sdk = new NodeSDK({\n resource,\n spanProcessors,\n textMapPropagator: SENTRY_TRACING_TO_SENTRY\n ? new SentryPropagator()\n : undefined,\n instrumentations: [\n new HttpInstrumentation({\n ignoreIncomingRequestHook: (req) =>\n req.url === \"/health\" || req.url === \"/ready\",\n requireParentforIncomingSpans: false,\n requireParentforOutgoingSpans: false,\n requestHook: (span, request) => {\n span.setAttribute(\n \"http.route\",\n redactWebhookPath((request as IncomingMessage).url),\n );\n },\n responseHook: (span, response) => {\n if (response.statusCode) {\n span.setAttribute(\"http.status_code\", response.statusCode);\n }\n },\n }),\n new ExpressInstrumentation({\n requestHook: (span, info) => {\n if (info.route) span.setAttribute(\"http.route\", info.route);\n },\n }),\n // HttpInstrumentation only patches node:http/https; outbound global\n // fetch() goes through undici and is otherwise untraced — e.g. the\n // per-request Renown credential check in AuthService.verifyCredentialExists.\n new UndiciInstrumentation(),\n new GraphQLInstrumentation({ mergeItems: true, allowValues: true }),\n // requireParentSpan: only trace DB queries that run inside a request\n // (HTTP/GraphQL) span. Parentless queries — the management\n // switchboard's background polling loops (vetra-cloud-observability\n // reconcile @60s + clint pull-worker @15s, each writing\n // environment_pods / clint_runtime_endpoints) — would otherwise each\n // become a standalone root transaction. That volume scales O(tenant\n // count) and was ~70% of all Sentry transactions before this change.\n // Dropping it at the instrumentation layer (no span created at all) is\n // cheaper and cleaner than sampling it away downstream.\n new PgInstrumentation({\n enhancedDatabaseReporting: true,\n requireParentSpan: true,\n }),\n ],\n });\n sdk.start();\n if (\n SENTRY_TRACING_TO_SENTRY &&\n typeof Sentry.validateOpenTelemetrySetup === \"function\"\n ) {\n Sentry.validateOpenTelemetrySetup();\n }\n logger.info(\"OpenTelemetry tracing initialized\");\n}\n\nasync function shutdown() {\n // Detach the observable callback before the provider drains, so the final\n // export does not fire a gauge callback against a shutting-down meter.\n eventLoopInstrumentation?.stop();\n await Promise.race([\n Promise.all([\n meterProvider?.shutdown().catch(() => undefined),\n sdk?.shutdown().catch(() => undefined),\n ]),\n new Promise<void>((resolve) => setTimeout(resolve, 5_000)),\n ]);\n}\n\nprocess.on(\"SIGINT\", () => {\n void shutdown().finally(() => process.exit(0));\n});\nprocess.on(\"SIGTERM\", () => {\n void shutdown().finally(() => process.exit(0));\n});\n\nexport { meterProvider, sdk };\n","import dotenv from \"dotenv\";\ndotenv.config();\n\nimport { getConfig } from \"@powerhousedao/config/node\";\nimport { parseForcePgVersion } from \"./pglite-version.js\";\nimport type {\n SwitchboardDriveDocumentType,\n SwitchboardDriveInput,\n} from \"./types.js\";\nconst phConfig = getConfig();\nconst { switchboard } = phConfig;\ninterface Config {\n database: {\n url: string;\n };\n port: number;\n mcp: boolean;\n migratePglite: boolean;\n forcePgVersion: 16 | 17 | null;\n drive: SwitchboardDriveInput;\n}\n\nfunction parseDriveType(\n raw: string | undefined,\n): SwitchboardDriveDocumentType | undefined {\n if (!raw) return undefined;\n if (raw === \"powerhouse/document-drive\" || raw === \"powerhouse/reactor-drive\")\n return raw;\n throw new Error(\n `Invalid PH_DEFAULT_DRIVE_TYPE: ${raw}. Expected \"powerhouse/document-drive\" or \"powerhouse/reactor-drive\".`,\n );\n}\n\nexport const config: Config = {\n database: {\n // url: process.env.PH_SWITCHBOARD_DATABASE_URL ?? switchboard?.database?.url ?? \"dev.db\",\n url:\n process.env.PH_SWITCHBOARD_DATABASE_URL ??\n switchboard?.database?.url ??\n \"dev.db\",\n },\n port:\n process.env.PH_SWITCHBOARD_PORT &&\n !isNaN(Number(process.env.PH_SWITCHBOARD_PORT))\n ? Number(process.env.PH_SWITCHBOARD_PORT)\n : (switchboard?.port ?? 4001),\n mcp: true,\n migratePglite: process.env.PH_MIGRATE_PGLITE === \"true\",\n forcePgVersion: parseForcePgVersion(process.env.PH_FORCE_PG_VERSION),\n drive: {\n id: \"powerhouse\",\n slug: \"powerhouse\",\n documentType: parseDriveType(process.env.PH_DEFAULT_DRIVE_TYPE),\n global: {\n name: \"Powerhouse\",\n icon: \"https://ipfs.io/ipfs/QmcaTDBYn8X2psGaXe7iQ6qd8q6oqHLgxvMX9yXf7f9uP7\",\n },\n local: {\n availableOffline: true,\n listeners: [],\n sharingType: \"public\",\n triggers: [],\n },\n },\n};\n","import type { PyroscopeConfig } from \"@pyroscope/nodejs\";\n\nexport async function initProfilerFromEnv(env: typeof process.env) {\n const {\n PYROSCOPE_SERVER_ADDRESS: serverAddress,\n PYROSCOPE_APPLICATION_NAME: appName,\n PYROSCOPE_USER: basicAuthUser,\n PYROSCOPE_PASSWORD: basicAuthPassword,\n PYROSCOPE_WALL_ENABLED: wallEnabled,\n PYROSCOPE_HEAP_ENABLED: heapEnabled,\n } = env;\n\n const options: PyroscopeConfig = {\n serverAddress,\n appName,\n basicAuthUser,\n basicAuthPassword,\n // Wall profiling captures wall-clock time (includes async I/O waits)\n // This shows GraphQL resolvers even when waiting for database\n wall: {\n samplingDurationMs: 10000, // 10 second sampling windows\n samplingIntervalMicros: 10000, // 10ms sampling interval (100 samples/sec)\n collectCpuTime: true, // Also collect CPU time alongside wall time\n },\n // Heap profiling for memory allocation tracking\n heap: {\n samplingIntervalBytes: 512 * 1024, // Sample every 512KB allocated\n stackDepth: 64, // Capture deeper stacks for better context\n },\n };\n return initProfiler(options, {\n wallEnabled: wallEnabled !== \"false\",\n heapEnabled: heapEnabled === \"true\",\n });\n}\n\ninterface ProfilerFlags {\n wallEnabled?: boolean;\n heapEnabled?: boolean;\n}\n\nexport async function initProfiler(\n options?: PyroscopeConfig,\n flags: ProfilerFlags = { wallEnabled: true, heapEnabled: false },\n) {\n console.log(\"Initializing Pyroscope profiler at:\", options?.serverAddress);\n console.log(\" Wall profiling:\", flags.wallEnabled ? \"enabled\" : \"disabled\");\n console.log(\" Heap profiling:\", flags.heapEnabled ? \"enabled\" : \"disabled\");\n\n const { default: Pyroscope } = await import(\"@pyroscope/nodejs\");\n Pyroscope.init(options);\n\n // Start wall profiling (captures async I/O time - shows resolvers)\n if (flags.wallEnabled) {\n Pyroscope.startWallProfiling();\n }\n\n // Start CPU profiling (captures CPU-bound work)\n Pyroscope.startCpuProfiling();\n\n // Optionally start heap profiling (memory allocations)\n if (flags.heapEnabled) {\n Pyroscope.startHeapProfiling();\n }\n}\n","#!/usr/bin/env node\n// Observability MUST load before any module that imports http/express/pg/graphql\n// so OpenTelemetry's require-time hooks can patch them. It also owns Sentry\n// init and the SIGINT/SIGTERM flush.\nimport \"./observability.mjs\";\n\nimport * as Sentry from \"@sentry/node\";\nimport { childLogger } from \"document-model\";\nimport { config } from \"./config.js\";\nimport { initProfilerFromEnv } from \"./profiler.js\";\nimport { startSwitchboard } from \"./server.mjs\";\n\nconst logger = childLogger([\"switchboard\"]);\n\nfunction ensureNodeVersion(minVersion = \"24\") {\n const version = process.versions.node;\n if (!version) {\n return;\n }\n\n if (version < minVersion) {\n console.error(\n `Node version ${minVersion} or higher is required. Current version: ${version}`,\n );\n process.exit(1);\n }\n}\n// Ensure minimum Node.js version\nensureNodeVersion(\"24\");\n\n// Each subgraph registers its own SIGINT/SIGTERM listeners, and the count\n// scales with dynamically-loaded document models beyond the default cap of 10.\nprocess.setMaxListeners(0);\n\nif (process.env.PYROSCOPE_SERVER_ADDRESS) {\n try {\n await initProfilerFromEnv(process.env);\n } catch (e) {\n Sentry.captureException(e);\n logger.error(\"Error starting profiler: @error\", e);\n }\n}\n\nconst cliMigratePglite = process.argv.slice(2).includes(\"--migrate-pglite\");\n\n// A boot failure leaves no serving process behind, so it must exit non-zero:\n// logging and continuing hides a dead switchboard from the supervisor, which\n// would otherwise never restart it.\ntry {\n await startSwitchboard({\n ...config,\n migratePglite: cliMigratePglite || config.migratePglite,\n forcePgVersion: config.forcePgVersion ?? undefined,\n });\n} catch (e) {\n Sentry.captureException(e);\n logger.error(\"Switchboard failed to start: @error\", e);\n await Sentry.flush(2000).catch(() => undefined);\n process.exit(1);\n}\n"],"names":["logger","logger"],"mappings":"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","debug_id":"c6c10af1-f4cc-56a5-abf6-fa35cf0de956"}
1
+ {"version":3,"file":"index.mjs","sources":["../src/metrics.ts","../src/redact-webhook-path.ts","../src/observability.mts","../src/config.ts","../src/profiler.ts","../src/index.mts"],"sourcesContent":["import { OTLPMetricExporter } from \"@opentelemetry/exporter-metrics-otlp-http\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport {\n MeterProvider,\n PeriodicExportingMetricReader,\n} from \"@opentelemetry/sdk-metrics\";\nimport { childLogger } from \"document-model\";\n\nconst logger = childLogger([\"switchboard\", \"metrics\"]);\n\nexport function createMeterProviderFromEnv(env: {\n OTEL_EXPORTER_OTLP_ENDPOINT?: string;\n OTEL_METRIC_EXPORT_INTERVAL?: string;\n OTEL_SERVICE_NAME?: string;\n}): MeterProvider | undefined {\n const endpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT;\n if (!endpoint) return undefined;\n\n const parsed = parseInt(env.OTEL_METRIC_EXPORT_INTERVAL ?? \"\", 10);\n const exportIntervalMillis =\n Number.isFinite(parsed) && parsed > 0 ? parsed : 5_000;\n\n const base = endpoint.replace(/\\/$/, \"\");\n const exporterUrl = base.endsWith(\"/v1/metrics\")\n ? base\n : `${base}/v1/metrics`;\n\n logger.info(`Initializing OpenTelemetry metrics exporter at: ${endpoint}`);\n const meterProvider = new MeterProvider({\n resource: resourceFromAttributes({\n \"service.name\": env.OTEL_SERVICE_NAME ?? \"switchboard\",\n }),\n readers: [\n new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({\n url: exporterUrl,\n }),\n exportIntervalMillis,\n exportTimeoutMillis: Math.max(exportIntervalMillis - 250, 1),\n }),\n ],\n });\n logger.info(`Metrics export enabled (interval: ${exportIntervalMillis}ms)`);\n return meterProvider;\n}\n","const WEBHOOK_SEGMENT = \"/webhooks/\";\n\n/**\n * Strips a webhook's delivery token out of a request path.\n *\n * The token is the endpoint's whole credential and it sits in the path, so it\n * must not reach a log line or a span attribute that ships off-box. The family\n * mounts under the host's base path, so the segment can sit at any depth.\n */\nexport function redactWebhookPath(url: string | undefined): string {\n if (!url) return \"\";\n // Dropped with the token: `http.route` is meant to be a template, and a\n // query string is another place a sender can put a credential.\n const path = url.split(\"?\")[0] ?? url;\n const at = path.indexOf(WEBHOOK_SEGMENT);\n if (at === -1) return url;\n // Nothing after the segment is nothing to redact.\n if (path.length === at + WEBHOOK_SEGMENT.length) return url;\n return `${path.slice(0, at)}${WEBHOOK_SEGMENT}[redacted]`;\n}\n","// Single observability bootstrap: Sentry + OpenTelemetry (tracing + metrics).\n//\n// MUST be imported as the very first module in apps/switchboard/src/index.mts.\n// OpenTelemetry instrumentations register require-time hooks at module load,\n// so http/express/pg/graphql must not be imported (transitively) before this\n// file runs.\n//\n// Replaces three legacy bootstrap sites:\n// - apps/switchboard/src/server.mts top-level Sentry.init\n// - apps/switchboard/src/metrics.ts standalone MeterProvider (still exported\n// here via createMeterProviderFromEnv so its tests keep passing)\n// - packages/reactor-api/src/tracing.ts side-effect NodeSDK\nimport { metrics } from \"@opentelemetry/api\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport { ExpressInstrumentation } from \"@opentelemetry/instrumentation-express\";\nimport { GraphQLInstrumentation } from \"@opentelemetry/instrumentation-graphql\";\nimport { HttpInstrumentation } from \"@opentelemetry/instrumentation-http\";\nimport { PgInstrumentation } from \"@opentelemetry/instrumentation-pg\";\nimport { UndiciInstrumentation } from \"@opentelemetry/instrumentation-undici\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport type { MeterProvider } from \"@opentelemetry/sdk-metrics\";\nimport { NodeSDK } from \"@opentelemetry/sdk-node\";\nimport {\n BatchSpanProcessor,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n} from \"@opentelemetry/semantic-conventions\";\nimport {\n registerEventLoopInstrumentation,\n type EventLoopInstrumentation,\n} from \"@powerhousedao/opentelemetry-instrumentation-reactor\";\nimport * as Sentry from \"@sentry/node\";\nimport { SentryPropagator, SentrySpanProcessor } from \"@sentry/opentelemetry\";\nimport { childLogger } from \"document-model\";\nimport type { IncomingMessage } from \"node:http\";\nimport { createMeterProviderFromEnv } from \"./metrics.js\";\nimport { redactWebhookPath } from \"./redact-webhook-path.js\";\n\nconst logger = childLogger([\"switchboard\", \"observability\"]);\n\nconst SERVICE_NAME = process.env.OTEL_SERVICE_NAME || \"switchboard\";\nconst SERVICE_VERSION = process.env.npm_package_version || \"unknown\";\nconst TENANT_ID = process.env.TENANT_ID || \"default\";\nconst DEPLOY_ENV = process.env.NODE_ENV || \"development\";\n\nconst TEMPO_ENDPOINT = process.env.TEMPO_ENDPOINT;\nconst SENTRY_DSN = process.env.SENTRY_DSN;\n\n// Whether to forward TRANSACTIONS (spans) to Sentry. Errors are always sent\n// when SENTRY_DSN is set — this flag only gates APM/tracing into Sentry.\n// Default on (back-compat). Set SENTRY_TRACING_ENABLED=false for an\n// \"errors-only\" deployment: errors still go to Sentry, traces still go to\n// Tempo, but no transactions hit Sentry (no Kafka/ClickHouse/nodestore\n// cost). This is the recommended mode for tenant workloads at scale —\n// Sentry's value there is error grouping; traces live in Tempo.\nconst SENTRY_TRACING_TO_SENTRY =\n Boolean(SENTRY_DSN) && process.env.SENTRY_TRACING_ENABLED !== \"false\";\n\nconst TRACING_REQUESTED =\n process.env.ENABLE_TRACING === \"true\" ||\n process.env.NODE_ENV === \"production\";\nconst HAS_TRACE_DESTINATION = Boolean(TEMPO_ENDPOINT) || Boolean(SENTRY_DSN);\nconst TRACING_ENABLED = TRACING_REQUESTED && HAS_TRACE_DESTINATION;\n\nif (TRACING_REQUESTED && !HAS_TRACE_DESTINATION) {\n logger.warn(\n \"Tracing was requested (NODE_ENV=production or ENABLE_TRACING=true) but \" +\n \"no destination is configured — instrumentation will not run. Set \" +\n \"TEMPO_ENDPOINT (e.g. http://tempo.monitoring.svc.cluster.local:4318/v1/traces) \" +\n \"to export OTLP spans, and/or SENTRY_DSN to forward spans to Sentry.\",\n );\n}\n\n// APM sampling for the Sentry-SDK-managed path (i.e. when TRACING is OFF and\n// @sentry/node runs its own bundled OTel — see skipOpenTelemetrySetup below).\n//\n// IMPORTANT: when TRACING_ENABLED, this value does NOT govern span volume.\n// Our NodeSDK (below) owns the pipeline and is constructed with no explicit\n// `sampler`, so @opentelemetry/sdk-node falls back to buildSamplerFromEnv()\n// and the REAL head-sampling knob is the standard OTEL_TRACES_SAMPLER /\n// OTEL_TRACES_SAMPLER_ARG env (set per-deploy in the k8s chart). That head\n// decision gates spans before any processor runs, so it bounds BOTH the\n// SentrySpanProcessor (→ Sentry transactions) and the Tempo OTLP export.\n// (Wiring @sentry/opentelemetry's SentrySampler here would let this rate\n// drive both backends and make DSC/sample_rand propagation spec-correct, but\n// that only matters for Sentry server-side dynamic sampling — a SaaS feature\n// our self-hosted install doesn't run — so it's intentionally deferred.)\nconst SENTRY_TRACES_SAMPLE_RATE = parseFloat(\n process.env.SENTRY_TRACES_SAMPLE_RATE ?? \"0.1\",\n);\n\nif (SENTRY_DSN) {\n logger.info(\"Initialized Sentry with env: @env\", process.env.SENTRY_ENV);\n Sentry.init({\n dsn: SENTRY_DSN,\n environment: process.env.SENTRY_ENV,\n // Match the version tag uploaded by release-branch.yml so source maps\n // resolve. Populated by the CI (WORKSPACE_VERSION) or npm at runtime.\n release:\n process.env.SENTRY_RELEASE ||\n (process.env.npm_package_version\n ? `v${process.env.npm_package_version}`\n : undefined),\n // 0 in errors-only mode so even @sentry/node's bundled auto-OTel path\n // (used when TRACING_ENABLED is false) produces no transactions.\n tracesSampleRate: SENTRY_TRACING_TO_SENTRY ? SENTRY_TRACES_SAMPLE_RATE : 0,\n // When tracing is on, our NodeSDK below owns the OTel globals and Sentry\n // receives spans via SentrySpanProcessor. Skipping Sentry's bundled OTel\n // setup avoids two TracerProviders fighting over setGlobalTracerProvider.\n // When tracing is off, leave the flag unset so @sentry/node's default\n // auto-OTel still records HTTP transactions for the APM dashboard.\n skipOpenTelemetrySetup: TRACING_ENABLED,\n });\n}\n\nconst meterProvider: MeterProvider | undefined = createMeterProviderFromEnv({\n OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,\n OTEL_METRIC_EXPORT_INTERVAL: process.env.OTEL_METRIC_EXPORT_INTERVAL,\n OTEL_SERVICE_NAME: process.env.OTEL_SERVICE_NAME,\n});\nlet eventLoopInstrumentation: EventLoopInstrumentation | undefined;\nif (meterProvider) {\n // One-way door: must register before any code calls metrics.getMeter() —\n // most notably ReactorInstrumentation inside the reactor module.\n metrics.setGlobalMeterProvider(meterProvider);\n // Host event-loop and CPU gauges (reactor.host.eventloop.*,\n // reactor.host.cpu.utilization). The bench sweep identified the host event\n // loop, not the executor, as the binding constraint on write throughput\n // (bench/test/integration/BASELINE.md, Run 10) — these are the production\n // counterpart of the metrics that finding was made from. Must come after\n // setGlobalMeterProvider or the gauges bind to a no-op meter.\n eventLoopInstrumentation = registerEventLoopInstrumentation();\n}\n\nlet sdk: NodeSDK | undefined;\n\nif (TRACING_ENABLED) {\n logger.info(`Initializing OpenTelemetry tracing for ${SERVICE_NAME}`);\n if (TEMPO_ENDPOINT) logger.info(` Tempo endpoint: ${TEMPO_ENDPOINT}`);\n if (SENTRY_DSN) logger.info(` Sentry span forwarding: enabled`);\n logger.info(` Tenant: ${TENANT_ID}`);\n\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: SERVICE_NAME,\n [ATTR_SERVICE_VERSION]: SERVICE_VERSION,\n \"tenant.id\": TENANT_ID,\n \"deployment.environment\": DEPLOY_ENV,\n });\n\n const spanProcessors: SpanProcessor[] = [];\n if (TEMPO_ENDPOINT) {\n spanProcessors.push(\n new BatchSpanProcessor(new OTLPTraceExporter({ url: TEMPO_ENDPOINT })),\n );\n }\n if (SENTRY_TRACING_TO_SENTRY) {\n // Fan the same OTel spans into Sentry — same trace IDs as Tempo, so\n // Sentry transactions cross-link to traces in Grafana. Skipped in\n // errors-only mode (SENTRY_TRACING_ENABLED=false): spans still flow to\n // Tempo via the BatchSpanProcessor above, just not to Sentry.\n spanProcessors.push(new SentrySpanProcessor());\n }\n\n sdk = new NodeSDK({\n resource,\n spanProcessors,\n textMapPropagator: SENTRY_TRACING_TO_SENTRY\n ? new SentryPropagator()\n : undefined,\n instrumentations: [\n new HttpInstrumentation({\n ignoreIncomingRequestHook: (req) =>\n req.url === \"/health\" || req.url === \"/ready\",\n requireParentforIncomingSpans: false,\n requireParentforOutgoingSpans: false,\n requestHook: (span, request) => {\n span.setAttribute(\n \"http.route\",\n redactWebhookPath((request as IncomingMessage).url),\n );\n },\n responseHook: (span, response) => {\n if (response.statusCode) {\n span.setAttribute(\"http.status_code\", response.statusCode);\n }\n },\n }),\n new ExpressInstrumentation({\n requestHook: (span, info) => {\n if (info.route) span.setAttribute(\"http.route\", info.route);\n },\n }),\n // HttpInstrumentation only patches node:http/https; outbound global\n // fetch() goes through undici and is otherwise untraced — e.g. the\n // per-request Renown credential check in AuthService.verifyCredentialExists.\n new UndiciInstrumentation(),\n new GraphQLInstrumentation({ mergeItems: true, allowValues: true }),\n // requireParentSpan: only trace DB queries that run inside a request\n // (HTTP/GraphQL) span. Parentless queries — the management\n // switchboard's background polling loops (vetra-cloud-observability\n // reconcile @60s + clint pull-worker @15s, each writing\n // environment_pods / clint_runtime_endpoints) — would otherwise each\n // become a standalone root transaction. That volume scales O(tenant\n // count) and was ~70% of all Sentry transactions before this change.\n // Dropping it at the instrumentation layer (no span created at all) is\n // cheaper and cleaner than sampling it away downstream.\n new PgInstrumentation({\n enhancedDatabaseReporting: true,\n requireParentSpan: true,\n }),\n ],\n });\n sdk.start();\n if (\n SENTRY_TRACING_TO_SENTRY &&\n typeof Sentry.validateOpenTelemetrySetup === \"function\"\n ) {\n Sentry.validateOpenTelemetrySetup();\n }\n logger.info(\"OpenTelemetry tracing initialized\");\n}\n\nasync function shutdown() {\n // Detach the observable callback before the provider drains, so the final\n // export does not fire a gauge callback against a shutting-down meter.\n eventLoopInstrumentation?.stop();\n await Promise.race([\n Promise.all([\n meterProvider?.shutdown().catch(() => undefined),\n sdk?.shutdown().catch(() => undefined),\n ]),\n new Promise<void>((resolve) => setTimeout(resolve, 5_000)),\n ]);\n}\n\nprocess.on(\"SIGINT\", () => {\n void shutdown().finally(() => process.exit(0));\n});\nprocess.on(\"SIGTERM\", () => {\n void shutdown().finally(() => process.exit(0));\n});\n\nexport { meterProvider, sdk };\n","import dotenv from \"dotenv\";\ndotenv.config();\n\nimport { getConfig } from \"@powerhousedao/config/node\";\nimport { parseForcePgVersion } from \"./pglite-version.js\";\nimport type {\n SwitchboardDriveDocumentType,\n SwitchboardDriveInput,\n} from \"./types.js\";\nconst phConfig = getConfig();\nconst { switchboard } = phConfig;\ninterface Config {\n database: {\n url: string;\n };\n port: number;\n mcp: boolean;\n migratePglite: boolean;\n forcePgVersion: 16 | 17 | null;\n drive: SwitchboardDriveInput;\n}\n\nfunction parseDriveType(\n raw: string | undefined,\n): SwitchboardDriveDocumentType | undefined {\n if (!raw) return undefined;\n if (raw === \"powerhouse/document-drive\" || raw === \"powerhouse/reactor-drive\")\n return raw;\n throw new Error(\n `Invalid PH_DEFAULT_DRIVE_TYPE: ${raw}. Expected \"powerhouse/document-drive\" or \"powerhouse/reactor-drive\".`,\n );\n}\n\nexport const config: Config = {\n database: {\n // url: process.env.PH_SWITCHBOARD_DATABASE_URL ?? switchboard?.database?.url ?? \"dev.db\",\n url:\n process.env.PH_SWITCHBOARD_DATABASE_URL ??\n switchboard?.database?.url ??\n \"dev.db\",\n },\n port:\n process.env.PH_SWITCHBOARD_PORT &&\n !isNaN(Number(process.env.PH_SWITCHBOARD_PORT))\n ? Number(process.env.PH_SWITCHBOARD_PORT)\n : (switchboard?.port ?? 4001),\n mcp: true,\n migratePglite: process.env.PH_MIGRATE_PGLITE === \"true\",\n forcePgVersion: parseForcePgVersion(process.env.PH_FORCE_PG_VERSION),\n drive: {\n id: \"powerhouse\",\n slug: \"powerhouse\",\n documentType: parseDriveType(process.env.PH_DEFAULT_DRIVE_TYPE),\n global: {\n name: \"Powerhouse\",\n icon: \"https://ipfs.io/ipfs/QmcaTDBYn8X2psGaXe7iQ6qd8q6oqHLgxvMX9yXf7f9uP7\",\n },\n local: {\n availableOffline: true,\n listeners: [],\n sharingType: \"public\",\n triggers: [],\n },\n },\n};\n","import type { PyroscopeConfig } from \"@pyroscope/nodejs\";\n\nexport async function initProfilerFromEnv(env: typeof process.env) {\n const {\n PYROSCOPE_SERVER_ADDRESS: serverAddress,\n PYROSCOPE_APPLICATION_NAME: appName,\n PYROSCOPE_USER: basicAuthUser,\n PYROSCOPE_PASSWORD: basicAuthPassword,\n PYROSCOPE_WALL_ENABLED: wallEnabled,\n PYROSCOPE_HEAP_ENABLED: heapEnabled,\n } = env;\n\n const options: PyroscopeConfig = {\n serverAddress,\n appName,\n basicAuthUser,\n basicAuthPassword,\n // Wall profiling captures wall-clock time (includes async I/O waits)\n // This shows GraphQL resolvers even when waiting for database\n wall: {\n samplingDurationMs: 10000, // 10 second sampling windows\n samplingIntervalMicros: 10000, // 10ms sampling interval (100 samples/sec)\n collectCpuTime: true, // Also collect CPU time alongside wall time\n },\n // Heap profiling for memory allocation tracking\n heap: {\n samplingIntervalBytes: 512 * 1024, // Sample every 512KB allocated\n stackDepth: 64, // Capture deeper stacks for better context\n },\n };\n return initProfiler(options, {\n wallEnabled: wallEnabled !== \"false\",\n heapEnabled: heapEnabled === \"true\",\n });\n}\n\ninterface ProfilerFlags {\n wallEnabled?: boolean;\n heapEnabled?: boolean;\n}\n\nexport async function initProfiler(\n options?: PyroscopeConfig,\n flags: ProfilerFlags = { wallEnabled: true, heapEnabled: false },\n) {\n console.log(\"Initializing Pyroscope profiler at:\", options?.serverAddress);\n console.log(\" Wall profiling:\", flags.wallEnabled ? \"enabled\" : \"disabled\");\n console.log(\" Heap profiling:\", flags.heapEnabled ? \"enabled\" : \"disabled\");\n\n const { default: Pyroscope } = await import(\"@pyroscope/nodejs\");\n Pyroscope.init(options);\n\n // Start wall profiling (captures async I/O time - shows resolvers)\n if (flags.wallEnabled) {\n Pyroscope.startWallProfiling();\n }\n\n // Start CPU profiling (captures CPU-bound work)\n Pyroscope.startCpuProfiling();\n\n // Optionally start heap profiling (memory allocations)\n if (flags.heapEnabled) {\n Pyroscope.startHeapProfiling();\n }\n}\n","#!/usr/bin/env node\n// Observability MUST load before any module that imports http/express/pg/graphql\n// so OpenTelemetry's require-time hooks can patch them. It also owns Sentry\n// init and the SIGINT/SIGTERM flush.\nimport \"./observability.mjs\";\n\nimport * as Sentry from \"@sentry/node\";\nimport { childLogger } from \"document-model\";\nimport { config } from \"./config.js\";\nimport { initProfilerFromEnv } from \"./profiler.js\";\nimport { startSwitchboard } from \"./server.mjs\";\n\nconst logger = childLogger([\"switchboard\"]);\n\nfunction ensureNodeVersion(minVersion = \"24\") {\n const version = process.versions.node;\n if (!version) {\n return;\n }\n\n if (version < minVersion) {\n console.error(\n `Node version ${minVersion} or higher is required. Current version: ${version}`,\n );\n process.exit(1);\n }\n}\n// Ensure minimum Node.js version\nensureNodeVersion(\"24\");\n\n// Each subgraph registers its own SIGINT/SIGTERM listeners, and the count\n// scales with dynamically-loaded document models beyond the default cap of 10.\nprocess.setMaxListeners(0);\n\nif (process.env.PYROSCOPE_SERVER_ADDRESS) {\n try {\n await initProfilerFromEnv(process.env);\n } catch (e) {\n Sentry.captureException(e);\n logger.error(\"Error starting profiler: @error\", e);\n }\n}\n\nconst cliMigratePglite = process.argv.slice(2).includes(\"--migrate-pglite\");\n\n// A boot failure leaves no serving process behind, so it must exit non-zero:\n// logging and continuing hides a dead switchboard from the supervisor, which\n// would otherwise never restart it.\ntry {\n await startSwitchboard({\n ...config,\n migratePglite: cliMigratePglite || config.migratePglite,\n forcePgVersion: config.forcePgVersion ?? undefined,\n fatalErrorShutdown: true,\n });\n} catch (e) {\n Sentry.captureException(e);\n logger.error(\"Switchboard failed to start: @error\", e);\n await Sentry.flush(2000).catch(() => undefined);\n process.exit(1);\n}\n"],"names":["logger","logger"],"mappings":"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","debug_id":"f6ffead6-4742-59d9-948c-1c651716e65b"}
@@ -1 +1,2 @@
1
- export { };
1
+ export {}
2
+ export {};
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="50e38408-9102-5ba1-977a-69acc9cb0fe9")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="58dbe86a-e88c-57d1-9ab1-54f06a068229")}catch(e){}}();
3
3
  import path from "path";
4
4
  import { execSync } from "child_process";
5
5
  import fs from "fs";
@@ -31,4 +31,4 @@ try {
31
31
  export {};
32
32
 
33
33
  //# sourceMappingURL=install-packages.mjs.map
34
- //# debugId=50e38408-9102-5ba1-977a-69acc9cb0fe9
34
+ //# debugId=58dbe86a-e88c-57d1-9ab1-54f06a068229