@sekiban/dcb-runtime 0.2.0 → 0.2.2

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 (44) hide show
  1. package/README.md +32 -1
  2. package/dist/allocator/AllocatorDurableObject.js +25 -2
  3. package/dist/bootstrap/BootstrapCoordinatorDurableObject.d.ts +2 -0
  4. package/dist/bootstrap/BootstrapCoordinatorDurableObject.js +64 -1
  5. package/dist/bootstrap/OperatorBootstrap.d.ts +5 -1
  6. package/dist/bootstrap/OperatorBootstrap.js +17 -10
  7. package/dist/bootstrap/types.d.ts +6 -0
  8. package/dist/cli/OperatorRepairCli.d.ts +6 -1
  9. package/dist/cli/OperatorRepairCli.js +26 -6
  10. package/dist/cloudflare.d.ts +5 -0
  11. package/dist/cloudflare.js +1567 -465
  12. package/dist/commit/CommitWorker.d.ts +19 -0
  13. package/dist/commit/CommitWorker.js +176 -27
  14. package/dist/completeness/IncidentLifecycle.d.ts +75 -0
  15. package/dist/completeness/IncidentLifecycle.js +503 -0
  16. package/dist/completeness/types.d.ts +67 -0
  17. package/dist/cosmos.d.ts +34 -4
  18. package/dist/cosmos.js +214 -49
  19. package/dist/downstream/OutboxDrain.d.ts +4 -1
  20. package/dist/downstream/OutboxDrain.js +24 -2
  21. package/dist/generated/cosmos-layout.d.ts +87 -0
  22. package/dist/generated/cosmos-layout.js +99 -0
  23. package/dist/http/ConfiguredBearer.d.ts +12 -0
  24. package/dist/http/ConfiguredBearer.js +33 -0
  25. package/dist/http/IncidentMaintenance.d.ts +8 -0
  26. package/dist/http/IncidentMaintenance.js +124 -0
  27. package/dist/http/SerializedQueryWorker.js +37 -0
  28. package/dist/index.d.ts +13 -0
  29. package/dist/index.js +1627 -465
  30. package/dist/read/SerializedReadWorker.d.ts +3 -0
  31. package/dist/read/SerializedReadWorker.js +3 -3
  32. package/dist/shard/ShardRotation.d.ts +48 -0
  33. package/dist/shard/ShardRotation.js +120 -0
  34. package/dist/store/CosmosEventStore.js +56 -46
  35. package/dist/store/PostgresEventStore.d.ts +20 -0
  36. package/dist/store/PostgresEventStore.js +53 -0
  37. package/dist/store/provider.d.ts +4 -0
  38. package/dist/tag/TagDurableObject.js +66 -14
  39. package/dist/tag/TagSqlSchema.d.ts +6 -1
  40. package/dist/tag/TagSqlSchema.js +15 -0
  41. package/dist/tag/types.d.ts +9 -0
  42. package/dist/trace/CommitTrace.d.ts +3 -3
  43. package/dist/trace/CommitTraceVerifier.js +1 -1
  44. package/package.json +6 -15
package/dist/cosmos.js CHANGED
@@ -138,6 +138,104 @@ function decayedLagEstimateMs(estimateMs, observedAtMs, nowMs) {
138
138
  return Math.max(0, estimateMs - elapsed);
139
139
  }
140
140
 
141
+ // src/generated/cosmos-layout.ts
142
+ var COSMOS_BINDINGS = Object.freeze({
143
+ "endpoint": "COSMOS_ENDPOINT",
144
+ "database": "COSMOS_DATABASE",
145
+ "key": "COSMOS_KEY"
146
+ });
147
+ var COSMOS_CONTAINER_LAYOUT = Object.freeze({
148
+ "events": {
149
+ "name": "dcb-events",
150
+ "partitionKeyPath": "/pk",
151
+ "partitionValueKinds": [
152
+ "{serviceId}|{eventId}",
153
+ "{serviceId}|__dcb_event_ops__"
154
+ ],
155
+ "documentIds": {
156
+ "logicalEvent": "canonical event id",
157
+ "eventSidecar": 'safeId("event-ops", eventId)',
158
+ "serviceGuards": {
159
+ "allocatorLineageBinding": 'safeId("allocator-lineage-binding")',
160
+ "suidBinding": 'safeId("suid-binding", suid)',
161
+ "deliveryIncident": 'safeId("incident", identityKey)'
162
+ }
163
+ }
164
+ },
165
+ "lagEstimates": {
166
+ "name": "dcb-lag-estimates",
167
+ "partitionKeyPath": "/serviceId",
168
+ "partitionValueKinds": [
169
+ "{serviceId}"
170
+ ],
171
+ "documentIds": {
172
+ "lagEstimate": "serviceId"
173
+ }
174
+ },
175
+ "pendingArrivals": {
176
+ "name": "dcb-pending-arrivals",
177
+ "partitionKeyPath": "/serviceId",
178
+ "partitionValueKinds": [
179
+ "{serviceId}"
180
+ ],
181
+ "documentIds": {
182
+ "pendingArrival": "safeId(eventId)"
183
+ }
184
+ },
185
+ "findings": {
186
+ "name": "dcb-findings",
187
+ "partitionKeyPath": "/serviceId",
188
+ "partitionValueKinds": [
189
+ "{serviceId}"
190
+ ],
191
+ "documentIds": {
192
+ "finding": "safeId(eventId, path, classification)",
193
+ "incidentProjection": 'safeId("incident", identityKey)'
194
+ }
195
+ },
196
+ "checkpoints": {
197
+ "name": "dcb-projection-checkpoints",
198
+ "partitionKeyPath": "/serviceId",
199
+ "partitionValueKinds": [
200
+ "{serviceId}"
201
+ ],
202
+ "documentIds": {
203
+ "checkpoint": "safeId(projectionId)"
204
+ }
205
+ }
206
+ });
207
+ var COSMOS_DOCUMENT_ID_RULES = Object.freeze({
208
+ "events": {
209
+ "logicalEvent": "canonical event id",
210
+ "eventSidecar": 'safeId("event-ops", eventId)',
211
+ "serviceGuards": {
212
+ "allocatorLineageBinding": 'safeId("allocator-lineage-binding")',
213
+ "suidBinding": 'safeId("suid-binding", suid)',
214
+ "deliveryIncident": 'safeId("incident", identityKey)'
215
+ }
216
+ },
217
+ "lagEstimates": {
218
+ "lagEstimate": "serviceId"
219
+ },
220
+ "pendingArrivals": {
221
+ "pendingArrival": "safeId(eventId)"
222
+ },
223
+ "findings": {
224
+ "finding": "safeId(eventId, path, classification)",
225
+ "incidentProjection": 'safeId("incident", identityKey)'
226
+ },
227
+ "checkpoints": {
228
+ "checkpoint": "safeId(projectionId)"
229
+ }
230
+ });
231
+ var COSMOS_CONTAINER_NAMES = Object.freeze({
232
+ "events": "dcb-events",
233
+ "lagEstimates": "dcb-lag-estimates",
234
+ "pendingArrivals": "dcb-pending-arrivals",
235
+ "findings": "dcb-findings",
236
+ "checkpoints": "dcb-projection-checkpoints"
237
+ });
238
+
141
239
  // src/store/types.ts
142
240
  var CanonicalEventIdentityConflictError = class extends Error {
143
241
  constructor(provider, eventId, message) {
@@ -152,34 +250,32 @@ var CanonicalEventIdentityConflictError = class extends Error {
152
250
  };
153
251
 
154
252
  // src/store/CosmosEventStore.ts
155
- var COSMOS_EVENT_PARTITION_KEY_PATH = "/pk";
156
- var COSMOS_AUXILIARY_PARTITION_KEY_PATH = "/serviceId";
157
- var DEFAULT_COSMOS_CONTAINERS = Object.freeze({
158
- events: "dcb-events",
159
- lagEstimates: "dcb-lag-estimates",
160
- pendingArrivals: "dcb-pending-arrivals",
161
- findings: "dcb-findings",
162
- checkpoints: "dcb-projection-checkpoints"
163
- });
253
+ var COSMOS_EVENT_PARTITION_KEY_PATH = COSMOS_CONTAINER_LAYOUT.events.partitionKeyPath;
254
+ var COSMOS_AUXILIARY_PARTITION_KEY_PATH = COSMOS_CONTAINER_LAYOUT.lagEstimates.partitionKeyPath;
255
+ var DEFAULT_COSMOS_CONTAINERS = COSMOS_CONTAINER_NAMES;
164
256
  function cosmosContainerDefinitions(containers = DEFAULT_COSMOS_CONTAINERS) {
165
- return [
166
- {
167
- name: containers.events,
168
- partitionKeyPath: COSMOS_EVENT_PARTITION_KEY_PATH,
169
- // The C# CosmosEvent is partitioned by the exact serviceId|id pair.
170
- // Refuse a missing id here so an introspection fixture cannot mistake
171
- // an auxiliary service partition for the logical event partition.
172
- partitionKeyValue: (serviceId, id) => {
173
- if (typeof id !== "string" || id.length === 0) throw new Error("Cosmos event partition key requires an event id");
174
- return eventPk(serviceId, id);
175
- }
176
- },
177
- ...[containers.lagEstimates, containers.pendingArrivals, containers.findings, containers.checkpoints].map((name) => ({
257
+ return Object.keys(COSMOS_CONTAINER_LAYOUT).map((key) => {
258
+ const layout = COSMOS_CONTAINER_LAYOUT[key];
259
+ const name = containers[key];
260
+ if (key === "events") {
261
+ return {
262
+ name,
263
+ partitionKeyPath: layout.partitionKeyPath,
264
+ // The C# CosmosEvent is partitioned by the exact serviceId|id pair.
265
+ // Refuse a missing id here so an introspection fixture cannot mistake
266
+ // an auxiliary service partition for the logical event partition.
267
+ partitionKeyValue: (serviceId, id) => {
268
+ if (typeof id !== "string" || id.length === 0) throw new Error("Cosmos event partition key requires an event id");
269
+ return eventPk(serviceId, id);
270
+ }
271
+ };
272
+ }
273
+ return {
178
274
  name,
179
- partitionKeyPath: COSMOS_AUXILIARY_PARTITION_KEY_PATH,
275
+ partitionKeyPath: layout.partitionKeyPath,
180
276
  partitionKeyValue: (serviceId) => serviceId
181
- }))
182
- ];
277
+ };
278
+ });
183
279
  }
184
280
  var CosmosClientError = class extends Error {
185
281
  constructor(status, message) {
@@ -238,6 +334,19 @@ function compareCosmosSuid(left, right) {
238
334
  function safeId(...parts) {
239
335
  return parts.map((part) => encodeURIComponent(part)).join("~");
240
336
  }
337
+ function rulePrefix(rule, fallback) {
338
+ return rule.match(/^safeId\("([^"]+)"/)?.[1] ?? fallback;
339
+ }
340
+ var EVENT_OPS_ID_PREFIX = rulePrefix(COSMOS_DOCUMENT_ID_RULES.events.eventSidecar, "event-ops");
341
+ var LINEAGE_ID_PREFIX = rulePrefix(
342
+ COSMOS_DOCUMENT_ID_RULES.events.serviceGuards.allocatorLineageBinding,
343
+ "allocator-lineage-binding"
344
+ );
345
+ var SUID_ID_PREFIX = rulePrefix(COSMOS_DOCUMENT_ID_RULES.events.serviceGuards.suidBinding, "suid-binding");
346
+ var INCIDENT_ID_PREFIX = rulePrefix(
347
+ COSMOS_DOCUMENT_ID_RULES.events.serviceGuards.deliveryIncident,
348
+ "incident"
349
+ );
241
350
  function eventPk(serviceId, id) {
242
351
  return `${serviceId}|${id}`;
243
352
  }
@@ -503,19 +612,22 @@ ${date}
503
612
  ["sign"]
504
613
  );
505
614
  const signature = encodeURIComponent(`type=master&ver=1.0&sig=${base64(await crypto.subtle.sign("HMAC", cryptoKey, new TextEncoder().encode(signaturePayload)))}`);
506
- const response = await this.fetcher(new URL(path, this.endpoint), {
507
- method,
508
- headers: {
509
- accept: "application/json",
510
- "content-type": "application/json",
511
- "x-ms-date": date,
512
- "x-ms-version": "2018-12-31",
513
- authorization: signature,
514
- ...extraHeaders
515
- },
516
- body: body === void 0 ? void 0 : JSON.stringify(body)
517
- });
518
- return response;
615
+ try {
616
+ return await this.fetcher(new URL(path, this.endpoint), {
617
+ method,
618
+ headers: {
619
+ accept: "application/json",
620
+ "content-type": "application/json",
621
+ "x-ms-date": date,
622
+ "x-ms-version": "2018-12-31",
623
+ authorization: signature,
624
+ ...extraHeaders
625
+ },
626
+ body: body === void 0 ? void 0 : JSON.stringify(body)
627
+ });
628
+ } catch {
629
+ throw new CosmosClientError(0, "Cosmos request failed before a response was received");
630
+ }
519
631
  }
520
632
  async errorFrom(response, prefix) {
521
633
  return new CosmosClientError(response.status, `${prefix} (HTTP ${response.status})`);
@@ -793,7 +905,7 @@ var CosmosEventStore = class {
793
905
  this.ready();
794
906
  return await this.client.read(
795
907
  this.containers.events,
796
- safeId("incident", identityKey),
908
+ safeId(INCIDENT_ID_PREFIX, identityKey),
797
909
  auxiliaryPk(serviceId)
798
910
  ) !== void 0;
799
911
  }
@@ -825,7 +937,7 @@ var CosmosEventStore = class {
825
937
  }
826
938
  async bindLineage(serviceId, allocatorLineageId, boundAt) {
827
939
  const document = {
828
- id: safeId("allocator-lineage-binding"),
940
+ id: safeId(LINEAGE_ID_PREFIX),
829
941
  pk: auxiliaryPk(serviceId),
830
942
  serviceId,
831
943
  kind: "allocator-lineage-binding",
@@ -845,7 +957,7 @@ var CosmosEventStore = class {
845
957
  * mutation. Historical rows are still checked by eventDocuments above.
846
958
  */
847
959
  async reserveSuid(serviceId, suid, eventId) {
848
- const id = safeId("suid-binding", suid);
960
+ const id = safeId(SUID_ID_PREFIX, suid);
849
961
  const existing = await this.client.read(this.containers.events, id, auxiliaryPk(serviceId));
850
962
  if (existing !== void 0) return existing.document.eventId;
851
963
  const document = {
@@ -868,7 +980,7 @@ var CosmosEventStore = class {
868
980
  }
869
981
  async persistIncident(incident) {
870
982
  const document = {
871
- id: safeId("incident", incident.identityKey),
983
+ id: safeId(INCIDENT_ID_PREFIX, incident.identityKey),
872
984
  pk: auxiliaryPk(incident.serviceId),
873
985
  serviceId: incident.serviceId,
874
986
  kind: "delivery-incident",
@@ -891,7 +1003,7 @@ var CosmosEventStore = class {
891
1003
  }
892
1004
  async projectIncident(incident) {
893
1005
  const document = {
894
- id: safeId("incident", incident.identityKey),
1006
+ id: safeId(INCIDENT_ID_PREFIX, incident.identityKey),
895
1007
  serviceId: incident.serviceId,
896
1008
  eventId: incident.eventId ?? incident.incomingEventId ?? incident.identityKey,
897
1009
  path: incident.classification,
@@ -948,7 +1060,7 @@ var CosmosEventStore = class {
948
1060
  async eventOps(serviceId, eventId) {
949
1061
  const row = await this.client.read(
950
1062
  this.containers.events,
951
- safeId("event-ops", eventId),
1063
+ safeId(EVENT_OPS_ID_PREFIX, eventId),
952
1064
  eventPk(serviceId, eventId)
953
1065
  );
954
1066
  return row?.document;
@@ -993,7 +1105,7 @@ var CosmosEventStore = class {
993
1105
  throw new Error("Cosmos event CAS did not converge");
994
1106
  }
995
1107
  async mutateEventOps(message, arrivedAt, lagMs) {
996
- const id = safeId("event-ops", message.eventId);
1108
+ const id = safeId(EVENT_OPS_ID_PREFIX, message.eventId);
997
1109
  const pk = eventPk(message.serviceId, message.eventId);
998
1110
  const nextArrival = {
999
1111
  serviceId: message.serviceId,
@@ -1389,12 +1501,64 @@ function createBootstrapStoreAdapter(provider, store, identityGuard) {
1389
1501
  }
1390
1502
 
1391
1503
  // src/cosmos.ts
1392
- function createCosmosStoreProvider(config) {
1504
+ var CosmosConfigurationError = class extends Error {
1505
+ constructor(missingBindings, message) {
1506
+ super(message);
1507
+ this.missingBindings = missingBindings;
1508
+ this.name = "CosmosConfigurationError";
1509
+ }
1510
+ missingBindings;
1511
+ code = "COSMOS_CONFIGURATION_INVALID";
1512
+ };
1513
+ function bindingValue(value) {
1514
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
1515
+ }
1516
+ function invalidConfiguration(missingBindings) {
1517
+ return new CosmosConfigurationError(
1518
+ missingBindings,
1519
+ `Cosmos configuration is incomplete; missing bindings: ${missingBindings.join(", ")}`
1520
+ );
1521
+ }
1522
+ function resolveCosmosOptions(config, env) {
1523
+ const staticValues = {
1524
+ COSMOS_ENDPOINT: config.endpoint,
1525
+ COSMOS_DATABASE: config.database,
1526
+ COSMOS_KEY: config.key
1527
+ };
1528
+ const hasStaticValues = Object.values(staticValues).some((value) => value !== void 0);
1529
+ if (config.client !== void 0) {
1530
+ if (hasStaticValues) {
1531
+ throw invalidConfiguration(["client cannot be combined with COSMOS_ENDPOINT, COSMOS_DATABASE, or COSMOS_KEY"]);
1532
+ }
1533
+ return { ...config };
1534
+ }
1535
+ const values = hasStaticValues ? staticValues : {
1536
+ COSMOS_ENDPOINT: env.COSMOS_ENDPOINT,
1537
+ COSMOS_DATABASE: env.COSMOS_DATABASE,
1538
+ COSMOS_KEY: env.COSMOS_KEY
1539
+ };
1540
+ const missingBindings = Object.entries(values).filter(([, value]) => bindingValue(value) === void 0).map(([name]) => name);
1541
+ if (missingBindings.length > 0) throw invalidConfiguration(missingBindings);
1542
+ return {
1543
+ ...config,
1544
+ endpoint: bindingValue(values.COSMOS_ENDPOINT),
1545
+ database: bindingValue(values.COSMOS_DATABASE),
1546
+ key: bindingValue(values.COSMOS_KEY)
1547
+ };
1548
+ }
1549
+ function createCosmosStoreProvider(config = {}) {
1550
+ const hasStaticConfiguration = [config.endpoint, config.database, config.key].some((value) => value !== void 0);
1551
+ if (config.client !== void 0 || hasStaticConfiguration) resolveCosmosOptions(config, {});
1393
1552
  return Object.freeze({
1394
1553
  name: "cosmos",
1395
- isConfigured: () => true,
1396
- create: () => new CosmosEventStore(config),
1397
- createBootstrapAdapter: () => createCosmosBootstrapAdapter(new CosmosEventStore(config))
1554
+ isConfigured: (env) => {
1555
+ resolveCosmosOptions(config, env);
1556
+ return true;
1557
+ },
1558
+ create: (env) => new CosmosEventStore(resolveCosmosOptions(config, env)),
1559
+ createBootstrapAdapter: (env) => createCosmosBootstrapAdapter(
1560
+ new CosmosEventStore(resolveCosmosOptions(config, env))
1561
+ )
1398
1562
  });
1399
1563
  }
1400
1564
  function createCosmosBootstrapAdapter(store) {
@@ -1402,6 +1566,7 @@ function createCosmosBootstrapAdapter(store) {
1402
1566
  }
1403
1567
  export {
1404
1568
  CosmosClientError,
1569
+ CosmosConfigurationError,
1405
1570
  CosmosEventStore,
1406
1571
  CosmosRestClient,
1407
1572
  DEFAULT_COSMOS_CONTAINERS,
@@ -1,6 +1,7 @@
1
1
  import { type DownstreamOutboxMessage, type PipelineClock } from "./types";
2
+ import { type ServiceIdentityEnvironment, type ServiceIdentityProvider } from "../service/ServiceIdentityProvider";
2
3
  import type { G60DurableHopObserver } from "../diagnostics/G60DurableHop";
3
- export interface OutboxDrainEnv {
4
+ export interface OutboxDrainEnv extends ServiceIdentityEnvironment {
4
5
  TAG: DurableObjectNamespace;
5
6
  DOWNSTREAM_QUEUE: Queue<DownstreamOutboxMessage>;
6
7
  }
@@ -18,6 +19,8 @@ export interface OutboxDrainOptions {
18
19
  readonly acknowledgement?: "transport" | "global-receipt";
19
20
  /** G60 observation is scheduled by the active Worker context. */
20
21
  readonly durableHopObserver?: G60DurableHopObserver;
22
+ /** Caller/deployment authority; body.serviceId is never the naming authority. */
23
+ readonly serviceIdentityProvider?: ServiceIdentityProvider;
21
24
  }
22
25
  export interface DrainResult {
23
26
  serviceId: string;
@@ -1,5 +1,7 @@
1
1
  import { outboxIdentity, systemPipelineClock } from "./types";
2
2
  import { scopeIdFor } from "../scope/ScopeName";
3
+ import { scopeIdentityMissingResponse } from "../scope/ControlRouteScope";
4
+ import { envServiceIdentity, ServiceIdentityMissingError, } from "../service/ServiceIdentityProvider";
3
5
  function json(body, status = 200) {
4
6
  return new Response(JSON.stringify(body), {
5
7
  status,
@@ -110,10 +112,30 @@ export async function handleOutboxDrainRequest(request, env, options = {}) {
110
112
  if (!isDrainRequest(body)) {
111
113
  return json({ error: "serviceId and unique non-empty tags are required", code: "invalid_outbox_drain_request" }, 400);
112
114
  }
115
+ const requestedServiceId = body.serviceId;
116
+ const tags = body.tags;
117
+ const provider = options.serviceIdentityProvider ?? envServiceIdentity(env);
118
+ let authority;
119
+ try {
120
+ authority = provider.forRequest(request).serviceId;
121
+ }
122
+ catch (caught) {
123
+ if (caught instanceof ServiceIdentityMissingError)
124
+ return scopeIdentityMissingResponse();
125
+ throw caught;
126
+ }
127
+ // Body serviceId is caller-supplied input, not Durable Object naming authority.
128
+ if (requestedServiceId !== authority) {
129
+ console.warn({ schema: "sdt.scope/v1", code: "scope.mismatch", route: "outbox-drain" });
130
+ return json({
131
+ code: "scope.mismatch",
132
+ error: "The request service identity is not authorized for this deployment",
133
+ }, 403);
134
+ }
113
135
  try {
114
136
  const results = [];
115
- for (const tag of body.tags) {
116
- results.push(await drainTagOutbox({ serviceId: body.serviceId, tag }, env, systemPipelineClock, options));
137
+ for (const tag of tags) {
138
+ results.push(await drainTagOutbox({ serviceId: authority, tag }, env, systemPipelineClock, options));
117
139
  }
118
140
  return json({ results });
119
141
  }
@@ -0,0 +1,87 @@
1
+ export declare const COSMOS_LAYOUT_STABILITY: "experimental";
2
+ export declare const COSMOS_PROVIDER_ID: "cosmos";
3
+ export declare const COSMOS_BINDINGS: {
4
+ readonly endpoint: "COSMOS_ENDPOINT";
5
+ readonly database: "COSMOS_DATABASE";
6
+ readonly key: "COSMOS_KEY";
7
+ };
8
+ export declare const COSMOS_CONTAINER_LAYOUT: Readonly<{
9
+ readonly events: {
10
+ readonly name: "dcb-events";
11
+ readonly partitionKeyPath: "/pk";
12
+ readonly partitionValueKinds: readonly ["{serviceId}|{eventId}", "{serviceId}|__dcb_event_ops__"];
13
+ readonly documentIds: {
14
+ readonly logicalEvent: "canonical event id";
15
+ readonly eventSidecar: "safeId(\"event-ops\", eventId)";
16
+ readonly serviceGuards: {
17
+ readonly allocatorLineageBinding: "safeId(\"allocator-lineage-binding\")";
18
+ readonly suidBinding: "safeId(\"suid-binding\", suid)";
19
+ readonly deliveryIncident: "safeId(\"incident\", identityKey)";
20
+ };
21
+ };
22
+ };
23
+ readonly lagEstimates: {
24
+ readonly name: "dcb-lag-estimates";
25
+ readonly partitionKeyPath: "/serviceId";
26
+ readonly partitionValueKinds: readonly ["{serviceId}"];
27
+ readonly documentIds: {
28
+ readonly lagEstimate: "serviceId";
29
+ };
30
+ };
31
+ readonly pendingArrivals: {
32
+ readonly name: "dcb-pending-arrivals";
33
+ readonly partitionKeyPath: "/serviceId";
34
+ readonly partitionValueKinds: readonly ["{serviceId}"];
35
+ readonly documentIds: {
36
+ readonly pendingArrival: "safeId(eventId)";
37
+ };
38
+ };
39
+ readonly findings: {
40
+ readonly name: "dcb-findings";
41
+ readonly partitionKeyPath: "/serviceId";
42
+ readonly partitionValueKinds: readonly ["{serviceId}"];
43
+ readonly documentIds: {
44
+ readonly finding: "safeId(eventId, path, classification)";
45
+ readonly incidentProjection: "safeId(\"incident\", identityKey)";
46
+ };
47
+ };
48
+ readonly checkpoints: {
49
+ readonly name: "dcb-projection-checkpoints";
50
+ readonly partitionKeyPath: "/serviceId";
51
+ readonly partitionValueKinds: readonly ["{serviceId}"];
52
+ readonly documentIds: {
53
+ readonly checkpoint: "safeId(projectionId)";
54
+ };
55
+ };
56
+ }>;
57
+ export declare const COSMOS_DOCUMENT_ID_RULES: Readonly<{
58
+ readonly events: {
59
+ readonly logicalEvent: "canonical event id";
60
+ readonly eventSidecar: "safeId(\"event-ops\", eventId)";
61
+ readonly serviceGuards: {
62
+ readonly allocatorLineageBinding: "safeId(\"allocator-lineage-binding\")";
63
+ readonly suidBinding: "safeId(\"suid-binding\", suid)";
64
+ readonly deliveryIncident: "safeId(\"incident\", identityKey)";
65
+ };
66
+ };
67
+ readonly lagEstimates: {
68
+ readonly lagEstimate: "serviceId";
69
+ };
70
+ readonly pendingArrivals: {
71
+ readonly pendingArrival: "safeId(eventId)";
72
+ };
73
+ readonly findings: {
74
+ readonly finding: "safeId(eventId, path, classification)";
75
+ readonly incidentProjection: "safeId(\"incident\", identityKey)";
76
+ };
77
+ readonly checkpoints: {
78
+ readonly checkpoint: "safeId(projectionId)";
79
+ };
80
+ }>;
81
+ export declare const COSMOS_CONTAINER_NAMES: Readonly<{
82
+ readonly events: "dcb-events";
83
+ readonly lagEstimates: "dcb-lag-estimates";
84
+ readonly pendingArrivals: "dcb-pending-arrivals";
85
+ readonly findings: "dcb-findings";
86
+ readonly checkpoints: "dcb-projection-checkpoints";
87
+ }>;
@@ -0,0 +1,99 @@
1
+ // Generated by scripts/cosmos-layout-contract.mjs. Do not edit.
2
+ export const COSMOS_LAYOUT_STABILITY = "experimental";
3
+ export const COSMOS_PROVIDER_ID = "cosmos";
4
+ export const COSMOS_BINDINGS = Object.freeze({
5
+ "endpoint": "COSMOS_ENDPOINT",
6
+ "database": "COSMOS_DATABASE",
7
+ "key": "COSMOS_KEY"
8
+ });
9
+ export const COSMOS_CONTAINER_LAYOUT = Object.freeze({
10
+ "events": {
11
+ "name": "dcb-events",
12
+ "partitionKeyPath": "/pk",
13
+ "partitionValueKinds": [
14
+ "{serviceId}|{eventId}",
15
+ "{serviceId}|__dcb_event_ops__"
16
+ ],
17
+ "documentIds": {
18
+ "logicalEvent": "canonical event id",
19
+ "eventSidecar": "safeId(\"event-ops\", eventId)",
20
+ "serviceGuards": {
21
+ "allocatorLineageBinding": "safeId(\"allocator-lineage-binding\")",
22
+ "suidBinding": "safeId(\"suid-binding\", suid)",
23
+ "deliveryIncident": "safeId(\"incident\", identityKey)"
24
+ }
25
+ }
26
+ },
27
+ "lagEstimates": {
28
+ "name": "dcb-lag-estimates",
29
+ "partitionKeyPath": "/serviceId",
30
+ "partitionValueKinds": [
31
+ "{serviceId}"
32
+ ],
33
+ "documentIds": {
34
+ "lagEstimate": "serviceId"
35
+ }
36
+ },
37
+ "pendingArrivals": {
38
+ "name": "dcb-pending-arrivals",
39
+ "partitionKeyPath": "/serviceId",
40
+ "partitionValueKinds": [
41
+ "{serviceId}"
42
+ ],
43
+ "documentIds": {
44
+ "pendingArrival": "safeId(eventId)"
45
+ }
46
+ },
47
+ "findings": {
48
+ "name": "dcb-findings",
49
+ "partitionKeyPath": "/serviceId",
50
+ "partitionValueKinds": [
51
+ "{serviceId}"
52
+ ],
53
+ "documentIds": {
54
+ "finding": "safeId(eventId, path, classification)",
55
+ "incidentProjection": "safeId(\"incident\", identityKey)"
56
+ }
57
+ },
58
+ "checkpoints": {
59
+ "name": "dcb-projection-checkpoints",
60
+ "partitionKeyPath": "/serviceId",
61
+ "partitionValueKinds": [
62
+ "{serviceId}"
63
+ ],
64
+ "documentIds": {
65
+ "checkpoint": "safeId(projectionId)"
66
+ }
67
+ }
68
+ });
69
+ export const COSMOS_DOCUMENT_ID_RULES = Object.freeze({
70
+ "events": {
71
+ "logicalEvent": "canonical event id",
72
+ "eventSidecar": "safeId(\"event-ops\", eventId)",
73
+ "serviceGuards": {
74
+ "allocatorLineageBinding": "safeId(\"allocator-lineage-binding\")",
75
+ "suidBinding": "safeId(\"suid-binding\", suid)",
76
+ "deliveryIncident": "safeId(\"incident\", identityKey)"
77
+ }
78
+ },
79
+ "lagEstimates": {
80
+ "lagEstimate": "serviceId"
81
+ },
82
+ "pendingArrivals": {
83
+ "pendingArrival": "safeId(eventId)"
84
+ },
85
+ "findings": {
86
+ "finding": "safeId(eventId, path, classification)",
87
+ "incidentProjection": "safeId(\"incident\", identityKey)"
88
+ },
89
+ "checkpoints": {
90
+ "checkpoint": "safeId(projectionId)"
91
+ }
92
+ });
93
+ export const COSMOS_CONTAINER_NAMES = Object.freeze({
94
+ "events": "dcb-events",
95
+ "lagEstimates": "dcb-lag-estimates",
96
+ "pendingArrivals": "dcb-pending-arrivals",
97
+ "findings": "dcb-findings",
98
+ "checkpoints": "dcb-projection-checkpoints"
99
+ });
@@ -0,0 +1,12 @@
1
+ export type ConfiguredBearerResult = "unconfigured" | "missing" | "invalid" | "authorized";
2
+ export type ConfiguredBearerTokenNormalization = "exact" | "trim";
3
+ export interface CheckConfiguredBearerOptions {
4
+ readonly tokenNormalization?: ConfiguredBearerTokenNormalization;
5
+ }
6
+ /**
7
+ * Decide whether a configured shared secret admits this request.
8
+ * Callers map the four results onto their own HTTP responses.
9
+ * The binding is configured only when it is a string with a non-whitespace
10
+ * character. Exact mode keeps those bytes; trim mode is opt-in.
11
+ */
12
+ export declare function checkConfiguredBearer(request: Request, binding: unknown, options?: CheckConfiguredBearerOptions): Promise<ConfiguredBearerResult>;
@@ -0,0 +1,33 @@
1
+ async function sha256(value) {
2
+ const encoded = new TextEncoder().encode(value);
3
+ return new Uint8Array(await crypto.subtle.digest("SHA-256", encoded));
4
+ }
5
+ function digestsMatch(expected, supplied) {
6
+ let difference = expected.length ^ supplied.length;
7
+ const length = Math.max(expected.length, supplied.length);
8
+ for (let index = 0; index < length; index += 1) {
9
+ difference |= (expected[index] ?? 0) ^ (supplied[index] ?? 0);
10
+ }
11
+ return difference === 0;
12
+ }
13
+ /**
14
+ * Decide whether a configured shared secret admits this request.
15
+ * Callers map the four results onto their own HTTP responses.
16
+ * The binding is configured only when it is a string with a non-whitespace
17
+ * character. Exact mode keeps those bytes; trim mode is opt-in.
18
+ */
19
+ export async function checkConfiguredBearer(request, binding, options) {
20
+ if (typeof binding !== "string" || binding.trim().length === 0) {
21
+ return "unconfigured";
22
+ }
23
+ const effectiveToken = options?.tokenNormalization === "trim" ? binding.trim() : binding;
24
+ const supplied = request.headers.get("authorization");
25
+ if (supplied === null)
26
+ return "missing";
27
+ const expected = `Bearer ${effectiveToken}`;
28
+ const [expectedDigest, suppliedDigest] = await Promise.all([
29
+ sha256(expected),
30
+ sha256(supplied),
31
+ ]);
32
+ return digestsMatch(expectedDigest, suppliedDigest) ? "authorized" : "invalid";
33
+ }
@@ -0,0 +1,8 @@
1
+ import { type ServiceIdentityProvider } from "../service/ServiceIdentityProvider";
2
+ export interface IncidentMaintenanceEnvironment {
3
+ readonly D1?: D1Database;
4
+ readonly INCIDENT_MAINTAINER_TOKEN?: string;
5
+ readonly G11_VERIFICATION_ENABLED?: string;
6
+ }
7
+ export declare function isIncidentMaintenancePath(pathname: string): boolean;
8
+ export declare function handleIncidentMaintenance(request: Request, env: IncidentMaintenanceEnvironment, serviceIdentityProvider: ServiceIdentityProvider): Promise<Response>;