@pepys/kit 0.0.0-stage → 0.0.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/README.md +63 -2
- package/package.json +34 -4
- package/src/constants.ts +1 -0
- package/src/consumers/define-consumer.ts +20 -0
- package/src/consumers/types.ts +48 -0
- package/src/delivery-checks/build-delivery-proof.ts +12 -0
- package/src/delivery-checks/is-delivery-authentic.ts +49 -0
- package/src/deployments/check-deployment.ts +444 -0
- package/src/deployments/define-deployment.ts +7 -0
- package/src/deployments/define-plugin.ts +56 -0
- package/src/deployments/list-receive-routes.ts +27 -0
- package/src/deployments/types.ts +110 -0
- package/src/entity-ids/build-entity-id.ts +16 -0
- package/src/entity-ids/entity-id-failure.ts +6 -0
- package/src/entity-ids/entity-id-parts.ts +5 -0
- package/src/entity-ids/fact-kinds.ts +3 -0
- package/src/entity-ids/find-entity-id-failure.ts +25 -0
- package/src/entity-ids/parse-entity-id.ts +27 -0
- package/src/fact-envelope-cases.json +464 -0
- package/src/facts/build-enrichment-fact-id.ts +15 -0
- package/src/facts/build-event-fact-id.ts +11 -0
- package/src/facts/build-fact-hash.ts +6 -0
- package/src/facts/build-manual-fact-id.ts +7 -0
- package/src/facts/build-observation-fact-id.ts +15 -0
- package/src/facts/build-webhook-fact-id.ts +11 -0
- package/src/facts/parse-fact-envelope.ts +44 -0
- package/src/facts/parse-stream-name.ts +33 -0
- package/src/generators/build-payload-shape.ts +75 -0
- package/src/generators/collect-view-kinds.ts +8 -0
- package/src/generators/format-column-type.ts +16 -0
- package/src/generators/format-entity-ids.ts +46 -0
- package/src/generators/format-fact-reader.ts +194 -0
- package/src/generators/format-literal.ts +13 -0
- package/src/generators/format-model-contract.ts +23 -0
- package/src/generators/format-operations.ts +109 -0
- package/src/generators/format-payload-shape.ts +181 -0
- package/src/generators/format-property-access.ts +6 -0
- package/src/generators/format-property-name.ts +6 -0
- package/src/generators/format-row-readers.ts +116 -0
- package/src/generators/generate-boundary-glue.ts +60 -0
- package/src/generators/generate-sqlite-schema.ts +78 -0
- package/src/generators/is-identifier.ts +5 -0
- package/src/generators/quote-sql-identifier.ts +3 -0
- package/src/generators/to-entity-id-type-name.ts +6 -0
- package/src/generators/to-pascal-case.ts +7 -0
- package/src/generators.ts +4 -0
- package/src/ids/types.ts +25 -0
- package/src/index.ts +36 -0
- package/src/link-types/define-link-type.ts +9 -0
- package/src/link-types/types.ts +15 -0
- package/src/links/find-link-problem.ts +53 -0
- package/src/links/link-check-cases.json +274 -0
- package/src/links/types.ts +22 -0
- package/src/queries/define-query.ts +14 -0
- package/src/queries/types.ts +56 -0
- package/src/send/create-fact-batch-sender.ts +143 -0
- package/src/send/types.ts +25 -0
- package/src/send.ts +2 -0
- package/src/sources/define-fact-family.ts +10 -0
- package/src/sources/define-source.ts +7 -0
- package/src/sources/read-fact-batch.ts +114 -0
- package/src/sources/receive-fact-batch.ts +11 -0
- package/src/sources/types.ts +154 -0
- package/src/store-reader/store-reader.ts +76 -0
- package/src/tables/define-table.ts +13 -0
- package/src/tables/table-row.ts +20 -0
- package/src/types.ts +47 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cases": [
|
|
3
|
+
{
|
|
4
|
+
"title": "accepts a webhook fact ID",
|
|
5
|
+
"envelope": {
|
|
6
|
+
"id": "webhook:github:d-1",
|
|
7
|
+
"type": "github.pull_request.opened",
|
|
8
|
+
"v": 1,
|
|
9
|
+
"api": "github/2022-11-28",
|
|
10
|
+
"entity": "github:pull_request:77",
|
|
11
|
+
"at": "2026-10-02T10:30:00Z",
|
|
12
|
+
"payload": {}
|
|
13
|
+
},
|
|
14
|
+
"isValid": true
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"title": "accepts an observation fact ID",
|
|
18
|
+
"envelope": {
|
|
19
|
+
"id": "observation:0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a",
|
|
20
|
+
"type": "github.pull_request.opened",
|
|
21
|
+
"v": 1,
|
|
22
|
+
"api": "github/2022-11-28",
|
|
23
|
+
"entity": "github:pull_request:77",
|
|
24
|
+
"at": "2026-10-02T10:30:00Z",
|
|
25
|
+
"payload": {}
|
|
26
|
+
},
|
|
27
|
+
"isValid": true
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"title": "accepts an enrichment fact ID",
|
|
31
|
+
"envelope": {
|
|
32
|
+
"id": "enrichment:b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1",
|
|
33
|
+
"type": "github.pull_request.opened",
|
|
34
|
+
"v": 1,
|
|
35
|
+
"api": "github/2022-11-28",
|
|
36
|
+
"entity": "github:pull_request:77",
|
|
37
|
+
"at": "2026-10-02T10:30:00Z",
|
|
38
|
+
"payload": {}
|
|
39
|
+
},
|
|
40
|
+
"isValid": true
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"title": "accepts a manual fact ID",
|
|
44
|
+
"envelope": {
|
|
45
|
+
"id": "manual:c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2",
|
|
46
|
+
"type": "github.pull_request.opened",
|
|
47
|
+
"v": 1,
|
|
48
|
+
"api": "github/2022-11-28",
|
|
49
|
+
"entity": "github:pull_request:77",
|
|
50
|
+
"at": "2026-10-02T10:30:00Z",
|
|
51
|
+
"payload": {}
|
|
52
|
+
},
|
|
53
|
+
"isValid": true
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"title": "accepts an event fact ID",
|
|
57
|
+
"envelope": {
|
|
58
|
+
"id": "event:boxxy:evt-0001",
|
|
59
|
+
"type": "boxxy.attempt.started",
|
|
60
|
+
"v": 1,
|
|
61
|
+
"api": "boxxy/1",
|
|
62
|
+
"entity": "boxxy:item:0193a1b2-0000-7000-8000-000000000001",
|
|
63
|
+
"at": "2026-10-02T10:30:00Z",
|
|
64
|
+
"payload": {}
|
|
65
|
+
},
|
|
66
|
+
"isValid": true
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"title": "accepts an event fact ID whose event ID holds a colon and a dot",
|
|
70
|
+
"envelope": {
|
|
71
|
+
"id": "event:telemetry:claude_code.api_request:req_01",
|
|
72
|
+
"type": "telemetry.claude_code.api_request",
|
|
73
|
+
"v": 1,
|
|
74
|
+
"api": "otlp-logs/1",
|
|
75
|
+
"entity": "telemetry:session:s-9",
|
|
76
|
+
"at": "2026-10-02T10:30:00Z",
|
|
77
|
+
"payload": {}
|
|
78
|
+
},
|
|
79
|
+
"isValid": true
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"title": "accepts an event fact ID whose event ID is a hash key",
|
|
83
|
+
"envelope": {
|
|
84
|
+
"id": "event:telemetry:codex.sse_event:h-5e1c9a07",
|
|
85
|
+
"type": "telemetry.claude_code.api_request",
|
|
86
|
+
"v": 1,
|
|
87
|
+
"api": "otlp-logs/1",
|
|
88
|
+
"entity": "telemetry:session:s-9",
|
|
89
|
+
"at": "2026-10-02T10:30:00Z",
|
|
90
|
+
"payload": {}
|
|
91
|
+
},
|
|
92
|
+
"isValid": true
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"title": "accepts an actor and a cursor",
|
|
96
|
+
"envelope": {
|
|
97
|
+
"id": "webhook:github:d-1",
|
|
98
|
+
"type": "github.pull_request.opened",
|
|
99
|
+
"v": 1,
|
|
100
|
+
"api": "github/2022-11-28",
|
|
101
|
+
"entity": "github:pull_request:77",
|
|
102
|
+
"at": "2026-10-02T10:30:00Z",
|
|
103
|
+
"payload": {},
|
|
104
|
+
"actor": "github:user:583231",
|
|
105
|
+
"cursor": "page-7"
|
|
106
|
+
},
|
|
107
|
+
"isValid": true
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"title": "accepts fractional seconds",
|
|
111
|
+
"envelope": {
|
|
112
|
+
"id": "webhook:github:d-1",
|
|
113
|
+
"type": "github.pull_request.opened",
|
|
114
|
+
"v": 1,
|
|
115
|
+
"api": "github/2022-11-28",
|
|
116
|
+
"entity": "github:pull_request:77",
|
|
117
|
+
"at": "2026-10-02T10:30:00.123456Z",
|
|
118
|
+
"payload": {}
|
|
119
|
+
},
|
|
120
|
+
"isValid": true
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"title": "accepts a leap day",
|
|
124
|
+
"envelope": {
|
|
125
|
+
"id": "webhook:github:d-1",
|
|
126
|
+
"type": "github.pull_request.opened",
|
|
127
|
+
"v": 1,
|
|
128
|
+
"api": "github/2022-11-28",
|
|
129
|
+
"entity": "github:pull_request:77",
|
|
130
|
+
"at": "2028-02-29T10:30:00Z",
|
|
131
|
+
"payload": {}
|
|
132
|
+
},
|
|
133
|
+
"isValid": true
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"title": "refuses an ID of no fact kind",
|
|
137
|
+
"envelope": {
|
|
138
|
+
"id": "not-a-fact-id",
|
|
139
|
+
"type": "github.pull_request.opened",
|
|
140
|
+
"v": 1,
|
|
141
|
+
"api": "github/2022-11-28",
|
|
142
|
+
"entity": "github:pull_request:77",
|
|
143
|
+
"at": "2026-10-02T10:30:00Z",
|
|
144
|
+
"payload": {}
|
|
145
|
+
},
|
|
146
|
+
"isValid": false
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"title": "refuses a manual fact ID with a short hash",
|
|
150
|
+
"envelope": {
|
|
151
|
+
"id": "manual:abc",
|
|
152
|
+
"type": "github.pull_request.opened",
|
|
153
|
+
"v": 1,
|
|
154
|
+
"api": "github/2022-11-28",
|
|
155
|
+
"entity": "github:pull_request:77",
|
|
156
|
+
"at": "2026-10-02T10:30:00Z",
|
|
157
|
+
"payload": {}
|
|
158
|
+
},
|
|
159
|
+
"isValid": false
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"title": "refuses a webhook fact ID with an upper-case source",
|
|
163
|
+
"envelope": {
|
|
164
|
+
"id": "webhook:GitHub:d-1",
|
|
165
|
+
"type": "github.pull_request.opened",
|
|
166
|
+
"v": 1,
|
|
167
|
+
"api": "github/2022-11-28",
|
|
168
|
+
"entity": "github:pull_request:77",
|
|
169
|
+
"at": "2026-10-02T10:30:00Z",
|
|
170
|
+
"payload": {}
|
|
171
|
+
},
|
|
172
|
+
"isValid": false
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"title": "refuses a webhook fact ID with a line feed",
|
|
176
|
+
"envelope": {
|
|
177
|
+
"id": "webhook:github:d\n1",
|
|
178
|
+
"type": "github.pull_request.opened",
|
|
179
|
+
"v": 1,
|
|
180
|
+
"api": "github/2022-11-28",
|
|
181
|
+
"entity": "github:pull_request:77",
|
|
182
|
+
"at": "2026-10-02T10:30:00Z",
|
|
183
|
+
"payload": {}
|
|
184
|
+
},
|
|
185
|
+
"isValid": false
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"title": "refuses a webhook fact ID with a carriage return",
|
|
189
|
+
"envelope": {
|
|
190
|
+
"id": "webhook:github:d\r1",
|
|
191
|
+
"type": "github.pull_request.opened",
|
|
192
|
+
"v": 1,
|
|
193
|
+
"api": "github/2022-11-28",
|
|
194
|
+
"entity": "github:pull_request:77",
|
|
195
|
+
"at": "2026-10-02T10:30:00Z",
|
|
196
|
+
"payload": {}
|
|
197
|
+
},
|
|
198
|
+
"isValid": false
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"title": "refuses a webhook fact ID with a line separator",
|
|
202
|
+
"envelope": {
|
|
203
|
+
"id": "webhook:github:d\u20281",
|
|
204
|
+
"type": "github.pull_request.opened",
|
|
205
|
+
"v": 1,
|
|
206
|
+
"api": "github/2022-11-28",
|
|
207
|
+
"entity": "github:pull_request:77",
|
|
208
|
+
"at": "2026-10-02T10:30:00Z",
|
|
209
|
+
"payload": {}
|
|
210
|
+
},
|
|
211
|
+
"isValid": false
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"title": "refuses a webhook fact ID with a paragraph separator",
|
|
215
|
+
"envelope": {
|
|
216
|
+
"id": "webhook:github:d\u20291",
|
|
217
|
+
"type": "github.pull_request.opened",
|
|
218
|
+
"v": 1,
|
|
219
|
+
"api": "github/2022-11-28",
|
|
220
|
+
"entity": "github:pull_request:77",
|
|
221
|
+
"at": "2026-10-02T10:30:00Z",
|
|
222
|
+
"payload": {}
|
|
223
|
+
},
|
|
224
|
+
"isValid": false
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"title": "refuses an event fact ID with no event ID",
|
|
228
|
+
"envelope": {
|
|
229
|
+
"id": "event:boxxy:",
|
|
230
|
+
"type": "boxxy.attempt.started",
|
|
231
|
+
"v": 1,
|
|
232
|
+
"api": "boxxy/1",
|
|
233
|
+
"entity": "boxxy:item:0193a1b2-0000-7000-8000-000000000001",
|
|
234
|
+
"at": "2026-10-02T10:30:00Z",
|
|
235
|
+
"payload": {}
|
|
236
|
+
},
|
|
237
|
+
"isValid": false
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"title": "refuses an event fact ID with an upper-case source",
|
|
241
|
+
"envelope": {
|
|
242
|
+
"id": "event:Boxxy:evt-0001",
|
|
243
|
+
"type": "boxxy.attempt.started",
|
|
244
|
+
"v": 1,
|
|
245
|
+
"api": "boxxy/1",
|
|
246
|
+
"entity": "boxxy:item:0193a1b2-0000-7000-8000-000000000001",
|
|
247
|
+
"at": "2026-10-02T10:30:00Z",
|
|
248
|
+
"payload": {}
|
|
249
|
+
},
|
|
250
|
+
"isValid": false
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"title": "refuses an empty type",
|
|
254
|
+
"envelope": {
|
|
255
|
+
"id": "webhook:github:d-1",
|
|
256
|
+
"type": "",
|
|
257
|
+
"v": 1,
|
|
258
|
+
"api": "github/2022-11-28",
|
|
259
|
+
"entity": "github:pull_request:77",
|
|
260
|
+
"at": "2026-10-02T10:30:00Z",
|
|
261
|
+
"payload": {}
|
|
262
|
+
},
|
|
263
|
+
"isValid": false
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"title": "refuses an envelope version other than 1",
|
|
267
|
+
"envelope": {
|
|
268
|
+
"id": "webhook:github:d-1",
|
|
269
|
+
"type": "github.pull_request.opened",
|
|
270
|
+
"v": 2,
|
|
271
|
+
"api": "github/2022-11-28",
|
|
272
|
+
"entity": "github:pull_request:77",
|
|
273
|
+
"at": "2026-10-02T10:30:00Z",
|
|
274
|
+
"payload": {}
|
|
275
|
+
},
|
|
276
|
+
"isValid": false
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"title": "refuses an api that is not text",
|
|
280
|
+
"envelope": {
|
|
281
|
+
"id": "webhook:github:d-1",
|
|
282
|
+
"type": "github.pull_request.opened",
|
|
283
|
+
"v": 1,
|
|
284
|
+
"api": 7,
|
|
285
|
+
"entity": "github:pull_request:77",
|
|
286
|
+
"at": "2026-10-02T10:30:00Z",
|
|
287
|
+
"payload": {}
|
|
288
|
+
},
|
|
289
|
+
"isValid": false
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"title": "refuses an entity ID with no kind",
|
|
293
|
+
"envelope": {
|
|
294
|
+
"id": "webhook:github:d-1",
|
|
295
|
+
"type": "github.pull_request.opened",
|
|
296
|
+
"v": 1,
|
|
297
|
+
"api": "github/2022-11-28",
|
|
298
|
+
"entity": "github:pull_request",
|
|
299
|
+
"at": "2026-10-02T10:30:00Z",
|
|
300
|
+
"payload": {}
|
|
301
|
+
},
|
|
302
|
+
"isValid": false
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"title": "refuses an entity ID whose source is a fact kind",
|
|
306
|
+
"envelope": {
|
|
307
|
+
"id": "webhook:github:d-1",
|
|
308
|
+
"type": "github.pull_request.opened",
|
|
309
|
+
"v": 1,
|
|
310
|
+
"api": "github/2022-11-28",
|
|
311
|
+
"entity": "manual:pr:7",
|
|
312
|
+
"at": "2026-10-02T10:30:00Z",
|
|
313
|
+
"payload": {}
|
|
314
|
+
},
|
|
315
|
+
"isValid": false
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
"title": "refuses an entity ID whose source is the event fact kind",
|
|
319
|
+
"envelope": {
|
|
320
|
+
"id": "webhook:github:d-1",
|
|
321
|
+
"type": "github.pull_request.opened",
|
|
322
|
+
"v": 1,
|
|
323
|
+
"api": "github/2022-11-28",
|
|
324
|
+
"entity": "event:x:y",
|
|
325
|
+
"at": "2026-10-02T10:30:00Z",
|
|
326
|
+
"payload": {}
|
|
327
|
+
},
|
|
328
|
+
"isValid": false
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
"title": "refuses an actor that is an event fact ID",
|
|
332
|
+
"envelope": {
|
|
333
|
+
"id": "webhook:github:d-1",
|
|
334
|
+
"type": "github.pull_request.opened",
|
|
335
|
+
"v": 1,
|
|
336
|
+
"api": "github/2022-11-28",
|
|
337
|
+
"entity": "github:pull_request:77",
|
|
338
|
+
"at": "2026-10-02T10:30:00Z",
|
|
339
|
+
"payload": {},
|
|
340
|
+
"actor": "event:boxxy:evt-0001"
|
|
341
|
+
},
|
|
342
|
+
"isValid": false
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"title": "accepts an entity ID whose source only starts with a fact kind",
|
|
346
|
+
"envelope": {
|
|
347
|
+
"id": "webhook:github:d-1",
|
|
348
|
+
"type": "github.pull_request.opened",
|
|
349
|
+
"v": 1,
|
|
350
|
+
"api": "github/2022-11-28",
|
|
351
|
+
"entity": "events:x:y",
|
|
352
|
+
"at": "2026-10-02T10:30:00Z",
|
|
353
|
+
"payload": {}
|
|
354
|
+
},
|
|
355
|
+
"isValid": true
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"title": "refuses an actor that is not an entity ID",
|
|
359
|
+
"envelope": {
|
|
360
|
+
"id": "webhook:github:d-1",
|
|
361
|
+
"type": "github.pull_request.opened",
|
|
362
|
+
"v": 1,
|
|
363
|
+
"api": "github/2022-11-28",
|
|
364
|
+
"entity": "github:pull_request:77",
|
|
365
|
+
"at": "2026-10-02T10:30:00Z",
|
|
366
|
+
"payload": {},
|
|
367
|
+
"actor": "github:7"
|
|
368
|
+
},
|
|
369
|
+
"isValid": false
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
"title": "refuses a time with no seconds",
|
|
373
|
+
"envelope": {
|
|
374
|
+
"id": "webhook:github:d-1",
|
|
375
|
+
"type": "github.pull_request.opened",
|
|
376
|
+
"v": 1,
|
|
377
|
+
"api": "github/2022-11-28",
|
|
378
|
+
"entity": "github:pull_request:77",
|
|
379
|
+
"at": "2026-10-02T10:30Z",
|
|
380
|
+
"payload": {}
|
|
381
|
+
},
|
|
382
|
+
"isValid": false
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
"title": "refuses a time with an offset",
|
|
386
|
+
"envelope": {
|
|
387
|
+
"id": "webhook:github:d-1",
|
|
388
|
+
"type": "github.pull_request.opened",
|
|
389
|
+
"v": 1,
|
|
390
|
+
"api": "github/2022-11-28",
|
|
391
|
+
"entity": "github:pull_request:77",
|
|
392
|
+
"at": "2026-10-02T10:30:00+01:00",
|
|
393
|
+
"payload": {}
|
|
394
|
+
},
|
|
395
|
+
"isValid": false
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"title": "refuses a day the calendar lacks",
|
|
399
|
+
"envelope": {
|
|
400
|
+
"id": "webhook:github:d-1",
|
|
401
|
+
"type": "github.pull_request.opened",
|
|
402
|
+
"v": 1,
|
|
403
|
+
"api": "github/2022-11-28",
|
|
404
|
+
"entity": "github:pull_request:77",
|
|
405
|
+
"at": "2026-02-30T10:30:00Z",
|
|
406
|
+
"payload": {}
|
|
407
|
+
},
|
|
408
|
+
"isValid": false
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
"title": "refuses the hour 24",
|
|
412
|
+
"envelope": {
|
|
413
|
+
"id": "webhook:github:d-1",
|
|
414
|
+
"type": "github.pull_request.opened",
|
|
415
|
+
"v": 1,
|
|
416
|
+
"api": "github/2022-11-28",
|
|
417
|
+
"entity": "github:pull_request:77",
|
|
418
|
+
"at": "2026-10-02T24:00:00Z",
|
|
419
|
+
"payload": {}
|
|
420
|
+
},
|
|
421
|
+
"isValid": false
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
"title": "refuses a cursor that is not text",
|
|
425
|
+
"envelope": {
|
|
426
|
+
"id": "webhook:github:d-1",
|
|
427
|
+
"type": "github.pull_request.opened",
|
|
428
|
+
"v": 1,
|
|
429
|
+
"api": "github/2022-11-28",
|
|
430
|
+
"entity": "github:pull_request:77",
|
|
431
|
+
"at": "2026-10-02T10:30:00Z",
|
|
432
|
+
"payload": {},
|
|
433
|
+
"cursor": 3
|
|
434
|
+
},
|
|
435
|
+
"isValid": false
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
"title": "refuses a field the envelope lacks",
|
|
439
|
+
"envelope": {
|
|
440
|
+
"id": "webhook:github:d-1",
|
|
441
|
+
"type": "github.pull_request.opened",
|
|
442
|
+
"v": 1,
|
|
443
|
+
"api": "github/2022-11-28",
|
|
444
|
+
"entity": "github:pull_request:77",
|
|
445
|
+
"at": "2026-10-02T10:30:00Z",
|
|
446
|
+
"payload": {},
|
|
447
|
+
"extra": 1
|
|
448
|
+
},
|
|
449
|
+
"isValid": false
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
"title": "refuses an envelope without a payload",
|
|
453
|
+
"envelope": {
|
|
454
|
+
"id": "webhook:github:d-1",
|
|
455
|
+
"type": "github.pull_request.opened",
|
|
456
|
+
"v": 1,
|
|
457
|
+
"api": "github/2022-11-28",
|
|
458
|
+
"entity": "github:pull_request:77",
|
|
459
|
+
"at": "2026-10-02T10:30:00Z"
|
|
460
|
+
},
|
|
461
|
+
"isValid": false
|
|
462
|
+
}
|
|
463
|
+
]
|
|
464
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { toTagged } from '@zgeoff/std';
|
|
2
|
+
import type { FactID } from '../ids/types.ts';
|
|
3
|
+
import { buildFactHash } from './build-fact-hash.ts';
|
|
4
|
+
|
|
5
|
+
interface EnrichmentFactIDParts {
|
|
6
|
+
job: string;
|
|
7
|
+
jobVersion: string;
|
|
8
|
+
inputHash: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function buildEnrichmentFactID(parts: EnrichmentFactIDParts): FactID {
|
|
12
|
+
const hash = buildFactHash([parts.job, parts.jobVersion, parts.inputHash]);
|
|
13
|
+
|
|
14
|
+
return toTagged<FactID>(`enrichment:${hash}`);
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { toTagged } from '@zgeoff/std';
|
|
2
|
+
import type { FactID } from '../ids/types.ts';
|
|
3
|
+
|
|
4
|
+
interface EventFactIDParts {
|
|
5
|
+
source: string;
|
|
6
|
+
event: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function buildEventFactID(parts: EventFactIDParts): FactID {
|
|
10
|
+
return toTagged<FactID>(`event:${parts.source}:${parts.event}`);
|
|
11
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export function buildFactHash(fields: readonly string[]): string {
|
|
4
|
+
// A JSON array keeps each field's boundary, which a delimiter-joined string would let a field forge.
|
|
5
|
+
return createHash('sha256').update(JSON.stringify(fields)).digest('hex');
|
|
6
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { toTagged } from '@zgeoff/std';
|
|
2
|
+
import type { ClientID, FactID, RequestID } from '../ids/types.ts';
|
|
3
|
+
import { buildFactHash } from './build-fact-hash.ts';
|
|
4
|
+
|
|
5
|
+
export function buildManualFactID(client: ClientID, request: RequestID): FactID {
|
|
6
|
+
return toTagged<FactID>(`manual:${buildFactHash([client, request])}`);
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { toTagged } from '@zgeoff/std';
|
|
2
|
+
import type { EntityID, FactID } from '../ids/types.ts';
|
|
3
|
+
import { buildFactHash } from './build-fact-hash.ts';
|
|
4
|
+
|
|
5
|
+
interface ObservationFactIDParts {
|
|
6
|
+
source: string;
|
|
7
|
+
entity: EntityID;
|
|
8
|
+
sourceUpdateTime: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function buildObservationFactID(parts: ObservationFactIDParts): FactID {
|
|
12
|
+
const hash = buildFactHash([parts.source, parts.entity, parts.sourceUpdateTime]);
|
|
13
|
+
|
|
14
|
+
return toTagged<FactID>(`observation:${hash}`);
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { toTagged } from '@zgeoff/std';
|
|
2
|
+
import type { FactID } from '../ids/types.ts';
|
|
3
|
+
|
|
4
|
+
interface WebhookFactIDParts {
|
|
5
|
+
source: string;
|
|
6
|
+
deliveryID: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function buildWebhookFactID(parts: WebhookFactIDParts): FactID {
|
|
10
|
+
return toTagged<FactID>(`webhook:${parts.source}:${parts.deliveryID}`);
|
|
11
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { toTagged } from '@zgeoff/std';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { parseEntityID } from '../entity-ids/parse-entity-id.ts';
|
|
4
|
+
import type { EntityID, FactID } from '../ids/types.ts';
|
|
5
|
+
import type { FactEnvelope, Result } from '../types.ts';
|
|
6
|
+
|
|
7
|
+
const factIDPattern = new RegExp(
|
|
8
|
+
['^(?:webhook|event):[a-z][a-z0-9_]*:.+$', '^(?:observation|enrichment|manual):[0-9a-f]{64}$']
|
|
9
|
+
.map((pattern) => `(?:${pattern})`)
|
|
10
|
+
.join('|'),
|
|
11
|
+
'u',
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const factIDSchema = z
|
|
15
|
+
.string()
|
|
16
|
+
.regex(factIDPattern, 'not a fact ID')
|
|
17
|
+
.transform((text) => toTagged<FactID>(text));
|
|
18
|
+
|
|
19
|
+
const entityIDSchema = z
|
|
20
|
+
.string()
|
|
21
|
+
.refine((text) => parseEntityID(text).ok, 'not an entity ID')
|
|
22
|
+
.transform((text) => toTagged<EntityID>(text));
|
|
23
|
+
|
|
24
|
+
const factEnvelopeSchema = z.strictObject({
|
|
25
|
+
id: factIDSchema,
|
|
26
|
+
type: z.string().min(1),
|
|
27
|
+
v: z.literal(1),
|
|
28
|
+
api: z.string().min(1),
|
|
29
|
+
entity: entityIDSchema,
|
|
30
|
+
actor: entityIDSchema.optional(),
|
|
31
|
+
at: z.iso.datetime(),
|
|
32
|
+
cursor: z.string().optional(),
|
|
33
|
+
payload: z.unknown().refine((payload) => payload !== undefined, 'a fact needs a payload'),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export function parseFactEnvelope(input: unknown): Result<FactEnvelope<unknown>, z.ZodError> {
|
|
37
|
+
const parsed = factEnvelopeSchema.safeParse(input);
|
|
38
|
+
|
|
39
|
+
if (!parsed.success) {
|
|
40
|
+
return { ok: false, failure: parsed.error };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { ok: true, value: parsed.data };
|
|
44
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Result } from '../types.ts';
|
|
2
|
+
|
|
3
|
+
const partPattern = /^[a-z][a-z0-9_]*$/u;
|
|
4
|
+
|
|
5
|
+
interface StreamNameParts {
|
|
6
|
+
source: string;
|
|
7
|
+
family: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type StreamNameFailure =
|
|
11
|
+
| { reason: 'malformed' }
|
|
12
|
+
| { reason: 'invalid-source' }
|
|
13
|
+
| { reason: 'invalid-family' };
|
|
14
|
+
|
|
15
|
+
export function parseStreamName(text: string): Result<StreamNameParts, StreamNameFailure> {
|
|
16
|
+
const parts = text.split('.');
|
|
17
|
+
|
|
18
|
+
if (parts.length !== 3 || parts[0] !== 'facts') {
|
|
19
|
+
return { ok: false, failure: { reason: 'malformed' } };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const [, source = '', family = ''] = parts;
|
|
23
|
+
|
|
24
|
+
if (!partPattern.test(source)) {
|
|
25
|
+
return { ok: false, failure: { reason: 'invalid-source' } };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!partPattern.test(family)) {
|
|
29
|
+
return { ok: false, failure: { reason: 'invalid-family' } };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { ok: true, value: { source, family } };
|
|
33
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { P, match } from 'ts-pattern';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
// What the glue makes of a payload schema. The log keeps a payload as the sender sent it, so the
|
|
5
|
+
// shape follows the input side of the schema: a field with a default may be absent.
|
|
6
|
+
export type PayloadShape =
|
|
7
|
+
| { kind: 'string' }
|
|
8
|
+
| { kind: 'number' }
|
|
9
|
+
| { kind: 'boolean' }
|
|
10
|
+
| { kind: 'literal'; values: readonly (string | number | boolean)[] }
|
|
11
|
+
| { kind: 'object'; fields: readonly PayloadField[] }
|
|
12
|
+
| { kind: 'array'; element: PayloadShape }
|
|
13
|
+
| { kind: 'nullable'; inner: PayloadShape }
|
|
14
|
+
| { kind: 'unknown' };
|
|
15
|
+
|
|
16
|
+
export function buildPayloadShape(schema: z.core.$ZodType): PayloadShape {
|
|
17
|
+
// A string format, such as `z.uuid()`, is no ZodString in zod 4, but shares the core class.
|
|
18
|
+
return match(schema)
|
|
19
|
+
.with(P.instanceOf(z.core.$ZodString), () => ({ kind: 'string' as const }))
|
|
20
|
+
.with(P.instanceOf(z.ZodNumber), () => ({ kind: 'number' as const }))
|
|
21
|
+
.with(P.instanceOf(z.ZodBoolean), () => ({ kind: 'boolean' as const }))
|
|
22
|
+
.with(P.instanceOf(z.ZodLiteral), (literal) => buildLiteralShape([...literal.def.values]))
|
|
23
|
+
.with(P.instanceOf(z.ZodEnum), (enumeration) =>
|
|
24
|
+
buildLiteralShape(Object.values(enumeration.def.entries)),
|
|
25
|
+
)
|
|
26
|
+
.with(P.instanceOf(z.ZodObject), (object) => ({
|
|
27
|
+
kind: 'object' as const,
|
|
28
|
+
fields: Object.entries<z.core.$ZodType>(object.shape).map(([name, field]) =>
|
|
29
|
+
buildPayloadField(name, field),
|
|
30
|
+
),
|
|
31
|
+
}))
|
|
32
|
+
.with(P.instanceOf(z.ZodArray), (array) => ({
|
|
33
|
+
kind: 'array' as const,
|
|
34
|
+
element: buildPayloadShape(array.element),
|
|
35
|
+
}))
|
|
36
|
+
.with(P.instanceOf(z.ZodNullable), (nullable) => buildNullableShape(nullable.unwrap()))
|
|
37
|
+
.otherwise(() => ({ kind: 'unknown' as const }));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function buildLiteralShape(values: readonly unknown[]): PayloadShape {
|
|
41
|
+
const literals = values.filter(
|
|
42
|
+
(value) => typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean',
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return literals.length === values.length && literals.length > 0
|
|
46
|
+
? { kind: 'literal', values: literals }
|
|
47
|
+
: { kind: 'unknown' };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface PayloadField {
|
|
51
|
+
name: string;
|
|
52
|
+
shape: PayloadShape;
|
|
53
|
+
isOptional: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildPayloadField(name: string, schema: z.core.$ZodType): PayloadField {
|
|
57
|
+
return match(schema)
|
|
58
|
+
.with(P.instanceOf(z.ZodOptional), (optional) => ({
|
|
59
|
+
name,
|
|
60
|
+
shape: buildPayloadShape(optional.unwrap()),
|
|
61
|
+
isOptional: true,
|
|
62
|
+
}))
|
|
63
|
+
.with(P.instanceOf(z.ZodDefault), (withDefault) => ({
|
|
64
|
+
name,
|
|
65
|
+
shape: buildPayloadShape(withDefault.unwrap()),
|
|
66
|
+
isOptional: true,
|
|
67
|
+
}))
|
|
68
|
+
.otherwise(() => ({ name, shape: buildPayloadShape(schema), isOptional: false }));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildNullableShape(schema: z.core.$ZodType): PayloadShape {
|
|
72
|
+
const inner = buildPayloadShape(schema);
|
|
73
|
+
|
|
74
|
+
return inner.kind === 'unknown' ? inner : { kind: 'nullable', inner };
|
|
75
|
+
}
|