stitchkit 0.104.2 → 0.105.1
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.
- package/CHANGELOG.md +401 -13
- package/darwin-native.cjs +4 -2
- package/dist/agent-runtime/browser-control-contract.d.ts +32 -2
- package/dist/agent-runtime/browser-control-contract.d.ts.map +1 -1
- package/dist/agent-runtime/browser-controller.d.ts +21 -3
- package/dist/agent-runtime/browser-controller.d.ts.map +1 -1
- package/dist/agent-runtime/coding-shell-process.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-refusals.d.ts +1 -0
- package/dist/agent-runtime/coding-tool-refusals.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-shell.d.ts.map +1 -1
- package/dist/agent-runtime/compaction.d.ts.map +1 -1
- package/dist/agent-runtime/contained-write.d.ts.map +1 -1
- package/dist/agent-runtime/control-schema.d.ts +48 -1
- package/dist/agent-runtime/control-schema.d.ts.map +1 -1
- package/dist/agent-runtime/deferred-tool-search.d.ts +3 -2
- package/dist/agent-runtime/deferred-tool-search.d.ts.map +1 -1
- package/dist/agent-runtime/harness-control.d.ts.map +1 -1
- package/dist/agent-runtime/harness-realtime.d.ts +34 -4
- package/dist/agent-runtime/harness-realtime.d.ts.map +1 -1
- package/dist/agent-runtime/runtime-recovery.d.ts.map +1 -1
- package/dist/agent-runtime/runtime-state.d.ts.map +1 -1
- package/dist/agent-runtime/sandbox-contract.d.ts.map +1 -1
- package/dist/agent-runtime/sandbox-process-owner.d.ts +9 -1
- package/dist/agent-runtime/sandbox-process-owner.d.ts.map +1 -1
- package/dist/agent-runtime/sandbox.d.ts +32 -1
- package/dist/agent-runtime/sandbox.d.ts.map +1 -1
- package/dist/agent-runtime/schedule-records.d.ts.map +1 -1
- package/dist/agent-runtime/schedules.d.ts.map +1 -1
- package/dist/agent-runtime/store-events.d.ts +9 -4
- package/dist/agent-runtime/store-events.d.ts.map +1 -1
- package/dist/agent-runtime/testing.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-contract.d.ts +50 -0
- package/dist/application/diagnostic-journal-contract.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-generations.d.ts +24 -0
- package/dist/application/diagnostic-journal-generations.d.ts.map +1 -0
- package/dist/application/diagnostic-journal-manager.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-open.d.ts +10 -0
- package/dist/application/diagnostic-journal-open.d.ts.map +1 -0
- package/dist/application/diagnostic-journal-quarantine.d.ts +28 -0
- package/dist/application/diagnostic-journal-quarantine.d.ts.map +1 -0
- package/dist/application/diagnostic-journal-read-contract.d.ts +73 -3
- package/dist/application/diagnostic-journal-read-contract.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-reader.d.ts +17 -2
- package/dist/application/diagnostic-journal-reader.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-recovery.d.ts +18 -2
- package/dist/application/diagnostic-journal-recovery.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-storage.d.ts +6 -2
- package/dist/application/diagnostic-journal-storage.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-tail.d.ts +12 -0
- package/dist/application/diagnostic-journal-tail.d.ts.map +1 -0
- package/dist/application/diagnostic-journal.d.ts +3 -0
- package/dist/application/diagnostic-journal.d.ts.map +1 -1
- package/dist/application/directory-inbox-accept.d.ts +3 -3
- package/dist/application/directory-inbox-accept.d.ts.map +1 -1
- package/dist/application/directory-inbox-contract.d.ts +38 -10
- package/dist/application/directory-inbox-contract.d.ts.map +1 -1
- package/dist/application/directory-inbox-delivery.d.ts +3 -3
- package/dist/application/directory-inbox-delivery.d.ts.map +1 -1
- package/dist/application/directory-inbox-files.d.ts +17 -2
- package/dist/application/directory-inbox-files.d.ts.map +1 -1
- package/dist/application/directory-inbox-state.d.ts +1 -1
- package/dist/application/directory-inbox-state.d.ts.map +1 -1
- package/dist/application/directory-inbox.d.ts +1 -1
- package/dist/application/directory-inbox.d.ts.map +1 -1
- package/dist/application/file-state-store.d.ts +21 -1
- package/dist/application/file-state-store.d.ts.map +1 -1
- package/dist/application/grammy.d.ts.map +1 -1
- package/dist/application/kernel-contract.d.ts.map +1 -1
- package/dist/application/revision-signal.d.ts.map +1 -1
- package/dist/application/state-store.d.ts +3 -0
- package/dist/application/state-store.d.ts.map +1 -1
- package/dist/browser/api-error.d.ts +38 -0
- package/dist/browser/api-error.d.ts.map +1 -0
- package/dist/browser/chunked-upload.d.ts.map +1 -1
- package/dist/browser/client-executors.d.ts.map +1 -1
- package/dist/browser/client-url.d.ts.map +1 -1
- package/dist/browser/contract-stream.d.ts.map +1 -1
- package/dist/browser/http.d.ts +0 -25
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/browser/resumable.d.ts.map +1 -1
- package/dist/contract/define-validation.d.ts +0 -1
- package/dist/contract/define-validation.d.ts.map +1 -1
- package/dist/contract/define.d.ts.map +1 -1
- package/dist/contract/errors-factory.d.ts.map +1 -1
- package/dist/contract/errors.d.ts +15 -18
- package/dist/contract/errors.d.ts.map +1 -1
- package/dist/contract/normalize.d.ts.map +1 -1
- package/dist/durability/effect.d.ts.map +1 -1
- package/dist/durability/scheduler.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime/browser.js +29 -18
- package/dist/entrypoints/agent-runtime/coding-tools.js +8 -7
- package/dist/entrypoints/agent-runtime/harness.js +15 -14
- package/dist/entrypoints/agent-runtime/realtime.d.ts +1 -1
- package/dist/entrypoints/agent-runtime/realtime.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime/realtime.js +119 -54
- package/dist/entrypoints/agent-runtime/sandbox.js +23 -17
- package/dist/entrypoints/agent-runtime/sqlite/bun.js +4 -3
- package/dist/entrypoints/agent-runtime/sqlite/node.js +4 -3
- package/dist/entrypoints/agent-runtime/testing.js +8 -6
- package/dist/entrypoints/agent-runtime.js +37 -38
- package/dist/entrypoints/application/diagnostic-journal.js +305 -111
- package/dist/entrypoints/application/directory-inbox.js +174 -77
- package/dist/entrypoints/application/grammy.js +5 -2
- package/dist/entrypoints/application/opentelemetry.js +2 -2
- package/dist/entrypoints/application.d.ts +2 -2
- package/dist/entrypoints/application.d.ts.map +1 -1
- package/dist/entrypoints/application.js +17 -8
- package/dist/entrypoints/cli/publish.d.ts +16 -0
- package/dist/entrypoints/cli/publish.d.ts.map +1 -0
- package/dist/entrypoints/cli/publish.js +527 -0
- package/dist/entrypoints/cli.d.ts +1 -2
- package/dist/entrypoints/cli.d.ts.map +1 -1
- package/dist/entrypoints/cli.js +59 -966
- package/dist/entrypoints/contract.js +3 -3
- package/dist/entrypoints/files/packaging.js +161 -66
- package/dist/entrypoints/files.d.ts +1 -0
- package/dist/entrypoints/files.d.ts.map +1 -1
- package/dist/entrypoints/files.js +17 -10
- package/dist/entrypoints/index.d.ts +2 -1
- package/dist/entrypoints/index.d.ts.map +1 -1
- package/dist/entrypoints/index.js +31 -60
- package/dist/entrypoints/node.js +8 -8
- package/dist/entrypoints/observability.js +3 -3
- package/dist/entrypoints/primitives.js +67 -83
- package/dist/entrypoints/process.d.ts +1 -1
- package/dist/entrypoints/process.d.ts.map +1 -1
- package/dist/entrypoints/process.js +2 -2
- package/dist/entrypoints/react.js +4 -2
- package/dist/entrypoints/remote.js +37 -13
- package/dist/entrypoints/server.js +18 -16
- package/dist/entrypoints/telegram/screens.js +5 -3
- package/dist/entrypoints/telegram.js +31 -46
- package/dist/entrypoints/testing.d.ts +1 -1
- package/dist/entrypoints/testing.d.ts.map +1 -1
- package/dist/entrypoints/testing.js +444 -448
- package/dist/entrypoints/tools/connections.js +268 -105
- package/dist/entrypoints/tools/contract.js +2 -2
- package/dist/entrypoints/tools/invoker.js +8 -7
- package/dist/entrypoints/tools/mcp.d.ts +2 -2
- package/dist/entrypoints/tools/mcp.d.ts.map +1 -1
- package/dist/entrypoints/tools/mcp.js +10 -9
- package/dist/entrypoints/tools.d.ts +0 -1
- package/dist/entrypoints/tools.d.ts.map +1 -1
- package/dist/entrypoints/tools.js +118 -53
- package/dist/entrypoints/tracking.js +2 -3
- package/dist/files/boundary.d.ts +2 -2
- package/dist/files/boundary.d.ts.map +1 -1
- package/dist/files/chunk-spool.d.ts.map +1 -1
- package/dist/files/file-io.d.ts +2 -2
- package/dist/files/file-io.d.ts.map +1 -1
- package/dist/files/native-packaging-layout.d.ts +50 -4
- package/dist/files/native-packaging-layout.d.ts.map +1 -1
- package/dist/files/native-packaging.d.ts +78 -17
- package/dist/files/native-packaging.d.ts.map +1 -1
- package/dist/{index-r5hsgkz5.js → index-0gs4ymfj.js} +6 -3
- package/dist/{index-zax7resm.js → index-0gxvns6s.js} +1 -13
- package/dist/index-0s4xebbg.js +8 -0
- package/dist/{index-r4r70zmh.js → index-12zsej7b.js} +5 -2
- package/dist/{index-269zqj8m.js → index-1ptj98e0.js} +28 -12
- package/dist/{index-kemm0ej5.js → index-1pwwan01.js} +65 -22
- package/dist/{index-vgkbcnkc.js → index-299sm5tr.js} +1 -1
- package/dist/{index-2vnh05w4.js → index-2k91mewf.js} +31 -74
- package/dist/{index-wh0ecvyk.js → index-37asdw3a.js} +1 -1
- package/dist/index-3h0ztg0z.js +285 -0
- package/dist/{index-g95fk660.js → index-4vsby8n8.js} +8 -15
- package/dist/index-556g5hsh.js +17 -0
- package/dist/{index-f330qqyg.js → index-5c26631f.js} +244 -67
- package/dist/{index-adhgd4pe.js → index-5p55xreg.js} +2 -2
- package/dist/index-5w6t91ze.js +47 -0
- package/dist/{index-9pw0a51a.js → index-6yxhe58g.js} +40 -7
- package/dist/{index-6d9xaxw1.js → index-8ks9b85e.js} +33 -5
- package/dist/{index-bhb950qs.js → index-916yh4e2.js} +25 -12
- package/dist/index-9tqbxv5p.js +44 -0
- package/dist/{index-5csmzesf.js → index-azx12jgv.js} +4 -3
- package/dist/{index-mbnnw3f2.js → index-b0kddvzw.js} +15 -5
- package/dist/{index-n5yqxd3d.js → index-b0w7rbpt.js} +23 -2
- package/dist/index-b56nnscm.js +132 -0
- package/dist/{index-0ndk4555.js → index-c0hffcmj.js} +2 -2
- package/dist/{index-pptyv13k.js → index-c17xcx4k.js} +42 -6
- package/dist/index-cazts87s.js +105 -0
- package/dist/{index-g2j7pnak.js → index-cmzvhhtd.js} +5 -2
- package/dist/{index-ssmd41kh.js → index-d2h99n7d.js} +1 -1
- package/dist/{index-z94wht4y.js → index-dky0dcmf.js} +34 -5
- package/dist/index-drs41arz.js +455 -0
- package/dist/{index-za6j7nwy.js → index-dsnjzb62.js} +104 -51
- package/dist/index-eemvahkc.js +21 -0
- package/dist/{index-fa4wg312.js → index-f199nfq5.js} +10 -6
- package/dist/{index-ck71tbcf.js → index-fsw1ybf3.js} +11 -15
- package/dist/{index-fk18k56k.js → index-gr5g3xfw.js} +11 -18
- package/dist/{index-jn9g8xdt.js → index-hjvje0pf.js} +6 -4
- package/dist/index-j4zh647a.js +59 -0
- package/dist/{index-x928baz2.js → index-j9gxbztg.js} +18 -14
- package/dist/index-kfjkb396.js +37 -0
- package/dist/{index-6drst9sg.js → index-ksfagmyz.js} +45 -345
- package/dist/index-m616f0yz.js +120 -0
- package/dist/{index-fm1c1a08.js → index-m6w6jtda.js} +40 -21
- package/dist/{index-bakgedh8.js → index-msfpyy2f.js} +2 -4
- package/dist/{index-ywppsaby.js → index-n1nszyk0.js} +23 -34
- package/dist/{index-v14qqmys.js → index-n4689krb.js} +1 -1
- package/dist/{index-a0zzh8ak.js → index-pz3y6b6g.js} +3 -3
- package/dist/{index-43p08g7t.js → index-pzb63z66.js} +12 -8
- package/dist/{index-h97tqqp1.js → index-qaet40b2.js} +5 -3
- package/dist/{index-t1sr4aks.js → index-qaqbdm7s.js} +52 -6
- package/dist/{index-77nxk6ay.js → index-r393tnfp.js} +16 -10
- package/dist/{index-kb8styz3.js → index-rpzewktb.js} +15 -5
- package/dist/{index-27a2pj09.js → index-sj28htvf.js} +1 -1
- package/dist/{index-hbd32c6a.js → index-w8etpm8j.js} +38 -89
- package/dist/{index-2hyavjjj.js → index-wbhdzxar.js} +3 -3
- package/dist/{index-4e0ah203.js → index-wdy68sh6.js} +47 -24
- package/dist/{index-hp9d77wk.js → index-x7w1z6pf.js} +15 -4
- package/dist/{index-ptn0g0gj.js → index-y7k2cdgv.js} +21 -20
- package/dist/{index-20yvtn9a.js → index-yt7gyg5x.js} +1 -1
- package/dist/{index-cvda079a.js → index-yvfk3y36.js} +12 -2
- package/dist/{index-5z6jn897.js → index-z23rfxhj.js} +1 -1
- package/dist/internal/abort-race.d.ts +8 -2
- package/dist/internal/abort-race.d.ts.map +1 -1
- package/dist/internal/atomic-file.d.ts +55 -9
- package/dist/internal/atomic-file.d.ts.map +1 -1
- package/dist/internal/atomic-publication.d.ts +39 -4
- package/dist/internal/atomic-publication.d.ts.map +1 -1
- package/dist/internal/atomic-staging.d.ts +47 -0
- package/dist/internal/atomic-staging.d.ts.map +1 -0
- package/dist/internal/bounded-file-read.d.ts +19 -11
- package/dist/internal/bounded-file-read.d.ts.map +1 -1
- package/dist/internal/bounded-lines.d.ts.map +1 -1
- package/dist/internal/close-after.d.ts +11 -0
- package/dist/internal/close-after.d.ts.map +1 -0
- package/dist/internal/deadline.d.ts +10 -0
- package/dist/internal/deadline.d.ts.map +1 -1
- package/dist/internal/exclusive-lock-read.d.ts +10 -12
- package/dist/internal/exclusive-lock-read.d.ts.map +1 -1
- package/dist/internal/exclusive-lock.d.ts +2 -2
- package/dist/internal/exclusive-lock.d.ts.map +1 -1
- package/dist/internal/file-observation.d.ts +14 -0
- package/dist/internal/file-observation.d.ts.map +1 -0
- package/dist/internal/json-data.d.ts +22 -5
- package/dist/internal/json-data.d.ts.map +1 -1
- package/dist/internal/positive-integer.d.ts +9 -0
- package/dist/internal/positive-integer.d.ts.map +1 -0
- package/dist/internal/process-instance.d.ts +35 -5
- package/dist/internal/process-instance.d.ts.map +1 -1
- package/dist/internal/secure-fetch.d.ts.map +1 -1
- package/dist/internal/timers.d.ts +11 -0
- package/dist/internal/timers.d.ts.map +1 -0
- package/dist/internal/with-exclusive-lock.d.ts +5 -4
- package/dist/internal/with-exclusive-lock.d.ts.map +1 -1
- package/dist/observability/bounded-sink.d.ts.map +1 -1
- package/dist/primitives/canonical-json.d.ts +23 -2
- package/dist/primitives/canonical-json.d.ts.map +1 -1
- package/dist/primitives/delivery.d.ts.map +1 -1
- package/dist/primitives/index.d.ts +1 -1
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/money.d.ts.map +1 -1
- package/dist/process/cleanup.d.ts +7 -2
- package/dist/process/cleanup.d.ts.map +1 -1
- package/dist/process/command-lifetime.d.ts +6 -1
- package/dist/process/command-lifetime.d.ts.map +1 -1
- package/dist/process/command-owner.d.ts +1 -0
- package/dist/process/command-owner.d.ts.map +1 -1
- package/dist/process/command.d.ts +4 -0
- package/dist/process/command.d.ts.map +1 -1
- package/dist/process/contract.d.ts +68 -2
- package/dist/process/contract.d.ts.map +1 -1
- package/dist/process/group.d.ts +42 -3
- package/dist/process/group.d.ts.map +1 -1
- package/dist/process/launch.d.ts +12 -1
- package/dist/process/launch.d.ts.map +1 -1
- package/dist/process/terminate.d.ts +40 -0
- package/dist/process/terminate.d.ts.map +1 -0
- package/dist/react/query-client.d.ts.map +1 -1
- package/dist/realtime/rejection.d.ts.map +1 -1
- package/dist/server/contract-stream.d.ts.map +1 -1
- package/dist/server/handler-state.d.ts.map +1 -1
- package/dist/server/implement.d.ts.map +1 -1
- package/dist/server/request-body.d.ts +0 -1
- package/dist/server/request-body.d.ts.map +1 -1
- package/dist/server/unix-client.d.ts.map +1 -1
- package/dist/telegram/broadcast-delivery.d.ts.map +1 -1
- package/dist/telegram/broadcast-failure.d.ts +10 -2
- package/dist/telegram/broadcast-failure.d.ts.map +1 -1
- package/dist/telegram/operator-channel.d.ts.map +1 -1
- package/dist/telegram/screens/outcome.d.ts.map +1 -1
- package/dist/testing/surface-conformance.d.ts +1 -1
- package/dist/testing/surface-conformance.d.ts.map +1 -1
- package/dist/testing/surface-manifest.d.ts +0 -6
- package/dist/testing/surface-manifest.d.ts.map +1 -1
- package/dist/tools/agent.d.ts +1 -1
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/cli/args.d.ts +1 -1
- package/dist/tools/cli/args.d.ts.map +1 -1
- package/dist/tools/cli/help.d.ts.map +1 -1
- package/dist/tools/cli/invoke.d.ts +2 -2
- package/dist/tools/cli/invoke.d.ts.map +1 -1
- package/dist/tools/cli/profile.d.ts.map +1 -1
- package/dist/tools/cli/publication-assets.d.ts.map +1 -1
- package/dist/tools/cli/publication-contract.d.ts +8 -0
- package/dist/tools/cli/publication-contract.d.ts.map +1 -1
- package/dist/tools/cli/publication-control.d.ts.map +1 -1
- package/dist/tools/cli/publication-storage.d.ts +23 -2
- package/dist/tools/cli/publication-storage.d.ts.map +1 -1
- package/dist/tools/cli/publication.d.ts +8 -1
- package/dist/tools/cli/publication.d.ts.map +1 -1
- package/dist/tools/connections/errors.d.ts +18 -6
- package/dist/tools/connections/errors.d.ts.map +1 -1
- package/dist/tools/connections/limits.d.ts +8 -2
- package/dist/tools/connections/limits.d.ts.map +1 -1
- package/dist/tools/connections/mcp-client.d.ts.map +1 -1
- package/dist/tools/connections/mcp-envelope.d.ts.map +1 -1
- package/dist/tools/connections/openapi.d.ts.map +1 -1
- package/dist/tools/connections/schema-budget.d.ts +1 -1
- package/dist/tools/connections/schema-budget.d.ts.map +1 -1
- package/dist/tools/connections/sse-session.d.ts.map +1 -1
- package/dist/tools/execute-args.d.ts.map +1 -1
- package/dist/tools/execute-result.d.ts.map +1 -1
- package/dist/tools/internal/surface-projector.d.ts +17 -3
- package/dist/tools/internal/surface-projector.d.ts.map +1 -1
- package/dist/tools/internal/tool-error-projection.d.ts +16 -0
- package/dist/tools/internal/tool-error-projection.d.ts.map +1 -0
- package/dist/tools/internal/tool-view.d.ts.map +1 -1
- package/dist/tools/invoker.d.ts.map +1 -1
- package/dist/tools/list-names.d.ts +3 -2
- package/dist/tools/list-names.d.ts.map +1 -1
- package/dist/tools/manifest.d.ts +3 -2
- package/dist/tools/manifest.d.ts.map +1 -1
- package/dist/tools/mcp/handler.d.ts +7 -0
- package/dist/tools/mcp/handler.d.ts.map +1 -1
- package/dist/tools/mcp/mount.d.ts +18 -2
- package/dist/tools/mcp/mount.d.ts.map +1 -1
- package/dist/tools/mcp/prepare.d.ts +3 -3
- package/dist/tools/mcp/prepare.d.ts.map +1 -1
- package/dist/tools/mcp/register.d.ts.map +1 -1
- package/dist/tools/mcp/runtime-tools.d.ts.map +1 -1
- package/dist/tools/mcp/stdio-signals.d.ts +14 -0
- package/dist/tools/mcp/stdio-signals.d.ts.map +1 -1
- package/dist/tools/mcp/stdio.d.ts +7 -0
- package/dist/tools/mcp/stdio.d.ts.map +1 -1
- package/dist/tools/operations/async-operation-runtime-tools.d.ts.map +1 -1
- package/dist/tools/operations/async-operation-runtime-types.d.ts +3 -3
- package/dist/tools/operations/async-operation-runtime-types.d.ts.map +1 -1
- package/dist/tools/operations/define-wait-tool.d.ts +1 -1
- package/dist/tools/operations/define-wait-tool.d.ts.map +1 -1
- package/dist/tools/operations/wait-core.d.ts +0 -6
- package/dist/tools/operations/wait-core.d.ts.map +1 -1
- package/dist/tools/registry.d.ts +1 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/remote.d.ts +1 -1
- package/dist/tools/remote.d.ts.map +1 -1
- package/dist/tools/{runtime-tool-execution.d.ts → runtime-tool-declaration.d.ts} +32 -9
- package/dist/tools/runtime-tool-declaration.d.ts.map +1 -0
- package/dist/tools/runtime-tool-mcp.d.ts +6 -15
- package/dist/tools/runtime-tool-mcp.d.ts.map +1 -1
- package/dist/tools/runtime-tool.d.ts +14 -12
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools/schema/coerce.d.ts +4 -0
- package/dist/tools/schema/coerce.d.ts.map +1 -1
- package/dist/tools/schema/flatten-join.d.ts.map +1 -1
- package/dist/tools/surface.d.ts +7 -11
- package/dist/tools/surface.d.ts.map +1 -1
- package/dist/tools/transfer/define-download-tool.d.ts +1 -1
- package/dist/tools/transfer/define-download-tool.d.ts.map +1 -1
- package/dist/tools/transfer/define-upload-tool.d.ts +1 -1
- package/dist/tools/transfer/define-upload-tool.d.ts.map +1 -1
- package/dist/tools/transfer/define-view-file-tool.d.ts +1 -1
- package/dist/tools/transfer/define-view-file-tool.d.ts.map +1 -1
- package/dist/tools/transfer/managed-file-error.d.ts.map +1 -1
- package/dist/tools/transfer/view-file-contract.d.ts.map +1 -1
- package/dist/tools/transfer/view-file.d.ts +1 -1
- package/dist/tools/transfer/view-file.d.ts.map +1 -1
- package/dist/tools/transports.d.ts +3 -2
- package/dist/tools/transports.d.ts.map +1 -1
- package/llms/agent-runtime-browser.txt +10 -4
- package/llms/agent-runtime-harness.txt +32 -31
- package/llms/agent-runtime-react.txt +1 -1
- package/llms/agent-runtime-realtime.txt +14 -5
- package/llms/agent-runtime-sandbox.txt +3 -11
- package/llms/agent-runtime.3.txt +2 -2
- package/llms/agent-runtime.txt +0 -9
- package/llms/application.2.txt +3 -12
- package/llms/application.3.txt +7 -4
- package/llms/application.txt +44 -15
- package/llms/cli-publish.txt +29 -0
- package/llms/cli.2.txt +9 -11
- package/llms/cli.txt +26 -14
- package/llms/declaration.txt +0 -9
- package/llms/files-packaging.txt +18 -8
- package/llms/files.txt +4 -2
- package/llms/geo.txt +0 -9
- package/llms/live.txt +4 -13
- package/llms/observability.txt +31 -5
- package/llms/primitives.txt +38 -20
- package/llms/process.txt +324 -160
- package/llms/react-keyboard.txt +0 -9
- package/llms/react.txt +0 -18
- package/llms/server.4.txt +1 -1
- package/llms/server.5.txt +1 -1
- package/llms/server.txt +1 -1
- package/llms/stitchkit.txt +6 -7
- package/llms/telegram-screens.txt +2 -2
- package/llms/testing.txt +0 -1
- package/llms/tools-mcp.txt +2 -3
- package/llms/tools.2.txt +12 -0
- package/llms/tools.3.txt +5 -6
- package/llms/tools.txt +6 -4
- package/llms/upgrading-0.37.0-0.50.0.txt +947 -0
- package/llms/{upgrading-0.44.0-0.61.0.txt → upgrading-0.53.0-0.67.0.txt} +576 -506
- package/llms/{upgrading-0.62.0-0.80.0.txt → upgrading-0.68.0-0.94.0.txt} +562 -576
- package/llms/{upgrading-0.81.0-0.104.0.txt → upgrading-0.95.0-0.105.0.txt} +452 -567
- package/llms/upgrading.txt +4 -4
- package/llms-full.txt +1068 -412
- package/llms.txt +24 -24
- package/native-assets.json +11 -3
- package/package.json +6 -6
- package/dist/agent-runtime/harness-tools.d.ts +0 -11
- package/dist/agent-runtime/harness-tools.d.ts.map +0 -1
- package/dist/entrypoints/agent-runtime/harness-tools.d.ts +0 -2
- package/dist/entrypoints/agent-runtime/harness-tools.d.ts.map +0 -1
- package/dist/entrypoints/agent-runtime/harness-tools.js +0 -32
- package/dist/index-1a6jtnd9.js +0 -88
- package/dist/index-8a9ksm91.js +0 -95
- package/dist/index-a3wma1hk.js +0 -48
- package/dist/index-jd470k6q.js +0 -109
- package/dist/index-n1dqr7gf.js +0 -20
- package/dist/index-nggfa2yn.js +0 -34
- package/dist/index-s4evrmp5.js +0 -98
- package/dist/index-ze1eph1k.js +0 -20
- package/dist/tools/connections/error-projection.d.ts +0 -4
- package/dist/tools/connections/error-projection.d.ts.map +0 -1
- package/dist/tools/runtime-tool-execution.d.ts.map +0 -1
- package/llms/agent-runtime-harness-tools.txt +0 -25
- package/llms/upgrading-0.37.0-0.44.0.txt +0 -444
package/llms-full.txt
CHANGED
|
@@ -59,6 +59,7 @@ own, recorded as an ADR.
|
|
|
59
59
|
| `stitchkit/tools/invoker` | server | stable | peer-free `createToolInvoker` over the canonical contract tool runner |
|
|
60
60
|
| `stitchkit/tools/connections` | server (Bun or Node) | evolving | consume external MCP servers and a bounded OpenAPI subset — typed reauthorization, a per-request host fence and per-call credentials |
|
|
61
61
|
| `stitchkit/cli` | server | stable | `createCli` / `createCliInvoker` — runtime and strict NodeNext declarations without MCP SDK / `ai` |
|
|
62
|
+
| `stitchkit/cli/publish` | build and release tooling (Bun or Node, POSIX) | evolving | `publishCli` — commit a verified CLI distribution and move its public manifest last; see [CLI guide](cli.md#publishing-a-complete-version) |
|
|
62
63
|
| `stitchkit/remote` | browser **and** server | stable | peer-free `implementRemote` for thin HTTP proxy processes |
|
|
63
64
|
| `stitchkit/process` | server (Bun or Node, POSIX) | evolving | finite native commands, byte sinks, bounded capture and process-group cancellation |
|
|
64
65
|
| `stitchkit/files/packaging` | build time (Bun or Node) | evolving | installed native asset graph, integrity and Bun packaging plugin; no runtime install policy |
|
|
@@ -73,25 +74,24 @@ own, recorded as an ADR.
|
|
|
73
74
|
| `stitchkit/geo` | server (Bun or Node) | evolving | managed GeoIP reader generations, last-known-good reload and the optional MaxMind adapter |
|
|
74
75
|
| `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
|
|
75
76
|
| `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
|
|
76
|
-
| `stitchkit/observability` | server | stable<br>_redefined in 2 of the
|
|
77
|
+
| `stitchkit/observability` | server | stable<br>_redefined in 2 of the 50 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
|
|
77
78
|
| `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the managed-resource conformance kit |
|
|
78
79
|
| `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
|
|
79
80
|
| `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
|
|
80
81
|
| `stitchkit/react/keyboard` | browser | evolving | which part of a screen gets a key — ordered layers, one Escape per level, list zones with real focus; see [keyboard guide](keyboard.md) |
|
|
81
|
-
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in
|
|
82
|
+
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 23 of the 50 minors since 0.56.2, most recently 0.105.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
|
|
82
83
|
| `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
|
|
83
84
|
| `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults, deterministic race controls and the agent-store conformance kit |
|
|
84
85
|
| `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
|
|
85
86
|
| `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 |
|
|
86
87
|
| `stitchkit/agent-runtime/openrouter` | server | evolving | isolated OpenRouter language-model adapter |
|
|
87
88
|
| `stitchkit/agent-runtime/browser` | browser + server | evolving | canonical agent records, events, reconnect cursor and optional conversation controller |
|
|
88
|
-
| `stitchkit/agent-runtime/
|
|
89
|
-
| `stitchkit/agent-runtime/
|
|
90
|
-
| `stitchkit/agent-runtime/realtime` | server (Bun or Node) | evolving | authorized agent control over the existing Socket.IO server |
|
|
89
|
+
| `stitchkit/agent-runtime/react` | browser + server | evolving, experimental: no committed consumer | React subscription to an application-owned agent controller |
|
|
90
|
+
| `stitchkit/agent-runtime/realtime` | server (Bun or Node) | evolving, experimental: no committed consumer | authorized agent control over the existing Socket.IO server |
|
|
91
91
|
| `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
|
|
92
92
|
| `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
|
|
93
93
|
| `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
|
|
94
|
-
| `stitchkit/application` | browser + server | evolving<br>_redefined in
|
|
94
|
+
| `stitchkit/application` | browser + server | evolving<br>_redefined in 13 of the 50 minors since 0.56.2, most recently 0.105.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
|
|
95
95
|
| `stitchkit/application/grammy` | server | evolving | a grammY bot as application resources — configuration, then polling with updates admitted by batch — and the webhook adapter |
|
|
96
96
|
| `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
|
|
97
97
|
| `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
|
|
@@ -197,7 +197,6 @@ map — feature → packages:
|
|
|
197
197
|
| MCP / agent adapters (`stitchkit/tools`) | `@modelcontextprotocol/server` `ai` |
|
|
198
198
|
| Agent application runtime (`stitchkit/agent-runtime`) | `ai` |
|
|
199
199
|
| Headless Agent harness (`stitchkit/agent-runtime/harness`) | `ai` |
|
|
200
|
-
| Harness tools (`stitchkit/agent-runtime/harness-tools`) | `ai` `@modelcontextprotocol/server` (tool declarations) |
|
|
201
200
|
| Agent coding tools (`stitchkit/agent-runtime/coding-tools`) | — |
|
|
202
201
|
| OpenRouter runtime adapter (`stitchkit/agent-runtime/openrouter`) | `ai` `@openrouter/ai-sdk-provider` |
|
|
203
202
|
| SQLite agent store (`stitchkit/agent-runtime/sqlite/bun` or `/node`) | — (runtime built-in) |
|
|
@@ -1207,7 +1206,7 @@ Three boundaries worth knowing:
|
|
|
1207
1206
|
|
|
1208
1207
|
- A field of another scope is not a compile error on access — `RuntimeContext`
|
|
1209
1208
|
keeps its `[key: string]: unknown` index signature (transports write through
|
|
1210
|
-
it). It degrades to `unknown`, so it
|
|
1209
|
+
it). It degrades to `unknown`, so it cannot pose as a `string`; using it
|
|
1211
1210
|
in a typed position fails.
|
|
1212
1211
|
- The map states what **your** `beforeHandle` / `createAuthHook.inject` puts on
|
|
1213
1212
|
the context. The framework does not verify a hand-written claim (→ ADR 0075) —
|
|
@@ -4229,15 +4228,17 @@ For example, an optional object `value` in `single` and a required array `value`
|
|
|
4229
4228
|
in `group` advertise:
|
|
4230
4229
|
|
|
4231
4230
|
```text
|
|
4232
|
-
When kind = group: Two to ten references.
|
|
4233
4231
|
When kind = single: One reference.
|
|
4234
|
-
|
|
4232
|
+
When kind = group: Two to ten references.
|
|
4233
|
+
Available if kind = single | group. Required if kind = group
|
|
4235
4234
|
```
|
|
4236
4235
|
|
|
4237
4236
|
A third branch without `value` is absent from those labels. The object and array
|
|
4238
4237
|
retain their separate shapes; the array's minimum length does not apply to the object.
|
|
4239
|
-
Discriminator values, branch explanation labels and joined
|
|
4240
|
-
|
|
4238
|
+
Discriminator values, branch explanation labels, enum values and joined
|
|
4239
|
+
properties keep the order of their first appearance across the variants, so the
|
|
4240
|
+
order you declare is the order the model reads. Alternatives of different kinds
|
|
4241
|
+
keep one canonical order, so one declaration always yields one document. → ADR [0221](../decisions/0221-flat-field-explanations-retain-branch-meaning.md).
|
|
4241
4242
|
|
|
4242
4243
|
**A field several variants declare keeps its type.** Flattening puts every
|
|
4243
4244
|
variant's fields side by side, so a key two variants share has to be advertised
|
|
@@ -4487,6 +4488,18 @@ An explicit `AppError.retryable` boolean also survives this remote hop. `false`
|
|
|
4487
4488
|
`true` on409 remain the application's recommendation through HTTP, CLI, MCP and AI tools.
|
|
4488
4489
|
Absent metadata keeps the previous defaults and does not alter HTTP transport retries.
|
|
4489
4490
|
|
|
4491
|
+
A remote failure that carries no stitchkit error envelope — an HTML `503` from a proxy
|
|
4492
|
+
during a deploy, a `429` from a rate limiter, a `404` page, a refused connection — keeps
|
|
4493
|
+
what the caller can act on and drops what it must not see. A response with a status
|
|
4494
|
+
becomes `HTTP_ERROR` with that status and a fixed message; a failure with no response
|
|
4495
|
+
becomes `CONNECTION_REQUEST_FAILED` (`502`). Neither carries `details`: the upstream
|
|
4496
|
+
body, URL and credentials stay in the raw cause that `onToolError` and `AgentToolError.cause`
|
|
4497
|
+
expose in-process. One rule decides `retryable` for every origin: the declared value wins,
|
|
4498
|
+
otherwise the status decides (`408`, `429`, `502`, `503`, `504` are retryable, as for any
|
|
4499
|
+
local tool). A call the caller cancelled (`REQUEST_ABORTED`) declares `retryable: false`;
|
|
4500
|
+
a deadline the remote client hit (`REQUEST_TIMEOUT`, `408`) follows the status like a
|
|
4501
|
+
`408` from a local handler.
|
|
4502
|
+
|
|
4490
4503
|
## Structured output
|
|
4491
4504
|
|
|
4492
4505
|
When a contract endpoint or runtime tool declares `output`, its MCP tool
|
|
@@ -5147,15 +5160,6 @@ identical. Write the handler once; it serves every surface.
|
|
|
5147
5160
|
# Guide: Agent application runtime (docs/guide/agent-runtime.md)
|
|
5148
5161
|
==============================================================================
|
|
5149
5162
|
|
|
5150
|
-
---
|
|
5151
|
-
title: "Agent application runtime"
|
|
5152
|
-
description: Configure Stitchkit's optional durable history, stream loop, run coordination and managed-tool fencing.
|
|
5153
|
-
type: architecture
|
|
5154
|
-
status: active
|
|
5155
|
-
created: 2026-08-22
|
|
5156
|
-
updated: 2026-10-02 16:28 +07:00
|
|
5157
|
-
---
|
|
5158
|
-
|
|
5159
5163
|
# Agent application runtime
|
|
5160
5164
|
|
|
5161
5165
|
> **Maturity: evolving.** This surface is still finding its shape and may be
|
|
@@ -6697,20 +6701,6 @@ existing runtime configurations need no migration.
|
|
|
6697
6701
|
# Guide: Agent composition (docs/guide/agent-composition.md)
|
|
6698
6702
|
==============================================================================
|
|
6699
6703
|
|
|
6700
|
-
---
|
|
6701
|
-
title: "Agent composition without a second runtime"
|
|
6702
|
-
description: "Compose contract tools, authenticated realtime control, React views and exact-run behavioral checks."
|
|
6703
|
-
type: architecture
|
|
6704
|
-
status: active
|
|
6705
|
-
created: 2026-09-30 22:45 +07:00
|
|
6706
|
-
updated: 2026-09-30 23:03 +07:00
|
|
6707
|
-
participants:
|
|
6708
|
-
- role: authored
|
|
6709
|
-
harness: Codex
|
|
6710
|
-
model: GPT-6
|
|
6711
|
-
at: 2026-09-30 22:45 +07:00
|
|
6712
|
-
---
|
|
6713
|
-
|
|
6714
6704
|
# Compose an agent application
|
|
6715
6705
|
|
|
6716
6706
|
Use these pieces when an application wants the existing headless harness and a generic
|
|
@@ -6726,7 +6716,7 @@ import {
|
|
|
6726
6716
|
createAgentHarnessFileResources,
|
|
6727
6717
|
createHeadlessAgentHarness,
|
|
6728
6718
|
} from 'stitchkit/agent-runtime/harness';
|
|
6729
|
-
import {
|
|
6719
|
+
import { composeToolLifecycle, mountAgent } from 'stitchkit/tools';
|
|
6730
6720
|
|
|
6731
6721
|
const resources = createAgentHarnessFileResources({
|
|
6732
6722
|
roots: [{ id: 'guide', kind: 'instruction', path: instructionRoot }],
|
|
@@ -6735,27 +6725,31 @@ const resources = createAgentHarnessFileResources({
|
|
|
6735
6725
|
const harness = createHeadlessAgentHarness({
|
|
6736
6726
|
protocol, store, models, promptBudget,
|
|
6737
6727
|
resources,
|
|
6738
|
-
tools:
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6728
|
+
tools: ({ context, run, toolFenceLifecycle }) =>
|
|
6729
|
+
mountAgent([catalogService], {
|
|
6730
|
+
context: { ...context, messageId: run.assistantMessageId },
|
|
6731
|
+
runtimeTools: resources.runtimeTools,
|
|
6732
|
+
lifecycle: composeToolLifecycle(authorizeTools, toolFenceLifecycle),
|
|
6733
|
+
hooks: observeTools,
|
|
6734
|
+
}),
|
|
6745
6735
|
});
|
|
6746
6736
|
```
|
|
6747
6737
|
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
coercion, output stripping, hooks and durability
|
|
6753
|
-
|
|
6738
|
+
`tools` is called for each run with its validated context. Mount through `mountAgent` and
|
|
6739
|
+
compose your authorization with the run's `toolFenceLifecycle` by `composeToolLifecycle`: the
|
|
6740
|
+
fence rejects a call from a run that no longer owns the conversation, and it must run exactly
|
|
6741
|
+
once. Every mount option remains available, including registry, runtime tools, extension,
|
|
6742
|
+
coercion, output stripping, hooks and durability, and the callback may be async. The same callback
|
|
6743
|
+
still accepts arbitrary typed AI SDK tools and prepareStep. `stitchkit/tools` needs the same `ai` and
|
|
6744
|
+
`@modelcontextprotocol/server` peers as any `mountAgent` use; the base harness stays AI-only.
|
|
6754
6745
|
The resource reader is explicitly selected: filtered allowlists do not gain tools merely
|
|
6755
6746
|
because a loader exists. Root IDs, kinds, provenance and limits are unchanged.
|
|
6756
6747
|
|
|
6757
6748
|
## Server and browser
|
|
6758
6749
|
|
|
6750
|
+
`stitchkit/agent-runtime/realtime` and `stitchkit/agent-runtime/react` are evolving and
|
|
6751
|
+
experimental: no application has committed to them yet, so expect their shape to change.
|
|
6752
|
+
|
|
6759
6753
|
```ts
|
|
6760
6754
|
import { bindAgentHarnessRealtime } from 'stitchkit/agent-runtime/realtime';
|
|
6761
6755
|
|
|
@@ -6767,11 +6761,19 @@ const binding = bindAgentHarnessRealtime(harness, socket, {
|
|
|
6767
6761
|
},
|
|
6768
6762
|
onError: reportServerError,
|
|
6769
6763
|
});
|
|
6764
|
+
|
|
6765
|
+
// When an application withdraws someone's access, tell the binding.
|
|
6766
|
+
await removeMember(conversationId, userId);
|
|
6767
|
+
binding.revoke(conversationId, (identity) => identity.userId === userId);
|
|
6770
6768
|
```
|
|
6771
6769
|
|
|
6772
6770
|
`socket` is the existing `createSocketIOServer` handle, attached to `createServer` (Bun)
|
|
6773
|
-
or the Node adapter. The access policy checks every
|
|
6774
|
-
|
|
6771
|
+
or the Node adapter. The access policy checks every request, file/reference and metadata, and
|
|
6772
|
+
the attach that opens a stream. Events then flow under that grant without a call per token;
|
|
6773
|
+
Without a `revoke()` call a grant lasts until the socket disconnects or the conversation is detached: events keep flowing even if your own permission data changed. That fail-open default is deliberate (one check per request, not one per token), so call `revoke` whenever access is withdrawn. `revoke` ends the grant, the next event is re-authorized (`source: 'delivery'`), and a refusal
|
|
6774
|
+
or failure detaches the conversation and delivers `access-denied`, which puts the controller in
|
|
6775
|
+
`error` with code `FORBIDDEN` instead of leaving a frozen view. Session handshake
|
|
6776
|
+
authentication alone is insufficient.
|
|
6775
6777
|
Conversation ownership and observer/controller roles are application policy; the canonical
|
|
6776
6778
|
control server additionally enforces its exclusive controller lease.
|
|
6777
6779
|
|
|
@@ -6809,7 +6811,10 @@ view does not interrupt its remote run. Use explicit `interrupt` for cancellatio
|
|
|
6809
6811
|
Reconnect reattaches and reads durable state. Mutations are not retried automatically:
|
|
6810
6812
|
if an acknowledgement is lost, retain the original idempotency key. Incoming event
|
|
6811
6813
|
buffers and outgoing requests have finite limits; capacity/overflow/lease failures stay
|
|
6812
|
-
visible
|
|
6814
|
+
visible, each with a code from the one `AgentControlErrorCode` vocabulary. A reconnect
|
|
6815
|
+
rejects the requests of the previous connection at once and attaches again, so they
|
|
6816
|
+
cannot hold capacity the new attach needs. A server overflow deliberately disconnects the
|
|
6817
|
+
transport; reconnect explicitly.
|
|
6813
6818
|
|
|
6814
6819
|
## Check behavior, not just admission
|
|
6815
6820
|
|
|
@@ -6837,15 +6842,6 @@ The helper does not invoke a model or poll; the existing runtime ticket owns exe
|
|
|
6837
6842
|
# Guide: Managed application kernel (docs/guide/application-kernel.md)
|
|
6838
6843
|
==============================================================================
|
|
6839
6844
|
|
|
6840
|
-
---
|
|
6841
|
-
title: Managed application kernel
|
|
6842
|
-
description: Compose process-local resources, schedules, readiness, drain and optional provider adapters without building a second job platform.
|
|
6843
|
-
type: architecture
|
|
6844
|
-
status: active
|
|
6845
|
-
created: 2026-08-23
|
|
6846
|
-
updated: 2026-10-04 13:28 +07:00
|
|
6847
|
-
---
|
|
6848
|
-
|
|
6849
6845
|
# Managed application kernel
|
|
6850
6846
|
|
|
6851
6847
|
> **Maturity: evolving.** This surface is still finding its shape and may be
|
|
@@ -7029,9 +7025,8 @@ lifecycle context; the application decides when a database/provider probe runs.
|
|
|
7029
7025
|
|
|
7030
7026
|
**A `reportHealth` call inside `start` is kept.** A resource that says nothing
|
|
7031
7027
|
is assumed healthy once it is ready; one that reports its own health has already
|
|
7032
|
-
answered the question, and the answer stands.
|
|
7033
|
-
|
|
7034
|
-
overwrote it.)
|
|
7028
|
+
answered the question, and the answer stands. The readiness default never
|
|
7029
|
+
overwrites it.
|
|
7035
7030
|
|
|
7036
7031
|
**Reporting the same health again is not a change.** A resource may confirm its
|
|
7037
7032
|
health on a timer; a report with the value it already reported publishes
|
|
@@ -7527,9 +7522,48 @@ The caller owns file selection and coordination with an active rotator.
|
|
|
7527
7522
|
Writer startup inspects retained and active files under its lock. On damage,
|
|
7528
7523
|
`journal.getStatus().recovery` contains counts and first/last anomaly locations, reconstructed
|
|
7529
7524
|
again on restart from the preserved bytes. This describes startup observations, not live
|
|
7530
|
-
continuous inspection.
|
|
7531
|
-
|
|
7532
|
-
|
|
7525
|
+
continuous inspection. Normal rotation retains the existing finite eviction policy.
|
|
7526
|
+
→ [ADR 0219](../decisions/0219-journal-damage-is-data-and-io-failure-is-failure.md).
|
|
7527
|
+
|
|
7528
|
+
A file opening cannot keep in place — a torn active file with `maxFiles: 1`, a generation name
|
|
7529
|
+
that is not a regular file, a retained file it cannot read — is renamed to
|
|
7530
|
+
`<file>.quarantined-<epoch>` beside it, and the journal starts (`onStartupRefusal: 'quarantine'`,
|
|
7531
|
+
the default). Nothing is deleted; quarantined files are outside retention and yours to remove.
|
|
7532
|
+
Every open lists them in `getStatus().recovery.quarantined` until you do: the ones it moved
|
|
7533
|
+
first, with their `reason`, then the ones earlier runs left, without one — at most 32, and
|
|
7534
|
+
`quarantinedUnlisted` counts the rest.
|
|
7535
|
+
A journal whose damage must stop the process — one that is a source of truth, not diagnostics —
|
|
7536
|
+
declares `onStartupRefusal: 'fail'` and gets a typed `DiagnosticJournalRecoveryError`:
|
|
7537
|
+
|
|
7538
|
+
```ts
|
|
7539
|
+
import { DiagnosticJournalRecoveryError } from 'stitchkit/application'
|
|
7540
|
+
|
|
7541
|
+
// A diagnostic journal: start, then say what is set aside.
|
|
7542
|
+
const journal = await createDiagnosticJournal({ ...config })
|
|
7543
|
+
const { quarantined = [], quarantinedUnlisted = 0 } = journal.getStatus().recovery ?? {}
|
|
7544
|
+
for (const entry of quarantined) {
|
|
7545
|
+
// `reason` is present only for a file this start moved; an earlier run's has none.
|
|
7546
|
+
internalLogger.warn({ file: entry.file, movedTo: entry.quarantinedAs, reason: entry.reason })
|
|
7547
|
+
}
|
|
7548
|
+
if (quarantinedUnlisted > 0) internalLogger.warn({ quarantinedUnlisted })
|
|
7549
|
+
|
|
7550
|
+
// A journal that is a source of truth: refuse to start over damage.
|
|
7551
|
+
try {
|
|
7552
|
+
await createDiagnosticJournal({ ...config, onStartupRefusal: 'fail' })
|
|
7553
|
+
} catch (error) {
|
|
7554
|
+
if (error instanceof DiagnosticJournalRecoveryError) {
|
|
7555
|
+
// error.reason: 'torn-without-retention-slot' | 'not-a-regular-file' | 'unreadable'
|
|
7556
|
+
}
|
|
7557
|
+
throw error
|
|
7558
|
+
}
|
|
7559
|
+
```
|
|
7560
|
+
|
|
7561
|
+
A quarantining journal writes, so the managed resource that owns it reports `healthy`, and its
|
|
7562
|
+
dependants read `recovery` from the value they `use()`. There is no degraded journal: the kernel
|
|
7563
|
+
degrades only optional resources, and a required resource cannot depend on one.
|
|
7564
|
+
`DiagnosticJournalRecoveryStatusSchema` is evolving and `.strict()`; if you embed it in your own
|
|
7565
|
+
protocol, every change to its shape is listed under breaking changes.
|
|
7566
|
+
→ [ADR 0240](../decisions/0240-a-diagnostic-journal-quarantines-what-it-cannot-keep.md).
|
|
7533
7567
|
|
|
7534
7568
|
Shutdown performs one phase barrier at a time: stop admission everywhere,
|
|
7535
7569
|
cancel future schedules, drain admitted work, then close in reverse stable
|
|
@@ -7983,17 +8017,6 @@ boundary is required. Send and projection callbacks own their external deadlines
|
|
|
7983
8017
|
# Guide: Optional process sandbox (docs/guide/sandbox.md)
|
|
7984
8018
|
==============================================================================
|
|
7985
8019
|
|
|
7986
|
-
---
|
|
7987
|
-
title: Optional process sandbox
|
|
7988
|
-
description: Linux namespace isolation, durable workspaces and a host credential gateway.
|
|
7989
|
-
updated: 2026-10-03 14:47 +07:00
|
|
7990
|
-
participants:
|
|
7991
|
-
- role: implemented
|
|
7992
|
-
harness: Codex
|
|
7993
|
-
model: GPT-6
|
|
7994
|
-
at: 2026-10-03 14:47 +07:00
|
|
7995
|
-
---
|
|
7996
|
-
|
|
7997
8020
|
# Optional process sandbox
|
|
7998
8021
|
|
|
7999
8022
|
`stitchkit/agent-runtime/sandbox` is a server-only, evolving API. Existing coding
|
|
@@ -8057,6 +8080,9 @@ access the same workspace through the host's contained-file implementation;
|
|
|
8057
8080
|
commands run inside the namespace with `/workspace` as their root. The host-only
|
|
8058
8081
|
`handle.coding` binding never belongs in model input or persisted state.
|
|
8059
8082
|
Unsupported backends omit the binding and the factory refuses explicitly.
|
|
8083
|
+
When the adapter's `probe()` or `prepare()` fails, the agent gets `SANDBOX_UNAVAILABLE` with a
|
|
8084
|
+
fixed reason; the adapter's own error is the refusal's `cause`, delivered to the mount's
|
|
8085
|
+
`hooks.onToolError`, which is where an application logs it.
|
|
8060
8086
|
|
|
8061
8087
|
Stop also kills coding commands. Both launch paths share the configured
|
|
8062
8088
|
`maxConcurrentCommands` admission limit (default 8); an occupied slot gives
|
|
@@ -8125,37 +8151,25 @@ be refused explicitly by the backend.
|
|
|
8125
8151
|
# Guide: Native IO (docs/guide/native-io.md)
|
|
8126
8152
|
==============================================================================
|
|
8127
8153
|
|
|
8128
|
-
---
|
|
8129
|
-
title: Конечные команды и файловые гарантии
|
|
8130
|
-
description: Native IO для Bun и Node, с явно выбранной durability, безопасными bounds и caller lifetime.
|
|
8131
|
-
type: guide
|
|
8132
|
-
status: active
|
|
8133
|
-
created: 2026-10-01 20:44 +07:00
|
|
8134
|
-
updated: 2026-10-04 20:04 +07:00
|
|
8135
|
-
participants:
|
|
8136
|
-
- role: authored
|
|
8137
|
-
harness: Codex
|
|
8138
|
-
model: GPT-6
|
|
8139
|
-
at: 2026-10-01 20:44 +07:00
|
|
8140
|
-
- role: implemented
|
|
8141
|
-
harness: Codex
|
|
8142
|
-
model: GPT-6
|
|
8143
|
-
at: 2026-10-02 00:08 +07:00
|
|
8144
|
-
- role: implemented
|
|
8145
|
-
harness: Codex
|
|
8146
|
-
model: GPT-6
|
|
8147
|
-
at: 2026-10-03 13:36 +07:00
|
|
8148
|
-
- role: implemented
|
|
8149
|
-
harness: Codex
|
|
8150
|
-
model: GPT-6
|
|
8151
|
-
at: 2026-10-04 09:21 +07:00
|
|
8152
|
-
---
|
|
8153
|
-
|
|
8154
8154
|
# Native IO
|
|
8155
8155
|
|
|
8156
|
-
|
|
8156
|
+
Shortest working example: publish a file atomically, then run one bounded command.
|
|
8157
|
+
|
|
8158
|
+
```ts
|
|
8159
|
+
import { writeFileAtomic } from 'stitchkit/files'
|
|
8160
|
+
import { runNativeCommand } from 'stitchkit/process'
|
|
8161
|
+
|
|
8162
|
+
await writeFileAtomic('/srv/app/state.json', '{"version":2}')
|
|
8163
|
+
const { exitCode, stdout } = await runNativeCommand({
|
|
8164
|
+
executable: '/usr/bin/git', args: ['rev-parse', 'HEAD'], cwd: '/srv/app',
|
|
8165
|
+
capture: true, maxOutputBytes: 4096, timeoutMs: 5000,
|
|
8166
|
+
})
|
|
8167
|
+
console.log(exitCode, new TextDecoder().decode(stdout))
|
|
8168
|
+
```
|
|
8157
8169
|
|
|
8158
|
-
|
|
8170
|
+
The examples use public imports and run the same in Bun and Node >= 22.
|
|
8171
|
+
|
|
8172
|
+
## Writing a file
|
|
8159
8173
|
|
|
8160
8174
|
```ts
|
|
8161
8175
|
import { writeFileAtomic, AtomicFilePublicationError } from 'stitchkit/files'
|
|
@@ -8168,7 +8182,7 @@ try {
|
|
|
8168
8182
|
})
|
|
8169
8183
|
} catch (error) {
|
|
8170
8184
|
if (error instanceof AtomicFilePublicationError) {
|
|
8171
|
-
// target
|
|
8185
|
+
// The target is already visible. Check the receipt before deciding whether to retry.
|
|
8172
8186
|
console.error(error.phase, error.cause)
|
|
8173
8187
|
} else throw error
|
|
8174
8188
|
}
|
|
@@ -8177,25 +8191,51 @@ await writeFileAtomic('/srv/app/state.json', '{"version":2}', {
|
|
|
8177
8191
|
})
|
|
8178
8192
|
```
|
|
8179
8193
|
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
Create
|
|
8185
|
-
|
|
8194
|
+
The defaults are `replace: true`, `durability: 'file'` and `mode: 0o600`: file fsync and atomic
|
|
8195
|
+
replacement. `durability: 'none'` skips every sync. `'directory'` runs the full sequence: write all
|
|
8196
|
+
bytes, exact descriptor chmod, file fsync, rename (replace) or link (create), remove the staging
|
|
8197
|
+
link, parent directory fsync. `writeFileAtomicSync` takes the same options and runs the same order.
|
|
8198
|
+
Create fails with the native `EEXIST` when the target exists, is a symlink, or a concurrent writer
|
|
8199
|
+
won. Never turn that refusal into a replace automatically.
|
|
8200
|
+
|
|
8201
|
+
`AtomicFilePublicationError.published === true` tells a visible target from an unconfirmed
|
|
8202
|
+
acknowledgement. `phase` is `cleanup`, `directory-sync` or `directory-close`; the primary cause is
|
|
8203
|
+
kept in `cause`. An error before publication leaves the previous target untouched. Cleanup errors are
|
|
8204
|
+
secondary to the primary error. While a create is in flight a second hardlink briefly exists, so a
|
|
8205
|
+
strict single-link reader may refuse in that window.
|
|
8206
|
+
|
|
8207
|
+
### Abandoned staging files
|
|
8208
|
+
|
|
8209
|
+
The bytes are staged beside the target under `.stitchkit-<24 lowercase hex>.tmp`, the same name for
|
|
8210
|
+
`writeFileAtomic`, `writeFileAtomicSync`, the managed writer and the chunk spool. A process killed
|
|
8211
|
+
between the staging and the publication (SIGKILL, OOM, power loss) leaves that file behind, and no
|
|
8212
|
+
later write removes it: a random name alone cannot tell an abandoned write from one in flight.
|
|
8213
|
+
Removing it is the caller's responsibility. A directory that does not exist yet has nothing to
|
|
8214
|
+
sweep and yields `[]`; a file standing where the directory should be, or a denied listing, throws:
|
|
8215
|
+
|
|
8216
|
+
```ts
|
|
8217
|
+
import { isAtomicStagingName, sweepAtomicStaging } from 'stitchkit/files'
|
|
8218
|
+
|
|
8219
|
+
// At startup, or before trusting that a directory holds only your own records:
|
|
8220
|
+
const removed = await sweepAtomicStaging({ directory: '/srv/app/registry', olderThanMs: 60 * 60_000 })
|
|
8186
8221
|
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
При create кратко существует второй hardlink; strict single-link reader вправе отказать в этом окне.
|
|
8222
|
+
// Skip staging files when listing a directory of your records:
|
|
8223
|
+
const records = (await readdir('/srv/app/registry')).filter((name) => !isAtomicStagingName(name))
|
|
8224
|
+
```
|
|
8191
8225
|
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
`
|
|
8196
|
-
|
|
8226
|
+
`olderThanMs` is required and must exceed the longest write in flight; a younger staging file is
|
|
8227
|
+
left alone. The sweep removes only regular files, never follows a symlink and never enters a
|
|
8228
|
+
subdirectory. The name form is a stable public contract: changing it is a breaking change of
|
|
8229
|
+
`stitchkit/files`. An exclusive lock's `.lock-*.tmp` file is not an atomic-write staging file and
|
|
8230
|
+
does not match.
|
|
8197
8231
|
|
|
8198
|
-
|
|
8232
|
+
Directory fsync is a native POSIX filesystem capability. Windows refuses it explicitly, and another
|
|
8233
|
+
filesystem may refuse at open or sync. A check on Linux does not confirm macOS behavior, network
|
|
8234
|
+
filesystems or survival of a power loss; the disk state is whatever the filesystem and OS guarantee.
|
|
8235
|
+
`createManagedFileBoundary.write(..., { durable: true })` uses the same publish owner and parent sync;
|
|
8236
|
+
its create stays a create, and `replace: true` allows replacement explicitly.
|
|
8237
|
+
|
|
8238
|
+
## Strict reads
|
|
8199
8239
|
|
|
8200
8240
|
```ts
|
|
8201
8241
|
import { createManagedFileBoundary } from 'stitchkit/files'
|
|
@@ -8206,25 +8246,27 @@ const source = await files.read('receipt.json', {
|
|
|
8206
8246
|
})
|
|
8207
8247
|
console.log(source.observation) // dev, ino, size, nlink, mtimeMs, ctimeMs
|
|
8208
8248
|
const receipt = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(source.bytes))
|
|
8209
|
-
//
|
|
8249
|
+
// Schema parsing and digest checks belong to the application.
|
|
8210
8250
|
```
|
|
8211
8251
|
|
|
8212
|
-
|
|
8213
|
-
`rejectSymlinks`
|
|
8214
|
-
`stable`
|
|
8215
|
-
`
|
|
8216
|
-
`FILE_UNSUPPORTED`
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8252
|
+
Without options the result is `{ ref, bytes }` and a trusted in-root symlink or hardlink is
|
|
8253
|
+
allowed. `rejectSymlinks` refuses a symlink at the requested leaf, `singleLink` checks `nlink` of
|
|
8254
|
+
the opened descriptor, `stable` compares dev/ino/size/mtime/ctime/nlink around the byte read, and
|
|
8255
|
+
`observe` adds the metadata. These refusals are distinct: `FILE_NOT_FOUND`, `FILE_NOT_REGULAR`,
|
|
8256
|
+
`FILE_UNSAFE_LINK`, `FILE_TOO_LARGE`, `FILE_CHANGED`, `FILE_UNSUPPORTED` and `FILE_IO_ERROR`. Abort
|
|
8257
|
+
keeps the caller's reason. A nonblocking open lets a FIFO be refused without waiting for a writer.
|
|
8258
|
+
Bytes are bounded while reading, including a file that grows, not only by the initial stat. Chunk
|
|
8259
|
+
allocation is at most `maxBytes + 1 + 64 KiB` and the flattened result at most `maxBytes`. The
|
|
8260
|
+
descriptor closes on success, refusal, abort and IO error. A close error keeps the primary read error;
|
|
8261
|
+
after a successful read a close error is returned to the caller.
|
|
8221
8262
|
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
snapshot
|
|
8263
|
+
The root and its ancestors belong to a trusted actor. These options do not give
|
|
8264
|
+
descriptor-relative containment against a hostile concurrent ancestor replacement, and there is no
|
|
8265
|
+
switch to treat ancestors as safe: the portable owner has no such capability. Metadata stability does
|
|
8266
|
+
not prove an immutable snapshot against an arbitrary hostile writer; use an application lock or
|
|
8267
|
+
immutable publication.
|
|
8226
8268
|
|
|
8227
|
-
##
|
|
8269
|
+
## Commands
|
|
8228
8270
|
|
|
8229
8271
|
```ts
|
|
8230
8272
|
import { runNativeCommand } from 'stitchkit/process'
|
|
@@ -8246,21 +8288,23 @@ await runNativeCommand({
|
|
|
8246
8288
|
})
|
|
8247
8289
|
```
|
|
8248
8290
|
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
`
|
|
8253
|
-
|
|
8254
|
-
`
|
|
8255
|
-
|
|
8256
|
-
|
|
8291
|
+
A command needs a caller `signal` or a finite `timeoutMs` (1 to 2147483647 ms, the native timer
|
|
8292
|
+
range; a larger value is refused, never turned into 1 ms). Streaming runs for the caller's lifetime,
|
|
8293
|
+
with no hidden 60-second timeout or 1 MiB output limit. `capture` defaults to false; `true` requires
|
|
8294
|
+
`maxOutputBytes` for the combined stdout and stderr. The limit counts source bytes; the API returns
|
|
8295
|
+
`Uint8Array` and does not decode invalid UTF-8. The result holds the observed `exitCode: number | null`
|
|
8296
|
+
and `signal: string | null`; a signal termination is never turned into an invented exit code.
|
|
8297
|
+
`maxOutputBytes` may bound streaming too. `stdin` is bytes bounded by `maxStdinBytes` (default 1 MiB).
|
|
8298
|
+
`envPolicy` defaults to `declared-only`: no `env` means an empty environment, and `ambient` inherits
|
|
8299
|
+
explicitly. The application owns cwd and executable policy; the primitive launches a native
|
|
8300
|
+
executable and never creates a shell.
|
|
8257
8301
|
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8302
|
+
`NativeCommandError.reason` classifies a limit error by machine: `deadline` when the command's own
|
|
8303
|
+
`timeoutMs` expires, `output-budget` when the combined stdout and stderr bytes exceed the limit, in
|
|
8304
|
+
capture or streaming. Both keep `code: 'COMMAND_LIMIT'`; `message` is for diagnosis, not for
|
|
8305
|
+
protocols. `COMMAND_UNAVAILABLE` and `COMMAND_CLEANUP` have no reason. A caller abort and a sink
|
|
8306
|
+
failure keep the original error. When cleanup fails, the original limit and its reason stay inside the
|
|
8307
|
+
`AggregateError` cause.
|
|
8264
8308
|
|
|
8265
8309
|
```ts
|
|
8266
8310
|
import { NativeCommandError } from 'stitchkit/process'
|
|
@@ -8276,38 +8320,137 @@ try {
|
|
|
8276
8320
|
}
|
|
8277
8321
|
```
|
|
8278
8322
|
|
|
8279
|
-
|
|
8280
|
-
`{ cause, reason: 'deadline' | 'output-budget' }`
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
bounded native Readable buffer
|
|
8286
|
-
lifetime signal. Abort
|
|
8287
|
-
|
|
8288
|
-
IO,
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
Cleanup
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
AggregateError cause.
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8323
|
+
The constructor is `new NativeCommandError(code, message, { cause })`; it accepts
|
|
8324
|
+
`{ cause, reason: 'deadline' | 'output-budget' }` only for `COMMAND_LIMIT`. The owner always sets the
|
|
8325
|
+
reason for its own limits. An error a caller constructs without a reason gets none inferred from the
|
|
8326
|
+
text; its `reason` is `undefined`.
|
|
8327
|
+
|
|
8328
|
+
Output is read with backpressure, stdout and stderr separately: one running sink per channel plus a
|
|
8329
|
+
bounded native Readable buffer, with no accumulation of the whole history while streaming. The
|
|
8330
|
+
callback receives the lifetime signal. Abort releases the wait even for a callback that ignores the
|
|
8331
|
+
signal, but it cannot cancel third-party work the callback continues after the abort. A sink must
|
|
8332
|
+
cancel its own IO, must not keep an unbounded history and must not change shared descriptor flags.
|
|
8333
|
+
After an abort the owner starts no new callbacks. Normal completion waits for all bytes and sinks.
|
|
8334
|
+
|
|
8335
|
+
Cleanup uses the POSIX process group under the `stop` policy, then KILL; `cleanupTimeoutMs`
|
|
8336
|
+
(default 2000, maximum 30 000) bounds the wait for close after the signals. On
|
|
8337
|
+
Darwin a transient `EPERM` when signalling a group can mean zombies the OS reaper has not collected
|
|
8338
|
+
yet. The retry is bounded by `cleanupTimeoutMs`; success needs a delivered signal or `ESRCH`, and a
|
|
8339
|
+
permanent refusal is kept in the cause. The close of every owned pipe and the leader's exit are
|
|
8340
|
+
observed separately, including the absence of a shared `child.close` during teardown. The caller or
|
|
8341
|
+
sink cause is kept; a cleanup failure is a `NativeCommandError` with `COMMAND_CLEANUP` and an
|
|
8342
|
+
`AggregateError` cause. Even an exited parent does not exclude a helper that holds a pipe. Windows
|
|
8343
|
+
refuses with `COMMAND_UNAVAILABLE`; a descendant that has left the group on its own with `setsid` is
|
|
8344
|
+
outside this capability. This is one-shot execution, not a PTY, a supervisor or a daemon host.
|
|
8345
|
+
|
|
8346
|
+
### Stopping a command: the `stop` policy
|
|
8347
|
+
|
|
8348
|
+
A caller abort, the `timeoutMs` deadline, an exceeded output budget and a failing sink all stop the
|
|
8349
|
+
command the same way, by its `stop` policy:
|
|
8350
|
+
|
|
8351
|
+
| field | meaning |
|
|
8352
|
+
|---|---|
|
|
8353
|
+
| `target` | `'group'`: `signal` goes to every member of the group. `'leader'`: `signal` goes to the leader alone; the leader's exit ends the grace at once. Required. |
|
|
8354
|
+
| `signal` | `SIGTERM` (default), `SIGINT`, `SIGHUP`, `SIGQUIT`, `SIGUSR1` or `SIGUSR2`. KILL is not a stop signal: it is what the grace ends in. |
|
|
8355
|
+
| `graceMs` | How long the leader (or the group) has to leave before the whole group gets KILL. 0 to 3 600 000 (one hour). Required. |
|
|
8356
|
+
| `killOn` | Optional `AbortSignal`. Its abort stops the command with KILL to the whole group at once, also in the middle of a grace. |
|
|
8357
|
+
|
|
8358
|
+
Omitting `stop` means `{ target: 'group', signal: 'SIGTERM', graceMs: 100 }`.
|
|
8359
|
+
|
|
8360
|
+
```ts
|
|
8361
|
+
// Cooperative cancellation: the leader rolls its work back, its helpers are not asked.
|
|
8362
|
+
await runNativeCommand({
|
|
8363
|
+
executable: '/usr/local/bin/migrate', args: ['apply'], signal: request.signal,
|
|
8364
|
+
stop: { target: 'leader', signal: 'SIGINT', graceMs: 15 * 60_000, killOn: shutdown.signal },
|
|
8365
|
+
})
|
|
8366
|
+
```
|
|
8367
|
+
|
|
8368
|
+
With `target: 'leader'` the rest of the group never receives the cooperative signal: they are
|
|
8369
|
+
processes the leader started, and the leader decides what happens to them. Whatever is left in the
|
|
8370
|
+
group when the leader exits, or when the grace ends, gets KILL, including a helper with its stdio
|
|
8371
|
+
closed that no pipe would have revealed. A leader that has already exited is never signalled again.
|
|
8372
|
+
|
|
8373
|
+
The output pipes stay open until the stop sequence ends. Output a stopping command writes is read and
|
|
8374
|
+
dropped, never passed to `onOutput` and never captured, so a leader that writes while it shuts down
|
|
8375
|
+
does not meet a broken pipe. The result rejects with the abort reason once the group is gone and the
|
|
8376
|
+
pipes are closed, so it can take `graceMs` plus `cleanupTimeoutMs` after the abort.
|
|
8377
|
+
|
|
8378
|
+
The grace is a timer inside the process that runs the command. If that process exits first, the
|
|
8379
|
+
timer is gone and the group, which is detached into its own session, keeps running: nothing in this
|
|
8380
|
+
package can bound it from outside. Abort `killOn` in your shutdown path, so the group is killed
|
|
8381
|
+
before the process exits, and let the supervisor own the case where the process dies without a
|
|
8382
|
+
shutdown. Under systemd, keep `KillMode=control-group` (the default) so the stop of the unit
|
|
8383
|
+
reaches every process in its cgroup, whatever its process group; launchd only kills the job's own
|
|
8384
|
+
process group, which a command's group is not.
|
|
8385
|
+
|
|
8386
|
+
### Stopping a group and its descendants
|
|
8387
|
+
|
|
8388
|
+
A group is signalled only while the kernel still reports members. `ESRCH` from the first signal,
|
|
8389
|
+
or from the existence probe that follows it during the grace, means no member is left: no KILL
|
|
8390
|
+
is sent afterwards, because a numeric group id that has vanished can already belong to an
|
|
8391
|
+
unrelated group. A group that is still visible after the grace period receives KILL.
|
|
8392
|
+
|
|
8393
|
+
`descendants` declares what happens to processes the leader left running once it has exited
|
|
8394
|
+
successfully:
|
|
8395
|
+
|
|
8396
|
+
| `descendants` | After the leader exits |
|
|
8397
|
+
|---|---|
|
|
8398
|
+
| `'terminate-after-leader'` (the default) | The group is stopped right after `onLeaderSettled` returns and before the pipes drain: with `target: 'group'` by the same signal, grace, KILL sequence as cancellation; with `target: 'leader'` by KILL at once, since the leader has already exited. A descendant that had already left the group with `setsid` when the leader exited stays out of reach. |
|
|
8399
|
+
| `'leave'` | Nothing. A helper started with its stdio detached keeps running. One that inherited stdout or stderr keeps the pipes open, and the command then waits for it until its deadline or abort. |
|
|
8400
|
+
|
|
8401
|
+
Omitting the option means `'terminate-after-leader'`: a command does not leak the helpers it
|
|
8402
|
+
started. The result's `descendantsStopped` is `true` when the group still had members after the
|
|
8403
|
+
leader exited and they were stopped, so a helper that was ended this way is visible to the caller
|
|
8404
|
+
rather than only missing afterwards; under `'leave'` it is always `false`.
|
|
8405
|
+
|
|
8406
|
+
A real daemon detaches into its own session (`setsid`, as `daemon(3)` does), is no member of the
|
|
8407
|
+
group and is unaffected, provided it has left the group **before the leader exits**. Leaving is
|
|
8408
|
+
the daemon's own system call, and the group is stopped as soon as the leader's exit is observed,
|
|
8409
|
+
so a helper started in the background races the leader:
|
|
8410
|
+
|
|
8411
|
+
| launched by the leader as | after the leader exits |
|
|
8412
|
+
|---|---|
|
|
8413
|
+
| `setsid -f helper` (returns once the new session exists) | survives |
|
|
8414
|
+
| `setsid helper &`, `(setsid helper &)`, `nohup helper &` | stopped: the leader usually exits before the background job has left the group |
|
|
8415
|
+
| any of these under `descendants: 'leave'` | survives |
|
|
8416
|
+
|
|
8417
|
+
Start a daemon so that the session exists before the leader returns (`setsid -f`, or the
|
|
8418
|
+
daemon's own double fork followed by the leader waiting for it), or declare `'leave'`. Declare
|
|
8419
|
+
`'leave'` only for a helper that has to outlive the command while staying in its group:
|
|
8420
|
+
|
|
8421
|
+
```ts
|
|
8422
|
+
await runNativeCommand({
|
|
8423
|
+
executable: '/usr/local/bin/start-helper', timeoutMs: 60_000,
|
|
8424
|
+
// the helper started here outlives this command on purpose
|
|
8425
|
+
descendants: 'leave',
|
|
8426
|
+
})
|
|
8427
|
+
```
|
|
8428
|
+
|
|
8429
|
+
A command whose group this package did not create (a structural
|
|
8430
|
+
launcher supplied by a host) has no group to stop, and `descendants` has nothing to act on there.
|
|
8431
|
+
|
|
8432
|
+
|
|
8433
|
+
## Replacing hand-written wrappers
|
|
8434
|
+
|
|
8435
|
+
An atomic-file wrapper becomes one `writeFileAtomic` call with the chosen options. A bounded
|
|
8436
|
+
regular-file read becomes `files.read`; JSON and schema parsing, receipt conflicts, journal rotation
|
|
8437
|
+
and application locks stay with the application. A spawn, drain, capture and cancellation wrapper
|
|
8438
|
+
becomes `runNativeCommand`; shell policy, command admission, logging, redaction, the bytes-to-text
|
|
8439
|
+
decoder and the operator sink stay with the application. Long signal-only commands stay streaming;
|
|
8440
|
+
bounded tar or capture commands get their own explicit limits.
|
|
8441
|
+
|
|
8442
|
+
A child the caller holds until it exits is the same call. A guardian that forwards its own SIGTERM
|
|
8443
|
+
passes an abort signal and `stop: { target: 'leader', signal: 'SIGTERM', graceMs }`; it reads the
|
|
8444
|
+
exit code in `onLeaderSettled`, before the pipes drain, and stderr through `onOutput` or `capture`
|
|
8445
|
+
with `maxOutputBytes`, and `cleanupTimeoutMs` bounds the drain (`COMMAND_CLEANUP` when a holder
|
|
8446
|
+
keeps the pipe past it). A worker whose whole group must stop on abort is the default
|
|
8447
|
+
`target: 'group'`. A child that inherits the caller's terminal is outside this capability: every
|
|
8448
|
+
command runs in a group of its own, and a background group that reads the terminal is stopped
|
|
8449
|
+
by the kernel (`SIGTTIN`), so an interactive re-execution stays a plain spawn in the caller's
|
|
8450
|
+
group. Importing one leaf does not make
|
|
8451
|
+
installing the whole npm package smaller; choose a dependency after measuring packed size and its
|
|
8452
|
+
dependency closure.
|
|
8453
|
+
|
|
8311
8454
|
## Resource-scoped launchers and shared owners
|
|
8312
8455
|
|
|
8313
8456
|
`onLeaderSettled(event, signal)` observes leader exit before inherited stdout/stderr pipes
|
|
@@ -8349,8 +8492,23 @@ syscall interruptible or provide a snapshot against a hostile writer.
|
|
|
8349
8492
|
An exclusive lock applies exact requested permissions through its creating descriptor,
|
|
8350
8493
|
independent of umask. Default `0600` stays private. Shared `0640` requires a common group and
|
|
8351
8494
|
traversable directories; another UID also needs directory write permission to reclaim a
|
|
8352
|
-
proven-dead owner's file.
|
|
8353
|
-
|
|
8495
|
+
proven-dead owner's file.
|
|
8496
|
+
|
|
8497
|
+
The lock is published with its owner already recorded: the owner record is written to a
|
|
8498
|
+
private temporary file in the lock's directory, then hard-linked to the lock name. `link`
|
|
8499
|
+
fails with `EEXIST` for every caller but one, and the name never exists without a complete
|
|
8500
|
+
record, so a holder that stalls or dies before publishing leaves no lock and cannot be
|
|
8501
|
+
displaced. The directory must therefore be on a filesystem that supports hard links. A crash
|
|
8502
|
+
between the temporary write and the link can leave a `.lock-*.tmp` file behind; it is not a
|
|
8503
|
+
lock and nothing waits on it.
|
|
8504
|
+
|
|
8505
|
+
`ownerlessGraceMs` defaults to `null`: a lock file with no readable owner is never taken
|
|
8506
|
+
because of its age. This library does not produce such a file; only a writer that creates the name
|
|
8507
|
+
before its owner record can leave one (for example an empty file from a crashed process).
|
|
8508
|
+
`ownerlessGraceMs: <ms>` opts in to reclaiming an empty lock file older than that, and also governs
|
|
8509
|
+
abandoned empty reclaim guards.
|
|
8510
|
+
When unset, an empty reclaim guard left by an older writer is taken after 5 000 ms so recovery
|
|
8511
|
+
is never disabled for good; `null` refuses that too.
|
|
8354
8512
|
Stale reclaim guards use the same lock owner and a child guard for the inode check and
|
|
8355
8513
|
unlink. Recovery depth is capped at 16; a deeper stale chain refuses recovery with a depth diagnosis retained as the cause of
|
|
8356
8514
|
the bounded `LOCK_TIMEOUT`. Live owners, unknown process-lifetime evidence and unsafe records are never
|
|
@@ -8376,27 +8534,26 @@ version: carry their reachable relative `.d.ts` closure, rather than emitting re
|
|
|
8376
8534
|
instead emit declarations for only that DTO contract. Keep the library's manifest at runtime
|
|
8377
8535
|
`dependencies: {}` and `peerDependencies: { zod: ... }` when that is its declared promise.
|
|
8378
8536
|
|
|
8379
|
-
The packaged Darwin loader
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8537
|
+
The packaged Darwin loader names its addons by a path computed at run time, so a bundler
|
|
8538
|
+
never follows it: bundling `stitchkit/server`, `stitchkit/files` or `stitchkit/process` yields
|
|
8539
|
+
the same single JS file on every operating system, with no `.node` output, and
|
|
8540
|
+
`bun build --outfile` works. Such a bundle looks for the addon beside the original package
|
|
8541
|
+
files, never inside itself. Where that file is absent (an artifact moved to another machine)
|
|
8542
|
+
the Darwin backend is `unavailable`: process identity cannot be recorded, so a lock held by a
|
|
8543
|
+
crashed owner is not reclaimed. An artifact that must carry the Darwin addon uses the packaging
|
|
8544
|
+
plugin of the next section; it is the only way a static loader enters a bundle.
|
|
8384
8545
|
|
|
8385
8546
|
```sh
|
|
8386
|
-
bun build src/native.ts --target=bun --minify --
|
|
8387
|
-
bun build src/native.ts --compile --bytecode --format=esm --outfile=dist/native
|
|
8547
|
+
bun build src/native.ts --target=bun --minify --outfile=dist/native.js
|
|
8388
8548
|
```
|
|
8389
8549
|
|
|
8390
|
-
For a JS build that previously used `--outfile=dist/native.js`, use
|
|
8391
|
-
`--outdir=dist --entry-naming=native.js` instead: the output can now include native assets.
|
|
8392
|
-
The standalone executable still uses `--outfile` because those assets are embedded inside it.
|
|
8393
|
-
|
|
8394
8550
|
Qualify the complete output outside its build tree and installed dependency graph. Test process
|
|
8395
|
-
identity, live-owner refusal, dead-owner recovery and contained file operations in
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8551
|
+
identity, live-owner refusal, dead-owner recovery and contained file operations in artifacts built
|
|
8552
|
+
with the packaging plugin, and check that an unpackaged build reports the backend `unavailable`
|
|
8553
|
+
rather than certifying a dead owner. Bun and Node package imports retain the same
|
|
8554
|
+
lazy loader; importing a portable leaf does not load a Darwin addon on another OS. Test
|
|
8555
|
+
declarations too, and check that importing an unbundled Stitchkit leaf fails in a deliberately
|
|
8556
|
+
isolated library distribution. A separate schema-only entry must not import the native entry.
|
|
8400
8557
|
|
|
8401
8558
|
If the Darwin backend cannot load, `observeProcessInstance` still returns `unavailable`.
|
|
8402
8559
|
Its backend `Error` preserves the original `cause`; JSON serialization carries only the stable
|
|
@@ -8412,17 +8569,21 @@ Stitchkit installation or create an independent lightweight npm package.
|
|
|
8412
8569
|
## Public native packaging
|
|
8413
8570
|
|
|
8414
8571
|
Custom archives and installers use `stitchkit/files/packaging` at **build time**.
|
|
8415
|
-
`createNativePackaging` resolves the installed version and target addon
|
|
8416
|
-
|
|
8572
|
+
`createNativePackaging` resolves the installed version and target addon, reads the addon once and
|
|
8573
|
+
checks it against the size and SHA256 that Stitchkit published in its `native-assets.json` when the
|
|
8574
|
+
package was built. A ready asset carries those verified `bytes` with the published `size` and
|
|
8575
|
+
`sha256`; its Bun-compatible plugin integrates the same bytes with the same lazy native loader.
|
|
8576
|
+
Each call reads and hashes the selected addons, so call it once per build and reuse the result.
|
|
8417
8577
|
The leaf is evolving. It requires no optional peer or Bun ambient declarations for Node imports.
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8578
|
+
Calling it must happen in a process that runs it from its installed package: it locates the
|
|
8579
|
+
package's `native-assets.json` beside its own file and throws `Native packaging must run from its
|
|
8580
|
+
installed Stitchkit package` anywhere else, such as inside an application's runtime artifact.
|
|
8581
|
+
Importing the leaf is side-effect free (it loads no addon and reads no file), so a module that
|
|
8582
|
+
both builds and is bundled may import it; only the call has to stay in the build step.
|
|
8421
8583
|
|
|
8422
8584
|
```ts
|
|
8423
8585
|
import { createNativePackaging } from 'stitchkit/files/packaging'
|
|
8424
|
-
import {
|
|
8425
|
-
import { copyFileSync, mkdirSync, readFileSync } from 'node:fs'
|
|
8586
|
+
import { mkdirSync, writeFileSync } from 'node:fs'
|
|
8426
8587
|
import { dirname, join } from 'node:path'
|
|
8427
8588
|
|
|
8428
8589
|
const native = createNativePackaging({
|
|
@@ -8436,28 +8597,33 @@ const result = await Bun.build({
|
|
|
8436
8597
|
plugins: [native.plugin],
|
|
8437
8598
|
})
|
|
8438
8599
|
if (!result.success) throw new AggregateError(result.logs, 'Build failed')
|
|
8439
|
-
// Preserve every result.outputs file under dist, then
|
|
8600
|
+
// Preserve every result.outputs file under dist, then write the verified companion bytes.
|
|
8440
8601
|
for (const asset of native.assets) {
|
|
8441
|
-
const bytes = readFileSync(asset.sourcePath)
|
|
8442
|
-
if (createHash('sha256').update(bytes).digest('hex') !== asset.sha256)
|
|
8443
|
-
throw new Error('Native asset changed during build')
|
|
8444
8602
|
const destination = join('dist', asset.outputPath)
|
|
8445
8603
|
mkdirSync(dirname(destination), { recursive: true })
|
|
8446
|
-
|
|
8604
|
+
writeFileSync(destination, asset.bytes)
|
|
8447
8605
|
}
|
|
8448
8606
|
```
|
|
8449
8607
|
|
|
8450
8608
|
`entryPath` and `assetPath` are relative paths inside the application's output root, without
|
|
8451
|
-
absolute paths, traversal or overlapping file/directory paths. `
|
|
8609
|
+
absolute paths, traversal or overlapping file/directory paths. `assetPath` may contain `[hash]`,
|
|
8610
|
+
which becomes the first 16 hex digits of that addon's published SHA256: the digest is known before
|
|
8611
|
+
the build, so `addons/darwin-arm64-[hash].node` gives two installed versions of the package
|
|
8612
|
+
different companion names, and the loader requires exactly that name. It is the only template an
|
|
8613
|
+
addon path accepts. `entryPath` is fixed, without
|
|
8452
8614
|
Bun naming templates such as `[dir]` or `[name]`. Companion JS builds require one entry, no splitting,
|
|
8453
8615
|
and `naming.entry` exactly matching `entryPath`; the plugin rejects a mismatching layout.
|
|
8454
8616
|
Choose any application layout; the framework does not prescribe an app directory or installer.
|
|
8455
|
-
Keep every bundler output
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8617
|
+
Keep every bundler output and write `asset.bytes`; never read the addon from `node_modules` a
|
|
8618
|
+
second time. An installed addon whose size or SHA256 differs from the published manifest refuses
|
|
8619
|
+
with `mismatch` / `NATIVE_ASSET_DIGEST_MISMATCH`, so a file substituted after installation never
|
|
8620
|
+
reaches an artifact. Archive the complete output directory with an integrity manifest that records
|
|
8621
|
+
`asset.sha256`, unpack to a clean directory, verify the same hashes there, and run offline without
|
|
8622
|
+
the build tree or `node_modules`. The published digest ties the bytes to the package Stitchkit
|
|
8623
|
+
built; the package itself is only as trusted as its tarball (the lockfile integrity of the install).
|
|
8624
|
+
Signature and trust policy remain application-owned.
|
|
8459
8625
|
|
|
8460
|
-
|
|
8626
|
+
One JS artifact can run on both Darwin architectures; change only the packaging inputs:
|
|
8461
8627
|
|
|
8462
8628
|
```ts
|
|
8463
8629
|
const native = createNativePackaging({
|
|
@@ -8467,10 +8633,10 @@ const native = createNativePackaging({
|
|
|
8467
8633
|
})
|
|
8468
8634
|
```
|
|
8469
8635
|
|
|
8470
|
-
Use the same single plugin and
|
|
8636
|
+
Use the same single plugin and write loop above. A single-target loader deliberately refuses the
|
|
8471
8637
|
other architecture; two competing plugins cannot produce a universal loader. The array form
|
|
8472
8638
|
requires exactly one distinct output path per declared target. A ready result carries the target
|
|
8473
|
-
array and both
|
|
8639
|
+
array and both verified assets with their published digests; `NativePackagingOptions<true>` /
|
|
8474
8640
|
`NativePackagingResult<true>` describe this form when explicitly annotating variables.
|
|
8475
8641
|
The loader selects only `process.arch`: a missing or wrong-architecture selected addon refuses,
|
|
8476
8642
|
even when the other valid addon is present. Linux runtime imports remain lazy and use Linux
|
|
@@ -8479,17 +8645,36 @@ Darwin arm64 and x64 machines, with both companions preserved through archive de
|
|
|
8479
8645
|
Build-machine architecture and cross-build success cannot establish universal native support.
|
|
8480
8646
|
|
|
8481
8647
|
For a standalone executable use `delivery: 'embedded'` and the same plugin in `Bun.build`
|
|
8482
|
-
with `compile: { outfile: ... }`. Bun embeds the selected addon
|
|
8483
|
-
|
|
8648
|
+
with `compile: { outfile: ... }`. Bun embeds the selected addon from the verified bytes the
|
|
8649
|
+
plugin holds, not from a second read of the file; `assets` identifies those bytes and their
|
|
8650
|
+
published digest for qualification, not a companion that must be installed beside the executable.
|
|
8484
8651
|
Cross-builds must select the requested architecture explicitly; the artifact must run on that target.
|
|
8485
8652
|
The installed package must contain that target's addon. No automatic fallback/downgrade occurs.
|
|
8486
8653
|
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8654
|
+
`platform` is the closed set of platforms with native addons, today `'darwin'`: another name is a
|
|
8655
|
+
type error and a schema refusal that throws, so pass the literal (or narrow `process.platform`
|
|
8656
|
+
with `=== 'darwin'`) rather than a free string. The API returns `unsupported` /
|
|
8657
|
+
`NATIVE_TARGET_UNSUPPORTED` for an unsupported architecture, `missing` / `NATIVE_ASSET_MISSING` for
|
|
8658
|
+
an addon the package does not publish or whose file is gone, and `mismatch` /
|
|
8659
|
+
`NATIVE_ASSET_DIGEST_MISMATCH` for an addon whose bytes differ from the published size or SHA256.
|
|
8660
|
+
A `mismatch` names the first differing `architecture` and carries `expected` (the published
|
|
8661
|
+
`{ size, sha256 }`) and `actual` (the installed file's size and the SHA256 of the bytes read), so
|
|
8662
|
+
a build log can tell a truncated copy from a substituted one:
|
|
8663
|
+
|
|
8664
|
+
```ts
|
|
8665
|
+
if (native.state === 'mismatch')
|
|
8666
|
+
throw new Error(
|
|
8667
|
+
`${native.architecture} addon: expected ${native.expected.size} bytes ` +
|
|
8668
|
+
`${native.expected.sha256}, found ${native.actual.size} bytes ${native.actual.sha256}`,
|
|
8669
|
+
)
|
|
8670
|
+
```
|
|
8671
|
+
|
|
8672
|
+
Portable libraries
|
|
8673
|
+
and Linux builds use their normal build, without a Darwin plugin; merely importing the build leaf
|
|
8674
|
+
loads no addon.
|
|
8675
|
+
Malformed inputs, a manifest whose `formatVersion` is not 2 and other IO errors throw before packaging.
|
|
8676
|
+
A missing or corrupt companion at runtime yields the native `unavailable` result with the safe
|
|
8677
|
+
stage and code from the observer; it never certifies a dead owner. Preserve the raw cause internally.
|
|
8493
8678
|
The plugin does not parse consumer JS or establish a second runtime native implementation.
|
|
8494
8679
|
|
|
8495
8680
|
## Qualification boundaries
|
|
@@ -8508,24 +8693,19 @@ the qualification for native Darwin behavior.
|
|
|
8508
8693
|
|
|
8509
8694
|
Local filesystem tests do not attest delayed NFS visibility, shared-volume microVM or gVisor
|
|
8510
8695
|
ownership, actual PGID reuse, power-loss durability or macOS full-sync behavior. Those
|
|
8511
|
-
require their actual topology or failure environment.
|
|
8512
|
-
|
|
8513
|
-
|
|
8696
|
+
require their actual topology or failure environment. No age-only observation proves a
|
|
8697
|
+
process dead; the lock's owner record exists before its name does.
|
|
8698
|
+
|
|
8699
|
+
A sandbox admission slot is released once a direct command's pipes have closed and its owner has settled.
|
|
8700
|
+
A `COMMAND_CLEANUP` outcome means the death of its group was not proven, so the slot stays
|
|
8701
|
+
occupied, `stop` keeps rejecting with the cleanup error, and the sandbox session is recreated to
|
|
8702
|
+
recover.
|
|
8514
8703
|
|
|
8515
8704
|
|
|
8516
8705
|
==============================================================================
|
|
8517
8706
|
# Guide: Generic application primitives (docs/guide/primitives.md)
|
|
8518
8707
|
==============================================================================
|
|
8519
8708
|
|
|
8520
|
-
---
|
|
8521
|
-
title: Generic application primitives
|
|
8522
|
-
description: Declare lifecycle, access, exact values, audit, delivery and exports without moving application infrastructure into Stitchkit.
|
|
8523
|
-
type: guide
|
|
8524
|
-
status: active
|
|
8525
|
-
created: 2026-09-01
|
|
8526
|
-
updated: 2026-10-01 21:03 +07:00
|
|
8527
|
-
---
|
|
8528
|
-
|
|
8529
8709
|
# Generic application primitives
|
|
8530
8710
|
|
|
8531
8711
|
`stitchkit/primitives` is an optional browser-safe leaf. It declares values and policies; it does
|
|
@@ -8628,33 +8808,50 @@ const bytes = canonicalJson({ '2': 'two', '10': 'ten', optional: undefined, nest
|
|
|
8628
8808
|
// Native crypto or browser WebCrypto hashes these same UTF-8 bytes.
|
|
8629
8809
|
```
|
|
8630
8810
|
|
|
8631
|
-
`canonicalJson`
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
negative zero, cycles, user prototypes, accessors
|
|
8635
|
-
|
|
8811
|
+
`canonicalJson` is the one serializer behind the digests: object keys sort by UTF-16 code unit,
|
|
8812
|
+
integer-like keys included; array order is kept; object members that are `undefined` are omitted.
|
|
8813
|
+
It refuses, before serializing, a top-level or array `undefined`, sparse arrays, functions, symbols,
|
|
8814
|
+
BigInt, non-finite numbers, negative zero, cycles, user prototypes, accessors and non-enumerable or
|
|
8815
|
+
symbol members. Validate your own schema first; the canonical boundary does not replace it.
|
|
8816
|
+
|
|
8817
|
+
Defaults: depth 100 (the maximum), nodes 100 000, UTF-8 bytes 1 MiB. Invalid options throw a Zod
|
|
8818
|
+
validation error. A refused value throws `CanonicalJsonError`, a `TypeError` whose `reason` is a
|
|
8819
|
+
`CanonicalJsonRefusal`; branch on the reason, never on the message:
|
|
8636
8820
|
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8821
|
+
| `reason` | Meaning |
|
|
8822
|
+
|---|---|
|
|
8823
|
+
| `depth` | The value is nested deeper than `maxDepth`. |
|
|
8824
|
+
| `nodes` | The value has more nodes than `maxNodes`. |
|
|
8825
|
+
| `bytes` | The output is longer than `maxBytes` in UTF-8. |
|
|
8826
|
+
| `cycle` | The value contains a cycle. |
|
|
8827
|
+
| `negative-zero` | The value contains `-0`, which JSON cannot carry. |
|
|
8828
|
+
| `not-json` | The value is not bounded plain JSON: sparse arrays, non-finite numbers, non-plain objects, accessors. |
|
|
8829
|
+
|
|
8830
|
+
`depth`, `nodes` and `bytes` are the caller's limits being exceeded; the other three say the value is
|
|
8831
|
+
not JSON data at all.
|
|
8832
|
+
|
|
8833
|
+
```ts
|
|
8834
|
+
import { CanonicalJsonError, canonicalJson } from 'stitchkit/primitives'
|
|
8835
|
+
|
|
8836
|
+
try {
|
|
8837
|
+
canonicalJson(payload, { maxBytes: 4096 })
|
|
8838
|
+
} catch (error) {
|
|
8839
|
+
if (error instanceof CanonicalJsonError && error.reason === 'bytes') {
|
|
8840
|
+
// too large: reject the request
|
|
8841
|
+
} else throw error
|
|
8842
|
+
}
|
|
8843
|
+
```
|
|
8844
|
+
|
|
8845
|
+
The leaf is browser-safe: no Node, Bun or crypto, no `agent-runtime/testing`, no optional peers. Native
|
|
8846
|
+
and browser code run the same algorithm; hashing stays a platform adapter. A consumer that needs the
|
|
8847
|
+
same bytes moves only the serializer recursion and keeps its own proof schemas and verification
|
|
8848
|
+
boundary.
|
|
8643
8849
|
|
|
8644
8850
|
|
|
8645
8851
|
==============================================================================
|
|
8646
8852
|
# Guide: Application migration recipes (docs/guide/application-migration-recipes.md)
|
|
8647
8853
|
==============================================================================
|
|
8648
8854
|
|
|
8649
|
-
---
|
|
8650
|
-
title: Application migration recipes
|
|
8651
|
-
description: Executable patterns for moving database, poller, queue and operational publishing lifecycle into the managed application kernel.
|
|
8652
|
-
type: architecture
|
|
8653
|
-
status: active
|
|
8654
|
-
created: 2026-08-24
|
|
8655
|
-
updated: 2026-10-04 13:28 +07:00
|
|
8656
|
-
---
|
|
8657
|
-
|
|
8658
8855
|
# Application migration recipes
|
|
8659
8856
|
|
|
8660
8857
|
These recipes are the migration companion to the
|
|
@@ -8806,10 +9003,10 @@ guard must refuse. Filesystem mutations made inside inbox transactions call
|
|
|
8806
9003
|
`assertHeld` immediately before publication or cleanup.
|
|
8807
9004
|
|
|
8808
9005
|
For `createFileStateStore`, remove `staleLockMs` and `retryMs` from configuration.
|
|
8809
|
-
`lockTimeoutMs`
|
|
9006
|
+
`lockTimeoutMs` is the acquisition budget. Timestamp age never revokes
|
|
8810
9007
|
a live or unknown writer. Stop and verify old writers before recovering a legacy
|
|
8811
|
-
pid-only lock; do not invent owner metadata.
|
|
8812
|
-
|
|
9008
|
+
pid-only lock; do not invent owner metadata. Temporary files are never
|
|
9009
|
+
deleted by age. See the [durable intake contract](application-kernel.md#durable-process-facts-and-owner-notifications).
|
|
8813
9010
|
|
|
8814
9011
|
## Long-running poller
|
|
8815
9012
|
|
|
@@ -9057,9 +9254,10 @@ declarations do not require `ai`, the MCP SDK or Bun globals. Node consumers can
|
|
|
9057
9254
|
use strict `NodeNext` resolution with `skipLibCheck: false` and ordinary Node
|
|
9058
9255
|
types; the package keeps its required dependencies separate from optional SDKs.
|
|
9059
9256
|
|
|
9060
|
-
CLI runtime tools use the
|
|
9061
|
-
schemas, handler, and explicit CLI exposure. SDK presenters
|
|
9062
|
-
|
|
9257
|
+
CLI runtime tools use the one runtime-tool declaration: identity, input and output
|
|
9258
|
+
schemas, handler, and explicit CLI exposure. SDK presenters are the adapter's extension
|
|
9259
|
+
of that declaration (the fourth type argument of `RuntimeToolDefinitionWithOutput`, or
|
|
9260
|
+
`defineRuntimeTool` in `stitchkit/tools`), which requires the corresponding peers. A typed
|
|
9063
9261
|
definition returned by `defineRuntimeTool` or `createRuntimeToolFactory` can be
|
|
9064
9262
|
passed directly to both CLI and SDK mounts.
|
|
9065
9263
|
|
|
@@ -9073,7 +9271,7 @@ For a CLI-only managed tool, use the neutral generic construction contract to
|
|
|
9073
9271
|
infer the handler from its schemas:
|
|
9074
9272
|
|
|
9075
9273
|
```ts
|
|
9076
|
-
import type {
|
|
9274
|
+
import type { RuntimeToolDefinitionWithOutput } from 'stitchkit/cli'
|
|
9077
9275
|
import { z } from 'zod'
|
|
9078
9276
|
|
|
9079
9277
|
const input = z.object({ text: z.string() })
|
|
@@ -9083,12 +9281,14 @@ const measure = {
|
|
|
9083
9281
|
identity: { serviceName: 'text', action: 'measure', method: 'POST' },
|
|
9084
9282
|
transports: ['CLI'], input, output,
|
|
9085
9283
|
handler: ({ input }) => ({ size: input.text.length }),
|
|
9086
|
-
} satisfies
|
|
9284
|
+
} satisfies RuntimeToolDefinitionWithOutput<typeof input, typeof output>
|
|
9087
9285
|
```
|
|
9088
9286
|
|
|
9089
|
-
Pass `measure` to `runtimeTools`.
|
|
9090
|
-
|
|
9091
|
-
`
|
|
9287
|
+
Pass `measure` to `runtimeTools`. `RuntimeToolDefinition`, the heterogeneous
|
|
9288
|
+
registration type, accepts it unchanged, and types an inline handler written directly
|
|
9289
|
+
in `runtimeTools` by the parsed object (use the construction type above to type the
|
|
9290
|
+
input from the schema). Execute it through `createCli` or `createCliInvoker` so
|
|
9291
|
+
validation remains on the canonical path.
|
|
9092
9292
|
|
|
9093
9293
|
## Exposure is opt-in
|
|
9094
9294
|
|
|
@@ -9278,7 +9478,9 @@ the values agree. Duplicates are refused before command execution. Array
|
|
|
9278
9478
|
options accumulate across long and short forms; unknown non-dotted passthrough
|
|
9279
9479
|
options retain their raw-list grammar. Tokens after `--` are literal data.
|
|
9280
9480
|
Command `--help` and `-h` keep their diagnostic precedence over malformed
|
|
9281
|
-
operation arguments; application globals are validated before routing.
|
|
9481
|
+
operation arguments; application globals are validated before routing. Help is a
|
|
9482
|
+
request, not a setting, so repeating it in any spelling (`-h -h`, `-h --help`) asks once
|
|
9483
|
+
and never errors.
|
|
9282
9484
|
|
|
9283
9485
|
Without presentation configuration, positional arguments fill non-boolean
|
|
9284
9486
|
fields in declaration order, so `myapp generate "a fox"` is
|
|
@@ -9505,8 +9707,10 @@ Declared aliases render beside their canonical options, for example
|
|
|
9505
9707
|
The argument table and `Application options:` show bounds from their JSON Schema,
|
|
9506
9708
|
for example `<integer> [>=1, <=50]`, `<string> [length >=1, length <=12]`, and
|
|
9507
9709
|
`<value…> [items >=2, items <=4]`. Numeric `>` and `<` are exclusive, while `>=`
|
|
9508
|
-
and `<=` include the endpoint; a zero limit is displayed too.
|
|
9509
|
-
|
|
9710
|
+
and `<=` include the endpoint; a zero limit is displayed too. Only limits the
|
|
9711
|
+
schema declares are shown: the safe-integer range every `z.int()` carries is the
|
|
9712
|
+
type, not a bound, so a bare integer prints `<integer>`, `.min(1)` prints `>=1` and
|
|
9713
|
+
`.positive()` prints `>0`. Unbounded fields keep just their type and description. Bounds on an array's items are not bounds
|
|
9510
9714
|
on the array itself.
|
|
9511
9715
|
|
|
9512
9716
|
Composed schemas retain their meaning: `any of:` and `one of:` separate
|
|
@@ -9734,11 +9938,16 @@ exists to distrust.
|
|
|
9734
9938
|
|
|
9735
9939
|
`publishCli` is an opt-in publisher for a local application-owned distribution
|
|
9736
9940
|
directory. It commits every target and its version manifest before atomically
|
|
9737
|
-
replacing the public `manifest.json`.
|
|
9738
|
-
|
|
9941
|
+
replacing the public `manifest.json`. It ships from its own evolving entrypoint,
|
|
9942
|
+
`stitchkit/cli/publish`: release infrastructure follows how distributions are
|
|
9943
|
+
published, so it is not held to the stable promise of `stitchkit/cli`
|
|
9944
|
+
([ADR 0245](../decisions/0245-cli-publication-is-an-evolving-leaf.md)). The
|
|
9945
|
+
manifest, signatures, installer and updater it writes for stay in
|
|
9946
|
+
`stitchkit/cli`. Your application selects the CLI source identity and admits
|
|
9947
|
+
that snapshot at each phase:
|
|
9739
9948
|
|
|
9740
9949
|
```ts
|
|
9741
|
-
import { publishCli } from 'stitchkit/cli'
|
|
9950
|
+
import { publishCli } from 'stitchkit/cli/publish'
|
|
9742
9951
|
|
|
9743
9952
|
const result = await publishCli({
|
|
9744
9953
|
storageRoot: '/srv/myapp/cli',
|
|
@@ -10063,7 +10272,7 @@ await createCli({ name: 'myapp', version, runtimeTools: discovered, commands: [.
|
|
|
10063
10272
|
commands, and a connection without it still contributes nothing to the CLI —
|
|
10064
10273
|
exposure stays explicit, as it is everywhere else in the framework.
|
|
10065
10274
|
|
|
10066
|
-
One unconvertible schema
|
|
10275
|
+
One unconvertible schema does not take the connection down. The tool
|
|
10067
10276
|
is skipped and **named** (`onSkippedTool`, or a stderr line by default), so a
|
|
10068
10277
|
surface of two hundred tools is not lost to one.
|
|
10069
10278
|
|
|
@@ -10096,13 +10305,12 @@ credential, body, identity and stack stay with the original in-process cause in
|
|
|
10096
10305
|
Unknown throws remain `INTERNAL_SERVER_ERROR`. A timeout is not proof that a
|
|
10097
10306
|
write did not happen: verify the remote outcome before repeating an operation.
|
|
10098
10307
|
|
|
10099
|
-
**A remote refusal keeps its code.** A failed `tools/call`
|
|
10100
|
-
|
|
10101
|
-
the
|
|
10102
|
-
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
A structured `{ error, details }` body is now relayed as the contract error it
|
|
10308
|
+
**A remote refusal keeps its code.** A failed `tools/call` must keep three things:
|
|
10309
|
+
the code (so `exitCodes` can map it instead of every remote failure exiting `1`),
|
|
10310
|
+
the message the operator needs, and the error's own class — a plain `Error` is
|
|
10311
|
+
an *unexpected* error to the runner, which would print a code frame of the framework
|
|
10312
|
+
bundle before the JSON failure and scrub it to `INTERNAL_SERVER_ERROR`.
|
|
10313
|
+
A structured `{ error, details }` body is relayed as the contract error it
|
|
10106
10314
|
is, on every transport; anything else fails as `UPSTREAM_TOOL_ERROR` carrying
|
|
10107
10315
|
what the server did send. Not `INTERNAL_SERVER_ERROR`, because nothing of ours
|
|
10108
10316
|
broke.
|
|
@@ -11251,15 +11459,6 @@ watch client; silent lost notifications are repaired by hub reconciliation.
|
|
|
11251
11459
|
# Guide: Live data (docs/guide/live.md)
|
|
11252
11460
|
==============================================================================
|
|
11253
11461
|
|
|
11254
|
-
---
|
|
11255
|
-
title: Live data — announcements, watched reads, keyspaces and the trust fence
|
|
11256
|
-
description: How to declare events beside the operation contract, share one server-side read between every browser asking the same question, keep authoritative memory in front of a durable backend, and refuse a request whose authority you never agreed to answer on.
|
|
11257
|
-
type: guide
|
|
11258
|
-
status: active
|
|
11259
|
-
created: 2026-09-02
|
|
11260
|
-
updated: 2026-09-02
|
|
11261
|
-
---
|
|
11262
|
-
|
|
11263
11462
|
# Live data
|
|
11264
11463
|
|
|
11265
11464
|
Four things an application builds by hand the second time it needs them.
|
|
@@ -11671,11 +11870,11 @@ Every refusal answers the same bare 403. A response that said *which* rule
|
|
|
11671
11870
|
refused would let a caller learn your trusted list one guess at a time; the
|
|
11672
11871
|
reason goes to `onRefused` and the log, where you are.
|
|
11673
11872
|
|
|
11674
|
-
###
|
|
11873
|
+
### Group hooks
|
|
11675
11874
|
|
|
11676
|
-
A route group
|
|
11677
|
-
|
|
11678
|
-
|
|
11875
|
+
A route group cannot declare `onRequest`: the framework dispatches only the
|
|
11876
|
+
server-level hook, and a group hook would need group prefixes matched a second
|
|
11877
|
+
time, ahead of the real router. Use the
|
|
11679
11878
|
server-level `hooks.onRequest` to refuse before dispatch, or the group's
|
|
11680
11879
|
`authorize` to gate once the endpoint is known.
|
|
11681
11880
|
|
|
@@ -12350,7 +12549,7 @@ whether the code declares `details`:
|
|
|
12350
12549
|
|
|
12351
12550
|
The model-facing envelope is `{ error, retryable, details?, _hint? }`; for a code with no
|
|
12352
12551
|
details schema the framework fills `details` with `{ message }`, so declaring a
|
|
12353
|
-
message changes what the model reads there
|
|
12552
|
+
message changes what the model reads there; without one it is the code itself. Put
|
|
12354
12553
|
anything the model must reliably read in `details` or `hint`, not in `message`.
|
|
12355
12554
|
|
|
12356
12555
|
With no `details` schema, the options object forbids `details`. A required
|
|
@@ -12863,6 +13062,32 @@ Without an `observability` the work still runs inside a context, so trace ids,
|
|
|
12863
13062
|
decision from having a context. `traceparent` continues the trace of whatever
|
|
12864
13063
|
scheduled the work, instead of starting an unrelated one.
|
|
12865
13064
|
|
|
13065
|
+
### CPU is not attributed to work
|
|
13066
|
+
|
|
13067
|
+
Request rows, `runUnitOfWork` rows and schedule status carry durations and
|
|
13068
|
+
counts, never CPU. One JavaScript thread interleaves every operation, so a
|
|
13069
|
+
`process.cpuUsage()` delta across an operation's start and end measures
|
|
13070
|
+
everything the process ran in that time — a long wait reads as the CPU of the
|
|
13071
|
+
timers that fired during it. The correct basis is the synchronous segment, and
|
|
13072
|
+
Bun gives no callback when a continuation is entered or left
|
|
13073
|
+
([ADR 0244](../decisions/0244-cpu-is-not-attributed-to-operations.md)).
|
|
13074
|
+
|
|
13075
|
+
Process CPU per window is the number you can observe:
|
|
13076
|
+
|
|
13077
|
+
```ts
|
|
13078
|
+
let last = process.cpuUsage()
|
|
13079
|
+
setInterval(() => {
|
|
13080
|
+
const now = process.cpuUsage() // cumulative { user, system } in µs
|
|
13081
|
+
logger.info('cpu', { cpuUserUs: now.user - last.user, cpuSystemUs: now.system - last.system })
|
|
13082
|
+
last = now
|
|
13083
|
+
}, 60_000)
|
|
13084
|
+
```
|
|
13085
|
+
|
|
13086
|
+
If you build attribution yourself: credit an operation only the CPU of the
|
|
13087
|
+
synchronous segments it ran, never its waiting time; put everything outside those
|
|
13088
|
+
segments into `unattributed`; never credit a window to the one operation in
|
|
13089
|
+
flight — that operation is usually the one waiting.
|
|
13090
|
+
|
|
12866
13091
|
### Write the calls that changed something
|
|
12867
13092
|
|
|
12868
13093
|
`auditChanges` is the filter most projects end up writing, shipped so they do not
|
|
@@ -12937,8 +13162,8 @@ needed:
|
|
|
12937
13162
|
createServer({ services, logging, observability: observability.request })
|
|
12938
13163
|
```
|
|
12939
13164
|
|
|
12940
|
-
`wrapInRequestContext`
|
|
12941
|
-
|
|
13165
|
+
`wrapInRequestContext` serves a custom fetch pipeline that does not use
|
|
13166
|
+
`createHandler`; built-in HTTP audit wiring does not use it:
|
|
12942
13167
|
|
|
12943
13168
|
```ts
|
|
12944
13169
|
Bun.serve({ fetch: wrapInRequestContext(customFetch) })
|
|
@@ -13275,9 +13500,9 @@ which the built-in **tool** row does not read: a tool event takes
|
|
|
13275
13500
|
`errorCode` / `errorMessage` / `errorDetail` from the `ToolResult`, and only
|
|
13276
13501
|
identity and `dimensions` from the context. Calling it in `onToolError` would
|
|
13277
13502
|
leave the tool row exactly as scrubbed as before. It is right for the **HTTP**
|
|
13278
|
-
path, where the request *is* the record. (
|
|
13279
|
-
|
|
13280
|
-
|
|
13503
|
+
path, where the request *is* the record. (A tool call runs in its own context, ADR 0045,
|
|
13504
|
+
so it cannot write into the enclosing `/mcp` request's row either — the call is
|
|
13505
|
+
simply not where that helper belongs.)
|
|
13281
13506
|
|
|
13282
13507
|
### One row that names the cause
|
|
13283
13508
|
|
|
@@ -14128,15 +14353,6 @@ per-transport tenant plumbing.
|
|
|
14128
14353
|
# Guide: Frontend integrations (docs/guide/frontend-integrations.md)
|
|
14129
14354
|
==============================================================================
|
|
14130
14355
|
|
|
14131
|
-
---
|
|
14132
|
-
title: Frontend integrations
|
|
14133
|
-
description: Compose Stitchkit with Next.js, React Router or a separate Vite development server
|
|
14134
|
-
type: architecture
|
|
14135
|
-
status: active
|
|
14136
|
-
created: 2026-08-08
|
|
14137
|
-
updated: 2026-08-08
|
|
14138
|
-
---
|
|
14139
|
-
|
|
14140
14356
|
# Frontend integrations
|
|
14141
14357
|
|
|
14142
14358
|
The official `bun create stitchkit` application uses Next.js with a separate
|
|
@@ -14198,15 +14414,6 @@ shared contract remains the only transport schema source.
|
|
|
14198
14414
|
# Guide: React Query policy (docs/guide/react.md)
|
|
14199
14415
|
==============================================================================
|
|
14200
14416
|
|
|
14201
|
-
---
|
|
14202
|
-
title: React Query policy
|
|
14203
|
-
description: One request-local server client, one browser singleton and explicit retry rules for Stitchkit API errors
|
|
14204
|
-
type: guide
|
|
14205
|
-
status: active
|
|
14206
|
-
created: 2026-09-06
|
|
14207
|
-
updated: 2026-09-06
|
|
14208
|
-
---
|
|
14209
|
-
|
|
14210
14417
|
# React Query policy
|
|
14211
14418
|
|
|
14212
14419
|
`stitchkit/react` contains policy adapters for TanStack Query. It does not own
|
|
@@ -14261,15 +14468,6 @@ endpoint or cookie vocabulary.
|
|
|
14261
14468
|
# Guide: Keyboard layers and list zones (docs/guide/keyboard.md)
|
|
14262
14469
|
==============================================================================
|
|
14263
14470
|
|
|
14264
|
-
---
|
|
14265
|
-
title: Keyboard layers and list zones
|
|
14266
|
-
description: One listener decides which part of a screen gets a key; lists move real focus with the arrows
|
|
14267
|
-
type: guide
|
|
14268
|
-
status: active
|
|
14269
|
-
created: 2026-09-29
|
|
14270
|
-
updated: 2026-09-29
|
|
14271
|
-
---
|
|
14272
|
-
|
|
14273
14471
|
# Keyboard layers and list zones
|
|
14274
14472
|
|
|
14275
14473
|
`stitchkit/react/keyboard` answers one question for a React screen: **who gets
|
|
@@ -15133,8 +15331,8 @@ the press was forged (a client can send any bytes) and goes to `onStale`.
|
|
|
15133
15331
|
|
|
15134
15332
|
A press on a message the record does not know — an older menu higher up — is
|
|
15135
15333
|
answered in place: a one-message view takes that message over, a longer view is
|
|
15136
|
-
sent below it and the pressed message is deleted. Either way the
|
|
15137
|
-
|
|
15334
|
+
sent below it and the pressed message is deleted. Either way the recorded
|
|
15335
|
+
view is removed: one live menu per chat.
|
|
15138
15336
|
|
|
15139
15337
|
For a message the screens do not own — a notification from the backend —
|
|
15140
15338
|
`screens.button(link('🔑 Refresh token', refreshToken, { botId }))` makes a
|
|
@@ -15248,15 +15446,6 @@ an incoming message to HTML, and a history stack.
|
|
|
15248
15446
|
# Guide: GeoIP (docs/guide/geo.md)
|
|
15249
15447
|
==============================================================================
|
|
15250
15448
|
|
|
15251
|
-
---
|
|
15252
|
-
title: GeoIP
|
|
15253
|
-
description: A server-only managed GeoIP reader with three observable states and last-known-good generation reloads.
|
|
15254
|
-
type: guide
|
|
15255
|
-
status: active
|
|
15256
|
-
created: 2026-09-06
|
|
15257
|
-
updated: 2026-09-06
|
|
15258
|
-
---
|
|
15259
|
-
|
|
15260
15449
|
# GeoIP
|
|
15261
15450
|
|
|
15262
15451
|
`stitchkit/geo` is a server-only, evolving entrypoint for reading local GeoIP
|
|
@@ -15340,6 +15529,413 @@ analytics policy belong to the application.
|
|
|
15340
15529
|
|
|
15341
15530
|
# Upgrading stitchkit
|
|
15342
15531
|
|
|
15532
|
+
## Released migration: 0.105.0
|
|
15533
|
+
|
|
15534
|
+
### CLI publication has its own entrypoint
|
|
15535
|
+
|
|
15536
|
+
**Who must act:** code that imports `publishCli`, `CliPublicationOptions`, `CliPublicationPhase` or
|
|
15537
|
+
`CliPublicationResult` from `stitchkit/cli`. Everything else in `stitchkit/cli` — `createCli`, the
|
|
15538
|
+
manifest schemas, signatures, `renderCliInstaller` and the updater — is unchanged.
|
|
15539
|
+
|
|
15540
|
+
`publishCli` is release infrastructure, so it now ships from the evolving entrypoint
|
|
15541
|
+
`stitchkit/cli/publish` instead of the stable `stitchkit/cli`
|
|
15542
|
+
([ADR 0245](../decisions/0245-cli-publication-is-an-evolving-leaf.md)). Its options, result and
|
|
15543
|
+
behaviour are the same.
|
|
15544
|
+
|
|
15545
|
+
1. Move the publication names to the new specifier; keep every other CLI import where it is:
|
|
15546
|
+
|
|
15547
|
+
```ts
|
|
15548
|
+
// before
|
|
15549
|
+
import { publishCli, type CliPublicationOptions, verifyCliManifest } from 'stitchkit/cli'
|
|
15550
|
+
// after
|
|
15551
|
+
import { verifyCliManifest } from 'stitchkit/cli'
|
|
15552
|
+
import { publishCli, type CliPublicationOptions } from 'stitchkit/cli/publish'
|
|
15553
|
+
```
|
|
15554
|
+
|
|
15555
|
+
Search pattern: `\b(publishCli|CliPublication(Options|Phase|Result))\b` in files that import
|
|
15556
|
+
from `'stitchkit/cli'`.
|
|
15557
|
+
2. Type-check: an import left on `stitchkit/cli` fails with "has no exported member".
|
|
15558
|
+
3. `stitchkit/cli/publish` is evolving: read its breaking entries in each minor you upgrade across.
|
|
15559
|
+
|
|
15560
|
+
### a command stops by its `stop` policy
|
|
15561
|
+
|
|
15562
|
+
**Who must act:** callers of `runNativeCommand` (`stitchkit/process`) that pass `killGraceMs`.
|
|
15563
|
+
Callers that omit it need no change: the default stop is the same TERM to the group, 100 ms, KILL.
|
|
15564
|
+
|
|
15565
|
+
1. Replace `killGraceMs: N` with a group stop of the same grace:
|
|
15566
|
+
|
|
15567
|
+
```ts
|
|
15568
|
+
// before
|
|
15569
|
+
await runNativeCommand({ executable, args, signal, killGraceMs: 500 })
|
|
15570
|
+
// after
|
|
15571
|
+
await runNativeCommand({ executable, args, signal, stop: { target: 'group', graceMs: 500 } })
|
|
15572
|
+
```
|
|
15573
|
+
|
|
15574
|
+
Search pattern: `killGraceMs:\s*(\d[\d_]*)` → `stop: { target: 'group', graceMs: $1 }`.
|
|
15575
|
+
2. If your own wrapper signals only the leader and waits a long grace (cooperative cancellation),
|
|
15576
|
+
replace it with `stop: { target: 'leader', signal: 'SIGINT', graceMs }` (up to one hour). Pass
|
|
15577
|
+
`killOn` with your shutdown signal so an application that stops sooner kills the group first.
|
|
15578
|
+
3. The result of a stopped command rejects once the group is gone, up to `graceMs` plus
|
|
15579
|
+
`cleanupTimeoutMs` after the abort; size your caller deadlines on that sum.
|
|
15580
|
+
|
|
15581
|
+
### a command stops what its leader left
|
|
15582
|
+
|
|
15583
|
+
**Who must act:** callers of `runNativeCommand` (`stitchkit/process`) whose command starts a helper
|
|
15584
|
+
in its own process group that must keep running after the command returns. A daemon that detaches
|
|
15585
|
+
with `setsid` (as `daemon(3)` does) is not in the group and needs
|
|
15586
|
+
no change.
|
|
15587
|
+
|
|
15588
|
+
`descendants` now defaults to `'terminate-after-leader'`
|
|
15589
|
+
([ADR 0246](../decisions/0246-a-command-stops-its-descendants-by-default.md)): when the leader
|
|
15590
|
+
exits, the members it left in the group are stopped by the command's `stop` policy before the
|
|
15591
|
+
pipes drain.
|
|
15592
|
+
|
|
15593
|
+
1. Find the commands that start a background helper on purpose (a `&` in a shell script, a spawned
|
|
15594
|
+
child the leader does not wait for) and declare `'leave'` on them:
|
|
15595
|
+
|
|
15596
|
+
```ts
|
|
15597
|
+
// before
|
|
15598
|
+
await runNativeCommand({ executable, args, timeoutMs })
|
|
15599
|
+
// after
|
|
15600
|
+
await runNativeCommand({ executable, args, timeoutMs, descendants: 'leave' })
|
|
15601
|
+
```
|
|
15602
|
+
|
|
15603
|
+
2. Remove an explicit `descendants: 'terminate-after-leader'`, or keep it: it is now the default.
|
|
15604
|
+
3. A command whose helper inherited stdout or stderr used to wait for that helper until its
|
|
15605
|
+
deadline. It now completes when the leader exits; drop a workaround that killed the helper from
|
|
15606
|
+
`onLeaderSettled`.
|
|
15607
|
+
|
|
15608
|
+
### native packaging verifies published digests
|
|
15609
|
+
|
|
15610
|
+
**Who must act:** build scripts that call `createNativePackaging` (`stitchkit/files/packaging`) and
|
|
15611
|
+
read `asset.sourcePath`, hash the addon themselves, switch over the refusal `state`/`code`, pass a
|
|
15612
|
+
`platform` other than the literal `'darwin'`, or parse `node_modules/stitchkit/native-assets.json`.
|
|
15613
|
+
|
|
15614
|
+
The package now publishes the `size` and SHA256 of each Darwin addon, computed when Stitchkit is
|
|
15615
|
+
built, in `native-assets.json` at `formatVersion: 2`. `createNativePackaging` reads each selected
|
|
15616
|
+
addon once, compares it with that digest and returns the verified bytes. An installed addon that
|
|
15617
|
+
differs refuses with `NATIVE_ASSET_DIGEST_MISMATCH`.
|
|
15618
|
+
|
|
15619
|
+
1. Replace every read of `asset.sourcePath` with `asset.bytes`, and delete your own re-hash of the
|
|
15620
|
+
addon: the result's `sha256` is the published digest and `bytes` already match it.
|
|
15621
|
+
|
|
15622
|
+
```ts
|
|
15623
|
+
// before
|
|
15624
|
+
const bytes = readFileSync(asset.sourcePath)
|
|
15625
|
+
if (createHash('sha256').update(bytes).digest('hex') !== asset.sha256) throw …
|
|
15626
|
+
copyFileSync(asset.sourcePath, destination)
|
|
15627
|
+
// after
|
|
15628
|
+
writeFileSync(destination, asset.bytes)
|
|
15629
|
+
```
|
|
15630
|
+
|
|
15631
|
+
2. Handle the third refusal. A `switch` over `result.state` gains `'mismatch'`
|
|
15632
|
+
(`code: 'NATIVE_ASSET_DIGEST_MISMATCH'`); treat it as a failed build, never as a missing addon.
|
|
15633
|
+
It carries the differing `architecture` with `expected` (published) and `actual` (installed)
|
|
15634
|
+
`{ size, sha256 }`; log both pairs.
|
|
15635
|
+
3. Pass the target platform as the literal `'darwin'`. `platform` is a closed enum, so a free
|
|
15636
|
+
string (a target name read from configuration, `process.platform`) no longer type-checks, and
|
|
15637
|
+
another platform throws instead of returning `unsupported`. Skip packaging for non-Darwin
|
|
15638
|
+
targets before the call:
|
|
15639
|
+
|
|
15640
|
+
```ts
|
|
15641
|
+
// before
|
|
15642
|
+
const native = createNativePackaging({ platform: target.platform, architecture, … })
|
|
15643
|
+
if (native.state === 'unsupported') return
|
|
15644
|
+
// after
|
|
15645
|
+
if (target.platform !== 'darwin') return
|
|
15646
|
+
const native = createNativePackaging({ platform: target.platform, architecture, … })
|
|
15647
|
+
```
|
|
15648
|
+
|
|
15649
|
+
`unsupported` now means only an architecture the package has no addon for.
|
|
15650
|
+
4. Call `createNativePackaging` once per build and pass the result (its `assets` and `plugin`)
|
|
15651
|
+
where it is needed. Each call reads and hashes the selected addons again.
|
|
15652
|
+
5. If you read `native-assets.json` directly, read `assets.<arch>.path`, `.size` and `.sha256`
|
|
15653
|
+
instead of a bare path per architecture; an architecture with no entry is not published.
|
|
15654
|
+
|
|
15655
|
+
### exclusive locks publish their owner first
|
|
15656
|
+
|
|
15657
|
+
**Who must act:** callers of `withExclusiveLock` (`stitchkit/files`) that rely on a lock file with no
|
|
15658
|
+
readable owner being taken by age, and anything that puts lock files on a filesystem without hard
|
|
15659
|
+
links. Every other caller needs no change.
|
|
15660
|
+
|
|
15661
|
+
A lock is now written to a private `.lock-<id>.tmp` file in the lock's directory, then hard-linked to
|
|
15662
|
+
the lock name. The name never exists without a complete owner record, so `ownerlessGraceMs` defaults
|
|
15663
|
+
to `null` instead of 5 000 ms: a lock file with no readable owner is never taken because of its age.
|
|
15664
|
+
|
|
15665
|
+
1. If you depended on age-based reclaim of an empty lock file left by an older writer, opt in:
|
|
15666
|
+
|
|
15667
|
+
```ts
|
|
15668
|
+
// before: an empty lock older than 5 s was taken by default
|
|
15669
|
+
await withExclusiveLock(path, run)
|
|
15670
|
+
// after: say so; only legacy empty files need it
|
|
15671
|
+
await withExclusiveLock(path, run, { ownerlessGraceMs: 5_000 })
|
|
15672
|
+
```
|
|
15673
|
+
|
|
15674
|
+
2. Keep lock directories on a filesystem that supports hard links (local ext4, xfs, APFS and the
|
|
15675
|
+
like). `link` failing with `EPERM` or `ENOTSUP` fails the acquisition.
|
|
15676
|
+
3. Expect a `.lock-*.tmp` sibling only after a holder was killed between writing its record and
|
|
15677
|
+
publishing it. It is not a lock; the next reclaim in that directory removes the ones whose owner
|
|
15678
|
+
is gone on this machine. Exclude the pattern from directory-size caps or backups if you have any.
|
|
15679
|
+
|
|
15680
|
+
### one runtime-tool declaration
|
|
15681
|
+
|
|
15682
|
+
**Who must act:** code that names `RuntimeToolExecution`, `RuntimeToolExecutionWithOutput`,
|
|
15683
|
+
`RuntimeMcpToolDefinition`, `RuntimeMcpToolDefinitionWithOutput` or `ToolSurfaceDefinition`; code
|
|
15684
|
+
that annotates a definition carrying `present` as `RuntimeToolDefinitionWithOutput<I, O>` from
|
|
15685
|
+
`stitchkit/tools`; imports of `stitchkit/agent-runtime/harness-tools`; code that compares agent
|
|
15686
|
+
control error codes; and callers that send a JSON array as text to a string-or-array tool field.
|
|
15687
|
+
Definitions returned by `defineRuntimeTool` / `createRuntimeToolFactory`, native CLI `commands` and
|
|
15688
|
+
contract tools need no change.
|
|
15689
|
+
|
|
15690
|
+
A runtime tool now has one declaration, `RuntimeToolDefinitionWithOutput` /
|
|
15691
|
+
`RuntimeToolDefinitionWithoutOutput`, exported with the same name from `stitchkit/cli`,
|
|
15692
|
+
`stitchkit/tools` and `stitchkit/tools/mcp`. It carries no SDK type: the presenters are the adapter's
|
|
15693
|
+
extension, named as the fourth type argument. `RuntimeToolDefinition` is the one registration type.
|
|
15694
|
+
|
|
15695
|
+
1. Replace each removed name.
|
|
15696
|
+
|
|
15697
|
+
| before | after |
|
|
15698
|
+
|---|---|
|
|
15699
|
+
| `RuntimeToolExecutionWithOutput<I, O>` (`stitchkit/cli`) | `RuntimeToolDefinitionWithOutput<I, O>` |
|
|
15700
|
+
| `RuntimeToolExecution` (`stitchkit/cli`) | `RuntimeToolDefinition` |
|
|
15701
|
+
| `RuntimeMcpToolDefinitionWithOutput<I, O>` (`stitchkit/tools/mcp`) | `RuntimeToolDefinitionWithOutput<I, O, undefined, RuntimeMcpToolPresenters<z.output<O>>>` |
|
|
15702
|
+
| `RuntimeMcpToolDefinition` (`stitchkit/tools/mcp`) | `RuntimeToolDefinition` |
|
|
15703
|
+
| `ToolSurfaceDefinition` (`stitchkit/tools`) | `ToolSurfaceProjection<RuntimeToolDefinition>` |
|
|
15704
|
+
|
|
15705
|
+
2. In `stitchkit/tools`, a type that names a definition with presenters passes the presenter type:
|
|
15706
|
+
|
|
15707
|
+
```ts
|
|
15708
|
+
// before
|
|
15709
|
+
const tool: RuntimeToolDefinitionWithOutput<typeof input, typeof output> = defineRuntimeTool({ …, present })
|
|
15710
|
+
// after: let defineRuntimeTool infer it, or name the presenters
|
|
15711
|
+
const tool = defineRuntimeTool({ …, present })
|
|
15712
|
+
const named: RuntimeToolDefinitionWithOutput<typeof input, typeof output, undefined, RuntimeToolPresenters<z.output<typeof output>>> = tool
|
|
15713
|
+
```
|
|
15714
|
+
|
|
15715
|
+
3. An inline handler in `runtimeTools: [...]` needs no wrapper: it is typed by the parsed object.
|
|
15716
|
+
To type `input` from the schema, construct with `defineRuntimeTool` (or
|
|
15717
|
+
`satisfies RuntimeToolDefinitionWithOutput<typeof input, typeof output>`) before registering.
|
|
15718
|
+
|
|
15719
|
+
4. Replace `createAgentHarnessTools` (`stitchkit/agent-runtime/harness-tools`) with the call it made:
|
|
15720
|
+
|
|
15721
|
+
```ts
|
|
15722
|
+
// before
|
|
15723
|
+
tools: createAgentHarnessTools(({ context }) => ({ services: [svc], context, lifecycle: auth }))
|
|
15724
|
+
// after
|
|
15725
|
+
import { composeToolLifecycle, mountAgent } from 'stitchkit/tools'
|
|
15726
|
+
tools: ({ context, toolFenceLifecycle }) =>
|
|
15727
|
+
mountAgent([svc], { context, lifecycle: composeToolLifecycle(auth, toolFenceLifecycle) })
|
|
15728
|
+
```
|
|
15729
|
+
|
|
15730
|
+
5. Agent control codes: `ACCESS_DENIED` is `FORBIDDEN`, and `CONNECTION_CLOSED` is returned for a closed
|
|
15731
|
+
connection. `AgentControlErrorCode` lists every code. `authorize` now runs once per request; after you
|
|
15732
|
+
withdraw someone's access, call `binding.revoke(conversationId, matches?)` so an open stream is
|
|
15733
|
+
re-authorized and ends with an `access-denied` delivery.
|
|
15734
|
+
|
|
15735
|
+
6. A string-or-array field that receives `'["a","b"]'` now fails with `VALIDATION_ERROR` ("`paths` is a
|
|
15736
|
+
list written as text"). Send the array itself. A string that is not a list the array member accepts
|
|
15737
|
+
(`[preview].png`) is still one plain string.
|
|
15738
|
+
|
|
15739
|
+
### flattened unions keep declaration order
|
|
15740
|
+
|
|
15741
|
+
**Who must act:** consumers that use `flattenUnionInput: true` and keep snapshot tests, golden
|
|
15742
|
+
files or provider prompt caches of flattened tool schemas.
|
|
15743
|
+
|
|
15744
|
+
A flattened union now lists its properties, enum values and branch labels in the order they first
|
|
15745
|
+
appear across the variants, not alphabetically. Declaration order often encodes priority, so the
|
|
15746
|
+
model now reads the order you wrote. Alternatives of different kinds (`anyOf`) keep their
|
|
15747
|
+
canonical order, independent of variant order.
|
|
15748
|
+
|
|
15749
|
+
1. Regenerate every snapshot or golden file of a flattened tool listing (`buildToolManifest`,
|
|
15750
|
+
`listToolNames`, MCP `tools/list`, Agent tool schemas) and review the diff: only ordering moves.
|
|
15751
|
+
2. Expect one provider prompt-cache miss per flattened tool after the upgrade.
|
|
15752
|
+
3. To keep an order you prefer, declare the variants and fields in that order.
|
|
15753
|
+
|
|
15754
|
+
### connection limits are validated
|
|
15755
|
+
|
|
15756
|
+
**Who must act:** callers that pass `timeoutMs` or `maxResponseBytes` to a connection and relied
|
|
15757
|
+
on an invalid value silently becoming the default.
|
|
15758
|
+
|
|
15759
|
+
`stitchkit/tools/connections` throws a `RangeError` when `timeoutMs` is not a positive integer
|
|
15760
|
+
within timer range (at most 2 147 483 647) or `maxResponseBytes` is not a positive safe integer.
|
|
15761
|
+
Before 0.104.0 a value of 0, a negative number, a fraction or an out-of-range number fell back to
|
|
15762
|
+
the default without a message.
|
|
15763
|
+
|
|
15764
|
+
```ts
|
|
15765
|
+
// Before: { timeoutMs: 0 } meant "use the default" by accident.
|
|
15766
|
+
// After: omit the option to take the default, or pass a real limit.
|
|
15767
|
+
{ timeoutMs: 30_000 }
|
|
15768
|
+
```
|
|
15769
|
+
|
|
15770
|
+
### one canonical JSON serializer
|
|
15771
|
+
|
|
15772
|
+
**Who must act:** tests that call `serializeSurfaceValue` from `stitchkit/testing`, and code that
|
|
15773
|
+
puts a value nested deeper than 100 levels into agent store events, archives or projections
|
|
15774
|
+
through `canonicalAgentJson`.
|
|
15775
|
+
|
|
15776
|
+
`canonicalJson` from `stitchkit/primitives` is the package's one canonical serializer. It
|
|
15777
|
+
admits dense arrays, plain objects, strings, booleans, `null` and finite numbers; an `undefined`
|
|
15778
|
+
object member is omitted. It refuses everything else with a `CanonicalJsonError`, a `TypeError`
|
|
15779
|
+
whose `reason` says why: `'depth'`, `'nodes'` and `'bytes'` are the limits you set, while
|
|
15780
|
+
`'cycle'`, `'negative-zero'` and `'not-json'` say the value is not JSON data.
|
|
15781
|
+
|
|
15782
|
+
`serializeSurfaceValue` is removed. It produced the same bytes without limits:
|
|
15783
|
+
|
|
15784
|
+
```ts
|
|
15785
|
+
// Before
|
|
15786
|
+
import { serializeSurfaceValue } from 'stitchkit/testing'
|
|
15787
|
+
const text = serializeSurfaceValue(manifest)
|
|
15788
|
+
// After: the limits default to 100 levels, 100 000 nodes and 1 MiB; raise them for a large manifest
|
|
15789
|
+
import { canonicalJson } from 'stitchkit/primitives'
|
|
15790
|
+
const text = canonicalJson(manifest, { maxBytes: 16 * 1024 * 1024 })
|
|
15791
|
+
```
|
|
15792
|
+
|
|
15793
|
+
`canonicalAgentJson` in `stitchkit/agent-runtime` is `canonicalJson` with the node and byte
|
|
15794
|
+
limits at their maximum. A value both accepted has the same bytes as before, so stored digests
|
|
15795
|
+
and archives still match: like `z.json()` before it, the store hashes `-0` as `0` and drops an
|
|
15796
|
+
own `__proto__` member (the public `canonicalJson` refuses `-0` and keeps `__proto__`). What
|
|
15797
|
+
changed is the refusal table: an `undefined` object member used to fail and is now omitted, and
|
|
15798
|
+
a value nested deeper than 100 levels is now refused.
|
|
15799
|
+
|
|
15800
|
+
```ts
|
|
15801
|
+
// Before: canonicalAgentJson({ a: undefined }) threw a ZodError
|
|
15802
|
+
// After: canonicalAgentJson({ a: undefined }) === '{}'
|
|
15803
|
+
// canonicalAgentJson(nestedBeyond100Levels) throws CanonicalJsonError (reason 'depth')
|
|
15804
|
+
```
|
|
15805
|
+
|
|
15806
|
+
### directory inbox entries
|
|
15807
|
+
|
|
15808
|
+
**Who must act:** producers that drop hard links into an inbox directory, and callers that pass
|
|
15809
|
+
`accept` the output of a transforming schema.
|
|
15810
|
+
|
|
15811
|
+
A file dropped into the inbox directory with more than one link is set aside as `invalid`
|
|
15812
|
+
(detail `managed file has multiple links`). Another name can still rewrite such a file after it
|
|
15813
|
+
was validated, so it is never trusted. Write each entry as its own regular file: a temporary
|
|
15814
|
+
name in the same directory, then a rename onto `<name>.json`.
|
|
15815
|
+
|
|
15816
|
+
`accept` takes the schema's input type, stores that input as given and parses it with the
|
|
15817
|
+
schema on delivery. A schema without a transform has the same input and output type and needs
|
|
15818
|
+
no change. With a transform, pass the input:
|
|
15819
|
+
|
|
15820
|
+
```ts
|
|
15821
|
+
const schema = z.object({ amount: z.string().transform(Number) })
|
|
15822
|
+
// Before: accept({ source, key, entry: { amount: 42 } }) // typed as the output; `z.string()` refused it
|
|
15823
|
+
// After: accept({ source, key, entry: { amount: '42' } }) // delivered as { amount: 42 }
|
|
15824
|
+
```
|
|
15825
|
+
|
|
15826
|
+
A name that is already in `rejected/` no longer stops the pass: identical bytes are the entry
|
|
15827
|
+
already set aside, different bytes are kept as `rejected/<name>.<16 hex digits>`.
|
|
15828
|
+
|
|
15829
|
+
### journal startup scan
|
|
15830
|
+
|
|
15831
|
+
**Who must act:** operators that read `status.recovery` right after `createDiagnosticJournal` to
|
|
15832
|
+
find damaged rows in the middle of a file.
|
|
15833
|
+
|
|
15834
|
+
Opening a journal reads only the last line of the active file and of every retained
|
|
15835
|
+
generation (`startupScan: 'tails'`, the default). A crash tears the end of a file, so a torn tail
|
|
15836
|
+
is still reported and still rotated away, and opening no longer parses and validates every
|
|
15837
|
+
line of every generation. It still reads each file once in large blocks to count lines, so the time
|
|
15838
|
+
grows with the bytes retained, at read speed. Pass `startupScan: 'full'` to read and validate every line as before:
|
|
15839
|
+
|
|
15840
|
+
```ts
|
|
15841
|
+
// Before: every open parsed every line of every generation.
|
|
15842
|
+
// After: the same full scan, on request.
|
|
15843
|
+
const journal = await createDiagnosticJournal({ path, eventSchema, limits, startupScan: 'full' })
|
|
15844
|
+
```
|
|
15845
|
+
|
|
15846
|
+
### journal startup quarantine
|
|
15847
|
+
|
|
15848
|
+
**Who must act:** operators of a journal that is a source of truth and must not start over
|
|
15849
|
+
damaged retained files; code that constructs `DiagnosticJournalRecoveryError` or reads its
|
|
15850
|
+
`recovery`; protocols that embed `DiagnosticJournalRecoveryStatusSchema`, and their readers.
|
|
15851
|
+
A diagnostic journal needs no change.
|
|
15852
|
+
|
|
15853
|
+
Opening a journal no longer refuses to start over a torn active file with `maxFiles: 1`, a
|
|
15854
|
+
generation name that is not a regular file or a retained file it cannot read. It renames the file
|
|
15855
|
+
to `<file>.quarantined-<epoch>` beside the journal, starts, and lists the move in
|
|
15856
|
+
`getStatus().recovery.quarantined`. Quarantined files are outside retention and never deleted;
|
|
15857
|
+
every later open lists them again, without a `reason`, until you remove them.
|
|
15858
|
+
|
|
15859
|
+
1. A journal whose damage must stop the process declares the old behaviour:
|
|
15860
|
+
|
|
15861
|
+
```ts
|
|
15862
|
+
// Before: refusing was the only behaviour.
|
|
15863
|
+
await createDiagnosticJournal({ path, eventSchema, limits })
|
|
15864
|
+
// After:
|
|
15865
|
+
await createDiagnosticJournal({ path, eventSchema, limits, onStartupRefusal: 'fail' })
|
|
15866
|
+
```
|
|
15867
|
+
|
|
15868
|
+
2. Branch on the refusal's `reason` instead of its message or error class. Every startup refusal
|
|
15869
|
+
about retained evidence is a `DiagnosticJournalRecoveryError`, including the former plain
|
|
15870
|
+
`Error` for a non-file generation and the raw `EACCES` for an unreadable one (now in `cause`):
|
|
15871
|
+
|
|
15872
|
+
```ts
|
|
15873
|
+
// Before: catch (error) { if (error instanceof DiagnosticJournalRecoveryError) use(error.recovery) }
|
|
15874
|
+
// After:
|
|
15875
|
+
catch (error) {
|
|
15876
|
+
if (error instanceof DiagnosticJournalRecoveryError && error.reason === 'torn-without-retention-slot') use(error.recovery)
|
|
15877
|
+
}
|
|
15878
|
+
```
|
|
15879
|
+
|
|
15880
|
+
`recovery` is present only for `torn-without-retention-slot`. The constructor takes one
|
|
15881
|
+
options object: `new DiagnosticJournalRecoveryError({ reason, file, recovery?, cause? })`.
|
|
15882
|
+
|
|
15883
|
+
3. A protocol that embeds `DiagnosticJournalRecoveryStatusSchema` carries the new optional
|
|
15884
|
+
`quarantined` array (at most 32 entries, `reason` optional) and `quarantinedUnlisted` count.
|
|
15885
|
+
The schema is `.strict()`: upgrade every reader that parses it with its own copy before a
|
|
15886
|
+
writer on this version sends a status with either field, or bump your protocol version.
|
|
15887
|
+
|
|
15888
|
+
### error constructors take named options
|
|
15889
|
+
|
|
15890
|
+
**Who must act:** code that constructs an `AppError` or an `ApiError` with anything beyond its
|
|
15891
|
+
code, and classes that extend `AppError` and call `super(code, message, status, …)`. Code that
|
|
15892
|
+
only throws the `notFound`, `badRequest`, `unauthorized`, `forbidden`, `conflict` and
|
|
15893
|
+
`rateLimited` helpers, or only reads `code`, `status`, `details`, `hint`, `traceId`,
|
|
15894
|
+
`retryable` and `cause` from a caught error, needs no change.
|
|
15895
|
+
|
|
15896
|
+
`AppError` (`stitchkit/contract`, `stitchkit/server`, `stitchkit/node`) and `ApiError`
|
|
15897
|
+
(`stitchkit`) keep the code as the one positional argument. Every other value is a named
|
|
15898
|
+
option, so a call site reads without the signature open. `new AppError(code)` and
|
|
15899
|
+
`new ApiError(code)` are unchanged. The compiler finds every old call: a string or number
|
|
15900
|
+
in the second position is a type error.
|
|
15901
|
+
|
|
15902
|
+
1. `AppError`: move `message`, `status`, `details`, `hint`, `traceId` and `retryable` into one
|
|
15903
|
+
object after the code. `status` defaults to 500 and `message` to the code.
|
|
15904
|
+
|
|
15905
|
+
```ts
|
|
15906
|
+
// before
|
|
15907
|
+
throw new AppError('CONFLICT', 'Selection is stale', 409, { revision: 7 })
|
|
15908
|
+
throw new AppError('BUSY', 'Try later', 503, undefined, undefined, undefined, true)
|
|
15909
|
+
// after
|
|
15910
|
+
throw new AppError('CONFLICT', { message: 'Selection is stale', status: 409, details: { revision: 7 } })
|
|
15911
|
+
throw new AppError('BUSY', { message: 'Try later', status: 503, retryable: true })
|
|
15912
|
+
```
|
|
15913
|
+
|
|
15914
|
+
2. A subclass passes the same object to `super`:
|
|
15915
|
+
|
|
15916
|
+
```ts
|
|
15917
|
+
// before
|
|
15918
|
+
super('FEATURE_LOCKED', 'locked', 403)
|
|
15919
|
+
// after
|
|
15920
|
+
super('FEATURE_LOCKED', { message: 'locked', status: 403 })
|
|
15921
|
+
```
|
|
15922
|
+
|
|
15923
|
+
3. `ApiError`: the old order was `code, status, details, message, hint, traceId, errorOptions,
|
|
15924
|
+
retryable`. Name each value; `status` defaults to 0 and the former `ErrorOptions` argument
|
|
15925
|
+
is now the `cause` option.
|
|
15926
|
+
|
|
15927
|
+
```ts
|
|
15928
|
+
// before
|
|
15929
|
+
new ApiError('RATE_LIMITED', 429, undefined, 'slow down', 'wait', 'trace-1', { cause }, true)
|
|
15930
|
+
new ApiError('VALIDATION_ERROR', 0, { issues }, 'bad input')
|
|
15931
|
+
// after
|
|
15932
|
+
new ApiError('RATE_LIMITED', { status: 429, message: 'slow down', hint: 'wait', traceId: 'trace-1', retryable: true, cause })
|
|
15933
|
+
new ApiError('VALIDATION_ERROR', { status: 0, details: { issues }, message: 'bad input' })
|
|
15934
|
+
```
|
|
15935
|
+
|
|
15936
|
+
4. Both classes accept the standard `cause`, which `error.cause` returns; omit the option to
|
|
15937
|
+
leave the property absent.
|
|
15938
|
+
|
|
15343
15939
|
## Released migration: 0.104.0
|
|
15344
15940
|
|
|
15345
15941
|
JS bundles carrying Darwin native capabilities also need the
|
|
@@ -15465,10 +16061,20 @@ that work actually completes.
|
|
|
15465
16061
|
|
|
15466
16062
|
### Darwin native assets in JS distributions
|
|
15467
16063
|
|
|
15468
|
-
**Who must act:** Bun
|
|
15469
|
-
|
|
15470
|
-
|
|
15471
|
-
|
|
16064
|
+
**Who must act:** Bun builds that import `stitchkit/server`, `stitchkit/files` or
|
|
16065
|
+
`stitchkit/process` (Linux-only server apps included) and use `--outfile`, copy only
|
|
16066
|
+
`outputs[0]`, or package a single file; custom single-file/archive packagers and installers
|
|
16067
|
+
delivering native process/files capabilities.
|
|
16068
|
+
First affected publication: **0.103.13**, through **0.104.2**. It shipped as a patch; its
|
|
16069
|
+
changed delivery graph requires this migration despite unchanged API signatures.
|
|
16070
|
+
|
|
16071
|
+
In those versions every Bun bundle that reaches the lock or process-identity code,
|
|
16072
|
+
`stitchkit/server` included, carries both Darwin `.node` addons as extra outputs on every
|
|
16073
|
+
operating system. `bun build --outfile` then fails with
|
|
16074
|
+
`cannot write multiple output files without an output directory`, and `--outdir` writes the
|
|
16075
|
+
addons next to a Linux artifact. Releases after 0.104.2 resolve the default loader at run time,
|
|
16076
|
+
so these bundles are one JS file with no `.node` output again. The static loader exists only
|
|
16077
|
+
in a build that uses the `stitchkit/files/packaging` plugin.
|
|
15472
16078
|
|
|
15473
16079
|
```sh
|
|
15474
16080
|
# Before: one JS file was assumed to be the entire distribution.
|
|
@@ -15485,12 +16091,39 @@ Custom output layout uses the evolving build-only packaging contract in the
|
|
|
15485
16091
|
|
|
15486
16092
|
Three deliveries have different rules: an ordinary npm install retains the package's
|
|
15487
16093
|
native assets; a JS distribution must ship its complete output graph; a Bun compiled
|
|
15488
|
-
executable embeds the matching addon and may still use `--outfile`.
|
|
16094
|
+
executable embeds the matching addon and may still use `--outfile`. In 0.103.13–0.104.2 a plain
|
|
16095
|
+
`bun build` carries the addon without a plugin; later releases carry it only when the build
|
|
16096
|
+
uses `createNativePackaging` (`delivery: 'companion'` for JS, `'embedded'` for a compiled
|
|
16097
|
+
executable).
|
|
15489
16098
|
The [native IO guide](native-io.md#libraries-with-a-zod-only-runtime) is the canonical recipe.
|
|
15490
16099
|
Check the resulting artifact offline outside the build tree and `node_modules`, including
|
|
15491
16100
|
process identity, live-owner lock refusal, dead-owner recovery and contained IO.
|
|
15492
16101
|
A missing or corrupt addon remains `unavailable`, never evidence that an owner died.
|
|
15493
16102
|
|
|
16103
|
+
### CLI: repeated options are refused
|
|
16104
|
+
|
|
16105
|
+
**Who must act:** wrappers, aliases and scripts that append `--json`, `--profile X` or an
|
|
16106
|
+
application global to arguments the user may already have given, and anything that passes the
|
|
16107
|
+
same global twice.
|
|
16108
|
+
First affected publication: **0.103.13**. It shipped as a patch; the change in grammar needs
|
|
16109
|
+
this migration.
|
|
16110
|
+
|
|
16111
|
+
A repeated option that is not an array field is an argument error:
|
|
16112
|
+
`--json --json` and `--profile a --profile b` fail with `--json was passed 2 times`. Before
|
|
16113
|
+
0.103.13 the last value won, for framework globals, application globals and repeated
|
|
16114
|
+
`kind: 'other'` fields alike. Array fields, and untyped passthrough fields that keep their
|
|
16115
|
+
raw-list grammar, still repeat.
|
|
16116
|
+
|
|
16117
|
+
```sh
|
|
16118
|
+
# Before: a wrapper appended its default and the last value won.
|
|
16119
|
+
mytool deploy --profile staging "$@" --profile production
|
|
16120
|
+
# After: build the argument list once, so each option appears once.
|
|
16121
|
+
mytool deploy --profile "${PROFILE:-production}" "$@"
|
|
16122
|
+
```
|
|
16123
|
+
|
|
16124
|
+
Dotted leaves such as `--filter.name` follow the same rule as their parent field. Declare a
|
|
16125
|
+
field as an array when repetition is meant.
|
|
16126
|
+
|
|
15494
16127
|
## Released migration: 0.103.0
|
|
15495
16128
|
|
|
15496
16129
|
### Diagnostic journal startup preserves torn evidence and inspects archives
|
|
@@ -19278,15 +19911,6 @@ end of this file.
|
|
|
19278
19911
|
# Guide: Project declaration (docs/guide/declaration.md)
|
|
19279
19912
|
==============================================================================
|
|
19280
19913
|
|
|
19281
|
-
---
|
|
19282
|
-
title: Project declaration
|
|
19283
|
-
description: One machine-readable statement a repository makes about itself — identity, roles, build, requirements, release steps and the names of the values a deployment supplies.
|
|
19284
|
-
type: architecture
|
|
19285
|
-
status: active
|
|
19286
|
-
created: 2026-08-25
|
|
19287
|
-
updated: 2026-08-30
|
|
19288
|
-
---
|
|
19289
|
-
|
|
19290
19914
|
# Project declaration
|
|
19291
19915
|
|
|
19292
19916
|
> **Maturity: evolving.** This surface is still finding its shape and may be
|
|
@@ -19849,6 +20473,7 @@ schedules, transports and file generation remain application-owned.
|
|
|
19849
20473
|
|---|---|---|
|
|
19850
20474
|
| `canonicalJson` | function | bounded plain JSON, UTF-16 key sort, optional object members omitted |
|
|
19851
20475
|
| `CanonicalJsonOptions` | type | inferred depth/node/UTF-8 bytes limits |
|
|
20476
|
+
| `CanonicalJsonError` / `CanonicalJsonRefusal` | class / _type_ | the `TypeError` of a refusal and its `reason`: `cycle`, `negative-zero`, `depth`, `nodes`, `bytes` or `not-json` |
|
|
19852
20477
|
|
|
19853
20478
|
|
|
19854
20479
|
## `stitchkit/server`
|
|
@@ -20002,7 +20627,7 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
20002
20627
|
### Primitives
|
|
20003
20628
|
|
|
20004
20629
|
| `createFileStateStore` | function | cross-process locked atomic JSON state updates over one Zod schema |
|
|
20005
|
-
| `FileStateStoreCorruption` / `FileStateStoreOptions` | _type_ | explicit corrupt-state report/policy
|
|
20630
|
+
| `FileStateStoreCorruption` / `FileStateStoreOptions` | _type_ | explicit corrupt-state report/policy, lock timing and the state read cap `maxBytes`; a transition whose resulting state encodes to the bytes it started from writes nothing |
|
|
20006
20631
|
| Export | Kind | Summary |
|
|
20007
20632
|
|--------|------|---------|
|
|
20008
20633
|
| `streamSSE` | function | an async generator → SSE `Response` — [guide](../guide/server.md#sse-streaming) |
|
|
@@ -20108,7 +20733,7 @@ cutovers are covered by the executable
|
|
|
20108
20733
|
| `NotificationOutboxItem` / `NotificationOutboxReceipt` / `NotificationOutboxState` / `NotificationSend` / `NotificationFailureClassification` / `DroppedNotification` / `EnqueueNotification` | _type_ | versioned queue records, stable send identity and delivery decisions |
|
|
20109
20734
|
| `notificationOutboxStateSchema` | function | strict persisted-state schema over an application payload schema |
|
|
20110
20735
|
| `notificationOutboxResource` / `NotificationOutboxResource` / `NotificationOutboxResourceConfig` | function / _type_ | thin managed-resource scheduler over an outbox handle |
|
|
20111
|
-
| `createDirectoryInbox` (from `stitchkit/application/directory-inbox`) / `DirectoryInbox` / `DirectoryInboxConfig` / `DirectoryInboxDelivery` / `DirectoryInboxResource` | function / _type_ | one managed directory inbox for files or durable `accept({ source, key, entry })`; first payload wins, bounded capacity/bytes/receipts and renewable fenced leases; delivery begins after readiness and effects remain at least once |
|
|
20736
|
+
| `createDirectoryInbox` (from `stitchkit/application/directory-inbox`) / `DirectoryInbox` / `DirectoryInboxConfig` / `DirectoryInboxDelivery` / `DirectoryInboxResource` | function / _type_ | one managed directory inbox for files or durable `accept({ source, key, entry })` (`entry` is the schema's input, stored as given); a hard-linked file is set aside as `invalid`; first payload wins, bounded capacity/bytes/receipts and renewable fenced leases; delivery begins after readiness and effects remain at least once |
|
|
20112
20737
|
| `DirectoryInboxIdentitySchema` / `DirectoryInboxIdentity` / `DirectoryInboxAccept` / `DirectoryInboxAcceptResultSchema` / `DirectoryInboxAcceptResult` | schema / _type_ | validated source/key, typed entry and durable accepted/duplicate result with filename; duplicates do not replace the first payload |
|
|
20113
20738
|
| `StateStoreUpdateContext` | _type_ | real active protected transaction: `assertHeld()` refuses after ownership loss or completion; custom adapters supply this context to their transition |
|
|
20114
20739
|
| `DirectoryInboxStateSchema` / `DirectoryInboxState` / `DirectoryInboxRejection` / `DirectoryInboxRejectionReasonSchema` / `DirectoryInboxRejectionReason` | schema / _type_ | the inbox's claims, receipts and rejections (`invalid`, `too-large`, `attempt-limit`) |
|
|
@@ -20194,9 +20819,10 @@ cutovers are covered by the executable
|
|
|
20194
20819
|
| Export | Kind | Summary |
|
|
20195
20820
|
|--------|------|---------|
|
|
20196
20821
|
| `createDiagnosticJournal` | function | create one schema-owned FIFO JSONL writer with bounded retained memory, exclusive local path ownership and finite rotation |
|
|
20197
|
-
| `DiagnosticJournalConfig` / `DiagnosticJournal` | _type_ | owner schema/path/limits/failure observer and the synchronous `submit`, bounded-wait `flush`/`close`, status handle |
|
|
20822
|
+
| `DiagnosticJournalConfig` / `DiagnosticJournal` | _type_ | owner schema/path/limits/failure observer, `startupScan` (`tails` by default, or `full`), `onStartupRefusal` (`quarantine` by default, or `fail`) and the synchronous `submit`, bounded-wait `flush`/`close`, status handle |
|
|
20198
20823
|
| `DiagnosticJournalLimitsSchema` / `DiagnosticJournalLimits` | schema / _type_ | positive event, pending-item, pending-byte, file-byte and retained-file limits |
|
|
20199
20824
|
| `DiagnosticJournalLockPolicySchema` / `DiagnosticJournalLockPolicy` | schema / _type_ | `refuse` (default) or `reclaim-stale`, which reclaims only a lock whose recorded owner is provably gone |
|
|
20825
|
+
| `DiagnosticJournalStartupScanSchema` / `DiagnosticJournalStartupScan` | schema / _type_ | how much of the retained generations opening a journal reads: `tails` (default) checks only the end of each file for a torn line, `full` parses every line |
|
|
20200
20826
|
| `readDiagnosticJournalLockDiagnosis` / `DiagnosticJournalLockDiagnosis` | function / _type_ | why a `reclaim-stale` acquisition refused — attribution, PID liveness and optional process `identity` evidence (`matched`, `different-boot`, `reused-pid`, `pid-gone`, `legacy`, `unavailable`) — read off the thrown `EEXIST` |
|
|
20201
20827
|
| `DiagnosticJournalSubmitResultSchema` / `DiagnosticJournalSubmitResult` | schema / _type_ | accepted epoch/sequence or explicit invalid, oversized, capacity, closed or failed refusal |
|
|
20202
20828
|
| `DiagnosticJournalStatusSchema` / `DiagnosticJournalStatus` | schema / _type_ | state, limits, exact admission/write/failure counters, pending ownership, rotations, partial tails and last safe sequences |
|
|
@@ -20209,8 +20835,10 @@ cutovers are covered by the executable
|
|
|
20209
20835
|
| `DiagnosticJournalWaitOptions` / `DiagnosticJournalFailure` | _type_ | caller wait signal/timeout and isolated internal failure callback record |
|
|
20210
20836
|
| `DiagnosticJournalAnomalySchema` / `DiagnosticJournalAnomaly` | schema / _type_ | corrupt-row reason, file, zero-based byte offset, one-based line, tail/interior position and skipped bytes |
|
|
20211
20837
|
| `createDiagnosticJournalReadResultSchema` / `DiagnosticJournalReadResult` | function / _type_ | owner-schema frame output or explicit anomaly; retains transformed event output types |
|
|
20212
|
-
| `DiagnosticJournalRecoveryStatusSchema` / `DiagnosticJournalRecoveryStatus` | schema / _type_ | bounded startup inspection counts
|
|
20213
|
-
| `
|
|
20838
|
+
| `DiagnosticJournalRecoveryStatusSchema` / `DiagnosticJournalRecoveryStatus` | schema / _type_ | bounded startup inspection counts, first/last anomaly locations and every quarantined file beside the journal (`quarantined`, this open's moves first with their `reason`, at most 32; `quarantinedUnlisted` counts the rest); evolving and `.strict()`, so every shape change is a breaking changelog item |
|
|
20839
|
+
| `DiagnosticJournalStartupRefusalPolicySchema` / `DiagnosticJournalStartupRefusalPolicy` | schema / _type_ | `quarantine` (default) renames a file opening cannot keep in place to `<file>.quarantined-<epoch>` and starts; `fail` throws and moves nothing |
|
|
20840
|
+
| `DiagnosticJournalStartupRefusalReasonSchema` / `DiagnosticJournalStartupRefusalReason` | schema / _type_ | `torn-without-retention-slot \| not-a-regular-file \| unreadable` |
|
|
20841
|
+
| `DiagnosticJournalRecoveryError` / `DiagnosticJournalRecoveryErrorOptions` | class / _type_ | typed startup refusal (`reason`, `file`, optional `recovery`, `quarantineFailed`) thrown under `fail` or when moving a file aside failed; preserves evidence and releases its lock |
|
|
20214
20842
|
|
|
20215
20843
|
`accepted` is bounded in-memory admission and `written` is completed append, not `fsync` or durable
|
|
20216
20844
|
delivery. Its filesystem reader is in the diagnostic-journal leaf; there is no managed replay/upload API. See the
|
|
@@ -20516,7 +21144,7 @@ Server-only optional application runtime. See the
|
|
|
20516
21144
|
|
|
20517
21145
|
| Export | Kind | Summary |
|
|
20518
21146
|
|--------|------|---------|
|
|
20519
|
-
| `AgentConversationArchiveSchema` / `AgentConversationArchive` / `encodeAgentConversationArchive` / `decodeAgentConversationArchive` / `canonicalAgentJson` | schema / _type_ / functions | canonical validated conversation archive with exact ledger events, recovery projection and durable spill payloads |
|
|
21147
|
+
| `AgentConversationArchiveSchema` / `AgentConversationArchive` / `encodeAgentConversationArchive` / `decodeAgentConversationArchive` / `canonicalAgentJson` | schema / _type_ / functions | canonical validated conversation archive (`canonicalAgentJson` is `canonicalJson` with the node and byte limits at their maximum, reading `-0` as `0` like `z.json()`) with exact ledger events, recovery projection and durable spill payloads |
|
|
20520
21148
|
| `AgentStoreEventEnvelopeSchema` / `AgentStoreEventEnvelope` / `AgentStoreEventKindSchema` / `AgentStoreEventKind` / `AgentStoreEventPageSchema` / `AgentStoreEventPage` | schema / _type_ | append-only event envelope, closed current vocabulary (including `provider/request` and `provider/response`) and bounded page |
|
|
20521
21149
|
| `AppendAgentStoreEventSchema` / `AppendAgentStoreEvent` / `ReadAgentStoreEventsSchema` / `ReadAgentStoreEvents` | schema / _type_ | required append and bounded-read store contracts |
|
|
20522
21150
|
| `AgentStoreTransitionSchema` / `AgentStoreTransition` / `AgentStoreEventDraft` / `AgentStoreEventDecodeAccepted` / `AgentStoreEventDecodeIgnored` / `AgentStoreEventDecodeResult` / `decodeAgentStoreEvent` | schema / _types_ / function | normalized runtime mutations, append drafts and strict current/ignorable future-event decoding outcomes |
|
|
@@ -20526,7 +21154,7 @@ Server-only optional application runtime. See the
|
|
|
20526
21154
|
| `defineStateSlot` / `AgentStateSlotDefinition` / `AgentStateSlotStore` / `AgentStateSlotValue` / `AnyAgentStateSlot` / `createAgentStateSlotStore` / `renderAgentStateSlots` | functions / _types_ | typed durable state written as ledger events and injected on every provider request |
|
|
20527
21155
|
| `agentGoalStateSlot` / `agentTodoStateSlot` / `createAgentStateTools` | constants / function | built-in goal/todo state and bound `goal_*` / `todo_write` Agent tools |
|
|
20528
21156
|
| `AgentSandboxGradeSchema` / `AgentSandboxGrade` / `AgentSandboxRestrictionSchema` / `AgentSandboxRestriction` / `AgentProcessSandbox` | schema / _type_ | host-provided process sandbox capability and explicit restriction vocabulary |
|
|
20529
|
-
| `probeAgentProcessSandbox` / `missingSandboxRestrictions` / `recordAgentSandboxProbe` | functions |
|
|
21157
|
+
| `probeAgentProcessSandbox` / `missingSandboxRestrictions` / `recordAgentSandboxProbe` | functions | probe cached per sandbox object (keep the sandbox long-lived), fail-closed required-gap calculation and durable probe record; a probe that throws or returns no valid grade resolves to `unavailable` with a fixed reason, its cause emitted as a process warning (the coding tools instead carry it as the refusal's `cause` to the mount's `onToolError` hook) |
|
|
20530
21158
|
| `AgentSandboxProcess` / `AgentSandboxOutputStream` | _types_ | structural child/output interface for optional lifecycle-owned `AgentProcessSandbox.spawn`, without Node ambient type dependencies |
|
|
20531
21159
|
| `AgentEventSearchResultSchema` / `AgentEventSearchResult` / `createSqliteAgentEventSearch` / `createAgentEventSearchTools` | schema / _type_ / functions | authorized FTS5 search with exact event addresses and `session_*` tools |
|
|
20532
21160
|
| `createSqliteAgentSpillStore` | function | durable content-address-checked artifact storage, bounded read/search, retention facts and archive participation |
|
|
@@ -20821,7 +21449,10 @@ terminal and provider-envelope schemas/types listed under
|
|
|
20821
21449
|
including `AgentAttemptResetEventSchema` for `attempt-reset`),
|
|
20822
21450
|
`AgentRuntimeEventCursorSchema`, `advanceAgentRuntimeEventCursor`,
|
|
20823
21451
|
`AgentControlRequestSchema` / `AgentControlRequest`, `AgentControlResponseSchema` /
|
|
20824
|
-
`AgentControlResponse`, `AgentControlDeliverySchema` / `AgentControlDelivery`,
|
|
21452
|
+
`AgentControlResponse`, `AgentControlDeliverySchema` / `AgentControlDelivery`,
|
|
21453
|
+
`AgentControlErrorCodeSchema` / `AgentControlErrorCode` (the one vocabulary of every control
|
|
21454
|
+
error code: `FORBIDDEN`, `CONNECTION_CLOSED`, `REQUEST_CAPACITY`, `ATTACHMENT_CAPACITY`,
|
|
21455
|
+
`LEASE_CONFLICT`, `EVENT_OVERFLOW` and the rest), `AgentMultiSessionCursorSchema` /
|
|
20825
21456
|
`AgentMultiSessionCursor`, `AgentConversationView`, `AgentControlView`,
|
|
20826
21457
|
`advanceAgentMultiSessionCursor`, `createAgentControlView`, `reduceAgentControlSnapshot`,
|
|
20827
21458
|
`reduceAgentControlEvent` and
|
|
@@ -20851,9 +21482,12 @@ attachment per conversation on that transport; share the controller between view
|
|
|
20851
21482
|
`submit` requires a caller-owned idempotency key. Unknown-outcome mutations are never
|
|
20852
21483
|
replayed automatically. `AgentControllerState` distinguishes connecting, ready,
|
|
20853
21484
|
disconnected, error and closed and retains the canonical full `view` plus safe error
|
|
20854
|
-
code/message. Errors in automatic attach/refresh appear there
|
|
20855
|
-
|
|
20856
|
-
|
|
21485
|
+
code/message. Errors in automatic attach/refresh appear there, and so does an
|
|
21486
|
+
`access-denied` delivery: the server detached the conversation, the state becomes
|
|
21487
|
+
`error` with code `FORBIDDEN`, and nothing arrives until the controller attaches again.
|
|
21488
|
+
Reconnect attaches again and reads a snapshot; requests of the previous connection are
|
|
21489
|
+
rejected at once and release their capacity, snapshot gaps coalesce, stale generations
|
|
21490
|
+
cannot overwrite state, and buffers/queues are bounded. Overflow or failed refresh is visible and requires
|
|
20857
21491
|
explicit recovery. Closing detaches without closing the shared transport or remote run.
|
|
20858
21492
|
|
|
20859
21493
|
See [agent composition](../guide/agent-composition.md) for a complete wiring example.
|
|
@@ -21082,10 +21716,10 @@ payload.
|
|
|
21082
21716
|
| `McpMountConfig` | _type_ | config for `mountMcp` |
|
|
21083
21717
|
| `McpSchemaValidationConfig` | _type_ | shared `{ policy, requireTypedProperties, allowUntyped, requirePortableFormats, allowFormats }` profile |
|
|
21084
21718
|
| `ValidateMcpSchemasConfig` | _type_ | standalone validation profile plus `services`, `extend`, flattening and logger |
|
|
21085
|
-
| `RuntimeToolDefinition` | _type_ |
|
|
21719
|
+
| `RuntimeToolDefinition` | _type_ | heterogeneous registration of pathless operations in `runtimeTools: [...]`; an inline handler is contextually typed by the parsed object, a definition constructed with its own schema registers unchanged, and the runner validates input before the handler |
|
|
21086
21720
|
| `RuntimeToolDefinitionBase` | _type_ | common name, identity, input, exposure and MCP metadata fields |
|
|
21087
|
-
| `RuntimeToolDefinitionWithOutput` | _type_ |
|
|
21088
|
-
| `RuntimeToolDefinitionWithoutOutput` | _type_ |
|
|
21721
|
+
| `RuntimeToolDefinitionWithOutput` | _type_ | the one neutral construction: identity, schemas and a handler typed from them; the fourth type argument names the adapter's `present` extension (`RuntimeToolPresenters<…>` here, `RuntimeMcpToolPresenters<…>` on `stitchkit/tools/mcp`, none by default) |
|
|
21722
|
+
| `RuntimeToolDefinitionWithoutOutput` | _type_ | the same construction with a void handler and no presentation callbacks |
|
|
21089
21723
|
| `RuntimeToolFactory` | _type_ | identity/context-bound runtime-tool definition factory |
|
|
21090
21724
|
| `RuntimeToolFactoryConfig` | _type_ | factory service identity and context schema |
|
|
21091
21725
|
| `RuntimeToolFactoryDefinitionWithOutput` | _type_ | factory-authored runtime tool with a validated output schema |
|
|
@@ -21294,10 +21928,9 @@ Advanced building blocks — the shared machinery the mounts are built on.
|
|
|
21294
21928
|
| `ToolCallRecord` | _type_ | the structured record `createToolLogger` passes to `onRecord` |
|
|
21295
21929
|
| `TransportSummary` | _type_ | `{ contractServices, runtimeTools, totals, sources }` from `summarizeTransports` |
|
|
21296
21930
|
| `TransportCounts` | _type_ | per-transport counts (`{ HTTP, MCP, AGENT, CLI }`) |
|
|
21297
|
-
| `
|
|
21298
|
-
| `ToolSurfaceProjection` | _type_ | one readonly services/runtime container shared by full and MCP-only surfaces; executable mounts specialize the runtime definition without pulling in another adapter's peers |
|
|
21931
|
+
| `ToolSurfaceProjection` | _type_ | the one readonly `{ services?, runtimeTools? }` container shared by full, MCP-only and introspection surfaces (`buildToolManifest`, `listToolNames`, `summarizeTransports` take `ToolSurfaceProjection<RuntimeToolDefinition>`) |
|
|
21299
21932
|
| `ToolManifestConfig` | _type_ | mixed surface plus required model-facing `transport` and presentation options |
|
|
21300
|
-
| `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments |
|
|
21933
|
+
| `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments; throws a `ZodError` naming the field when a union that keeps a string member receives a string that parses to an array its array member accepts |
|
|
21301
21934
|
| `flattenToolJsonSchema` | function | project structurally identifiable discriminated unions into conservative object joins; scalar collisions retain provable types, object/array collisions retain structural alternatives, and field descriptions retain discriminator labels plus availability/requiredness hints; the projection never executes validation |
|
|
21302
21935
|
| `ToolPresentationSchema` | _type_ | immutable model-facing JSON Schema document shared by tool transports |
|
|
21303
21936
|
| `MountableTool` | _type_ | one operation with separate executable CLI argument schema and model-facing presentation schema |
|
|
@@ -21316,10 +21949,9 @@ behavior and names. See [MCP guide](../guide/mcp-and-agents.md#mcp--createmcphan
|
|
|
21316
21949
|
|--------|------|---------|
|
|
21317
21950
|
| `createMcpHandler` / `createMcpHttpRoute` / `buildMcpServer` / `mountMcp` | function | the shared authenticated HTTP handler, raw-route adapter, SDK server and contract mount |
|
|
21318
21951
|
| `createStdioMcpServer` / `bindStdioProcessSignals` | function | the shared stdio server and bounded process cleanup |
|
|
21319
|
-
| `
|
|
21320
|
-
| `
|
|
21952
|
+
| `RuntimeToolDefinitionWithOutput` / `RuntimeToolDefinitionWithoutOutput` | _type_ | the one neutral construction, shared with `stitchkit/cli` and `stitchkit/tools`; MCP-only presenters are its fourth type argument, `RuntimeMcpToolPresenters<z.output<typeof output>>` |
|
|
21953
|
+
| `RuntimeToolDefinition` | _type_ | heterogeneous registration; the canonical runner validates input/output before invocation |
|
|
21321
21954
|
| `RuntimeMcpToolPresenters` / `RuntimeMcpPresentation` | _type_ | official SDK content without framework-owned `structuredContent` or `isError`; one presenter owner shared with the full SDK surface |
|
|
21322
|
-
| `RuntimeToolDefinitionWithoutOutput` | _type_ | the shared neutral void definition |
|
|
21323
21955
|
|
|
21324
21956
|
## `stitchkit/tools/connections`
|
|
21325
21957
|
|
|
@@ -21419,7 +22051,6 @@ handler pipeline without opening a TCP port.
|
|
|
21419
22051
|
| `McpSchemaValidationConfig` / `IncompatibleSchemaPolicy` | _type_ | canonical MCP schema-preparation policy used by real mounts and manifests |
|
|
21420
22052
|
| `SurfaceRealtimeSchemaPairSchema` | schema | input/output digest pair for args or acknowledgements |
|
|
21421
22053
|
| `SurfaceSchemaDigestsSchema` | schema | params/input/output/multipart digest object |
|
|
21422
|
-
| `serializeSurfaceValue` | function | canonical versioned serialization used for deterministic digests |
|
|
21423
22054
|
|
|
21424
22055
|
---
|
|
21425
22056
|
|
|
@@ -21440,8 +22071,10 @@ available from `stitchkit/contract`.
|
|
|
21440
22071
|
| `ManagedFileError` / `ManagedFileErrorCode` | class / _type_ | stable boundary failures; registered `FILE_*` mistakes are caller-safe while `FILE_IO_ERROR` remains internal |
|
|
21441
22072
|
| `ManagedFileInspector` | _type_ | bounded-prefix read/write inspection callback with a finite cancellation signal that cannot own path or size |
|
|
21442
22073
|
| `ManagedFileInspectionInput` / `ManagedFileInspection` | _type_ | inspector prefix/name/declared media/signal input and validated metadata-only result |
|
|
21443
|
-
| `writeFileAtomic` / `writeFileAtomicSync` / `WriteFileAtomicOptions` | function / _type_ | replace a file atomically: a random staging name created exclusively (never through a planted link), the mode set on the descriptor before the file is visible (default `0o600`, not masked by the umask), `fsync`, rename; a failure leaves the target and no staging file. The asynchronous form keeps the event loop running |
|
|
21444
|
-
| `
|
|
22074
|
+
| `writeFileAtomic` / `writeFileAtomicSync` / `WriteFileAtomicOptions` | function / _type_ | replace a file atomically: a random staging name created exclusively (never through a planted link), the mode set on the descriptor before the file is visible (default `0o600`, not masked by the umask), `fsync`, rename; a failure leaves the target and no staging file. The asynchronous form keeps the event loop running. A process killed before publication leaves its staging file; sweeping it is the caller's job |
|
|
22075
|
+
| `isAtomicStagingName` | function | true for a staging file name of an atomic write of this library (`.stitchkit-<24 lowercase hex>.tmp`, also used by the managed writer and the chunk spool); the form is a stable contract, and changing it is a breaking change ([ADR 0242](../decisions/0242-atomic-staging-name-is-a-public-contract.md)) |
|
|
22076
|
+
| `sweepAtomicStaging` / `SweepAtomicStagingOptions` | function / _type_ | remove abandoned staging files from one `directory`: only regular files matching `isAtomicStagingName` and older than the required `olderThanMs`, never following a symlink or entering a subdirectory; stops on `signal`; returns the removed names, `[]` for a `directory` that does not exist |
|
|
22077
|
+
| `withExclusiveLock` / `ExclusiveLockOptions` / `ExclusiveLock` / `ExclusiveLockOwner` | function / _type_ | run work under an exclusive lock between processes — a file recording its owner (pid, host, machine identity, time and nullable kernel process identity); waits up to `timeoutMs` (default 10 s) and stops on `signal`; a dead owner on this machine is taken over, a live, slow or foreign one never is; the lock is published with its owner already recorded (temporary file plus hard link), so a stalled holder is never displaced; a legacy empty lock file is taken by age only when `ownerlessGraceMs` is a number (default `null`: never; an abandoned empty reclaim guard is taken after 5 000 ms unless a value is set); exact `mode` is independent of umask; shared readers need a common group and traversable directories |
|
|
21445
22078
|
| `ExclusiveLockError` | class | `LOCK_TIMEOUT` naming the resource and its holder, or `LOCK_ABORTED` with the signal's reason |
|
|
21446
22079
|
| `createChunkSpool` | function | the server half of a chunked upload: parts on disk under `owner` + client-minted `uploadId`, each with a size+sha256 receipt; idempotent `open` and `put`, the first writer of a part wins across processes, `assemble` in order once every part is in, `sweep` of untouched uploads — [guide](../guide/client.md#chunked-uploads) |
|
|
21447
22080
|
| `ChunkSpool` / `ChunkSpoolConfig` / `ChunkSpoolKey` / `ChunkSpoolOpen` / `ChunkSpoolPart` / `ChunkSpoolAssembly` | _type_ | the spool, its directory, part size, file limit, meta schema and age; an upload's key; its declaration; one part; the assembled parts with a concatenating `stream()` |
|
|
@@ -21819,7 +22452,6 @@ SDK nor the `ai` peer.
|
|
|
21819
22452
|
| `checkCliUpdate` | function | bounded, interval-limited, never-throwing check for a newer published build |
|
|
21820
22453
|
| `applyCliUpdate` | function | download, verify the decompressed digest and replace the binary by rename |
|
|
21821
22454
|
| `assertCliPublishable` | function | refuse republishing one version from a different commit — takes one manifest or every manifest already published, because the promise a version makes does not stop at a channel boundary |
|
|
21822
|
-
| `publishCli` / `CliPublicationOptions` / `CliPublicationPhase` / `CliPublicationResult` | function / _type_ | optional bounded builder/admission over one immutable name/version/commit stamp; commit verified assets before the manifest pointer, recover exact completed versions, retain verified history; existing signatures/installer/updater own the wire contract |
|
|
21823
22455
|
| `rollbackCliUpdate` / `CliRollbackConfig` / `RolledBackCliUpdate` | function / _types_ | put the kept previous build back, digest-checked first and written by the same atomic rename the update uses |
|
|
21824
22456
|
| `signCliManifest` / `verifyCliManifest` / `cliManifestSigningPayload` | function | Ed25519 over `{name, version, commit, builtAt, assets[]}` — every asset's digest included, so the chain closes on the file that executes |
|
|
21825
22457
|
| `cliSignatureAccepted` / `CliSignatureVerdict` | function / _type_ | `valid` \| `unenforced` \| `missing` \| `unknown-key` \| `invalid`; `unenforced` keeps an unpinned build updating while making the absence of a check visible |
|
|
@@ -21851,8 +22483,8 @@ SDK nor the `ai` peer.
|
|
|
21851
22483
|
| `CliConfig` | _type_ | config for `createCli`; `defaultCommand`, `globalOptions`, `optionAliases` and `positionals` define the shared command presentation policy |
|
|
21852
22484
|
| `CliPresentationPolicyConfig` | _type_ | shared command presentation-policy subset of `CliConfig` |
|
|
21853
22485
|
| `CliSurfaceSource` | _type_ | static service/runtime array or identity-dependent factory |
|
|
21854
|
-
| `
|
|
21855
|
-
| `
|
|
22486
|
+
| `RuntimeToolDefinition` | _type_ | neutral heterogeneous runtime-tool registration; an inline handler is contextually typed, and handlers execute through the schema-validating managed runner |
|
|
22487
|
+
| `RuntimeToolDefinitionWithOutput` | _type_ | strict neutral managed definition with schema-derived handler input/output, the same declaration `stitchkit/tools` constructs with presenters |
|
|
21856
22488
|
| `RuntimeToolDefinitionWithoutOutput` | _type_ | strict neutral managed void definition without an output contract or transport presenters |
|
|
21857
22489
|
| `CliCommandDefinition` | _type_ | native command definition union |
|
|
21858
22490
|
| `CliCommandDefinitionBase` | _type_ | native command name, description and input schema |
|
|
@@ -21869,6 +22501,22 @@ SDK nor the `ai` peer.
|
|
|
21869
22501
|
|
|
21870
22502
|
---
|
|
21871
22503
|
|
|
22504
|
+
## `stitchkit/cli/publish`
|
|
22505
|
+
|
|
22506
|
+
Build and release tooling, evolving. Publishes a CLI distribution into a local
|
|
22507
|
+
application-owned directory; the manifest, signatures, installer and updater it
|
|
22508
|
+
writes for stay in `stitchkit/cli` — [guide](../guide/cli.md#publishing-a-complete-version),
|
|
22509
|
+
[ADR 0245](../decisions/0245-cli-publication-is-an-evolving-leaf.md).
|
|
22510
|
+
|
|
22511
|
+
| Export | Kind | Summary |
|
|
22512
|
+
|--------|------|---------|
|
|
22513
|
+
| `publishCli` | function | optional bounded builder/admission over one immutable name/version/commit stamp; commit verified assets before the manifest pointer, recover exact completed versions, retain verified history |
|
|
22514
|
+
| `CliPublicationOptions` | _type_ | identity, storage root, base URL, targets, limits, retention, the `admit` and `build` callbacks, optional signing and trust |
|
|
22515
|
+
| `CliPublicationPhase` | _type_ | the step `admit` is asked about — `prepare`, `build`, `commit` or `promote` |
|
|
22516
|
+
| `CliPublicationResult` | _type_ | `published` or `existing`, with the manifest ready to serve |
|
|
22517
|
+
|
|
22518
|
+
---
|
|
22519
|
+
|
|
21872
22520
|
## `stitchkit/react`
|
|
21873
22521
|
|
|
21874
22522
|
Browser- and server-render-safe React data-layer helpers. Needs the
|
|
@@ -21957,11 +22605,20 @@ network enforcement, gateway credentials and platform/resource boundaries.
|
|
|
21957
22605
|
|
|
21958
22606
|
## `stitchkit/agent-runtime/realtime`
|
|
21959
22607
|
|
|
21960
|
-
Optional evolving Socket.IO server binding
|
|
21961
|
-
|
|
21962
|
-
|
|
21963
|
-
|
|
21964
|
-
|
|
22608
|
+
Optional evolving Socket.IO server binding, experimental: no committed consumer yet.
|
|
22609
|
+
`bindAgentHarnessRealtime` takes `(harness, socket, config)` and returns an
|
|
22610
|
+
`AgentHarnessRealtimeBinding`: `close()` for its subscriptions and control connections, without closing
|
|
22611
|
+
socket or harness, and `revoke(conversationId, matches?)`. `AgentHarnessRealtimeConfig` requires
|
|
22612
|
+
`authorize({ identity, request, source, signal })`: return `{ context }` after verifying the session
|
|
22613
|
+
principal, conversation, operation, files and metadata, or `null`. Identity is `socket.data` from the
|
|
22614
|
+
application's verified handshake. Every request is authorized on arrival. The events of an
|
|
22615
|
+
attached conversation are delivered under the grant the latest successful request created, so a
|
|
22616
|
+
token stream costs no authorization call per event. `revoke` drops the grant of every matching
|
|
22617
|
+
socket; the next event is re-authorized (`source: 'delivery'`, an `operation: 'snapshot'` request),
|
|
22618
|
+
and a refusal or failure detaches the conversation and sends an `access-denied` delivery, which
|
|
22619
|
+
turns the browser controller into an `error` state. Without a `revoke` call a grant lasts until the socket disconnects or the conversation
|
|
22620
|
+
is detached; this fail-open default is deliberate, so call `revoke` whenever access to a conversation may
|
|
22621
|
+
have been withdrawn. An authorization that began before a `revoke` never restores the grant. Observe access does
|
|
21965
22622
|
not grant mutation; controller leases and approval policy still belong to the harness.
|
|
21966
22623
|
|
|
21967
22624
|
`maxPendingEvents` and `maxPendingAttachments` forward to the canonical control server;
|
|
@@ -21973,23 +22630,11 @@ application transport. A transport's Engine.IO payload limit remains application
|
|
|
21973
22630
|
|
|
21974
22631
|
## `stitchkit/agent-runtime/react`
|
|
21975
22632
|
|
|
21976
|
-
Optional evolving React peer leaf. `useAgent` takes a controller and subscribes through
|
|
22633
|
+
Optional evolving React peer leaf, experimental: no committed consumer yet. `useAgent` takes a controller and subscribes through
|
|
21977
22634
|
`useSyncExternalStore`, returning the same `AgentControllerState` as `getSnapshot()`.
|
|
21978
22635
|
Create and close the controller at application/session scope. Multiple components can
|
|
21979
22636
|
observe it; StrictMode and unmount release only the component's own subscription.
|
|
21980
22637
|
|
|
21981
|
-
## `stitchkit/agent-runtime/harness-tools`
|
|
21982
|
-
|
|
21983
|
-
Optional evolving composition of the full `mountAgent` configuration with the runtime
|
|
21984
|
-
fence. Its runtime peer is `ai`; its full tool presenter declarations also require
|
|
21985
|
-
`@modelcontextprotocol/server`, as `stitchkit/tools` does. The base harness retains its
|
|
21986
|
-
AI-only peer boundary. Existing custom tool callbacks need no new import.
|
|
21987
|
-
|
|
21988
|
-
| Export | Kind | Summary |
|
|
21989
|
-
|--------|------|---------|
|
|
21990
|
-
| `createAgentHarnessTools` / `AgentHarnessToolsConfig` | function / _type_ | async per-run full mount configuration (`services` plus all `AgentMountConfig` options); composes application lifecycle then runtime fence exactly once |
|
|
21991
|
-
|
|
21992
|
-
|
|
21993
22638
|
## `stitchkit/process`
|
|
21994
22639
|
|
|
21995
22640
|
Native POSIX Bun/Node one-shot command execution. See [native IO](../guide/native-io.md).
|
|
@@ -21997,8 +22642,9 @@ Native POSIX Bun/Node one-shot command execution. See [native IO](../guide/nativ
|
|
|
21997
22642
|
| Export | Kind | Purpose |
|
|
21998
22643
|
|---|---|---|
|
|
21999
22644
|
| `runNativeCommand` | function | caller-lifetime streaming or explicitly bounded binary capture |
|
|
22000
|
-
| `NativeCommandOptions` | type | executable/args, env policy, signal/deadline, bytes sink
|
|
22001
|
-
| `
|
|
22645
|
+
| `NativeCommandOptions` | type | executable/args, env policy, signal/deadline, bytes sink, finite cleanup limits, the `stop` policy and `descendants` (`'terminate-after-leader'`, the default: stop the group after the leader exits, or `'leave'`); an unknown key is refused by name, a removed one (`killGraceMs`) with its replacement |
|
|
22646
|
+
| `NativeCommandStopPolicy` | type | how a cancelled command stops: `target` `'group'` (signal every member) or `'leader'` (signal only the leader; its exit ends the grace), `signal` (default `SIGTERM`), `graceMs` (at most one hour) before KILL to the group, and `killOn`, an `AbortSignal` that skips the grace; default `{ target: 'group', signal: 'SIGTERM', graceMs: 100 }` |
|
|
22647
|
+
| `NativeCommandResult` | type | observed nullable exit code/signal, `descendantsStopped` (members left in the group after the leader exited were stopped) and captured stdout/stderr bytes |
|
|
22002
22648
|
| `NativeCommandError` | class | COMMAND_LIMIT with machine reason `deadline` or `output-budget`; COMMAND_UNAVAILABLE / COMMAND_CLEANUP have no limit reason. Preserves internal cause and existing constructor; caller-created limits without supplied reason remain unclassified |
|
|
22003
22649
|
|
|
22004
22650
|
| `NativeCommandSettlement` | type | observed leader `exit` or terminal `error`, including synchronous native launch failure; `onLeaderSettled` runs once before drain, bounded by cleanupTimeoutMs, with a cancellable settlement signal; schema/pre-abort refusal creates no settlement obligation |
|
|
@@ -22010,19 +22656,29 @@ Native POSIX Bun/Node one-shot command execution. See [native IO](../guide/nativ
|
|
|
22010
22656
|
|
|
22011
22657
|
`stitchkit/files/packaging` owns `createNativePackaging` and the types
|
|
22012
22658
|
`NativePackagingOptions`, `NativePackagingAsset`, `NativePackagingPlugin`, `NativePackagingResult`.
|
|
22013
|
-
It resolves the installed package's target/version
|
|
22014
|
-
|
|
22015
|
-
|
|
22016
|
-
|
|
22659
|
+
It resolves the installed package's target/version, reads each selected addon once and checks it
|
|
22660
|
+
against the `size` and `sha256` published in the package's `native-assets.json` (`formatVersion: 2`,
|
|
22661
|
+
written when Stitchkit is built). Call it once per build and reuse the result.
|
|
22662
|
+
`platform`, `architecture`, `delivery`, `entryPath` and `assetPath` are explicit public inputs;
|
|
22663
|
+
`assetPath` accepts one template, `[hash]`, replaced by the first 16 hex digits of the addon's
|
|
22664
|
+
published SHA256;
|
|
22665
|
+
`platform` is the closed set `'darwin'`, and another name is a type error and a schema refusal.
|
|
22666
|
+
The result is `ready` with verified assets and a structural Bun plugin, or `unsupported` /
|
|
22667
|
+
`missing` / `mismatch` with the safe code `NATIVE_TARGET_UNSUPPORTED`, `NATIVE_ASSET_MISSING` or
|
|
22668
|
+
`NATIVE_ASSET_DIGEST_MISMATCH`. A `mismatch` names the first differing `architecture` with
|
|
22669
|
+
`expected` (published) and `actual` (installed) `{ size, sha256 }`.
|
|
22670
|
+
Malformed options, a manifest of another `formatVersion` and other filesystem errors throw rather
|
|
22671
|
+
than masquerade as missing.
|
|
22017
22672
|
Companion accepts a single `architecture` string with an `assetPath` string, or an architecture
|
|
22018
22673
|
array with an exact per-architecture `assetPath` map. `NativePackagingOptions<true>` and
|
|
22019
22674
|
`NativePackagingResult<true>` describe the array form; default types preserve single-target signatures.
|
|
22020
22675
|
A ready array-form result carries the architecture array; `NativePackagingAsset<true>` identifies
|
|
22021
|
-
each asset architecture.
|
|
22676
|
+
each asset architecture. A single-target asset has four fields: `outputPath`, the verified `bytes`,
|
|
22677
|
+
and the published `size` and `sha256`.
|
|
22022
22678
|
Targets must be unique; entry and all asset paths must be disjoint. One lazy loader chooses only
|
|
22023
22679
|
the actual runtime architecture, with no fallback.
|
|
22024
|
-
Companion mode places external addons relative to the emitted entry; embedded mode
|
|
22025
|
-
Bun compile
|
|
22026
|
-
and
|
|
22680
|
+
Companion mode places external addons relative to the emitted entry; embedded mode gives
|
|
22681
|
+
Bun compile the verified bytes of the matching addon. Keep JS builds without splitting, preserve
|
|
22682
|
+
every output, and write each asset's `bytes` to its `outputPath`.
|
|
22027
22683
|
This evolving entrypoint is a build dependency; runtime imports do not reach it.
|
|
22028
22684
|
The detailed recipe is in [native IO](../guide/native-io.md#public-native-packaging).
|