@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,1008 @@
1
+ import commitTraceManifestJson from "../../../../contracts/commit-trace-manifest.json";
2
+ import { calculateUnattributedRatio } from "./AttributionRatio";
3
+ const NOOP_NATIVE_TRACE_SPAN = Object.freeze({
4
+ isTraced: false,
5
+ setAttribute: () => undefined,
6
+ });
7
+ /**
8
+ * The portable runtime must remain importable from Node-side provider and
9
+ * contract tooling. Cloudflare-only entrypoints inject the real tracer;
10
+ * this adapter preserves the trace-only, fail-open contract everywhere else.
11
+ */
12
+ export const noOpNativeTracing = Object.freeze({
13
+ enterSpan(_name, callback, ...args) {
14
+ return callback(NOOP_NATIVE_TRACE_SPAN, ...args);
15
+ },
16
+ });
17
+ /**
18
+ * A request handler can use the platform context without importing the
19
+ * Cloudflare-only module into the portable Node/provider graph.
20
+ */
21
+ export function nativeTracingFromContext(context) {
22
+ const native = context?.tracing;
23
+ return native ?? noOpNativeTracing;
24
+ }
25
+ const manifest = commitTraceManifestJson;
26
+ const DEFAULT_CLOCK = { now: () => Date.now() };
27
+ const RAW_TAG_KEY = /(^|[._])tag($|[._])/i;
28
+ const HEX64 = /^[0-9a-f]{64}$/;
29
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
30
+ const NATIVE_ROW_ID_ATTRIBUTE = "sdt.row.id";
31
+ function hasOwn(value, key) {
32
+ return Object.prototype.hasOwnProperty.call(value, key);
33
+ }
34
+ function rowFor(schema, rowId) {
35
+ const schemaRows = manifest.schemas[schema]?.rows;
36
+ const row = schemaRows?.find((entry) => entry.rowId === rowId);
37
+ if (row === undefined) {
38
+ throw new Error(`Commit trace row ${rowId} is not defined by ${schema}`);
39
+ }
40
+ return row;
41
+ }
42
+ /**
43
+ * G30's emitted-row inventory is deliberately limited to rows emitted from
44
+ * the primary Worker process. It is an observation-side discriminator, not a
45
+ * new trace attribute or a cross-actor propagation mechanism.
46
+ */
47
+ function isWorkerLocalEmitter(row) {
48
+ return row.emitter === "root-worker" || row.emitter === "caller-worker";
49
+ }
50
+ function faceState(attribute, face) {
51
+ const matrix = manifest.attributeMatrix.attributes[attribute];
52
+ if (matrix === undefined) {
53
+ throw new Error(`Commit trace attribute ${attribute} is not declared by the authority matrix`);
54
+ }
55
+ const state = matrix.faces[face];
56
+ if (state !== "required" && state !== "optional" && state !== "forbidden") {
57
+ throw new Error(`Commit trace matrix omits face ${face} for ${attribute}`);
58
+ }
59
+ return state;
60
+ }
61
+ function rowScopedState(declaration, face, rowId) {
62
+ if (declaration.rowScope !== undefined && !declaration.rowScope.includes(rowId))
63
+ return "forbidden";
64
+ return declaration.faces[face];
65
+ }
66
+ function asTraceValue(value, name) {
67
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean")
68
+ return value;
69
+ throw new Error(`Commit trace attribute ${name} must be scalar`);
70
+ }
71
+ /**
72
+ * Keep the callback-boundary emission path subject to the same typed matrix
73
+ * as the in-memory/export verifiers. A malformed observation remains
74
+ * fail-open for the commit itself, but it must never be emitted as a
75
+ * schema-conformant custom span.
76
+ */
77
+ function assertAttributeValue(value, declaration, key) {
78
+ const type = declaration.type;
79
+ if (type === "hex64") {
80
+ if (typeof value !== "string" || !HEX64.test(value))
81
+ throw new Error(`Commit trace attribute ${key} must be lower-case 64-hex`);
82
+ return;
83
+ }
84
+ if (type === "uuid") {
85
+ if (typeof value !== "string" || !UUID.test(value))
86
+ throw new Error(`Commit trace attribute ${key} must be a UUID`);
87
+ return;
88
+ }
89
+ if (type === "boolean") {
90
+ if (typeof value !== "boolean")
91
+ throw new Error(`Commit trace attribute ${key} must be boolean`);
92
+ return;
93
+ }
94
+ if (type === "integer" || type === "non-negative-integer") {
95
+ if (typeof value !== "number" || !Number.isInteger(value) || (type === "non-negative-integer" && value < 0)) {
96
+ throw new Error(`Commit trace attribute ${key} must be ${type}`);
97
+ }
98
+ return;
99
+ }
100
+ if (typeof value !== "string")
101
+ throw new Error(`Commit trace attribute ${key} must be a string`);
102
+ if (type === "enum-from-manifest-recovery") {
103
+ const reconcile = manifest.schemas["sdt.commit.reconcile/v1"];
104
+ const recoveryKinds = Object.keys(reconcile.recoveryDag ?? {});
105
+ const terminalAtEntry = reconcile.terminalAtEntryBoundary?.name;
106
+ if (!recoveryKinds.includes(value) && value !== terminalAtEntry) {
107
+ throw new Error(`Commit trace attribute ${key} is not a manifest recovery kind`);
108
+ }
109
+ return;
110
+ }
111
+ if (declaration.values !== undefined && !declaration.values.includes(value)) {
112
+ throw new Error(`Commit trace attribute ${key} is not manifest-declared`);
113
+ }
114
+ }
115
+ function assertAttributeTypes(attributes) {
116
+ for (const [key, value] of Object.entries(attributes)) {
117
+ const declaration = manifest.attributeMatrix.attributes[key];
118
+ if (declaration === undefined)
119
+ continue;
120
+ assertAttributeValue(value, declaration, key);
121
+ }
122
+ }
123
+ function assertNoRawTagAttributes(attributes) {
124
+ for (const [key, value] of Object.entries(attributes)) {
125
+ if (key === "tag.key_hash")
126
+ continue;
127
+ if (RAW_TAG_KEY.test(key)) {
128
+ throw new Error(`Commit trace forbids raw tag attribute ${key}=${String(value)}`);
129
+ }
130
+ }
131
+ }
132
+ /**
133
+ * Stable, non-authoritative, non-cryptographic observation key. Four
134
+ * independent FNV-1a lanes yield the lower-case 64-hex spelling mandated by
135
+ * the trace schema without making the key a protocol or storage authority.
136
+ */
137
+ export function stableTraceHash(value) {
138
+ const masks = [
139
+ 0xcbf29ce484222325n,
140
+ 0x9e3779b185ebca87n,
141
+ 0x84222325cbf29ce4n,
142
+ 0x100000001b3n,
143
+ ];
144
+ const prime = 0x100000001b3n;
145
+ const modulus = (1n << 64n) - 1n;
146
+ return masks.map((seed, index) => {
147
+ let hash = seed;
148
+ for (let offset = 0; offset < value.length; offset += 1) {
149
+ hash ^= BigInt(value.charCodeAt(offset) + index);
150
+ hash = (hash * prime) & modulus;
151
+ }
152
+ return hash.toString(16).padStart(16, "0");
153
+ }).join("");
154
+ }
155
+ /**
156
+ * The DO request bodies already carry attemptId, while G30 cannot add a
157
+ * correlation wire field. This deterministic, distinct observation key lets
158
+ * independently exported caller/callee spans be joined without treating
159
+ * attempt.id and correlation.id as the same attribute.
160
+ */
161
+ export function correlationIdForAttempt(attemptId) {
162
+ return `corr-${stableTraceHash(`sdt.commit/v1:${attemptId}`)}`;
163
+ }
164
+ export function createTraceCorrelationId() {
165
+ return crypto.randomUUID();
166
+ }
167
+ export function traceManifest() {
168
+ return manifest;
169
+ }
170
+ /**
171
+ * A scope is the test/evidence adapter's reflection of callback nesting. It
172
+ * does not accept a native span context or a parent span ID; Cloudflare owns
173
+ * real parentage through enterSpan's active async context.
174
+ */
175
+ export class CommitTraceScope {
176
+ trace;
177
+ state;
178
+ constructor(trace, state) {
179
+ this.trace = trace;
180
+ this.state = state;
181
+ }
182
+ accepted(attemptId) {
183
+ return new CommitTraceScope(this.trace, {
184
+ ...this.state,
185
+ face: "accepted",
186
+ attemptId,
187
+ });
188
+ }
189
+ withActor(actorClass, actorKey, clockDomain = this.state.clockDomain) {
190
+ return new CommitTraceScope(this.trace, {
191
+ ...this.state,
192
+ actorClass,
193
+ actorKey,
194
+ clockDomain,
195
+ });
196
+ }
197
+ fork() {
198
+ return new CommitTraceScope(this.trace, { ...this.state });
199
+ }
200
+ async span(rowId, options, callback) {
201
+ return this.trace.span(this.state, rowId, {
202
+ face: options.face ?? this.state.face,
203
+ actorClass: options.actorClass ?? this.state.actorClass,
204
+ ...(options.actorKey === undefined && this.state.actorKey === undefined ? {} : { actorKey: options.actorKey ?? this.state.actorKey }),
205
+ ...(options.attemptId === undefined && this.state.attemptId === undefined ? {} : { attemptId: options.attemptId ?? this.state.attemptId }),
206
+ clockDomain: options.clockDomain ?? this.state.clockDomain,
207
+ ...(options.outcome === undefined ? {} : { outcome: options.outcome }),
208
+ ...(options.httpStatus === undefined ? {} : { httpStatus: options.httpStatus }),
209
+ ...(options.phaseOrdinal === undefined ? {} : { phaseOrdinal: options.phaseOrdinal }),
210
+ ...(options.retryIndex === undefined ? {} : { retryIndex: options.retryIndex }),
211
+ ...(options.memberIndex === undefined ? {} : { memberIndex: options.memberIndex }),
212
+ ...(options.tag === undefined ? {} : { tag: options.tag }),
213
+ ...(options.attributes === undefined ? {} : { attributes: options.attributes }),
214
+ ...(options.zeroDurationPlatformLimited === undefined ? {} : { zeroDurationPlatformLimited: options.zeroDurationPlatformLimited }),
215
+ }, callback);
216
+ }
217
+ }
218
+ export class CommitTrace {
219
+ options;
220
+ spans = [];
221
+ emittedWorkerRows = new Set();
222
+ provider;
223
+ diagnostics;
224
+ correlationId;
225
+ activeRootId;
226
+ activeRootRecord;
227
+ activeRootNative;
228
+ nextRootIndex = 0;
229
+ runtimeVerification;
230
+ observationFailureCode;
231
+ constructor(options) {
232
+ this.options = options;
233
+ this.provider = Object.freeze({ ...(options.provider ?? {}) });
234
+ this.diagnostics = Object.freeze({ ...(options.diagnostics ?? {}) });
235
+ this.correlationId = options.correlationId;
236
+ }
237
+ async root(rowId, options, callback) {
238
+ if (this.activeRootId !== undefined) {
239
+ throw new Error("Commit trace root is already active");
240
+ }
241
+ const rootId = `${this.options.correlationId}:${this.nextRootIndex++}`;
242
+ const state = {
243
+ rootId,
244
+ currentRowId: null,
245
+ face: options.face ?? "pre-admission",
246
+ actorClass: options.actorClass ?? "ROOT",
247
+ ...(options.actorKey === undefined ? {} : { actorKey: options.actorKey }),
248
+ ...(options.attemptId === undefined ? {} : { attemptId: options.attemptId }),
249
+ clockDomain: options.clockDomain ?? "caller",
250
+ };
251
+ this.activeRootId = rootId;
252
+ try {
253
+ return await this.span(state, rowId, {
254
+ face: state.face,
255
+ actorClass: state.actorClass,
256
+ ...(state.actorKey === undefined ? {} : { actorKey: state.actorKey }),
257
+ ...(state.attemptId === undefined ? {} : { attemptId: state.attemptId }),
258
+ clockDomain: state.clockDomain,
259
+ ...(options.outcome === undefined ? {} : { outcome: options.outcome }),
260
+ ...(options.httpStatus === undefined ? {} : { httpStatus: options.httpStatus }),
261
+ ...(options.phaseOrdinal === undefined ? {} : { phaseOrdinal: options.phaseOrdinal }),
262
+ ...(options.retryIndex === undefined ? {} : { retryIndex: options.retryIndex }),
263
+ ...(options.memberIndex === undefined ? {} : { memberIndex: options.memberIndex }),
264
+ ...(options.tag === undefined ? {} : { tag: options.tag }),
265
+ ...(options.attributes === undefined ? {} : { attributes: options.attributes }),
266
+ ...(options.zeroDurationPlatformLimited === undefined ? {} : { zeroDurationPlatformLimited: options.zeroDurationPlatformLimited }),
267
+ }, async (scope) => {
268
+ const result = await callback(scope);
269
+ // This has the same containment rule as a Worker waitUntil task: it
270
+ // is spawned while the response is being finalized, but is never
271
+ // awaited by S00. The production entrypoint intentionally supplies
272
+ // no callback here; exporters may use it without touching protocol.
273
+ void this.scheduleDetachedObservation();
274
+ return result;
275
+ });
276
+ }
277
+ finally {
278
+ this.activeRootId = undefined;
279
+ this.activeRootRecord = undefined;
280
+ this.activeRootNative = undefined;
281
+ // A sink/exporter error is observation-only. It must not replace the
282
+ // response or durable outcome produced by the callback above.
283
+ this.publishSnapshot();
284
+ }
285
+ }
286
+ /**
287
+ * Returns the Worker-local rows for which a native tracing callback was
288
+ * actually entered on this request. This private-in-effect observation is
289
+ * copied only into sdt.observe/v1 by CommitWorker; it never changes the
290
+ * sealed trace schema, verifier, public response, or a control branch.
291
+ */
292
+ emittedWorkerRowIds() {
293
+ return Object.freeze([...this.emittedWorkerRows]);
294
+ }
295
+ snapshot() {
296
+ const rootId = this.spans.find((span) => span.logicalParent === null)?.rootId ?? `${this.correlationId}:none`;
297
+ const spans = Object.freeze(this.spans.map((span) => Object.freeze({
298
+ ...span,
299
+ attributes: Object.freeze({ ...span.attributes }),
300
+ })));
301
+ const accepted = this.options.schema === "sdt.commit/v1" && spans.some((span) => typeof span.attributes["attempt.id"] === "string");
302
+ const base = {
303
+ schema: this.options.schema,
304
+ rootId,
305
+ correlationId: this.correlationId,
306
+ serviceId: this.options.serviceId,
307
+ spans,
308
+ provider: this.provider,
309
+ diagnostics: this.diagnostics,
310
+ ...(this.runtimeVerification === undefined ? {} : { runtimeVerification: this.runtimeVerification }),
311
+ };
312
+ if (!accepted)
313
+ return Object.freeze(base);
314
+ try {
315
+ const unattributed = calculateUnattributedRatio(base);
316
+ return Object.freeze({
317
+ ...base,
318
+ unattributed,
319
+ attributionGatePassed: unattributed.unattributedRatio <= 0.05,
320
+ });
321
+ }
322
+ catch (error) {
323
+ // A malformed observation must be visible to evidence consumers, but
324
+ // its calculation cannot become a new commit failure.
325
+ this.noteObservationFailure(error);
326
+ return Object.freeze({
327
+ ...base,
328
+ runtimeVerification: this.observationVerification(),
329
+ });
330
+ }
331
+ }
332
+ /**
333
+ * A request becomes accepted after admission. Updating the still-active root
334
+ * preserves the single root correlation while S01 remains an honest
335
+ * pre-admission record with no attempt.id.
336
+ */
337
+ markAccepted(attemptId) {
338
+ if (this.activeRootRecord === undefined)
339
+ return;
340
+ const correlationId = correlationIdForAttempt(attemptId);
341
+ // S00 begins before admission, but the accepted trace's root is the
342
+ // accepted face: S01 is the separately retained pre-admission row. Keep
343
+ // the two phases distinct rather than leaking an attempt ID into a
344
+ // pre-admission record (or leaving the accepted root without one).
345
+ const attributes = {
346
+ ...this.activeRootRecord.attributes,
347
+ "correlation.id": correlationId,
348
+ "attempt.id": attemptId,
349
+ outcome: "accepted",
350
+ };
351
+ const record = Object.freeze({
352
+ ...this.activeRootRecord,
353
+ face: "accepted",
354
+ attributes: Object.freeze(attributes),
355
+ });
356
+ const index = this.spans.indexOf(this.activeRootRecord);
357
+ if (index >= 0)
358
+ this.spans[index] = record;
359
+ this.activeRootRecord = record;
360
+ this.correlationId = correlationId;
361
+ this.setNativeAttributes(this.activeRootNative, {
362
+ "correlation.id": correlationId,
363
+ "attempt.id": attemptId,
364
+ outcome: "accepted",
365
+ });
366
+ }
367
+ /**
368
+ * Observation-only consumers can read the post-admission correlation after
369
+ * it has been written to S00. This exposes no wire field and never controls
370
+ * a commit decision or native-parent relationship.
371
+ */
372
+ observationCorrelationId() {
373
+ return this.correlationId;
374
+ }
375
+ async span(state, rowId, options, callback) {
376
+ const disabledChild = () => new CommitTraceScope(this, {
377
+ rootId: state.rootId,
378
+ currentRowId: rowId,
379
+ face: options.face,
380
+ actorClass: options.actorClass,
381
+ ...(options.actorKey === undefined ? {} : { actorKey: options.actorKey }),
382
+ ...(options.attemptId === undefined ? {} : { attemptId: options.attemptId }),
383
+ clockDomain: options.clockDomain ?? state.clockDomain,
384
+ observationDisabled: true,
385
+ });
386
+ if (state.observationDisabled === true)
387
+ return callback(disabledChild());
388
+ let row;
389
+ let attributes;
390
+ try {
391
+ row = rowFor(this.options.schema, rowId);
392
+ if (row.logicalParent !== state.currentRowId) {
393
+ throw new Error(`Commit trace row ${rowId} must run under ${row.logicalParent ?? "a root"}, observed ${state.currentRowId ?? "a root"}`);
394
+ }
395
+ if (row.logicalParent === null && this.activeRootId !== state.rootId) {
396
+ throw new Error(`Commit trace root ${rowId} is not active`);
397
+ }
398
+ attributes = this.attributesFor(row, options);
399
+ }
400
+ catch (error) {
401
+ if (!this.isFailOpen())
402
+ throw error;
403
+ this.noteObservationFailure(error);
404
+ return callback(disabledChild());
405
+ }
406
+ const execute = async (native) => {
407
+ this.setNativeAttributes(native, nativeAttributesWithRowId(this.options.schema, rowId, attributes));
408
+ this.recordEmittedWorkerRow(row, native);
409
+ const startMs = this.now();
410
+ const record = {
411
+ rowId,
412
+ schema: this.options.schema,
413
+ face: options.face,
414
+ span: row.span,
415
+ emitter: row.emitter,
416
+ logicalParent: row.logicalParent,
417
+ rootId: state.rootId,
418
+ clockDomain: options.clockDomain ?? state.clockDomain,
419
+ startMs,
420
+ endMs: startMs,
421
+ present: true,
422
+ attributes: Object.freeze({ ...attributes }),
423
+ zeroDurationPlatformLimited: options.zeroDurationPlatformLimited ?? false,
424
+ };
425
+ this.spans.push(record);
426
+ if (row.logicalParent === null) {
427
+ this.activeRootRecord = record;
428
+ this.activeRootNative = native;
429
+ }
430
+ const child = new CommitTraceScope(this, {
431
+ rootId: state.rootId,
432
+ currentRowId: rowId,
433
+ face: options.face,
434
+ actorClass: options.actorClass,
435
+ ...(options.actorKey === undefined ? {} : { actorKey: options.actorKey }),
436
+ ...(options.attemptId === undefined ? {} : { attemptId: options.attemptId }),
437
+ clockDomain: options.clockDomain ?? state.clockDomain,
438
+ });
439
+ try {
440
+ const result = await callback(child);
441
+ const final = this.finish(record, native, this.outcomeFromResult(result, options.outcome, options.httpStatus));
442
+ if (row.logicalParent === null)
443
+ this.activeRootRecord = final;
444
+ return result;
445
+ }
446
+ catch (failure) {
447
+ const final = this.finish(record, native, {
448
+ outcome: options.outcome ?? "exception",
449
+ ...(options.httpStatus === undefined ? {} : { httpStatus: options.httpStatus }),
450
+ });
451
+ if (row.logicalParent === null)
452
+ this.activeRootRecord = final;
453
+ throw failure;
454
+ }
455
+ };
456
+ const native = this.options.nativeTracing;
457
+ return native === undefined
458
+ ? execute(undefined)
459
+ : this.enterNativeSpan(native, row.span, execute);
460
+ }
461
+ finish(existing, native, outcome) {
462
+ // markAccepted replaces the still-open S00 record so it can move from
463
+ // the pre-admission to accepted face. Finish the replacement rather than
464
+ // silently leaving its terminal outcome behind in the in-memory evidence
465
+ // adapter. Normal spans retain identity and follow the fast path.
466
+ const current = this.activeRootRecord !== undefined && existing.logicalParent === null
467
+ ? this.activeRootRecord
468
+ : existing;
469
+ const attributes = {
470
+ ...current.attributes,
471
+ ...(outcome?.outcome === undefined ? {} : { outcome: outcome.outcome }),
472
+ ...(outcome?.httpStatus === undefined ? {} : { "http.status": outcome.httpStatus }),
473
+ };
474
+ const endMs = this.now();
475
+ const next = Object.freeze({
476
+ ...current,
477
+ endMs,
478
+ // Cloudflare may quantize custom spans to a zero-length interval. The
479
+ // row remains present; duration is not used as a proxy for emission.
480
+ zeroDurationPlatformLimited: current.zeroDurationPlatformLimited || endMs === current.startMs,
481
+ attributes: Object.freeze(attributes),
482
+ });
483
+ const index = this.spans.indexOf(current);
484
+ if (index >= 0)
485
+ this.spans[index] = next;
486
+ this.setNativeAttributes(native, {
487
+ ...(outcome?.outcome === undefined ? {} : { outcome: outcome.outcome }),
488
+ ...(outcome?.httpStatus === undefined ? {} : { "http.status": outcome.httpStatus }),
489
+ });
490
+ return next;
491
+ }
492
+ recordEmittedWorkerRow(row, native) {
493
+ // `enterNativeSpan` calls this callback only after the platform has
494
+ // accepted the child callback. A declined/untraced span remains absent
495
+ // from the inventory so the exporter can distinguish local emission from
496
+ // a later provider-ingestion loss. DO and provider-subrequest rows are
497
+ // intentionally outside this Worker-local inventory.
498
+ if (native === undefined || native.isTraced === false || !isWorkerLocalEmitter(row))
499
+ return;
500
+ this.emittedWorkerRows.add(row.rowId);
501
+ }
502
+ attributesFor(row, options) {
503
+ const attributes = {
504
+ "schema.version": this.options.schema,
505
+ "correlation.id": this.correlationId,
506
+ "service.id": this.options.serviceId,
507
+ "actor.class": options.actorClass,
508
+ operation: row.span,
509
+ "span.kind": row.kind,
510
+ outcome: options.outcome ?? "in-progress",
511
+ };
512
+ if (options.actorKey !== undefined)
513
+ attributes["actor.key_hash"] = stableTraceHash(options.actorKey);
514
+ if (options.attemptId !== undefined)
515
+ attributes["attempt.id"] = options.attemptId;
516
+ if (options.phaseOrdinal !== undefined)
517
+ attributes["phase.ordinal"] = options.phaseOrdinal;
518
+ if (options.retryIndex !== undefined)
519
+ attributes["retry.index"] = options.retryIndex;
520
+ if (options.memberIndex !== undefined)
521
+ attributes["member.index"] = options.memberIndex;
522
+ if (options.tag !== undefined)
523
+ attributes["tag.key_hash"] = stableTraceHash(options.tag);
524
+ if (options.httpStatus !== undefined)
525
+ attributes["http.status"] = options.httpStatus;
526
+ if (this.provider.scriptVersion !== undefined)
527
+ attributes["script.version"] = this.provider.scriptVersion;
528
+ if (this.provider.colo !== undefined)
529
+ attributes.colo = this.provider.colo;
530
+ if (this.provider.placement !== undefined)
531
+ attributes.placement = this.provider.placement;
532
+ for (const [key, value] of Object.entries(options.attributes ?? {})) {
533
+ attributes[key] = asTraceValue(value, key);
534
+ }
535
+ assertNoRawTagAttributes(attributes);
536
+ for (const [attribute, declaration] of Object.entries(manifest.attributeMatrix.attributes)) {
537
+ const present = hasOwn(attributes, attribute);
538
+ const state = rowScopedState(declaration, options.face, row.rowId);
539
+ if (state === "required" && !present) {
540
+ throw new Error(`Commit trace row ${row.rowId} is missing required ${attribute} on ${options.face}`);
541
+ }
542
+ if (state === "forbidden" && present) {
543
+ throw new Error(`Commit trace row ${row.rowId} must not emit ${attribute} on ${options.face}`);
544
+ }
545
+ }
546
+ // Every emitted schema attribute must have an authority-matrix row,
547
+ // except diagnostics which intentionally live out-of-band in snapshots.
548
+ for (const attribute of Object.keys(attributes)) {
549
+ faceState(attribute, options.face);
550
+ }
551
+ assertAttributeTypes(attributes);
552
+ return attributes;
553
+ }
554
+ setNativeAttributes(native, attributes) {
555
+ if (native === undefined || native.isTraced === false)
556
+ return;
557
+ for (const [key, value] of Object.entries(attributes)) {
558
+ try {
559
+ native.setAttribute(key, value);
560
+ }
561
+ catch (error) {
562
+ this.noteObservationFailure(error);
563
+ }
564
+ }
565
+ }
566
+ outcomeFromResult(result, explicit, fallbackHttpStatus) {
567
+ const response = result instanceof Response
568
+ ? result
569
+ : typeof result === "object" && result !== null && "response" in result && result.response instanceof Response
570
+ ? result.response
571
+ : undefined;
572
+ return {
573
+ outcome: explicit ?? (response === undefined ? "success" : response.ok ? "success" : `http-${response.status}`),
574
+ ...(response === undefined ? fallbackHttpStatus === undefined ? {} : { httpStatus: fallbackHttpStatus } : { httpStatus: response.status }),
575
+ };
576
+ }
577
+ clock() {
578
+ return this.options.clock ?? DEFAULT_CLOCK;
579
+ }
580
+ now() {
581
+ try {
582
+ return this.clock().now();
583
+ }
584
+ catch (error) {
585
+ this.noteObservationFailure(error);
586
+ // The timestamp is evidence-only. Fall back to the process clock so
587
+ // a broken injected/provider clock cannot change the observed commit.
588
+ return Date.now();
589
+ }
590
+ }
591
+ publishSnapshot() {
592
+ let beforeVerification;
593
+ try {
594
+ beforeVerification = this.snapshot();
595
+ }
596
+ catch (error) {
597
+ this.noteObservationFailure(error);
598
+ return;
599
+ }
600
+ if (this.options.runtimeVerifier !== undefined) {
601
+ try {
602
+ if (this.observationFailureCode !== undefined) {
603
+ this.runtimeVerification = this.observationVerification();
604
+ }
605
+ else {
606
+ this.options.runtimeVerifier.verify(beforeVerification);
607
+ this.runtimeVerification = Object.freeze({ passed: true });
608
+ }
609
+ }
610
+ catch (error) {
611
+ // G30 observes the existing protocol. A trace defect must be retained
612
+ // for the sink/evidence lane but can never turn a successful commit
613
+ // into a new rejection or retry.
614
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string"
615
+ ? error.code
616
+ : "runtime-verifier-error";
617
+ this.runtimeVerification = Object.freeze({ passed: false, code });
618
+ }
619
+ }
620
+ try {
621
+ this.options.sink?.record(this.snapshot());
622
+ }
623
+ catch (error) {
624
+ // Export/sink loss is recorded in-process when a later observer asks
625
+ // for a snapshot, but it can never replace the application result.
626
+ this.noteObservationFailure(error);
627
+ }
628
+ }
629
+ /**
630
+ * A telemetry/export failure is retained as an observation failure, but a
631
+ * detached task is never allowed to hold the root open or replace the
632
+ * application's Response. Keeping this as a method makes the exact
633
+ * `void` call at root finalization mutation-testable.
634
+ */
635
+ async scheduleDetachedObservation() {
636
+ try {
637
+ await this.options.scheduleDetachedObservation?.();
638
+ }
639
+ catch (error) {
640
+ this.noteObservationFailure(error);
641
+ }
642
+ }
643
+ isFailOpen() {
644
+ return this.options.failOpen !== false;
645
+ }
646
+ observationVerification() {
647
+ return Object.freeze({ passed: false, code: this.observationFailureCode ?? "trace-observation-error" });
648
+ }
649
+ noteObservationFailure(error) {
650
+ if (this.observationFailureCode !== undefined)
651
+ return;
652
+ this.observationFailureCode = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string"
653
+ ? error.code
654
+ : "trace-observation-error";
655
+ this.runtimeVerification = this.observationVerification();
656
+ }
657
+ /**
658
+ * If a platform tracer declines a span before invoking its callback, run
659
+ * the application callback once without a span. Once the callback has
660
+ * started, its own error remains authoritative and is never replayed.
661
+ */
662
+ async enterNativeSpan(native, name, callback) {
663
+ let callbackStarted = false;
664
+ const wrapped = (span) => {
665
+ callbackStarted = true;
666
+ return callback(span);
667
+ };
668
+ try {
669
+ const result = native.enterSpan(name, wrapped);
670
+ if (!callbackStarted) {
671
+ this.noteObservationFailure(new Error("native tracer returned without invoking callback"));
672
+ return callback(undefined);
673
+ }
674
+ return await result;
675
+ }
676
+ catch (error) {
677
+ if (callbackStarted)
678
+ throw error;
679
+ this.noteObservationFailure(error);
680
+ return callback(undefined);
681
+ }
682
+ }
683
+ }
684
+ /**
685
+ * Constructor-only activation observation. It never touches Durable Object
686
+ * storage and callers must invoke beginHandler synchronously before awaiting.
687
+ */
688
+ export class DurableObjectActivation {
689
+ activationId = crypto.randomUUID();
690
+ constructedAtMs = Date.now();
691
+ first = true;
692
+ beginHandler() {
693
+ const first = this.first;
694
+ this.first = false;
695
+ const handlerStartedAtMs = Date.now();
696
+ return Object.freeze({
697
+ activationId: this.activationId,
698
+ first,
699
+ handlerStartedAtMs,
700
+ constructorToHandlerMs: Math.max(0, handlerStartedAtMs - this.constructedAtMs),
701
+ });
702
+ }
703
+ }
704
+ // Workerd forbids random generation while evaluating a module. Keep the
705
+ // isolate identity module-scoped once observed, but create it at the first
706
+ // request-handler boundary rather than during Worker startup.
707
+ let workerIsolateInstanceId;
708
+ let workerIsolateFirstInvocation = true;
709
+ export function beginWorkerInvocationObservation() {
710
+ workerIsolateInstanceId ??= crypto.randomUUID();
711
+ const firstInvocation = workerIsolateFirstInvocation;
712
+ workerIsolateFirstInvocation = false;
713
+ return Object.freeze({ isolateInstanceId: workerIsolateInstanceId, firstInvocation });
714
+ }
715
+ /**
716
+ * This deliberately accepts only an externally collected complete ledger.
717
+ * The DO runtime never records timestamps or idle observations in storage.
718
+ */
719
+ export function observedIdleGapLowerBoundMs(previous, currentStartMs) {
720
+ if (previous === undefined || !previous.complete || !Number.isFinite(previous.endMs) || !Number.isFinite(currentStartMs))
721
+ return null;
722
+ return Math.max(0, currentStartMs - previous.endMs);
723
+ }
724
+ export const IDLE_EXPERIMENT_SCHEDULE_MS = Object.freeze([2_000, 15_000, 180_000]);
725
+ export function classifyReactivationCause(evidence) {
726
+ if (evidence.deployedVersionChanged)
727
+ return "deployment-correlated";
728
+ if (typeof evidence.platformEvidence === "string" && evidence.platformEvidence.length > 0)
729
+ return "platform-evidenced";
730
+ return "unknown";
731
+ }
732
+ function assertNativeFaceAttributes(schema, face, rowId, attributes, options = {}) {
733
+ const row = rowFor(schema, rowId);
734
+ if (attributes.operation !== row.span) {
735
+ throw new Error(`Native commit trace row ${rowId} operation must equal ${row.span}`);
736
+ }
737
+ if (attributes["span.kind"] !== row.kind) {
738
+ throw new Error(`Native commit trace row ${rowId} span.kind must equal ${row.kind}`);
739
+ }
740
+ assertNoRawTagAttributes(attributes);
741
+ for (const [attribute, declaration] of Object.entries(manifest.attributeMatrix.attributes)) {
742
+ const present = hasOwn(attributes, attribute);
743
+ const state = rowScopedState(declaration, face, rowId);
744
+ if (state === "required" && !present && !(options.allowDeferredFactDerived === true && declaration.factDerived === true)) {
745
+ throw new Error(`Native commit trace row ${rowId} is missing required ${attribute} on ${face}`);
746
+ }
747
+ if (state === "forbidden" && present) {
748
+ throw new Error(`Native commit trace row ${rowId} must not emit ${attribute} on ${face}`);
749
+ }
750
+ }
751
+ for (const attribute of Object.keys(attributes)) {
752
+ faceState(attribute, face);
753
+ }
754
+ assertAttributeTypes(attributes);
755
+ }
756
+ function nativeAttributesAreValid(schema, face, rowId, attributes, options = {}) {
757
+ try {
758
+ assertNativeFaceAttributes(schema, face, rowId, attributes, options);
759
+ return true;
760
+ }
761
+ catch {
762
+ // The static manifest and mutation lanes make this observable. At a
763
+ // production callback boundary it must remain telemetry loss, never a
764
+ // new application rejection or alarm retry.
765
+ return false;
766
+ }
767
+ }
768
+ function nativeTracingEnabled(span) {
769
+ return span !== undefined && span.isTraced !== false;
770
+ }
771
+ function setNativeAttribute(span, key, value) {
772
+ if (!nativeTracingEnabled(span))
773
+ return;
774
+ try {
775
+ span.setAttribute(key, value);
776
+ }
777
+ catch {
778
+ // Span export is explicitly non-semantic.
779
+ }
780
+ }
781
+ function setNativeAttributes(span, attributes) {
782
+ for (const [key, value] of Object.entries(attributes))
783
+ setNativeAttribute(span, key, value);
784
+ }
785
+ /**
786
+ * `sdt.row.id` is an exporter-facing native-span discriminator, not a
787
+ * commit-snapshot attribute. Keep it outside the frozen authority matrix so
788
+ * it cannot alter validation, reservation, fence, response, or the in-memory
789
+ * evidence contract; add it only after that matrix has accepted the normal
790
+ * attributes for a real sdt.commit/v1 S-row.
791
+ */
792
+ function nativeAttributesWithRowId(schema, rowId, attributes) {
793
+ if (schema !== "sdt.commit/v1" || !/^S\d/.test(rowId))
794
+ return { ...attributes };
795
+ return { ...attributes, [NATIVE_ROW_ID_ATTRIBUTE]: rowId };
796
+ }
797
+ /**
798
+ * A tracer that fails before callback invocation simply loses this span. A
799
+ * callback that did begin is never run a second time: its normal result or
800
+ * error remains the protocol authority.
801
+ */
802
+ async function enterNativeSpanFailOpen(nativeTracing, name, callback) {
803
+ let callbackStarted = false;
804
+ const wrapped = (span) => {
805
+ callbackStarted = true;
806
+ return callback(span);
807
+ };
808
+ try {
809
+ const result = nativeTracing.enterSpan(name, wrapped);
810
+ if (!callbackStarted)
811
+ return callback(undefined);
812
+ return await result;
813
+ }
814
+ catch (error) {
815
+ if (callbackStarted)
816
+ throw error;
817
+ return callback(undefined);
818
+ }
819
+ }
820
+ /**
821
+ * Emit a real Cloudflare custom span in the currently active async context.
822
+ * There is intentionally no parent argument: Worker/DO parentage is the
823
+ * platform's concern, and this function only supplies manifest attributes.
824
+ */
825
+ export async function enterNativeCommitSpan(nativeTracing, name, input, callback) {
826
+ const attributes = {
827
+ "schema.version": input.schema,
828
+ "correlation.id": input.correlationId,
829
+ "service.id": input.serviceId,
830
+ "actor.class": input.actorClass,
831
+ "actor.key_hash": stableTraceHash(input.actorKey),
832
+ "activation.id": input.activation.activationId,
833
+ "activation.first": input.activation.first,
834
+ operation: input.operation,
835
+ "span.kind": input.kind,
836
+ outcome: "in-progress",
837
+ ...(input.attributes ?? {}),
838
+ };
839
+ if (input.attemptId !== undefined)
840
+ attributes["attempt.id"] = input.attemptId;
841
+ if (!nativeAttributesAreValid(input.schema, input.face, input.rowId, attributes)) {
842
+ return callback();
843
+ }
844
+ return enterNativeSpanFailOpen(nativeTracing, name, async (span) => {
845
+ setNativeAttributes(span, nativeAttributesWithRowId(input.schema, input.rowId, attributes));
846
+ try {
847
+ const result = await callback();
848
+ const response = result instanceof Response
849
+ ? result
850
+ : typeof result === "object" && result !== null && "response" in result && result.response instanceof Response
851
+ ? result.response
852
+ : undefined;
853
+ if (nativeTracingEnabled(span)) {
854
+ setNativeAttribute(span, "outcome", response === undefined ? "success" : response.ok ? "success" : `http-${response.status}`);
855
+ // Alarm-root rows have no HTTP response surface, and the manifest
856
+ // forbids http.status for that face even when a test harness happens
857
+ // to represent an internal result as Response.
858
+ if (response !== undefined && input.face !== "reconcile-root")
859
+ setNativeAttribute(span, "http.status", response.status);
860
+ }
861
+ return result;
862
+ }
863
+ catch (error) {
864
+ setNativeAttribute(span, "outcome", "exception");
865
+ throw error;
866
+ }
867
+ });
868
+ }
869
+ /**
870
+ * Emits S16 at the Durable Object handler boundary. Identity may only become
871
+ * available after a clone of the internal request body is decoded, but the
872
+ * custom span itself begins synchronously at handler entry. Failure to derive
873
+ * an internal identity merely leaves that non-commit actor invocation outside
874
+ * the sdt.commit universe; it never changes the request's control path.
875
+ */
876
+ export async function enterNativeActorHandleSpan(nativeTracing, input, resolveIdentity, callback) {
877
+ return enterNativeSpanFailOpen(nativeTracing, "actor.handle", async (span) => {
878
+ let identity;
879
+ try {
880
+ identity = await resolveIdentity();
881
+ }
882
+ catch {
883
+ // Observation is deliberately fail-open. Parsing a cloned body must
884
+ // never turn an internal actor request into a new rejection.
885
+ identity = undefined;
886
+ }
887
+ if (identity !== undefined) {
888
+ input.observation?.bindCorrelation(correlationIdForAttempt(identity.attemptId));
889
+ const attributes = {
890
+ "schema.version": "sdt.commit/v1",
891
+ "correlation.id": correlationIdForAttempt(identity.attemptId),
892
+ "attempt.id": identity.attemptId,
893
+ "service.id": identity.serviceId,
894
+ "actor.class": input.actorClass,
895
+ "actor.key_hash": stableTraceHash(identity.actorKey ?? input.actorKey),
896
+ "activation.id": input.activation.activationId,
897
+ "activation.first": input.activation.first,
898
+ operation: "actor.handle",
899
+ "span.kind": "callee",
900
+ outcome: "in-progress",
901
+ };
902
+ if (nativeAttributesAreValid("sdt.commit/v1", "accepted", "S16", attributes)) {
903
+ setNativeAttributes(span, nativeAttributesWithRowId("sdt.commit/v1", "S16", attributes));
904
+ }
905
+ }
906
+ try {
907
+ const result = await callback();
908
+ if (identity !== undefined && nativeTracingEnabled(span)) {
909
+ const response = result instanceof Response
910
+ ? result
911
+ : typeof result === "object" && result !== null && "response" in result && result.response instanceof Response
912
+ ? result.response
913
+ : undefined;
914
+ setNativeAttribute(span, "outcome", response === undefined ? "success" : response.ok ? "success" : `http-${response.status}`);
915
+ if (response !== undefined)
916
+ setNativeAttribute(span, "http.status", response.status);
917
+ }
918
+ return result;
919
+ }
920
+ catch (error) {
921
+ if (identity !== undefined)
922
+ setNativeAttribute(span, "outcome", "exception");
923
+ throw error;
924
+ }
925
+ finally {
926
+ // The structured log is emitted while the actor span is still active.
927
+ // Its existing attempt correlation is bound above; no header or request
928
+ // shape is added merely to label observation telemetry.
929
+ input.observation?.finish();
930
+ }
931
+ });
932
+ }
933
+ /**
934
+ * Emit R00 at actual alarm-handler entry. The Journal must read the durable
935
+ * record to discover the generation/attempt identity, so resolving it inside
936
+ * the active callback is the only way to keep both the entry boundary and
937
+ * Cloudflare-owned parentage without synthetic parent IDs.
938
+ */
939
+ export async function enterNativeReconcileRootSpan(nativeTracing, resolveIdentity, callback) {
940
+ return enterNativeSpanFailOpen(nativeTracing, "sdt.commit.reconcile", async (span) => {
941
+ let identity;
942
+ let attributes;
943
+ let recoveryKind;
944
+ const facts = {
945
+ setRecoveryKind: (kind) => {
946
+ recoveryKind = kind;
947
+ },
948
+ };
949
+ try {
950
+ identity = await resolveIdentity();
951
+ }
952
+ catch {
953
+ // A missing/corrupt Journal record cannot be turned into a different
954
+ // alarm outcome by attribution. The handler preserves its normal
955
+ // fail-closed path while this non-commit invocation remains unlabelled.
956
+ identity = undefined;
957
+ }
958
+ if (identity !== undefined) {
959
+ attributes = {
960
+ "schema.version": "sdt.commit.reconcile/v1",
961
+ "correlation.id": correlationIdForAttempt(identity.attemptId),
962
+ "attempt.id": identity.attemptId,
963
+ "service.id": identity.serviceId,
964
+ "actor.class": "JOURNAL",
965
+ "actor.key_hash": stableTraceHash(identity.actorKey),
966
+ "activation.id": identity.activation.activationId,
967
+ "activation.first": identity.activation.first,
968
+ operation: "sdt.commit.reconcile",
969
+ "span.kind": "root",
970
+ outcome: "in-progress",
971
+ "alarm.event.id": identity.alarmEventId,
972
+ "alarm.invocation.id": identity.invocationId,
973
+ "alarm.retryCount": identity.retryCount,
974
+ "alarm.isRetry": identity.isRetry,
975
+ "durable.prefix.at_entry": identity.prefixAtEntry,
976
+ };
977
+ // recovery.kind is the one fact-derived R00 attribute. It is checked
978
+ // after the recovery callback establishes the durable branch.
979
+ if (nativeAttributesAreValid("sdt.commit.reconcile/v1", "reconcile-root", "R00", attributes, { allowDeferredFactDerived: true })) {
980
+ setNativeAttributes(span, attributes);
981
+ }
982
+ }
983
+ const finish = (outcome) => {
984
+ if (identity === undefined || attributes === undefined || !nativeTracingEnabled(span))
985
+ return;
986
+ if (recoveryKind === undefined) {
987
+ // Trace-only behavior: an incomplete observation cannot alter the
988
+ // alarm result. The export verifier retains the missing required fact.
989
+ setNativeAttribute(span, "outcome", outcome);
990
+ return;
991
+ }
992
+ const completed = { ...attributes, "recovery.kind": recoveryKind, outcome };
993
+ if (!nativeAttributesAreValid("sdt.commit.reconcile/v1", "reconcile-root", "R00", completed))
994
+ return;
995
+ setNativeAttribute(span, "recovery.kind", recoveryKind);
996
+ setNativeAttribute(span, "outcome", outcome);
997
+ };
998
+ try {
999
+ const result = await callback(identity, facts);
1000
+ finish("success");
1001
+ return result;
1002
+ }
1003
+ catch (error) {
1004
+ finish("exception");
1005
+ throw error;
1006
+ }
1007
+ });
1008
+ }