stitchkit 0.70.6 → 0.72.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 (116) hide show
  1. package/README.md +1 -1
  2. package/dist/agent-runtime/coding-tool-contract.d.ts +11 -1
  3. package/dist/agent-runtime/coding-tool-contract.d.ts.map +1 -1
  4. package/dist/agent-runtime/coding-tool-files.d.ts.map +1 -1
  5. package/dist/agent-runtime/coding-tool-listing.d.ts +15 -0
  6. package/dist/agent-runtime/coding-tool-listing.d.ts.map +1 -0
  7. package/dist/agent-runtime/coding-tool-paths.d.ts.map +1 -1
  8. package/dist/agent-runtime/coding-tool-refusals.d.ts +59 -0
  9. package/dist/agent-runtime/coding-tool-refusals.d.ts.map +1 -0
  10. package/dist/agent-runtime/coding-tool-search-patch.d.ts.map +1 -1
  11. package/dist/agent-runtime/coding-tools.d.ts +3 -1
  12. package/dist/agent-runtime/coding-tools.d.ts.map +1 -1
  13. package/dist/agent-runtime/compaction.d.ts +35 -0
  14. package/dist/agent-runtime/compaction.d.ts.map +1 -1
  15. package/dist/agent-runtime/contained-files.d.ts +21 -2
  16. package/dist/agent-runtime/contained-files.d.ts.map +1 -1
  17. package/dist/agent-runtime/provider-failure.d.ts +66 -0
  18. package/dist/agent-runtime/provider-failure.d.ts.map +1 -0
  19. package/dist/agent-runtime/run-execution.d.ts.map +1 -1
  20. package/dist/agent-runtime/runtime.d.ts +31 -1
  21. package/dist/agent-runtime/runtime.d.ts.map +1 -1
  22. package/dist/agent-runtime/schemas.d.ts +1 -0
  23. package/dist/agent-runtime/schemas.d.ts.map +1 -1
  24. package/dist/agent-runtime-coding-tools.js +438 -124
  25. package/dist/agent-runtime-harness.js +4 -4
  26. package/dist/agent-runtime-openrouter.d.ts +19 -0
  27. package/dist/agent-runtime-openrouter.d.ts.map +1 -1
  28. package/dist/agent-runtime-openrouter.js +2 -1
  29. package/dist/agent-runtime.d.ts +3 -2
  30. package/dist/agent-runtime.d.ts.map +1 -1
  31. package/dist/agent-runtime.js +118 -14
  32. package/dist/application/admission.d.ts +22 -2
  33. package/dist/application/admission.d.ts.map +1 -1
  34. package/dist/application/channel.d.ts +28 -0
  35. package/dist/application/channel.d.ts.map +1 -1
  36. package/dist/application/diagnostic-journal-contract.d.ts +20 -0
  37. package/dist/application/diagnostic-journal-contract.d.ts.map +1 -1
  38. package/dist/application/diagnostic-journal-lock.d.ts +17 -0
  39. package/dist/application/diagnostic-journal-lock.d.ts.map +1 -0
  40. package/dist/application/diagnostic-journal-manager.d.ts +2 -1
  41. package/dist/application/diagnostic-journal-manager.d.ts.map +1 -1
  42. package/dist/application/diagnostic-journal-storage.d.ts +4 -1
  43. package/dist/application/diagnostic-journal-storage.d.ts.map +1 -1
  44. package/dist/application/diagnostic-journal.d.ts +4 -2
  45. package/dist/application/diagnostic-journal.d.ts.map +1 -1
  46. package/dist/application-opentelemetry.js +1 -1
  47. package/dist/application.d.ts +3 -3
  48. package/dist/application.d.ts.map +1 -1
  49. package/dist/application.js +134 -21
  50. package/dist/browser/resumable.d.ts +56 -0
  51. package/dist/browser/resumable.d.ts.map +1 -0
  52. package/dist/cli.js +5 -5
  53. package/dist/{index-cx84zg25.js → index-118v0z41.js} +1 -1
  54. package/dist/{index-zpyj7hsv.js → index-3cwck0rm.js} +98 -33
  55. package/dist/{index-ejv31h14.js → index-7qy2ex0m.js} +1 -1
  56. package/dist/{index-trrf7nch.js → index-ezmn6ac6.js} +3 -3
  57. package/dist/{index-ekkt4gy9.js → index-jqtsc9mj.js} +2 -2
  58. package/dist/{index-hxtm2xr2.js → index-k2zczx1g.js} +1 -1
  59. package/dist/{index-by57nwhc.js → index-m668wzyc.js} +10 -2
  60. package/dist/{index-w9m1wznn.js → index-nemjkxjp.js} +118 -1
  61. package/dist/{index-tnazr5tc.js → index-pz7ytdga.js} +3 -3
  62. package/dist/{index-1z5xxfst.js → index-rqpdar1e.js} +102 -5
  63. package/dist/{index-qm87g51s.js → index-s4c8wy8m.js} +1 -1
  64. package/dist/index.d.ts +1 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +103 -14
  67. package/dist/node.js +2 -2
  68. package/dist/observability/index.js +1 -1
  69. package/dist/primitives/audit.d.ts +52 -0
  70. package/dist/primitives/audit.d.ts.map +1 -0
  71. package/dist/primitives/deadline.d.ts +39 -0
  72. package/dist/primitives/deadline.d.ts.map +1 -0
  73. package/dist/primitives/decimal.d.ts +10 -0
  74. package/dist/primitives/decimal.d.ts.map +1 -0
  75. package/dist/primitives/delivery.d.ts +96 -0
  76. package/dist/primitives/delivery.d.ts.map +1 -0
  77. package/dist/primitives/event.d.ts +42 -0
  78. package/dist/primitives/event.d.ts.map +1 -0
  79. package/dist/primitives/export-operation.d.ts +89 -0
  80. package/dist/primitives/export-operation.d.ts.map +1 -0
  81. package/dist/primitives/index.d.ts +12 -0
  82. package/dist/primitives/index.d.ts.map +1 -0
  83. package/dist/primitives/lifecycle.d.ts +90 -0
  84. package/dist/primitives/lifecycle.d.ts.map +1 -0
  85. package/dist/primitives/migration-checks.d.ts +15 -0
  86. package/dist/primitives/migration-checks.d.ts.map +1 -0
  87. package/dist/primitives/money.d.ts +43 -0
  88. package/dist/primitives/money.d.ts.map +1 -0
  89. package/dist/primitives/owner-scope.d.ts +30 -0
  90. package/dist/primitives/owner-scope.d.ts.map +1 -0
  91. package/dist/primitives/permission.d.ts +28 -0
  92. package/dist/primitives/permission.d.ts.map +1 -0
  93. package/dist/primitives/quantity.d.ts +57 -0
  94. package/dist/primitives/quantity.d.ts.map +1 -0
  95. package/dist/primitives.d.ts +3 -0
  96. package/dist/primitives.d.ts.map +1 -0
  97. package/dist/primitives.js +750 -0
  98. package/dist/server/index.d.ts +1 -1
  99. package/dist/server/index.d.ts.map +1 -1
  100. package/dist/server/index.js +4 -2
  101. package/dist/server/request.d.ts +19 -0
  102. package/dist/server/request.d.ts.map +1 -1
  103. package/dist/telegram/init-data.d.ts +134 -0
  104. package/dist/telegram/init-data.d.ts.map +1 -0
  105. package/dist/telegram/send-failure.d.ts +73 -0
  106. package/dist/telegram/send-failure.d.ts.map +1 -0
  107. package/dist/telegram.d.ts +3 -0
  108. package/dist/telegram.d.ts.map +1 -0
  109. package/dist/telegram.js +224 -0
  110. package/dist/tool-invoker.js +4 -4
  111. package/dist/tools.js +7 -7
  112. package/llms-full.txt +510 -10
  113. package/llms.txt +1 -0
  114. package/native/darwin-arm64.node +0 -0
  115. package/native/darwin-x64.node +0 -0
  116. package/package.json +11 -3
package/llms-full.txt CHANGED
@@ -48,6 +48,7 @@ own, recorded as an ADR.
48
48
  |--------|--------|----------|-------|
49
49
  | `stitchkit` | browser **and** server | stable | `defineContract`, `createClient`, `createHttpClient`, `createSocketIOClient`, `parseSSE`, the error model |
50
50
  | `stitchkit/contract` | browser **and** server | stable | the contract layer alone — `defineContract`, errors, pagination |
51
+ | `stitchkit/primitives` | browser **and** server | evolving | generic values and declarations for lifecycle, access, audit, delivery and exports |
51
52
  | `stitchkit/server` | server (Bun) | stable | `createServer`, `implement`, hooks, auth, Socket.IO server, server primitives |
52
53
  | `stitchkit/node` | server (Node ≥ 22) | stable | `serveNode` + the runtime-agnostic core — the Node mirror of `/server` |
53
54
  | `stitchkit/tools` | server | stable | `createMcpHandler`, `mountMcp`, `mountAgent`, the OAuth provider, native tools |
@@ -55,11 +56,12 @@ own, recorded as an ADR.
55
56
  | `stitchkit/cli` | server | stable | `createCli` — the CLI transport, light (no MCP SDK / `ai`) |
56
57
  | `stitchkit/remote` | browser **and** server | stable | peer-free `implementRemote` for thin HTTP proxy processes |
57
58
  | `stitchkit/files` | server (Bun or Node) | stable | peer-free managed local-file boundary |
59
+ | `stitchkit/telegram` | server (Bun or Node) | evolving | peer-free Telegram platform primitives — Mini App `initData` verification and Bot API send-failure classification |
58
60
  | `stitchkit/observability` | server | stable | request/tool event projections — `createObservability`, trace context, sanitisation |
59
61
  | `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the store and managed-resource conformance kits |
60
62
  | `stitchkit/declaration` | build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
61
63
  | `stitchkit/react` | browser | stable | `createCursorQuery`, `createCacheBridge` |
62
- | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 10 of the 15 minors since 0.56.2, most recently 0.69.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
64
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 10 of the 17 minors since 0.56.2, most recently 0.69.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
63
65
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
64
66
  | `stitchkit/agent-runtime/coding-tools` | server (Bun or Node) | evolving | bounded host-authorized direct file and shell tools; a root boundary, not an OS sandbox |
65
67
  | `stitchkit/agent-runtime/openrouter` | server | evolving | isolated OpenRouter language-model adapter |
@@ -67,7 +69,7 @@ own, recorded as an ADR.
67
69
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
68
70
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
69
71
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
70
- | `stitchkit/application` | server | evolving<br>_redefined in 3 of the 15 minors since 0.56.2, most recently 0.67.0_ | managed resource graph, readiness, admission, schedules and bounded shutdown |
72
+ | `stitchkit/application` | server | evolving<br>_redefined in 4 of the 17 minors since 0.56.2, most recently 0.72.0_ | managed resource graph, readiness, admission, schedules and bounded shutdown |
71
73
  | `stitchkit/application/grammy` | server | evolving | isolated grammY polling and webhook lifecycle adapters |
72
74
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
73
75
 
@@ -180,6 +182,7 @@ map — feature → packages:
180
182
  | **Socket.IO server on Node** | `socket.io` |
181
183
  | Socket.IO client | `socket.io-client` (runtime peer; unrelated root declarations remain peer-free) |
182
184
  | grammY lifecycle adapters (`stitchkit/application/grammy`) | `grammy` |
185
+ | Telegram platform primitives (`stitchkit/telegram`) | — (peer-free) |
183
186
  | OpenTelemetry gauges (`stitchkit/application/opentelemetry`) | `@opentelemetry/api` |
184
187
 
185
188
  ```bash
@@ -2563,6 +2566,49 @@ Set `finalLine: 'require-newline'` when the final newline is part of the
2563
2566
  protocol's truncation proof. The default `allow` continues to accept one valid
2564
2567
  final JSON document without a newline.
2565
2568
 
2569
+ ## Resumable streams
2570
+
2571
+ A stream that survives a dropped connection needs four things beyond opening it:
2572
+ re-open, back off before retrying, resume from where it stopped rather than
2573
+ restart, and stop for good on a terminal item. `resumableIterator` owns those
2574
+ four; your code keeps every decision that is about your data.
2575
+
2576
+ ```ts
2577
+ import { resumableIterator } from 'stitchkit'
2578
+
2579
+ for await (const event of resumableIterator<Event, string>({
2580
+ async open(cursor) {
2581
+ const url = cursor ? `/api/events?after=${cursor}` : '/api/events'
2582
+ return parseNDJSON(await fetch(url, { signal }))
2583
+ },
2584
+ advance: (event) => event.id, // what "where it stopped" means
2585
+ isTerminal: (event) => event.done, // which item ends the stream
2586
+ retry: { minDelayMs: 500, maxDelayMs: 30_000, jitter: 0.5 },
2587
+ signal,
2588
+ onAttempt: ({ number, delayMs, error }) => log.warn({ number, delayMs, error }),
2589
+ })) {
2590
+ render(event)
2591
+ }
2592
+ ```
2593
+
2594
+ `open` receives the cursor produced by the last **delivered** item, so a source
2595
+ that fails after three items re-opens after the third, not from the beginning.
2596
+ A source that simply ends without a terminal item is treated as a dropped
2597
+ connection — that is the case a hand-written loop usually mistakes for
2598
+ completion, and it is why the stream stops resuming.
2599
+
2600
+ **Jitter is not a detail.** Without it every consumer that lost the same server
2601
+ retries at the same instant and the fleet arrives together on a server that has
2602
+ just come back. The randomisation only ever *shortens* a delay, so `maxDelayMs`
2603
+ stays a real ceiling. `createBackoff({ minDelayMs, maxDelayMs, jitter })` is the
2604
+ same policy as a standalone value — `next()` and `reset()` — when you need the
2605
+ delays without the iterator.
2606
+
2607
+ A delivered item resets the backoff, so a stream that reconnects, works for an
2608
+ hour and drops again starts its next retry at `minDelayMs` rather than the
2609
+ ceiling it reached last time. Aborting the signal ends the iteration promptly,
2610
+ including in the middle of a wait.
2611
+
2566
2612
 
2567
2613
  ==============================================================================
2568
2614
  # Guide: MCP & agents (docs/guide/mcp-and-agents.md)
@@ -4101,7 +4147,8 @@ responses and results with a different call or tool name are invalid; dropping a
4101
4147
  approval input fails the run with a private diagnostic rather than starting a fresh model turn.
4102
4148
 
4103
4149
  `stitchkit/agent-runtime/coding-tools` returns ordinary direct runtime tools named `read_file`,
4104
- `write_file`, `search_files`, `apply_patch`, `run_command` and optional `read_output`. Every call passes a
4150
+ `write_file`, `edit_file`, `list_directory`, `glob`, `search_files`, `run_command` and optional
4151
+ `read_output`. Every call passes a
4105
4152
  required host authorization callback. File paths are relative, bounded and contained after
4106
4153
  descriptor-relative resolution: each ancestor is opened without following symlinks and remains
4107
4154
  pinned through authorization and the filesystem effect. Reads revalidate the pinned file identity;
@@ -4117,10 +4164,32 @@ Arguments, output and time are bounded, while cancellation terminates the child.
4117
4164
  root and cwd are path boundaries, not a security sandbox: isolate the process when an executable
4118
4165
  must not access the rest of the machine.
4119
4166
 
4120
- `apply_patch` binds the exact source to `baseSha256`, supports dry-run, authorizes the exact
4121
- replacement count, result digest and byte size, then rechecks the base under a per-target lock
4122
- before same-directory atomic replacement. It deliberately does not claim
4123
- multi-file atomicity. With an optional `AgentCodingArtifactStore`, shell output beyond the inline
4167
+ `edit_file` replaces one exact snippet. `oldText` is itself the freshness guard for the region it
4168
+ changes, so the digest is the optional `expectedSha256` and an edit is one call; pass the digest
4169
+ `read_file` returned when you want the whole-file guarantee, and read the `sha256` it returns to
4170
+ chain the next edit without re-reading. The read, the occurrence count and the construction of the
4171
+ new content all happen inside one per-target lock, so two concurrent edits of different snippets in
4172
+ one file cannot each build a file from the same base and have the second erase the first — the lock
4173
+ is process-local, which covers many agents in one process and not two processes over one workspace.
4174
+ It authorizes the exact replacement count, result digest and byte size, and deliberately does not
4175
+ claim multi-file atomicity.
4176
+
4177
+ `write_file` creates missing parent directories inside the root and names them in
4178
+ `createdDirectories`; the walk that finds them runs before authorization and reports them in the
4179
+ authorization payload, so no directory is created before a host approves it.
4180
+
4181
+ **Every ordinary outcome is a refusal a model can act on** — a missing file is `NOT_FOUND`, an
4182
+ existing file without `overwrite` is `CONFLICT`, an ambiguous snippet is `CONFLICT` carrying its
4183
+ occurrence count, a path outside the root is `FORBIDDEN` — each with a `hint` naming the next move.
4184
+ Host-level causes stay scrubbed to `INTERNAL_SERVER_ERROR` and name nothing outside the workspace.
4185
+ → ADR 0139
4186
+
4187
+ `list_directory` marks excluded directories rather than hiding them, and `glob` reports
4188
+ `skippedDirectories` beside its matches: an empty result from a tree whose files all live under an
4189
+ excluded directory is not "no files", and a model told only "nothing found" concludes the wrong
4190
+ thing. `search_files` takes `regex`, `context` lines and an `include` pattern; regex is bounded by
4191
+ refusing backreferences and lookaround and capping line length rather than by a timeout, because a
4192
+ JavaScript `RegExp` cannot be interrupted once it starts backtracking. With an optional `AgentCodingArtifactStore`, shell output beyond the inline
4124
4193
  preview continues into an opaque bounded artifact and `read_output` reads slices without
4125
4194
  exposing a host path. Without a store, the previous finite output-limit behavior is unchanged.
4126
4195
 
@@ -4807,6 +4876,40 @@ replace a leading summary on the next compaction.
4807
4876
  Set `maxAttempts` to allow bounded conflict recovery. Every retry reloads the snapshot, reselects the
4808
4877
  eligible range and recomputes the summary; the stale summary is never retried.
4809
4878
 
4879
+ ### Compacting without the store
4880
+
4881
+ The *selection* inside compaction needs no store, no version and no runtime, so
4882
+ it is published on its own as `selectCompactableHistory` — for an application
4883
+ that drives the model itself and keeps its own history (→ ADR 0142):
4884
+
4885
+ ```ts
4886
+ import { selectCompactableHistory } from 'stitchkit/agent-runtime'
4887
+
4888
+ const { leadingSummary, compactable, retained } = selectCompactableHistory({
4889
+ messages,
4890
+ keepRecentTurns: 3,
4891
+ })
4892
+ if (compactable.length > 0) {
4893
+ const summary = await summarize(compactable, leadingSummary)
4894
+ messages = [summary, ...retained] // replace the old summary, never stack one
4895
+ }
4896
+ ```
4897
+
4898
+ `compactable` is the oldest **whole complete** turns and `retained` is
4899
+ everything the model must still hear; together they are the input, in order. A
4900
+ turn holding a tool call whose result never arrived is never eligible, and
4901
+ neither is anything after it: half a turn hands the provider a call with no
4902
+ result, which most of them refuse outright.
4903
+
4904
+ `structuredCompaction` calls this same function, so the published selection and
4905
+ the runtime's cannot drift into two behaviours. What it adds is the part that
4906
+ does need the store — writing the result back under a version check.
4907
+
4908
+ Deciding *when* to compact is yours: `AgentRuntimeRunContext.contextUsage`
4909
+ carries the last step's prompt size beside the model's window, and dividing them
4910
+ is one line where the threshold is decided. The core does not own that ratio, on
4911
+ purpose — see ADR 0142 for what it publishes and what it declines to.
4912
+
4810
4913
  ## Observability
4811
4914
 
4812
4915
  `createAgentObservability` emits a separate operator-only `AgentRunEvent`. It
@@ -5550,6 +5653,110 @@ retry rules do **not** disappear. They were never process-local glue and remain
5550
5653
  application-owned.
5551
5654
 
5552
5655
 
5656
+ ==============================================================================
5657
+ # Guide: Generic application primitives (docs/guide/primitives.md)
5658
+ ==============================================================================
5659
+
5660
+ ---
5661
+ title: Generic application primitives
5662
+ description: Declare lifecycle, access, exact values, audit, delivery and exports without moving application infrastructure into Stitchkit.
5663
+ type: guide
5664
+ status: active
5665
+ created: 2026-09-01
5666
+ updated: 2026-09-01
5667
+ ---
5668
+
5669
+ # Generic application primitives
5670
+
5671
+ `stitchkit/primitives` is an optional browser-safe leaf. It declares values and policies; it does
5672
+ not own a database, transaction, scheduler, durable queue, transport or document generator.
5673
+
5674
+ ## Lifecycle and access
5675
+
5676
+ ```ts
5677
+ import { defineLifecycle, defineOwnerScope, definePermissionMatrix } from 'stitchkit/primitives';
5678
+ import { z } from 'zod';
5679
+
5680
+ const lifecycle = defineLifecycle({
5681
+ name: 'document',
5682
+ states: ['draft', 'published'],
5683
+ roles: ['author'],
5684
+ transitions: {
5685
+ publish: {
5686
+ from: 'draft',
5687
+ to: 'published',
5688
+ by: ['author'],
5689
+ payload: z.object({ note: z.string() }),
5690
+ },
5691
+ },
5692
+ });
5693
+
5694
+ const permissions = definePermissionMatrix({
5695
+ roles: ['reader', 'author'],
5696
+ operations: ['read', 'publish'],
5697
+ grants: {
5698
+ reader: { read: true, publish: false },
5699
+ author: { read: true, publish: true },
5700
+ },
5701
+ });
5702
+ ```
5703
+
5704
+ Persist `transition(...).event` in the same application transaction as the next state. A state is
5705
+ an immutable branded value; `transition()` produces the next one. `availableTransitions()` and
5706
+ `transition()` read the same declaration.
5707
+
5708
+ `defineOwnerScope` resolves one owner from authenticated identity. Reading across owners is a
5709
+ separate `acrossAllOwners(identity)` call and permission. Pass the resulting branded `OwnerScope`
5710
+ to the data adapter. This proves the scope was explicit; Stitchkit cannot prove what SQL an
5711
+ arbitrary adapter emits. `scanOwnerFilterRisks` maps existing caller-named manual filters before a
5712
+ migration.
5713
+
5714
+ ## Exact values and deadlines
5715
+
5716
+ `defineMoney(currency)` stores integer minor units as a JSON string. Arithmetic keeps the currency
5717
+ literal in the type; division and rational shares return an explicit remainder. It does not own
5718
+ exchange rates.
5719
+
5720
+ `defineUnitSystem` stores exact canonical decimal strings. Every conversion is an application
5721
+ rational with an id. A converted `QuantityProjection` is `derived` and carries both its source and
5722
+ conversion id; a form/database value can be marked `recorded`. Whether a derived projection is
5723
+ persisted or recomputed is an application decision.
5724
+
5725
+ `defineDeadlinePolicy` requires an explicit `now`, IANA timezone and elapsed/calendar-day boundary.
5726
+ It returns the due instant, signed remaining days, overdue days and caller-owned category key.
5727
+ `queryBoundary(now)` returns instants suitable for a data-adapter predicate; reminders and
5728
+ escalations are not included.
5729
+
5730
+ ## Audit and delivery
5731
+
5732
+ Put an audit decision beside each operation:
5733
+
5734
+ ```ts
5735
+ meta: { audit: audit.record(z.object({ changed: z.array(z.string()) })) }
5736
+ // or
5737
+ meta: { audit: audit.omit('read-only projection') }
5738
+ ```
5739
+
5740
+ `assertAuditDeclared(contract)` refuses silence. `createAuditRecord()` validates the change and
5741
+ returns a `DomainEvent`; the same id/value can feed both the journal and delivery.
5742
+
5743
+ `defineDomainEventDelivery().plan(event)` is pure. Save that event and its destinations in the
5744
+ application's transaction. After commit, call `dispatch(event.id)`: the dispatcher accepts only an
5745
+ id and obtains actual work from `DomainEventOutbox.claim`. A transport returns `delivered`,
5746
+ `retryable`, `terminal` or `unknown`; the dispatcher calls the matching outbox transition. Only
5747
+ `retry` may schedule another claim, while `unknown` is held for inspection instead of guessed into
5748
+ a retry. The application owns retention and scheduling. There is deliberately no scheduler or
5749
+ distributed lease.
5750
+
5751
+ ## Exports
5752
+
5753
+ `defineExportOperation` supplies one input and one result schema to an ordinary contract endpoint.
5754
+ A small result can be `ready` with a `ManagedFileRef`; a larger result can be `pending` with the id
5755
+ of the application's existing async operation. The input, scope, metadata and tool identity do not
5756
+ change. The application creates bytes and owns operation persistence; the managed-file boundary
5757
+ streams them.
5758
+
5759
+
5553
5760
  ==============================================================================
5554
5761
  # Guide: Application migration recipes (docs/guide/application-migration-recipes.md)
5555
5762
  ==============================================================================
@@ -7445,6 +7652,68 @@ const payload = await verifyJwt(token, env.JWT_SECRET)
7445
7652
  `extractToken(req, cookieName?)` reads a bearer token from the `Authorization`
7446
7653
  header, or from the named cookie.
7447
7654
 
7655
+ ## Telegram Mini Apps
7656
+
7657
+ A Mini App hands its backend an `initData` query string that Telegram signed
7658
+ with a key derived from the bot token. `verifyTelegramInitData` checks that
7659
+ signature and reports what the string says — and refuses with a **reason**,
7660
+ because an application answers a stale string differently from a forged one.
7661
+
7662
+ ```ts
7663
+ import { verifyTelegramInitData } from 'stitchkit/telegram'
7664
+ import { unauthorized } from 'stitchkit/contract'
7665
+ import { env } from './env'
7666
+
7667
+ const result = await verifyTelegramInitData({
7668
+ initData,
7669
+ botToken: env.BOT_TOKEN,
7670
+ maxAgeSeconds: 3600, // a signed string is otherwise valid forever
7671
+ })
7672
+ if (!result.valid) {
7673
+ // 'missing-hash' | 'signature-mismatch' | 'malformed' | 'expired'
7674
+ throw unauthorized(result.reason === 'expired' ? 'Reopen the app' : 'Invalid session')
7675
+ }
7676
+ const telegramId = result.data.user?.id
7677
+ ```
7678
+
7679
+ Three things it does that a hand-written check usually does not:
7680
+
7681
+ - the **signature is checked first**, before `auth_date` or anything else is
7682
+ read — an expiry inside an unverified payload is a number the sender chose;
7683
+ - digests are compared **without an early exit**, so the time a rejection takes
7684
+ is not a measurement of how much of the digest was right;
7685
+ - `raw` keeps every signed pair, so a field Telegram adds after this release
7686
+ does not need a release to reach.
7687
+
7688
+ `maxAgeSeconds` is optional and there is no default: only the application knows
7689
+ how long its own session is worth. Omitting it means the string never expires,
7690
+ which is a decision rather than an oversight.
7691
+
7692
+ The module is server-only and pulls in no bot library — the token never belongs
7693
+ in a browser bundle. `stitchkit/application/grammy` remains the lifecycle
7694
+ adapter for an injected grammY bot. Its companion,
7695
+ `classifyTelegramSendFailure`, names why a send was refused and separates *retry
7696
+ this send* from *stop addressing this recipient*:
7697
+
7698
+ ```ts
7699
+ import { classifyTelegramSendFailure } from 'stitchkit/telegram'
7700
+
7701
+ try {
7702
+ await bot.api.sendMessage(chatId, text)
7703
+ } catch (error) {
7704
+ const failure = classifyTelegramSendFailure(error)
7705
+ if (failure.recipientUnreachable) await markUnreachable(chatId, failure.reason)
7706
+ else if (failure.retryable) await requeue(chatId, failure.retryAfterSeconds)
7707
+ }
7708
+ ```
7709
+
7710
+ The two flags are separate because one answer cannot serve both. A rate limit is
7711
+ retryable and implicates nobody; a blocked user is unreachable and no retry
7712
+ helps; a message Telegram could not parse is *neither* — the recipient is fine
7713
+ and our payload is wrong, which is the case a list of substrings quietly counts
7714
+ against the user. An unrecognised refusal leaves the recipient reachable: losing
7715
+ a working subscriber forever costs more than one wasted send.
7716
+
7448
7717
  ## Cookies
7449
7718
 
7450
7719
  ```ts
@@ -9172,6 +9441,133 @@ additive** — adopting it changes nothing in your code. (See
9172
9441
  So upgrading is: read the `### ⚠️ Breaking changes` of every version *above* your
9173
9442
  current one *up to* your target, and apply each snippet.
9174
9443
 
9444
+ ## Released migration: 0.72.0
9445
+
9446
+ Nothing you *pass* changed. Both items are about types you read or build, and
9447
+ both stop the compiler rather than surprising you at runtime.
9448
+
9449
+ ### If you read `perKey` off `BoundedAdmissionPolicy`
9450
+
9451
+ `perKey` is now a union: the flat ceiling it always was, or `{ maxKeys, limits }`
9452
+ where the ceiling is resolved from the key. Declaring one is unchanged; reading
9453
+ one needs a narrowing.
9454
+
9455
+ ```ts
9456
+ // before
9457
+ const ceiling = policy.perKey?.maxConcurrent
9458
+
9459
+ // after
9460
+ const ceiling =
9461
+ policy.perKey && !('limits' in policy.perKey) ? policy.perKey.maxConcurrent : undefined
9462
+ ```
9463
+
9464
+ A shape carrying members of both branches — `{ maxKeys, maxConcurrent, limits }` —
9465
+ is refused at construction. It does *not* fail to typecheck: an excess-property
9466
+ check against a union admits any property some member declares, so this one is
9467
+ caught by `createBoundedAdmission`, not by `tsc`.
9468
+
9469
+ ### If you build a snapshot or a status by hand
9470
+
9471
+ `CreditWindowSnapshot` gained `waiting` (producers parked in the new waiting
9472
+ `acquire`) and `DiagnosticJournalStatus` gained `lock`. Both are produced by the
9473
+ framework, so reading them is unaffected — but a test double stops compiling:
9474
+
9475
+ ```ts
9476
+ // before
9477
+ const snapshot: CreditWindowSnapshot = { state: 'open', capacityBytes: 100, /* … */ }
9478
+
9479
+ // after
9480
+ const snapshot: CreditWindowSnapshot = { state: 'open', capacityBytes: 100, /* … */ waiting: 0 }
9481
+ ```
9482
+
9483
+ ## Released migration: 0.71.0
9484
+
9485
+ The Agent coding tools. Two of the three changes are visible to the compiler;
9486
+ the third is the one to read carefully, because nothing will point at it.
9487
+
9488
+ ### If you implement `authorize`
9489
+
9490
+ The operation union gained `edit`, `list` and `glob` and lost `patch`. An
9491
+ exhaustive matcher stops compiling and the compiler shows you every arm. **A
9492
+ matcher with a default branch does not**, and that is the dangerous case:
9493
+
9494
+ ```ts
9495
+ // before — and after this upgrade, silently wrong in both directions
9496
+ if (request.operation === 'patch') return reviewPatch(request)
9497
+ return true // now also authorizes edit, list and glob
9498
+ // …or: return false // now also kills edit_file
9499
+ ```
9500
+
9501
+ ```ts
9502
+ // after
9503
+ switch (request.operation) {
9504
+ case 'edit': return reviewEdit(request) // the old `patch` payload, unchanged
9505
+ case 'list':
9506
+ case 'glob': return true // or your own policy
9507
+ // …existing read / write / search / shell / artifact-read arms
9508
+ }
9509
+ ```
9510
+
9511
+ `write` gained `createsDirectories` — the workspace-relative directories the
9512
+ call would create, outermost first, reported **before** anything is created. A
9513
+ host that wants to refuse implicit directory creation now can.
9514
+
9515
+ ### If you call `apply_patch`
9516
+
9517
+ It is `edit_file`, and it is one call:
9518
+
9519
+ ```ts
9520
+ // before
9521
+ const read = await readFile({ path })
9522
+ await applyPatch({ path, baseSha256: read.sha256, oldText, newText, dryRun: true })
9523
+ await applyPatch({ path, baseSha256: read.sha256, oldText, newText, dryRun: false })
9524
+
9525
+ // after
9526
+ await editFile({ path, oldText, newText })
9527
+ ```
9528
+
9529
+ `expectedSha256` is optional and still refuses a stale base with `CONFLICT` when
9530
+ you pass it; `edit_file` returns the resulting `sha256`, so a chain of edits
9531
+ never needs to re-read. If you key an approval policy or a UI label on the string
9532
+ `apply_patch`, update the key — nothing will fail loudly.
9533
+
9534
+ ### If you match on `INTERNAL_SERVER_ERROR` from a coding tool
9535
+
9536
+ Ordinary outcomes no longer arrive that way. A missing file is `NOT_FOUND`, an
9537
+ existing file without `overwrite` is `CONFLICT`, an ambiguous snippet is
9538
+ `CONFLICT` with the occurrence count, a path outside the root is `FORBIDDEN`.
9539
+ Code that treated any coding-tool failure as an internal fault will now see
9540
+ codes it did not before; code that showed the model an empty error now has a
9541
+ sentence and a `hint` to show it. Host-level causes are unchanged and still
9542
+ scrubbed.
9543
+
9544
+ ### If you write files into new directories
9545
+
9546
+ Nothing to change: `write_file` creates missing parents inside the root. Read
9547
+ `createdDirectories` in the result if you want to notice a typo — a path that
9548
+ was a failure before is now a successful write into a new tree.
9549
+
9550
+ ### If you want a step to know its context budget
9551
+
9552
+ Opt in by reading it; nothing is injected for you:
9553
+
9554
+ ```ts
9555
+ loop: {
9556
+ prepareStep: ({ contextUsage }) => {
9557
+ const used = contextUsage?.usedTokens
9558
+ if (used?.provenance === 'unavailable') return {} // no step has landed yet
9559
+ const fraction = (used?.value ?? 0) / (contextUsage?.contextWindow ?? 1)
9560
+ // …render it wherever you put it
9561
+ return {}
9562
+ },
9563
+ }
9564
+ ```
9565
+
9566
+ Put it at the **tail** of the conversation rather than in the system
9567
+ instructions unless you have a reason: changing the system prompt on every step
9568
+ invalidates the provider's prefix cache for the whole conversation, and on a long
9569
+ run that is a multiple of the input cost.
9570
+
9175
9571
  ## Released migration: 0.70.0
9176
9572
 
9177
9573
  ### Descriptor-backed Agent filesystem containment
@@ -11811,6 +12207,8 @@ The browser-and-server entrypoint. Re-exports everything from
11811
12207
  | `createRetainedTopics` | function | retained last-value store for sticky events — [guide](../guide/realtime.md#sticky-events) |
11812
12208
  | `parseSSE` | function | parse an SSE `Response` into an async generator — [guide](../guide/client.md#sse) |
11813
12209
  | `parseNDJSON` | function | parse bounded fatal-UTF-8 NDJSON; blank keep-alives are skipped and `finalLine: 'require-newline'` can make the delimiter mandatory — [guide](../guide/client.md#ndjson) |
12210
+ | `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) |
12211
+ | `createBackoff` | function | exponential backoff with subtractive jitter as a value: `next()` / `reset()` |
11814
12212
  | `ContractStreamFrameSchema` / `ContractStreamFrame` | schema / _type_ | default on-the-wire `data` / safe `error` / `end` envelope of a contract-first stream |
11815
12213
  | `ContractStreamFraming` / `ContractStreamCompletion` | _types_ | opt-in item-vs-envelope framing and terminal-vs-stream-end completion policies |
11816
12214
  | `StreamFinalLinePolicy` | _type_ | permissive or newline-required final NDJSON line policy |
@@ -11854,6 +12252,10 @@ The browser-and-server entrypoint. Re-exports everything from
11854
12252
  | `RetainedTopics` | _type_ | the `createRetainedTopics` handle |
11855
12253
  | `ParseSSEOptions` | _type_ | options for `parseSSE` |
11856
12254
  | `ParseNDJSONOptions` | _type_ | options for `parseNDJSON` |
12255
+ | `BackoffPolicySchema` / `BackoffPolicy` | schema / _type_ | `minDelayMs`, `maxDelayMs` and a `0`–`1` jitter fraction; a ceiling below the floor is refused |
12256
+ | `Backoff` | _type_ | the backoff handle returned by `createBackoff` |
12257
+ | `ResumableIteratorConfig` | _type_ | caller-owned `open` / `advance` / `isTerminal`, retry policy, abort signal and `onAttempt` observer |
12258
+ | `ResumableAttempt` | _type_ | attempt number, the delay about to be waited and the error that caused it |
11857
12259
 
11858
12260
  ### Trace (client)
11859
12261
 
@@ -11965,6 +12367,51 @@ from the root `stitchkit`.
11965
12367
 
11966
12368
  ---
11967
12369
 
12370
+ ## `stitchkit/primitives`
12371
+
12372
+ Browser-and-server-safe declarations for facts an application wants to define once. See the
12373
+ [primitives guide](../guide/primitives.md). Storage, transactions, data-adapter predicates,
12374
+ schedules, transports and file generation remain application-owned.
12375
+
12376
+ | Export | Kind | Summary |
12377
+ |--------|------|---------|
12378
+ | `defineLifecycle` | function | immutable finite-state transition declaration, role/payload validation, available-action projection and transition event |
12379
+ | `LifecycleState` / `LifecycleTransitionDefinition` / `LifecycleDefinition` | _types_ | branded state value and the declaration inferred from application strings |
12380
+ | `LifecycleTransitionInput` / `LifecycleTransitionResult` / `LifecycleTransitionSuccess` / `LifecycleTransitionFailure` | _types_ | typed execution input and distinct transition/state, role and payload outcomes |
12381
+ | `LifecycleTransitionEventSchema` / `LifecycleTransitionEvent` | schema / _type_ | canonical event returned for application-atomic persistence |
12382
+ | `defineOwnerScope` | function | resolve one owner from identity or require the explicit `acrossAllOwners` capability |
12383
+ | `OwnerScope` / `OwnerScopeDefinition` / `OwnerScopeResolution` | _types_ | branded adapter input and its resolved/refused outcomes |
12384
+ | `definePermissionMatrix` | function | exhaustive role × operation decisions with server check and client capability projection |
12385
+ | `PermissionGrantMatrix` / `PermissionCheckResult` | _types_ | compile-time complete matrix and unknown/allowed/denied runtime result |
12386
+ | `defineMoney` / `createMoneySchema` | functions | fixed-currency JSON-safe minor-unit value and Zod schema |
12387
+ | `addMoney` / `subtractMoney` / `multiplyMoney` / `shareMoney` / `splitMoney` | functions | exact same-currency arithmetic with explicit indivisible remainder |
12388
+ | `Money` / `MoneyShare` / `MoneySplit` | _types_ | currency-literal value and remainder-bearing operation results |
12389
+ | `defineUnitSystem` / `createQuantitySchema` / `addQuantity` | functions | exact decimal quantities and caller-declared finite conversions |
12390
+ | `Quantity` / `UnitConversion` / `QuantityProjection` | _types_ | unit-literal value, rational conversion and recorded/derived provenance |
12391
+ | `QuantityProjectionSchema` | schema | transport-safe recorded/derived quantity union |
12392
+ | `defineDeadlinePolicy` | function | elapsed- or calendar-day projection with explicit timezone, threshold, current time and caller category keys |
12393
+ | `DeadlineResultSchema` / `DeadlineResult` | schema / _type_ | due instant, remaining/overdue days and projected category |
12394
+ | `audit` | constant | constructors for explicit `record(changeSchema)` or `omit(reason)` endpoint metadata |
12395
+ | `assertAuditDeclared` | function | refuse a contract operation with no audit decision |
12396
+ | `createAuditRecord` | function | validate one declared change and return the canonical event value |
12397
+ | `AuditPolicy` / `AuditRecordPolicy` / `AuditOmitPolicy` / `CreateAuditRecordInput` | _types_ | audit declaration and record input contracts |
12398
+ | `AuditRecordSchema` / `AuditRecord` | schema / _type_ | domain-event-shaped audit record |
12399
+ | `createDomainEventSchema` | function | wrap a typed payload in the canonical event envelope |
12400
+ | `DomainEventSchema` / `DomainEvent` | schema / _type_ | generic event with stable id, time, subject and optional actor |
12401
+ | `DomainEventActorSchema` / `DomainEventActor` | schema / _type_ | structured actor identity and application role |
12402
+ | `DomainEventSubjectSchema` / `DomainEventSubject` | schema / _type_ | generic subject type/id pair |
12403
+ | `defineDomainEventDelivery` | function | plan routes and dispatch only application-outbox claims by committed event id |
12404
+ | `DomainEventDestinationSchema` / `DomainEventDestination` | schema / _type_ | transport-neutral destination identity |
12405
+ | `DomainEventDeliveryOutcomeSchema` / `DomainEventDeliveryOutcome` | schema / _type_ | delivered, retryable, terminal or unknown transport result |
12406
+ | `DomainEventDeliveryClaimSchema` / `DomainEventDeliveryClaim` | schema / _type_ | application-owned atomic outbox claim |
12407
+ | `DomainEventOutbox` / `DomainEventRoute` / `DomainEventTransport` | _types_ | host capabilities composed by process-local delivery |
12408
+ | `DomainEventDeliveryPlan` / `DomainEventDispatchResult` | _types_ | transaction input and bounded dispatch summary |
12409
+ | `defineExportOperation` / `createExportResultSchema` | functions | one typed contract operation returning a ready managed file or pending operation id |
12410
+ | `scanMoneyNumberRisks` / `scanOwnerFilterRisks` | functions | source-text migration diagnostics with caller-owned identifiers |
12411
+ | `SourceText` / `SourceRisk` | _types_ | migration scanner input and exact path/line evidence |
12412
+
12413
+ ---
12414
+
11968
12415
  ## `stitchkit/server`
11969
12416
 
11970
12417
  Server-only. Builds and runs the HTTP server, and carries the server primitives.
@@ -12143,6 +12590,7 @@ Also re-exports the error helpers from `stitchkit/contract`.
12143
12590
  | `generateTraceId` | function | a fresh trace id |
12144
12591
  | `resolveTraceId` | function | the default per-request trace-id resolver |
12145
12592
  | `extractIp` | function | the caller IP from a request |
12593
+ | `isPublicIp` | function | whether an address belongs to the public internet; anything unparseable is not |
12146
12594
  | `resolveSocketIp` | function | the caller IP for a Socket.IO handshake (proxy-aware) |
12147
12595
  | `getClientInfo` | function | caller IP + user-agent |
12148
12596
  | `EventBus` | _type_ | the `createEventBus` handle |
@@ -12213,7 +12661,9 @@ cutovers are covered by the executable
12213
12661
  | Export | Kind | Summary |
12214
12662
  |--------|------|---------|
12215
12663
  | `createBoundedAdmission` | function | process-local no-queue global/per-key concurrency and rate leases, optionally composed with application admission |
12216
- | `BoundedAdmissionPolicySchema` / `BoundedAdmissionPolicy` | schema / _type_ | finite global budget and optional finite per-key budget with `maxKeys` |
12664
+ | `BoundedAdmissionPolicySchema` / `BoundedAdmissionPolicy` | schema / _type_ | finite global budget and optional finite per-key budget with `maxKeys`, declared either as one flat ceiling or as a resolver — never both |
12665
+ | `BoundedAdmissionPerKeyLimitsSchema` / `BoundedAdmissionPerKeyLimits` | schema / _type_ | one key's resolved ceiling: `maxConcurrent` and optional `rate` |
12666
+ | `BoundedAdmissionPerKeyLimitResolver` | _type_ | `(key) => limits`, resolved on a key's first admission and cached until the key is evicted |
12217
12667
  | `BoundedRateBudgetSchema` / `BoundedRateBudget` | schema / _type_ | `{ limit, intervalMs }` monotonic sliding-window budget |
12218
12668
  | `BoundedAdmissionStateSchema` / `BoundedAdmissionState` | schema / _type_ | `accepting \| draining \| closed` |
12219
12669
  | `BoundedAdmissionRefusalReasonSchema` / `BoundedAdmissionRefusalReason` | schema / _type_ | exact local/upstream refusal vocabulary |
@@ -12240,6 +12690,8 @@ cutovers are covered by the executable
12240
12690
  | `createCreditWindow` | function | finite byte-credit lease window with exact once-only replenishment |
12241
12691
  | `CreditWindow` / `CreditWindowSnapshot` / `CreditWindowSnapshotSchema` | _type_ / schema | byte-credit handle and absolute accounting record |
12242
12692
  | `CreditAcquireResult` / `CreditLease` | _type_ | reasoned refusal or idempotently releasable byte-credit lease |
12693
+ | `CreditAcquireWaitOptions` | _type_ | `signal` / `timeoutMs` for the waiting `acquire` overload; absent budget waits until credit, close or abort |
12694
+ | `CreditWaitResult` / `CreditWaitRefusalReason` | _type_ | waiting-acquire outcome; `insufficient-credit` is absent by construction, `timed-out` and `aborted` replace it |
12243
12695
 
12244
12696
  ### Bounded diagnostic journal
12245
12697
 
@@ -12248,6 +12700,7 @@ cutovers are covered by the executable
12248
12700
  | `createDiagnosticJournal` | function | create one schema-owned FIFO JSONL writer with bounded retained memory, exclusive local path ownership and finite rotation |
12249
12701
  | `DiagnosticJournalConfig` / `DiagnosticJournal` | _type_ | owner schema/path/limits/failure observer and the synchronous `submit`, bounded-wait `flush`/`close`, status handle |
12250
12702
  | `DiagnosticJournalLimitsSchema` / `DiagnosticJournalLimits` | schema / _type_ | positive event, pending-item, pending-byte, file-byte and retained-file limits |
12703
+ | `DiagnosticJournalLockPolicySchema` / `DiagnosticJournalLockPolicy` | schema / _type_ | `refuse` (default) or `reclaim-stale`, which reclaims only a lock whose recorded owner is provably gone |
12251
12704
  | `DiagnosticJournalSubmitResultSchema` / `DiagnosticJournalSubmitResult` | schema / _type_ | accepted epoch/sequence or explicit invalid, oversized, capacity, closed or failed refusal |
12252
12705
  | `DiagnosticJournalStatusSchema` / `DiagnosticJournalStatus` | schema / _type_ | state, limits, exact admission/write/failure counters, pending ownership, rotations, partial tails and last safe sequences |
12253
12706
  | `DiagnosticJournalFrameSchema` / `DiagnosticJournalFrame` | schema / _type_ | version-1 JSONL frame carrying process epoch, contiguous accepted sequence and schema-validated JSON event |
@@ -12397,6 +12850,8 @@ Server-only optional application runtime. See the
12397
12850
  | `AgentConversationMessagePageSchema` / `AgentConversationMessagePage` | schema / _type_ | cursor-paged durable message history |
12398
12851
  | `composeAgentPrompt` | function | ordered prompt contributions and provenance-aware signed context budget; irreducible reservation deficits are `oversized`, not compactable history |
12399
12852
  | `structuredCompaction` | function | summarize a provider-valid snapshot range and replace it through CAS |
12853
+ | `selectCompactableHistory` | function | which oldest whole complete turns may be summarised away — the half of compaction that needs no store (→ ADR 0142) |
12854
+ | `SelectCompactableHistoryOptions` / `CompactableHistory` | _type_ | message list, retained-turn count and evidence policy in; `leadingSummary`, `compactable` and `retained` out |
12400
12855
  | `createAgentSessionCoordinator` | function | strict process-local queue/interrupt/supersede lifecycle |
12401
12856
  | `AgentRuntimeStopPolicy` | _type_ | named custom AI SDK stop condition persisted and published on policy stop |
12402
12857
  | `AgentRuntimePrepareStep` | _type_ | per-run controlled step callback with typed domain context and managed run signal/fence |
@@ -12425,6 +12880,29 @@ transport adapters validate the same records. Runtime composition types are `Age
12425
12880
  `AgentSessionCoordinator`, `AgentCompactionContext`, `AgentCompactionResult` and
12426
12881
  `StructuredCompactionConfig`.
12427
12882
 
12883
+ A provider refusal is classified rather than phrased. `classifyProviderFailure` returns an
12884
+ `AgentProviderFailure` — an `AgentProviderFailureReason` (`insufficient-credits`, `rate-limited`,
12885
+ `model-unavailable`, `context-overflow`, `timeout`, `cancelled`, `unknown`), the provider's `status`
12886
+ when it supplied one, whether the same request is `retryable` unchanged, and the `evidence` the
12887
+ answer rests on: `status` is the provider stating its own answer, `message` is us reading its prose,
12888
+ and `none` is an honest refusal to guess. The sentence a user reads stays with the application —
12889
+ its tone and its decision about what to admit are not the core's to make. `isToolResultFailure`
12890
+ recognises a failure carried inside a *successful* tool result, in both the bare and the
12891
+ `{ value: … }` envelope. Both are plain functions and need no runtime. → ADR 0141
12892
+
12893
+ `normalizeOpenRouterUsage` is the same normalisation `openRouterProvider`
12894
+ applies, exported so an application calling the SDK directly gets provenance-correct numbers
12895
+ without adopting the runtime.
12896
+
12897
+ `AgentContextUsage` reaches every step through `AgentRuntimeRunContext.contextUsage`: how full the
12898
+ model's context is, as `usedTokens` (an `AgentUsageValue`, so it carries the provenance that says
12899
+ where the number came from) beside the model's declared `contextWindow`. It is the **last completed
12900
+ step's prompt size**, not the run's cumulative input tokens — cumulative counts every step's prompt
12901
+ again and is a multiple of the real fill. Before the first step lands there is no provider-reported
12902
+ number and the provenance is `unavailable`, which is a different fact from zero. No fraction is
12903
+ exposed: dividing is one line where it is rendered, and the output reserve belongs to the
12904
+ consumer's prompt budget rather than to this layer.
12905
+
12428
12906
  Canonical protocol exports are `AgentProtocol`, `AgentProtocolConfig`, `AgentTerminalAcceptance`,
12429
12907
  `AgentTerminalAcceptanceInput`, `hasAgentTerminalOutput`, `AgentRecordIdSchema`, `AgentRecordVersionSchema`,
12430
12908
  `AgentTimestampSchema`, `AgentJsonObjectSchema`, `AgentProviderEnvelopeSchema`,
@@ -12566,12 +13044,14 @@ explicit policy; use the Bun or Node SQLite leaf for durable reopen.
12566
13044
  ## `stitchkit/agent-runtime/coding-tools`
12567
13045
 
12568
13046
  Server-only evolving, peer-free direct runtime tools. `createAgentCodingTools(config)` returns
12569
- `read_file`, `write_file`, `search_files`, `apply_patch`, optional `run_command` and, when an
13047
+ `read_file`, `write_file`, `edit_file`, `list_directory`, `glob`, `search_files`, optional
13048
+ `run_command` and, when an
12570
13049
  artifact store is supplied, `read_output`.
12571
13050
 
12572
13051
  | Export | Kind | Summary |
12573
13052
  |--------|------|---------|
12574
- | `createAgentCodingTools` | function | construct direct host-authorized bounded file, search, guarded patch, shell and artifact runtime-tool definitions; filesystem operations use Linux `/proc/self/fd` or the packaged macOS Node-API backend and otherwise fail closed |
13053
+ | `createAgentCodingTools` | function | construct direct host-authorized bounded file, listing, glob, search, exact-snippet edit, shell and artifact runtime-tool definitions; every ordinary refusal is a typed code with an instructive `hint`, and filesystem operations use Linux `/proc/self/fd` or the packaged macOS Node-API backend and otherwise fail closed |
13054
+ | `AGENT_CODING_TOOL_NAMES` | const | the mounted tool names — `read_file`, `write_file`, `edit_file`, `list_directory`, `glob`, `search_files`, `run_command`, `read_output` |
12575
13055
  | `AgentCodingToolDefinition` | _type_ | peer-free structural direct-tool shape accepted by the canonical runtime-tool surface |
12576
13056
  | `AgentCodingToolConfig` | _type_ | absolute root, required authorization callback, finite executable alias map, exact child environment and optional limits |
12577
13057
  | `AgentCodingToolAuthorizationSchema` / `AgentCodingToolAuthorization` | schema / _type_ | discriminated read/write/search/patch/shell/artifact decision presented to host policy before effect |
@@ -13094,6 +13574,26 @@ available from `stitchkit/contract`.
13094
13574
 
13095
13575
  ---
13096
13576
 
13577
+ ## `stitchkit/telegram`
13578
+
13579
+ Peer-free server-only Telegram platform primitives. Importing this resolves no
13580
+ bot library; `stitchkit/application/grammy` remains the lifecycle adapter for an
13581
+ injected grammY bot. → ADR 0143
13582
+
13583
+ | Export | Kind | Summary |
13584
+ |--------|------|---------|
13585
+ | `verifyTelegramInitData` | function | verify a Mini App `initData` signature against the bot token in constant time, before reading anything out of it |
13586
+ | `VerifyTelegramInitDataOptions` | _type_ | raw `initData`, bot token, optional `maxAgeSeconds` bound and injected clock |
13587
+ | `TelegramInitDataVerification` | _type_ | `{ valid: true, data }` or a refusal carrying its reason |
13588
+ | `TelegramInitData` | _type_ | verified `user`/`receiver`, `authDate`, `ageSeconds`, `queryId`, `startParam`, `chatType`, `chatInstance` and every signed pair in `raw` |
13589
+ | `TelegramInitDataUser` | _type_ | camelCase user record inferred from Telegram's signed `user` payload |
13590
+ | `TelegramInitDataRefusal` | _type_ | `missing-hash` / `signature-mismatch` / `malformed` / `expired` — an expired string is not a forged one |
13591
+ | `classifyTelegramSendFailure` | function | name a refused Bot API send and separate "retry this send" from "stop addressing this recipient" |
13592
+ | `TelegramSendFailure` | _type_ | reason, `status`, Telegram-stated `retryAfterSeconds`, `retryable`, `recipientUnreachable` and which evidence produced the answer |
13593
+ | `TelegramSendFailureReason` | _type_ | `blocked-by-user` / `user-deactivated` / `chat-not-found` / `not-started` / `rate-limited` / `message-invalid` / `server-error` / `unknown` |
13594
+
13595
+ ---
13596
+
13097
13597
  ## `stitchkit/declaration`
13098
13598
 
13099
13599
  Zod-only, dependency-free. The **project declaration**: the single