stitchkit 0.118.1 → 0.119.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 (144) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/browser/http.d.ts.map +1 -1
  3. package/dist/contract/client-types.d.ts +6 -3
  4. package/dist/contract/client-types.d.ts.map +1 -1
  5. package/dist/contract/define-validation.d.ts.map +1 -1
  6. package/dist/contract/define.d.ts +18 -12
  7. package/dist/contract/define.d.ts.map +1 -1
  8. package/dist/contract/endpoint-capabilities.d.ts +28 -0
  9. package/dist/contract/endpoint-capabilities.d.ts.map +1 -0
  10. package/dist/contract/factory.d.ts +49 -19
  11. package/dist/contract/factory.d.ts.map +1 -1
  12. package/dist/contract/tool-view.d.ts +1 -1
  13. package/dist/contract/tool-view.d.ts.map +1 -1
  14. package/dist/contract/transports.d.ts +5 -0
  15. package/dist/contract/transports.d.ts.map +1 -0
  16. package/dist/entrypoints/agent-runtime/coding-tools.js +6 -5
  17. package/dist/entrypoints/agent-runtime/harness.js +6 -5
  18. package/dist/entrypoints/agent-runtime/sandbox.js +6 -5
  19. package/dist/entrypoints/agent-runtime/sqlite/bun.js +9 -8
  20. package/dist/entrypoints/agent-runtime/sqlite/node.js +9 -8
  21. package/dist/entrypoints/agent-runtime/testing.js +9 -8
  22. package/dist/entrypoints/agent-runtime.js +14 -13
  23. package/dist/entrypoints/application/diagnostic-journal.js +3 -3
  24. package/dist/entrypoints/application/directory-inbox.js +4 -4
  25. package/dist/entrypoints/application/opentelemetry.js +1 -1
  26. package/dist/entrypoints/application.js +5 -5
  27. package/dist/entrypoints/cli/publish.js +4 -4
  28. package/dist/entrypoints/cli.js +8 -7
  29. package/dist/entrypoints/contract.d.ts +1 -1
  30. package/dist/entrypoints/contract.d.ts.map +1 -1
  31. package/dist/entrypoints/contract.js +6 -4
  32. package/dist/entrypoints/files.js +3 -3
  33. package/dist/entrypoints/geo/contract.d.ts +7 -0
  34. package/dist/entrypoints/geo/contract.d.ts.map +1 -0
  35. package/dist/entrypoints/geo/contract.js +6 -0
  36. package/dist/entrypoints/geo.js +60 -23
  37. package/dist/entrypoints/http.js +2 -2
  38. package/dist/entrypoints/index.js +8 -6
  39. package/dist/entrypoints/live.js +1 -1
  40. package/dist/entrypoints/node.js +7 -6
  41. package/dist/entrypoints/primitives.js +1 -1
  42. package/dist/entrypoints/process/owner-loss.js +1 -1
  43. package/dist/entrypoints/process.js +3 -3
  44. package/dist/entrypoints/react.js +1 -1
  45. package/dist/entrypoints/remote.js +4 -3
  46. package/dist/entrypoints/server.js +11 -10
  47. package/dist/entrypoints/telegram.js +2 -2
  48. package/dist/entrypoints/testing.js +7 -6
  49. package/dist/entrypoints/tools/connections.js +3 -2
  50. package/dist/entrypoints/tools/contract.js +1 -1
  51. package/dist/entrypoints/tools/invoker.js +5 -4
  52. package/dist/entrypoints/tools/mcp.js +7 -6
  53. package/dist/entrypoints/tools.js +17 -15
  54. package/dist/entrypoints/tracking/contract.js +5 -3
  55. package/dist/entrypoints/tracking/performance.js +3 -3
  56. package/dist/entrypoints/tracking.d.ts +1 -1
  57. package/dist/entrypoints/tracking.d.ts.map +1 -1
  58. package/dist/entrypoints/tracking.js +63 -83
  59. package/dist/entrypoints/xlsx.js +1 -1
  60. package/dist/geo/index.d.ts +2 -1
  61. package/dist/geo/index.d.ts.map +1 -1
  62. package/dist/geo/record.d.ts +8 -0
  63. package/dist/geo/record.d.ts.map +1 -0
  64. package/dist/geo/resolver.d.ts +1 -3
  65. package/dist/geo/resolver.d.ts.map +1 -1
  66. package/dist/geo/schema.d.ts +44 -0
  67. package/dist/geo/schema.d.ts.map +1 -0
  68. package/dist/geo/types.d.ts +2 -13
  69. package/dist/geo/types.d.ts.map +1 -1
  70. package/dist/{index-n5sv4z0w.js → index-1k18qzqa.js} +13 -15
  71. package/dist/{index-tsngj2b5.js → index-2dfvv1jg.js} +206 -206
  72. package/dist/{index-d9hq291n.js → index-4jpykzvw.js} +70 -81
  73. package/dist/{index-ghtpfz4z.js → index-57gqaq8b.js} +2 -2
  74. package/dist/index-69t14s4a.js +39 -0
  75. package/dist/{index-6p3frkv5.js → index-6a69xzrx.js} +4 -4
  76. package/dist/{index-dky0dcmf.js → index-72r9k0av.js} +6 -2
  77. package/dist/{index-vv1sch8a.js → index-8vzh5wpv.js} +13 -8
  78. package/dist/{index-xx1j310k.js → index-8yxgcyt6.js} +171 -169
  79. package/dist/{index-p6apg0jg.js → index-9frf01t9.js} +16 -9
  80. package/dist/{index-q0th8q0z.js → index-9mjrnqxs.js} +1 -3
  81. package/dist/{index-ega3yz4c.js → index-aqwg2dm5.js} +1 -1
  82. package/dist/{index-073nw9qr.js → index-ba7are04.js} +524 -522
  83. package/dist/{index-095ya5hm.js → index-bbvc59gw.js} +13 -11
  84. package/dist/{index-txg1npmg.js → index-cyxp8rbf.js} +1 -1
  85. package/dist/index-d3xdyjt6.js +38 -0
  86. package/dist/{index-n8z3rvzw.js → index-f4njz9j0.js} +3 -5
  87. package/dist/{index-90p34rts.js → index-fcqj44mb.js} +3 -1
  88. package/dist/{index-86nbfysb.js → index-ff39f9dr.js} +3 -3
  89. package/dist/{index-75ns2ybj.js → index-fqd4yqwd.js} +1 -1
  90. package/dist/{index-nqxrm176.js → index-g444vfz2.js} +1 -1
  91. package/dist/{index-vwhrnp67.js → index-kz9734jc.js} +3 -3
  92. package/dist/{index-dr3t5n1v.js → index-nn9qvkww.js} +46 -46
  93. package/dist/{index-qezhmp51.js → index-nz06qw5a.js} +3 -1
  94. package/dist/{index-1bx1xv1k.js → index-pfvde2fz.js} +5 -2
  95. package/dist/{index-s80awwsw.js → index-qch1vcaa.js} +3 -1
  96. package/dist/{index-cycp3r3e.js → index-rxcgp4b9.js} +2 -2
  97. package/dist/{index-w7jq3fz9.js → index-s0qjs6ee.js} +1198 -1196
  98. package/dist/{index-p0rf7sq9.js → index-s20s2mad.js} +209 -209
  99. package/dist/{index-v6vb5qx8.js → index-s3yxw6kt.js} +1 -1
  100. package/dist/{index-cqmqyjax.js → index-sa99p98w.js} +1 -1
  101. package/dist/{index-0gxvns6s.js → index-sba3e6m4.js} +1 -0
  102. package/dist/{index-f24bdt13.js → index-v1swr26p.js} +3 -1
  103. package/dist/{index-rhzkndr5.js → index-x4p3kw1p.js} +533 -530
  104. package/dist/{index-hdjmdwsj.js → index-x8d9fdeg.js} +513 -507
  105. package/dist/{index-htxh46ej.js → index-ycqhyars.js} +2 -2
  106. package/dist/{index-9bwcthxw.js → index-yj6saztf.js} +31 -13
  107. package/dist/{index-tdpmfks8.js → index-ynrf908v.js} +1 -1
  108. package/dist/{index-f6m4c545.js → index-zrdcmg6j.js} +4 -6
  109. package/dist/process/bun-child.d.ts +3 -2
  110. package/dist/process/bun-child.d.ts.map +1 -1
  111. package/dist/server/contract-method.d.ts.map +1 -1
  112. package/dist/tools/internal/surface-projector.d.ts.map +1 -1
  113. package/dist/tools/operations/async-operation-canonical-types.d.ts +1 -1
  114. package/dist/tracking/attribution.d.ts +21 -25
  115. package/dist/tracking/attribution.d.ts.map +1 -1
  116. package/dist/tracking/client-visit.d.ts +5 -7
  117. package/dist/tracking/client-visit.d.ts.map +1 -1
  118. package/dist/tracking/client.d.ts +2 -2
  119. package/dist/tracking/client.d.ts.map +1 -1
  120. package/dist/tracking/contract.d.ts +1 -1
  121. package/dist/tracking/host.d.ts +0 -1
  122. package/dist/tracking/host.d.ts.map +1 -1
  123. package/dist/tracking/privacy.d.ts +6 -0
  124. package/dist/tracking/privacy.d.ts.map +1 -1
  125. package/dist/tracking/schemas.d.ts +2 -2
  126. package/dist/tracking/schemas.d.ts.map +1 -1
  127. package/llms/contract.txt +46 -24
  128. package/llms/geo-contract.txt +23 -0
  129. package/llms/geo.txt +40 -4
  130. package/llms/stitchkit.txt +4 -3
  131. package/llms/tools.2.txt +2 -2
  132. package/llms/tools.3.txt +1 -1
  133. package/llms/tracking-contract.txt +1 -1
  134. package/llms/tracking.txt +27 -5
  135. package/llms/{upgrading-0.105.0-0.118.0.txt → upgrading-0.106.0-0.119.0.txt} +121 -410
  136. package/llms/upgrading-0.37.0-0.53.0.txt +969 -0
  137. package/llms/{upgrading-0.44.0-0.62.0.txt → upgrading-0.55.0-0.68.0.txt} +509 -528
  138. package/llms/{upgrading-0.63.0-0.83.0.txt → upgrading-0.69.0-0.95.0.txt} +506 -509
  139. package/llms/{upgrading-0.84.0-0.104.0.txt → upgrading-0.96.0-0.105.0.txt} +410 -506
  140. package/llms/upgrading.txt +5 -5
  141. package/llms-full.txt +247 -38
  142. package/llms.txt +10 -9
  143. package/package.json +23 -19
  144. package/llms/upgrading-0.37.0-0.44.0.txt +0 -444
@@ -1,14 +1,132 @@
1
- # Upgrading 0.105.0 → 0.118.0 — stitchkit docs slice
1
+ # Upgrading 0.106.0 → 0.119.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.105.0 → 0.118.0 (docs/guide/upgrading.md)
6
+ Contains: Guide: Upgrading, 0.106.0 → 0.119.0 (docs/guide/upgrading.md)
7
7
 
8
8
  ==============================================================================
9
- # Guide: Upgrading, 0.105.0 → 0.118.0 (docs/guide/upgrading.md)
9
+ # Guide: Upgrading, 0.106.0 → 0.119.0 (docs/guide/upgrading.md)
10
10
  ==============================================================================
11
11
 
12
+ ## Released migration: 0.119.0
13
+
14
+ ### factory-default-exposure
15
+
16
+ **Who must act:** code passing arguments to `createContractFactory`, depending on its fixed-string
17
+ policy types or the renamed exposure types, calling HTTP clients of endpoints typed as the broad
18
+ `EndpointDef` / `FactoryScopedEndpoint`, or declaring multipart endpoints with non-HTTP exposure or
19
+ tool options.
20
+
21
+ Scope and default transports are inferred separately. Replace the constructor configuration:
22
+
23
+ ```ts
24
+ // before
25
+ const { defineContract } = createContractFactory<AppScope>({ toolExposure: 'explicit' })
26
+ // after: preserve the same curated HTTP-only default
27
+ const { defineContract } = createContractFactory<AppScope>().configure({
28
+ toolExposure: { default: ['HTTP'] },
29
+ })
30
+ ```
31
+
32
+ To deliberately expose ordinary operations through HTTP and CLI, choose `default: ['HTTP', 'CLI']`
33
+ and remove redundant matching endpoint arrays. Explicit arrays replace, rather than extend, the
34
+ default. Raw responses, multipart, raw bodies, response metadata, response streams and HEAD get only
35
+ HTTP from implicit defaults; a CLI-only or empty default leaves them hidden on every transport.
36
+ Explicit contradictions fail at definition; remove non-HTTP transports and tool options from multipart
37
+ endpoints rather than relying on their silent omission from tool catalogs.
38
+
39
+ `ExplicitToolExposureEndpoints` and `ExplicitScopedDefineContract` are renamed
40
+ `ConfiguredToolExposureEndpoints<Endpoints, Defaults>` and `ConfiguredScopedDefineContract<Scope, Defaults>`:
41
+ they describe materialization from any default tuple, their second generic, and HTTP-only when it is
42
+ omitted.
43
+ An optional or conditional endpoint override remains an alternative in the returned type. It does not
44
+ guarantee an HTTP client method unless every alternative includes HTTP.
45
+
46
+ HTTP clients and URL builders no longer assume a widened `Transport[]` contains
47
+ HTTP. Pass the literal array directly to `configure` when the HTTP surface is static;
48
+ its const generic infers the tuple. An annotation or `satisfies ContractFactoryConfig`
49
+ on a separately stored configuration does not preserve the tuple by itself.
50
+ Optional capabilities likewise retain the filtered alternative in endpoint types.
51
+
52
+ The rule reads the endpoint's type, with or without a factory. An endpoint returned by a helper
53
+ annotated `(): EndpointDef` or `(): FactoryScopedEndpoint<Scope>`, or holding a stored
54
+ `readonly Transport[]`, has no HTTP client method or URL, because that type allows an exposure
55
+ without HTTP. Keep the literal type with a const generic helper:
56
+
57
+ ```ts
58
+ // before
59
+ const read = (): FactoryScopedEndpoint<AppScope> => ({ method: 'GET', path: '/', desc: 'Read', output })
60
+ // after
61
+ const endpoint = <const Endpoint extends FactoryScopedEndpoint<AppScope>>(value: Endpoint) => value
62
+ const read = () => endpoint({ method: 'GET', path: '/', desc: 'Read', output })
63
+ ```
64
+
65
+ If every endpoint already has an explicit array, removing the constructor configuration alone also
66
+ preserves exposure. [ADR 0283](../decisions/0283-factory-default-exposure-is-materialized-by-capability.md).
67
+
68
+ Search patterns: `createContractFactory`, `toolExposure`, `ExplicitToolExposureEndpoints`,
69
+ `ExplicitScopedDefineContract`, `: EndpointDef`, `: FactoryScopedEndpoint`, and multipart endpoints
70
+ with `expose` or `tool`.
71
+
72
+ ### geo-full-snapshot
73
+
74
+ **Who must act:** code comparing `stitchkit/geo` results by equality, parsing them with a strict
75
+ schema of its own, mutating a returned `GeoAttribution`, or relying on empty labels.
76
+
77
+ The default mapper returns everything the City + ASN record states: continent, the registered
78
+ country as its own fields, GeoNames identifiers, `accuracyRadiusKm` and `localizedNames` with every
79
+ language. A record that used to give `{ city: 'Bangkok' }` now also gives
80
+ `localizedNames: { city: { en: 'Bangkok' } }`.
81
+
82
+ ```ts
83
+ // before: an application's copy of the shape
84
+ const StoredGeo = z.strictObject({ city: z.string().optional(), countryCode: z.string().optional() })
85
+ // after: the framework's schema, importable from shared code
86
+ import { GeoAttributionSchema } from 'stitchkit/geo/contract'
87
+ ```
88
+
89
+ `GeoAttribution` is readonly to its nested dictionaries; copy before changing a field. Empty labels
90
+ and coordinates outside ±90° / ±180° are omitted. The located country never borrows the registered
91
+ one; write `countryCode ?? registeredCountryCode` where a country for every address is wanted.
92
+
93
+ Search pattern: `stitchkit/geo`, `mapGeoIpRecord`, `GeoAttribution`, `resolve(`.
94
+
95
+ ### raw-tracking-attribution
96
+
97
+ **Who must act:** code passing `referrerMap` or `hostname` to `resolveAttribution`, importing
98
+ `ReferrerRule` or `parseReferrer`, relying on inferred, lowercased or retained UTM, or reading `utm`
99
+ from an event batch on the server.
100
+
101
+ Tracking sends raw attribution facts. Remove `referrerMap`, `ReferrerRule`, `parseReferrer` and the
102
+ resolver's `hostname`; `TrackingPageContext` and `browserTrackingHost()` no longer require or return
103
+ `hostname` either. Classify `referrer` and campaign tags on your server instead. Configure the
104
+ click-marker names your application recognises:
105
+
106
+ ```ts
107
+ // before
108
+ createTrackingClient({ ...config, referrerMap: [{ pattern: /search\.example/, source: 'search', medium: 'organic' }] })
109
+ // after
110
+ createTrackingClient({ ...config, clickIdParams: ['gclid', 'msclkid'] })
111
+ ```
112
+
113
+ Bootstrap and both touches carry `clickId`, the configured parameter's name without its value. Extend
114
+ your bootstrap DTO or stored attribution shape if you previously copied the framework's fields. No
115
+ marker is recognised unless configured. Real UTM retains its case and whitespace, and a tag without
116
+ `utm_source` is no longer discarded. Keep any channel normalisation in the server classifier.
117
+
118
+ `currentTouch` is the current capture's context; an untagged entry has no campaign even when a prior
119
+ one did. Implement last-non-direct retention on the server if required. First-touch storage uses a
120
+ versioned format and resets unversioned records once, because inferred and real tags in those records
121
+ cannot be distinguished. Without browser storage, current landing facts still reach bootstrap.
122
+
123
+ An event batch (`TrackEventsRequest`, the `request` schema of `createTrackingSchemas`) carries
124
+ `buildId` and `events` only. A server that read `request.utm` attributes events through their visit:
125
+ store the bootstrap entry's `utm`, `referrer` and `clickId` with the visit and join on `visitId`.
126
+
127
+ Search pattern: `referrerMap`, `ReferrerRule`, `parseReferrer`, `resolveAttribution`, `currentTouch`,
128
+ `request.utm`, `input.utm`.
129
+
12
130
  ## Released migration: 0.118.0
13
131
 
14
132
  ### telegram-due-cursor
@@ -480,410 +598,3 @@ as `{ kind: 'stopped', cause, exitCode, signal }`, with how the kernel reported
480
598
  3. A hook that stops an external scope on a stopped command now runs once the leader is gone,
481
599
  still bounded by `cleanupTimeoutMs`; no change is needed unless the leader waited for that scope
482
600
  to exit (it gets KILL when the grace ends either way).
483
-
484
- ## Released migration: 0.105.0
485
-
486
- ### CLI publication has its own entrypoint
487
-
488
- **Who must act:** code that imports `publishCli`, `CliPublicationOptions`, `CliPublicationPhase` or
489
- `CliPublicationResult` from `stitchkit/cli`. Everything else in `stitchkit/cli` — `createCli`, the
490
- manifest schemas, signatures, `renderCliInstaller` and the updater — is unchanged.
491
-
492
- `publishCli` is release infrastructure, so it now ships from the evolving entrypoint
493
- `stitchkit/cli/publish` instead of the stable `stitchkit/cli`
494
- ([ADR 0245](../decisions/0245-cli-publication-is-an-evolving-leaf.md)). Its options, result and
495
- behaviour are the same.
496
-
497
- 1. Move the publication names to the new specifier; keep every other CLI import where it is:
498
-
499
- ```ts
500
- // before
501
- import { publishCli, type CliPublicationOptions, verifyCliManifest } from 'stitchkit/cli'
502
- // after
503
- import { verifyCliManifest } from 'stitchkit/cli'
504
- import { publishCli, type CliPublicationOptions } from 'stitchkit/cli/publish'
505
- ```
506
-
507
- Search pattern: `\b(publishCli|CliPublication(Options|Phase|Result))\b` in files that import
508
- from `'stitchkit/cli'`.
509
- 2. Type-check: an import left on `stitchkit/cli` fails with "has no exported member".
510
- 3. `stitchkit/cli/publish` is evolving: read its breaking entries in each minor you upgrade across.
511
-
512
- ### a command stops by its `stop` policy
513
-
514
- **Who must act:** callers of `runNativeCommand` (`stitchkit/process`) that pass `killGraceMs`.
515
- Callers that omit it need no change: the default stop is the same TERM to the group, 100 ms, KILL.
516
-
517
- 1. Replace `killGraceMs: N` with a group stop of the same grace:
518
-
519
- ```ts
520
- // before
521
- await runNativeCommand({ executable, args, signal, killGraceMs: 500 })
522
- // after
523
- await runNativeCommand({ executable, args, signal, stop: { target: 'group', graceMs: 500 } })
524
- ```
525
-
526
- Search pattern: `killGraceMs:\s*(\d[\d_]*)` → `stop: { target: 'group', graceMs: $1 }`.
527
- 2. If your own wrapper signals only the leader and waits a long grace (cooperative cancellation),
528
- replace it with `stop: { target: 'leader', signal: 'SIGINT', graceMs }` (up to one hour). Pass
529
- `killOn` with your shutdown signal so an application that stops sooner kills the group first.
530
- 3. The result of a stopped command rejects once the group is gone, up to `graceMs` plus
531
- `cleanupTimeoutMs` after the abort; size your caller deadlines on that sum.
532
-
533
- ### a command stops what its leader left
534
-
535
- **Who must act:** callers of `runNativeCommand` (`stitchkit/process`) whose command starts a helper
536
- in its own process group that must keep running after the command returns. A daemon that detaches
537
- with `setsid` (as `daemon(3)` does) is not in the group and needs
538
- no change.
539
-
540
- `descendants` now defaults to `'terminate-after-leader'`
541
- ([ADR 0246](../decisions/0246-a-command-stops-its-descendants-by-default.md)): when the leader
542
- exits, the members it left in the group are stopped by the command's `stop` policy before the
543
- pipes drain.
544
-
545
- 1. Find the commands that start a background helper on purpose (a `&` in a shell script, a spawned
546
- child the leader does not wait for) and declare `'leave'` on them:
547
-
548
- ```ts
549
- // before
550
- await runNativeCommand({ executable, args, timeoutMs })
551
- // after
552
- await runNativeCommand({ executable, args, timeoutMs, descendants: 'leave' })
553
- ```
554
-
555
- 2. Remove an explicit `descendants: 'terminate-after-leader'`, or keep it: it is now the default.
556
- 3. A command whose helper inherited stdout or stderr used to wait for that helper until its
557
- deadline. It now completes when the leader exits; drop a workaround that killed the helper from
558
- `onLeaderSettled`.
559
-
560
- ### native packaging verifies published digests
561
-
562
- **Who must act:** build scripts that call `createNativePackaging` (`stitchkit/files/packaging`) and
563
- read `asset.sourcePath`, hash the addon themselves, switch over the refusal `state`/`code`, pass a
564
- `platform` other than the literal `'darwin'`, or parse `node_modules/stitchkit/native-assets.json`.
565
-
566
- The package now publishes the `size` and SHA256 of each Darwin addon, computed when Stitchkit is
567
- built, in `native-assets.json` at `formatVersion: 2`. `createNativePackaging` reads each selected
568
- addon once, compares it with that digest and returns the verified bytes. An installed addon that
569
- differs refuses with `NATIVE_ASSET_DIGEST_MISMATCH`.
570
-
571
- 1. Replace every read of `asset.sourcePath` with `asset.bytes`, and delete your own re-hash of the
572
- addon: the result's `sha256` is the published digest and `bytes` already match it.
573
-
574
- ```ts
575
- // before
576
- const bytes = readFileSync(asset.sourcePath)
577
- if (createHash('sha256').update(bytes).digest('hex') !== asset.sha256) throw …
578
- copyFileSync(asset.sourcePath, destination)
579
- // after
580
- writeFileSync(destination, asset.bytes)
581
- ```
582
-
583
- 2. Handle the third refusal. A `switch` over `result.state` gains `'mismatch'`
584
- (`code: 'NATIVE_ASSET_DIGEST_MISMATCH'`); treat it as a failed build, never as a missing addon.
585
- It carries the differing `architecture` with `expected` (published) and `actual` (installed)
586
- `{ size, sha256 }`; log both pairs.
587
- 3. Pass the target platform as the literal `'darwin'`. `platform` is a closed enum, so a free
588
- string (a target name read from configuration, `process.platform`) no longer type-checks, and
589
- another platform throws instead of returning `unsupported`. Skip packaging for non-Darwin
590
- targets before the call:
591
-
592
- ```ts
593
- // before
594
- const native = createNativePackaging({ platform: target.platform, architecture, … })
595
- if (native.state === 'unsupported') return
596
- // after
597
- if (target.platform !== 'darwin') return
598
- const native = createNativePackaging({ platform: target.platform, architecture, … })
599
- ```
600
-
601
- `unsupported` now means only an architecture the package has no addon for.
602
- 4. Call `createNativePackaging` once per build and pass the result (its `assets` and `plugin`)
603
- where it is needed. Each call reads and hashes the selected addons again.
604
- 5. If you read `native-assets.json` directly, read `assets.<arch>.path`, `.size` and `.sha256`
605
- instead of a bare path per architecture; an architecture with no entry is not published.
606
-
607
- ### exclusive locks publish their owner first
608
-
609
- **Who must act:** callers of `withExclusiveLock` (`stitchkit/files`) that rely on a lock file with no
610
- readable owner being taken by age, and anything that puts lock files on a filesystem without hard
611
- links. Every other caller needs no change.
612
-
613
- A lock is now written to a private `.lock-<id>.tmp` file in the lock's directory, then hard-linked to
614
- the lock name. The name never exists without a complete owner record, so `ownerlessGraceMs` defaults
615
- to `null` instead of 5 000 ms: a lock file with no readable owner is never taken because of its age.
616
-
617
- 1. If you depended on age-based reclaim of an empty lock file left by an older writer, opt in:
618
-
619
- ```ts
620
- // before: an empty lock older than 5 s was taken by default
621
- await withExclusiveLock(path, run)
622
- // after: say so; only legacy empty files need it
623
- await withExclusiveLock(path, run, { ownerlessGraceMs: 5_000 })
624
- ```
625
-
626
- 2. Keep lock directories on a filesystem that supports hard links (local ext4, xfs, APFS and the
627
- like). `link` failing with `EPERM` or `ENOTSUP` fails the acquisition.
628
- 3. Expect a `.lock-*.tmp` sibling only after a holder was killed between writing its record and
629
- publishing it. It is not a lock; the next reclaim in that directory removes the ones whose owner
630
- is gone on this machine. Exclude the pattern from directory-size caps or backups if you have any.
631
-
632
- ### one runtime-tool declaration
633
-
634
- **Who must act:** code that names `RuntimeToolExecution`, `RuntimeToolExecutionWithOutput`,
635
- `RuntimeMcpToolDefinition`, `RuntimeMcpToolDefinitionWithOutput` or `ToolSurfaceDefinition`; code
636
- that annotates a definition carrying `present` as `RuntimeToolDefinitionWithOutput<I, O>` from
637
- `stitchkit/tools`; imports of `stitchkit/agent-runtime/harness-tools`; code that compares agent
638
- control error codes; and callers that send a JSON array as text to a string-or-array tool field.
639
- Definitions returned by `defineRuntimeTool` / `createRuntimeToolFactory`, native CLI `commands` and
640
- contract tools need no change.
641
-
642
- A runtime tool now has one declaration, `RuntimeToolDefinitionWithOutput` /
643
- `RuntimeToolDefinitionWithoutOutput`, exported with the same name from `stitchkit/cli`,
644
- `stitchkit/tools` and `stitchkit/tools/mcp`. It carries no SDK type: the presenters are the adapter's
645
- extension, named as the fourth type argument. `RuntimeToolDefinition` is the one registration type.
646
-
647
- 1. Replace each removed name.
648
-
649
- | before | after |
650
- |---|---|
651
- | `RuntimeToolExecutionWithOutput<I, O>` (`stitchkit/cli`) | `RuntimeToolDefinitionWithOutput<I, O>` |
652
- | `RuntimeToolExecution` (`stitchkit/cli`) | `RuntimeToolDefinition` |
653
- | `RuntimeMcpToolDefinitionWithOutput<I, O>` (`stitchkit/tools/mcp`) | `RuntimeToolDefinitionWithOutput<I, O, undefined, RuntimeMcpToolPresenters<z.output<O>>>` |
654
- | `RuntimeMcpToolDefinition` (`stitchkit/tools/mcp`) | `RuntimeToolDefinition` |
655
- | `ToolSurfaceDefinition` (`stitchkit/tools`) | `ToolSurfaceProjection<RuntimeToolDefinition>` |
656
-
657
- 2. In `stitchkit/tools`, a type that names a definition with presenters passes the presenter type:
658
-
659
- ```ts
660
- // before
661
- const tool: RuntimeToolDefinitionWithOutput<typeof input, typeof output> = defineRuntimeTool({ …, present })
662
- // after: let defineRuntimeTool infer it, or name the presenters
663
- const tool = defineRuntimeTool({ …, present })
664
- const named: RuntimeToolDefinitionWithOutput<typeof input, typeof output, undefined, RuntimeToolPresenters<z.output<typeof output>>> = tool
665
- ```
666
-
667
- 3. An inline handler in `runtimeTools: [...]` needs no wrapper: it is typed by the parsed object.
668
- To type `input` from the schema, construct with `defineRuntimeTool` (or
669
- `satisfies RuntimeToolDefinitionWithOutput<typeof input, typeof output>`) before registering.
670
-
671
- 4. Replace `createAgentHarnessTools` (`stitchkit/agent-runtime/harness-tools`) with the call it made:
672
-
673
- ```ts
674
- // before
675
- tools: createAgentHarnessTools(({ context }) => ({ services: [svc], context, lifecycle: auth }))
676
- // after
677
- import { composeToolLifecycle, mountAgent } from 'stitchkit/tools'
678
- tools: ({ context, toolFenceLifecycle }) =>
679
- mountAgent([svc], { context, lifecycle: composeToolLifecycle(auth, toolFenceLifecycle) })
680
- ```
681
-
682
- 5. Agent control codes: `ACCESS_DENIED` is `FORBIDDEN`, and `CONNECTION_CLOSED` is returned for a closed
683
- connection. `AgentControlErrorCode` lists every code. `authorize` now runs once per request; after you
684
- withdraw someone's access, call `binding.revoke(conversationId, matches?)` so an open stream is
685
- re-authorized and ends with an `access-denied` delivery.
686
-
687
- 6. A string-or-array field that receives `'["a","b"]'` now fails with `VALIDATION_ERROR` ("`paths` is a
688
- list written as text"). Send the array itself. A string that is not a list the array member accepts
689
- (`[preview].png`) is still one plain string.
690
-
691
- ### flattened unions keep declaration order
692
-
693
- **Who must act:** consumers that use `flattenUnionInput: true` and keep snapshot tests, golden
694
- files or provider prompt caches of flattened tool schemas.
695
-
696
- A flattened union now lists its properties, enum values and branch labels in the order they first
697
- appear across the variants, not alphabetically. Declaration order often encodes priority, so the
698
- model now reads the order you wrote. Alternatives of different kinds (`anyOf`) keep their
699
- canonical order, independent of variant order.
700
-
701
- 1. Regenerate every snapshot or golden file of a flattened tool listing (`buildToolManifest`,
702
- `listToolNames`, MCP `tools/list`, Agent tool schemas) and review the diff: only ordering moves.
703
- 2. Expect one provider prompt-cache miss per flattened tool after the upgrade.
704
- 3. To keep an order you prefer, declare the variants and fields in that order.
705
-
706
- ### connection limits are validated
707
-
708
- **Who must act:** callers that pass `timeoutMs` or `maxResponseBytes` to a connection and relied
709
- on an invalid value silently becoming the default.
710
-
711
- `stitchkit/tools/connections` throws a `RangeError` when `timeoutMs` is not a positive integer
712
- within timer range (at most 2 147 483 647) or `maxResponseBytes` is not a positive safe integer.
713
- Before 0.104.0 a value of 0, a negative number, a fraction or an out-of-range number fell back to
714
- the default without a message.
715
-
716
- ```ts
717
- // Before: { timeoutMs: 0 } meant "use the default" by accident.
718
- // After: omit the option to take the default, or pass a real limit.
719
- { timeoutMs: 30_000 }
720
- ```
721
-
722
- ### one canonical JSON serializer
723
-
724
- **Who must act:** tests that call `serializeSurfaceValue` from `stitchkit/testing`, and code that
725
- puts a value nested deeper than 100 levels into agent store events, archives or projections
726
- through `canonicalAgentJson`.
727
-
728
- `canonicalJson` from `stitchkit/primitives` is the package's one canonical serializer. It
729
- admits dense arrays, plain objects, strings, booleans, `null` and finite numbers; an `undefined`
730
- object member is omitted. It refuses everything else with a `CanonicalJsonError`, a `TypeError`
731
- whose `reason` says why: `'depth'`, `'nodes'` and `'bytes'` are the limits you set, while
732
- `'cycle'`, `'negative-zero'` and `'not-json'` say the value is not JSON data.
733
-
734
- `serializeSurfaceValue` is removed. It produced the same bytes without limits:
735
-
736
- ```ts
737
- // Before
738
- import { serializeSurfaceValue } from 'stitchkit/testing'
739
- const text = serializeSurfaceValue(manifest)
740
- // After: the limits default to 100 levels, 100 000 nodes and 1 MiB; raise them for a large manifest
741
- import { canonicalJson } from 'stitchkit/primitives'
742
- const text = canonicalJson(manifest, { maxBytes: 16 * 1024 * 1024 })
743
- ```
744
-
745
- `canonicalAgentJson` in `stitchkit/agent-runtime` is `canonicalJson` with the node and byte
746
- limits at their maximum. A value both accepted has the same bytes as before, so stored digests
747
- and archives still match: like `z.json()` before it, the store hashes `-0` as `0` and drops an
748
- own `__proto__` member (the public `canonicalJson` refuses `-0` and keeps `__proto__`). What
749
- changed is the refusal table: an `undefined` object member used to fail and is now omitted, and
750
- a value nested deeper than 100 levels is now refused.
751
-
752
- ```ts
753
- // Before: canonicalAgentJson({ a: undefined }) threw a ZodError
754
- // After: canonicalAgentJson({ a: undefined }) === '{}'
755
- // canonicalAgentJson(nestedBeyond100Levels) throws CanonicalJsonError (reason 'depth')
756
- ```
757
-
758
- ### directory inbox entries
759
-
760
- **Who must act:** producers that drop hard links into an inbox directory, and callers that pass
761
- `accept` the output of a transforming schema.
762
-
763
- A file dropped into the inbox directory with more than one link is set aside as `invalid`
764
- (detail `managed file has multiple links`). Another name can still rewrite such a file after it
765
- was validated, so it is never trusted. Write each entry as its own regular file: a temporary
766
- name in the same directory, then a rename onto `<name>.json`.
767
-
768
- `accept` takes the schema's input type, stores that input as given and parses it with the
769
- schema on delivery. A schema without a transform has the same input and output type and needs
770
- no change. With a transform, pass the input:
771
-
772
- ```ts
773
- const schema = z.object({ amount: z.string().transform(Number) })
774
- // Before: accept({ source, key, entry: { amount: 42 } }) // typed as the output; `z.string()` refused it
775
- // After: accept({ source, key, entry: { amount: '42' } }) // delivered as { amount: 42 }
776
- ```
777
-
778
- A name that is already in `rejected/` no longer stops the pass: identical bytes are the entry
779
- already set aside, different bytes are kept as `rejected/<name>.<16 hex digits>`.
780
-
781
- ### journal startup scan
782
-
783
- **Who must act:** operators that read `status.recovery` right after `createDiagnosticJournal` to
784
- find damaged rows in the middle of a file.
785
-
786
- Opening a journal reads only the last line of the active file and of every retained
787
- generation (`startupScan: 'tails'`, the default). A crash tears the end of a file, so a torn tail
788
- is still reported and still rotated away, and opening no longer parses and validates every
789
- line of every generation. It still reads each file once in large blocks to count lines, so the time
790
- grows with the bytes retained, at read speed. Pass `startupScan: 'full'` to read and validate every line as before:
791
-
792
- ```ts
793
- // Before: every open parsed every line of every generation.
794
- // After: the same full scan, on request.
795
- const journal = await createDiagnosticJournal({ path, eventSchema, limits, startupScan: 'full' })
796
- ```
797
-
798
- ### journal startup quarantine
799
-
800
- **Who must act:** operators of a journal that is a source of truth and must not start over
801
- damaged retained files; code that constructs `DiagnosticJournalRecoveryError` or reads its
802
- `recovery`; protocols that embed `DiagnosticJournalRecoveryStatusSchema`, and their readers.
803
- A diagnostic journal needs no change.
804
-
805
- Opening a journal no longer refuses to start over a torn active file with `maxFiles: 1`, a
806
- generation name that is not a regular file or a retained file it cannot read. It renames the file
807
- to `<file>.quarantined-<epoch>` beside the journal, starts, and lists the move in
808
- `getStatus().recovery.quarantined`. Quarantined files are outside retention and never deleted;
809
- every later open lists them again, without a `reason`, until you remove them.
810
-
811
- 1. A journal whose damage must stop the process declares the old behaviour:
812
-
813
- ```ts
814
- // Before: refusing was the only behaviour.
815
- await createDiagnosticJournal({ path, eventSchema, limits })
816
- // After:
817
- await createDiagnosticJournal({ path, eventSchema, limits, onStartupRefusal: 'fail' })
818
- ```
819
-
820
- 2. Branch on the refusal's `reason` instead of its message or error class. Every startup refusal
821
- about retained evidence is a `DiagnosticJournalRecoveryError`, including the former plain
822
- `Error` for a non-file generation and the raw `EACCES` for an unreadable one (now in `cause`):
823
-
824
- ```ts
825
- // Before: catch (error) { if (error instanceof DiagnosticJournalRecoveryError) use(error.recovery) }
826
- // After:
827
- catch (error) {
828
- if (error instanceof DiagnosticJournalRecoveryError && error.reason === 'torn-without-retention-slot') use(error.recovery)
829
- }
830
- ```
831
-
832
- `recovery` is present only for `torn-without-retention-slot`. The constructor takes one
833
- options object: `new DiagnosticJournalRecoveryError({ reason, file, recovery?, cause? })`.
834
-
835
- 3. A protocol that embeds `DiagnosticJournalRecoveryStatusSchema` carries the new optional
836
- `quarantined` array (at most 32 entries, `reason` optional) and `quarantinedUnlisted` count.
837
- The schema is `.strict()`: upgrade every reader that parses it with its own copy before a
838
- writer on this version sends a status with either field, or bump your protocol version.
839
-
840
- ### error constructors take named options
841
-
842
- **Who must act:** code that constructs an `AppError` or an `ApiError` with anything beyond its
843
- code, and classes that extend `AppError` and call `super(code, message, status, …)`. Code that
844
- only throws the `notFound`, `badRequest`, `unauthorized`, `forbidden`, `conflict` and
845
- `rateLimited` helpers, or only reads `code`, `status`, `details`, `hint`, `traceId`,
846
- `retryable` and `cause` from a caught error, needs no change.
847
-
848
- `AppError` (`stitchkit/contract`, `stitchkit/server`, `stitchkit/node`) and `ApiError`
849
- (`stitchkit`) keep the code as the one positional argument. Every other value is a named
850
- option, so a call site reads without the signature open. `new AppError(code)` and
851
- `new ApiError(code)` are unchanged. The compiler finds every old call: a string or number
852
- in the second position is a type error.
853
-
854
- 1. `AppError`: move `message`, `status`, `details`, `hint`, `traceId` and `retryable` into one
855
- object after the code. `status` defaults to 500 and `message` to the code.
856
-
857
- ```ts
858
- // before
859
- throw new AppError('CONFLICT', 'Selection is stale', 409, { revision: 7 })
860
- throw new AppError('BUSY', 'Try later', 503, undefined, undefined, undefined, true)
861
- // after
862
- throw new AppError('CONFLICT', { message: 'Selection is stale', status: 409, details: { revision: 7 } })
863
- throw new AppError('BUSY', { message: 'Try later', status: 503, retryable: true })
864
- ```
865
-
866
- 2. A subclass passes the same object to `super`:
867
-
868
- ```ts
869
- // before
870
- super('FEATURE_LOCKED', 'locked', 403)
871
- // after
872
- super('FEATURE_LOCKED', { message: 'locked', status: 403 })
873
- ```
874
-
875
- 3. `ApiError`: the old order was `code, status, details, message, hint, traceId, errorOptions,
876
- retryable`. Name each value; `status` defaults to 0 and the former `ErrorOptions` argument
877
- is now the `cause` option.
878
-
879
- ```ts
880
- // before
881
- new ApiError('RATE_LIMITED', 429, undefined, 'slow down', 'wait', 'trace-1', { cause }, true)
882
- new ApiError('VALIDATION_ERROR', 0, { issues }, 'bad input')
883
- // after
884
- new ApiError('RATE_LIMITED', { status: 429, message: 'slow down', hint: 'wait', traceId: 'trace-1', retryable: true, cause })
885
- new ApiError('VALIDATION_ERROR', { status: 0, details: { issues }, message: 'bad input' })
886
- ```
887
-
888
- 4. Both classes accept the standard `cause`, which `error.cause` returns; omit the option to
889
- leave the property absent.