@sekiban/dcb-runtime 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/LICENSE +86 -0
  2. package/README.md +13 -0
  3. package/dist/allocator/AllocatorDurableObject.d.ts +36 -0
  4. package/dist/allocator/AllocatorDurableObject.js +658 -0
  5. package/dist/allocator/IssuanceLedger.d.ts +188 -0
  6. package/dist/allocator/IssuanceLedger.js +608 -0
  7. package/dist/allocator/IssuanceReconciler.d.ts +41 -0
  8. package/dist/allocator/IssuanceReconciler.js +191 -0
  9. package/dist/allocator/OrderClock.d.ts +41 -0
  10. package/dist/allocator/OrderClock.js +92 -0
  11. package/dist/allocator/SortableUniqueId.d.ts +46 -0
  12. package/dist/allocator/SortableUniqueId.js +156 -0
  13. package/dist/allocator/types.d.ts +52 -0
  14. package/dist/allocator/types.js +1 -0
  15. package/dist/bootstrap/BootstrapCoordinatorDurableObject.d.ts +28 -0
  16. package/dist/bootstrap/BootstrapCoordinatorDurableObject.js +357 -0
  17. package/dist/bootstrap/BootstrapStoreAdapter.d.ts +51 -0
  18. package/dist/bootstrap/BootstrapStoreAdapter.js +162 -0
  19. package/dist/bootstrap/OperatorBootstrap.d.ts +20 -0
  20. package/dist/bootstrap/OperatorBootstrap.js +104 -0
  21. package/dist/bootstrap/manifest.d.ts +9 -0
  22. package/dist/bootstrap/manifest.js +115 -0
  23. package/dist/bootstrap/types.d.ts +75 -0
  24. package/dist/bootstrap/types.js +1 -0
  25. package/dist/cli/OperatorRepairCli.d.ts +11 -0
  26. package/dist/cli/OperatorRepairCli.js +108 -0
  27. package/dist/cloudflare.d.ts +164 -0
  28. package/dist/cloudflare.js +19487 -0
  29. package/dist/commit/CommitWorker.d.ts +101 -0
  30. package/dist/commit/CommitWorker.js +921 -0
  31. package/dist/commit/types.d.ts +25 -0
  32. package/dist/commit/types.js +13 -0
  33. package/dist/completeness/GlobalCompletenessReconciler.d.ts +77 -0
  34. package/dist/completeness/GlobalCompletenessReconciler.js +572 -0
  35. package/dist/completeness/types.d.ts +85 -0
  36. package/dist/completeness/types.js +6 -0
  37. package/dist/composition.d.ts +123 -0
  38. package/dist/composition.js +237 -0
  39. package/dist/cosmos.d.ts +11 -0
  40. package/dist/cosmos.js +1411 -0
  41. package/dist/d1-mv.d.ts +8 -0
  42. package/dist/d1-mv.js +2001 -0
  43. package/dist/d1.d.ts +16 -0
  44. package/dist/d1.js +3418 -0
  45. package/dist/diagnostics/G60DurableHop.d.ts +99 -0
  46. package/dist/diagnostics/G60DurableHop.js +248 -0
  47. package/dist/diagnostics/G65Admission.d.ts +23 -0
  48. package/dist/diagnostics/G65Admission.js +43 -0
  49. package/dist/diagnostics/G65DirectRing.d.ts +33 -0
  50. package/dist/diagnostics/G65DirectRing.js +85 -0
  51. package/dist/diagnostics/G69AdmissionAttempt.d.ts +43 -0
  52. package/dist/diagnostics/G69AdmissionAttempt.js +22 -0
  53. package/dist/downstream/DeliveryCore.d.ts +127 -0
  54. package/dist/downstream/DeliveryCore.js +304 -0
  55. package/dist/downstream/Doorbell.d.ts +43 -0
  56. package/dist/downstream/Doorbell.js +137 -0
  57. package/dist/downstream/DownstreamAdapter.d.ts +52 -0
  58. package/dist/downstream/DownstreamAdapter.js +299 -0
  59. package/dist/downstream/ExclusionLookup.d.ts +36 -0
  60. package/dist/downstream/ExclusionLookup.js +50 -0
  61. package/dist/downstream/InconsistencyDetector.d.ts +27 -0
  62. package/dist/downstream/InconsistencyDetector.js +82 -0
  63. package/dist/downstream/OutboxDrain.d.ts +35 -0
  64. package/dist/downstream/OutboxDrain.js +126 -0
  65. package/dist/downstream/types.d.ts +77 -0
  66. package/dist/downstream/types.js +64 -0
  67. package/dist/eventIdentity.d.ts +30 -0
  68. package/dist/eventIdentity.js +49 -0
  69. package/dist/eventRecord.d.ts +26 -0
  70. package/dist/eventRecord.js +53 -0
  71. package/dist/g42-probe.d.ts +9 -0
  72. package/dist/g42-probe.js +9 -0
  73. package/dist/http/SerializedQueryWorker.d.ts +58 -0
  74. package/dist/http/SerializedQueryWorker.js +497 -0
  75. package/dist/http/commitResponse.d.ts +46 -0
  76. package/dist/http/commitResponse.js +80 -0
  77. package/dist/http/testServiceId.d.ts +17 -0
  78. package/dist/http/testServiceId.js +14 -0
  79. package/dist/index.d.ts +112 -0
  80. package/dist/index.js +19926 -0
  81. package/dist/journal/JournalDurableObject.d.ts +30 -0
  82. package/dist/journal/JournalDurableObject.js +481 -0
  83. package/dist/journal/JournalFirstTouchProbe.d.ts +190 -0
  84. package/dist/journal/JournalFirstTouchProbe.js +375 -0
  85. package/dist/journal/types.d.ts +116 -0
  86. package/dist/journal/types.js +52 -0
  87. package/dist/mv/MaterializedViewCatchUp.d.ts +60 -0
  88. package/dist/mv/MaterializedViewCatchUp.js +353 -0
  89. package/dist/mv/MaterializedViewRuntime.d.ts +80 -0
  90. package/dist/mv/MaterializedViewRuntime.js +328 -0
  91. package/dist/mv/MaterializedViewStore.d.ts +257 -0
  92. package/dist/mv/MaterializedViewStore.js +875 -0
  93. package/dist/mv/UnsafeWindowMaterializedView.d.ts +112 -0
  94. package/dist/mv/UnsafeWindowMaterializedView.js +495 -0
  95. package/dist/mv/index.d.ts +8 -0
  96. package/dist/mv/index.js +2357 -0
  97. package/dist/projection/LiveProjectionWorker.d.ts +86 -0
  98. package/dist/projection/LiveProjectionWorker.js +240 -0
  99. package/dist/projection/ProjectionRuntime.d.ts +113 -0
  100. package/dist/projection/ProjectionRuntime.js +346 -0
  101. package/dist/projection/ProjectorRegistry.d.ts +51 -0
  102. package/dist/projection/ProjectorRegistry.js +116 -0
  103. package/dist/query/ProjectionQueryStore.d.ts +102 -0
  104. package/dist/query/ProjectionQueryStore.js +185 -0
  105. package/dist/query/QueryRegistry.d.ts +22 -0
  106. package/dist/query/QueryRegistry.js +36 -0
  107. package/dist/read/SerializedReadWorker.d.ts +36 -0
  108. package/dist/read/SerializedReadWorker.js +225 -0
  109. package/dist/repair/RepairWorker.d.ts +72 -0
  110. package/dist/repair/RepairWorker.js +461 -0
  111. package/dist/safeWindow.d.ts +20 -0
  112. package/dist/safeWindow.js +36 -0
  113. package/dist/scope/ControlRouteScope.d.ts +22 -0
  114. package/dist/scope/ControlRouteScope.js +36 -0
  115. package/dist/scope/ScopeName.d.ts +34 -0
  116. package/dist/scope/ScopeName.js +67 -0
  117. package/dist/service/ServiceIdentityProvider.d.ts +35 -0
  118. package/dist/service/ServiceIdentityProvider.js +56 -0
  119. package/dist/store/CosmosEventStore.d.ts +126 -0
  120. package/dist/store/CosmosEventStore.js +950 -0
  121. package/dist/store/D1EventStore.d.ts +111 -0
  122. package/dist/store/D1EventStore.js +1111 -0
  123. package/dist/store/PostgresEventStore.d.ts +35 -0
  124. package/dist/store/PostgresEventStore.js +811 -0
  125. package/dist/store/provider.d.ts +25 -0
  126. package/dist/store/provider.js +32 -0
  127. package/dist/store/types.d.ts +205 -0
  128. package/dist/store/types.js +18 -0
  129. package/dist/tag/EventDigest.d.ts +40 -0
  130. package/dist/tag/EventDigest.js +158 -0
  131. package/dist/tag/TagDurableObject.d.ts +286 -0
  132. package/dist/tag/TagDurableObject.js +4071 -0
  133. package/dist/tag/TagSqlMeasurement.d.ts +26 -0
  134. package/dist/tag/TagSqlMeasurement.js +44 -0
  135. package/dist/tag/TagSqlSchema.d.ts +29 -0
  136. package/dist/tag/TagSqlSchema.js +211 -0
  137. package/dist/tag/types.d.ts +173 -0
  138. package/dist/tag/types.js +3 -0
  139. package/dist/tagstate/TagStateDurableObject.d.ts +57 -0
  140. package/dist/tagstate/TagStateDurableObject.js +499 -0
  141. package/dist/trace/AttributionRatio.d.ts +12 -0
  142. package/dist/trace/AttributionRatio.js +68 -0
  143. package/dist/trace/CloudflareTracing.d.ts +7 -0
  144. package/dist/trace/CloudflareTracing.js +9 -0
  145. package/dist/trace/CommitTrace.d.ts +405 -0
  146. package/dist/trace/CommitTrace.js +1008 -0
  147. package/dist/trace/CommitTraceConsoleSink.d.ts +68 -0
  148. package/dist/trace/CommitTraceConsoleSink.js +114 -0
  149. package/dist/trace/CommitTraceVerifier.d.ts +22 -0
  150. package/dist/trace/CommitTraceVerifier.js +295 -0
  151. package/dist/trace/ObservationStream.d.ts +118 -0
  152. package/dist/trace/ObservationStream.js +148 -0
  153. package/package.json +71 -0
@@ -0,0 +1,99 @@
1
+ /**
2
+ * SDT-G60 AC1 operational hop ledger.
3
+ *
4
+ * This is deliberately separate from sdt.commit/v1 and from every public
5
+ * response. The ledger records immutable observations against the stable
6
+ * event/SUID/attempt identity; it is never consulted by admission, Queue
7
+ * acknowledgement, projection fencing, or a read response.
8
+ */
9
+ export declare const G60_HOP_STAGES: readonly ["command-receipt", "tag-append-committed", "outbox-obligation-written", "queue-send-returned", "consumer-invocation-started", "record-delivery-batch-committed", "first-unsafe-visible-read"];
10
+ export type G60HopStage = typeof G60_HOP_STAGES[number];
11
+ export type G60HopTransport = "" | "queue" | "fast" | "import" | "public-read";
12
+ /** Observation-only boundaries inside the interval after recordDelivery. */
13
+ export declare const G60_POST_ADMISSION_STAGES: readonly ["post-record-delivery-global-receipt-readback", "source-tag-acknowledgement", "completeness-coverage", "detector", "unsafe-view-apply"];
14
+ export type G60PostAdmissionStage = typeof G60_POST_ADMISSION_STAGES[number];
15
+ export type G60PostAdmissionBoundary = "start" | "end";
16
+ /** Concrete observation paths for an unsafe materialized-view row write. */
17
+ export declare const G60_UNSAFE_WRITER_PATHS: readonly ["inline-delivery", "scheduled-drain"];
18
+ export type G60UnsafeWriterPath = typeof G60_UNSAFE_WRITER_PATHS[number];
19
+ export type G60UnsafeWriterBoundary = "start" | "end";
20
+ export type G60UnsafeWriterTransport = "" | "queue" | "fast" | "import" | "scheduled";
21
+ export interface G60DurableHopObservation {
22
+ readonly stage: G60HopStage;
23
+ readonly serviceId: string;
24
+ readonly eventId: string;
25
+ readonly suid: string;
26
+ readonly attemptId: string;
27
+ readonly observedAt: number;
28
+ readonly partitionTag?: string;
29
+ readonly viewId?: string;
30
+ readonly transport?: G60HopTransport;
31
+ }
32
+ export interface G60DurableHopObserver {
33
+ observe(input: G60DurableHopObservation): void;
34
+ observeSubstep?(input: G60DurablePostAdmissionObservation): void;
35
+ observeUnsafeWriter?(input: G60DurableUnsafeWriterObservation): void;
36
+ }
37
+ export interface G60DurablePostAdmissionObservation {
38
+ readonly stage: G60PostAdmissionStage;
39
+ readonly boundary: G60PostAdmissionBoundary;
40
+ readonly outcome: string;
41
+ readonly serviceId: string;
42
+ readonly eventId: string;
43
+ readonly suid: string;
44
+ readonly attemptId: string;
45
+ readonly observedAt: number;
46
+ readonly partitionTag?: string;
47
+ readonly viewId?: string;
48
+ readonly transport?: G60HopTransport;
49
+ }
50
+ /**
51
+ * Observation-only boundary at the concrete unsafe MV writer. This table is
52
+ * separate from the older seven-hop and post-admission tables so the writer
53
+ * path remains explicit without changing either deployed schema contract.
54
+ */
55
+ export interface G60DurableUnsafeWriterObservation {
56
+ readonly writerPath: G60UnsafeWriterPath;
57
+ readonly boundary: G60UnsafeWriterBoundary;
58
+ readonly outcome: string;
59
+ readonly serviceId: string;
60
+ readonly eventId: string;
61
+ readonly suid: string;
62
+ readonly attemptId: string;
63
+ readonly viewId: string;
64
+ readonly observedAt: number;
65
+ readonly transport?: G60UnsafeWriterTransport;
66
+ }
67
+ /** Write one idempotent observation. Replays retain the earliest timestamp. */
68
+ export declare function recordDurableHop(database: D1Database, input: G60DurableHopObservation): Promise<void>;
69
+ /** Append one observation-only post-admission boundary; replays are ignored. */
70
+ export declare function recordDurableHopSubstep(database: D1Database, input: G60DurablePostAdmissionObservation): Promise<void>;
71
+ /** Append one exact unsafe-row writer boundary; replays retain the first row. */
72
+ export declare function recordDurableUnsafeWriterBoundary(database: D1Database, input: G60DurableUnsafeWriterObservation): Promise<void>;
73
+ /**
74
+ * Deliver one post-admission observation to the active waitUntil-backed
75
+ * observer. A diagnostic observer is never allowed to change protocol flow.
76
+ */
77
+ export declare function observeG60PostAdmission(observer: G60DurableHopObserver | undefined, input: G60DurablePostAdmissionObservation): void;
78
+ /** Deliver a concrete writer observation without allowing it to affect flow. */
79
+ export declare function observeG60UnsafeWriter(observer: G60DurableHopObserver | undefined, input: G60DurableUnsafeWriterObservation): void;
80
+ /**
81
+ * Schedule an observation without extending the application response or
82
+ * transport loop. The timestamp is captured by the caller at the hop; only
83
+ * the operational D1 write is deferred to the active Worker invocation.
84
+ */
85
+ export declare function createG60DurableHopObserver(database: D1Database | undefined, waitUntil: (promise: Promise<unknown>) => void): G60DurableHopObserver | undefined;
86
+ /**
87
+ * Resolve a public MV row back to the exact source EventId. Prefer the
88
+ * generation's durable unsafe receipt; if it has already been retired, use
89
+ * the source D1 point only when the SUID is unambiguous. G32 intentionally
90
+ * permits SUID collisions, so an ambiguous fallback is censored rather than
91
+ * attributed to the wrong event.
92
+ */
93
+ export declare function recordFirstUnsafeVisibleRead(pipelineDatabase: D1Database, materializedViewDatabase: D1Database, input: Readonly<{
94
+ serviceId: string;
95
+ viewId: string;
96
+ suid: string;
97
+ eventIdHint?: string;
98
+ observedAt: number;
99
+ }>): Promise<boolean>;
@@ -0,0 +1,248 @@
1
+ /**
2
+ * SDT-G60 AC1 operational hop ledger.
3
+ *
4
+ * This is deliberately separate from sdt.commit/v1 and from every public
5
+ * response. The ledger records immutable observations against the stable
6
+ * event/SUID/attempt identity; it is never consulted by admission, Queue
7
+ * acknowledgement, projection fencing, or a read response.
8
+ */
9
+ export const G60_HOP_STAGES = [
10
+ "command-receipt",
11
+ "tag-append-committed",
12
+ "outbox-obligation-written",
13
+ "queue-send-returned",
14
+ "consumer-invocation-started",
15
+ "record-delivery-batch-committed",
16
+ "first-unsafe-visible-read",
17
+ ];
18
+ /** Observation-only boundaries inside the interval after recordDelivery. */
19
+ export const G60_POST_ADMISSION_STAGES = [
20
+ "post-record-delivery-global-receipt-readback",
21
+ "source-tag-acknowledgement",
22
+ "completeness-coverage",
23
+ "detector",
24
+ "unsafe-view-apply",
25
+ ];
26
+ /** Concrete observation paths for an unsafe materialized-view row write. */
27
+ export const G60_UNSAFE_WRITER_PATHS = ["inline-delivery", "scheduled-drain"];
28
+ function nonEmpty(value, field) {
29
+ if (value.length === 0)
30
+ throw new Error(`G60 ${field} must be non-empty`);
31
+ }
32
+ function validate(input) {
33
+ nonEmpty(input.serviceId, "serviceId");
34
+ nonEmpty(input.eventId, "eventId");
35
+ nonEmpty(input.suid, "suid");
36
+ nonEmpty(input.attemptId, "attemptId");
37
+ if (!Number.isSafeInteger(input.observedAt) || input.observedAt < 0) {
38
+ throw new Error("G60 observedAt must be a non-negative safe integer");
39
+ }
40
+ const partitionTag = input.partitionTag ?? "";
41
+ const viewId = input.viewId ?? "";
42
+ const transport = input.transport ?? "";
43
+ if ((input.stage === "tag-append-committed" || input.stage === "outbox-obligation-written" || input.stage === "queue-send-returned" || input.stage === "consumer-invocation-started") && partitionTag.length === 0) {
44
+ throw new Error(`G60 ${input.stage} requires partitionTag`);
45
+ }
46
+ if (input.stage === "first-unsafe-visible-read" && viewId.length === 0) {
47
+ throw new Error("G60 first-unsafe-visible-read requires viewId");
48
+ }
49
+ if ((input.stage === "queue-send-returned" || input.stage === "consumer-invocation-started" || input.stage === "record-delivery-batch-committed") && transport === "") {
50
+ throw new Error(`G60 ${input.stage} requires transport`);
51
+ }
52
+ return { partitionTag, viewId, transport };
53
+ }
54
+ /** Write one idempotent observation. Replays retain the earliest timestamp. */
55
+ export async function recordDurableHop(database, input) {
56
+ const normalized = validate(input);
57
+ await database.prepare(`
58
+ INSERT INTO serialized_dcb_hop_measurements
59
+ (service_id, event_id, suid, attempt_id, stage, partition_tag, view_id, transport, observed_at)
60
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
61
+ ON CONFLICT (service_id, event_id, stage, partition_tag, view_id, transport)
62
+ DO UPDATE SET observed_at = MIN(serialized_dcb_hop_measurements.observed_at, excluded.observed_at)
63
+ WHERE serialized_dcb_hop_measurements.suid = excluded.suid
64
+ AND serialized_dcb_hop_measurements.attempt_id = excluded.attempt_id
65
+ `).bind(input.serviceId, input.eventId, input.suid, input.attemptId, input.stage, normalized.partitionTag, normalized.viewId, normalized.transport, input.observedAt).run();
66
+ }
67
+ function validatePostAdmission(input) {
68
+ nonEmpty(input.serviceId, "serviceId");
69
+ nonEmpty(input.eventId, "eventId");
70
+ nonEmpty(input.suid, "suid");
71
+ nonEmpty(input.attemptId, "attemptId");
72
+ nonEmpty(input.outcome, "outcome");
73
+ if (!G60_POST_ADMISSION_STAGES.includes(input.stage)) {
74
+ throw new Error(`G60 unknown post-admission stage: ${input.stage}`);
75
+ }
76
+ if (input.boundary !== "start" && input.boundary !== "end") {
77
+ throw new Error(`G60 post-admission boundary must be start or end: ${input.boundary}`);
78
+ }
79
+ if (!Number.isSafeInteger(input.observedAt) || input.observedAt < 0) {
80
+ throw new Error("G60 post-admission observedAt must be a non-negative safe integer");
81
+ }
82
+ const partitionTag = input.partitionTag ?? "";
83
+ const viewId = input.viewId ?? "";
84
+ const transport = input.transport ?? "";
85
+ if (input.stage === "source-tag-acknowledgement" && partitionTag.length === 0) {
86
+ throw new Error("G60 source-tag-acknowledgement requires partitionTag");
87
+ }
88
+ if (input.stage === "unsafe-view-apply" && viewId.length === 0) {
89
+ throw new Error("G60 unsafe-view-apply requires viewId");
90
+ }
91
+ return { partitionTag, viewId, transport };
92
+ }
93
+ /** Append one observation-only post-admission boundary; replays are ignored. */
94
+ export async function recordDurableHopSubstep(database, input) {
95
+ const normalized = validatePostAdmission(input);
96
+ await database.prepare(`
97
+ INSERT OR IGNORE INTO serialized_dcb_hop_submeasurements
98
+ (service_id, event_id, suid, attempt_id, stage, boundary, outcome,
99
+ partition_tag, view_id, transport, observed_at)
100
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
101
+ `).bind(input.serviceId, input.eventId, input.suid, input.attemptId, input.stage, input.boundary, input.outcome, normalized.partitionTag, normalized.viewId, normalized.transport, input.observedAt).run();
102
+ }
103
+ function validateUnsafeWriter(input) {
104
+ nonEmpty(input.serviceId, "serviceId");
105
+ nonEmpty(input.eventId, "eventId");
106
+ nonEmpty(input.suid, "suid");
107
+ nonEmpty(input.attemptId, "attemptId");
108
+ nonEmpty(input.viewId, "viewId");
109
+ nonEmpty(input.outcome, "outcome");
110
+ if (!G60_UNSAFE_WRITER_PATHS.includes(input.writerPath)) {
111
+ throw new Error(`G60 unknown unsafe writer path: ${input.writerPath}`);
112
+ }
113
+ if (input.boundary !== "start" && input.boundary !== "end") {
114
+ throw new Error(`G60 unsafe writer boundary must be start or end: ${input.boundary}`);
115
+ }
116
+ if (!Number.isSafeInteger(input.observedAt) || input.observedAt < 0) {
117
+ throw new Error("G60 unsafe writer observedAt must be a non-negative safe integer");
118
+ }
119
+ const transport = input.transport ?? "";
120
+ if (!["", "queue", "fast", "import", "scheduled"].includes(transport)) {
121
+ throw new Error(`G60 unsafe writer transport is invalid: ${transport}`);
122
+ }
123
+ return { transport };
124
+ }
125
+ /** Append one exact unsafe-row writer boundary; replays retain the first row. */
126
+ export async function recordDurableUnsafeWriterBoundary(database, input) {
127
+ const normalized = validateUnsafeWriter(input);
128
+ await database.prepare(`
129
+ INSERT OR IGNORE INTO serialized_dcb_unsafe_writer_boundaries
130
+ (service_id, event_id, suid, attempt_id, writer_path, boundary,
131
+ outcome, view_id, transport, observed_at)
132
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
133
+ `).bind(input.serviceId, input.eventId, input.suid, input.attemptId, input.writerPath, input.boundary, input.outcome, input.viewId, normalized.transport, input.observedAt).run();
134
+ }
135
+ /**
136
+ * Deliver one post-admission observation to the active waitUntil-backed
137
+ * observer. A diagnostic observer is never allowed to change protocol flow.
138
+ */
139
+ export function observeG60PostAdmission(observer, input) {
140
+ try {
141
+ observer?.observeSubstep?.(input);
142
+ }
143
+ catch {
144
+ // Observation is never allowed to affect admission, delivery, or reads.
145
+ }
146
+ }
147
+ /** Deliver a concrete writer observation without allowing it to affect flow. */
148
+ export function observeG60UnsafeWriter(observer, input) {
149
+ try {
150
+ observer?.observeUnsafeWriter?.(input);
151
+ }
152
+ catch {
153
+ // Observation is never allowed to affect the unsafe apply or its caller.
154
+ }
155
+ }
156
+ /**
157
+ * Schedule an observation without extending the application response or
158
+ * transport loop. The timestamp is captured by the caller at the hop; only
159
+ * the operational D1 write is deferred to the active Worker invocation.
160
+ */
161
+ export function createG60DurableHopObserver(database, waitUntil) {
162
+ if (database === undefined)
163
+ return undefined;
164
+ return Object.freeze({
165
+ observe(input) {
166
+ const write = recordDurableHop(database, input).catch(() => undefined);
167
+ waitUntil(write);
168
+ },
169
+ observeSubstep(input) {
170
+ const write = recordDurableHopSubstep(database, input).catch(() => undefined);
171
+ waitUntil(write);
172
+ },
173
+ observeUnsafeWriter(input) {
174
+ const write = recordDurableUnsafeWriterBoundary(database, input).catch(() => undefined);
175
+ waitUntil(write);
176
+ },
177
+ });
178
+ }
179
+ function asString(value) {
180
+ return typeof value === "string" && value.length > 0 ? value : undefined;
181
+ }
182
+ /**
183
+ * Resolve a public MV row back to the exact source EventId. Prefer the
184
+ * generation's durable unsafe receipt; if it has already been retired, use
185
+ * the source D1 point only when the SUID is unambiguous. G32 intentionally
186
+ * permits SUID collisions, so an ambiguous fallback is censored rather than
187
+ * attributed to the wrong event.
188
+ */
189
+ export async function recordFirstUnsafeVisibleRead(pipelineDatabase, materializedViewDatabase, input) {
190
+ if (input.suid.length === 0 || input.viewId.length === 0)
191
+ return false;
192
+ const receiptRows = await materializedViewDatabase.prepare(`
193
+ SELECT event_id, suid
194
+ FROM mv_unsafe_receipts
195
+ WHERE service_id = ? AND view_id = ? AND suid COLLATE BINARY = ? COLLATE BINARY
196
+ ORDER BY observed_at ASC, event_id COLLATE BINARY ASC
197
+ `).bind(input.serviceId, input.viewId, input.suid).all();
198
+ let eventId;
199
+ let suid = input.suid;
200
+ if (input.eventIdHint !== undefined && receiptRows.results.some((row) => row.event_id === input.eventIdHint)) {
201
+ eventId = input.eventIdHint;
202
+ }
203
+ else if (receiptRows.results.length === 1) {
204
+ eventId = asString(receiptRows.results[0]?.event_id);
205
+ suid = asString(receiptRows.results[0]?.suid) ?? input.suid;
206
+ }
207
+ let attemptId;
208
+ if (eventId === undefined) {
209
+ const sourceRows = await pipelineDatabase.prepare(`
210
+ SELECT event."Id" AS event_id, event."SortableUniqueId" AS suid, ops."AttemptId" AS attempt_id
211
+ FROM dcb_events AS event
212
+ JOIN dcb_event_ops AS ops
213
+ ON ops."ServiceId" = event."ServiceId" AND ops."Id" = event."Id"
214
+ WHERE event."ServiceId" = ?
215
+ AND event."SortableUniqueId" COLLATE BINARY = ? COLLATE BINARY
216
+ ORDER BY event."Id" COLLATE BINARY ASC
217
+ `).bind(input.serviceId, input.suid).all();
218
+ if (sourceRows.results.length !== 1)
219
+ return false;
220
+ const source = sourceRows.results[0];
221
+ eventId = asString(source?.event_id);
222
+ suid = asString(source?.suid) ?? input.suid;
223
+ attemptId = asString(source?.attempt_id);
224
+ }
225
+ if (eventId === undefined)
226
+ return false;
227
+ if (attemptId === undefined) {
228
+ const source = await pipelineDatabase.prepare(`
229
+ SELECT "AttemptId" AS attempt_id
230
+ FROM dcb_event_ops
231
+ WHERE "ServiceId" = ? AND "Id" = ?
232
+ `).bind(input.serviceId, eventId).first();
233
+ attemptId = asString(source?.attempt_id);
234
+ }
235
+ if (attemptId === undefined)
236
+ return false;
237
+ await recordDurableHop(pipelineDatabase, {
238
+ stage: "first-unsafe-visible-read",
239
+ serviceId: input.serviceId,
240
+ eventId,
241
+ suid,
242
+ attemptId,
243
+ viewId: input.viewId,
244
+ transport: "public-read",
245
+ observedAt: input.observedAt,
246
+ });
247
+ return true;
248
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * SDT-G65 admission observations.
3
+ *
4
+ * This table is diagnostic only. It is written through waitUntil after the
5
+ * bounded derived attempt and is never consulted by commit, Queue, safe-lane,
6
+ * or projection code.
7
+ */
8
+ export declare const G65_ADMISSION_CLOCK_ORIGIN: "Date.now epoch ms";
9
+ export declare const G65_ADMISSION_OUTCOMES: readonly ["admitted", "not-admitted", "unknown"];
10
+ export type G65AdmissionOutcome = typeof G65_ADMISSION_OUTCOMES[number];
11
+ export interface G65AdmissionAttemptObservation {
12
+ readonly serviceId: string;
13
+ readonly eventId: string;
14
+ readonly suid: string;
15
+ readonly attemptId: string;
16
+ readonly partitionTag: string;
17
+ readonly deliverySource: "fast";
18
+ readonly admissionStartedAt: number;
19
+ readonly admissionFinishedAt: number;
20
+ readonly outcome: G65AdmissionOutcome;
21
+ readonly globalCompletionObservedAt: number | null;
22
+ }
23
+ export declare function recordG65AdmissionAttempt(database: D1Database, input: G65AdmissionAttemptObservation): Promise<void>;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * SDT-G65 admission observations.
3
+ *
4
+ * This table is diagnostic only. It is written through waitUntil after the
5
+ * bounded derived attempt and is never consulted by commit, Queue, safe-lane,
6
+ * or projection code.
7
+ */
8
+ export const G65_ADMISSION_CLOCK_ORIGIN = "Date.now epoch ms";
9
+ export const G65_ADMISSION_OUTCOMES = ["admitted", "not-admitted", "unknown"];
10
+ function nonEmpty(value, field) {
11
+ if (value.length === 0)
12
+ throw new Error(`G65 admission ${field} must be non-empty`);
13
+ }
14
+ function validate(input) {
15
+ nonEmpty(input.serviceId, "serviceId");
16
+ nonEmpty(input.eventId, "eventId");
17
+ nonEmpty(input.suid, "suid");
18
+ nonEmpty(input.attemptId, "attemptId");
19
+ nonEmpty(input.partitionTag, "partitionTag");
20
+ if (!Number.isSafeInteger(input.admissionStartedAt) || input.admissionStartedAt < 0) {
21
+ throw new Error("G65 admission start must be a non-negative safe integer");
22
+ }
23
+ if (!Number.isSafeInteger(input.admissionFinishedAt) || input.admissionFinishedAt < input.admissionStartedAt) {
24
+ throw new Error("G65 admission finish must be after start");
25
+ }
26
+ if (!G65_ADMISSION_OUTCOMES.includes(input.outcome)) {
27
+ throw new Error(`G65 admission outcome is invalid: ${input.outcome}`);
28
+ }
29
+ if (input.globalCompletionObservedAt !== null &&
30
+ (!Number.isSafeInteger(input.globalCompletionObservedAt) || input.globalCompletionObservedAt < input.admissionStartedAt)) {
31
+ throw new Error("G65 global completion observation is invalid");
32
+ }
33
+ }
34
+ export async function recordG65AdmissionAttempt(database, input) {
35
+ validate(input);
36
+ await database.prepare(`
37
+ INSERT OR IGNORE INTO serialized_dcb_g65_admission_attempts
38
+ (service_id, event_id, suid, attempt_id, partition_tag, delivery_source,
39
+ admission_started_at, admission_finished_at, outcome,
40
+ global_completion_observed_at, clock_origin)
41
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
42
+ `).bind(input.serviceId, input.eventId, input.suid, input.attemptId, input.partitionTag, input.deliverySource, input.admissionStartedAt, input.admissionFinishedAt, input.outcome, input.globalCompletionObservedAt, G65_ADMISSION_CLOCK_ORIGIN).run();
43
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * SDT-G65 AC0 direct-doorbell RING/APPLY ledger.
3
+ *
4
+ * A ring is a durable receiver acceptance of the immutable Queue envelope.
5
+ * Only the ring insert is on the caller's RPC path. The receiver reads the
6
+ * retained bytes in its own execution context and runs the existing delivery
7
+ * core asynchronously; this ledger is never an admission, retry, projection,
8
+ * or public-response decision.
9
+ */
10
+ import type { DownstreamOutboxMessage } from "../downstream/types";
11
+ export declare const G65_DIRECT_RING_BUDGET_MS = 100;
12
+ export declare const G65_DIRECT_RING_CLOCK_ORIGIN: "Date.now epoch ms";
13
+ export type G65DirectRingOutcome = "rung" | "duplicate";
14
+ export type G65DirectApplyOutcome = "applied" | "duplicate" | "failed";
15
+ export interface G65DirectRingRecord {
16
+ readonly serviceId: string;
17
+ readonly eventId: string;
18
+ readonly suid: string;
19
+ readonly attemptId: string;
20
+ readonly partitionTag: string;
21
+ readonly messageJson: string;
22
+ readonly ringStartedAt: number;
23
+ readonly ringFinishedAt: number;
24
+ readonly ringOutcome: G65DirectRingOutcome;
25
+ }
26
+ /** Persist the exact immutable envelope before returning the receiver RPC. */
27
+ export declare function recordG65DirectRing(database: D1Database, message: DownstreamOutboxMessage, ringStartedAt: number): Promise<G65DirectRingOutcome>;
28
+ /** Read the exact bytes retained by the receiver ring. */
29
+ export declare function readG65DirectRing(database: D1Database, message: Pick<DownstreamOutboxMessage, "serviceId" | "eventId" | "attemptId">): Promise<DownstreamOutboxMessage | undefined>;
30
+ /** Set the first apply start boundary; duplicate apply attempts do not rewrite it. */
31
+ export declare function markG65DirectApplyStarted(database: D1Database, message: Pick<DownstreamOutboxMessage, "serviceId" | "eventId" | "attemptId">, observedAt: number): Promise<void>;
32
+ /** Set the first terminal apply outcome and timing. */
33
+ export declare function markG65DirectApplyFinished(database: D1Database, message: Pick<DownstreamOutboxMessage, "serviceId" | "eventId" | "attemptId">, observedAt: number, outcome: G65DirectApplyOutcome, error?: string): Promise<void>;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * SDT-G65 AC0 direct-doorbell RING/APPLY ledger.
3
+ *
4
+ * A ring is a durable receiver acceptance of the immutable Queue envelope.
5
+ * Only the ring insert is on the caller's RPC path. The receiver reads the
6
+ * retained bytes in its own execution context and runs the existing delivery
7
+ * core asynchronously; this ledger is never an admission, retry, projection,
8
+ * or public-response decision.
9
+ */
10
+ export const G65_DIRECT_RING_BUDGET_MS = 100;
11
+ export const G65_DIRECT_RING_CLOCK_ORIGIN = "Date.now epoch ms";
12
+ function nonEmpty(value, field) {
13
+ if (value.length === 0)
14
+ throw new Error(`G65 direct ring ${field} must be non-empty`);
15
+ }
16
+ function validateMessage(message) {
17
+ nonEmpty(message.serviceId, "serviceId");
18
+ nonEmpty(message.eventId, "eventId");
19
+ nonEmpty(message.suid, "suid");
20
+ nonEmpty(message.attemptId, "attemptId");
21
+ nonEmpty(message.tag, "partitionTag");
22
+ }
23
+ function validateTimestamp(value, field) {
24
+ if (!Number.isSafeInteger(value) || value < 0)
25
+ throw new Error(`G65 direct ring ${field} must be a non-negative safe integer`);
26
+ }
27
+ /** Persist the exact immutable envelope before returning the receiver RPC. */
28
+ export async function recordG65DirectRing(database, message, ringStartedAt) {
29
+ validateMessage(message);
30
+ validateTimestamp(ringStartedAt, "ring start");
31
+ const result = await database.prepare(`
32
+ INSERT OR IGNORE INTO serialized_dcb_g65_direct_rings
33
+ (service_id, event_id, suid, attempt_id, partition_tag, message_json,
34
+ ring_started_at, ring_finished_at, ring_outcome, apply_outcome)
35
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'rung', NULL)
36
+ `).bind(message.serviceId, message.eventId, message.suid, message.attemptId, message.tag, JSON.stringify(message), ringStartedAt, ringStartedAt).run();
37
+ const changes = typeof result.meta?.changes === "number" ? result.meta.changes : undefined;
38
+ if (changes === 0)
39
+ return "duplicate";
40
+ const ringFinishedAt = Date.now();
41
+ validateTimestamp(ringFinishedAt, "ring finish");
42
+ if (ringFinishedAt < ringStartedAt)
43
+ throw new Error("G65 direct ring finish precedes start");
44
+ await database.prepare(`
45
+ UPDATE serialized_dcb_g65_direct_rings
46
+ SET ring_finished_at = ?
47
+ WHERE service_id = ? AND event_id = ? AND attempt_id = ?
48
+ `).bind(ringFinishedAt, message.serviceId, message.eventId, message.attemptId).run();
49
+ return "rung";
50
+ }
51
+ /** Read the exact bytes retained by the receiver ring. */
52
+ export async function readG65DirectRing(database, message) {
53
+ const row = await database.prepare(`
54
+ SELECT message_json
55
+ FROM serialized_dcb_g65_direct_rings
56
+ WHERE service_id = ? AND event_id = ? AND attempt_id = ?
57
+ `).bind(message.serviceId, message.eventId, message.attemptId).first();
58
+ if (typeof row?.message_json !== "string")
59
+ return undefined;
60
+ const parsed = JSON.parse(row.message_json);
61
+ validateMessage(parsed);
62
+ return parsed;
63
+ }
64
+ /** Set the first apply start boundary; duplicate apply attempts do not rewrite it. */
65
+ export async function markG65DirectApplyStarted(database, message, observedAt) {
66
+ validateTimestamp(observedAt, "apply start");
67
+ await database.prepare(`
68
+ UPDATE serialized_dcb_g65_direct_rings
69
+ SET apply_started_at = COALESCE(apply_started_at, ?)
70
+ WHERE service_id = ? AND event_id = ? AND attempt_id = ?
71
+ `).bind(observedAt, message.serviceId, message.eventId, message.attemptId).run();
72
+ }
73
+ /** Set the first terminal apply outcome and timing. */
74
+ export async function markG65DirectApplyFinished(database, message, observedAt, outcome, error) {
75
+ validateTimestamp(observedAt, "apply finish");
76
+ if (outcome.length === 0)
77
+ throw new Error("G65 direct apply outcome must be non-empty");
78
+ await database.prepare(`
79
+ UPDATE serialized_dcb_g65_direct_rings
80
+ SET apply_finished_at = COALESCE(apply_finished_at, ?),
81
+ apply_outcome = COALESCE(apply_outcome, ?),
82
+ apply_error = COALESCE(apply_error, ?)
83
+ WHERE service_id = ? AND event_id = ? AND attempt_id = ?
84
+ `).bind(observedAt, outcome, error ?? null, message.serviceId, message.eventId, message.attemptId).run();
85
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * SDT-G69 append-only delivery-attempt evidence.
3
+ *
4
+ * This ledger is diagnostic only. DeliveryCore, Queue disposition, G44
5
+ * coverage, and the safe frontier must never read it. A row represents one
6
+ * call to D1EventStore.recordDelivery, including an idempotent replay or a
7
+ * typed delivery failure when the diagnostic table itself is available.
8
+ */
9
+ import type { DeliverySource } from "../downstream/types";
10
+ export type G69AdmissionAttemptStatus = "stored" | "duplicate" | "suid-collision" | "lineage-mismatch" | "failed";
11
+ export interface G69AdmissionAttemptReceipt {
12
+ readonly serviceId: string;
13
+ readonly eventId: string;
14
+ readonly suid: string;
15
+ readonly tag: string;
16
+ readonly deliverySource: DeliverySource;
17
+ /** Platform Queue wrapper identity; null when the transport did not expose one. */
18
+ readonly queueMessageId: string | null;
19
+ readonly attemptId: string;
20
+ readonly allocatorLineageId: string;
21
+ readonly obligationSequence: number;
22
+ readonly enqueuedAt: number;
23
+ readonly observedAt: number;
24
+ readonly arrivedAt: number;
25
+ readonly firstArrivedAtBefore: number | null;
26
+ readonly lastArrivedAtBefore: number | null;
27
+ readonly firstArrivedAtAfter: number | null;
28
+ readonly lastArrivedAtAfter: number | null;
29
+ /** Timestamp at which the diagnostic before-read completed, if observed. */
30
+ readonly beforeObservedAt: number | null;
31
+ /** Timestamp captured immediately before the diagnostic after-read. */
32
+ readonly afterObservedAt: number | null;
33
+ /** Relates observations to the actual admission mutation, or stays unknown. */
34
+ readonly observationConsistency: "before-admission" | "before-admission-absent" | "after-admission" | "before-read-failed" | "after-read-failed" | "unverified";
35
+ /** Mutation-owned classification; never inferred from the diagnostic read. */
36
+ readonly mutationEvidence: "first-admission" | "duplicate-admission" | "unverified";
37
+ /** Elapsed diagnostic/receipt preparation cost before the append is issued. */
38
+ readonly diagnosticDurationMs: number;
39
+ readonly status: G69AdmissionAttemptStatus;
40
+ readonly retryReason: string | null;
41
+ }
42
+ /** Best-effort because evidence availability cannot change delivery semantics. */
43
+ export declare function appendG69AdmissionAttempt(database: D1Database, receipt: G69AdmissionAttemptReceipt): Promise<void>;
@@ -0,0 +1,22 @@
1
+ /** Best-effort because evidence availability cannot change delivery semantics. */
2
+ export async function appendG69AdmissionAttempt(database, receipt) {
3
+ const insert = database.prepare(`INSERT INTO serialized_dcb_g69_admission_attempts
4
+ (service_id, event_id, suid, partition_tag, delivery_source,
5
+ queue_message_id, attempt_id, allocator_lineage_id, obligation_sequence,
6
+ enqueued_at, observed_at, arrived_at,
7
+ first_arrived_at_before, last_arrived_at_before,
8
+ first_arrived_at_after, last_arrived_at_after,
9
+ before_observed_at, after_observed_at, observation_consistency,
10
+ mutation_evidence, diagnostic_duration_ms, receipt_status, retry_reason, clock_origin)
11
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(receipt.serviceId, receipt.eventId, receipt.suid, receipt.tag, receipt.deliverySource, receipt.queueMessageId, receipt.attemptId, receipt.allocatorLineageId, receipt.obligationSequence, receipt.enqueuedAt, receipt.observedAt, receipt.arrivedAt, receipt.firstArrivedAtBefore, receipt.lastArrivedAtBefore, receipt.firstArrivedAtAfter, receipt.lastArrivedAtAfter, receipt.beforeObservedAt, receipt.afterObservedAt, receipt.observationConsistency, receipt.mutationEvidence, receipt.diagnosticDurationMs, receipt.status, receipt.retryReason, "Date.now epoch ms");
12
+ // Keep diagnostic history bounded per service. The receipt is deliberately
13
+ // outside core admission, so inability to retain it never changes Queue
14
+ // disposition or the public commit/delivery result.
15
+ const trim = database.prepare(`DELETE FROM serialized_dcb_g69_admission_attempts
16
+ WHERE service_id = ?
17
+ AND sequence NOT IN (
18
+ SELECT sequence FROM serialized_dcb_g69_admission_attempts
19
+ WHERE service_id = ? ORDER BY sequence DESC LIMIT 512
20
+ )`).bind(receipt.serviceId, receipt.serviceId);
21
+ await database.batch([insert, trim]);
22
+ }