stitchkit 0.108.4 → 0.109.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 (64) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/browser/cancellation.d.ts +4 -5
  3. package/dist/browser/cancellation.d.ts.map +1 -1
  4. package/dist/browser/client-executors.d.ts.map +1 -1
  5. package/dist/browser/http.d.ts.map +1 -1
  6. package/dist/entrypoints/agent-runtime/harness.js +4 -3
  7. package/dist/entrypoints/agent-runtime.js +7 -6
  8. package/dist/entrypoints/cli.js +6 -5
  9. package/dist/entrypoints/index.js +2 -2
  10. package/dist/entrypoints/react.js +1 -1
  11. package/dist/entrypoints/remote.js +2 -2
  12. package/dist/entrypoints/telegram.d.ts +2 -2
  13. package/dist/entrypoints/telegram.d.ts.map +1 -1
  14. package/dist/entrypoints/telegram.js +868 -137
  15. package/dist/entrypoints/testing.js +2 -2
  16. package/dist/entrypoints/tools/connections.js +6 -1
  17. package/dist/entrypoints/tools/contract.js +2 -1
  18. package/dist/entrypoints/tools/invoker.js +5 -4
  19. package/dist/entrypoints/tools/mcp.js +6 -5
  20. package/dist/entrypoints/tools.js +9 -8
  21. package/dist/{index-7jvwrjqa.js → index-00v1qz76.js} +20 -17
  22. package/dist/{index-cx2nh595.js → index-3wm2xp2a.js} +1 -1
  23. package/dist/{index-fgq4w9bf.js → index-5cw390s6.js} +100 -20
  24. package/dist/{index-h7qms8f9.js → index-68y3n9j3.js} +3 -3
  25. package/dist/{index-6yxhe58g.js → index-cykxpxz5.js} +7 -0
  26. package/dist/{index-p5kx5vqd.js → index-e32vy8jw.js} +22 -11
  27. package/dist/{index-dm2fv45z.js → index-k4v2kq4q.js} +1 -1
  28. package/dist/{index-6y9t2fwz.js → index-kvvpw1kq.js} +2 -2
  29. package/dist/{index-2s6fpehp.js → index-ryv32myz.js} +1 -1
  30. package/dist/index-t5sjh517.js +100 -0
  31. package/dist/{index-yz8x0hbx.js → index-tpk71wqn.js} +1 -1
  32. package/dist/{index-mep4sb48.js → index-x5xs71dw.js} +3 -3
  33. package/dist/telegram/update-intake-work.d.ts +39 -0
  34. package/dist/telegram/update-intake-work.d.ts.map +1 -0
  35. package/dist/telegram/update-intake.d.ts +47 -9
  36. package/dist/telegram/update-intake.d.ts.map +1 -1
  37. package/dist/telegram/update-store-check.d.ts.map +1 -1
  38. package/dist/telegram/update-store-postgres.d.ts +2 -2
  39. package/dist/telegram/update-store-postgres.d.ts.map +1 -1
  40. package/dist/telegram/update-store-sqlite.d.ts +2 -2
  41. package/dist/telegram/update-store-sqlite.d.ts.map +1 -1
  42. package/dist/telegram/update-store.d.ts +52 -3
  43. package/dist/telegram/update-store.d.ts.map +1 -1
  44. package/dist/tools/cli/args-fields.d.ts +7 -1
  45. package/dist/tools/cli/args-fields.d.ts.map +1 -1
  46. package/dist/tools/cli/args.d.ts.map +1 -1
  47. package/dist/tools/connections/runtime.d.ts.map +1 -1
  48. package/dist/tools/schema/coerce.d.ts.map +1 -1
  49. package/dist/tools/schema/json-schema-origin.d.ts +14 -0
  50. package/dist/tools/schema/json-schema-origin.d.ts.map +1 -0
  51. package/llms/cli.2.txt +1 -1
  52. package/llms/cli.txt +12 -0
  53. package/llms/stitchkit.txt +10 -3
  54. package/llms/telegram.txt +59 -8
  55. package/llms/tools.3.txt +1 -1
  56. package/llms/{upgrading-0.100.0-0.108.0.txt → upgrading-0.101.0-0.109.0.txt} +70 -72
  57. package/llms/upgrading-0.37.0.txt +176 -0
  58. package/llms/{upgrading-0.37.0-0.58.0.txt → upgrading-0.44.0-0.60.0.txt} +229 -169
  59. package/llms/{upgrading-0.59.0-0.71.0.txt → upgrading-0.61.0-0.73.0.txt} +122 -229
  60. package/llms/{upgrading-0.72.0-0.99.0.txt → upgrading-0.74.0-0.100.0.txt} +72 -122
  61. package/llms/upgrading.txt +5 -4
  62. package/llms-full.txt +150 -13
  63. package/llms.txt +9 -8
  64. package/package.json +1 -1
package/llms/telegram.txt CHANGED
@@ -337,9 +337,10 @@ import { Database } from 'bun:sqlite'
337
337
  import type { Update } from 'grammy/types'
338
338
 
339
339
  const webhook = { token: env.BOT_TOKEN, url: env.WEBHOOK_URL, secret: env.WEBHOOK_SECRET }
340
+ const updateStore = sqliteTelegramUpdateStore({ database: new Database(env.UPDATES_DB) })
340
341
 
341
342
  const intake = createTelegramUpdateIntake<Update>({
342
- store: sqliteTelegramUpdateStore({ database: new Database(env.UPDATES_DB) }),
343
+ store: updateStore,
343
344
  handle: (update) => bot.handleUpdate(update),
344
345
  onFailure: (failure) => logger.warn('Update failed', { ...failure }),
345
346
  })
@@ -383,13 +384,55 @@ fix (a refused message, a blocked user) is abandoned; `maxAttempts` (5) ends
383
384
  the rest. Delivery is at least once: a handler that finished but could not be
384
385
  recorded as finished runs again.
385
386
 
387
+ Use `handleAttempt` when the handler needs the immutable attempt fence and an
388
+ ownership-loss signal. A signal asks slow work to stop; the fence is what
389
+ authorizes a domain write. Check it through a transaction-bound reference store
390
+ inside the same database transaction as that write:
391
+
392
+ ```ts
393
+ handleAttempt: async (update, { fence, ownerLost }) => {
394
+ ownerLost.throwIfAborted()
395
+ await database.transaction(async (tx) => {
396
+ const transactionStore = postgresTelegramUpdateStore({
397
+ query: (text, parameters) => tx.query(text, parameters),
398
+ createTable: false,
399
+ })
400
+ if (!(await transactionStore.owns(fence, Date.now()))) return
401
+ await applyDomainTransition(tx, update)
402
+ })
403
+ }
404
+ ```
405
+
406
+ The fence contains the claim's own `{ updateId, attempt, claimId }`. The opaque
407
+ `claimId` is persisted and never reused, so an old handler cannot adopt either
408
+ a newer attempt or a pruned and recreated `updateId`. Existing `handle(update)`
409
+ remains unchanged, including direct functions whose own optional second
410
+ argument has a different meaning.
411
+
412
+ For a final obligation, configure `handleExhaustion`. The reference store keeps
413
+ the terminal record unacknowledged until this handler resolves, including
414
+ across restart; a throw leaves it for the next sweep. Delivery is at least once,
415
+ so use the complete `{ updateId, attempt, claimId }` as an idempotency key in
416
+ the consumer database:
417
+
418
+ ```ts
419
+ handleExhaustion: ({ updateId, attempt, claimId, update, error }) =>
420
+ recordFinalFailureOnce({ key: `telegram:${updateId}:${attempt}:${claimId}`, update, error })
421
+ ```
422
+
423
+ This reuses the update row and table. Without `handleExhaustion`, terminal
424
+ abandonment retains its previous behavior.
425
+
386
426
  With grammY the handler is `bot.handleUpdate`, which wraps a middleware's error
387
427
  in a `BotError`; the intake unwraps it, so `retry`, `onFailure` and the error
388
428
  stored with the update are what the middleware threw — a `retry` that returns
389
429
  `false` for the bot's own terminal error sees that error.
390
430
 
391
- The store is an interface of six atomic steps (`TelegramUpdateStore`), so the
392
- bot's own database holds it:
431
+ The base store remains the six atomic steps of `TelegramUpdateStore`. A
432
+ `TelegramUpdateFencedStore` adds `claimOwned`, `renewOwned`, `owns` and
433
+ `settleOwned`; `TelegramUpdateDurableStore` adds the final-obligation methods.
434
+ Reference stores implement both, so the bot's own database holds the fence and
435
+ final obligation without a second queue:
393
436
 
394
437
  | Store | Takes |
395
438
  |---|---|
@@ -416,7 +459,12 @@ postgresTelegramUpdateStore({
416
459
  Each rule is one conditional statement, so a claim is atomic across every
417
460
  process sharing the table. By default the table is created on first use; where
418
461
  migrations own the schema, `postgresTelegramUpdateStoreSchema(table)` is the
419
- statement to put in one.
462
+ statement to put in one. Apply it before using `handleAttempt` or
463
+ `handleExhaustion`; it adds `claim_id` idempotently. Legacy `handle(update)`
464
+ uses base PostgreSQL operations that do not reference that column. SQLite adds
465
+ the column automatically and tolerates two rolling processes racing the
466
+ migration. A direct base-store claim with `durableExhaustion: true` also opts
467
+ into the column-backed durable capability.
420
468
 
421
469
  A store of your own — over an ORM, another database — is put through the same
422
470
  rules the shipped ones are, concurrent claims included:
@@ -506,13 +554,16 @@ injected grammY bot. → ADR 0143, ADR 0201 — [guide](../guide/telegram.md)
506
554
  | `telegramWebhookUrl` | function | the address with an `owner` tag derived from the secret: equal for one secret, different for another, the path untouched |
507
555
  | `checkTelegramWebhook` / `TelegramWebhookState` | function / _type_ | whether the webhook still points here: `owned`, `ownerHost`, `pendingUpdates`, Telegram's `lastError` |
508
556
  | `receiveTelegramWebhook` / `ReceiveTelegramWebhookOptions` / `TelegramUpdateAcceptance` | function / _type_ | one webhook `Request`: 403 without the secret (compared in constant time), 405 for another method, 400 for no update, 200 once `accept` recorded it |
509
- | `createTelegramUpdateIntake` | function | webhook updates recorded before Telegram is answered and handled after: chat order kept, other chats alongside up to `maxConcurrent`, a renewed lease per attempt, a sweep that takes over what a restart or a dead process left, retries, `maxAttempts`, finished updates remembered `retainMs` to refuse repeats; at least once |
510
- | `TelegramUpdateIntakeConfig` / `TelegramUpdateIntake` / `TelegramUpdateEnvelope` / `TelegramUpdateFailure` / `TelegramUpdateStoreStep` | _type_ | `store`, `handle` (a grammY `BotError` is unwrapped: `retry`, `onFailure` and the stored error see what the middleware threw), `retry`, `maxAttempts` (5), `leaseMs` (120 000), `pendingGraceMs` (60 000), `sweepEveryMs` (30 000), `retainMs` (24 h), `maxConcurrent` (32), `onFailure`, `onStoreError`, `now`; `accept(body)`, `start()`, `sweep()`, `idle()`, `close()` |
511
- | `TelegramUpdateStore` / `StoredTelegramUpdate` / `TelegramUpdateState` / `TelegramUpdateSettlement` / `TelegramUpdateClaimOptions` / `TelegramUpdateDueQuery` | _type_ | the store an intake runs on: `add`, `claim`, `renew`, `settle`, `due`, `prune`, each atomic on one record — a claim takes `pending`, a due `failed` or a lapsed `processing`; renew and settle only by the attempt holding it |
557
+ | `createTelegramUpdateIntake` | function | webhook updates recorded before Telegram is answered and handled after: chat order kept, other chats alongside up to `maxConcurrent`, a renewed lease and immutable fence per attempt, a sweep that takes over what a restart or a dead process left, retries, `maxAttempts`, optional recoverable exhaustion delivery, finished updates remembered `retainMs` to refuse repeats; at least once |
558
+ | `TelegramUpdateIntakeConfig` / `TelegramUpdateAttemptIntakeConfig` / `TelegramUpdateIntakeOptions` / `TelegramUpdateIntake` / `TelegramUpdateEnvelope` / `TelegramUpdateFailure` / `TelegramUpdateStoreStep` | _type_ | `store`, unchanged `handle(update)` or attempt-aware `handleAttempt(update, context)` (a grammY `BotError` is unwrapped), shared `retry`, `maxAttempts` (5), `leaseMs` (120 000), `pendingGraceMs` (60 000), `sweepEveryMs` (30 000), `retainMs` (24 h), `maxConcurrent` (32), `onFailure`, durable `handleExhaustion`, `onExhaustionFailure`, `onStoreError`, `now`; `accept(body)`, `start()`, `sweep()`, `idle()`, `close()` |
559
+ | `TelegramUpdateAttemptContext` / `TelegramUpdateAttemptIdentity` / `TelegramUpdateOwnershipLostError` | _type_ / class | immutable `{ updateId, attempt, claimId }` fence plus `ownerLost`; the persisted opaque claim token prevents a stale capability from matching after prune/re-add, while a store check in the consumer's DB transaction fences a domain write |
560
+ | `TelegramUpdateExhaustion` / `TelegramUpdateExhaustionFailure` / `StoredTelegramUpdateExhaustion` / `TelegramUpdateExhaustionQuery` | _type_ | a terminal update/attempt/error retained in the update store until `handleExhaustion` resolves; ordered `{ limit, afterUpdateId? }` pages prevent a failing first batch from starving later rows; delivery failures are observational and leave it due |
561
+ | `TelegramUpdateStore` / `StoredTelegramUpdate` / `TelegramUpdateState` / `TelegramUpdateSettlement` / `TelegramUpdateClaimOptions` / `TelegramUpdateDueQuery` | _type_ | the base store an intake runs on: `add`, `claim`, `renew`, `settle`, `due`, `prune`, each atomic on one record — a claim takes `pending`, a due `failed` or a lapsed `processing`; renew and settle only by the attempt holding it |
562
+ | `TelegramUpdateFencedStore` / `TelegramUpdateDurableStore` | _type_ | reference-store capabilities: `claimOwned` creates and returns the persisted identity, `renewOwned`, `owns(identity, at)` and `settleOwned(identity, settlement)` act only on that exact unexpired claim; `exhaust`, `dueExhaustions`, `acknowledgeExhaustion` keep a final consumer obligation in the same row until exact acknowledgement |
512
563
  | `memoryTelegramUpdateStore` | function | the store in this process's memory — tests, or a bot that accepts losing unhandled updates to a restart |
513
564
  | `sqliteTelegramUpdateStore` / `SqliteTelegramUpdateStoreConfig` / `TelegramSqliteDatabase` | function / _type_ | the store in SQLite over the handle the application opened — `bun:sqlite` `Database` or `node:sqlite` `DatabaseSync`; `table` (default `telegram_updates`) created when missing |
514
565
  | `postgresTelegramUpdateStore` / `PostgresTelegramUpdateStoreConfig` / `TelegramPostgresQuery` | function / _type_ | the store in Postgres over the client the application holds, as one `query(text, parameters)` returning rows — Bun `sql.unsafe`, Prisma `$queryRawUnsafe`, `pg` `pool.query`; each rule one conditional statement; `table`, `createTable` (default `true`) |
515
- | `postgresTelegramUpdateStoreSchema` | function | the table and its index as a statement for a migration, safe to run twice |
566
+ | `postgresTelegramUpdateStoreSchema` | function | the table, idempotent `claim_id` upgrade and index as a statement for a migration, safe to run twice; required before fenced methods when `createTable: false`, while legacy base methods remain compatible with the prior table |
516
567
  | `checkTelegramUpdateStore` | function | put any store through every rule, each on a fresh store from `make`, concurrent claims included; resolves with the violations, empty when it keeps them |
517
568
 
518
569
  ---
package/llms/tools.3.txt CHANGED
@@ -279,7 +279,7 @@ Advanced building blocks — the shared machinery the mounts are built on.
279
279
  | `TransportCounts` | _type_ | per-transport counts (`{ HTTP, MCP, AGENT, CLI }`) |
280
280
  | `ToolSurfaceProjection` | _type_ | the one readonly `{ services?, runtimeTools? }` container shared by full, MCP-only and introspection surfaces (`buildToolManifest`, `listToolNames`, `summarizeTransports` take `ToolSurfaceProjection<RuntimeToolDefinition>`) |
281
281
  | `ToolManifestConfig` | _type_ | mixed surface plus required model-facing `transport` and presentation options |
282
- | `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments; throws a `ZodError` naming the field when a union that keeps a string member receives a string that parses to an array its array member accepts |
282
+ | `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments, including object guards restored from JSON Schema; arbitrary transform pipes stay untouched; throws a `ZodError` naming the field when a union that keeps a string member receives a string that parses to an array its array member accepts |
283
283
  | `flattenToolJsonSchema` | function | project structurally identifiable discriminated unions into conservative object joins; scalar collisions retain provable types, object/array collisions retain structural alternatives, and field descriptions retain discriminator labels plus availability/requiredness hints; the projection never executes validation |
284
284
  | `ToolPresentationSchema` | _type_ | immutable model-facing JSON Schema document shared by tool transports |
285
285
  | `MountableTool` | _type_ | one operation with separate executable CLI argument schema and model-facing presentation schema |
@@ -1,14 +1,81 @@
1
- # Upgrading 0.100.0 → 0.108.0 — stitchkit docs slice
1
+ # Upgrading 0.101.0 → 0.109.0 — stitchkit docs slice
2
2
 
3
3
  > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
4
  > `llms.txt` in this package.
5
5
 
6
- Contains: Guide: Upgrading, 0.100.0 → 0.108.0 (docs/guide/upgrading.md)
6
+ Contains: Guide: Upgrading, 0.101.0 → 0.109.0 (docs/guide/upgrading.md)
7
7
 
8
8
  ==============================================================================
9
- # Guide: Upgrading, 0.100.0 → 0.108.0 (docs/guide/upgrading.md)
9
+ # Guide: Upgrading, 0.101.0 → 0.109.0 (docs/guide/upgrading.md)
10
10
  ==============================================================================
11
11
 
12
+ ## Released migration: 0.109.0
13
+
14
+ ### free CLI record leaves are lexical
15
+
16
+ **Who must act:** scripts that pass dotted values into a free `record<string, unknown>` and rely on
17
+ stitchkit guessing numbers or booleans from their spelling. Declared object leaves and whole-object
18
+ JSON keep their typed behavior.
19
+
20
+ Free record values have no schema type to justify coercion. They now preserve argv text, so values
21
+ such as `007`, `1e3`, `true` and `false` arrive exactly as written.
22
+ See [ADR 0257](../decisions/0257-cli-dotted-values-follow-their-declared-schema.md).
23
+
24
+ 1. Pass typed free-record values as one JSON object:
25
+
26
+ ```sh
27
+ # before: produced { count: 5 } by spelling-based inference
28
+ app command --data.count=5
29
+ # after: JSON says explicitly that 5 is a number
30
+ app command --data='{"count":5}'
31
+ ```
32
+
33
+ 2. If dotted flags are the intended interface, declare their object shape; a `z.number()` or
34
+ `z.boolean()` leaf still coerces and validates its own CLI value.
35
+
36
+ ### Opting into Telegram attempt fencing
37
+
38
+ Existing `handle(update)` callbacks and the six-method `TelegramUpdateStore` need no change. The
39
+ new capability is opt-in
40
+ ([ADR 0256](../decisions/0256-a-telegram-attempt-is-fenced-by-a-persisted-claim.md)).
41
+
42
+ 1. To fence a domain write, move only that handler to the explicit context-aware option:
43
+
44
+ ```ts
45
+ createTelegramUpdateIntake({
46
+ store,
47
+ handleAttempt: async (update, { fence, ownerLost }) => {
48
+ ownerLost.throwIfAborted()
49
+ await domainTransaction(async (tx) => {
50
+ if (await transactionStore(tx).owns(fence, Date.now())) await apply(tx, update)
51
+ })
52
+ },
53
+ })
54
+ ```
55
+
56
+ 2. A direct grammY handler remains `handle: bot.handleUpdate.bind(bot)`; stitchkit does not pass its
57
+ attempt context into grammY's optional webhook-envelope parameter.
58
+
59
+ 3. A custom store used by `handleAttempt` implements `claimOwned`, `renewOwned`, `owns` and
60
+ `settleOwned` over the full `{ updateId, attempt, claimId }` identity. If it also delivers
61
+ `handleExhaustion`, implement `exhaust`, `dueExhaustions` and `acknowledgeExhaustion`. Run
62
+ `checkTelegramUpdateStore` against a fresh database; it checks base, fenced and durable rules
63
+ according to the methods the store exposes.
64
+
65
+ 4. SQLite stores add `claim_id` automatically. PostgreSQL stores with the default
66
+ `createTable: true` do the same. When migrations own a PostgreSQL table, apply the returned
67
+ schema before enabling `handleAttempt` or `handleExhaustion`:
68
+
69
+ ```ts
70
+ await sql.unsafe(postgresTelegramUpdateStoreSchema('telegram_updates'))
71
+ // Equivalent addition for an existing table:
72
+ // ALTER TABLE telegram_updates ADD COLUMN IF NOT EXISTS claim_id TEXT;
73
+ ```
74
+
75
+ Legacy `handle(update)` continues to use base operations that do not reference `claim_id`, so
76
+ the migration is required only when the new capability or an explicit base
77
+ `claim(..., { durableExhaustion: true })` is enabled.
78
+
12
79
  ## Released migration: 0.108.0
13
80
 
14
81
  ### a request that never left is its own send outcome
@@ -831,72 +898,3 @@ text.replace(new RegExp(TELEGRAM_BOT_TOKEN_PATTERN.source, 'g'), '[redacted]')
831
898
  createJsonLogger({})
832
899
  redactTelegramBotToken(text)
833
900
  ```
834
-
835
- ## Released migration: 0.100.0
836
-
837
- ### watch capacity and session/live/delivery adoption
838
-
839
- **Who must act:** watch consumers exceeding 1024 hub sources, 1024 hub subscribers,
840
- 1024 client keys or 128 listeners per key. These previously unbounded collections
841
- now refuse excess admission. Choose explicit positive finite limits, for example:
842
-
843
- ```ts
844
- // Before: no hub-wide capacity bounds.
845
- createWatchHub({ read, watchable });
846
- // After: explicitly budget a larger workload.
847
- createWatchHub({ read, watchable, maxSources: 4096, maxSubscribers: 4096 });
848
- // Client limits are configured independently.
849
- createWatchClient({ ...clientConfig, maxKeys: 2048, maxListenersPerKey: 256 });
850
- ```
851
-
852
- The remaining steps apply only to applications opting into session lifetimes,
853
- scoped watched reads, managed subscriptions or multi-action notification delivery.
854
- Existing clients within the limits and single-send outboxes need no migration.
855
-
856
- Replace local session generation counters with one `createSessionScope`; share it
857
- with `createSessionCredentials`. Guard transport attempts, complete asynchronous
858
- operations and final synchronous delivery. Route credential persistence through
859
- one coordinator. Keep provider-specific error classification and bounded command
860
- retry in the application. Coordination is per client instance.
861
-
862
- Upgrade both watch endpoints before enabling session-scoped watches: strict older
863
- servers reject the new optional `WatchKey.instance`. Server-verified scope controls
864
- sharing; the client instance is only routing identity. Bind exact watched handles
865
- to session-qualified cache keys. Existing delta/reconnect behavior stays in place.
866
- For applications needing more than 1024 hub sources/subscribers or client keys, set
867
- explicit finite capacity options. Enable a reconciliation interval when lost hints
868
- must recover without a reconnect.
869
-
870
- For multi-action outboxes, enqueue new items with a versioned delivery plan and
871
- return small JSON receipts from `send`. Existing queued single-send items should
872
- be drained before enabling a delivery executor on that queue, or migrated under
873
- the store's atomic transition. When a new executor version ships, list the old
874
- version's projection in `delivery.retiredVersions` until its queued items drain;
875
- a plan version with no executor waits, reported to `onError` as
876
- `NotificationPlanVersionError` (from 0.100.1).
877
- Make projection idempotent. Provider idempotency/reconciliation remains necessary
878
- for a crash between remote success and checkpoint. These APIs are available starting with 0.100.0.
879
-
880
- ### exclusive lock recovery
881
-
882
- New lock and reclaim-guard records include `owner.process`: kernel boot identity,
883
- process birth token and namespace. `readDiagnosticJournalLockDiagnosis(error)`
884
- adds `identity` to distinguish matched lifetime evidence, PID reuse, a different
885
- boot, an absent PID, a legacy record or unavailable identity. Legacy `alive` only
886
- means the PID is present; it does not prove that the recorded writer still exists.
887
-
888
- **Who must act:** deployments recovering a legacy lock with a reused live PID,
889
- or bundling file-lock consumers into a standalone Darwin executable. Legacy
890
- records cannot safely infer their missing boot/start identity from `acquiredAt`.
891
- Stop all writers to that resource, verify exclusive ownership, preserve the old
892
- lock for incident evidence outside the active lock path, and restart with the
893
- new package. Never delete a lock merely because it is old. New acquisitions
894
- write the modern record; no journal data migration is needed.
895
-
896
- Darwin distributions must retain the package's architecture-matched native
897
- `.node` asset for process birth measurement, as for contained filesystem access.
898
- If native/proc identity cannot be read, new locks explicitly record null and
899
- recovery refuses rather than treating unknown ownership as death. From 0.100.1 a
900
- Linux kernel without time namespaces (before 5.6) records the PID namespace alone
901
- instead of null, so its locks recover like any other. Existing
902
- `refuse` policy and the separately configured ownerless-file grace are unchanged.
@@ -0,0 +1,176 @@
1
+ # Upgrading 0.37.0 → 0.37.0 — stitchkit docs slice
2
+
3
+ > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
+ > `llms.txt` in this package.
5
+
6
+ Contains: Guide: Upgrading, 0.37.0 → 0.37.0 (docs/guide/upgrading.md)
7
+
8
+ ==============================================================================
9
+ # Guide: Upgrading, 0.37.0 → 0.37.0 (docs/guide/upgrading.md)
10
+ ==============================================================================
11
+
12
+ ## The 0.37 migration
13
+
14
+ Tool presentation is no longer an executable Zod parser. Replace the removed
15
+ flatten helpers with the JSON Schema compiler, and choose the explicit
16
+ `MountableTool` surface when using the advanced collection API:
17
+
18
+ ```ts
19
+ // before
20
+ const flat = flattenUnionsDeep(zodSchema)
21
+ mountable.schema
22
+
23
+ // after
24
+ const flat = flattenToolJsonSchema(
25
+ z.toJSONSchema(zodSchema, { target: 'draft-07', io: 'input' }),
26
+ )
27
+ mountable.presentationSchema // model/MCP/manifest JSON Schema
28
+ mountable.argumentSchema // executable CLI argument adapter only
29
+ ```
30
+
31
+ Contract and native handlers keep their original Zod schemas; MCP and agent SDK
32
+ adapters now forward raw arguments so defaults, coercions, refinements and
33
+ transforms execute exactly once inside Stitchkit. There are no compatibility
34
+ exports for `flattenDiscriminatedUnion`, `flattenUnionsDeep` or
35
+ `MountableTool.schema`. → ADR 0050
36
+
37
+ Tool-call hooks now take one options object. Migrate all three callbacks; there
38
+ are no positional overloads:
39
+
40
+ ```ts
41
+ // before
42
+ beforeToolCall: (toolName, args, context, endpoint) => {}
43
+ afterToolCall: (toolName, args, result, durationMs, context, endpoint, error) => {}
44
+ onToolError: (toolName, error, context, endpoint) => {}
45
+
46
+ // after
47
+ beforeToolCall: ({ toolName, args, context, endpoint }) => {}
48
+ afterToolCall: ({ toolName, args, result, durationMs, context, endpoint, error }) => {}
49
+ onToolError: ({ toolName, error, context, endpoint }) => {}
50
+ ```
51
+
52
+ ### MCP schema validation
53
+
54
+ The standalone validator now takes one object and live MCP configs carry the
55
+ same rules under `schemaValidation`. Migrate every positional call and every
56
+ `onIncompatibleSchema` field; there is no old-shape overload:
57
+
58
+ ```ts
59
+ // before
60
+ validateMcpSchemas(services, 'throw', logger, { requireTypedProperties: true })
61
+ createMcpHandler({ services, onIncompatibleSchema: 'throw' })
62
+
63
+ // after
64
+ validateMcpSchemas({ services, policy: 'throw', logger, requireTypedProperties: true })
65
+ createMcpHandler({ services, schemaValidation: { policy: 'throw' } })
66
+ ```
67
+
68
+ Put `extend` and `flattenUnionInput` beside `services`, not inside
69
+ `schemaValidation`. The handler applies the profile to the exact prepared schema
70
+ it advertises. Add `requirePortableFormats: true` when every custom JSON Schema
71
+ format must be rejected before a client sees it.
72
+
73
+ Native MCP registration also changed shape in 0.37. Move protected tools to the
74
+ framework registrar; keep an SDK-raw tool only by naming the opt-out:
75
+
76
+ ```ts
77
+ // before
78
+ nativeTools: (server, auth) => server.registerTool(name, config, handler)
79
+
80
+ // after — lifecycle, hooks and schema policy apply
81
+ nativeTools: ({ registerTool }, auth) => registerTool({
82
+ name, description, identity, input, output, handler,
83
+ })
84
+
85
+ // after — intentionally raw
86
+ nativeTools: ({ rawServer }, auth) => rawServer.registerTool(name, config, handler)
87
+ ```
88
+
89
+ If a tool hook annotated `endpoint` as `MethodDef`, remove that annotation or
90
+ use `OperationIdentity`: native operations have service/action/scope/method but
91
+ no HTTP path.
92
+
93
+ ### Node-facing server types
94
+
95
+ `stitchkit/server` remains Bun-concrete: an explicitly annotated `RawRoute`
96
+ still receives `BunServer`. `stitchkit/node` no longer drags Bun declarations
97
+ into a Node project. Its raw routes default the host server to `unknown`, and
98
+ its Socket.IO handle exposes only Node capabilities:
99
+
100
+ ```ts
101
+ // before — Node entry still leaked Bun-only fields and ambient types
102
+ const route: RawRoute = { handler: (_req, ctx) => ctx.server?.upgrade(...) }
103
+ const socket = await createSocketIOServer(config)
104
+ socket.websocket
105
+
106
+ // after — name a custom embedding host only when one exists
107
+ const route: RawRoute<MyHostServer> = {
108
+ handler: (_req, ctx) => useHost(ctx.server),
109
+ }
110
+ const socket = await createSocketIOServer(config)
111
+ socket.io
112
+ socket.attach(nodeHttpServer)
113
+ ```
114
+
115
+ Node consumers can remove `@types/bun` unless another dependency independently
116
+ requires it.
117
+
118
+ ### Managed server shutdown
119
+
120
+ `createServer()` and `serveNode()` now return the same structural managed
121
+ lifecycle. Replace every direct runtime stop and parallel Socket.IO close:
122
+
123
+ ```ts
124
+ // before — split ownership
125
+ const socket = await createSocketIOServer(config)
126
+ const server = createServer({
127
+ services,
128
+ websocket: socket.websocket,
129
+ rawRoutes: [socket.route],
130
+ })
131
+ server.stop()
132
+ await socket.io.close()
133
+
134
+ // after — one owner and one total deadline
135
+ const socket = await createSocketIOServer(config)
136
+ const server = createServer({ services, socket })
137
+ const result = await server.shutdown({ gracePeriodMs: 30_000 })
138
+ ```
139
+
140
+ On Node, keep the same `socket` field and replace `handle.close()` with
141
+ `handle.shutdown()`. Runtime-specific diagnostics move under `handle.runtime`;
142
+ do not use it as a second shutdown path. Standalone CLI/tools that create a
143
+ Socket.IO handle without an HTTP server call `await socket.close()`.
144
+
145
+ If Bun Socket.IO shares the port with a raw lane, keep the explicit composition
146
+ but let the server mount the Socket.IO route:
147
+
148
+ ```ts
149
+ createServer({
150
+ services,
151
+ socket,
152
+ websocket: composeWebSocketHandlers([
153
+ webSocketLane({ match: isRaw, handlers: rawHandlers }),
154
+ socketIoLane(socket.websocket),
155
+ ]),
156
+ rawRoutes: [rawUpgradeRoute],
157
+ })
158
+ ```
159
+
160
+ Move native Bun `routes` entries to `rawRoutes`. Native routes run before the
161
+ Fetch handler and therefore cannot participate in admission or drain. Wire
162
+ `SIGTERM`/`SIGINT` in the application; the first signal starts `shutdown()`, and
163
+ a later signal may abort the same controller. Close MCP, databases and queues
164
+ after the server result—those resources remain application-owned.
165
+
166
+ Move a handshake policy from the Node-only callback shape inside
167
+ `serverOptions` to the runtime-neutral top-level policy. It receives a Web
168
+ `Request`, may be async, and returns whether to admit the handshake:
169
+
170
+ ```ts
171
+ // before
172
+ serverOptions: { allowRequest: (request, done) => done(null, allowed(request)) }
173
+
174
+ // after
175
+ allowRequest: (request) => allowed(request)
176
+ ```