@pulse-compute/cli 0.0.0 → 1.0.0-beta.2

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 (191) hide show
  1. package/API.md +572 -0
  2. package/CHANGELOG.md +71 -0
  3. package/README.md +71 -1
  4. package/bin/pulse.js +15 -0
  5. package/bin/pulsewasm-extract.js +22 -0
  6. package/cli-spec.json +575 -0
  7. package/completions/_pulse +106 -0
  8. package/completions/pulse.bash +31 -0
  9. package/completions/pulse.fish +76 -0
  10. package/docs/README.md +77 -0
  11. package/docs/architecture/current-contracts.md +470 -0
  12. package/docs/architecture/overview.md +93 -0
  13. package/docs/architecture/vision.md +515 -0
  14. package/docs/concepts/bodies.md +186 -0
  15. package/docs/concepts/compilation-and-lowering.md +496 -0
  16. package/docs/concepts/contracts-and-providers.md +158 -0
  17. package/docs/concepts/effects-and-continuations.md +300 -0
  18. package/docs/concepts/entities-and-adapters.md +71 -0
  19. package/docs/concepts/package-owned-lowering.md +175 -0
  20. package/docs/concepts/targets-and-hosts.md +70 -0
  21. package/docs/contributing/README.md +38 -0
  22. package/docs/contributing/adding-core-provider.md +315 -0
  23. package/docs/contributing/adding-first-party-lowerer.md +327 -0
  24. package/docs/contributing/entities-lowering.md +91 -0
  25. package/docs/contributing/package-lowerer-contract.md +383 -0
  26. package/docs/contributing/pulse-aware-packages.md +149 -0
  27. package/docs/examples.md +112 -0
  28. package/docs/fixtures/inspect-fetch-composition.selected.json +181 -0
  29. package/docs/getting-started.md +128 -0
  30. package/docs/guides/compatibility-imports.md +60 -0
  31. package/docs/guides/deploying-fastly.md +188 -0
  32. package/docs/guides/deploying-node.md +143 -0
  33. package/docs/guides/events.md +233 -0
  34. package/docs/guides/fastly-capabilities.md +153 -0
  35. package/docs/guides/fetching-and-composition.md +101 -0
  36. package/docs/guides/grip.md +94 -0
  37. package/docs/guides/json-schemas.md +233 -0
  38. package/docs/guides/migrating-from-express.md +192 -0
  39. package/docs/guides/project-lifecycle.md +143 -0
  40. package/docs/guides/routing.md +202 -0
  41. package/docs/guides/troubleshooting.md +47 -0
  42. package/docs/maintainers/README.md +60 -0
  43. package/docs/maintainers/codex-maintainer.md +71 -0
  44. package/docs/maintainers/documentation-deployment.md +147 -0
  45. package/docs/maintainers/documentation-system.md +58 -0
  46. package/docs/maintainers/documentation-versioning.md +65 -0
  47. package/docs/maintainers/maintainer-charter.md +100 -0
  48. package/docs/maintainers/maintenance-policy.json +809 -0
  49. package/docs/maintainers/maintenance-policy.md +124 -0
  50. package/docs/maintainers/npm-publishing.md +154 -0
  51. package/docs/maintainers/plugin-readiness.json +19 -0
  52. package/docs/maintainers/plugin-readiness.md +31 -0
  53. package/docs/maintainers/public-site.md +158 -0
  54. package/docs/maintainers/release-acceptance.md +293 -0
  55. package/docs/maintainers/release-manifest.md +89 -0
  56. package/docs/maintainers/repository-setup.md +145 -0
  57. package/docs/maintainers/scope-policy.md +105 -0
  58. package/docs/maintainers/support-and-triage.md +74 -0
  59. package/docs/maintainers/testing.md +334 -0
  60. package/docs/packages/README.md +60 -0
  61. package/docs/packages/assets.md +85 -0
  62. package/docs/packages/cli.md +190 -0
  63. package/docs/packages/crypto.md +78 -0
  64. package/docs/packages/entities.md +129 -0
  65. package/docs/packages/grip.md +82 -0
  66. package/docs/packages/implementation-packages.md +122 -0
  67. package/docs/packages/jwt.md +55 -0
  68. package/docs/packages/provider-fastly.md +243 -0
  69. package/docs/packages/pulse.md +159 -0
  70. package/docs/packages/runtime.md +265 -0
  71. package/docs/packages/s3.md +101 -0
  72. package/docs/preview-scope.md +142 -0
  73. package/docs/reference/README.md +33 -0
  74. package/docs/reference/cli-spec.json +575 -0
  75. package/docs/reference/cli.md +457 -0
  76. package/docs/reference/compatibility-matrix.md +169 -0
  77. package/docs/reference/diagnostics.md +1872 -0
  78. package/docs/reference/documentation-versions.json +22 -0
  79. package/docs/reference/environment.md +347 -0
  80. package/docs/reference/handler-authoring.md +264 -0
  81. package/docs/reference/project-config.md +582 -0
  82. package/docs/reference/project-config.schema.json +981 -0
  83. package/docs/reference/release-manifest.json +441 -0
  84. package/docs/reference/shell-completion.md +23 -0
  85. package/documentation-site.json +668 -0
  86. package/documentation-versions.json +22 -0
  87. package/examples/01-hello-json/.pulse/.gitignore +3 -0
  88. package/examples/01-hello-json/.pulse/config.ts +16 -0
  89. package/examples/01-hello-json/README.md +119 -0
  90. package/examples/01-hello-json/package.json +20 -0
  91. package/examples/01-hello-json/src/index.ts +9 -0
  92. package/examples/01-hello-json/tests/pulse.harness.ts +22 -0
  93. package/examples/01-hello-json/tsconfig.json +14 -0
  94. package/examples/02-request-schema/.pulse/.gitignore +3 -0
  95. package/examples/02-request-schema/.pulse/config.ts +17 -0
  96. package/examples/02-request-schema/README.md +145 -0
  97. package/examples/02-request-schema/package.json +20 -0
  98. package/examples/02-request-schema/src/index.ts +18 -0
  99. package/examples/02-request-schema/src/schemas.ts +20 -0
  100. package/examples/02-request-schema/tests/pulse.harness.ts +37 -0
  101. package/examples/02-request-schema/tsconfig.json +14 -0
  102. package/examples/03-fetch-composition/.pulse/.gitignore +3 -0
  103. package/examples/03-fetch-composition/.pulse/config.ts +31 -0
  104. package/examples/03-fetch-composition/README.md +210 -0
  105. package/examples/03-fetch-composition/package.json +20 -0
  106. package/examples/03-fetch-composition/src/index.ts +60 -0
  107. package/examples/03-fetch-composition/tests/pulse.harness.ts +47 -0
  108. package/examples/03-fetch-composition/tsconfig.json +14 -0
  109. package/examples/05-fastly-capabilities/.pulse/.gitignore +3 -0
  110. package/examples/05-fastly-capabilities/.pulse/config.ts +57 -0
  111. package/examples/05-fastly-capabilities/README.md +257 -0
  112. package/examples/05-fastly-capabilities/package.json +22 -0
  113. package/examples/05-fastly-capabilities/src/index.ts +55 -0
  114. package/examples/05-fastly-capabilities/tests/pulse.harness.ts +66 -0
  115. package/examples/05-fastly-capabilities/tsconfig.json +14 -0
  116. package/examples/07-opaque-proxy/.pulse/.gitignore +3 -0
  117. package/examples/07-opaque-proxy/.pulse/config.ts +38 -0
  118. package/examples/07-opaque-proxy/README.md +150 -0
  119. package/examples/07-opaque-proxy/package.json +21 -0
  120. package/examples/07-opaque-proxy/src/index.ts +9 -0
  121. package/examples/07-opaque-proxy/tests/pulse.harness.ts +25 -0
  122. package/examples/07-opaque-proxy/tsconfig.json +14 -0
  123. package/examples/09-router-lowering/.pulse/.gitignore +3 -0
  124. package/examples/09-router-lowering/.pulse/config.ts +22 -0
  125. package/examples/09-router-lowering/README.md +287 -0
  126. package/examples/09-router-lowering/package.json +22 -0
  127. package/examples/09-router-lowering/src/index.ts +65 -0
  128. package/examples/09-router-lowering/tests/pulse.harness.ts +91 -0
  129. package/examples/09-router-lowering/tsconfig.json +14 -0
  130. package/examples/10-entities-tools/.pulse/.gitignore +4 -0
  131. package/examples/10-entities-tools/.pulse/config.ts +23 -0
  132. package/examples/10-entities-tools/README.md +165 -0
  133. package/examples/10-entities-tools/package.json +21 -0
  134. package/examples/10-entities-tools/src/handlers.ts +15 -0
  135. package/examples/10-entities-tools/src/index.ts +31 -0
  136. package/examples/10-entities-tools/src/schemas.ts +18 -0
  137. package/examples/10-entities-tools/tests/pulse.harness.ts +40 -0
  138. package/examples/10-entities-tools/tools-facade.cjs +158 -0
  139. package/examples/10-entities-tools/tsconfig.json +14 -0
  140. package/examples/11-events/.pulse/.gitignore +4 -0
  141. package/examples/11-events/.pulse/config.ts +24 -0
  142. package/examples/11-events/README.md +194 -0
  143. package/examples/11-events/package.json +20 -0
  144. package/examples/11-events/src/index.ts +27 -0
  145. package/examples/11-events/src/schemas.ts +19 -0
  146. package/examples/11-events/tests/pulse.harness.ts +34 -0
  147. package/examples/11-events/tsconfig.json +15 -0
  148. package/examples/12-mcp-proxy/.pulse/.gitignore +3 -0
  149. package/examples/12-mcp-proxy/.pulse/config.ts +25 -0
  150. package/examples/12-mcp-proxy/README.md +149 -0
  151. package/examples/12-mcp-proxy/package.json +20 -0
  152. package/examples/12-mcp-proxy/src/index.ts +17 -0
  153. package/examples/12-mcp-proxy/tests/pulse.harness.ts +29 -0
  154. package/examples/12-mcp-proxy/tsconfig.json +14 -0
  155. package/examples/13-jwt-es256/.pulse/config.ts +19 -0
  156. package/examples/13-jwt-es256/README.md +195 -0
  157. package/examples/13-jwt-es256/package.json +21 -0
  158. package/examples/13-jwt-es256/src/index.ts +21 -0
  159. package/examples/13-jwt-es256/tests/pulse.harness.ts +54 -0
  160. package/examples/13-jwt-es256/tsconfig.json +14 -0
  161. package/examples/README.md +36 -0
  162. package/package.json +66 -6
  163. package/project-config.schema.json +981 -0
  164. package/release-manifest.json +441 -0
  165. package/src/command-spec.js +279 -0
  166. package/src/completion.js +113 -0
  167. package/src/diagnostics.js +350 -0
  168. package/src/documentation.js +45 -0
  169. package/src/index.d.ts +133 -0
  170. package/src/index.js +15 -0
  171. package/src/internal/command-executor.d.ts +29 -0
  172. package/src/internal/command-executor.js +143 -0
  173. package/src/internal/command-plan.d.ts +9 -0
  174. package/src/internal/command-plan.js +73 -0
  175. package/src/internal/command-reporter.d.ts +15 -0
  176. package/src/internal/command-reporter.js +133 -0
  177. package/src/internal/command-request.d.ts +35 -0
  178. package/src/internal/command-request.js +154 -0
  179. package/src/internal/node-http.js +54 -0
  180. package/src/internal/project-context.d.ts +66 -0
  181. package/src/internal/project-context.js +175 -0
  182. package/src/project-config-reference.js +4 -0
  183. package/src/project-config-schema.d.ts +23 -0
  184. package/src/project-config-schema.js +271 -0
  185. package/src/project-config.js +724 -0
  186. package/src/project-execution.js +2946 -0
  187. package/src/provider-drivers.js +3 -0
  188. package/src/target-support.js +3 -0
  189. package/src/typescript-module-loader.js +269 -0
  190. package/src/workflow.js +78 -0
  191. package/src/workspace.js +82 -0
@@ -0,0 +1,496 @@
1
+ # Compilation and lowering
2
+
3
+ Pulse handlers are async-shaped TypeScript, but native targets do not execute them with a JavaScript Promise runtime. The `pulse` CLI compiles the whole project into a provider-neutral program, records every trusted host operation the program requires, and then asks the selected provider to realize those operations.
4
+
5
+ This page explains that process. Start with [Getting started](../getting-started.md) when you only need to run an application.
6
+
7
+ ## The compilation path
8
+
9
+ ```text
10
+ async Pulse or Router application + .pulse/config.ts profile
11
+
12
+
13
+ project normalization and schema loading
14
+
15
+
16
+ canonical authoring lowering
17
+ routes • values • branches • effects • continuations
18
+
19
+
20
+ provider-neutral native execution plan
21
+
22
+ ├── pulse compile → compact Pulse-owned Wasm
23
+
24
+
25
+ capability requirements and provider operations
26
+
27
+
28
+ provider lowering plan
29
+
30
+ ├── Node execution/build output
31
+ └── Fastly Compute source + bin/main.wasm
32
+ ```
33
+
34
+ **Lowering** means translating a higher-level source operation into a smaller, explicit representation that later stages can validate and execute. It is not minification and it is not merely TypeScript-to-JavaScript transpilation.
35
+
36
+ The compiler must be able to answer four questions before provider execution begins:
37
+
38
+ 1. Which values and branches are part of the portable program?
39
+ 2. Which operations need host authority, such as fetch, secrets, KV, or GRIP?
40
+ 3. Where does execution continue after each host operation completes?
41
+ 4. Can the chosen provider satisfy every required capability and binding?
42
+
43
+
44
+ ## Router topology and middleware use the same downstream plan
45
+
46
+ Static Router authoring is normalized before canonical handler analysis:
47
+
48
+ ```text
49
+ Router registrations, middleware, error handlers, and mounts
50
+ → retained topology extraction and path normalization
51
+ → flat ordered execution-entry graph
52
+ → terminal normal/error cursor transfers
53
+ → canonical route and middleware branches
54
+ → the same effects, continuations, native plan, and providers
55
+ ```
56
+
57
+ `return next()` does not call another JavaScript function and later unwind. It ends the current state and advances the normal Router cursor. `return next(error)` does the same for the error cursor. Middleware effects before that transfer use the same suspend/resume states as effects inside ordinary handlers; only the Router cursor determines where execution continues afterward.
58
+
59
+ Router dispatch is not a separate runtime or compiler. `pulse inspect` reports `compiler.routing.entries`, `compiler.routing.routes`, and terminal semantics. Host operations carry the stable identity of the route or middleware entry that owns them. See the [Router lowering example](../../examples/09-router-lowering/) and [routing guide](../guides/routing.md).
60
+
61
+ ## From source to an effect
62
+
63
+ The single-fetch example uses the portable async authoring shape:
64
+
65
+ <!-- pulse-doc-source: examples/03-fetch-composition/src/index.ts -->
66
+ ```ts
67
+ import { Pulse } from '@pulse-compute/pulse'
68
+
69
+ interface User {
70
+ id: number
71
+ name: string
72
+ }
73
+
74
+ interface Stats {
75
+ score: number
76
+ }
77
+
78
+ interface Flags {
79
+ enabled: boolean
80
+ }
81
+
82
+ const app = new Pulse({ auto: true })
83
+
84
+ // One structured origin.
85
+ app.get('/user', async (ctx) => {
86
+ const user = await ctx
87
+ .fetch('https://users.example.test/users/123')
88
+ .json<User>()
89
+ return ctx.json({ found: true, user })
90
+ })
91
+
92
+ // Multiple origins with explicit sequential awaits.
93
+ app.get('/user-summary', async (ctx) => {
94
+ const user = await ctx
95
+ .fetch('https://users.example.test/users/123')
96
+ .json<User>()
97
+ const stats = await ctx
98
+ .fetch('https://stats.example.test/users/123')
99
+ .json<Stats>()
100
+ const flags = await ctx
101
+ .fetch('https://flags.example.test/users/123')
102
+ .json<Flags>()
103
+ return ctx.json({
104
+ id: user.id,
105
+ name: user.name,
106
+ score: stats.score,
107
+ enabled: flags.enabled,
108
+ })
109
+ })
110
+
111
+ // Multiple origins with explicit portable concurrency.
112
+ app.get('/user-summary-parallel', async (ctx) => {
113
+ const { user, stats, flags } = await ctx.parallel({
114
+ user: ctx.fetch('https://users.example.test/users/123').json<User>(),
115
+ stats: ctx.fetch('https://stats.example.test/users/123').json<Stats>(),
116
+ flags: ctx.fetch('https://flags.example.test/users/123').json<Flags>(),
117
+ })
118
+ return ctx.json({
119
+ id: user.id,
120
+ name: user.name,
121
+ score: stats.score,
122
+ enabled: flags.enabled,
123
+ })
124
+ })
125
+
126
+ export default app
127
+ ```
128
+ <!-- /pulse-doc-source -->
129
+
130
+ Run the compiler inspection without building a target:
131
+
132
+ <!-- pulse-doc-run {"args":["inspect","examples/03-fetch-composition","--json"],"display":"pulse inspect examples/03-fetch-composition --json"} -->
133
+ ```bash
134
+ pulse inspect examples/03-fetch-composition --json
135
+ ```
136
+ ```json
137
+ {
138
+ "status": "ok",
139
+ "provider": {
140
+ "id": "node"
141
+ },
142
+ "compiler": {
143
+ "effectCount": 7,
144
+ "continuationCount": 3,
145
+ "groupedContinuationCount": 2,
146
+ "opaqueReturnCount": 0
147
+ }
148
+ }
149
+ ```
150
+
151
+ The selected fields below are copied from that inspection result. Paths, hashes, source offsets, and unrelated schema detail are intentionally omitted so the example remains stable and readable.
152
+
153
+ <!-- pulse-doc-source: docs/fixtures/inspect-fetch-composition.selected.json -->
154
+ ```json
155
+ {
156
+ "status": "ok",
157
+ "project": {
158
+ "provider": "node",
159
+ "entry": "src/index.ts"
160
+ },
161
+ "compiler": {
162
+ "version": "pulse.canonical-api-compiler.v7",
163
+ "capabilities": [
164
+ "fetch",
165
+ "response.json",
166
+ "response.text"
167
+ ],
168
+ "effects": [
169
+ {
170
+ "id": "fetch-1",
171
+ "kind": "fetch",
172
+ "operation": "dispatch",
173
+ "capability": "fetch",
174
+ "resource": {
175
+ "kind": "literal",
176
+ "value": "https://users.example.test/users/123",
177
+ "origin": "https://users.example.test"
178
+ },
179
+ "grouped": false
180
+ },
181
+ {
182
+ "id": "fetch-2",
183
+ "kind": "fetch",
184
+ "operation": "dispatch",
185
+ "capability": "fetch",
186
+ "resource": {
187
+ "kind": "literal",
188
+ "value": "https://users.example.test/users/123",
189
+ "origin": "https://users.example.test"
190
+ },
191
+ "grouped": true
192
+ },
193
+ {
194
+ "id": "fetch-3",
195
+ "kind": "fetch",
196
+ "operation": "dispatch",
197
+ "capability": "fetch",
198
+ "resource": {
199
+ "kind": "literal",
200
+ "value": "https://stats.example.test/users/123",
201
+ "origin": "https://stats.example.test"
202
+ },
203
+ "grouped": true
204
+ },
205
+ {
206
+ "id": "fetch-4",
207
+ "kind": "fetch",
208
+ "operation": "dispatch",
209
+ "capability": "fetch",
210
+ "resource": {
211
+ "kind": "literal",
212
+ "value": "https://flags.example.test/users/123",
213
+ "origin": "https://flags.example.test"
214
+ },
215
+ "grouped": true
216
+ },
217
+ {
218
+ "id": "fetch-5",
219
+ "kind": "fetch",
220
+ "operation": "dispatch",
221
+ "capability": "fetch",
222
+ "resource": {
223
+ "kind": "literal",
224
+ "value": "https://users.example.test/users/123",
225
+ "origin": "https://users.example.test"
226
+ },
227
+ "grouped": true,
228
+ "groupKey": "user"
229
+ },
230
+ {
231
+ "id": "fetch-6",
232
+ "kind": "fetch",
233
+ "operation": "dispatch",
234
+ "capability": "fetch",
235
+ "resource": {
236
+ "kind": "literal",
237
+ "value": "https://stats.example.test/users/123",
238
+ "origin": "https://stats.example.test"
239
+ },
240
+ "grouped": true,
241
+ "groupKey": "stats"
242
+ },
243
+ {
244
+ "id": "fetch-7",
245
+ "kind": "fetch",
246
+ "operation": "dispatch",
247
+ "capability": "fetch",
248
+ "resource": {
249
+ "kind": "literal",
250
+ "value": "https://flags.example.test/users/123",
251
+ "origin": "https://flags.example.test"
252
+ },
253
+ "grouped": true,
254
+ "groupKey": "flags"
255
+ }
256
+ ],
257
+ "continuations": [
258
+ {
259
+ "id": "continuation-1",
260
+ "kind": "single-fetch",
261
+ "effectIds": [
262
+ "fetch-1"
263
+ ]
264
+ },
265
+ {
266
+ "id": "continuation-2",
267
+ "kind": "fetch-group",
268
+ "effectIds": [
269
+ "fetch-2",
270
+ "fetch-3",
271
+ "fetch-4"
272
+ ]
273
+ },
274
+ {
275
+ "id": "continuation-3",
276
+ "kind": "parallel-group",
277
+ "effectIds": [
278
+ "fetch-5",
279
+ "fetch-6",
280
+ "fetch-7"
281
+ ]
282
+ }
283
+ ],
284
+ "providerLowering": {
285
+ "version": "pulse.canonical-provider-plan.v1",
286
+ "contractVersion": "pulse.canonical-provider-contract.v1",
287
+ "provider": "node",
288
+ "requirements": [
289
+ "fetch",
290
+ "response.json",
291
+ "response.text"
292
+ ],
293
+ "operations": [
294
+ {
295
+ "id": "fetch-1",
296
+ "lowering": "node.fetch.dispatch",
297
+ "binding": "https://users.example.test"
298
+ },
299
+ {
300
+ "id": "fetch-2",
301
+ "lowering": "node.fetch.dispatch",
302
+ "binding": "https://users.example.test"
303
+ },
304
+ {
305
+ "id": "fetch-3",
306
+ "lowering": "node.fetch.dispatch",
307
+ "binding": "https://stats.example.test"
308
+ },
309
+ {
310
+ "id": "fetch-4",
311
+ "lowering": "node.fetch.dispatch",
312
+ "binding": "https://flags.example.test"
313
+ },
314
+ {
315
+ "id": "fetch-5",
316
+ "lowering": "node.fetch.dispatch",
317
+ "binding": "https://users.example.test"
318
+ },
319
+ {
320
+ "id": "fetch-6",
321
+ "lowering": "node.fetch.dispatch",
322
+ "binding": "https://stats.example.test"
323
+ },
324
+ {
325
+ "id": "fetch-7",
326
+ "lowering": "node.fetch.dispatch",
327
+ "binding": "https://flags.example.test"
328
+ }
329
+ ],
330
+ "providerSpecificUserland": false,
331
+ "providerSdkUserland": false,
332
+ "capabilityDiscoveryFromUserland": false
333
+ }
334
+ }
335
+ }
336
+ ```
337
+ <!-- /pulse-doc-source -->
338
+
339
+ The source call becomes a canonical `fetch-1` effect. The code after the call becomes `continuation-1`. The provider plan then maps that effect to `node.fetch.dispatch`. Application source does not import Node APIs, a Fastly SDK, or provider objects.
340
+
341
+
342
+ ## Keyed parallel lowering
343
+
344
+ `ctx.parallel({ ... })` is the explicit portable concurrency form. The compiler
345
+ requires one nonempty static object literal with fixed non-index string keys and
346
+ recognized Pulse effects as values. It rejects dynamic records, spreads, computed
347
+ keys, methods, accessors, arbitrary promises, reused effect roots, and nested
348
+ parallel groups with stable diagnostics.
349
+
350
+ Native lowering erases the call, emits its members into one canonical effect group,
351
+ records the source key on each effect site, suspends once, and reconstructs an
352
+ ordinary keyed object on resume. Property order remains authoritative even when
353
+ provider completion order differs. The JavaScript target executes the same source
354
+ through the request-owned shared effect adapter and implements the same all-settle,
355
+ keyed result and failure contract.
356
+
357
+ This does not remove Native implicit grouping. Adjacent independently lowerable
358
+ effects may still be grouped as a performance optimization, while separate awaits
359
+ executed directly as JavaScript retain ordinary sequential semantics. Authors use
360
+ `ctx.parallel` when concurrency itself must be portable.
361
+
362
+ ## Synchronous logging lowering
363
+
364
+ `ctx.log.error`, `warn`, `info`, and `debug` share one numeric level contract
365
+ across all four provider/target modes. Logging is synchronous and is not added to
366
+ the effect or continuation graph.
367
+
368
+ For Native targets, the resolved flat profile `reporting` threshold participates
369
+ in the build identity. Disabled statements are erased during lowering and enabled
370
+ statements call the compact `pulse_log(level, ptr, len)` ABI. JavaScript targets
371
+ retain ordinary expression evaluation and filter through the same threshold at
372
+ runtime. Provider formatting and destination may differ; level identity,
373
+ enabled/disabled decisions, redaction, and request-contained sink failure are the
374
+ portable conformance surface.
375
+
376
+ ## What the compiler owns
377
+
378
+ The whole-project compiler owns:
379
+
380
+ - discovering the authoritative workspace, loading `.pulse/config.ts`, and selecting one flat profile;
381
+ - parsing the canonical handler and its explicitly declared JSON schema sources;
382
+ - rejecting unsupported ambient authority, asynchronous syntax, and dynamic forms that cannot be represented safely;
383
+ - assigning stable effect and continuation identities;
384
+ - deriving capability requirements from the program;
385
+ - invoking trusted package-owned lowerers for supported facades;
386
+ - asking the provider contract to validate and map required operations;
387
+ - emitting canonical metadata used by `inspect`, `test`, `dev`, `compile`, and `build`;
388
+ - lowering the canonical program into the versioned provider-neutral native plan used by `pulse compile`.
389
+
390
+ The compiler does **not** make network requests, read deployment secrets, or invent provider bindings while analyzing source.
391
+
392
+ ## Canonical lowering and package-owned lowering
393
+
394
+ Pulse has two related lowering paths.
395
+
396
+ ### Canonical handler lowering
397
+
398
+ Calls on `PulseContext`, such as `ctx.fetch`, `ctx.config.get`, `ctx.secret.get`, `ctx.kv.get`, and response constructors, are part of the canonical API contract. The central compiler recognizes them and emits canonical operations.
399
+
400
+ ### Package-owned lowering
401
+
402
+ A supported package can own a normal application root, manifest, contract
403
+ mapping, and compiler builder. Assets is the converged example:
404
+ `@pulse-compute/assets` executes as the real JavaScript package and the same
405
+ supported `assets.lookup(ctx, ...)` shape lowers into `pulse.assets` operations
406
+ on Native targets. The package keeps its domain-specific validation rules; the
407
+ generic loader handles reachable-graph discovery and trusted invocation. Older
408
+ `/pulsewasm` imports are isolated in the
409
+ [compatibility migration guide](../guides/compatibility-imports.md).
410
+
411
+ In Pulse `1.0.0-beta.2`, package-owned builders must declare `compiler.trust: 'first-party'` and ship in the synchronized release set. This is an internal contributor mechanism, not a general third-party plugin API. See [Add a first-party package-owned lowerer](../contributing/adding-first-party-lowerer.md).
412
+
413
+ ## Provider lowering is a second contract
414
+
415
+ Canonical effects describe **what** the program needs. A provider lowering plan describes **how** a selected provider realizes those needs.
416
+
417
+ For example:
418
+
419
+ ```text
420
+ canonical capability: fetch
421
+ Node lowering: node.fetch.dispatch
422
+ Fastly lowering: fastly.fetch.dispatch
423
+ ```
424
+
425
+ Provider validation is fail-closed. A build or execution command stops with a stable diagnostic when a required capability or binding is missing rather than silently substituting provider-specific behavior. See [Contracts and providers](./contracts-and-providers.md).
426
+
427
+ ## Why source restrictions exist
428
+
429
+ The compiler accepts a deliberately bounded TypeScript subset because every accepted construct needs deterministic lowering and equivalent provider behavior. In particular:
430
+
431
+ - managed handlers are async-shaped; only trusted Pulse awaits lower natively, while arbitrary Promise construction and arbitrary library awaits remain outside native eligibility;
432
+ - ambient `process.env`, global `fetch`, timers, and randomness do not become hidden authority;
433
+ - package facade calls use statically recognizable imports and supported argument shapes;
434
+ - opaque bodies can cross the boundary but cannot be inspected or transformed in userland;
435
+ - provider SDK objects never enter handler scope.
436
+
437
+ These restrictions are compatibility guarantees, not temporary parser
438
+ accidents. The complete boundary is in the
439
+ [Beta scope](../preview-scope.md).
440
+
441
+ ## Reading `pulse inspect`
442
+
443
+ Use `pulse inspect` before target compilation when you need to answer:
444
+
445
+ - Is the selected target's core execution lane ready, and is the target generally available under its declared support policy?
446
+ - Is this project eligible for the selected target's implemented commands? If not, which stable capability or package reasons and owners apply?
447
+ - Which target descriptor, command matrix, application graph, loader observation, and parity evidence produced that decision?
448
+ - Which capabilities did this source require?
449
+ - Which source calls became effects?
450
+ - Which effects are grouped?
451
+ - Which continuations resume after them?
452
+ - What native plan hash and portable Wasm import/export surface will `pulse compile` produce?
453
+ - Which provider lowering and deployment binding was selected?
454
+ - Did package-owned lowering run?
455
+ - Is any provider-specific userland entering the program?
456
+
457
+ For a Node or Fastly JavaScript profile, `provider.targetSupport` is separate from
458
+ the provider-neutral compiler plan. General availability is defined as full
459
+ target support: both current declarations report `coreExecutionReady: true`,
460
+ `fullTargetSupportReady: true`, and `generalAvailable: true` because every
461
+ declared gate is satisfied. The project-level status (`eligible`, `pending`, or
462
+ `blocked`) is still derived from the JavaScript application plan, reachable
463
+ capability, provider-requirement, and package evidence. Loader state is a
464
+ separate observation: it changes the `observationHash`, not the static
465
+ `evidenceHash` or project eligibility. Native eligibility independently controls
466
+ whether the provider-neutral `compile` command can lower the project.
467
+ `automaticFallback` remains false; a Native compiler observation in the same
468
+ inspection does not change the selected JavaScript target.
469
+
470
+ A JavaScript-configured `pulse compile` still emits provider-neutral Pulse-owned
471
+ Wasm. Its manifest records the configured target and plan-only eligibility
472
+ evidence. `pulse build` is separate: Node JavaScript emits a deterministic
473
+ executable CommonJS package, while Fastly JavaScript emits a deterministic ESM
474
+ source/deployment closure with exact `esbuild` and `@fastly/js-compute` pins. The
475
+ offline release gate compiles that Fastly closure to a runtime Wasm and binds the
476
+ artifact by SHA-256; it does not deploy or publish it. Neither JavaScript build
477
+ emits a Pulse Native artifact or falls back to Native.
478
+
479
+ A healthy canonical plan reports all three provider isolation flags as `false`:
480
+
481
+ ```json
482
+ {
483
+ "providerSpecificUserland": false,
484
+ "providerSdkUserland": false,
485
+ "capabilityDiscoveryFromUserland": false
486
+ }
487
+ ```
488
+
489
+ ## Related documentation
490
+
491
+ - [Effects and continuations](./effects-and-continuations.md)
492
+ - [Structured and opaque bodies](./bodies.md)
493
+ - [Contracts and providers](./contracts-and-providers.md)
494
+ - [CLI reference](../reference/cli.md#pulse-inspect)
495
+ - [Diagnostics](../reference/diagnostics.md)
496
+ - [Architecture overview](../architecture/overview.md)
@@ -0,0 +1,158 @@
1
+ # Contracts and providers
2
+
3
+ Pulse separates application semantics from provider realization. The compiler emits canonical contracts; a provider declares supported capabilities and maps canonical operations to its runtime and build target.
4
+
5
+ This separation is why the same handler can run through the Node conformance provider and compile to a Fastly Compute target without importing either provider’s SDK.
6
+
7
+ ## Contract layers
8
+
9
+ | Layer | Owner | Purpose |
10
+ |---|---|---|
11
+ | Canonical handler API | `@pulse-compute/runtime` | Defines portable request, response, fetch, config, secret, and KV semantics. |
12
+ | Canonical compiler program | Pulse compiler | Records values, branches, effects, continuations, schemas, and capability requirements. |
13
+ | Package effect contract | Owning package + shared contracts | Defines narrow package-owned operations such as `pulse.grip`. |
14
+ | Provider descriptor | Provider package | Declares capabilities, operation lowering names, runtime identity, and build target. |
15
+ | Provider lowering plan | Shared provider contract | Verifies requirements and binds every canonical operation to provider behavior. |
16
+ | Provider toolchain boundary | Shared provider contract + selected provider package | Validates the exact driver/target surface and projects versioned planning, target invocation, and result envelopes. |
17
+ | Local conformance runtime | Provider package | Executes canonical programs for `test` and `dev`. |
18
+ | Deployable target builder | Provider package | Emits provider source and compiled target artifacts for `build`. |
19
+
20
+ No layer may infer capability support from arbitrary application imports or provider-specific objects.
21
+
22
+ ## A package-owned contract in source
23
+
24
+ Packages expose ordinary application APIs while retaining package-owned effect identities and provider requirements. Assets is the converged example:
25
+
26
+ ```ts
27
+ import { assets } from '@pulse-compute/assets'
28
+
29
+ const found = await assets.lookup(ctx, 'public', '/app.js')
30
+ return assets.respond(found)
31
+ ```
32
+
33
+ JavaScript executes the package implementation through the request-bound package bridge. Native compilation recognizes the same root call and emits the `pulse.assets` package operation. The package owns payload validation and result semantics; the selected provider owns lookup realization and opaque response delivery.
34
+
35
+ GRIP uses the same split. `isWebSocket`, `subscribe`, and `handoff` are pure HTTP helpers; only `grip.broadcast(ctx, message)` is a provider effect. Bounded broadcast realization is provider-owned and does not imply Pulse-owned WebSocket lifecycle.
36
+
37
+ `pulse inspect --json` reports the reachable package contract, selected target support, emitted package effects, provider requirements, and any pending realization reason separately.
38
+
39
+ ## Provider selection
40
+
41
+ Pulse `1.0.0-beta.2` has three provider driver identifiers:
42
+
43
+ | Provider | Local execution | Deployable build | Purpose |
44
+ |---|---:|---:|---|
45
+ | `node` | Yes | Yes, as Node output | Default local and portable conformance path. |
46
+ | `fastly` | Yes, through local conformance | Yes, Fastly Compute | Typed bindings plus source and `bin/main.wasm` generation. |
47
+ | `none` | No | Canonical compile output only | Inspect or compile without an executable provider. |
48
+
49
+ Provider selection is normalized from the active `.pulse/config.ts` profile. Public provider overrides are not supported, and `none` cannot drive `pulse test` or `pulse dev`.
50
+
51
+ ## Capability validation
52
+
53
+ The compiler derives requirements from source. The provider descriptor lists what it implements. The shared contract rejects the plan when any requirement is absent.
54
+
55
+ ```text
56
+ source operation
57
+ → canonical capability
58
+ → provider descriptor lookup
59
+ → provider lowering + binding
60
+ → execution/build
61
+ ```
62
+
63
+ This catches unsupported behavior before a request reaches a provider runtime. Relevant diagnostics include:
64
+
65
+ - [`PULSE_PROVIDER_CAPABILITY_MISSING`](../reference/diagnostics.md#pulse-provider-capability-missing);
66
+ - [`PULSE_PROVIDER_CAPABILITY_UNSUPPORTED`](../reference/diagnostics.md#pulse-provider-capability-unsupported);
67
+ - [`PULSE_FASTLY_BACKEND_REQUIRED`](../reference/diagnostics.md#pulse-fastly-backend-required);
68
+ - [`PULSE_FASTLY_KV_BINDINGS_INVALID`](../reference/diagnostics.md#pulse-fastly-kv-bindings-invalid).
69
+
70
+ ## Bindings are configuration, not userland APIs
71
+
72
+ A Fastly provider configuration maps logical canonical resources to deployment bindings:
73
+
74
+ ```ts
75
+ import { defineConfig } from '@pulse-compute/cli'
76
+ import { fastly } from '@pulse-compute/provider-fastly'
77
+
78
+ export default defineConfig({
79
+ provider: fastly({
80
+ configStore: 'pulse_config',
81
+ secretStore: 'pulse_secrets',
82
+ kv: { sessions: 'sessions_store' },
83
+ backends: { 'https://api.example.com': 'api_backend' },
84
+ dynamicBackends: false,
85
+ }),
86
+ })
87
+ ```
88
+
89
+ Handler source still calls `ctx.config.get`, `ctx.secret.get`, `ctx.kv.get`, or `ctx.fetch`. It never imports a Fastly store, backend, request, response, or SDK type.
90
+
91
+ ## Local conformance versus deployment reality
92
+
93
+ `pulse test` and `pulse dev` execute through provider-owned local conformance runtimes using configured fixtures and values. They prove the canonical program and provider mapping, not the health of an external deployment.
94
+
95
+ For Fastly, `pulse build` additionally emits a real Compute target. The explicit environment-dependent reality gate executes it through an inspected Fastly CLI or direct Viceroy launcher and real HTTP requests; remote deployment remains separate. See [Fastly deployment candidates](../guides/deploying-fastly.md), [Fastly package guide](../packages/provider-fastly.md), and [Release acceptance](../maintainers/release-acceptance.md).
96
+
97
+ ## Current extension boundary
98
+
99
+ There is no automatic provider plugin registry in `1.0.0-beta.2`. Configuration
100
+ selects a bare host ID, internal `none`, or an exact scoped project-installed
101
+ package name. Bare IDs resolve by the `@pulse-compute/provider-<id>` convention.
102
+ A package is loadable only when selected and when it exports the versioned
103
+ `./toolchain` contract; Pulse does not scan, self-register, or fall back to
104
+ another provider. Adding a host to the built-in support catalog requires a
105
+ coordinated source, release, documentation, and acceptance change.
106
+
107
+ Likewise, package-owned lowerer manifests are executable only when they are trusted first-party packages. External npm packages cannot self-register compiler code merely by shipping a manifest.
108
+
109
+ Contributor workflows:
110
+
111
+ - [Pulse-aware package authoring](../contributing/pulse-aware-packages.md)
112
+ - [Add a first-party package-owned lowerer](../contributing/adding-first-party-lowerer.md)
113
+ - [Add a core provider](../contributing/adding-core-provider.md)
114
+
115
+ The provider toolchain page defines only its narrow, explicit bootstrap
116
+ contract. It does not define general plugin discovery or widen the
117
+ first-party-only lowerer compatibility promise.
118
+
119
+ ## Toolchain and target boundary
120
+
121
+ Loading `./toolchain` is only the bootstrap. The export, driver, selected target,
122
+ planning input, target invocation, Native artifact, JavaScript package result,
123
+ and provider result each carry an exact contract identity. Driver creation
124
+ receives no compiler callback or option bag. Shared validation rejects unknown
125
+ versions and fields, missing required methods, malformed target descriptors,
126
+ and supported Native targets without a final-Wasm policy before provider code
127
+ can generate output.
128
+
129
+ The compiler composition root projects only:
130
+
131
+ - the canonical Native or JavaScript application plan;
132
+ - the canonical provider lowering plan and explicit capability, binding, and
133
+ package requirements;
134
+ - the exact selected target and normalized provider configuration;
135
+ - explicit project root, output root, profile, synchronized package versions,
136
+ and optimization posture;
137
+ - for Native, copied Wasm bytes plus the narrowed artifact manifest,
138
+ realization contributions, and final guest-audit authorization.
139
+
140
+ Compiler ASTs and services, builders, raw lowerer output, mutable manifests,
141
+ diagnostic collectors, caches, and unrelated environment/profile records do not
142
+ cross the seam. Target results are normalized into data-only versioned
143
+ envelopes before command orchestration reads them. Guest-linked packaging is
144
+ accepted only when the packaged artifact hash equals the final authorized audit
145
+ hash. Failure remains attached to the selected provider and target; Pulse does
146
+ not attempt another target or provider.
147
+
148
+ ## Related documentation
149
+
150
+ - [Compilation and lowering](./compilation-and-lowering.md)
151
+ - [Effects and continuations](./effects-and-continuations.md)
152
+ - [Package support policy](../packages/README.md)
153
+ - [Managed handler TypeScript and JavaScript](../reference/handler-authoring.md)
154
+ - [Provider and target compatibility](../reference/compatibility-matrix.md)
155
+ - [Node build and execution](../guides/deploying-node.md)
156
+ - [Fastly deployment candidates](../guides/deploying-fastly.md)
157
+ - [Project configuration](../reference/project-config.md)
158
+ - [Fastly capabilities](../guides/fastly-capabilities.md)