@smthrs/triggers 0.0.0-stage → 1.0.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/CHANGELOG.md +219 -0
  2. package/LICENSE +21 -0
  3. package/README.md +109 -2
  4. package/dist/cjs/CatchUp.d.ts +25 -0
  5. package/dist/cjs/CatchUp.d.ts.map +1 -0
  6. package/dist/cjs/CatchUp.js +72 -0
  7. package/dist/cjs/CatchUp.js.map +7 -0
  8. package/dist/cjs/Channel.d.ts +108 -0
  9. package/dist/cjs/Channel.d.ts.map +1 -0
  10. package/dist/cjs/Channel.js +25 -0
  11. package/dist/cjs/Channel.js.map +7 -0
  12. package/dist/cjs/ClaimDecision.d.ts +183 -0
  13. package/dist/cjs/ClaimDecision.d.ts.map +1 -0
  14. package/dist/cjs/ClaimDecision.js +144 -0
  15. package/dist/cjs/ClaimDecision.js.map +7 -0
  16. package/dist/cjs/Cron.d.ts +91 -0
  17. package/dist/cjs/Cron.d.ts.map +1 -0
  18. package/dist/cjs/Cron.js +108 -0
  19. package/dist/cjs/Cron.js.map +7 -0
  20. package/dist/cjs/DispatchReader.d.ts +77 -0
  21. package/dist/cjs/DispatchReader.d.ts.map +1 -0
  22. package/dist/cjs/DispatchReader.js +121 -0
  23. package/dist/cjs/DispatchReader.js.map +7 -0
  24. package/dist/cjs/Overlap.d.ts +40 -0
  25. package/dist/cjs/Overlap.d.ts.map +1 -0
  26. package/dist/cjs/Overlap.js +37 -0
  27. package/dist/cjs/Overlap.js.map +7 -0
  28. package/dist/cjs/Schedule.d.ts +96 -0
  29. package/dist/cjs/Schedule.d.ts.map +1 -0
  30. package/dist/cjs/Schedule.js +61 -0
  31. package/dist/cjs/Schedule.js.map +7 -0
  32. package/dist/cjs/Scheduler.d.ts +248 -0
  33. package/dist/cjs/Scheduler.d.ts.map +1 -0
  34. package/dist/cjs/Scheduler.js +647 -0
  35. package/dist/cjs/Scheduler.js.map +7 -0
  36. package/dist/cjs/SqlTriggerStore.d.ts +29 -0
  37. package/dist/cjs/SqlTriggerStore.d.ts.map +1 -0
  38. package/dist/cjs/SqlTriggerStore.js +483 -0
  39. package/dist/cjs/SqlTriggerStore.js.map +7 -0
  40. package/dist/cjs/Trigger.d.ts +81 -0
  41. package/dist/cjs/Trigger.d.ts.map +1 -0
  42. package/dist/cjs/Trigger.js +54 -0
  43. package/dist/cjs/Trigger.js.map +7 -0
  44. package/dist/cjs/TriggerError.d.ts +58 -0
  45. package/dist/cjs/TriggerError.d.ts.map +1 -0
  46. package/dist/cjs/TriggerError.js +83 -0
  47. package/dist/cjs/TriggerError.js.map +7 -0
  48. package/dist/cjs/TriggerStore.d.ts +411 -0
  49. package/dist/cjs/TriggerStore.d.ts.map +1 -0
  50. package/dist/cjs/TriggerStore.js +127 -0
  51. package/dist/cjs/TriggerStore.js.map +7 -0
  52. package/dist/cjs/Webhook.d.ts +118 -0
  53. package/dist/cjs/Webhook.d.ts.map +1 -0
  54. package/dist/cjs/Webhook.js +153 -0
  55. package/dist/cjs/Webhook.js.map +7 -0
  56. package/dist/cjs/index.d.ts +30 -0
  57. package/dist/cjs/index.d.ts.map +1 -0
  58. package/dist/cjs/index.js +57 -0
  59. package/dist/cjs/index.js.map +7 -0
  60. package/dist/cjs/internal/ActiveRuns.d.ts +51 -0
  61. package/dist/cjs/internal/ActiveRuns.d.ts.map +1 -0
  62. package/dist/cjs/internal/ActiveRuns.js +56 -0
  63. package/dist/cjs/internal/ActiveRuns.js.map +7 -0
  64. package/dist/cjs/internal/DueOccurrences.d.ts +32 -0
  65. package/dist/cjs/internal/DueOccurrences.d.ts.map +1 -0
  66. package/dist/cjs/internal/DueOccurrences.js +80 -0
  67. package/dist/cjs/internal/DueOccurrences.js.map +7 -0
  68. package/dist/cjs/internal/WallClock.d.ts +59 -0
  69. package/dist/cjs/internal/WallClock.d.ts.map +1 -0
  70. package/dist/cjs/internal/WallClock.js +132 -0
  71. package/dist/cjs/internal/WallClock.js.map +7 -0
  72. package/dist/cjs/migrations/0001_triggers.d.ts +15 -0
  73. package/dist/cjs/migrations/0001_triggers.d.ts.map +1 -0
  74. package/dist/cjs/migrations/0001_triggers.js +64 -0
  75. package/dist/cjs/migrations/0001_triggers.js.map +7 -0
  76. package/dist/cjs/migrations/0002_reservation_lease.d.ts +15 -0
  77. package/dist/cjs/migrations/0002_reservation_lease.d.ts.map +1 -0
  78. package/dist/cjs/migrations/0002_reservation_lease.js +41 -0
  79. package/dist/cjs/migrations/0002_reservation_lease.js.map +7 -0
  80. package/dist/cjs/migrations/0003_heartbeat.d.ts +16 -0
  81. package/dist/cjs/migrations/0003_heartbeat.d.ts.map +1 -0
  82. package/dist/cjs/migrations/0003_heartbeat.js +44 -0
  83. package/dist/cjs/migrations/0003_heartbeat.js.map +7 -0
  84. package/dist/cjs/migrations/0004_fire_run_index.d.ts +17 -0
  85. package/dist/cjs/migrations/0004_fire_run_index.d.ts.map +1 -0
  86. package/dist/cjs/migrations/0004_fire_run_index.js +40 -0
  87. package/dist/cjs/migrations/0004_fire_run_index.js.map +7 -0
  88. package/dist/cjs/migrations/index.d.ts +33 -0
  89. package/dist/cjs/migrations/index.d.ts.map +1 -0
  90. package/dist/cjs/migrations/index.js +50 -0
  91. package/dist/cjs/migrations/index.js.map +7 -0
  92. package/dist/cjs/package.json +1 -0
  93. package/dist/cjs/test/TestTriggers.d.ts +12 -0
  94. package/dist/cjs/test/TestTriggers.d.ts.map +1 -0
  95. package/dist/cjs/test/TestTriggers.js +421 -0
  96. package/dist/cjs/test/TestTriggers.js.map +7 -0
  97. package/dist/esm/CatchUp.d.ts +25 -0
  98. package/dist/esm/CatchUp.d.ts.map +1 -0
  99. package/dist/esm/CatchUp.js +52 -0
  100. package/dist/esm/CatchUp.js.map +1 -0
  101. package/dist/esm/Channel.d.ts +108 -0
  102. package/dist/esm/Channel.d.ts.map +1 -0
  103. package/dist/esm/Channel.js +19 -0
  104. package/dist/esm/Channel.js.map +1 -0
  105. package/dist/esm/ClaimDecision.d.ts +183 -0
  106. package/dist/esm/ClaimDecision.d.ts.map +1 -0
  107. package/dist/esm/ClaimDecision.js +172 -0
  108. package/dist/esm/ClaimDecision.js.map +1 -0
  109. package/dist/esm/Cron.d.ts +91 -0
  110. package/dist/esm/Cron.d.ts.map +1 -0
  111. package/dist/esm/Cron.js +132 -0
  112. package/dist/esm/Cron.js.map +1 -0
  113. package/dist/esm/DispatchReader.d.ts +77 -0
  114. package/dist/esm/DispatchReader.d.ts.map +1 -0
  115. package/dist/esm/DispatchReader.js +152 -0
  116. package/dist/esm/DispatchReader.js.map +1 -0
  117. package/dist/esm/Overlap.d.ts +40 -0
  118. package/dist/esm/Overlap.d.ts.map +1 -0
  119. package/dist/esm/Overlap.js +31 -0
  120. package/dist/esm/Overlap.js.map +1 -0
  121. package/dist/esm/Schedule.d.ts +96 -0
  122. package/dist/esm/Schedule.d.ts.map +1 -0
  123. package/dist/esm/Schedule.js +73 -0
  124. package/dist/esm/Schedule.js.map +1 -0
  125. package/dist/esm/Scheduler.d.ts +248 -0
  126. package/dist/esm/Scheduler.d.ts.map +1 -0
  127. package/dist/esm/Scheduler.js +734 -0
  128. package/dist/esm/Scheduler.js.map +1 -0
  129. package/dist/esm/SqlTriggerStore.d.ts +29 -0
  130. package/dist/esm/SqlTriggerStore.d.ts.map +1 -0
  131. package/dist/esm/SqlTriggerStore.js +483 -0
  132. package/dist/esm/SqlTriggerStore.js.map +1 -0
  133. package/dist/esm/Trigger.d.ts +81 -0
  134. package/dist/esm/Trigger.d.ts.map +1 -0
  135. package/dist/esm/Trigger.js +56 -0
  136. package/dist/esm/Trigger.js.map +1 -0
  137. package/dist/esm/TriggerError.d.ts +58 -0
  138. package/dist/esm/TriggerError.d.ts.map +1 -0
  139. package/dist/esm/TriggerError.js +110 -0
  140. package/dist/esm/TriggerError.js.map +1 -0
  141. package/dist/esm/TriggerStore.d.ts +411 -0
  142. package/dist/esm/TriggerStore.d.ts.map +1 -0
  143. package/dist/esm/TriggerStore.js +209 -0
  144. package/dist/esm/TriggerStore.js.map +1 -0
  145. package/dist/esm/Webhook.d.ts +118 -0
  146. package/dist/esm/Webhook.d.ts.map +1 -0
  147. package/dist/esm/Webhook.js +186 -0
  148. package/dist/esm/Webhook.js.map +1 -0
  149. package/dist/esm/index.d.ts +30 -0
  150. package/dist/esm/index.d.ts.map +1 -0
  151. package/dist/esm/index.js +30 -0
  152. package/dist/esm/index.js.map +1 -0
  153. package/dist/esm/internal/ActiveRuns.d.ts +51 -0
  154. package/dist/esm/internal/ActiveRuns.d.ts.map +1 -0
  155. package/dist/esm/internal/ActiveRuns.js +34 -0
  156. package/dist/esm/internal/ActiveRuns.js.map +1 -0
  157. package/dist/esm/internal/DueOccurrences.d.ts +32 -0
  158. package/dist/esm/internal/DueOccurrences.d.ts.map +1 -0
  159. package/dist/esm/internal/DueOccurrences.js +55 -0
  160. package/dist/esm/internal/DueOccurrences.js.map +1 -0
  161. package/dist/esm/internal/WallClock.d.ts +59 -0
  162. package/dist/esm/internal/WallClock.d.ts.map +1 -0
  163. package/dist/esm/internal/WallClock.js +155 -0
  164. package/dist/esm/internal/WallClock.js.map +1 -0
  165. package/dist/esm/migrations/0001_triggers.d.ts +15 -0
  166. package/dist/esm/migrations/0001_triggers.d.ts.map +1 -0
  167. package/dist/esm/migrations/0001_triggers.js +42 -0
  168. package/dist/esm/migrations/0001_triggers.js.map +1 -0
  169. package/dist/esm/migrations/0002_reservation_lease.d.ts +15 -0
  170. package/dist/esm/migrations/0002_reservation_lease.d.ts.map +1 -0
  171. package/dist/esm/migrations/0002_reservation_lease.js +19 -0
  172. package/dist/esm/migrations/0002_reservation_lease.js.map +1 -0
  173. package/dist/esm/migrations/0003_heartbeat.d.ts +16 -0
  174. package/dist/esm/migrations/0003_heartbeat.d.ts.map +1 -0
  175. package/dist/esm/migrations/0003_heartbeat.js +23 -0
  176. package/dist/esm/migrations/0003_heartbeat.js.map +1 -0
  177. package/dist/esm/migrations/0004_fire_run_index.d.ts +17 -0
  178. package/dist/esm/migrations/0004_fire_run_index.d.ts.map +1 -0
  179. package/dist/esm/migrations/0004_fire_run_index.js +20 -0
  180. package/dist/esm/migrations/0004_fire_run_index.js.map +1 -0
  181. package/dist/esm/migrations/index.d.ts +33 -0
  182. package/dist/esm/migrations/index.d.ts.map +1 -0
  183. package/dist/esm/migrations/index.js +37 -0
  184. package/dist/esm/migrations/index.js.map +1 -0
  185. package/dist/esm/test/TestTriggers.d.ts +12 -0
  186. package/dist/esm/test/TestTriggers.d.ts.map +1 -0
  187. package/dist/esm/test/TestTriggers.js +439 -0
  188. package/dist/esm/test/TestTriggers.js.map +1 -0
  189. package/docs/README.md +140 -0
  190. package/docs/api.md +1186 -0
  191. package/docs/concepts/channels.md +103 -0
  192. package/docs/concepts/claim-protocol.md +229 -0
  193. package/docs/concepts/policies.md +124 -0
  194. package/docs/concepts/schedules.md +134 -0
  195. package/docs/guides/choose-a-policy.md +113 -0
  196. package/docs/guides/declare-a-trigger.md +136 -0
  197. package/docs/guides/ingest-a-webhook.md +164 -0
  198. package/docs/guides/run-the-scheduler.md +173 -0
  199. package/docs/guides/testing.md +157 -0
  200. package/docs/installation.md +81 -0
  201. package/docs/quickstart.md +163 -0
  202. package/docs/troubleshooting.md +281 -0
  203. package/package.json +241 -3
  204. package/src/CatchUp.ts +73 -0
  205. package/src/Channel.ts +121 -0
  206. package/src/ClaimDecision.ts +303 -0
  207. package/src/Cron.ts +173 -0
  208. package/src/DispatchReader.ts +179 -0
  209. package/src/Overlap.ts +53 -0
  210. package/src/Schedule.ts +112 -0
  211. package/src/Scheduler.ts +1154 -0
  212. package/src/SqlTriggerStore.ts +638 -0
  213. package/src/Trigger.ts +84 -0
  214. package/src/TriggerError.ts +129 -0
  215. package/src/TriggerStore.ts +514 -0
  216. package/src/Webhook.ts +270 -0
  217. package/src/index.ts +30 -0
  218. package/src/internal/ActiveRuns.ts +83 -0
  219. package/src/internal/DueOccurrences.ts +101 -0
  220. package/src/internal/WallClock.ts +172 -0
  221. package/src/migrations/0001_triggers.ts +43 -0
  222. package/src/migrations/0002_reservation_lease.ts +20 -0
  223. package/src/migrations/0003_heartbeat.ts +24 -0
  224. package/src/migrations/0004_fire_run_index.ts +21 -0
  225. package/src/migrations/index.ts +40 -0
  226. package/src/test/TestTriggers.ts +563 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,219 @@
1
+ # @smthrs/triggers
2
+
3
+ ## [Unreleased]
4
+
5
+ ### Added
6
+
7
+ - `TriggerStore.history` reads the fire ledger newest first, filtered by
8
+ trigger, run, or outcome and paged by cursor and limit, so a listing can show
9
+ what a trigger did without joining the runtime's run table.
10
+ - `TriggerStore.inspect` reads the run or reservation and the buffered
11
+ occurrence one trigger holds without expiring anything.
12
+ - `TriggerStore.heartbeat` and `lastHeartbeat`, backed by the
13
+ `0003_heartbeat` migration, record which scheduler host last polled the
14
+ store. `Scheduler.Options.host` names the host; `Scheduler.defaultHost` is
15
+ `"local"`. A heartbeat the store cannot record is logged and the tick goes on.
16
+ - `DispatchReader`, the `@smthrs/control` read port served from a
17
+ `TriggerStore`, so `Control.list` can answer `triggers` and `fires` from the
18
+ store the scheduler writes: last fire, buffered occurrence, active run, the
19
+ next five occurrences, and the newest heartbeat.
20
+ - `TriggerStore.pruneFires({ olderThan })` deletes settled ledger rows older
21
+ than a cutoff and answers how many it removed, keeping the buffered
22
+ occurrence and the row naming the active run. Nothing else in the store
23
+ deletes from `flows_trigger_fires`, so a host that never prunes keeps one row
24
+ per occurrence for as long as the database lives.
25
+ - `Scheduler.Options.fireRetention`, defaulting to thirty days
26
+ (`Scheduler.defaultFireRetention`): a tick prunes settled fires older than
27
+ that at most once an hour. The `0004_fire_run_index` migration indexes the
28
+ ledger by run id.
29
+ - Daylight-saving tests cover `America/Los_Angeles` and `America/New_York`
30
+ across both 2026 transitions: weekly Friday series (including one that
31
+ crosses UTC midnight), a daily time in the spring-forward gap and in the
32
+ fall-back repeat, an hourly series, and host-zone independence, each through
33
+ `Cron` and through the scheduler over the SQL store.
34
+ - The scheduler traces `Scheduler.runOnce`, `Scheduler.processTrigger`, and
35
+ `Scheduler.launch`, and records `smithers.triggers.fires` by outcome plus
36
+ tick and launch duration histograms (`Scheduler.durationBoundaries`).
37
+
38
+ ### Changed
39
+
40
+ - `RunnerService.isActive` is replaced by `inspect`, which answers a
41
+ `RunState`. A run that failed, was cancelled, or is unknown to the runner is
42
+ recorded as a `failed` fire naming that state; it used to be recorded as
43
+ `completed`.
44
+ - A tick no longer waits for a launch. The claim is durable and the launch runs
45
+ on its own fiber, so a plan parked awaiting approval no longer holds other
46
+ triggers, and a boundary another trigger crosses meanwhile is claimed.
47
+ - The first poll after a restart dispatches the current occurrence even when
48
+ the backlog exceeds `maxCatchUp` or the policy is `catchUp: "none"`.
49
+ - `DispatchReader.fires` reads only the ledger rows the requested page needs.
50
+
51
+ - `TriggerStore.list` answers `Listed` rows: the decode result for each row and
52
+ the state that row holds. A row whose stored input cannot be decoded is
53
+ isolated there, with its trigger id in the failure, rather than failing the
54
+ whole listing, so one corrupt declaration no longer stops every healthy
55
+ schedule in the store on every tick.
56
+ - A scheduler tick reads the listed row before it asks the store again. A
57
+ trigger holding neither a run nor a buffered occurrence answers `activeRun`
58
+ and `claimPending` by itself, which cost two write transactions per idle
59
+ trigger per tick. `DispatchReader.list` takes each summary's held state from
60
+ the same listing instead of inspecting per trigger.
61
+ - The guides, the claim-protocol concept, and the `Service` documentation say
62
+ that a tick polls `list` rather than `listEnabled`, because a trigger
63
+ disabled while a run was active still has to recover that occurrence, and
64
+ that only claim, result, pending-state, and active-run methods fail with
65
+ `unknown_trigger`: `get` answers `None` and `register` creates the row.
66
+
67
+ - Both trigger stores now apply one claim decision. The revision and enabled
68
+ fences, the expired-reservation reclaim with its supersede predecessor
69
+ lookup, the three resumable predicates, the cursor advance, and the lease
70
+ recovery behind `activeRun` were written out once in SQL and again over maps;
71
+ each store now gathers its snapshot, calls the shared decision, and applies
72
+ the writes it answers.
73
+ - `test/TestTriggers` registration validates the declaration and serializes its
74
+ input at the call boundary, the way SQL registration already did, so a
75
+ declaration mutated after `register` returns cannot change what was stored
76
+ and an unsatisfiable schedule is refused rather than accepted. Its `list` and
77
+ `listEnabled` order by id, and every reading is an independent value that
78
+ cannot be mutated back into stored state.
79
+ - The testing guide, the reference, the claim-protocol concept, and the README
80
+ state what the in-memory store shares with the SQL store and where it stops:
81
+ it holds one process's state, reports no `store` write failures, shows no
82
+ contention between connections, and applies no migrations.
83
+
84
+ ### Fixed
85
+
86
+ - Daylight saving (#1930). `Cron` now searches wall-clock times and resolves
87
+ each one in the trigger's zone. A time that spring forward skips fires once,
88
+ at the instant it would have had on the old offset (a daily 02:30 fires at
89
+ 03:30 daylight time); `previousAtOrBefore` used to fail `unsatisfiable_cron`
90
+ there, so the scheduler never launched the gap day. A time that fall back
91
+ repeats fires once, at its first instant; the repeated instant used to match
92
+ too and launch the day again under a second key. An expression whose hour
93
+ field is every hour still fires in both passes. Milliseconds are zeroed in
94
+ UTC; on a host whose own zone was repeating an hour, a matched occurrence
95
+ used to come back an hour early.
96
+
97
+ ### Removed
98
+
99
+ - `TriggerStore.takePending`. Nothing dispatched through it: the scheduler
100
+ takes a buffered occurrence with `claimPending`, which applies the claim
101
+ fences in the same transaction, and reads one without consuming it through
102
+ `inspect`. A destructive read beside those was one more method every store
103
+ had to implement and one more way to drop a buffered occurrence with no
104
+ claim decision behind it.
105
+
106
+ ## [1.0.0-rc.0] - 2026-09-01
107
+
108
+ ### Added
109
+
110
+ - Added Effect Cron-backed declarations, overlap and bounded catch-up
111
+ policies, durable CAS fire claims, a scoped scheduler, authority-free
112
+ channels, and verified raw-byte webhooks.
113
+
114
+ ### Changed
115
+
116
+ - A fire claim now carries `expectedRevision` and reads the overlap policy,
117
+ `enabled`, and `revision` inside the claim transaction, so a caller holding a
118
+ declaration snapshot from before an edit is refused with `revision_mismatch`
119
+ or `trigger_disabled` instead of firing it.
120
+ - `TriggerStore.due(now)` is now `TriggerStore.listEnabled()`. The parameter was
121
+ accepted and discarded; due-ness is a cron computation the scheduler performs
122
+ against its own watermark.
123
+ - A webhook `credential` is required and is forwarded to `Channel.Verify` and to
124
+ `Webhook.SignatureConfig.expected` on every request, so a signing secret is
125
+ resolved through the host resolver rather than captured in a closure at
126
+ declaration time. `expected` returns an Effect, so a resolution or HMAC
127
+ failure is a typed `verification_failed` rather than a defect.
128
+ - `Webhook.ingest` copies `body`, `headers`, and `idempotencyKey` before
129
+ verification, so verification, delivery fingerprinting, and decoding all read
130
+ one private snapshot. It no longer registers the channel on every request;
131
+ registration is `Webhook.register`.
132
+ - `Webhook.constantTimeEqual` iterates over the expected signature's length
133
+ rather than the longer of the two inputs, so its work is fixed by the secret
134
+ side of the comparison.
135
+ - Error codes gained `runner`, `invalid_options`, `trigger_disabled`, and
136
+ `revision_mismatch`, and `TriggerError` gained `path`. A declaration failure
137
+ names its offending field and summarizes the schema issue tree instead of
138
+ carrying a five-kilobyte AST as its cause.
139
+ - `last_fired_at_ms` only ever moves forward, so an older run settling after a
140
+ newer occurrence was skipped no longer replays settled work.
141
+ - Scope closure detaches run monitors instead of cancelling them: scheduled runs
142
+ are durable and the next incarnation re-attaches to them.
143
+ - `Cron.occurrencesBetween` caps an unstated limit at `Cron.maxOccurrences` and
144
+ refuses a limit that is not a non-negative safe integer.
145
+ - `Schedule.maxCatchUp` defaults to `0` and is bounded above by
146
+ `Schedule.maxCatchUpLimit`. Every catch-up policy answers to it, `one`
147
+ included.
148
+ - `TriggerStore.Claim` is a discriminated union: a claim that hands the caller
149
+ work to launch carries a required `reservationId`, and a claim that only
150
+ records a decision carries none.
151
+ - `TriggerStore.Service` gained `activeOccurrence(triggerId, runId)`, and
152
+ `TriggerStore.reservationOccurrence` reads the occurrence out of a reservation
153
+ id. A recovered run is settled against the occurrence it actually launched,
154
+ which `lastFiredAt` cannot name once a later occurrence has been skipped or
155
+ buffered past it.
156
+ - A supersede claim records the run it is displacing on the occurrence that
157
+ displaced it, so a claimant that dies before it can cancel leaves the
158
+ predecessor's run id behind for the next incarnation to find.
159
+ - The package documentation is colocated and hand-maintained.
160
+ `packages/smithers/agent/triggers/README.md`, its `docs/` directory, and the
161
+ JSDoc in `src/` own the contract. The `docs` and `docsFiles` targets in
162
+ `PACKAGE.ts` expose them, and `apps/site/scripts/sync-api-docs.mjs` owns the
163
+ site copy.
164
+
165
+ ### Fixed
166
+
167
+ - A run held at Control's `accepted` status is no longer recorded as completed
168
+ on the next poll, which had released the overlap guard for a run that had not
169
+ started.
170
+ - A newly registered trigger no longer fires an arbitrarily stale past
171
+ occurrence under `catchUp: "none"`; its first sight establishes the watermark.
172
+ - A launch reservation recovered from the store is no longer cleared on the
173
+ following tick before its lease expires.
174
+ - The occurrence watermark advances only past occurrences this process finished
175
+ dispatching, and a buffered occurrence taken from the row is re-armed when the
176
+ claim or dispatch that followed it failed.
177
+ - `TriggerStore` failures no longer collapse into `store`: a typed refusal the
178
+ store raised travels out unchanged, and every missing-row path in both the SQL
179
+ store and the in-memory test layer fails with `unknown_trigger`.
180
+ - A parked plan is re-offered a bounded number of times rather than once a
181
+ second for the life of the scope, and a scheduler poll interval must be finite
182
+ and positive.
183
+ - A schema the migrator cannot apply arrives as a typed `store` failure instead
184
+ of a defect that escapes a constructor whose signature promises
185
+ `TriggerError`.
186
+ - A launch no longer resolves the wrong `started` handle. An inner binding
187
+ shadowed the `Deferred` the launch awaits, so `Deferred.succeed` was handed the
188
+ run id string: every scheduler tick that launched a run waited forever.
189
+ - A recovered launch reservation is re-read from the store on every tick rather
190
+ than cached as live, so its lease can expire and re-arm the occurrence it
191
+ holds instead of pinning the trigger until the process restarts.
192
+ - An expired reservation over an occurrence that was claimed but never launched
193
+ re-arms that occurrence instead of leaving it with no outcome, no active run,
194
+ and nothing pointing at it.
195
+ - `claimPending` clears the buffer only when the claim it made is not itself a
196
+ buffer, so an occurrence the overlap policy re-buffers is no longer discarded.
197
+ - A supersede whose cancellation fails restores the run it displaced and queues
198
+ the replacement occurrence, and it records the terminal result before it
199
+ detaches the only monitor that could have written one.
200
+ - A launch whose `launched` result cannot be persisted is left to a later poll
201
+ rather than recorded failed while its run is still executing.
202
+ - A terminal result carrying no run id no longer clears a newer run's active
203
+ pointer.
204
+ - Coverage thresholds are 100 on all four metrics. The ratchet that stood at
205
+ 68/67/81/80 is what let every defect above ship untested.
206
+ - The CommonJS build no longer hands the migrator `{ default }` objects instead
207
+ of migration Effects. esbuild compiles a default import of a sibling ESM
208
+ module to `__toESM(require(...), 1).default`, which under Node interop is the
209
+ whole exports object, so `require("@smthrs/triggers/SqlTriggerStore")` failed
210
+ at the first migration. The migration modules export `triggers` and
211
+ `reservationLease` by name, `migrations/index.ts` imports them by name, and
212
+ the record is exported as `migrations` so a test can assert each entry has
213
+ `pipe`.
214
+
215
+ ### Removed
216
+
217
+ - `@smthrs/triggers/migrations/*` is no longer a public subpath. The intended
218
+ entry point is `SqlTriggerStore.layer`, which applies both migrations in
219
+ order.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 William Cory and the Smithers Flows contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,110 @@
1
- # Temporary Holding Version
1
+ # @smthrs/triggers
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Release candidate scope, host requirements and compatibility review are defined in the [library support policy](https://github.com/smithersai/smithers/blob/main/RELEASE_SUPPORT.md).
4
+
5
+ **Documentation:** https://triggers.smithers.sh
6
+
7
+ Durable cron triggers and verified inbound channels for flows. A trigger is a
8
+ durable row: a flow id, a JSON input, and a cron expression. A channel is a
9
+ verified door: it authenticates raw request bytes and maps them onto a start or
10
+ a signal.
11
+
12
+ Nothing here executes a flow. A launch is handed to
13
+ [`@smthrs/control`](https://control.smithers.sh), so a scheduled or webhook
14
+ started run gets exactly the authority a person starting it would get.
15
+
16
+ ## Install
17
+
18
+ `@smthrs/triggers` is not on npm at 1.0.0-rc.0. It ships as a member of the
19
+ [smithers repository](https://github.com/smithersai/smithers) workspace, so
20
+ using it today means working from a checkout:
21
+
22
+ ```bash
23
+ git clone https://github.com/smithersai/smithers.git
24
+ cd smithers
25
+ pnpm install
26
+ ```
27
+
28
+ It needs Node.js 26.4.0 or later and `effect` 4.0.0-rc.115. The full
29
+ requirements, the import forms, and the layers a running host adds are on
30
+ the [installation page](https://triggers.smithers.sh/installation/).
31
+
32
+ ## The smallest declaration
33
+
34
+ ```ts
35
+ import * as Trigger from "@smthrs/triggers/Trigger"
36
+
37
+ const nightly = Trigger.make({
38
+ id: "nightly-report",
39
+ flowId: "reports/nightly",
40
+ input: { channel: "#ops" },
41
+ cron: "0 3 * * *",
42
+ timezone: "UTC",
43
+ overlap: "skip",
44
+ catchUp: "one",
45
+ maxCatchUp: 1,
46
+ enabled: true
47
+ })
48
+ ```
49
+
50
+ `nightly` is an `Effect<Trigger, TriggerError>`. `Trigger.make` fills the policy
51
+ defaults and refuses a cron expression the calendar never satisfies. Registering
52
+ it in a `TriggerStore` makes it durable, and a running `Scheduler` fires it from
53
+ there. The [quickstart](https://triggers.smithers.sh/quickstart/) walks that
54
+ whole path, launch included, on a test clock.
55
+
56
+ ## What the package does that a cron loop does not
57
+
58
+ - Coordinates claims when two hosts share a database. Lease recovery can retry
59
+ the same occurrence, so preventing duplicate runs requires runner deduplication.
60
+ - Decides what a boundary means while the previous run is still going: skip it,
61
+ remember the newest one, or cancel the run in flight and replace it.
62
+ - Bounds what a trigger owes after downtime, by a number the declaration states.
63
+ - Accepts an inbound request without handing it authority. A verified payload
64
+ names a flow; it cannot widen what that flow may do.
65
+
66
+ Scheduled dispatch makes at-least-once launch attempts. `RunnerService.start`
67
+ must durably deduplicate by `idempotencyKey` and return the same run identity on
68
+ replay, including across host restarts.
69
+
70
+ ## Public API
71
+
72
+ The root entry point exports these namespaces, and each is also importable from
73
+ `@smthrs/triggers/<Module>`. Every export, with its signature and its
74
+ guarantees, is on the
75
+ [API reference](https://triggers.smithers.sh/reference/api/).
76
+
77
+ | Namespace | What it is |
78
+ | ----------------- | -------------------------------------------------------------------------------------------- |
79
+ | `Trigger` | The trigger declaration: a flow id, a JSON input, a schedule, and an enabled flag. |
80
+ | `Schedule` | The reusable schedule half of a declaration: cron, timezone, and the two policies. |
81
+ | `Cron` | Typed wrappers around Effect's cron: parse, next, previous, and a bounded occurrence search. |
82
+ | `Overlap` | The pure decision for an occurrence that arrives while a run is in flight. |
83
+ | `CatchUp` | The pure computation of what a trigger owes after downtime, bounded by its declaration. |
84
+ | `TriggerStore` | The durable state contract: registration, listing, the claim protocol, and results. |
85
+ | `SqlTriggerStore` | The SQLite and PostgreSQL implementation of that contract, with its own migrations. |
86
+ | `Scheduler` | The Clock-driven poller, and the `Runner` port it launches through. |
87
+ | `DispatchReader` | The `@smthrs/control` read port served from a store: trigger summaries and the fire ledger. |
88
+ | `Channel` | The authority-free inbound channel declaration: verify, then map to a start or a signal. |
89
+ | `Webhook` | A verified webhook door built on `Channel`, dispatching only through Control. |
90
+ | `TriggerError` | The one failure type, carrying a stable code and an optional field path. |
91
+
92
+ `@smthrs/triggers/test/TestTriggers` is an in-memory `TriggerStore` for tests.
93
+ It applies the same claim decision as the SQL store, with the same refusal
94
+ codes, lease timing, watermark rules, registration validation, and id-ordered
95
+ listings. It keeps no rows and applies no migrations, so a test about a schema
96
+ or a column shape needs `SqlTriggerStore`.
97
+
98
+ ## Documentation
99
+
100
+ - [Overview](https://triggers.smithers.sh)
101
+ - [Quickstart](https://triggers.smithers.sh/quickstart/)
102
+ - [The claim protocol](https://triggers.smithers.sh/concepts/claim-protocol/)
103
+ - [Choose an overlap and catch-up policy](https://triggers.smithers.sh/guides/choose-a-policy/)
104
+ - [Ingest a verified webhook](https://triggers.smithers.sh/guides/ingest-a-webhook/)
105
+ - [Troubleshooting](https://triggers.smithers.sh/troubleshooting/), which lists
106
+ every failure code, what causes it, and what to change.
107
+
108
+ ## License
109
+
110
+ MIT. See [LICENSE](./LICENSE).
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Pure catch-up policy decisions.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+ import * as Effect from "effect/Effect";
7
+ import type { Cron } from "./Cron.ts";
8
+ import type { CatchUp as Policy } from "./Schedule.ts";
9
+ import { TriggerError } from "./TriggerError.ts";
10
+ /**
11
+ * The occurrences a trigger owes since it last fired, bounded by
12
+ * `maxCatchUp` and by the policy named in `Schedule.CatchUp`: `none` owes
13
+ * nothing, `one` owes only the most recent, and `all` owes every missed tick.
14
+ *
15
+ * Every policy answers to `maxCatchUp`, `one` included: a bound of zero says
16
+ * no occurrence may be caught up, so a missed occurrence under `one` is
17
+ * `catch_up_bound_exceeded` exactly as three missed occurrences under `all`
18
+ * are. The bound is validated before any policy branch, so an unusable bound
19
+ * is refused even where the policy owes nothing.
20
+ *
21
+ * @category computation
22
+ * @since 0.1.0
23
+ */
24
+ export declare const occurrences: (policy: Policy, maxCatchUp: number, lastFiredAt: Date | undefined, now: Date, cron: Cron) => Effect.Effect<ReadonlyArray<Date>, TriggerError>;
25
+ //# sourceMappingURL=CatchUp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CatchUp.d.ts","sourceRoot":"","sources":["../../src/CatchUp.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAErC,OAAO,KAAK,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,WAAW,GACtB,QAAQ,MAAM,EACd,YAAY,MAAM,EAClB,aAAa,IAAI,GAAG,SAAS,EAC7B,KAAK,IAAI,EACT,MAAM,IAAI,KACT,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,YAAY,CAwCjD,CAAA"}
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var CatchUp_exports = {};
30
+ __export(CatchUp_exports, {
31
+ occurrences: () => occurrences
32
+ });
33
+ module.exports = __toCommonJS(CatchUp_exports);
34
+ var Effect = __toESM(require("effect/Effect"), 1);
35
+ var CronSchedule = __toESM(require("./Cron.js"), 1);
36
+ var import_TriggerError = require("./TriggerError.js");
37
+ const occurrences = (policy, maxCatchUp, lastFiredAt, now, cron) => {
38
+ if (!Number.isSafeInteger(maxCatchUp) || maxCatchUp < 0) {
39
+ return Effect.fail(
40
+ new import_TriggerError.TriggerError({
41
+ code: "catch_up_bound_exceeded",
42
+ message: `maxCatchUp must be a non-negative safe integer, received ${maxCatchUp}`,
43
+ path: "maxCatchUp"
44
+ })
45
+ );
46
+ }
47
+ if (lastFiredAt === void 0) return Effect.succeed([]);
48
+ if (policy === "none") return Effect.succeed([]);
49
+ if (policy === "one") {
50
+ return CronSchedule.previousAtOrBefore(cron, now).pipe(
51
+ Effect.flatMap(
52
+ (latest) => latest.getTime() <= lastFiredAt.getTime() ? Effect.succeed([]) : maxCatchUp < 1 ? Effect.fail(
53
+ new import_TriggerError.TriggerError({
54
+ code: "catch_up_bound_exceeded",
55
+ message: `missed 1 occurrence; maxCatchUp is ${maxCatchUp}`
56
+ })
57
+ ) : Effect.succeed([latest])
58
+ )
59
+ );
60
+ }
61
+ return CronSchedule.occurrencesBetween(cron, lastFiredAt, now, maxCatchUp + 1).pipe(
62
+ Effect.flatMap(
63
+ (missed) => missed.length > maxCatchUp ? Effect.fail(
64
+ new import_TriggerError.TriggerError({
65
+ code: "catch_up_bound_exceeded",
66
+ message: `missed ${missed.length} occurrences; maxCatchUp is ${maxCatchUp}`
67
+ })
68
+ ) : Effect.succeed(missed)
69
+ )
70
+ );
71
+ };
72
+ //# sourceMappingURL=CatchUp.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/CatchUp.ts"],
4
+ "sourcesContent": ["/**\n * Pure catch-up policy decisions.\n *\n * @since 0.1.0\n */\n\nimport * as Effect from \"effect/Effect\"\nimport type { Cron } from \"./Cron.ts\"\nimport * as CronSchedule from \"./Cron.ts\"\nimport type { CatchUp as Policy } from \"./Schedule.ts\"\nimport { TriggerError } from \"./TriggerError.ts\"\n\n/**\n * The occurrences a trigger owes since it last fired, bounded by\n * `maxCatchUp` and by the policy named in `Schedule.CatchUp`: `none` owes\n * nothing, `one` owes only the most recent, and `all` owes every missed tick.\n *\n * Every policy answers to `maxCatchUp`, `one` included: a bound of zero says\n * no occurrence may be caught up, so a missed occurrence under `one` is\n * `catch_up_bound_exceeded` exactly as three missed occurrences under `all`\n * are. The bound is validated before any policy branch, so an unusable bound\n * is refused even where the policy owes nothing.\n *\n * @category computation\n * @since 0.1.0\n */\nexport const occurrences = (\n policy: Policy,\n maxCatchUp: number,\n lastFiredAt: Date | undefined,\n now: Date,\n cron: Cron\n): Effect.Effect<ReadonlyArray<Date>, TriggerError> => {\n if (!Number.isSafeInteger(maxCatchUp) || maxCatchUp < 0) {\n return Effect.fail(\n new TriggerError({\n code: \"catch_up_bound_exceeded\",\n message: `maxCatchUp must be a non-negative safe integer, received ${maxCatchUp}`,\n path: \"maxCatchUp\"\n })\n )\n }\n if (lastFiredAt === undefined) return Effect.succeed([])\n if (policy === \"none\") return Effect.succeed([])\n if (policy === \"one\") {\n return CronSchedule.previousAtOrBefore(cron, now).pipe(\n Effect.flatMap((latest) =>\n latest.getTime() <= lastFiredAt.getTime()\n ? Effect.succeed<ReadonlyArray<Date>>([])\n : maxCatchUp < 1\n ? Effect.fail(\n new TriggerError({\n code: \"catch_up_bound_exceeded\",\n message: `missed 1 occurrence; maxCatchUp is ${maxCatchUp}`\n })\n )\n : Effect.succeed<ReadonlyArray<Date>>([latest])\n )\n )\n }\n return CronSchedule.occurrencesBetween(cron, lastFiredAt, now, maxCatchUp + 1).pipe(\n Effect.flatMap((missed) =>\n missed.length > maxCatchUp\n ? Effect.fail(\n new TriggerError({\n code: \"catch_up_bound_exceeded\",\n message: `missed ${missed.length} occurrences; maxCatchUp is ${maxCatchUp}`\n })\n )\n : Effect.succeed(missed)\n )\n )\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,aAAwB;AAExB,mBAA8B;AAE9B,0BAA6B;AAgBtB,MAAM,cAAc,CACzB,QACA,YACA,aACA,KACA,SACqD;AACrD,MAAI,CAAC,OAAO,cAAc,UAAU,KAAK,aAAa,GAAG;AACvD,WAAO,OAAO;AAAA,MACZ,IAAI,iCAAa;AAAA,QACf,MAAM;AAAA,QACN,SAAS,4DAA4D,UAAU;AAAA,QAC/E,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAAA,EACF;AACA,MAAI,gBAAgB,OAAW,QAAO,OAAO,QAAQ,CAAC,CAAC;AACvD,MAAI,WAAW,OAAQ,QAAO,OAAO,QAAQ,CAAC,CAAC;AAC/C,MAAI,WAAW,OAAO;AACpB,WAAO,aAAa,mBAAmB,MAAM,GAAG,EAAE;AAAA,MAChD,OAAO;AAAA,QAAQ,CAAC,WACd,OAAO,QAAQ,KAAK,YAAY,QAAQ,IACpC,OAAO,QAA6B,CAAC,CAAC,IACtC,aAAa,IACb,OAAO;AAAA,UACP,IAAI,iCAAa;AAAA,YACf,MAAM;AAAA,YACN,SAAS,sCAAsC,UAAU;AAAA,UAC3D,CAAC;AAAA,QACH,IACE,OAAO,QAA6B,CAAC,MAAM,CAAC;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AACA,SAAO,aAAa,mBAAmB,MAAM,aAAa,KAAK,aAAa,CAAC,EAAE;AAAA,IAC7E,OAAO;AAAA,MAAQ,CAAC,WACd,OAAO,SAAS,aACZ,OAAO;AAAA,QACP,IAAI,iCAAa;AAAA,UACf,MAAM;AAAA,UACN,SAAS,UAAU,OAAO,MAAM,+BAA+B,UAAU;AAAA,QAC3E,CAAC;AAAA,MACH,IACE,OAAO,QAAQ,MAAM;AAAA,IAC3B;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Authority-free channel declarations.
3
+ *
4
+ * A channel authenticates opaque transport input, maps verified payloads to
5
+ * control-plane starts or signals, and may project run state outbound. It
6
+ * carries no execution authority: the target flow's envelope and permission
7
+ * checks apply unchanged.
8
+ *
9
+ * @see packages/smithers/agent/triggers/docs/api.md
10
+ * @since 0.1.0
11
+ */
12
+ import type { IdempotencyKey } from "@smthrs/control/ControlSchema";
13
+ import type { CredentialRef } from "@smthrs/control/Credential";
14
+ import type * as Effect from "effect/Effect";
15
+ import type * as Redacted from "effect/Redacted";
16
+ import type * as Schema from "effect/Schema";
17
+ import type { TriggerError } from "./TriggerError.ts";
18
+ /**
19
+ * Opaque inbound transport data. Verification must inspect this value before
20
+ * any payload decoding occurs.
21
+ *
22
+ * @category models
23
+ * @since 0.1.0
24
+ */
25
+ export interface RawInbound {
26
+ readonly body: Uint8Array;
27
+ readonly headers: Readonly<Record<string, string | undefined>>;
28
+ readonly idempotencyKey: IdempotencyKey;
29
+ }
30
+ /**
31
+ * A verified request that starts a flow.
32
+ *
33
+ * @category models
34
+ * @since 0.1.0
35
+ */
36
+ export interface Start {
37
+ readonly start: {
38
+ readonly flowId: string;
39
+ readonly input: unknown;
40
+ };
41
+ }
42
+ /**
43
+ * A verified request that resolves a waiting flow step.
44
+ *
45
+ * @category models
46
+ * @since 0.1.0
47
+ */
48
+ export interface Signal {
49
+ readonly signal: {
50
+ readonly runId: string;
51
+ readonly stepId: string;
52
+ readonly value: unknown;
53
+ };
54
+ }
55
+ /**
56
+ * The only operations an inbound channel may request.
57
+ *
58
+ * @category models
59
+ * @since 0.1.0
60
+ */
61
+ export type Inbound = Start | Signal;
62
+ /**
63
+ * Authenticates raw request bytes and headers against a credential reference.
64
+ *
65
+ * The credential arrives as a redacted reference rather than a secret, and it
66
+ * arrives per request rather than at declaration time: a verifier resolves it
67
+ * through the host's resolver when it needs the bytes. Dropping this parameter
68
+ * left a verifier no way to reach a secret except by closing over it in plain
69
+ * memory at declaration time, which is the shape the reference exists to
70
+ * prevent. `packages/smithers/control/src/WebhookChannel.ts` states the same contract
71
+ * for the control-plane side of the boundary.
72
+ *
73
+ * @category models
74
+ * @since 0.1.0
75
+ */
76
+ export type Verify = (raw: RawInbound, credential: Redacted.Redacted<CredentialRef>) => Effect.Effect<void, TriggerError>;
77
+ /**
78
+ * An authority-free bidirectional channel declaration.
79
+ *
80
+ * `inbound` only describes a control-plane start or signal. It cannot supply
81
+ * capabilities, grants, or an alternate execution envelope.
82
+ *
83
+ * @category declarations
84
+ * @since 0.1.0
85
+ */
86
+ export interface Channel<Payload, Run = never, Outbound = never> {
87
+ readonly name: string;
88
+ readonly verify: Verify;
89
+ readonly inbound: (payload: Payload) => Inbound;
90
+ readonly outbound?: ((run: Run) => Outbound) | undefined;
91
+ }
92
+ /**
93
+ * Configuration for a schema-declared channel.
94
+ *
95
+ * @category models
96
+ * @since 0.1.0
97
+ */
98
+ export interface Config<Payload, Run = never, Outbound = never> extends Channel<Payload, Run, Outbound> {
99
+ readonly schema: Schema.Schema<Payload>;
100
+ }
101
+ /**
102
+ * Declares a channel without adding authority or execution behavior.
103
+ *
104
+ * @category constructors
105
+ * @since 0.1.0
106
+ */
107
+ export declare const make: <Payload, Run = never, Outbound = never>(config: Channel<Payload, Run, Outbound>) => Channel<Payload, Run, Outbound>;
108
+ //# sourceMappingURL=Channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Channel.d.ts","sourceRoot":"","sources":["../../src/Channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC/D,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAChD,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;IAC9D,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAA;CACxC;AAED;;;;;GAKG;AACH,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;KACxB,CAAA;CACF;AAED;;;;;GAKG;AACH,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,MAAM,EAAE;QACf,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;QACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;KACxB,CAAA;CACF;AAED;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,MAAM,CAAA;AAEpC;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,MAAM,GAAG,CACnB,GAAG,EAAE,UAAU,EACf,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,KACzC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;AAEtC;;;;;;;;GAQG;AACH,MAAM,WAAW,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,KAAK,EAAE,QAAQ,GAAG,KAAK;IAC7D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAA;IAC/C,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,QAAQ,CAAC,GAAG,SAAS,CAAA;CACzD;AAED;;;;;GAKG;AACH,MAAM,WAAW,MAAM,CAAC,OAAO,EAAE,GAAG,GAAG,KAAK,EAAE,QAAQ,GAAG,KAAK,CAAE,SAAQ,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,QAAQ,CAAC;IACrG,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;CACxC;AAED;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,OAAO,EAAE,GAAG,GAAG,KAAK,EAAE,QAAQ,GAAG,KAAK,EACzD,QAAQ,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,QAAQ,CAAC,KACtC,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,QAAQ,CAAW,CAAA"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var Channel_exports = {};
20
+ __export(Channel_exports, {
21
+ make: () => make
22
+ });
23
+ module.exports = __toCommonJS(Channel_exports);
24
+ const make = (config) => config;
25
+ //# sourceMappingURL=Channel.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/Channel.ts"],
4
+ "sourcesContent": ["/**\n * Authority-free channel declarations.\n *\n * A channel authenticates opaque transport input, maps verified payloads to\n * control-plane starts or signals, and may project run state outbound. It\n * carries no execution authority: the target flow's envelope and permission\n * checks apply unchanged.\n *\n * @see packages/smithers/agent/triggers/docs/api.md\n * @since 0.1.0\n */\n\nimport type { IdempotencyKey } from \"@smthrs/control/ControlSchema\"\nimport type { CredentialRef } from \"@smthrs/control/Credential\"\nimport type * as Effect from \"effect/Effect\"\nimport type * as Redacted from \"effect/Redacted\"\nimport type * as Schema from \"effect/Schema\"\nimport type { TriggerError } from \"./TriggerError.ts\"\n\n/**\n * Opaque inbound transport data. Verification must inspect this value before\n * any payload decoding occurs.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface RawInbound {\n readonly body: Uint8Array\n readonly headers: Readonly<Record<string, string | undefined>>\n readonly idempotencyKey: IdempotencyKey\n}\n\n/**\n * A verified request that starts a flow.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Start {\n readonly start: {\n readonly flowId: string\n readonly input: unknown\n }\n}\n\n/**\n * A verified request that resolves a waiting flow step.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Signal {\n readonly signal: {\n readonly runId: string\n readonly stepId: string\n readonly value: unknown\n }\n}\n\n/**\n * The only operations an inbound channel may request.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Inbound = Start | Signal\n\n/**\n * Authenticates raw request bytes and headers against a credential reference.\n *\n * The credential arrives as a redacted reference rather than a secret, and it\n * arrives per request rather than at declaration time: a verifier resolves it\n * through the host's resolver when it needs the bytes. Dropping this parameter\n * left a verifier no way to reach a secret except by closing over it in plain\n * memory at declaration time, which is the shape the reference exists to\n * prevent. `packages/smithers/control/src/WebhookChannel.ts` states the same contract\n * for the control-plane side of the boundary.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Verify = (\n raw: RawInbound,\n credential: Redacted.Redacted<CredentialRef>\n) => Effect.Effect<void, TriggerError>\n\n/**\n * An authority-free bidirectional channel declaration.\n *\n * `inbound` only describes a control-plane start or signal. It cannot supply\n * capabilities, grants, or an alternate execution envelope.\n *\n * @category declarations\n * @since 0.1.0\n */\nexport interface Channel<Payload, Run = never, Outbound = never> {\n readonly name: string\n readonly verify: Verify\n readonly inbound: (payload: Payload) => Inbound\n readonly outbound?: ((run: Run) => Outbound) | undefined\n}\n\n/**\n * Configuration for a schema-declared channel.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Config<Payload, Run = never, Outbound = never> extends Channel<Payload, Run, Outbound> {\n readonly schema: Schema.Schema<Payload>\n}\n\n/**\n * Declares a channel without adding authority or execution behavior.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = <Payload, Run = never, Outbound = never>(\n config: Channel<Payload, Run, Outbound>\n): Channel<Payload, Run, Outbound> => config\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAsHO,MAAM,OAAO,CAClB,WACoC;",
6
+ "names": []
7
+ }