@hearthlog/ledger 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1651 @@
1
+ openapi: 3.1.0
2
+ info:
3
+ title: Event Store Service API
4
+ version: 0.4.0
5
+ license:
6
+ name: Proprietary
7
+ identifier: LicenseRef-Proprietary
8
+ description: |
9
+ Minimal API for managing append-only event logs with entity-level optimistic locking.
10
+ Each event is associated with an entity, and version tracking is maintained per entity,
11
+ allowing concurrent writes to different entities while preventing conflicts on the same entity.
12
+ Bearer tokens are scoped to a single app. App tokens can read and write in that app.
13
+
14
+ Typical client flow:
15
+ 1. List/select a log.
16
+ 2. Replay events after the local head.
17
+ 3. Append events with entity-level version checks (idempotent via `requestId`).
18
+ 4. Handle conflicts by replaying missing log events and retrying.
19
+ 5. Open a gateway-authenticated `ledger.v1` WebSocket for realtime events.
20
+
21
+ Key concepts:
22
+ - Each entity has its own version counter starting at 1
23
+ - New entity chains use no first base, then `1`, `2`, and so on
24
+ - Existing entity chains at version `N` use `N`, `N+1`, and so on
25
+ - Conflicts return the current entity version for client-side resolution
26
+ - Multiple entities can be updated in a single atomic request
27
+ - Ledger validates event envelopes but leaves domain payload rules to clients
28
+ - `ADMIN_TOKEN` can optionally enable `/admin/v1` for operator-only provisioning
29
+ - Unknown request fields are rejected
30
+ - Log forking and request-level log version checks are not supported
31
+ servers:
32
+ - url: /
33
+ description: The same origin that serves this specification.
34
+ security:
35
+ - bearerAuth: []
36
+ paths:
37
+ /health:
38
+ get:
39
+ operationId: healthCheck
40
+ summary: Check database readiness
41
+ security: []
42
+ responses:
43
+ "204":
44
+ description: The service can query its database.
45
+ "500":
46
+ description: The database readiness check failed.
47
+ /session:
48
+ get:
49
+ operationId: getSession
50
+ summary: Resolve the authenticated app and principal
51
+ responses:
52
+ "200":
53
+ description: Stable tenant and credential-principal identity.
54
+ content:
55
+ application/json:
56
+ schema:
57
+ $ref: "#/components/schemas/SessionResponse"
58
+ example:
59
+ appId: 00000000-0000-0000-0000-000000000001
60
+ principalId: 33333333-3333-4333-8333-333333333333
61
+ "401":
62
+ $ref: "#/components/responses/Unauthorized"
63
+ /logs:
64
+ get:
65
+ operationId: listLogs
66
+ summary: List available logs
67
+ description: Returns every log in the caller's app.
68
+ responses:
69
+ "200":
70
+ description: List of logs.
71
+ content:
72
+ application/json:
73
+ schema:
74
+ $ref: "#/components/schemas/LogListResponse"
75
+ examples:
76
+ default:
77
+ value:
78
+ logs:
79
+ - id: 11111111-1111-1111-1111-111111111111
80
+ name: default
81
+ headVersion: 187
82
+ createdAt: "2024-05-01T08:00:00.000Z"
83
+ lastUpdatedAt: "2024-06-02T09:24:55.612Z"
84
+ - id: 22222222-2222-2222-2222-222222222222
85
+ name: finance-simulation
86
+ headVersion: 42
87
+ createdAt: "2024-05-20T12:00:00.000Z"
88
+ lastUpdatedAt: "2024-05-30T19:02:11.004Z"
89
+ "401":
90
+ $ref: "#/components/responses/Unauthorized"
91
+ post:
92
+ operationId: createLog
93
+ summary: Create a new log
94
+ description: Creates an empty log. The service assigns the log ID and generates a name when omitted.
95
+ requestBody:
96
+ required: true
97
+ content:
98
+ application/json:
99
+ schema:
100
+ $ref: "#/components/schemas/CreateLogRequest"
101
+ example:
102
+ name: elections-archive
103
+ responses:
104
+ "201":
105
+ description: Log created.
106
+ content:
107
+ application/json:
108
+ schema:
109
+ $ref: "#/components/schemas/CreateLogResponse"
110
+ examples:
111
+ default:
112
+ value:
113
+ id: 33333333-3333-3333-3333-333333333333
114
+ name: elections-archive
115
+ headVersion: 0
116
+ createdAt: "2024-06-02T10:18:03.447Z"
117
+ "400":
118
+ $ref: "#/components/responses/ValidationError"
119
+ "401":
120
+ $ref: "#/components/responses/Unauthorized"
121
+ "422":
122
+ description: The JSON body does not match the request schema.
123
+ /logs/{logId}:
124
+ parameters:
125
+ - $ref: "#/components/parameters/LogId"
126
+ get:
127
+ operationId: getLog
128
+ summary: Fetch log metadata
129
+ description: Returns current head information for a log in the caller's app.
130
+ responses:
131
+ "200":
132
+ description: Log metadata.
133
+ content:
134
+ application/json:
135
+ schema:
136
+ $ref: "#/components/schemas/LogMetadata"
137
+ examples:
138
+ default:
139
+ value:
140
+ id: 11111111-1111-1111-1111-111111111111
141
+ name: default
142
+ headVersion: 187
143
+ createdAt: "2024-05-01T08:00:00.000Z"
144
+ lastUpdatedAt: "2024-06-02T09:24:55.612Z"
145
+ "401":
146
+ $ref: "#/components/responses/Unauthorized"
147
+ "404":
148
+ $ref: "#/components/responses/NotFound"
149
+ delete:
150
+ operationId: deleteLog
151
+ summary: Delete a log
152
+ description: Deletes the log and its events. A source log with child forks cannot be deleted.
153
+ responses:
154
+ "204":
155
+ description: Log deleted.
156
+ "401":
157
+ $ref: "#/components/responses/Unauthorized"
158
+ "404":
159
+ $ref: "#/components/responses/NotFound"
160
+ "409":
161
+ description: This log has forks that depend on its provenance.
162
+ /logs/{logId}/forks:
163
+ parameters:
164
+ - $ref: "#/components/parameters/LogId"
165
+ post:
166
+ operationId: forkLog
167
+ summary: Fork an accepted prefix into a new log
168
+ description: Creates a preparing child log with the source events through a complete action boundary. Repeating the same requestId returns the same child. The frontend Worker prepares inherited private files before the child is marked ready.
169
+ requestBody:
170
+ required: true
171
+ content:
172
+ application/json:
173
+ schema:
174
+ $ref: "#/components/schemas/ForkLogRequest"
175
+ responses:
176
+ "201":
177
+ description: Fork created or idempotently found.
178
+ content:
179
+ application/json:
180
+ schema:
181
+ $ref: "#/components/schemas/LogSummary"
182
+ "400":
183
+ $ref: "#/components/responses/ValidationError"
184
+ "409":
185
+ description: Source head changed or requestId was reused with different content.
186
+ /logs/{logId}/gallery-photos/{photoId}/files/{fileVersionId}/reference:
187
+ parameters:
188
+ - $ref: "#/components/parameters/LogId"
189
+ - name: photoId
190
+ in: path
191
+ required: true
192
+ schema:
193
+ type: string
194
+ format: uuid
195
+ - name: fileVersionId
196
+ in: path
197
+ required: true
198
+ schema:
199
+ type: string
200
+ format: uuid
201
+ get:
202
+ operationId: getGalleryPhotoReference
203
+ summary: Check whether an accepted Gallery photo version belongs to a log
204
+ description: The private file gateway calls this with its server-side app token. Preparing forks cannot access inherited photo bytes. A missing reference returns present false.
205
+ responses:
206
+ "200":
207
+ description: Exact per-log photo file reference status.
208
+ content:
209
+ application/json:
210
+ schema:
211
+ type: object
212
+ required: [present]
213
+ additionalProperties: false
214
+ properties:
215
+ present:
216
+ type: boolean
217
+ "400":
218
+ $ref: "#/components/responses/ValidationError"
219
+ "401":
220
+ $ref: "#/components/responses/Unauthorized"
221
+ "404":
222
+ $ref: "#/components/responses/NotFound"
223
+ "409":
224
+ description: The fork is still preparing.
225
+ /logs/{logId}/visibility:
226
+ parameters:
227
+ - $ref: "#/components/parameters/LogId"
228
+ post:
229
+ operationId: setLogVisibility
230
+ summary: Hide or show a log in the log tree
231
+ description: Hiding records when a log was hidden and changes nothing else; the log, its events and its forks stay readable and writable. Hiding an already hidden log keeps the first time. Showing clears it.
232
+ requestBody:
233
+ required: true
234
+ content:
235
+ application/json:
236
+ schema:
237
+ $ref: "#/components/schemas/LogVisibilityRequest"
238
+ responses:
239
+ "200":
240
+ description: Updated log metadata.
241
+ content:
242
+ application/json:
243
+ schema:
244
+ $ref: "#/components/schemas/LogSummary"
245
+ "400":
246
+ $ref: "#/components/responses/ValidationError"
247
+ "404":
248
+ description: Log not found.
249
+ /logs/{logId}/finalize-fork:
250
+ parameters:
251
+ - $ref: "#/components/parameters/LogId"
252
+ post:
253
+ operationId: finalizeFork
254
+ summary: Mark a fork ready after file preparation
255
+ description: Internal frontend Worker operation. The public same-origin Ledger gateway blocks this route.
256
+ responses:
257
+ "200":
258
+ description: Ready fork metadata.
259
+ content:
260
+ application/json:
261
+ schema:
262
+ $ref: "#/components/schemas/LogSummary"
263
+ /logs/{logId}/events:
264
+ parameters:
265
+ - $ref: "#/components/parameters/LogId"
266
+ post:
267
+ operationId: appendEvents
268
+ summary: Append events to a log
269
+ parameters:
270
+ - name: X-Ledger-Actor
271
+ in: header
272
+ required: false
273
+ description: |
274
+ Identity of the person on whose behalf a trusted gateway appends. Ledger stores it as
275
+ each accepted event's `actor`. A value that is empty, longer than 320 characters or
276
+ contains control characters is ignored. Browsers never send it: the frontend Worker
277
+ replaces any incoming value with its verified Access identity.
278
+ schema:
279
+ type: string
280
+ maxLength: 320
281
+ description: |
282
+ Appends one or more events to the log with optimistic locking.
283
+ Re-sending the same `requestId` with canonically equivalent content returns the original
284
+ outcome. Reusing it with different content returns `idempotency_mismatch`.
285
+ A request carries at most 512 events, and the accepted events must serialize to at most
286
+ 1,900,000 bytes; larger requests return `400 request_too_large`. Event ids must be unique
287
+ within a request (`400 invalid_event_envelope`) and within the log
288
+ (`409 event_already_accepted`).
289
+ requestBody:
290
+ required: true
291
+ content:
292
+ application/json:
293
+ schema:
294
+ $ref: "#/components/schemas/AppendEventsRequest"
295
+ examples:
296
+ createNewEntity:
297
+ summary: Create a new entity (first event)
298
+ value:
299
+ requestId: b5b2c5b0-64de-4e9f-9c2a-7e69f238d2c1
300
+ events:
301
+ - id: 44444444-4444-4444-4444-444444444444
302
+ type: USER_CREATED
303
+ entityId: user-123
304
+ timestamp: 1717323612903
305
+ payload:
306
+ name: John Doe
307
+ email: john@example.com
308
+ appendToExistingEntity:
309
+ summary: Append to existing entity with version check
310
+ value:
311
+ requestId: a1b2c3d4-5e6f-4788-9190-a1b2c3d4e5f6
312
+ events:
313
+ - id: 55555555-5555-5555-5555-555555555555
314
+ type: USER_UPDATED
315
+ entityId: user-123
316
+ baseEntityVersion: 1
317
+ timestamp: 1717323700000
318
+ payload:
319
+ email: john.doe@example.com
320
+ appendEntityChain:
321
+ summary: Append a sequential chain to one existing entity
322
+ value:
323
+ requestId: 727e32f7-2c5e-48bb-b377-cac25427ac32
324
+ events:
325
+ - type: USER_UPDATED
326
+ entityId: user-123
327
+ baseEntityVersion: 1
328
+ payload:
329
+ email: john.doe@example.com
330
+ - type: USER_VERIFIED
331
+ entityId: user-123
332
+ baseEntityVersion: 2
333
+ payload:
334
+ verified: true
335
+ multiEntityBatch:
336
+ summary: Append events to multiple entities
337
+ value:
338
+ requestId: f1e2d3c4-b5a6-9788-0192-a3b4c5d6e7f8
339
+ events:
340
+ - type: TRANSACTION_ADDED
341
+ entityId: txn-b030
342
+ timestamp: 1717323612903
343
+ payload:
344
+ name: Groceries
345
+ amount: 1249.5
346
+ - type: USER_UPDATED
347
+ entityId: user-456
348
+ baseEntityVersion: 3
349
+ timestamp: 1717323613000
350
+ payload:
351
+ lastActivity: "2024-06-02T09:26:53.000Z"
352
+ responses:
353
+ "200":
354
+ description: Events appended successfully.
355
+ content:
356
+ application/json:
357
+ schema:
358
+ $ref: "#/components/schemas/AppendEventsResponse"
359
+ examples:
360
+ success:
361
+ value:
362
+ newVersion: 188
363
+ events:
364
+ - version: 188
365
+ id: 44444444-4444-4444-4444-444444444444
366
+ type: USER_CREATED
367
+ entityId: user-123
368
+ entityVersion: 1
369
+ timestamp: 1717323612903
370
+ payload:
371
+ name: John Doe
372
+ email: john@example.com
373
+ "400":
374
+ $ref: "#/components/responses/ValidationError"
375
+ "409":
376
+ description: |
377
+ Entity conflict, malformed entity version chain, request ID reuse with different content,
378
+ or an action or event id that the log already accepted.
379
+ content:
380
+ application/json:
381
+ schema:
382
+ oneOf:
383
+ - $ref: "#/components/schemas/ConflictResponse"
384
+ - $ref: "#/components/schemas/EntityVersionChainErrorResponse"
385
+ - $ref: "#/components/schemas/ErrorResponse"
386
+ examples:
387
+ entityConflict:
388
+ summary: Entity version conflict
389
+ value:
390
+ code: entity_conflict
391
+ entityId: user-123
392
+ currentEntityVersion: 3
393
+ currentLogVersion: 190
394
+ description: |
395
+ Client attempted to append with baseEntityVersion=1, but the entity is now at version 3.
396
+ Client should fetch events for user-123 after entity version 1 and retry.
397
+ invalidEntityVersionChain:
398
+ summary: Later event does not continue the batch chain
399
+ value:
400
+ error: ValidationError
401
+ code: invalid_entity_version_chain
402
+ message: baseEntityVersion does not continue the entity version chain
403
+ entityId: user-123
404
+ expectedBaseEntityVersion: 2
405
+ actualBaseEntityVersion: 1
406
+ idempotencyMismatch:
407
+ summary: Request ID reused with different content
408
+ value:
409
+ error: Conflict
410
+ code: idempotency_mismatch
411
+ message: requestId was already used with different request content
412
+ "401":
413
+ $ref: "#/components/responses/Unauthorized"
414
+ "404":
415
+ $ref: "#/components/responses/NotFound"
416
+ "422":
417
+ description: |
418
+ The JSON body does not match the request schema, or the deployment enforces event
419
+ validation (`EVENT_VALIDATION=enforce`) and an event is of an unknown type or has a
420
+ payload key of the wrong kind or a missing required key. The latter returns
421
+ `code: invalid_event_payload` and a `message` naming the event and key.
422
+ get:
423
+ operationId: listEvents
424
+ summary: Fetch events after a version
425
+ parameters:
426
+ - $ref: "#/components/parameters/AfterVersion"
427
+ - $ref: "#/components/parameters/Limit"
428
+ description: |
429
+ Returns at most `limit` events with `version` greater than `afterVersion`.
430
+ Page until fewer than `limit` events are returned.
431
+ Available to app tokens in the same app.
432
+ responses:
433
+ "200":
434
+ description: Events after the requested version.
435
+ content:
436
+ application/json:
437
+ schema:
438
+ $ref: "#/components/schemas/EventBatch"
439
+ examples:
440
+ default:
441
+ value:
442
+ events:
443
+ - version: 181
444
+ id: 66666666-6666-6666-6666-666666666666
445
+ type: TRANSACTION_UPDATED
446
+ entityId: txn-5a00
447
+ entityVersion: 2
448
+ timestamp: 1717319500111
449
+ payload:
450
+ amount: 420.0
451
+ - version: 182
452
+ id: 77777777-7777-7777-7777-777777777777
453
+ type: ASSET_ADDED
454
+ entityId: asset-33a8
455
+ entityVersion: 1
456
+ timestamp: 1717319610786
457
+ payload:
458
+ name: Savings
459
+ amount: 3000
460
+ asset_type: cash
461
+ "401":
462
+ $ref: "#/components/responses/Unauthorized"
463
+ "404":
464
+ $ref: "#/components/responses/NotFound"
465
+ /logs/{logId}/events/socket:
466
+ parameters:
467
+ - $ref: "#/components/parameters/LogId"
468
+ - name: afterVersion
469
+ in: query
470
+ required: false
471
+ schema:
472
+ type: integer
473
+ minimum: 0
474
+ default: 0
475
+ description: Last locally committed log version used for socket catch-up.
476
+ get:
477
+ operationId: connectEventSocket
478
+ summary: Upgrade to the ledger.v1 WebSocket protocol
479
+ description: |
480
+ The browser connects through the same-origin frontend gateway, which
481
+ injects the app bearer credential while forwarding the upgrade through
482
+ a Cloudflare service binding. The server sends `event`, `ready`,
483
+ `resync_required`, and `error` JSON frames. Client messages are not
484
+ supported.
485
+ responses:
486
+ "101":
487
+ description: WebSocket upgrade accepted.
488
+ "400":
489
+ description: The `ledger.v1` subprotocol was not requested.
490
+ "401":
491
+ $ref: "#/components/responses/Unauthorized"
492
+ "404":
493
+ $ref: "#/components/responses/NotFound"
494
+ "409":
495
+ description: |
496
+ The client must upgrade to read this log (`client_upgrade_required`), or the log is a
497
+ fork that is still being prepared (`fork_preparing`).
498
+ "426":
499
+ description: The request is not a WebSocket upgrade.
500
+ /admin/v1/apps:
501
+ get:
502
+ operationId: listApps
503
+ summary: List apps
504
+ description: Returns app lifecycle metadata for operators. Requires `ADMIN_TOKEN`.
505
+ security:
506
+ - adminBearerAuth: []
507
+ responses:
508
+ "200":
509
+ description: List of apps.
510
+ content:
511
+ application/json:
512
+ schema:
513
+ $ref: "#/components/schemas/AdminAppListResponse"
514
+ "401":
515
+ $ref: "#/components/responses/Unauthorized"
516
+ post:
517
+ operationId: createApp
518
+ summary: Create app
519
+ description: Creates an app and returns its newly generated app token exactly once.
520
+ security:
521
+ - adminBearerAuth: []
522
+ requestBody:
523
+ required: true
524
+ content:
525
+ application/json:
526
+ schema:
527
+ $ref: "#/components/schemas/AdminCreateAppRequest"
528
+ responses:
529
+ "201":
530
+ description: App created.
531
+ content:
532
+ application/json:
533
+ schema:
534
+ $ref: "#/components/schemas/AdminAppTokenResponse"
535
+ "400":
536
+ $ref: "#/components/responses/ValidationError"
537
+ "401":
538
+ $ref: "#/components/responses/Unauthorized"
539
+ /admin/v1/apps/{appId}/revoke:
540
+ parameters:
541
+ - $ref: "#/components/parameters/AppId"
542
+ post:
543
+ operationId: revokeApp
544
+ summary: Revoke app
545
+ description: Revokes the app and all active principals in that app.
546
+ security:
547
+ - adminBearerAuth: []
548
+ responses:
549
+ "200":
550
+ description: Revoked app summary.
551
+ content:
552
+ application/json:
553
+ schema:
554
+ $ref: "#/components/schemas/AdminAppSummary"
555
+ "401":
556
+ $ref: "#/components/responses/Unauthorized"
557
+ "404":
558
+ $ref: "#/components/responses/NotFound"
559
+ /admin/v1/apps/{appId}/logs:
560
+ parameters:
561
+ - $ref: "#/components/parameters/AppId"
562
+ get:
563
+ operationId: listAppLogsAsAdmin
564
+ summary: List an app's logs
565
+ description: Returns logical log metadata for an active or revoked app. Requires `ADMIN_TOKEN`.
566
+ security:
567
+ - adminBearerAuth: []
568
+ responses:
569
+ "200":
570
+ description: List of application logs.
571
+ content:
572
+ application/json:
573
+ schema:
574
+ $ref: "#/components/schemas/LogListResponse"
575
+ "401":
576
+ $ref: "#/components/responses/Unauthorized"
577
+ "404":
578
+ $ref: "#/components/responses/NotFound"
579
+ /admin/v1/apps/{appId}/logs/{logId}/lineage:
580
+ parameters:
581
+ - $ref: "#/components/parameters/AppId"
582
+ - $ref: "#/components/parameters/LogId"
583
+ post:
584
+ operationId: setRestoredLogLineage
585
+ summary: Restore a fork's source relationship
586
+ description: Admin-only logical backup restore step. Validates the child prefix against the mapped parent and rejects cycles.
587
+ security:
588
+ - adminBearerAuth: []
589
+ requestBody:
590
+ required: true
591
+ content:
592
+ application/json:
593
+ schema:
594
+ type: object
595
+ required: [sourceLogId, sourceVersion]
596
+ additionalProperties: false
597
+ properties:
598
+ sourceLogId: { type: string, format: uuid }
599
+ sourceVersion: { type: integer, minimum: 0 }
600
+ responses:
601
+ "200":
602
+ description: Updated log metadata.
603
+ content:
604
+ application/json:
605
+ schema:
606
+ $ref: "#/components/schemas/LogSummary"
607
+ "400":
608
+ $ref: "#/components/responses/ValidationError"
609
+ "401":
610
+ $ref: "#/components/responses/Unauthorized"
611
+ /admin/v1/apps/{appId}/connections:
612
+ parameters:
613
+ - $ref: "#/components/parameters/AppId"
614
+ get:
615
+ operationId: listAppConnectionsAsAdmin
616
+ summary: Inspect live and recent WebSocket connections
617
+ description: Samples the Durable Object runtime, including closing sockets. Client handshake metadata is descriptive, not authenticated identity. Cursor is the last event sent, not a client acknowledgement. Legacy metadata may be null. Connection changes between pages are reflected live. Every page also lists recently started sessions that are no longer attached; clients close their stream while hidden, so live sockets alone rarely show who uses an application.
618
+ security:
619
+ - adminBearerAuth: []
620
+ parameters:
621
+ - name: afterId
622
+ in: query
623
+ description: Exclusive connection ID cursor. Pages contain at most 100 connections.
624
+ schema: { type: string, format: uuid }
625
+ responses:
626
+ "200":
627
+ description: Live connection page.
628
+ content:
629
+ application/json:
630
+ schema:
631
+ $ref: "#/components/schemas/AdminConnectionPage"
632
+ "400":
633
+ $ref: "#/components/responses/ValidationError"
634
+ "401":
635
+ $ref: "#/components/responses/Unauthorized"
636
+ "404":
637
+ $ref: "#/components/responses/NotFound"
638
+ /admin/v1/apps/{appId}/export/sqlite:
639
+ parameters:
640
+ - $ref: "#/components/parameters/AppId"
641
+ get:
642
+ operationId: exportAppSqliteAsAdmin
643
+ summary: Export every Ledger-owned SQL table for an application
644
+ description: Streams a SQL dump of schema, metadata, logs, events, entity claims, migration records and idempotency rows. Includes revoked applications. Interleaved Ledger writes or schema changes abort the stream; incomplete downloads must be discarded. No storage mutation is performed.
645
+ security:
646
+ - adminBearerAuth: []
647
+ responses:
648
+ "200":
649
+ $ref: "#/components/responses/SqliteExport"
650
+ "401":
651
+ $ref: "#/components/responses/Unauthorized"
652
+ "404":
653
+ $ref: "#/components/responses/NotFound"
654
+ /admin/v1/registry/export/sqlite:
655
+ get:
656
+ operationId: exportRegistrySqliteAsAdmin
657
+ summary: Export all registry SQL tables
658
+ description: Includes all application registrations, provisioning state, principal metadata, migrations and indexes. The registry and each application are separate databases; separate downloads are not one atomic cross-database snapshot.
659
+ security:
660
+ - adminBearerAuth: []
661
+ responses:
662
+ "200":
663
+ $ref: "#/components/responses/SqliteExport"
664
+ "401":
665
+ $ref: "#/components/responses/Unauthorized"
666
+ /admin/v1/apps/{appId}/metrics:
667
+ parameters:
668
+ - $ref: "#/components/parameters/AppId"
669
+ get:
670
+ operationId: getAppMetricsAsAdmin
671
+ summary: Sample application storage and connection metrics
672
+ description: Includes active and revoked applications. Counts cover current logs only.
673
+ security:
674
+ - adminBearerAuth: []
675
+ responses:
676
+ "200":
677
+ description: A per-application snapshot, not request or billing analytics.
678
+ content:
679
+ application/json:
680
+ schema:
681
+ $ref: "#/components/schemas/AdminMetrics"
682
+ "401":
683
+ $ref: "#/components/responses/Unauthorized"
684
+ "404":
685
+ $ref: "#/components/responses/NotFound"
686
+ /admin/v1/apps/{appId}/logs/{logId}/events:
687
+ parameters:
688
+ - $ref: "#/components/parameters/AppId"
689
+ - $ref: "#/components/parameters/LogId"
690
+ get:
691
+ operationId: listLogEventsAsAdmin
692
+ summary: Browse all stored events in descending version order
693
+ description: Includes revoked applications. Search uses case-insensitive substrings over event type, entity ID and event ID across the selected log; payloads are not searched. Older pages exclude events appended after the first page was read.
694
+ security:
695
+ - adminBearerAuth: []
696
+ parameters:
697
+ - name: beforeVersion
698
+ in: query
699
+ description: Exclusive upper version bound; omit to start at the newest event.
700
+ schema:
701
+ type: integer
702
+ minimum: 1
703
+ maximum: 9007199254740991
704
+ - name: limit
705
+ in: query
706
+ schema:
707
+ type: integer
708
+ minimum: 1
709
+ maximum: 100
710
+ default: 50
711
+ - name: search
712
+ in: query
713
+ schema:
714
+ type: string
715
+ maxLength: 200
716
+ responses:
717
+ "200":
718
+ description: Event page; a null nextBeforeVersion means the end of the matching history.
719
+ content:
720
+ application/json:
721
+ schema:
722
+ $ref: "#/components/schemas/AdminEventPage"
723
+ "400":
724
+ description: Invalid pagination or search parameters.
725
+ "401":
726
+ $ref: "#/components/responses/Unauthorized"
727
+ "404":
728
+ $ref: "#/components/responses/NotFound"
729
+ /admin/v1/apps/{appId}/export:
730
+ parameters:
731
+ - $ref: "#/components/parameters/AppId"
732
+ get:
733
+ operationId: exportAppAsAdmin
734
+ summary: Download all logical app data
735
+ description: |
736
+ Streams a consistent JSON snapshot containing application metadata,
737
+ every log, and every event through each log's captured head version.
738
+ Authentication secrets and internal idempotency rows are not included.
739
+ security:
740
+ - adminBearerAuth: []
741
+ responses:
742
+ "200":
743
+ description: Streamed application export.
744
+ headers:
745
+ Content-Disposition:
746
+ schema:
747
+ type: string
748
+ description: Attachment filename for the JSON export.
749
+ content:
750
+ application/json:
751
+ schema:
752
+ $ref: "#/components/schemas/AdminApplicationExport"
753
+ "401":
754
+ $ref: "#/components/responses/Unauthorized"
755
+ "404":
756
+ $ref: "#/components/responses/NotFound"
757
+ /admin/v1/apps/{appId}/fence:
758
+ parameters:
759
+ - $ref: "#/components/parameters/AppId"
760
+ get:
761
+ operationId: getAppFence
762
+ summary: Get an app's write fence
763
+ security:
764
+ - adminBearerAuth: []
765
+ responses:
766
+ "200":
767
+ description: "`{ fence: null }` or `{ fence: { mode, destination } }`."
768
+ "401":
769
+ $ref: "#/components/responses/Unauthorized"
770
+ "404":
771
+ $ref: "#/components/responses/NotFound"
772
+ put:
773
+ operationId: setAppFence
774
+ summary: Set or lift an app's write fence
775
+ description: "`tenant_migrating` pauses writes (503, `Retry-After`); `moved` ends them (410 with the destination origin); `off` lifts the fence. Reads stay open."
776
+ security:
777
+ - adminBearerAuth: []
778
+ requestBody:
779
+ required: true
780
+ content:
781
+ application/json:
782
+ schema:
783
+ type: object
784
+ properties:
785
+ mode:
786
+ type: string
787
+ enum: [off, tenant_migrating, moved]
788
+ destination:
789
+ type: string
790
+ description: HTTPS origin; only with `moved`.
791
+ required:
792
+ - mode
793
+ additionalProperties: false
794
+ responses:
795
+ "200":
796
+ description: The fence now in force.
797
+ "400":
798
+ $ref: "#/components/responses/ValidationError"
799
+ "401":
800
+ $ref: "#/components/responses/Unauthorized"
801
+ "404":
802
+ $ref: "#/components/responses/NotFound"
803
+ /admin/v1/apps/{appId}/transfer:
804
+ parameters:
805
+ - $ref: "#/components/parameters/AppId"
806
+ get:
807
+ operationId: exportAppTransfer
808
+ summary: Export an app for an identity-preserving import
809
+ description: |
810
+ Streams NDJSON: the application, then for each log its record, its events
811
+ (with authors and action batches), and its idempotency records, then fork
812
+ requests and a totals line. Refused with `409 fork_preparing` while a fork is
813
+ still copying its files.
814
+ security:
815
+ - adminBearerAuth: []
816
+ responses:
817
+ "200":
818
+ description: The transfer stream.
819
+ content:
820
+ application/x-ndjson:
821
+ schema:
822
+ type: string
823
+ "401":
824
+ $ref: "#/components/responses/Unauthorized"
825
+ "404":
826
+ $ref: "#/components/responses/NotFound"
827
+ "409":
828
+ description: A fork is still preparing.
829
+ /admin/v1/apps/{appId}/checksums:
830
+ parameters:
831
+ - $ref: "#/components/parameters/AppId"
832
+ get:
833
+ operationId: getAppChecksums
834
+ summary: Per-log event checksums
835
+ description: One SHA-256 per log over its events in version order, with the head version and event count, for comparing a source with its imported copy.
836
+ security:
837
+ - adminBearerAuth: []
838
+ responses:
839
+ "200":
840
+ description: Checksums per log.
841
+ "401":
842
+ $ref: "#/components/responses/Unauthorized"
843
+ "404":
844
+ $ref: "#/components/responses/NotFound"
845
+ /admin/v1/import:
846
+ post:
847
+ operationId: beginImport
848
+ summary: Begin an identity-preserving import
849
+ description: Creates the application under its original id, empty and unusable (no token exists) until the import finishes. Accepted only by a Ledger with no application (`409 ledger_not_empty`).
850
+ security:
851
+ - adminBearerAuth: []
852
+ responses:
853
+ "201":
854
+ description: The application now awaits its records.
855
+ "401":
856
+ $ref: "#/components/responses/Unauthorized"
857
+ "409":
858
+ description: The Ledger already has an application.
859
+ /admin/v1/apps/{appId}/import:
860
+ parameters:
861
+ - $ref: "#/components/parameters/AppId"
862
+ post:
863
+ operationId: importRecords
864
+ summary: Import a chunk of transfer records
865
+ description: "`{ records: [...] }` taken from the export stream in order. Strictly validated: a gap in versions, an entity version that does not follow, or a log that does not exist yet is refused."
866
+ security:
867
+ - adminBearerAuth: []
868
+ responses:
869
+ "200":
870
+ description: Counts of what was imported.
871
+ "401":
872
+ $ref: "#/components/responses/Unauthorized"
873
+ "404":
874
+ $ref: "#/components/responses/NotFound"
875
+ "409":
876
+ description: Out of order, a gap, or not importing.
877
+ /admin/v1/apps/{appId}/import/abort:
878
+ parameters:
879
+ - $ref: "#/components/parameters/AppId"
880
+ post:
881
+ operationId: abortImport
882
+ summary: Abort an import in progress
883
+ description: Empties an application whose import failed so the Ledger is empty again and the import can start over. Refused once the import has finished.
884
+ security:
885
+ - adminBearerAuth: []
886
+ responses:
887
+ "204":
888
+ description: The half-imported application is gone.
889
+ "401":
890
+ $ref: "#/components/responses/Unauthorized"
891
+ "404":
892
+ $ref: "#/components/responses/NotFound"
893
+ /admin/v1/apps/{appId}/import/finish:
894
+ parameters:
895
+ - $ref: "#/components/parameters/AppId"
896
+ post:
897
+ operationId: finishImport
898
+ summary: Finish an import
899
+ description: "`{ logs: [{ id, headVersion }] }` as the source reported. The application opens only when every log holds exactly that many contiguous events."
900
+ security:
901
+ - adminBearerAuth: []
902
+ responses:
903
+ "200":
904
+ description: The application is active; mint its token with rotate-token.
905
+ "401":
906
+ $ref: "#/components/responses/Unauthorized"
907
+ "404":
908
+ $ref: "#/components/responses/NotFound"
909
+ "409":
910
+ description: The history is incomplete.
911
+ /admin/v1/apps/{appId}/policy:
912
+ parameters:
913
+ - $ref: "#/components/parameters/AppId"
914
+ get:
915
+ operationId: getAppPolicy
916
+ summary: Get an app's event policy
917
+ description: Returns the policy the app's appends are checked against, or `null` for an app created before policies existed (it accepts every module's events and runtime entity types, in the deployment's validation mode).
918
+ security:
919
+ - adminBearerAuth: []
920
+ responses:
921
+ "200":
922
+ description: The app's event policy.
923
+ content:
924
+ application/json:
925
+ schema:
926
+ type: object
927
+ properties:
928
+ policy:
929
+ oneOf:
930
+ - $ref: "#/components/schemas/AdminEventPolicy"
931
+ - type: "null"
932
+ required:
933
+ - policy
934
+ "401":
935
+ $ref: "#/components/responses/Unauthorized"
936
+ "404":
937
+ $ref: "#/components/responses/NotFound"
938
+ put:
939
+ operationId: setAppPolicy
940
+ summary: Set an app's event policy
941
+ description: Replaces the policy. Events already accepted are never re-judged; later appends follow it.
942
+ security:
943
+ - adminBearerAuth: []
944
+ requestBody:
945
+ required: true
946
+ content:
947
+ application/json:
948
+ schema:
949
+ $ref: "#/components/schemas/AdminEventPolicy"
950
+ responses:
951
+ "200":
952
+ description: The policy now in force.
953
+ content:
954
+ application/json:
955
+ schema:
956
+ $ref: "#/components/schemas/AdminEventPolicy"
957
+ "400":
958
+ $ref: "#/components/responses/ValidationError"
959
+ "401":
960
+ $ref: "#/components/responses/Unauthorized"
961
+ "404":
962
+ $ref: "#/components/responses/NotFound"
963
+ /admin/v1/apps/{appId}/rotate-token:
964
+ parameters:
965
+ - $ref: "#/components/parameters/AppId"
966
+ post:
967
+ operationId: rotateAppToken
968
+ summary: Rotate app token
969
+ description: Revokes the current app token and returns a new generated token exactly once.
970
+ security:
971
+ - adminBearerAuth: []
972
+ responses:
973
+ "200":
974
+ description: New app token returned once.
975
+ content:
976
+ application/json:
977
+ schema:
978
+ $ref: "#/components/schemas/AdminAppTokenResponse"
979
+ "400":
980
+ $ref: "#/components/responses/ValidationError"
981
+ "401":
982
+ $ref: "#/components/responses/Unauthorized"
983
+ "404":
984
+ $ref: "#/components/responses/NotFound"
985
+ components:
986
+ securitySchemes:
987
+ bearerAuth:
988
+ type: http
989
+ scheme: bearer
990
+ bearerFormat: opaque
991
+ adminBearerAuth:
992
+ type: http
993
+ scheme: bearer
994
+ bearerFormat: opaque
995
+ parameters:
996
+ LogId:
997
+ name: logId
998
+ in: path
999
+ required: true
1000
+ schema:
1001
+ type: string
1002
+ format: uuid
1003
+ description: Identifier of the log to operate on (as returned by `/logs`).
1004
+ AfterVersion:
1005
+ name: afterVersion
1006
+ in: query
1007
+ required: false
1008
+ schema:
1009
+ type: integer
1010
+ minimum: 0
1011
+ default: 0
1012
+ description: >
1013
+ Only return events with `version` greater than this number.
1014
+ Omit or set to 0 to begin with the first event.
1015
+ Limit:
1016
+ name: limit
1017
+ in: query
1018
+ required: false
1019
+ schema:
1020
+ type: integer
1021
+ minimum: 1
1022
+ default: 512
1023
+ description: Maximum events returned in one page. Values above 512 are clamped.
1024
+ AppId:
1025
+ name: appId
1026
+ in: path
1027
+ required: true
1028
+ schema:
1029
+ type: string
1030
+ format: uuid
1031
+ description: Identifier of the app.
1032
+ responses:
1033
+ SqliteExport:
1034
+ description: A streamed SQL dump, suitable for sqlite3 or incremental reconstruction by the admin UI. Contains statement-boundary comments and ends with the ledger-sqlite-export-complete marker. Excludes Cloudflare private tables, KV, WebSocket attachments, Worker secrets and PITR history. This is not a raw physical SQLite file.
1035
+ headers:
1036
+ Content-Disposition:
1037
+ description: Attachment filename ending in .sql.
1038
+ schema: { type: string }
1039
+ Cache-Control:
1040
+ schema: { type: string, const: no-store }
1041
+ content:
1042
+ application/sql:
1043
+ schema: { type: string }
1044
+ Unauthorized:
1045
+ description: Missing, invalid, or revoked bearer token.
1046
+ content:
1047
+ application/json:
1048
+ schema:
1049
+ $ref: "#/components/schemas/ErrorResponse"
1050
+ example:
1051
+ error: Unauthorized
1052
+ code: unauthorized
1053
+ message: Invalid bearer token
1054
+ NotFound:
1055
+ description: Log not found in the caller's app scope.
1056
+ content:
1057
+ application/json:
1058
+ schema:
1059
+ $ref: "#/components/schemas/ErrorResponse"
1060
+ example:
1061
+ error: NotFound
1062
+ code: log_not_found
1063
+ ValidationError:
1064
+ description: Request validation failed.
1065
+ content:
1066
+ application/json:
1067
+ schema:
1068
+ $ref: "#/components/schemas/ErrorResponse"
1069
+ example:
1070
+ error: ValidationError
1071
+ code: validation_error
1072
+ message: app name must not be empty
1073
+ schemas:
1074
+ AdminConnectionPage:
1075
+ type: object
1076
+ required: [sampledAt, total, unavailable, connections, nextAfterId, recent]
1077
+ properties:
1078
+ sampledAt: { type: string, format: date-time }
1079
+ total: { type: integer, minimum: 0 }
1080
+ unavailable:
1081
+ type: integer
1082
+ minimum: 0
1083
+ description: Attached sockets with unreadable metadata, omitted from the connection list.
1084
+ nextAfterId: { type: [string, "null"], format: uuid }
1085
+ connections:
1086
+ type: array
1087
+ maxItems: 100
1088
+ items:
1089
+ $ref: "#/components/schemas/AdminConnection"
1090
+ recent:
1091
+ type: array
1092
+ maxItems: 50
1093
+ description: Sessions no longer attached, newest connection first. Ledger keeps the 50 most recent sessions per application, including live ones; the list is the same on every page.
1094
+ items:
1095
+ $ref: "#/components/schemas/AdminRecentConnection"
1096
+ AdminConnection:
1097
+ type: object
1098
+ required: [id, logId, logName, principalId, generation, state, cursor, headVersion, connectedAt, lastSentAt, client]
1099
+ properties:
1100
+ id: { type: string, format: uuid }
1101
+ logId: { type: string, format: uuid }
1102
+ logName: { type: [string, "null"] }
1103
+ principalId: { type: string, format: uuid }
1104
+ generation: { type: integer, minimum: 1 }
1105
+ state: { type: string, enum: [connecting, open, closing, closed] }
1106
+ cursor: { type: integer, minimum: 0 }
1107
+ headVersion: { type: [integer, "null"], minimum: 0 }
1108
+ connectedAt: { type: [string, "null"], format: date-time }
1109
+ lastSentAt: { type: [string, "null"], format: date-time }
1110
+ client:
1111
+ type: object
1112
+ required: [userAgent, ip, country, colo]
1113
+ properties:
1114
+ userAgent: { type: [string, "null"] }
1115
+ ip: { type: [string, "null"] }
1116
+ country: { type: [string, "null"] }
1117
+ colo: { type: [string, "null"] }
1118
+ AdminRecentConnection:
1119
+ type: object
1120
+ required: [id, logId, logName, principalId, generation, cursor, connectedAt, disconnectedAt, closeCode, lastSentAt, client]
1121
+ properties:
1122
+ id: { type: string, format: uuid }
1123
+ logId: { type: string, format: uuid }
1124
+ logName: { type: [string, "null"] }
1125
+ principalId: { type: string, format: uuid }
1126
+ generation: { type: integer, minimum: 1 }
1127
+ cursor:
1128
+ type: integer
1129
+ minimum: 0
1130
+ description: Last event sent before the session ended.
1131
+ connectedAt: { type: string, format: date-time }
1132
+ disconnectedAt:
1133
+ type: [string, "null"]
1134
+ format: date-time
1135
+ description: Null when the session ended without a close notification, for example during a deployment.
1136
+ closeCode: { type: [integer, "null"], minimum: 0 }
1137
+ lastSentAt: { type: [string, "null"], format: date-time }
1138
+ client:
1139
+ type: object
1140
+ required: [userAgent, ip, country, colo]
1141
+ properties:
1142
+ userAgent: { type: [string, "null"] }
1143
+ ip: { type: [string, "null"] }
1144
+ country: { type: [string, "null"] }
1145
+ colo: { type: [string, "null"] }
1146
+ ErrorResponse:
1147
+ type: object
1148
+ properties:
1149
+ error:
1150
+ type: string
1151
+ code:
1152
+ type: string
1153
+ message:
1154
+ type:
1155
+ - string
1156
+ - "null"
1157
+ required:
1158
+ - error
1159
+ - code
1160
+ SessionResponse:
1161
+ type: object
1162
+ properties:
1163
+ appId:
1164
+ type: string
1165
+ format: uuid
1166
+ principalId:
1167
+ type: string
1168
+ format: uuid
1169
+ required:
1170
+ - appId
1171
+ - principalId
1172
+ AdminEventPolicy:
1173
+ type: object
1174
+ description: What an app may append. The core's events are always allowed.
1175
+ properties:
1176
+ contracts:
1177
+ oneOf:
1178
+ - type: array
1179
+ items:
1180
+ type: string
1181
+ description: A module id such as `org.stor.finance`, or `core`.
1182
+ maxItems: 64
1183
+ - type: "null"
1184
+ description: Modules whose event types are accepted; `null` accepts every module.
1185
+ dynamicEvents:
1186
+ type: boolean
1187
+ description: Accept runtime-defined entity types (`ENTITY_TYPE_DEFINED` and their events).
1188
+ validation:
1189
+ oneOf:
1190
+ - type: string
1191
+ enum: [report, enforce]
1192
+ - type: "null"
1193
+ description: How violations are handled; `null` follows the deployment's `EVENT_VALIDATION`.
1194
+ required:
1195
+ - contracts
1196
+ - dynamicEvents
1197
+ - validation
1198
+ additionalProperties: false
1199
+ AdminCreateAppRequest:
1200
+ type: object
1201
+ properties:
1202
+ name:
1203
+ type: string
1204
+ policy:
1205
+ $ref: "#/components/schemas/AdminEventPolicy"
1206
+ description: Omitted, the app starts with `{ contracts: null, dynamicEvents: false, validation: enforce }`.
1207
+ required:
1208
+ - name
1209
+ additionalProperties: false
1210
+ AdminAppSummary:
1211
+ type: object
1212
+ properties:
1213
+ id:
1214
+ type: string
1215
+ format: uuid
1216
+ name:
1217
+ type: string
1218
+ createdAt:
1219
+ type: string
1220
+ format: date-time
1221
+ revokedAt:
1222
+ type:
1223
+ - string
1224
+ - "null"
1225
+ format: date-time
1226
+ activeTokenCreatedAt:
1227
+ type:
1228
+ - string
1229
+ - "null"
1230
+ format: date-time
1231
+ required:
1232
+ - id
1233
+ - name
1234
+ - createdAt
1235
+ - revokedAt
1236
+ - activeTokenCreatedAt
1237
+ AdminAppListResponse:
1238
+ type: object
1239
+ properties:
1240
+ apps:
1241
+ type: array
1242
+ items:
1243
+ $ref: "#/components/schemas/AdminAppSummary"
1244
+ required:
1245
+ - apps
1246
+ AdminAppTokenResponse:
1247
+ type: object
1248
+ properties:
1249
+ app:
1250
+ $ref: "#/components/schemas/AdminAppSummary"
1251
+ token:
1252
+ type: string
1253
+ description: Newly generated raw token returned exactly once.
1254
+ required:
1255
+ - app
1256
+ - token
1257
+ AdminApplicationExport:
1258
+ type: object
1259
+ properties:
1260
+ format:
1261
+ type: string
1262
+ enum:
1263
+ - ledger-application-export
1264
+ version:
1265
+ type: integer
1266
+ enum:
1267
+ - 1
1268
+ exportedAt:
1269
+ type: string
1270
+ format: date-time
1271
+ application:
1272
+ $ref: "#/components/schemas/AdminAppSummary"
1273
+ logs:
1274
+ type: array
1275
+ items:
1276
+ $ref: "#/components/schemas/AdminExportedLog"
1277
+ required:
1278
+ - format
1279
+ - version
1280
+ - exportedAt
1281
+ - application
1282
+ - logs
1283
+ AdminExportedLog:
1284
+ allOf:
1285
+ - $ref: "#/components/schemas/LogSummary"
1286
+ - type: object
1287
+ properties:
1288
+ events:
1289
+ type: array
1290
+ items:
1291
+ $ref: "#/components/schemas/AppEvent"
1292
+ required:
1293
+ - events
1294
+ AdminMetrics:
1295
+ type: object
1296
+ additionalProperties: false
1297
+ required: [sampledAt, logCount, eventCount, entityCount, databaseBytes, connectedClients, lastUpdatedAt]
1298
+ properties:
1299
+ sampledAt:
1300
+ type: string
1301
+ format: date-time
1302
+ logCount:
1303
+ type: integer
1304
+ minimum: 0
1305
+ eventCount:
1306
+ type: integer
1307
+ minimum: 0
1308
+ entityCount:
1309
+ type: integer
1310
+ minimum: 0
1311
+ description: Entity streams counted separately in each log.
1312
+ databaseBytes:
1313
+ type: integer
1314
+ minimum: 0
1315
+ description: SQLite storage including indexes and other Ledger tables.
1316
+ connectedClients:
1317
+ type: integer
1318
+ minimum: 0
1319
+ description: Attached WebSocket connections at sampling time.
1320
+ lastUpdatedAt:
1321
+ type: [string, "null"]
1322
+ format: date-time
1323
+ AdminEventPage:
1324
+ type: object
1325
+ additionalProperties: false
1326
+ required: [events, headVersion, nextBeforeVersion]
1327
+ properties:
1328
+ events:
1329
+ type: array
1330
+ maxItems: 100
1331
+ items:
1332
+ $ref: "#/components/schemas/AppEvent"
1333
+ headVersion:
1334
+ type: integer
1335
+ minimum: 0
1336
+ nextBeforeVersion:
1337
+ type: [integer, "null"]
1338
+ minimum: 1
1339
+ LogListResponse:
1340
+ type: object
1341
+ properties:
1342
+ logs:
1343
+ type: array
1344
+ items:
1345
+ $ref: "#/components/schemas/LogSummary"
1346
+ lineage:
1347
+ type: object
1348
+ description: Server-ordered log roots and direct children for rendering fork lineage.
1349
+ properties:
1350
+ rootIds:
1351
+ type: array
1352
+ items: { type: string, format: uuid }
1353
+ childIdsByLogId:
1354
+ type: object
1355
+ additionalProperties:
1356
+ type: array
1357
+ items: { type: string, format: uuid }
1358
+ required: [rootIds, childIdsByLogId]
1359
+ additionalProperties: false
1360
+ required:
1361
+ - logs
1362
+ - lineage
1363
+ additionalProperties: false
1364
+ LogSummary:
1365
+ type: object
1366
+ properties:
1367
+ id:
1368
+ type: string
1369
+ format: uuid
1370
+ description: Server-generated log identifier.
1371
+ name:
1372
+ type: string
1373
+ description: Human-readable log name.
1374
+ headVersion:
1375
+ type: integer
1376
+ minimum: 0
1377
+ description: Current highest version in the log.
1378
+ createdAt:
1379
+ type: string
1380
+ format: date-time
1381
+ description: Timestamp when the log was created.
1382
+ lastUpdatedAt:
1383
+ type:
1384
+ - string
1385
+ - "null"
1386
+ format: date-time
1387
+ description: Timestamp of the most recent update.
1388
+ sourceLogId:
1389
+ type: [string, "null"]
1390
+ format: uuid
1391
+ sourceLogName:
1392
+ type: [string, "null"]
1393
+ sourceVersion:
1394
+ type: [integer, "null"]
1395
+ minimum: 0
1396
+ sourceTimestamp:
1397
+ type: [string, "null"]
1398
+ format: date-time
1399
+ hiddenAt:
1400
+ type: [string, "null"]
1401
+ format: date-time
1402
+ description: When the log was hidden from the log tree, or null.
1403
+ description: Timestamp of the accepted source event at sourceVersion.
1404
+ status:
1405
+ type: string
1406
+ enum: [ready, preparing]
1407
+ required:
1408
+ - id
1409
+ - name
1410
+ - headVersion
1411
+ - createdAt
1412
+ - lastUpdatedAt
1413
+ - sourceLogId
1414
+ - sourceLogName
1415
+ - sourceVersion
1416
+ - sourceTimestamp
1417
+ - status
1418
+ - hiddenAt
1419
+ LogVisibilityRequest:
1420
+ type: object
1421
+ properties:
1422
+ hidden:
1423
+ type: boolean
1424
+ required:
1425
+ - hidden
1426
+ additionalProperties: false
1427
+ ForkLogRequest:
1428
+ type: object
1429
+ properties:
1430
+ name:
1431
+ type: string
1432
+ minLength: 1
1433
+ targetVersion:
1434
+ type: integer
1435
+ minimum: 0
1436
+ expectedSourceHeadVersion:
1437
+ type: integer
1438
+ minimum: 0
1439
+ requestId:
1440
+ type: string
1441
+ format: uuid
1442
+ required: [name, targetVersion, expectedSourceHeadVersion, requestId]
1443
+ additionalProperties: false
1444
+ CreateLogRequest:
1445
+ type: object
1446
+ properties:
1447
+ name:
1448
+ type: string
1449
+ description: Optional display name for the log. If omitted, the service may assign one.
1450
+ additionalProperties: false
1451
+ CreateLogResponse:
1452
+ type: object
1453
+ properties:
1454
+ id:
1455
+ type: string
1456
+ format: uuid
1457
+ description: Newly created log identifier.
1458
+ name:
1459
+ type: string
1460
+ description: Effective log name (may be generated when none provided).
1461
+ headVersion:
1462
+ type: integer
1463
+ minimum: 0
1464
+ createdAt:
1465
+ type: string
1466
+ format: date-time
1467
+ required:
1468
+ - id
1469
+ - name
1470
+ - headVersion
1471
+ - createdAt
1472
+ LogMetadata:
1473
+ allOf:
1474
+ - $ref: "#/components/schemas/LogSummary"
1475
+ AppendEventsRequest:
1476
+ type: object
1477
+ properties:
1478
+ requestId:
1479
+ type: string
1480
+ format: uuid
1481
+ description: Client-generated UUID used for idempotency.
1482
+ events:
1483
+ type: array
1484
+ minItems: 1
1485
+ maxItems: 512
1486
+ description: Events to append (in desired order).
1487
+ items:
1488
+ $ref: "#/components/schemas/IncomingEvent"
1489
+ required:
1490
+ - requestId
1491
+ - events
1492
+ additionalProperties: false
1493
+ IncomingEvent:
1494
+ type: object
1495
+ properties:
1496
+ id:
1497
+ type:
1498
+ - string
1499
+ - "null"
1500
+ format: uuid
1501
+ description: Optional event identifier supplied by the client. If omitted, the service generates one.
1502
+ type:
1503
+ type: string
1504
+ description: Domain event type (e.g., `TRANSACTION_ADDED`).
1505
+ entityId:
1506
+ type: string
1507
+ description: Identifier of the entity affected by the event. Required for all events.
1508
+ baseEntityVersion:
1509
+ type:
1510
+ - integer
1511
+ - "null"
1512
+ format: int64
1513
+ minimum: 0
1514
+ description: |
1515
+ Expected entity version immediately before this event. For an existing entity at version
1516
+ `N`, repeated events in one request use `N`, `N+1`, and so on. For a new entity, omit the
1517
+ first base and use `1`, `2`, and so on for later events in the same request.
1518
+ timestamp:
1519
+ type:
1520
+ - integer
1521
+ - "null"
1522
+ format: int64
1523
+ minimum: 0
1524
+ maximum: 8640000000000000
1525
+ description: Optional client timestamp in milliseconds since Unix epoch.
1526
+ payload:
1527
+ type: object
1528
+ description: Event payload to persist as-is.
1529
+ required:
1530
+ - type
1531
+ - entityId
1532
+ - payload
1533
+ additionalProperties: false
1534
+ AppendEventsResponse:
1535
+ type: object
1536
+ properties:
1537
+ newVersion:
1538
+ type: integer
1539
+ minimum: 0
1540
+ description: Version assigned to the last persisted event.
1541
+ events:
1542
+ type: array
1543
+ description: Events actually stored (with server-assigned ids/timestamps/versions).
1544
+ items:
1545
+ $ref: "#/components/schemas/AppEvent"
1546
+ required:
1547
+ - newVersion
1548
+ - events
1549
+ ConflictResponse:
1550
+ type: object
1551
+ properties:
1552
+ code:
1553
+ type: string
1554
+ const: entity_conflict
1555
+ entityId:
1556
+ type: string
1557
+ description: The entity identifier that caused the conflict.
1558
+ currentEntityVersion:
1559
+ type: integer
1560
+ format: int64
1561
+ minimum: 1
1562
+ description: Current version of the conflicting entity. Client should replay missing log events and retry with the correct baseEntityVersion.
1563
+ currentLogVersion:
1564
+ type: integer
1565
+ minimum: 0
1566
+ description: Current head version of the entire log (for context).
1567
+ required:
1568
+ - code
1569
+ - entityId
1570
+ - currentEntityVersion
1571
+ - currentLogVersion
1572
+ EntityVersionChainErrorResponse:
1573
+ type: object
1574
+ properties:
1575
+ error:
1576
+ type: string
1577
+ const: ValidationError
1578
+ code:
1579
+ type: string
1580
+ const: invalid_entity_version_chain
1581
+ message:
1582
+ type: string
1583
+ entityId:
1584
+ type: string
1585
+ expectedBaseEntityVersion:
1586
+ type:
1587
+ - integer
1588
+ - "null"
1589
+ format: int64
1590
+ actualBaseEntityVersion:
1591
+ type:
1592
+ - integer
1593
+ - "null"
1594
+ format: int64
1595
+ required:
1596
+ - error
1597
+ - code
1598
+ - message
1599
+ - entityId
1600
+ - expectedBaseEntityVersion
1601
+ - actualBaseEntityVersion
1602
+ EventBatch:
1603
+ type: object
1604
+ properties:
1605
+ events:
1606
+ type: array
1607
+ items:
1608
+ $ref: "#/components/schemas/AppEvent"
1609
+ required:
1610
+ - events
1611
+ AppEvent:
1612
+ type: object
1613
+ properties:
1614
+ version:
1615
+ type: integer
1616
+ minimum: 1
1617
+ description: Monotonic sequence number within the log (global log version).
1618
+ id:
1619
+ type: string
1620
+ format: uuid
1621
+ description: Event identifier (typically a UUID).
1622
+ type:
1623
+ type: string
1624
+ description: Domain event type (e.g., `TRANSACTION_ADDED`, `ASSET_UPDATED`).
1625
+ entityId:
1626
+ type: string
1627
+ description: Identifier of the entity associated with the event.
1628
+ entityVersion:
1629
+ type: integer
1630
+ format: int64
1631
+ minimum: 1
1632
+ description: Monotonic sequence number specific to this entity. Starts at 1 for each entity's first event.
1633
+ timestamp:
1634
+ type: integer
1635
+ format: int64
1636
+ description: Milliseconds since Unix epoch when the event was committed.
1637
+ payload:
1638
+ type: object
1639
+ description: Event-specific data; mirrors the client’s expectations.
1640
+ actor:
1641
+ type: string
1642
+ maxLength: 320
1643
+ description: Identity the gateway verified for the request that wrote the event. Absent for events accepted without one.
1644
+ required:
1645
+ - version
1646
+ - id
1647
+ - type
1648
+ - entityId
1649
+ - entityVersion
1650
+ - timestamp
1651
+ - payload