stitchkit 0.122.2 → 0.123.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/browser/client.d.ts.map +1 -1
  3. package/dist/browser/contract-identity.d.ts +21 -0
  4. package/dist/browser/contract-identity.d.ts.map +1 -0
  5. package/dist/browser/unknown-operation.d.ts +3 -0
  6. package/dist/browser/unknown-operation.d.ts.map +1 -0
  7. package/dist/contract/define.d.ts +6 -2
  8. package/dist/contract/define.d.ts.map +1 -1
  9. package/dist/contract/endpoint-capabilities.d.ts.map +1 -1
  10. package/dist/contract/errors.d.ts +4 -0
  11. package/dist/contract/errors.d.ts.map +1 -1
  12. package/dist/contract/fingerprint.d.ts +51 -0
  13. package/dist/contract/fingerprint.d.ts.map +1 -0
  14. package/dist/contract/identity.d.ts +21 -0
  15. package/dist/contract/identity.d.ts.map +1 -0
  16. package/dist/contract/stream.d.ts +32 -0
  17. package/dist/contract/stream.d.ts.map +1 -1
  18. package/dist/entrypoints/agent-runtime/browser.js +2 -2
  19. package/dist/entrypoints/agent-runtime/coding-tools.js +11 -11
  20. package/dist/entrypoints/agent-runtime/harness.js +12 -12
  21. package/dist/entrypoints/agent-runtime/openrouter.js +1 -1
  22. package/dist/entrypoints/agent-runtime/realtime.js +2 -2
  23. package/dist/entrypoints/agent-runtime/sandbox.js +11 -11
  24. package/dist/entrypoints/agent-runtime/sqlite/bun.js +15 -15
  25. package/dist/entrypoints/agent-runtime/sqlite/node.js +15 -15
  26. package/dist/entrypoints/agent-runtime/testing.js +15 -15
  27. package/dist/entrypoints/agent-runtime.js +22 -22
  28. package/dist/entrypoints/application/diagnostic-journal.js +5 -5
  29. package/dist/entrypoints/application/directory-inbox.js +5 -5
  30. package/dist/entrypoints/application/grammy.js +1 -1
  31. package/dist/entrypoints/application.js +7 -7
  32. package/dist/entrypoints/cli/publish.js +5 -5
  33. package/dist/entrypoints/cli.d.ts +1 -1
  34. package/dist/entrypoints/cli.d.ts.map +1 -1
  35. package/dist/entrypoints/cli.js +20 -17
  36. package/dist/entrypoints/contract.d.ts +2 -0
  37. package/dist/entrypoints/contract.d.ts.map +1 -1
  38. package/dist/entrypoints/contract.js +11 -5
  39. package/dist/entrypoints/files/packaging.d.ts +1 -0
  40. package/dist/entrypoints/files/packaging.d.ts.map +1 -1
  41. package/dist/entrypoints/files/packaging.js +230 -61
  42. package/dist/entrypoints/files.js +5 -5
  43. package/dist/entrypoints/geo.js +3 -3
  44. package/dist/entrypoints/http.js +3 -3
  45. package/dist/entrypoints/index.d.ts +1 -0
  46. package/dist/entrypoints/index.d.ts.map +1 -1
  47. package/dist/entrypoints/index.js +45 -24
  48. package/dist/entrypoints/live.js +2 -2
  49. package/dist/entrypoints/node.js +11 -11
  50. package/dist/entrypoints/observability.js +4 -4
  51. package/dist/entrypoints/primitives.js +1 -1
  52. package/dist/entrypoints/process/owner-loss.js +2 -2
  53. package/dist/entrypoints/process.d.ts +6 -1
  54. package/dist/entrypoints/process.d.ts.map +1 -1
  55. package/dist/entrypoints/process.js +370 -9
  56. package/dist/entrypoints/react.js +2 -2
  57. package/dist/entrypoints/realtime.js +3 -3
  58. package/dist/entrypoints/remote.js +20 -10
  59. package/dist/entrypoints/server.d.ts +1 -0
  60. package/dist/entrypoints/server.d.ts.map +1 -1
  61. package/dist/entrypoints/server.js +56 -20
  62. package/dist/entrypoints/telegram/screens.js +4 -4
  63. package/dist/entrypoints/telegram.js +6 -6
  64. package/dist/entrypoints/testing.js +10 -10
  65. package/dist/entrypoints/tools/agent.js +9 -9
  66. package/dist/entrypoints/tools/connections.js +7 -7
  67. package/dist/entrypoints/tools/contract.js +1 -1
  68. package/dist/entrypoints/tools/invoker.js +7 -7
  69. package/dist/entrypoints/tools/mcp.js +9 -9
  70. package/dist/entrypoints/tools/toolkit.js +15 -14
  71. package/dist/entrypoints/tools.js +17 -17
  72. package/dist/entrypoints/tracking/contract.js +2 -2
  73. package/dist/entrypoints/tracking.js +1 -1
  74. package/dist/files/client-artifact.d.ts +75 -0
  75. package/dist/files/client-artifact.d.ts.map +1 -0
  76. package/dist/{index-am1h8c9v.js → index-0d3sm0m9.js} +5 -5
  77. package/dist/{index-72xtvcvr.js → index-0ndz5r8z.js} +1 -1
  78. package/dist/index-0v9xb8pz.js +66 -0
  79. package/dist/{index-qcn84byh.js → index-0w0hxp7n.js} +4 -0
  80. package/dist/{index-3za04re9.js → index-1c945ten.js} +1 -1
  81. package/dist/{index-r6v4mc67.js → index-1d7s7da0.js} +1 -1
  82. package/dist/{index-zsvzhjtk.js → index-20hn8pbh.js} +1 -1
  83. package/dist/{index-f6pw75e1.js → index-2x2ch0bt.js} +2 -2
  84. package/dist/{index-61hve0ez.js → index-303qne11.js} +1 -1
  85. package/dist/{index-g149e9pk.js → index-3bzk2645.js} +1 -1
  86. package/dist/index-45dyex0j.js +119 -0
  87. package/dist/{index-nwm3ww60.js → index-64b2dxqz.js} +1 -1
  88. package/dist/{index-aqwg2dm5.js → index-6k47xdtt.js} +2 -2
  89. package/dist/index-7q05ag0z.js +10 -0
  90. package/dist/{index-kz9734jc.js → index-96yjwwgf.js} +6 -5
  91. package/dist/{index-ff39f9dr.js → index-993vtv1w.js} +3 -3
  92. package/dist/{index-4jpykzvw.js → index-99nnsz39.js} +2 -2
  93. package/dist/{index-pfvde2fz.js → index-9ypczvpj.js} +1 -1
  94. package/dist/{index-qch1vcaa.js → index-a7ebd769.js} +2 -2
  95. package/dist/{index-98hv3sba.js → index-aw6kx2e6.js} +1 -1
  96. package/dist/{index-yj6saztf.js → index-bz9cmnqq.js} +96 -14
  97. package/dist/{index-mtwh9hyp.js → index-cjzaj7ev.js} +1 -1
  98. package/dist/{index-gtx3eknx.js → index-dv7pa9w6.js} +2 -2
  99. package/dist/{index-sa99p98w.js → index-eabx6qjh.js} +3 -3
  100. package/dist/{index-8yxgcyt6.js → index-egg0yp2j.js} +1 -1
  101. package/dist/{index-h1j71dr1.js → index-enkc9pem.js} +1 -1
  102. package/dist/{index-aqmadanx.js → index-fa53vygr.js} +2 -2
  103. package/dist/{index-e6c2spsd.js → index-fpwbskp2.js} +1 -1
  104. package/dist/{index-m8a44e64.js → index-ftk3838x.js} +1 -1
  105. package/dist/{index-j95jp7ee.js → index-gdqawggb.js} +1 -1
  106. package/dist/{index-9dyd8xzf.js → index-hkw233vx.js} +2 -2
  107. package/dist/{index-9jfdm5tg.js → index-hkxgennw.js} +7 -1
  108. package/dist/{index-qaqxdbxf.js → index-j8rda9n6.js} +7 -7
  109. package/dist/{index-4tyt1qya.js → index-jc5xgkpk.js} +2 -2
  110. package/dist/{index-s9a0fjh3.js → index-kffm4r8w.js} +1 -1
  111. package/dist/{index-5w6t91ze.js → index-m8ddk770.js} +1 -1
  112. package/dist/{index-2fc1k9k2.js → index-mdyhr36s.js} +32 -122
  113. package/dist/{index-r7a7r7ha.js → index-mfntvj58.js} +6 -3
  114. package/dist/{index-gq3fnq3f.js → index-n90tkkcv.js} +2 -2
  115. package/dist/{index-k42ckyzx.js → index-ndjzbt2v.js} +2 -2
  116. package/dist/{index-ycqhyars.js → index-qc7tq0za.js} +3 -3
  117. package/dist/{index-9frf01t9.js → index-r0xr7y4m.js} +1 -1
  118. package/dist/{index-s3yxw6kt.js → index-s512scra.js} +9 -7
  119. package/dist/{index-1h2zv8rx.js → index-sqc6ezrb.js} +41 -1
  120. package/dist/{index-2bqf0a74.js → index-sqkttz7z.js} +1 -1
  121. package/dist/{index-rbzq0q16.js → index-t8jz0e20.js} +1 -1
  122. package/dist/{index-8ryydawp.js → index-tt3ej8r3.js} +1 -1
  123. package/dist/{index-x4p3kw1p.js → index-tynna0n4.js} +6 -6
  124. package/dist/{index-72r9k0av.js → index-v62cpkx3.js} +1 -1
  125. package/dist/{index-10rg78vf.js → index-v8xm2s9v.js} +2 -2
  126. package/dist/{index-y6ye582c.js → index-w0wy05gr.js} +10 -10
  127. package/dist/{index-kw1ws0cs.js → index-w3nv6kr8.js} +1 -1
  128. package/dist/{index-eeahxwvg.js → index-ww3gss55.js} +1 -1
  129. package/dist/{index-6j15ktag.js → index-wzqd6pen.js} +1 -1
  130. package/dist/{index-aswegq1r.js → index-xjb131sc.js} +367 -131
  131. package/dist/{index-1fyea34p.js → index-yebkpytc.js} +5 -5
  132. package/dist/{index-bgcwxhxh.js → index-yfpt7sxr.js} +24 -46
  133. package/dist/{index-69t14s4a.js → index-yt1gg11x.js} +3 -2
  134. package/dist/{index-jk29rwkr.js → index-zfncctjp.js} +1 -1
  135. package/dist/internal/stable-digest.d.ts +5 -0
  136. package/dist/internal/stable-digest.d.ts.map +1 -1
  137. package/dist/internal/typed.d.ts +2 -0
  138. package/dist/internal/typed.d.ts.map +1 -1
  139. package/dist/process/operation-child.d.ts +38 -0
  140. package/dist/process/operation-child.d.ts.map +1 -0
  141. package/dist/process/operation-wire.d.ts +68 -0
  142. package/dist/process/operation-wire.d.ts.map +1 -0
  143. package/dist/process/operation.d.ts +74 -0
  144. package/dist/process/operation.d.ts.map +1 -0
  145. package/dist/server/contract-identity.d.ts +10 -0
  146. package/dist/server/contract-identity.d.ts.map +1 -0
  147. package/dist/server/contract-stream.d.ts.map +1 -1
  148. package/dist/server/endpoint-response.d.ts.map +1 -1
  149. package/dist/server/error-hook.d.ts +1 -0
  150. package/dist/server/error-hook.d.ts.map +1 -1
  151. package/dist/tools/cli/args-route.d.ts.map +1 -1
  152. package/dist/tools/cli/args-tokens.d.ts.map +1 -1
  153. package/dist/tools/cli/command.d.ts +39 -18
  154. package/dist/tools/cli/command.d.ts.map +1 -1
  155. package/dist/tools/cli/format.d.ts.map +1 -1
  156. package/dist/tools/cli/help.d.ts.map +1 -1
  157. package/dist/tools/cli/invoke.d.ts +3 -1
  158. package/dist/tools/cli/invoke.d.ts.map +1 -1
  159. package/dist/tools/cli/io.d.ts +6 -0
  160. package/dist/tools/cli/io.d.ts.map +1 -1
  161. package/dist/tools/cli/managed-surface.d.ts +2 -0
  162. package/dist/tools/cli/managed-surface.d.ts.map +1 -1
  163. package/dist/tools/cli/policy.d.ts +14 -0
  164. package/dist/tools/cli/policy.d.ts.map +1 -1
  165. package/dist/tools/cli/run-managed.d.ts.map +1 -1
  166. package/dist/tools/cli/run-native.d.ts.map +1 -1
  167. package/dist/tools/cli/run-stream.d.ts +23 -0
  168. package/dist/tools/cli/run-stream.d.ts.map +1 -0
  169. package/dist/tools/cli/session.d.ts +8 -0
  170. package/dist/tools/cli/session.d.ts.map +1 -1
  171. package/dist/tools/cli/stream-surface.d.ts +17 -0
  172. package/dist/tools/cli/stream-surface.d.ts.map +1 -0
  173. package/dist/tools/execute.d.ts +7 -1
  174. package/dist/tools/execute.d.ts.map +1 -1
  175. package/dist/tools/invoker.d.ts.map +1 -1
  176. package/dist/tools/remote.d.ts.map +1 -1
  177. package/llms/cli.2.txt +56 -1
  178. package/llms/cli.txt +68 -1
  179. package/llms/contract.txt +4 -1
  180. package/llms/files-packaging.txt +10 -0
  181. package/llms/process.2.txt +75 -1
  182. package/llms/process.txt +59 -25
  183. package/llms/server.4.txt +8 -4
  184. package/llms/server.5.txt +1 -0
  185. package/llms/stitchkit.2.txt +74 -0
  186. package/llms/stitchkit.txt +5 -5
  187. package/llms/tools.txt +1 -1
  188. package/llms/{upgrading-0.106.0-0.122.0.txt → upgrading-0.106.0-0.123.0.txt} +50 -3
  189. package/llms/upgrading.txt +1 -1
  190. package/llms-full.txt +379 -13
  191. package/llms.txt +7 -7
  192. package/package.json +1 -1
  193. package/dist/index-h2fhjqy7.js +0 -19
  194. package/dist/index-m5m9rmvt.js +0 -44
package/llms-full.txt CHANGED
@@ -65,9 +65,9 @@ own, recorded as an ADR.
65
65
  | `stitchkit/cli` | server | stable | `createCli` / `createCliInvoker` — runtime and strict NodeNext declarations without MCP SDK / `ai` |
66
66
  | `stitchkit/cli/publish` | build and release tooling (Bun or Node, POSIX) | evolving | `publishCli` — commit a verified CLI distribution and move its public manifest last; see [CLI guide](cli.md#publishing-a-complete-version) |
67
67
  | `stitchkit/remote` | browser **and** server | stable | peer-free `implementRemote` for thin HTTP proxy processes |
68
- | `stitchkit/process` | server (Bun or Node, POSIX) | evolving | finite native commands, byte sinks, bounded capture, process-group cancellation and opt-in Linux/Darwin owner-loss cleanup |
68
+ | `stitchkit/process` | server (Bun or Node, POSIX) | evolving | finite native commands, byte sinks, bounded capture, process-group cancellation and opt-in Linux/Darwin owner-loss cleanup; contract operations executed in one-call child processes |
69
69
  | `stitchkit/process/owner-loss` | bundled server entry (Bun, POSIX) | evolving | narrow bootstrap that keeps the process runner lazy while carrying its owner-loss guard in a single-file bundle |
70
- | `stitchkit/files/packaging` | build time (Bun or Node) | evolving | installed native asset graph, integrity, Bun packaging plugin and the artifact loader check; no runtime install policy |
70
+ | `stitchkit/files/packaging` | build time (Bun or Node) | evolving | installed native asset graph, integrity, Bun packaging plugin and the artifact loader check; a client bundled into one self-contained, hashed and verified file; no runtime install policy |
71
71
  | `stitchkit/files` | server (Bun or Node) | stable | peer-free managed local-file boundary |
72
72
  | `stitchkit/telegram` | server (Bun or Node) | evolving | peer-free Telegram platform primitives — Mini App `initData` verification, send-failure classification, one outbound budget per bot token with weighted albums and per-chat 429 pauses, resumable broadcasts, the operator channel with dedupe, a Bot API transport that separates a request that never left from an unknown outcome, local Bot API files, webhook ownership and durable webhook update intake |
73
73
  | `stitchkit/telegram/grammy` | server (Bun or Node) | evolving | the token's outbound budget as one grammY client transformer — a 429 that comes back as a response pauses its chat, and a cancelled wait is a request that never left |
@@ -84,13 +84,13 @@ own, recorded as an ADR.
84
84
  | `stitchkit/xlsx` | browser **and** server | evolving | a workbook from rows and typed columns, with no dependency; text never runs as a formula |
85
85
  | `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
86
86
  | `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
87
- | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 67 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
87
+ | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 68 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
88
88
  | `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, the managed-resource conformance kit and manual time for schedules and retries |
89
89
  | `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
90
90
  | `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
91
91
  | `stitchkit/react/keyboard` | browser | evolving | which part of a screen gets a key — ordered layers, one Escape per level, list zones with real focus; see [keyboard guide](keyboard.md) |
92
92
  | `stitchkit/react/keyboard/testing` | server (Bun or Node) | evolving | the keyboard layers and zones without a DOM — fakes for the window, elements and key events beside the two engines, to test the order of layers, `scope`/`inert` and the edges of a zone in milliseconds |
93
- | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 26 of the 67 minors since 0.56.2, most recently 0.121.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
93
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 26 of the 68 minors since 0.56.2, most recently 0.121.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
94
94
  | `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
95
95
  | `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults, deterministic race controls and the agent-store conformance kit |
96
96
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
@@ -102,7 +102,7 @@ own, recorded as an ADR.
102
102
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
103
103
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
104
104
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
105
- | `stitchkit/application` | browser + server | evolving<br>_redefined in 14 of the 67 minors since 0.56.2, most recently 0.110.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
105
+ | `stitchkit/application` | browser + server | evolving<br>_redefined in 14 of the 68 minors since 0.56.2, most recently 0.110.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
106
106
  | `stitchkit/application/grammy` | server | evolving | a grammY bot as application resources — configuration, then polling with updates admitted by batch — and the webhook adapter |
107
107
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
108
108
  | `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
@@ -970,7 +970,10 @@ Six intrinsic HTTP-only forms intersect an **implicit factory default** with HTT
970
970
  responses and `HEAD`. Under `['HTTP', 'CLI']` they receive `['HTTP']`; under
971
971
  `['CLI']` or `[]` they receive `[]`, not an implicitly reopened HTTP route.
972
972
  An **explicit** non-HTTP exposure on any of them is a definition error, not a
973
- silent downgrade. `safelistedBody` only changes HTTP parsing and remains
973
+ silent downgrade — with one exception: a streaming response may list `'CLI'`
974
+ (`['HTTP', 'CLI']`, or `['CLI']` for a feed only a terminal reads) and then runs
975
+ as a [command printing a line per item](./cli.md#a-contract-stream-as-a-command).
976
+ It is still never an MCP or agent tool. `safelistedBody` only changes HTTP parsing and remains
974
977
  multi-transport. A factory policy applies only to contracts authored through
975
978
  that factory, not to contracts another helper builds internally.
976
979
 
@@ -3491,6 +3494,75 @@ seventh constructor argument remains `ErrorOptions`, including `cause`; the opti
3491
3494
  retryability argument is eighth. Closed error/frame schemas must accept the new optional
3492
3495
  field before their producer declares it.
3493
3496
 
3497
+ ## Which contract does the server speak
3498
+
3499
+ A client is built from one version of a contract and a server from another
3500
+ whenever the two are deployed separately. Three facts then look alike unless
3501
+ they are told apart: the server has no such operation, the operation ran and
3502
+ found nothing, and the field is simply absent.
3503
+
3504
+ **An operation the server cannot route is `UNKNOWN_OPERATION`.** A server
3505
+ answers an unmatched route with `404 UNKNOWN_OPERATION`
3506
+ (`details: { method, path }`), never `NOT_FOUND` — that code is a handler
3507
+ saying its object does not exist. A contract client adds the operation it was
3508
+ calling, and reads the router's `405` for its own call the same way, because it
3509
+ always calls with the method its contract declares. The router's `405` carries
3510
+ `details: { method, path, allow }`; a `METHOD_NOT_ALLOWED` a handler throws
3511
+ itself has no `allow` list and stays `METHOD_NOT_ALLOWED`.
3512
+
3513
+ ```ts
3514
+ try {
3515
+ await api.search({ query })
3516
+ } catch (error) {
3517
+ if (error instanceof ApiError && error.code === 'UNKNOWN_OPERATION') {
3518
+ // error.details.operation === 'records.search' — this server predates it
3519
+ }
3520
+ }
3521
+ ```
3522
+
3523
+ **The fingerprint says so before the call.** `contractFingerprint(contracts)`
3524
+ (`stitchkit/contract`) is a synchronous digest of the operations HTTP carries —
3525
+ method, route and the JSON Schemas of params, input, output and stream item —
3526
+ with one digest per operation under `operations['prefix.key']`. It is the same
3527
+ value in every process and runtime. A server answers its own through an
3528
+ ordinary contract it mounts by choice, and a client reads both sides in one
3529
+ call, over HTTP or a Unix socket alike:
3530
+
3531
+ ```ts
3532
+ // server: the list to mount, with the identity of exactly that list
3533
+ import { withContractIdentity } from 'stitchkit/server'
3534
+ createServer({ services: withContractIdentity(services) })
3535
+
3536
+ // client
3537
+ import { readContractIdentity } from 'stitchkit'
3538
+ const { client, server } = await readContractIdentity(contracts, config)
3539
+ if (server === undefined) {
3540
+ // the server does not answer its identity: it predates it or did not mount it
3541
+ } else if (server.operations['records.search'] === undefined) {
3542
+ // this server has no `records.search`
3543
+ } else if (server.operations['records.get'] !== client.operations['records.get']) {
3544
+ // `records.get` has another schema there: a missing field may be "not sent yet"
3545
+ }
3546
+ ```
3547
+
3548
+ Both fingerprints arrive as a value; a difference never throws. Stitchkit
3549
+ computes the two identities and compares nothing — what a mismatch means is the
3550
+ application's decision. The client's fingerprint is computed locally and is not
3551
+ sent to the server. The identity operation is public and lists operation names,
3552
+ which is why it is mounted explicitly. Routing alone cannot always tell the
3553
+ cases apart — a new `GET /records/search` reaches an older server's
3554
+ `GET /records/:name` and comes back `NOT_FOUND` — so a client that must know
3555
+ reads the fingerprint rather than inferring from a refusal.
3556
+
3557
+ What the fingerprint covers is what constrains a value. An endpoint's `desc`, a
3558
+ field's `.describe()` or `.meta()` annotations, and the order of an object's
3559
+ fields do not move it; a type, a constraint, a required field, a method or a
3560
+ route does. It does not cover a stream's wire framing or format, `multipart`,
3561
+ `rawResponse` and `contentType`, and it cannot tell apart types JSON Schema has
3562
+ no word for — a `Date` and a `bigint` both read as "anything". Two different schemas registered
3563
+ under one `.meta({ id })` are refused by Zod's own conversion, here as in OpenAPI. The digest
3564
+ identifies, it does not authenticate: it is not a cryptographic hash.
3565
+
3494
3566
  ## Auth events
3495
3567
 
3496
3568
  The HTTP client emits events your app can react to globally:
@@ -4000,7 +4072,7 @@ presentation envelope. `invokeOrThrow` returns validated data and throws the
4000
4072
  runner's normalized `AppError` on failure, preserving an application error's
4001
4073
  code, status, message, details and hint without reconstructing it from the
4002
4074
  model-safe envelope. Unexpected throws remain scrubbed. An unknown name throws
4003
- `AppError('NOT_FOUND')` before the runner because there is no operation identity
4075
+ `AppError('UNKNOWN_OPERATION')` before the runner because there is no operation identity
4004
4076
  against which hooks could run. Duplicate and provider-invalid names fail when
4005
4077
  the invoker is created.
4006
4078
 
@@ -9240,6 +9312,65 @@ refused there and nothing is stopped after its leader exits. A command whose gro
9240
9312
  launcher supplied by a host) has no group to stop, and `descendants` has nothing to act on there.
9241
9313
 
9242
9314
 
9315
+ ## A contract operation in a process of its own
9316
+
9317
+ A handler that reads gigabytes holds the server's event loop while it reads, and every cheap
9318
+ operation on that server waits behind it. `createOperationProcesses` runs such an operation in a
9319
+ child process, one per call, on top of `runNativeCommand` and `streamNativeCommand`. What it
9320
+ returns are ordinary contract handlers, so the operation is routed, gated and validated like any
9321
+ other:
9322
+
9323
+ ```ts
9324
+ import { createOperationProcesses, runOperationProcess } from 'stitchkit/process'
9325
+
9326
+ // server
9327
+ const processes = createOperationProcesses({
9328
+ command: { executable: process.execPath, args: [cliPath, '__operation'], envPolicy: 'ambient' },
9329
+ maxConcurrent: 2, // a third call is refused, not queued
9330
+ maxResponseBytes: 8 * 1024 * 1024,
9331
+ timeoutMs: 60_000,
9332
+ })
9333
+ const service = implement(searchContract, {
9334
+ search: processes.unary(searchContract, 'search'),
9335
+ follow: processes.stream(searchContract, 'follow'),
9336
+ count: ({ input }) => countCheaply(input), // stays in process
9337
+ })
9338
+
9339
+ // the child: the same contract, the real handlers
9340
+ if (process.argv[2] === '__operation') {
9341
+ process.exitCode = await runOperationProcess([implement(searchContract, realHandlers)])
9342
+ }
9343
+ ```
9344
+
9345
+ - **The contract's schemas hold at the server.** The server validates the input before it starts
9346
+ a process — a bad call never costs one — and validates the answer against the output schema
9347
+ after; a stream item is validated against `stream.item` on its way out. The child receives the
9348
+ input the server already parsed and does not parse it again.
9349
+ - **The call and the answer cross as JSON.** A schema whose parsed value JSON cannot carry — a
9350
+ `Date`, a `Map`, a `bigint` — cannot be an input or an output of such an operation: the value
9351
+ arrives as its JSON form and the output check refuses it. A call may weigh up to 64 MiB; bytes
9352
+ an operation works on belong in a file it is told the path of.
9353
+ - **The call's end is the process's end.** The caller's abort (`ctx.signal`) stops the process
9354
+ group under the command's `stop` policy. A unary call also ends at `timeoutMs` with
9355
+ `REQUEST_TIMEOUT`. A stream's deadline is the contract's own `stream.lifetimeMs`: when it
9356
+ passes, the stream ends with `STREAM_LIFETIME_EXCEEDED` and the process is stopped; without it
9357
+ a stream lives until it ends or its reader leaves.
9358
+ - **The answer is bounded.** `maxResponseBytes` bounds the whole answer of a unary operation and
9359
+ each item of a stream; more is `OPERATION_RESPONSE_TOO_LARGE`. Only stdout counts: what the
9360
+ child writes to stderr does not spend the answer's budget.
9361
+ - **The count is bounded.** A call beyond `maxConcurrent` is refused with `503 OPERATION_BUSY`
9362
+ (`retryable: true`) at once. There is no queue: a queue without a bound is the same memory the
9363
+ process boundary was meant to protect. A call that cannot start gives its slot back.
9364
+ - **A stream delivers as it produces.** `processes.stream` yields each item when the child
9365
+ writes it.
9366
+
9367
+ A refusal the child's handler throws arrives as that same `AppError` — code, status, details,
9368
+ hint and retryability — for a unary call and in the middle of a stream alike. A process that
9369
+ dies, writes something that is not an answer, or cannot start is `OPERATION_PROCESS_FAILED`; the
9370
+ last 16 KiB of its stderr stay in the error's cause for the log and never reach the caller.
9371
+ `processes.active` reports the calls running now. The child reads one call from stdin and writes
9372
+ its answer to stdout, so its handlers must not print there.
9373
+
9243
9374
  ## Replacing hand-written wrappers
9244
9375
 
9245
9376
  An atomic-file wrapper becomes one `writeFileAtomic` call with the chosen options. A bounded
@@ -9602,6 +9733,45 @@ A missing or corrupt companion at runtime yields the native `unavailable` result
9602
9733
  stage and code from the observer; it never certifies a dead owner. Preserve the raw cause internally.
9603
9734
  The plugin does not parse consumer JS or establish a second runtime native implementation.
9604
9735
 
9736
+ ## A client as one self-contained file
9737
+
9738
+ A package that owns a server usually ships a typed client for it, and the client's consumers
9739
+ must not inherit the server's dependencies. `buildClientArtifact` (`stitchkit/files/packaging`)
9740
+ bundles one entry module into a single file, publishes its SHA-256 beside it and can prove an
9741
+ npm archive of it:
9742
+
9743
+ ```ts
9744
+ import { buildClientArtifact } from 'stitchkit/files/packaging'
9745
+
9746
+ const artifact = await buildClientArtifact({
9747
+ entry: 'src/api-client.ts',
9748
+ outFile: 'dist/api-client.js', // + dist/api-client.js.sha256
9749
+ forbiddenModules: ['chalk', '@acme/terminal-ui', 'src/terminal/', /providers\//],
9750
+ package: {
9751
+ name: '@acme/api-client', version: '1.4.0', outDir: 'dist',
9752
+ exports: ['createApiClient'],
9753
+ },
9754
+ })
9755
+ // artifact.file, artifact.sha256, artifact.bytes, artifact.modules, artifact.package.tarball
9756
+ ```
9757
+
9758
+ - **`forbiddenModules`** refuses a bundle whose module graph reaches a listed module with a
9759
+ `ForbiddenModuleError` naming the module and what imports it. A bare or scoped name
9760
+ (`'chalk'`, `'@scope/pkg'`) is a package: it is matched by the specifier that imports it, so a
9761
+ linked or workspace package is caught wherever it really lives, and by its installed path. A
9762
+ string with a slash matches a fragment of the project's own source path; a `RegExp` is tested
9763
+ against each module's absolute path. Nothing is left at `outFile` when the build is refused.
9764
+ - **`package`** packs the file as an npm archive, installs it into an empty directory, imports
9765
+ it by name and checks every name in `exports`. A client that still reaches for the building
9766
+ package's `node_modules` fails there, before it reaches a consumer; a failed proof removes the
9767
+ file and its hash again.
9768
+ - **`runtime`** is the platform the file is built for — `node` by default, which Bun also runs,
9769
+ or `bun` or `browser`; a module the platform does not have fails the build.
9770
+
9771
+ The build, the pack and the verification each run Bun as a bounded command: `timeoutMs`
9772
+ (default 120 s) is the deadline of each step, `signal` cancels, and `executable` names the Bun
9773
+ binary when it is not `bun` on `PATH`.
9774
+
9605
9775
  ## Qualification boundaries
9606
9776
 
9607
9777
  Native command cleanup owns the child before launch subscriptions are installed. Setup
@@ -10382,6 +10552,49 @@ result on stdout and reports `CLI_PRESENTATION_FAILED` separately, as described
10382
10552
  under [Aggregate views](#aggregate-views). Void native commands cannot declare
10383
10553
  either callback.
10384
10554
 
10555
+ ### The same policy for a managed command
10556
+
10557
+ A command that comes from a contract or a runtime tool declares the same two
10558
+ callbacks where the CLI is mounted — `CliConfig.results`, keyed by command name.
10559
+ A client and a server print nothing, so the policy belongs to the CLI, not to
10560
+ the contract. `defineCliResult` types both callbacks against the operation's
10561
+ output schema:
10562
+
10563
+ ```ts
10564
+ import { createCli, defineCliResult } from 'stitchkit/cli'
10565
+
10566
+ await createCli({
10567
+ name: 'myapp',
10568
+ version: '1.0.0',
10569
+ services: [recordService],
10570
+ results: {
10571
+ lookup: defineCliResult(LookupOutput, {
10572
+ present: ({ result, options }) =>
10573
+ options.json ? `${JSON.stringify(result)}\n` : `${result.location ?? 'not found'}\n`,
10574
+ exitCode: (result) => (result.found ? 0 : 1),
10575
+ }),
10576
+ },
10577
+ })
10578
+ ```
10579
+
10580
+ The guarantees are the native ones, because one function applies both: the
10581
+ callbacks run only after the operation's output is validated, `present` is
10582
+ called with and without `--json` — honouring it is the presenter's — and
10583
+ returns the exact stdout bytes, a throwing `present` reports
10584
+ `CLI_PRESENTATION_FAILED`, and a throwing or out-of-range `exitCode` becomes
10585
+ `INTERNAL_SERVER_ERROR`.
10586
+
10587
+ The schema handed to `defineCliResult` is the command's own output schema — the
10588
+ object the contract declares, or the item schema of a stream. A policy typed
10589
+ against any other schema is a startup error, as is a key naming no managed
10590
+ command or naming a native one.
10591
+
10592
+ The policy speaks for the command's own result only. An
10593
+ [aggregate view](#aggregate-views) (`--table`, `--count-by`, …) answers a
10594
+ different question, and after `--wait` the result is the polled command's, so
10595
+ both print without the policy. `createCliInvoker` applies `exitCode` — the
10596
+ in-process code equals the printed one — and never calls `present`.
10597
+
10385
10598
  `--version`, a selected native command and its command help run before
10386
10599
  `resolveAuth`, services, context or runtime-tool factories. Top-level help is
10387
10600
  also credential-free when managed surfaces are static. A dynamic factory must
@@ -10580,6 +10793,7 @@ same Zod schema an HTTP or MCP call does.
10580
10793
  | `--output-dir <dir>` | Download result media into a directory |
10581
10794
  | `--quiet` | Suppress non-essential stderr output |
10582
10795
  | `--dry-run` | Print the resolved call without executing |
10796
+ | `--schema` | One JSON line: the JSON Schema of a command's input and output |
10583
10797
  | `--help`, `-h` | Usage — top-level or per-command flag table |
10584
10798
  | `--help <text>` | List only the commands matching a substring |
10585
10799
  | `--count-by <field>` | Count records per distinct value — see [Aggregate views](#aggregate-views) |
@@ -10595,9 +10809,32 @@ newline-terminated JSON record on its respective stream; progress and CLI usage
10595
10809
  diagnostics remain ordinary stderr text. This keeps stdout pipeable and
10596
10810
  `2>/dev/null` clean. The process exit code carries the error class (`0` ok,
10597
10811
  `VALIDATION_ERROR → 1`, `UNAUTHORIZED → 2`, `FORBIDDEN → 3`, `NOT_FOUND → 4`,
10812
+ `UNKNOWN_OPERATION → 4`, `TIMEOUT → 7`, `STREAM_LIFETIME_EXCEEDED → 7`,
10598
10813
  `CLI_PRESENTATION_FAILED → 8`,
10599
10814
  …) — override per app with `exitCodes`.
10600
10815
 
10816
+ ### The schema of a command — `--schema`
10817
+
10818
+ `myapp <command> --schema` prints one line of JSON and exits `0` without
10819
+ validating arguments or calling anything:
10820
+
10821
+ ```json
10822
+ {"command":"lookup","input":{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]},"output":{"type":"object","properties":{"found":{"type":"boolean"}},"required":["found"]}}
10823
+ ```
10824
+
10825
+ `input` is the schema the command's arguments are read by — the one `--help`
10826
+ renders — and `output` is the JSON Schema of the result, both derived from the
10827
+ operation's Zod schemas and both in JSON Schema draft-07, so a script or another
10828
+ program can hold a command to its schema without a copy. A command without a
10829
+ declared output carries no `output`; a
10830
+ [stream command](#a-contract-stream-as-a-command) carries `item` instead.
10831
+ Managed and native commands answer alike. Like `--help`, the answer needs no
10832
+ identity when the managed surface is static; a surface built by a
10833
+ `services` or `runtimeTools` function resolves identity first to learn its
10834
+ commands. `schema` is a reserved option name: a command field called `schema`
10835
+ is a startup error for a contract command and makes a runtime-catalogue command
10836
+ unavailable, like every other reserved name.
10837
+
10601
10838
  ### A narrower question than "all of them"
10602
10839
 
10603
10840
  On a discovered surface `--help` is the only way to learn what exists, and that
@@ -10682,7 +10919,7 @@ reason in `details.message` on stderr; the default exit code is 1. `--json`
10682
10919
  compacts the error as usual; `exitCodes` and `errorHint` apply. The refusal precedes
10683
10920
  command argument parsing, stdin, context, hooks and the handler; `--dry-run` cannot
10684
10921
  turn it into success. A known unavailable `--wait` target returns the same refusal;
10685
- an unknown target keeps `NOT_FOUND`.
10922
+ an unknown target answers `UNKNOWN_OPERATION`.
10686
10923
 
10687
10924
  This isolates only reserved/global **field** conflicts in `runtimeTools`.
10688
10925
  Native commands and contract-defined `services` still fail at startup for those
@@ -11224,6 +11461,59 @@ your server. A channel is two documents at two addresses — which of them a bui
11224
11461
  consults is your decision, and `assertCliPublishable` is told about all of them
11225
11462
  rather than taught the shape of any.
11226
11463
 
11464
+ ## A contract stream as a command
11465
+
11466
+ A streaming endpoint whose `expose` lists `'CLI'` is a command that prints one
11467
+ line per item — `['HTTP', 'CLI']` for a stream both carry, `['CLI']` for a feed
11468
+ only a terminal reads:
11469
+
11470
+ ```ts
11471
+ watch: {
11472
+ method: 'POST', path: '/watch', desc: 'Follow record events',
11473
+ expose: ['HTTP', 'CLI'],
11474
+ input: z.object({ record: z.string() }),
11475
+ stream: { item: RecordEvent },
11476
+ }
11477
+ ```
11478
+
11479
+ ```sh
11480
+ myapp watch_record --record main | head -n 5
11481
+ ```
11482
+
11483
+ The command is named from the contract prefix and the endpoint key
11484
+ (`records.watch` → `watch_record`); a stream takes no `tool` group, so it has no
11485
+ `tool.name`. `optionAliases` and `positionals` apply as to any command.
11486
+
11487
+ The call opens through the same runner as every command — validation, the
11488
+ `lifecycle.beforeHandle` gate, hooks — and the handler's async iterable is read
11489
+ by the loop the HTTP response uses: each item is validated against
11490
+ `stream.item` (`STREAM_ITEM_INVALID` otherwise), a declared `terminal` item
11491
+ completes the command, and a stream that ends before it fails with
11492
+ `STREAM_TERMINAL_MISSING`. A stream proxied by `implementRemote` is a stream
11493
+ here too. Without a policy an item is one compact JSON line;
11494
+ `results[command].present` formats each item instead. A presenter that throws
11495
+ ends the feed with `CLI_PRESENTATION_FAILED` after the lines already printed.
11496
+ `exitCode` is refused for a stream at startup — a feed has no single result to
11497
+ classify.
11498
+
11499
+ How a feed ends:
11500
+
11501
+ | End | stdout / stderr | Exit |
11502
+ | --- | --- | --- |
11503
+ | the stream completes | every item | `0` |
11504
+ | stdout is closed (`head`, a pager that quit) | the items read so far | `0` |
11505
+ | `CliConfig.signal` aborts | nothing more, no error | `130` (`REQUEST_ABORTED`) |
11506
+ | `stream.lifetimeMs` passes | `STREAM_LIFETIME_EXCEEDED` on stderr | `7` |
11507
+ | the source or an item fails | the error on stderr | by its code |
11508
+
11509
+ In every case the handler's `ctx.signal` aborts and the source is asked to
11510
+ close. The command does not wait for a source that ignores its signal: the
11511
+ deadline and the cancellation are the command's, not the source's.
11512
+
11513
+ `--wait`, `--output-dir` and aggregate views do not apply to a stream and exit
11514
+ `2`; a stream command is not reachable through `createCliInvoker`, whose answer
11515
+ is one result per line.
11516
+
11227
11517
  ## One operation per line — `createCliInvoker`
11228
11518
 
11229
11519
  The most common way an agent drives a CLI is not one command: it is a stream —
@@ -11276,7 +11566,7 @@ await createCliInvoker({ name: 'myapp', services, commands: [describe] })
11276
11566
  ```
11277
11567
 
11278
11568
  A command that declares `output?: never` prints itself and returns nothing, so
11279
- there is no result to hand back; it answers `NOT_FOUND` and stays on the command
11569
+ there is no result to hand back; it answers `UNKNOWN_OPERATION` and stays on the command
11280
11570
  line, where stdout exists. Your stream command is one of these, which is why a
11281
11571
  stream still cannot invoke itself.
11282
11572
 
@@ -13559,7 +13849,8 @@ error round-trips without adding correlation data to the response body.
13559
13849
 
13560
13850
  A `code` is a free string — your app codes (`BOT_NOT_FOUND`, …) are yours and the
13561
13851
  core never models them (ADR 0002). But stitchkit itself emits a set of its own:
13562
- `BAD_REQUEST`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `METHOD_NOT_ALLOWED`,
13852
+ `BAD_REQUEST`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `UNKNOWN_OPERATION`,
13853
+ `METHOD_NOT_ALLOWED`,
13563
13854
  `CONFLICT`, `RATE_LIMITED`, `REQUEST_ABORTED`, `REQUEST_TIMEOUT`,
13564
13855
  `VALIDATION_ERROR`, `FILE_INVALID_PATH`,
13565
13856
  `FILE_OUTSIDE_ROOT`, `FILE_NOT_FOUND`, `FILE_NOT_REGULAR`,
@@ -13569,7 +13860,8 @@ core never models them (ADR 0002). But stitchkit itself emits a set of its own:
13569
13860
  `WAIT_FAILED`, `CLI_COMMAND_UNAVAILABLE`, `CLI_PRESENTATION_FAILED`, `DOWNLOAD_NOT_FOUND`, `VIEW_HTTP_ERROR`,
13570
13861
  `CONNECTION_TIMEOUT`, `CONNECTION_RESPONSE_TOO_LARGE`,
13571
13862
  `CONNECTION_REQUEST_FAILED`, `UPSTREAM_TOOL_ERROR`,
13572
- `OPERATION_NOT_SUCCEEDED`, `STREAM_ITEM_INVALID`, `STREAM_FRAME_TOO_LARGE`,
13863
+ `OPERATION_NOT_SUCCEEDED`, `OPERATION_BUSY`, `OPERATION_RESPONSE_TOO_LARGE`,
13864
+ `OPERATION_PROCESS_FAILED`, `STREAM_ITEM_INVALID`, `STREAM_FRAME_TOO_LARGE`,
13573
13865
  `STREAM_TERMINAL_MISSING`, `STREAM_LIFETIME_EXCEEDED`,
13574
13866
  `GRAMMY_WEBHOOK_NOT_ACCEPTING`, `SANDBOX_UNAVAILABLE`, `SANDBOX_INSUFFICIENT`,
13575
13867
  `SPILL_REFERENCE_UNKNOWN`, `INTERNAL_SERVER_ERROR` — a set that grows in
@@ -13597,7 +13889,8 @@ envelope is a published contract, at the cost of an edit on those releases:
13597
13889
  // a map written exhaustively stops compiling on every addition. Codes you leave
13598
13890
  // out fall through to `unmappedCode`.
13599
13891
  const STITCH_TO_APP: Partial<Record<StitchErrorCode, AppCode>> = {
13600
- NOT_FOUND: 'NOT_FOUND', METHOD_NOT_ALLOWED: 'METHOD_NOT_ALLOWED',
13892
+ NOT_FOUND: 'NOT_FOUND', UNKNOWN_OPERATION: 'NOT_FOUND',
13893
+ METHOD_NOT_ALLOWED: 'METHOD_NOT_ALLOWED',
13601
13894
  BAD_REQUEST: 'VALIDATION_ERROR', VALIDATION_ERROR: 'VALIDATION_ERROR',
13602
13895
  UNAUTHORIZED: 'UNAUTHORIZED', FORBIDDEN: 'FORBIDDEN', CONFLICT: 'CONFLICT',
13603
13896
  RATE_LIMITED: 'RATE_LIMITED', INTERNAL_SERVER_ERROR: 'INTERNAL_SERVER_ERROR',
@@ -13723,7 +14016,8 @@ const onError = createErrorHook({
13723
14016
  codeMap: {
13724
14017
  BAD_REQUEST: 'bad_request', VALIDATION_ERROR: 'bad_request',
13725
14018
  UNAUTHORIZED: 'unauthenticated', FORBIDDEN: 'forbidden',
13726
- NOT_FOUND: 'not_found', METHOD_NOT_ALLOWED: 'not_found',
14019
+ NOT_FOUND: 'not_found', UNKNOWN_OPERATION: 'not_found',
14020
+ METHOD_NOT_ALLOWED: 'not_found',
13727
14021
  CONFLICT: 'conflict', RATE_LIMITED: 'rate_limited',
13728
14022
  INTERNAL_SERVER_ERROR: 'internal',
13729
14023
  },
@@ -17322,6 +17616,53 @@ charts, no merged cells and no streaming: an application that needs them takes a
17322
17616
 
17323
17617
  # Upgrading stitchkit
17324
17618
 
17619
+ ## Released migration: 0.123.0
17620
+
17621
+ ### unknown-operation
17622
+
17623
+ **Who must act:** HTTP callers and `onError` code maps that read `NOT_FOUND` to mean "no such
17624
+ route", callers of `createCliInvoker`, `--wait` and `createToolInvoker` that read `NOT_FOUND` for
17625
+ an unknown command or tool name, and contract-client callers that branch on the router's
17626
+ `METHOD_NOT_ALLOWED`.
17627
+
17628
+ A call no surface can route is answered `UNKNOWN_OPERATION`; `NOT_FOUND` is left to handlers.
17629
+
17630
+ - The server answers an unmatched route `404 UNKNOWN_OPERATION` with `details: { method, path }`.
17631
+ - A contract client reports both routing refusals of its own call — that `404` and the router's
17632
+ `405` — as `UNKNOWN_OPERATION` with `details.operation`. A `405` thrown by a handler is
17633
+ unchanged.
17634
+ - `createCliInvoker().invoke`, an unknown `--wait` target and `createToolInvoker` answer
17635
+ `UNKNOWN_OPERATION` for a name they do not have. The CLI exit code for it is `4`, as it was for
17636
+ `NOT_FOUND`.
17637
+
17638
+ ```ts
17639
+ // before
17640
+ if (error.code === 'NOT_FOUND') showMissingPage()
17641
+
17642
+ // after
17643
+ if (error.code === 'UNKNOWN_OPERATION') showMissingPage() // no such route, command or tool
17644
+ if (error.code === 'NOT_FOUND') showMissingRecord() // the handler's own answer
17645
+ ```
17646
+
17647
+ A `createErrorHook` `codeMap` that spells `NOT_FOUND` for clients adds
17648
+ `UNKNOWN_OPERATION: 'not_found'` to keep the previous wire code.
17649
+
17650
+ ### cli-schema-option
17651
+
17652
+ **Who must act:** applications whose CLI exposes a command with an input field named `schema`.
17653
+
17654
+ `--schema` is a framework option that prints a command's JSON Schema, so a command field of that
17655
+ name can no longer receive a value. A contract command declaring it fails at startup; a
17656
+ runtime-catalogue command becomes unavailable with the reason in help. Rename the field.
17657
+
17658
+ ```ts
17659
+ // before
17660
+ input: z.object({ schema: z.string() }) // app export --schema public
17661
+
17662
+ // after
17663
+ input: z.object({ schemaName: z.string() }) // app export --schema-name public
17664
+ ```
17665
+
17325
17666
  ## Released migration: 0.122.0
17326
17667
 
17327
17668
  ### read-view-refusal
@@ -22683,6 +23024,8 @@ The browser-and-server entrypoint. Re-exports everything from
22683
23024
  | `RegistryScope` | _type_ | contract-scope union inferred from a scoped client registry value |
22684
23025
  | `createUrlBuilder` | function | build synchronous browser-native URLs for all HTTP endpoints; body methods accept URL-bound args only — [guide](../guide/client.md#contract-url-builders) |
22685
23026
  | `createUrlBuilders` | function | build one exact URL builder per contract in a registry |
23027
+ | `readContractIdentity` | function | read the server's contract fingerprint in one call and return it beside the client's own — [guide](../guide/client.md#which-contract-does-the-server-speak) |
23028
+ | `ContractIdentity` | _type_ | `{ client, server }` fingerprints; `server` is `undefined` when the server answers no identity |
22686
23029
  | `UrlBuilderConfig` | _type_ | explicit `{ baseUrl }` source for a URL builder |
22687
23030
  | `ClientConfig` | _type_ | config for `createClient`'s bare-fetch mode (2nd arg, no `HttpClient`) |
22688
23031
  | `ClientFetch` | _type_ | injectable Fetch-compatible transport used by framework and application-owned adapters |
@@ -22735,6 +23078,9 @@ The browser-and-server entrypoint. Re-exports everything from
22735
23078
  | `StreamLineLimitError` / `StreamTruncatedLineError` | class | a line past `maxLineBytes` (`limitBytes`, `lineBytes`; a `RangeError`) and a source ended inside a line under `require-newline` (`lineBytes`; a `SyntaxError`) |
22736
23079
  | `resumableIterator` | function | re-open a long-lived stream from the last delivered cursor, with jittered backoff, a caller-owned terminal item and prompt abort — [guide](../guide/client.md#resumable-streams) |
22737
23080
  | `createBackoff` | function | exponential backoff with subtractive jitter as a value: `next()` / `reset()` |
23081
+ | `contractFingerprint` | function | synchronous digest of the operations HTTP carries, whole and per `prefix.key` — [guide](../guide/client.md#which-contract-does-the-server-speak) |
23082
+ | `ContractFingerprintSchema` / `ContractFingerprint` | schema / _type_ | `{ fingerprint, operations }` — one digest for the set and one per operation |
23083
+ | `contractIdentityContract` | contract | the operation a server answers its fingerprint with; mount it with `withContractIdentity` |
22738
23084
  | `ContractStreamFrameSchema` / `ContractStreamFrame` | schema / _type_ | default on-the-wire `data` / safe `error` / `end` envelope of a contract-first stream |
22739
23085
  | `ContractStreamFraming` / `ContractStreamCompletion` | _types_ | opt-in item-vs-envelope framing and terminal-vs-stream-end completion policies |
22740
23086
  | `StreamFinalLinePolicy` | _type_ | permissive or newline-required final NDJSON line policy |
@@ -23043,6 +23389,7 @@ Also re-exports the error helpers from `stitchkit/contract`.
23043
23389
  | `createServer` | function | build the router and start `Bun.serve` — [guide](../guide/server.md#createserver) |
23044
23390
  | `createHandler` | function | the router as a bare `(req) => Response` — [guide](../guide/server.md#createserver) |
23045
23391
  | `implement` | function | bind a contract to typed handlers — [guide](../guide/server.md#implement) |
23392
+ | `withContractIdentity` | function | the given services plus the one answering `contractIdentityContract` with their fingerprint — the list to mount — [guide](../guide/client.md#which-contract-does-the-server-speak) |
23046
23393
  | `createImplement` | function | fix the handler context type once |
23047
23394
  | `createScopedImplement` | function | fix one scope→context map, then type every handler by its endpoint's effective scope; `.declare(contract)(handlers)` types without binding for the registry path, `.stream(scope, …)` covers streaming multipart — [guide](../guide/server.md#per-scope-handler-context--createscopedimplement) |
23048
23395
  | `ScopedHandlers` | _type_ | handler map typed per endpoint by its effective scope |
@@ -25165,6 +25512,7 @@ SDK nor the `ai` peer.
25165
25512
  |--------|------|---------|
25166
25513
  | `createCli` | function | build and run a CLI from contracts; a runtime catalogue field conflicting with reserved/global argv options disables only that entry, retained in help with its reason; invocation returns `CLI_COMMAND_UNAVAILABLE` before side effects — [guide](../guide/cli.md#when-one-runtime-command-cannot-be-represented-in-argv) |
25167
25514
  | `defineCliCommand` | function | define one Zod-typed CLI-only executable command with optional validated-result presentation/exit policy |
25515
+ | `defineCliResult` | function | type a managed command's `present` / `exitCode` policy against its output schema, for `CliConfig.results` — [guide](../guide/cli.md#the-same-policy-for-a-managed-command) |
25168
25516
  | `parseCliArgs` | function | argv → typed tool args against a schema; inline `--field:=JSON` assigns a whole JSON value, including null, while ordinary string flags remain lexical; named array options together with bare positionals require an explicit `positionals` policy (advanced) |
25169
25517
  | `routeCliArgv` | function | select the command out of argv without duplicating the global-option grammar (advanced) |
25170
25518
  | `extractCliGlobalOptions` | function | lift the application's own global options out of argv before routing (advanced) |
@@ -25215,6 +25563,7 @@ SDK nor the `ai` peer.
25215
25563
  | `CliCommandDefinitionBase` | _type_ | native command name, description and input schema |
25216
25564
  | `CliCommandDefinitionWithOutput` | _type_ | native command with validated declared output and typed optional `present` / successful `exitCode` callbacks |
25217
25565
  | `CliCommandDefinitionWithoutOutput` | _type_ | native void command without an output contract |
25566
+ | `CliResultPolicy` | _type_ | `present` / successful `exitCode` callbacks of one command, typed against its validated result; `CliConfig.results` holds one per managed command |
25218
25567
  | `CliCommandContext` | _type_ | parsed input, framework run options, the application's `globals` and stdout/stderr writers |
25219
25568
  | `CliRunOptions` | _type_ | parsed global flags (`--json` compacts success/error records, `--wait`, …) |
25220
25569
  | `ParsedCliArgs` | _type_ | result of `parseCliArgs` |
@@ -25387,6 +25736,13 @@ Native POSIX Bun/Node one-shot command execution. See [native IO](../guide/nativ
25387
25736
  | Export | Kind | Purpose |
25388
25737
  |---|---|---|
25389
25738
  | `runNativeCommand` | function | caller-lifetime streaming or explicitly bounded binary capture |
25739
+ | `createOperationProcesses` | function | run contract operations in one-call child processes: `unary` / `stream` return contract handlers bounded by `maxConcurrent`, `maxResponseBytes` and `timeoutMs` — [guide](../guide/native-io.md#a-contract-operation-in-a-process-of-its-own) |
25740
+ | `runOperationProcess` | function | the child side: read one call from stdin, run it against `implement(contract, handlers)` services, write the answer |
25741
+ | `OperationProcessesConfig` | _type_ | the child command and the three bounds of `createOperationProcesses` |
25742
+ | `OperationProcesses` | _type_ | `unary(contract, key)`, `stream(contract, key)` and the `active` count |
25743
+ | `OperationCallContext` | _type_ | what a process-operation handler reads of its call: `params`, `input` and the caller's `signal` |
25744
+ | `OperationProcessService` | _type_ | what `runOperationProcess` reads of a service; the result of `implement` fits |
25745
+ | `OperationProcessIo` | _type_ | injectable call source, writer and stop signal of `runOperationProcess` |
25390
25746
  | `streamNativeCommand` | function | starts a command under the same group ownership, stop policy and bounds as `runNativeCommand` and returns `stdout` / `stderr` as `ReadableStream<Uint8Array>`, `result` and `stop()`; a slow reader slows the child, a cancelled stream drops its bytes, a failed command errors both streams |
25391
25747
  | `NativeCommandStream` | type | what `streamNativeCommand` returns: `stdout`, `stderr`, `result` (its buffers are empty) and `stop()` |
25392
25748
  | `NativeCommandStreamOptions` | type | `NativeCommandOptions` without `capture`, `onOutput` and `stdio`, which the streams own |
@@ -25446,6 +25802,16 @@ without splitting, preserve every output, and write each asset's `bytes` to its
25446
25802
  This evolving entrypoint is a build dependency; runtime imports do not reach it.
25447
25803
  The detailed recipe is in [native IO](../guide/native-io.md#public-native-packaging).
25448
25804
 
25805
+ It also owns the build of a client into one self-contained file:
25806
+
25807
+ | Export | Kind | Summary |
25808
+ |--------|------|---------|
25809
+ | `buildClientArtifact` | function | bundle a client entry into one self-contained file with its SHA-256, refuse forbidden modules in the graph, optionally pack and verify an npm archive — [guide](../guide/native-io.md#a-client-as-one-self-contained-file) |
25810
+ | `ClientArtifactOptions` | _type_ | entry, output file, runtime, forbidden modules and the optional package to pack and verify |
25811
+ | `ClientArtifact` | _type_ | the built file, its SHA-256 and size, the bundled modules and the verified archive |
25812
+ | `ForbiddenModule` | _type_ | a package name, a source path fragment or a `RegExp` over module paths |
25813
+ | `ForbiddenModuleError` | class | the refusal of a bundle that reaches a forbidden module: `module` and `importedBy` |
25814
+
25449
25815
  ## `stitchkit/http`
25450
25816
 
25451
25817
  An HTTP-only entrypoint for browsers, React Native/Hermes and servers. It exports the same `createClient`, `createClients`, `createHttpClient`, URL builders, client configuration types and `ApiError` as the root, without loading its optional Socket.IO graph. Import shared contracts from `stitchkit/contract`.
package/llms.txt CHANGED
@@ -9,7 +9,7 @@ Build with stitchkit: define a contract once, then `implement` it and serve it (
9
9
  Load the slice for each entrypoint your code imports — `import … from 'stitchkit/server'` → `llms/server.txt`; the root `stitchkit` import → `llms/stitchkit.txt`. A slice holds that entrypoint's API reference section and the guide pages whose home it is; every guide lives in exactly one slice, and a slice that needs another's guide names it under "Also read". Each file is at most 50.0 KB; a longer one continues in numbered parts, each naming the next at its top. Moving across versions: load `llms/upgrading.txt` and the range slices it lists between your version and the target. `llms-full.txt` inlines everything; it is far larger than a context window, so do not read it whole.
10
10
 
11
11
  ## Entrypoint slices
12
- - **stitchkit**: [llms/stitchkit.txt](llms/stitchkit.txt) 44.6 KB → [llms/stitchkit.2.txt](llms/stitchkit.2.txt) 43.1 KB
12
+ - **stitchkit**: [llms/stitchkit.txt](llms/stitchkit.txt) 44.8 KB → [llms/stitchkit.2.txt](llms/stitchkit.2.txt) 47.4 KB
13
13
  - **stitchkit/live**: [llms/live.txt](llms/live.txt) 21.2 KB
14
14
  - **stitchkit/react**: [llms/react.txt](llms/react.txt) 8.7 KB
15
15
  - **stitchkit/react/keyboard**: [llms/react-keyboard.txt](llms/react-keyboard.txt) 22.7 KB
@@ -21,12 +21,12 @@ Load the slice for each entrypoint your code imports — `import … from 'stitc
21
21
  - **stitchkit/tools/contract**: [llms/tools-contract.txt](llms/tools-contract.txt) 0.2 KB
22
22
  - **stitchkit/tools/invoker**: [llms/tools-invoker.txt](llms/tools-invoker.txt) 0.2 KB
23
23
  - **stitchkit/tools/connections**: [llms/tools-connections.txt](llms/tools-connections.txt) 3.9 KB
24
- - **stitchkit/cli**: [llms/cli.txt](llms/cli.txt) 37.8 KB → [llms/cli.2.txt](llms/cli.2.txt) 36.4 KB
24
+ - **stitchkit/cli**: [llms/cli.txt](llms/cli.txt) 40.9 KB → [llms/cli.2.txt](llms/cli.2.txt) 39.0 KB
25
25
  - **stitchkit/cli/publish**: [llms/cli-publish.txt](llms/cli-publish.txt) 1.5 KB
26
26
  - **stitchkit/remote**: [llms/remote.txt](llms/remote.txt) 1.0 KB
27
- - **stitchkit/contract**: [llms/contract.txt](llms/contract.txt) 33.8 KB
27
+ - **stitchkit/contract**: [llms/contract.txt](llms/contract.txt) 34.1 KB
28
28
  - **stitchkit/primitives**: [llms/primitives.txt](llms/primitives.txt) 13.2 KB
29
- - **stitchkit/server**: [llms/server.txt](llms/server.txt) 44.2 KB → [llms/server.2.txt](llms/server.2.txt) 23.1 KB → [llms/server.3.txt](llms/server.3.txt) 47.0 KB → [llms/server.4.txt](llms/server.4.txt) 38.6 KB → [llms/server.5.txt](llms/server.5.txt) 23.5 KB
29
+ - **stitchkit/server**: [llms/server.txt](llms/server.txt) 44.2 KB → [llms/server.2.txt](llms/server.2.txt) 23.1 KB → [llms/server.3.txt](llms/server.3.txt) 47.0 KB → [llms/server.4.txt](llms/server.4.txt) 38.7 KB → [llms/server.5.txt](llms/server.5.txt) 23.7 KB
30
30
  - **stitchkit/observability**: [llms/observability.txt](llms/observability.txt) 46.9 KB
31
31
  - **stitchkit/agent-runtime**: [llms/agent-runtime.txt](llms/agent-runtime.txt) 5.7 KB → [llms/agent-runtime.2.txt](llms/agent-runtime.2.txt) 44.1 KB → [llms/agent-runtime.3.txt](llms/agent-runtime.3.txt) 42.5 KB → [llms/agent-runtime.4.txt](llms/agent-runtime.4.txt) 45.2 KB → [llms/agent-runtime.5.txt](llms/agent-runtime.5.txt) 11.8 KB
32
32
  - **stitchkit/agent-runtime/testing**: [llms/agent-runtime-testing.txt](llms/agent-runtime-testing.txt) 2.6 KB
@@ -64,15 +64,15 @@ Load the slice for each entrypoint your code imports — `import … from 'stitc
64
64
  - **stitchkit/node**: [llms/node.txt](llms/node.txt) 3.1 KB
65
65
  - **stitchkit/agent-runtime/react**: [llms/agent-runtime-react.txt](llms/agent-runtime-react.txt) 1.0 KB
66
66
  - **stitchkit/agent-runtime/realtime**: [llms/agent-runtime-realtime.txt](llms/agent-runtime-realtime.txt) 2.6 KB
67
- - **stitchkit/process**: [llms/process.txt](llms/process.txt) 44.7 KB → [llms/process.2.txt](llms/process.2.txt) 4.3 KB
67
+ - **stitchkit/process**: [llms/process.txt](llms/process.txt) 46.8 KB → [llms/process.2.txt](llms/process.2.txt) 9.1 KB
68
68
  - **stitchkit/process/owner-loss**: [llms/process-owner-loss.txt](llms/process-owner-loss.txt) 0.9 KB
69
- - **stitchkit/files/packaging**: [llms/files-packaging.txt](llms/files-packaging.txt) 3.5 KB
69
+ - **stitchkit/files/packaging**: [llms/files-packaging.txt](llms/files-packaging.txt) 4.3 KB
70
70
  - **stitchkit/http**: [llms/http.txt](llms/http.txt) 0.9 KB
71
71
  - **stitchkit/realtime**: [llms/realtime.txt](llms/realtime.txt) 1.4 KB
72
72
 
73
73
  ## Upgrading slices
74
74
  - **Upgrading — how to move across versions**: [llms/upgrading.txt](llms/upgrading.txt) 12.7 KB
75
- - **Upgrading 0.106.0 → 0.122.0**: [llms/upgrading-0.106.0-0.122.0.txt](llms/upgrading-0.106.0-0.122.0.txt) 34.4 KB
75
+ - **Upgrading 0.106.0 → 0.123.0**: [llms/upgrading-0.106.0-0.123.0.txt](llms/upgrading-0.106.0-0.123.0.txt) 36.3 KB
76
76
  - **Upgrading 0.96.0 → 0.105.0**: [llms/upgrading-0.96.0-0.105.0.txt](llms/upgrading-0.96.0-0.105.0.txt) 45.8 KB
77
77
  - **Upgrading 0.69.0 → 0.95.0**: [llms/upgrading-0.69.0-0.95.0.txt](llms/upgrading-0.69.0-0.95.0.txt) 46.6 KB
78
78
  - **Upgrading 0.55.0 → 0.68.0**: [llms/upgrading-0.55.0-0.68.0.txt](llms/upgrading-0.55.0-0.68.0.txt) 47.0 KB
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.122.2",
3
+ "version": "0.123.0",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",