@sekiban/dcb-runtime 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/allocator/AllocatorDurableObject.js +25 -2
- package/dist/bootstrap/BootstrapCoordinatorDurableObject.d.ts +2 -0
- package/dist/bootstrap/BootstrapCoordinatorDurableObject.js +64 -1
- package/dist/bootstrap/types.d.ts +6 -0
- package/dist/cloudflare.d.ts +3 -0
- package/dist/cloudflare.js +1493 -451
- package/dist/commit/CommitWorker.d.ts +19 -0
- package/dist/commit/CommitWorker.js +176 -27
- package/dist/completeness/IncidentLifecycle.d.ts +75 -0
- package/dist/completeness/IncidentLifecycle.js +503 -0
- package/dist/completeness/types.d.ts +67 -0
- package/dist/cosmos.d.ts +34 -4
- package/dist/cosmos.js +214 -49
- package/dist/downstream/OutboxDrain.d.ts +4 -1
- package/dist/downstream/OutboxDrain.js +24 -2
- package/dist/generated/cosmos-layout.d.ts +87 -0
- package/dist/generated/cosmos-layout.js +99 -0
- package/dist/http/IncidentMaintenance.d.ts +8 -0
- package/dist/http/IncidentMaintenance.js +145 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1548 -451
- package/dist/read/SerializedReadWorker.d.ts +3 -0
- package/dist/read/SerializedReadWorker.js +3 -3
- package/dist/shard/ShardRotation.d.ts +48 -0
- package/dist/shard/ShardRotation.js +120 -0
- package/dist/store/CosmosEventStore.js +56 -46
- package/dist/store/PostgresEventStore.d.ts +20 -0
- package/dist/store/PostgresEventStore.js +53 -0
- package/dist/store/provider.d.ts +4 -0
- package/dist/tag/TagDurableObject.js +66 -14
- package/dist/tag/TagSqlSchema.d.ts +6 -1
- package/dist/tag/TagSqlSchema.js +15 -0
- package/dist/tag/types.d.ts +9 -0
- package/dist/trace/CommitTrace.d.ts +3 -3
- package/dist/trace/CommitTraceVerifier.js +1 -1
- 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 =
|
|
156
|
-
var COSMOS_AUXILIARY_PARTITION_KEY_PATH =
|
|
157
|
-
var DEFAULT_COSMOS_CONTAINERS =
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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:
|
|
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
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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: () =>
|
|
1396
|
-
|
|
1397
|
-
|
|
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
|
|
116
|
-
results.push(await drainTagOutbox({ serviceId:
|
|
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,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>;
|