@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.
- package/README.md +32 -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/OperatorBootstrap.d.ts +5 -1
- package/dist/bootstrap/OperatorBootstrap.js +17 -10
- package/dist/bootstrap/types.d.ts +6 -0
- package/dist/cli/OperatorRepairCli.d.ts +6 -1
- package/dist/cli/OperatorRepairCli.js +26 -6
- package/dist/cloudflare.d.ts +5 -0
- package/dist/cloudflare.js +1567 -465
- 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/ConfiguredBearer.d.ts +12 -0
- package/dist/http/ConfiguredBearer.js +33 -0
- package/dist/http/IncidentMaintenance.d.ts +8 -0
- package/dist/http/IncidentMaintenance.js +124 -0
- package/dist/http/SerializedQueryWorker.js +37 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +1627 -465
- 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/index.js
CHANGED
|
@@ -942,15 +942,15 @@ function encodeOrderOrdinal(value) {
|
|
|
942
942
|
function decodeOrderOrdinal(value) {
|
|
943
943
|
try {
|
|
944
944
|
return assertSortableUniqueId(value).ticks;
|
|
945
|
-
} catch (
|
|
946
|
-
const detail =
|
|
947
|
-
throw new OrderClockReadError(detail, { cause:
|
|
945
|
+
} catch (error11) {
|
|
946
|
+
const detail = error11 instanceof Error ? error11.message : "invalid SortableUniqueId";
|
|
947
|
+
throw new OrderClockReadError(detail, { cause: error11 });
|
|
948
948
|
}
|
|
949
949
|
}
|
|
950
|
-
function asOrderClockError(
|
|
951
|
-
if (
|
|
952
|
-
const detail =
|
|
953
|
-
return new OrderClockReadError(detail, { cause:
|
|
950
|
+
function asOrderClockError(error11) {
|
|
951
|
+
if (error11 instanceof OrderClockReadError) return error11;
|
|
952
|
+
const detail = error11 instanceof Error ? error11.message : "SortableUniqueId allocation failed";
|
|
953
|
+
return new OrderClockReadError(detail, { cause: error11 });
|
|
954
954
|
}
|
|
955
955
|
function allocateOrderRange(watermark, count, clockTick, options = {}) {
|
|
956
956
|
if (!Number.isSafeInteger(count) || count <= 0) {
|
|
@@ -979,9 +979,9 @@ function allocateOrderRange(watermark, count, clockTick, options = {}) {
|
|
|
979
979
|
watermark: suids.at(-1),
|
|
980
980
|
suids: Object.freeze(suids)
|
|
981
981
|
});
|
|
982
|
-
} catch (
|
|
983
|
-
if (
|
|
984
|
-
throw asOrderClockError(
|
|
982
|
+
} catch (error11) {
|
|
983
|
+
if (error11 instanceof SortableUniqueIdError) throw error11;
|
|
984
|
+
throw asOrderClockError(error11);
|
|
985
985
|
}
|
|
986
986
|
}
|
|
987
987
|
function diagnosticAllocatedAt(ticks) {
|
|
@@ -999,7 +999,7 @@ function diagnosticAllocatedAt(ticks) {
|
|
|
999
999
|
var commit_trace_manifest_default = {
|
|
1000
1000
|
schemaVersion: 2,
|
|
1001
1001
|
name: "commit-trace-manifest",
|
|
1002
|
-
description: "
|
|
1002
|
+
description: "Repository-owned authority for the sdt.commit span manifests. The generated views are kept in RENDERED-VIEWS.md; the manifest does not duplicate those tables.",
|
|
1003
1003
|
remoteInvocationUniverse: [
|
|
1004
1004
|
"S16"
|
|
1005
1005
|
],
|
|
@@ -3412,9 +3412,9 @@ var commit_trace_manifest_default = {
|
|
|
3412
3412
|
|
|
3413
3413
|
// src/trace/AttributionRatio.ts
|
|
3414
3414
|
function failure(code, message) {
|
|
3415
|
-
const
|
|
3416
|
-
|
|
3417
|
-
throw
|
|
3415
|
+
const error11 = new Error(`${code}:${message}`);
|
|
3416
|
+
error11.name = "CommitTraceAttributionError";
|
|
3417
|
+
throw error11;
|
|
3418
3418
|
}
|
|
3419
3419
|
function clip(interval, root) {
|
|
3420
3420
|
const start = Math.max(interval[0], root[0]);
|
|
@@ -3731,8 +3731,8 @@ var CommitTrace = class {
|
|
|
3731
3731
|
unattributed,
|
|
3732
3732
|
attributionGatePassed: unattributed.unattributedRatio <= 0.05
|
|
3733
3733
|
});
|
|
3734
|
-
} catch (
|
|
3735
|
-
this.noteObservationFailure(
|
|
3734
|
+
} catch (error11) {
|
|
3735
|
+
this.noteObservationFailure(error11);
|
|
3736
3736
|
return Object.freeze({
|
|
3737
3737
|
...base,
|
|
3738
3738
|
runtimeVerification: this.observationVerification()
|
|
@@ -3799,9 +3799,9 @@ var CommitTrace = class {
|
|
|
3799
3799
|
throw new Error(`Commit trace root ${rowId} is not active`);
|
|
3800
3800
|
}
|
|
3801
3801
|
attributes = this.attributesFor(row, options);
|
|
3802
|
-
} catch (
|
|
3803
|
-
if (!this.isFailOpen()) throw
|
|
3804
|
-
this.noteObservationFailure(
|
|
3802
|
+
} catch (error11) {
|
|
3803
|
+
if (!this.isFailOpen()) throw error11;
|
|
3804
|
+
this.noteObservationFailure(error11);
|
|
3805
3805
|
return callback(disabledChild());
|
|
3806
3806
|
}
|
|
3807
3807
|
const execute = async (native2) => {
|
|
@@ -3927,8 +3927,8 @@ var CommitTrace = class {
|
|
|
3927
3927
|
for (const [key, value] of Object.entries(attributes)) {
|
|
3928
3928
|
try {
|
|
3929
3929
|
native.setAttribute(key, value);
|
|
3930
|
-
} catch (
|
|
3931
|
-
this.noteObservationFailure(
|
|
3930
|
+
} catch (error11) {
|
|
3931
|
+
this.noteObservationFailure(error11);
|
|
3932
3932
|
}
|
|
3933
3933
|
}
|
|
3934
3934
|
}
|
|
@@ -3945,8 +3945,8 @@ var CommitTrace = class {
|
|
|
3945
3945
|
now() {
|
|
3946
3946
|
try {
|
|
3947
3947
|
return this.clock().now();
|
|
3948
|
-
} catch (
|
|
3949
|
-
this.noteObservationFailure(
|
|
3948
|
+
} catch (error11) {
|
|
3949
|
+
this.noteObservationFailure(error11);
|
|
3950
3950
|
return Date.now();
|
|
3951
3951
|
}
|
|
3952
3952
|
}
|
|
@@ -3954,8 +3954,8 @@ var CommitTrace = class {
|
|
|
3954
3954
|
let beforeVerification;
|
|
3955
3955
|
try {
|
|
3956
3956
|
beforeVerification = this.snapshot();
|
|
3957
|
-
} catch (
|
|
3958
|
-
this.noteObservationFailure(
|
|
3957
|
+
} catch (error11) {
|
|
3958
|
+
this.noteObservationFailure(error11);
|
|
3959
3959
|
return;
|
|
3960
3960
|
}
|
|
3961
3961
|
if (this.options.runtimeVerifier !== void 0) {
|
|
@@ -3966,15 +3966,15 @@ var CommitTrace = class {
|
|
|
3966
3966
|
this.options.runtimeVerifier.verify(beforeVerification);
|
|
3967
3967
|
this.runtimeVerification = Object.freeze({ passed: true });
|
|
3968
3968
|
}
|
|
3969
|
-
} catch (
|
|
3970
|
-
const code = typeof
|
|
3969
|
+
} catch (error11) {
|
|
3970
|
+
const code = typeof error11 === "object" && error11 !== null && "code" in error11 && typeof error11.code === "string" ? error11.code : "runtime-verifier-error";
|
|
3971
3971
|
this.runtimeVerification = Object.freeze({ passed: false, code });
|
|
3972
3972
|
}
|
|
3973
3973
|
}
|
|
3974
3974
|
try {
|
|
3975
3975
|
this.options.sink?.record(this.snapshot());
|
|
3976
|
-
} catch (
|
|
3977
|
-
this.noteObservationFailure(
|
|
3976
|
+
} catch (error11) {
|
|
3977
|
+
this.noteObservationFailure(error11);
|
|
3978
3978
|
}
|
|
3979
3979
|
}
|
|
3980
3980
|
/**
|
|
@@ -3986,8 +3986,8 @@ var CommitTrace = class {
|
|
|
3986
3986
|
async scheduleDetachedObservation() {
|
|
3987
3987
|
try {
|
|
3988
3988
|
await this.options.scheduleDetachedObservation?.();
|
|
3989
|
-
} catch (
|
|
3990
|
-
this.noteObservationFailure(
|
|
3989
|
+
} catch (error11) {
|
|
3990
|
+
this.noteObservationFailure(error11);
|
|
3991
3991
|
}
|
|
3992
3992
|
}
|
|
3993
3993
|
isFailOpen() {
|
|
@@ -3996,9 +3996,9 @@ var CommitTrace = class {
|
|
|
3996
3996
|
observationVerification() {
|
|
3997
3997
|
return Object.freeze({ passed: false, code: this.observationFailureCode ?? "trace-observation-error" });
|
|
3998
3998
|
}
|
|
3999
|
-
noteObservationFailure(
|
|
3999
|
+
noteObservationFailure(error11) {
|
|
4000
4000
|
if (this.observationFailureCode !== void 0) return;
|
|
4001
|
-
this.observationFailureCode = typeof
|
|
4001
|
+
this.observationFailureCode = typeof error11 === "object" && error11 !== null && "code" in error11 && typeof error11.code === "string" ? error11.code : "trace-observation-error";
|
|
4002
4002
|
this.runtimeVerification = this.observationVerification();
|
|
4003
4003
|
}
|
|
4004
4004
|
/**
|
|
@@ -4019,9 +4019,9 @@ var CommitTrace = class {
|
|
|
4019
4019
|
return callback(void 0);
|
|
4020
4020
|
}
|
|
4021
4021
|
return await result2;
|
|
4022
|
-
} catch (
|
|
4023
|
-
if (callbackStarted) throw
|
|
4024
|
-
this.noteObservationFailure(
|
|
4022
|
+
} catch (error11) {
|
|
4023
|
+
if (callbackStarted) throw error11;
|
|
4024
|
+
this.noteObservationFailure(error11);
|
|
4025
4025
|
return callback(void 0);
|
|
4026
4026
|
}
|
|
4027
4027
|
}
|
|
@@ -4119,8 +4119,8 @@ async function enterNativeSpanFailOpen(nativeTracing, name, callback) {
|
|
|
4119
4119
|
const result2 = nativeTracing.enterSpan(name, wrapped);
|
|
4120
4120
|
if (!callbackStarted) return callback(void 0);
|
|
4121
4121
|
return await result2;
|
|
4122
|
-
} catch (
|
|
4123
|
-
if (callbackStarted) throw
|
|
4122
|
+
} catch (error11) {
|
|
4123
|
+
if (callbackStarted) throw error11;
|
|
4124
4124
|
return callback(void 0);
|
|
4125
4125
|
}
|
|
4126
4126
|
}
|
|
@@ -4152,9 +4152,9 @@ async function enterNativeCommitSpan(nativeTracing, name, input, callback) {
|
|
|
4152
4152
|
if (response3 !== void 0 && input.face !== "reconcile-root") setNativeAttribute(span, "http.status", response3.status);
|
|
4153
4153
|
}
|
|
4154
4154
|
return result2;
|
|
4155
|
-
} catch (
|
|
4155
|
+
} catch (error11) {
|
|
4156
4156
|
setNativeAttribute(span, "outcome", "exception");
|
|
4157
|
-
throw
|
|
4157
|
+
throw error11;
|
|
4158
4158
|
}
|
|
4159
4159
|
});
|
|
4160
4160
|
}
|
|
@@ -4193,9 +4193,9 @@ async function enterNativeActorHandleSpan(nativeTracing, input, resolveIdentity,
|
|
|
4193
4193
|
if (response3 !== void 0) setNativeAttribute(span, "http.status", response3.status);
|
|
4194
4194
|
}
|
|
4195
4195
|
return result2;
|
|
4196
|
-
} catch (
|
|
4196
|
+
} catch (error11) {
|
|
4197
4197
|
if (identity2 !== void 0) setNativeAttribute(span, "outcome", "exception");
|
|
4198
|
-
throw
|
|
4198
|
+
throw error11;
|
|
4199
4199
|
} finally {
|
|
4200
4200
|
input.observation?.finish();
|
|
4201
4201
|
}
|
|
@@ -4260,9 +4260,9 @@ async function enterNativeReconcileRootSpan(nativeTracing, resolveIdentity, call
|
|
|
4260
4260
|
const result2 = await callback(identity2, facts);
|
|
4261
4261
|
finish("success");
|
|
4262
4262
|
return result2;
|
|
4263
|
-
} catch (
|
|
4263
|
+
} catch (error11) {
|
|
4264
4264
|
finish("exception");
|
|
4265
|
-
throw
|
|
4265
|
+
throw error11;
|
|
4266
4266
|
}
|
|
4267
4267
|
});
|
|
4268
4268
|
}
|
|
@@ -4373,6 +4373,8 @@ var ATTEMPT_KEY_PREFIX2 = "attempt:";
|
|
|
4373
4373
|
var ROLLBACK_WARNING_WINDOW_MS = 1000n;
|
|
4374
4374
|
var AllocationTransactionFault = class extends Error {
|
|
4375
4375
|
};
|
|
4376
|
+
var ImportSeedRequiredError = class extends Error {
|
|
4377
|
+
};
|
|
4376
4378
|
function json(body, status = 200) {
|
|
4377
4379
|
return new Response(JSON.stringify(body), {
|
|
4378
4380
|
status,
|
|
@@ -4412,7 +4414,17 @@ function seedFrom(value) {
|
|
|
4412
4414
|
} catch {
|
|
4413
4415
|
return { error: "highWatermark must be an allocator SUID" };
|
|
4414
4416
|
}
|
|
4415
|
-
|
|
4417
|
+
let storeMaximum;
|
|
4418
|
+
if (value.storeMaximum !== void 0) {
|
|
4419
|
+
if (!isNonEmptyString2(value.storeMaximum)) return { error: "storeMaximum must be an allocator SUID" };
|
|
4420
|
+
try {
|
|
4421
|
+
decodeSuid(value.storeMaximum);
|
|
4422
|
+
} catch {
|
|
4423
|
+
return { error: "storeMaximum must be an allocator SUID" };
|
|
4424
|
+
}
|
|
4425
|
+
storeMaximum = value.storeMaximum;
|
|
4426
|
+
}
|
|
4427
|
+
return { value: { importId: value.importId, leaseEpoch: value.leaseEpoch, highWatermark: value.highWatermark, ...storeMaximum !== void 0 ? { storeMaximum } : {} } };
|
|
4416
4428
|
}
|
|
4417
4429
|
function newAllocatorLineageId() {
|
|
4418
4430
|
return crypto.randomUUID();
|
|
@@ -4467,7 +4479,8 @@ function allocateFrom(value) {
|
|
|
4467
4479
|
faultInjection,
|
|
4468
4480
|
...serviceId !== void 0 ? { serviceId } : {},
|
|
4469
4481
|
...bootstrapCommandId !== void 0 ? { bootstrapCommandId } : {},
|
|
4470
|
-
...bootstrapEpoch !== void 0 ? { bootstrapEpoch } : {}
|
|
4482
|
+
...bootstrapEpoch !== void 0 ? { bootstrapEpoch } : {},
|
|
4483
|
+
...value.requiresImportSeed === true ? { requiresImportSeed: true } : {}
|
|
4471
4484
|
}
|
|
4472
4485
|
};
|
|
4473
4486
|
}
|
|
@@ -4852,11 +4865,12 @@ var AllocatorDurableObject = class {
|
|
|
4852
4865
|
};
|
|
4853
4866
|
}
|
|
4854
4867
|
const state = persistedState === void 0 ? currentState(lineage) : { ...persistedState, allocatorLineageId: lineage, schemaVersion: 5, bootstrapSeed: persistedState.bootstrapSeed ?? null, lastRollbackWarningFingerprint: persistedState.lastRollbackWarningFingerprint ?? null };
|
|
4868
|
+
if (input.requiresImportSeed === true && state.bootstrapSeed === null) throw new ImportSeedRequiredError();
|
|
4855
4869
|
let clockTick;
|
|
4856
4870
|
try {
|
|
4857
4871
|
clockTick = this.orderClock.tick();
|
|
4858
|
-
} catch (
|
|
4859
|
-
throw
|
|
4872
|
+
} catch (error11) {
|
|
4873
|
+
throw error11 instanceof OrderClockReadError ? error11 : new OrderClockReadError("Order clock failed before allocation write", { cause: error11 });
|
|
4860
4874
|
}
|
|
4861
4875
|
const range = allocateOrderRange(state.allocatedWatermark, input.candidates.length, clockTick);
|
|
4862
4876
|
const rollback = range.observedTicks !== null && range.physicalTicks < range.observedTicks;
|
|
@@ -4930,6 +4944,9 @@ var AllocatorDurableObject = class {
|
|
|
4930
4944
|
}
|
|
4931
4945
|
return json(result2.vector, result2.created ? 201 : 200);
|
|
4932
4946
|
} catch (failure3) {
|
|
4947
|
+
if (failure3 instanceof ImportSeedRequiredError) {
|
|
4948
|
+
return error(409, "allocator_seed_required", "commit before import seed");
|
|
4949
|
+
}
|
|
4933
4950
|
if (failure3 instanceof AllocationTransactionFault) {
|
|
4934
4951
|
return error(
|
|
4935
4952
|
503,
|
|
@@ -4970,7 +4987,10 @@ var AllocatorDurableObject = class {
|
|
|
4970
4987
|
return { status: 409, body: { code: "allocator_seed_rejected", error: "allocator already seeded" } };
|
|
4971
4988
|
}
|
|
4972
4989
|
if (state.allocatedWatermark !== null) return { status: 409, body: { code: "allocator_seed_rejected", error: "allocator already allocating" } };
|
|
4973
|
-
|
|
4990
|
+
if (input.storeMaximum !== void 0 && input.highWatermark < input.storeMaximum) {
|
|
4991
|
+
return { status: 409, body: { code: "allocator_seed_below_store_max", error: "seed below store maximum" } };
|
|
4992
|
+
}
|
|
4993
|
+
const updated = { ...state, allocatedWatermark: input.highWatermark, bootstrapSeed: { importId: input.importId, leaseEpoch: input.leaseEpoch, highWatermark: input.highWatermark } };
|
|
4974
4994
|
await txn.put(STATE_KEY, updated);
|
|
4975
4995
|
return { status: 201, body: updated };
|
|
4976
4996
|
});
|
|
@@ -4995,7 +5015,7 @@ var isPlainRecord = (value) => {
|
|
|
4995
5015
|
return prototype === Object.prototype || prototype === null;
|
|
4996
5016
|
};
|
|
4997
5017
|
function assertJsonValue(value, boundary = "value") {
|
|
4998
|
-
const
|
|
5018
|
+
const active2 = /* @__PURE__ */ new WeakSet();
|
|
4999
5019
|
const visit = (candidate, path) => {
|
|
5000
5020
|
if (candidate === null || typeof candidate === "string" || typeof candidate === "boolean") {
|
|
5001
5021
|
return candidate;
|
|
@@ -5007,10 +5027,10 @@ function assertJsonValue(value, boundary = "value") {
|
|
|
5007
5027
|
if (typeof candidate !== "object") {
|
|
5008
5028
|
throw new JsonValidationError(`Value at ${path} is not JSON serializable`, boundary, path);
|
|
5009
5029
|
}
|
|
5010
|
-
if (
|
|
5030
|
+
if (active2.has(candidate)) {
|
|
5011
5031
|
throw new JsonValidationError(`Cyclic JSON value at ${path}`, boundary, path);
|
|
5012
5032
|
}
|
|
5013
|
-
|
|
5033
|
+
active2.add(candidate);
|
|
5014
5034
|
try {
|
|
5015
5035
|
if (Array.isArray(candidate)) {
|
|
5016
5036
|
return candidate.map((item, index) => visit(item, `${path}[${index}]`));
|
|
@@ -5024,7 +5044,7 @@ function assertJsonValue(value, boundary = "value") {
|
|
|
5024
5044
|
}
|
|
5025
5045
|
return result2;
|
|
5026
5046
|
} finally {
|
|
5027
|
-
|
|
5047
|
+
active2.delete(candidate);
|
|
5028
5048
|
}
|
|
5029
5049
|
};
|
|
5030
5050
|
return visit(value, "$");
|
|
@@ -5119,16 +5139,16 @@ function createUuidV7(nowMs = Date.now()) {
|
|
|
5119
5139
|
if (typeof globalThis.crypto?.getRandomValues !== "function") {
|
|
5120
5140
|
throw new Error("crypto.getRandomValues is required for UUIDv7");
|
|
5121
5141
|
}
|
|
5122
|
-
const
|
|
5123
|
-
globalThis.crypto.getRandomValues(
|
|
5142
|
+
const bytes2 = new Uint8Array(16);
|
|
5143
|
+
globalThis.crypto.getRandomValues(bytes2);
|
|
5124
5144
|
let time = BigInt(nowMs);
|
|
5125
5145
|
for (let index = 5; index >= 0; index -= 1) {
|
|
5126
|
-
|
|
5146
|
+
bytes2[index] = Number(time & 0xffn);
|
|
5127
5147
|
time >>= 8n;
|
|
5128
5148
|
}
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
const hex = [...
|
|
5149
|
+
bytes2[6] = bytes2[6] & 15 | 112;
|
|
5150
|
+
bytes2[8] = bytes2[8] & 63 | 128;
|
|
5151
|
+
const hex = [...bytes2].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
5132
5152
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
5133
5153
|
}
|
|
5134
5154
|
function isRfc4122Uuid(value) {
|
|
@@ -5244,7 +5264,7 @@ var epoch = (value) => typeof value === "number" && Number.isSafeInteger(value)
|
|
|
5244
5264
|
var response = (body, status = 200) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json; charset=utf-8" } });
|
|
5245
5265
|
var reject = (code, message, status = 409) => response({ code, error: message }, status);
|
|
5246
5266
|
function empty(serviceId) {
|
|
5247
|
-
return { schemaVersion: 1, status: "EMPTY", importId: null, targetServiceId: serviceId, allocatorLineageId: null, source: null, digest: null, manifest: null, progress: {}, leaseEpoch: 0, leaseUntil: null, failure: null, readyAt: null, normalInFlight: 0, normalCommands: {}, releasedCommands: {}, verifiedImportId: null, verifiedLeaseEpoch: null, storeCompletion: null };
|
|
5267
|
+
return { schemaVersion: 1, status: "EMPTY", importId: null, targetServiceId: serviceId, allocatorLineageId: null, source: null, digest: null, manifest: null, progress: {}, leaseEpoch: 0, leaseUntil: null, failure: null, readyAt: null, normalInFlight: 0, normalCommands: {}, releasedCommands: {}, writePermits: {}, verifiedImportId: null, verifiedLeaseEpoch: null, storeCompletion: null };
|
|
5248
5268
|
}
|
|
5249
5269
|
function expired(control) {
|
|
5250
5270
|
return control.leaseUntil !== null && control.leaseUntil <= Date.now();
|
|
@@ -5255,12 +5275,22 @@ function commands(control) {
|
|
|
5255
5275
|
function released(control) {
|
|
5256
5276
|
return { ...control.releasedCommands ?? {} };
|
|
5257
5277
|
}
|
|
5278
|
+
function permits(control) {
|
|
5279
|
+
return { ...control.writePermits ?? {} };
|
|
5280
|
+
}
|
|
5258
5281
|
function fault(body, point) {
|
|
5259
5282
|
return object(body) && body.faultAt === point;
|
|
5260
5283
|
}
|
|
5261
5284
|
function encodedBytes(value) {
|
|
5262
5285
|
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
5263
5286
|
}
|
|
5287
|
+
async function readBody(request) {
|
|
5288
|
+
try {
|
|
5289
|
+
return await request.json();
|
|
5290
|
+
} catch {
|
|
5291
|
+
return void 0;
|
|
5292
|
+
}
|
|
5293
|
+
}
|
|
5264
5294
|
var BootstrapCoordinatorDurableObject = class {
|
|
5265
5295
|
constructor(ctx, env, nativeTracing = noOpNativeTracing) {
|
|
5266
5296
|
this.ctx = ctx;
|
|
@@ -5282,6 +5312,8 @@ var BootstrapCoordinatorDurableObject = class {
|
|
|
5282
5312
|
if (request.method === "POST" && url.pathname === "/command/admit") return this.traceCommand(request, serviceId, activation, observation, "admit");
|
|
5283
5313
|
if (request.method === "POST" && url.pathname === "/command/finalize") return this.traceCommand(request, serviceId, activation, observation, "finalize");
|
|
5284
5314
|
if (request.method === "POST" && url.pathname === "/command/release") return this.traceCommand(request, serviceId, activation, observation, "release");
|
|
5315
|
+
if (request.method === "POST" && url.pathname === "/command/permit") return this.permit(serviceId, await readBody(request));
|
|
5316
|
+
if (request.method === "POST" && url.pathname === "/command/permit-release") return this.permitRelease(serviceId, await readBody(request));
|
|
5285
5317
|
let body;
|
|
5286
5318
|
try {
|
|
5287
5319
|
body = await request.json();
|
|
@@ -5338,6 +5370,7 @@ var BootstrapCoordinatorDurableObject = class {
|
|
|
5338
5370
|
const control = await txn.get(CONTROL) ?? empty(serviceId);
|
|
5339
5371
|
if (control.status === "READY") return { error: "bootstrap_ready_permanent" };
|
|
5340
5372
|
if (control.status !== "EMPTY") return control.importId === importId && control.digest === dump.manifest.contentDigest ? { control } : { error: "bootstrap_plan_conflict" };
|
|
5373
|
+
if (Object.keys(permits(control)).length !== 0) return { error: "bootstrap_write_permit_active" };
|
|
5341
5374
|
if (control.normalInFlight !== 0 || Object.keys(commands(control)).length !== 0) return { error: "bootstrap_normal_command_in_flight" };
|
|
5342
5375
|
const next = { ...control, status: "PLANNED", importId, allocatorLineageId: dump.manifest.target.allocatorLineageId, source: dump.manifest.source, digest: dump.manifest.contentDigest, manifest: dump.manifest, leaseEpoch: control.leaseEpoch + 1, leaseUntil: Date.now() + 3e4, failure: null, progress: {}, verifiedImportId: null, verifiedLeaseEpoch: null, storeCompletion: null };
|
|
5343
5376
|
await txn.put(CONTROL, next);
|
|
@@ -5350,17 +5383,17 @@ var BootstrapCoordinatorDurableObject = class {
|
|
|
5350
5383
|
chunks(events) {
|
|
5351
5384
|
const chunks = [];
|
|
5352
5385
|
let current = [];
|
|
5353
|
-
let
|
|
5386
|
+
let bytes2 = 0;
|
|
5354
5387
|
for (const event of events) {
|
|
5355
5388
|
const eventBytes = encodedBytes(event);
|
|
5356
5389
|
if (eventBytes > MAX_CHUNK_BYTES) throw new Error("bootstrap event exceeds encoded chunk bound");
|
|
5357
|
-
if (current.length === MAX_CHUNK_EVENTS ||
|
|
5390
|
+
if (current.length === MAX_CHUNK_EVENTS || bytes2 + eventBytes > MAX_CHUNK_BYTES) {
|
|
5358
5391
|
chunks.push(current);
|
|
5359
5392
|
current = [];
|
|
5360
|
-
|
|
5393
|
+
bytes2 = 0;
|
|
5361
5394
|
}
|
|
5362
5395
|
current.push(event);
|
|
5363
|
-
|
|
5396
|
+
bytes2 += eventBytes;
|
|
5364
5397
|
}
|
|
5365
5398
|
if (current.length > 0) chunks.push(current);
|
|
5366
5399
|
return chunks;
|
|
@@ -5506,6 +5539,45 @@ var BootstrapCoordinatorDurableObject = class {
|
|
|
5506
5539
|
const control = await this.control(serviceId);
|
|
5507
5540
|
return control.status === "EMPTY" || control.status === "READY" ? response({ admitted: true, leaseEpoch: control.leaseEpoch, route: body.route }) : reject("bootstrap_route_rejected", `${body.route} is unavailable during bootstrap`);
|
|
5508
5541
|
}
|
|
5542
|
+
async permit(serviceId, body) {
|
|
5543
|
+
if (!object(body) || !string(body.commandId) || !string(body.digest)) return reject("bootstrap_permit_invalid", "commandId and digest are required", 400);
|
|
5544
|
+
const commandId = body.commandId;
|
|
5545
|
+
const digest = body.digest;
|
|
5546
|
+
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
5547
|
+
const control = await txn.get(CONTROL) ?? empty(serviceId);
|
|
5548
|
+
const active2 = permits(control);
|
|
5549
|
+
if (active2[commandId] !== void 0) {
|
|
5550
|
+
return active2[commandId] === digest ? { control } : { digestMismatch: true };
|
|
5551
|
+
}
|
|
5552
|
+
if (control.status !== "EMPTY" && control.status !== "READY") return { rejected: true };
|
|
5553
|
+
const updated = { ...control, writePermits: { ...active2, [commandId]: digest } };
|
|
5554
|
+
await txn.put(CONTROL, updated);
|
|
5555
|
+
return { control: updated };
|
|
5556
|
+
});
|
|
5557
|
+
if ("digestMismatch" in result2) return reject("bootstrap_permit_digest_mismatch", "bootstrap permit digest does not match the held permit");
|
|
5558
|
+
if ("rejected" in result2) return reject("bootstrap_command_rejected", "bootstrap epoch rejects normal durable mutation");
|
|
5559
|
+
return response({ leaseEpoch: result2.control.leaseEpoch, admitted: true });
|
|
5560
|
+
}
|
|
5561
|
+
async permitRelease(serviceId, body) {
|
|
5562
|
+
if (!object(body) || !string(body.commandId)) return reject("bootstrap_permit_invalid", "commandId is required", 400);
|
|
5563
|
+
const commandId = body.commandId;
|
|
5564
|
+
const digest = string(body.digest) ? body.digest : void 0;
|
|
5565
|
+
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
5566
|
+
const control = await txn.get(CONTROL) ?? empty(serviceId);
|
|
5567
|
+
const active2 = permits(control);
|
|
5568
|
+
if (active2[commandId] !== void 0 && digest !== void 0 && active2[commandId] !== digest) {
|
|
5569
|
+
return { digestMismatch: true };
|
|
5570
|
+
}
|
|
5571
|
+
if (active2[commandId] === void 0) return { control };
|
|
5572
|
+
const next = { ...active2 };
|
|
5573
|
+
delete next[commandId];
|
|
5574
|
+
const updated = { ...control, writePermits: next };
|
|
5575
|
+
await txn.put(CONTROL, updated);
|
|
5576
|
+
return { control: updated };
|
|
5577
|
+
});
|
|
5578
|
+
if ("digestMismatch" in result2) return reject("bootstrap_permit_digest_mismatch", "bootstrap permit digest does not match the held permit");
|
|
5579
|
+
return response({ leaseEpoch: result2.control.leaseEpoch, admitted: true });
|
|
5580
|
+
}
|
|
5509
5581
|
async command(serviceId, body, action, observation) {
|
|
5510
5582
|
if (!object(body) || !string(body.commandId)) return reject("bootstrap_command_invalid", "commandId is required", 400);
|
|
5511
5583
|
const commandId = body.commandId;
|
|
@@ -5571,13 +5643,13 @@ function byteCompare(left, right) {
|
|
|
5571
5643
|
}
|
|
5572
5644
|
function concat(parts) {
|
|
5573
5645
|
const length = parts.reduce((total, part) => total + part.byteLength, 0);
|
|
5574
|
-
const
|
|
5646
|
+
const bytes2 = new Uint8Array(length);
|
|
5575
5647
|
let offset = 0;
|
|
5576
5648
|
for (const part of parts) {
|
|
5577
|
-
|
|
5649
|
+
bytes2.set(part, offset);
|
|
5578
5650
|
offset += part.byteLength;
|
|
5579
5651
|
}
|
|
5580
|
-
return
|
|
5652
|
+
return bytes2;
|
|
5581
5653
|
}
|
|
5582
5654
|
function lengthPrefix(length) {
|
|
5583
5655
|
if (!Number.isSafeInteger(length) || length < 0 || length > 4294967295) {
|
|
@@ -5606,8 +5678,8 @@ function requiredUtf8(input, name) {
|
|
|
5606
5678
|
function tagSetBytes(tags) {
|
|
5607
5679
|
const canonical2 = canonicalDeclaredTagSet(tags);
|
|
5608
5680
|
return concat(canonical2.map((tag) => {
|
|
5609
|
-
const
|
|
5610
|
-
return concat([lengthPrefix(
|
|
5681
|
+
const bytes2 = utf8(tag);
|
|
5682
|
+
return concat([lengthPrefix(bytes2.byteLength), bytes2]);
|
|
5611
5683
|
}));
|
|
5612
5684
|
}
|
|
5613
5685
|
function canonicalDeclaredTagSet(tags) {
|
|
@@ -5677,16 +5749,16 @@ function eventDigestBytes(input) {
|
|
|
5677
5749
|
]);
|
|
5678
5750
|
}
|
|
5679
5751
|
async function eventDigestHex(input) {
|
|
5680
|
-
const
|
|
5681
|
-
const source =
|
|
5752
|
+
const bytes2 = eventDigestBytes(input);
|
|
5753
|
+
const source = bytes2.buffer.slice(bytes2.byteOffset, bytes2.byteOffset + bytes2.byteLength);
|
|
5682
5754
|
const digest = await crypto.subtle.digest("SHA-256", source);
|
|
5683
5755
|
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
5684
5756
|
}
|
|
5685
5757
|
|
|
5686
5758
|
// src/bootstrap/BootstrapStoreAdapter.ts
|
|
5687
|
-
function arrayBufferBase64(
|
|
5759
|
+
function arrayBufferBase64(bytes2) {
|
|
5688
5760
|
let binary = "";
|
|
5689
|
-
for (const byte of
|
|
5761
|
+
for (const byte of bytes2) binary += String.fromCharCode(byte);
|
|
5690
5762
|
return btoa(binary);
|
|
5691
5763
|
}
|
|
5692
5764
|
var BootstrapIdentityConflictError = class extends Error {
|
|
@@ -5826,6 +5898,34 @@ function createBootstrapStoreAdapter(provider, store, identityGuard) {
|
|
|
5826
5898
|
return new BootstrapStoreAdapter(provider, store, identityGuard);
|
|
5827
5899
|
}
|
|
5828
5900
|
|
|
5901
|
+
// src/http/ConfiguredBearer.ts
|
|
5902
|
+
async function sha256(value) {
|
|
5903
|
+
const encoded = new TextEncoder().encode(value);
|
|
5904
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", encoded));
|
|
5905
|
+
}
|
|
5906
|
+
function digestsMatch(expected, supplied) {
|
|
5907
|
+
let difference = expected.length ^ supplied.length;
|
|
5908
|
+
const length = Math.max(expected.length, supplied.length);
|
|
5909
|
+
for (let index = 0; index < length; index += 1) {
|
|
5910
|
+
difference |= (expected[index] ?? 0) ^ (supplied[index] ?? 0);
|
|
5911
|
+
}
|
|
5912
|
+
return difference === 0;
|
|
5913
|
+
}
|
|
5914
|
+
async function checkConfiguredBearer(request, binding, options) {
|
|
5915
|
+
if (typeof binding !== "string" || binding.trim().length === 0) {
|
|
5916
|
+
return "unconfigured";
|
|
5917
|
+
}
|
|
5918
|
+
const effectiveToken = options?.tokenNormalization === "trim" ? binding.trim() : binding;
|
|
5919
|
+
const supplied = request.headers.get("authorization");
|
|
5920
|
+
if (supplied === null) return "missing";
|
|
5921
|
+
const expected = `Bearer ${effectiveToken}`;
|
|
5922
|
+
const [expectedDigest, suppliedDigest] = await Promise.all([
|
|
5923
|
+
sha256(expected),
|
|
5924
|
+
sha256(supplied)
|
|
5925
|
+
]);
|
|
5926
|
+
return digestsMatch(expectedDigest, suppliedDigest) ? "authorized" : "invalid";
|
|
5927
|
+
}
|
|
5928
|
+
|
|
5829
5929
|
// src/bootstrap/OperatorBootstrap.ts
|
|
5830
5930
|
function json2(body, status = 200) {
|
|
5831
5931
|
return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json; charset=utf-8" } });
|
|
@@ -5833,15 +5933,17 @@ function json2(body, status = 200) {
|
|
|
5833
5933
|
function object2(value) {
|
|
5834
5934
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5835
5935
|
}
|
|
5836
|
-
function
|
|
5837
|
-
const
|
|
5838
|
-
if (
|
|
5839
|
-
|
|
5936
|
+
async function authenticateOperatorBootstrap(request, binding) {
|
|
5937
|
+
const decision = await checkConfiguredBearer(request, binding);
|
|
5938
|
+
if (decision === "unconfigured" || decision === "missing") {
|
|
5939
|
+
return json2({ code: "not_found", error: "not found" }, 404);
|
|
5940
|
+
}
|
|
5941
|
+
if (decision === "invalid") {
|
|
5942
|
+
return json2({ code: "operator_auth_required", error: "valid bearer required" }, 403);
|
|
5943
|
+
}
|
|
5840
5944
|
return void 0;
|
|
5841
5945
|
}
|
|
5842
|
-
async function
|
|
5843
|
-
const denied = bearer(request, env.REPAIR_OPERATOR_TOKEN);
|
|
5844
|
-
if (denied !== void 0) return denied;
|
|
5946
|
+
async function handleAuthorizedOperatorBootstrap(request, env, storeProvider, hooks = {}) {
|
|
5845
5947
|
const url = new URL(request.url);
|
|
5846
5948
|
const match = url.pathname.match(/^\/operator\/bootstrap\/([^/]+)\/(plan|import|status|abort|export)$/);
|
|
5847
5949
|
if (match === null) return json2({ code: "not_found", error: "not found" }, 404);
|
|
@@ -6740,14 +6842,21 @@ function testFaultFromRequest(request) {
|
|
|
6740
6842
|
const value = request.headers.get("x-sdt-g6-test-fault");
|
|
6741
6843
|
return value === "after-lease-before-prepare" || value === "after-prepare-before-apply" || value === "after-apply-before-observation" || value === "after-verify-before-audit" || value === "after-audit-before-clear" || value === "after-clear-before-final-observation" ? value : void 0;
|
|
6742
6844
|
}
|
|
6743
|
-
|
|
6744
|
-
if (request.method
|
|
6745
|
-
|
|
6845
|
+
function nonPostOperatorRepairResponse(request) {
|
|
6846
|
+
if (request.method === "POST") return void 0;
|
|
6847
|
+
return error2(404, "operator_repair_route_not_found", "Operator repair requires POST");
|
|
6848
|
+
}
|
|
6849
|
+
async function authenticateOperatorRepair(request, binding) {
|
|
6850
|
+
const decision = await checkConfiguredBearer(request, binding);
|
|
6851
|
+
if (decision === "unconfigured") {
|
|
6852
|
+
return error2(404, "operator_repair_route_not_found", "Operator repair route not found");
|
|
6746
6853
|
}
|
|
6747
|
-
|
|
6748
|
-
if (!isNonEmptyString3(token) || request.headers.get("authorization") !== `Bearer ${token}`) {
|
|
6854
|
+
if (decision === "missing" || decision === "invalid") {
|
|
6749
6855
|
return error2(401, "operator_auth_required", "A valid operator repair bearer token is required");
|
|
6750
6856
|
}
|
|
6857
|
+
return void 0;
|
|
6858
|
+
}
|
|
6859
|
+
async function handleAuthorizedOperatorRepair(request, env, nativeTracing = noOpNativeTracing, serviceIdentityProvider) {
|
|
6751
6860
|
let body;
|
|
6752
6861
|
try {
|
|
6753
6862
|
body = await request.json();
|
|
@@ -6797,6 +6906,22 @@ var COMMIT_TEST_FAULTS = [
|
|
|
6797
6906
|
var INITIAL_OWNER_EPOCH = 0;
|
|
6798
6907
|
var MAX_WRITE_ATTEMPTS = 2;
|
|
6799
6908
|
var OUTCOME_UNDETERMINED_ERROR = "Commit outcome is undetermined; reread tag heads and event/query state before retrying because blind retry may create duplicate events.";
|
|
6909
|
+
function commitPermitDigest(allTags, candidates) {
|
|
6910
|
+
const normalized = JSON.stringify({
|
|
6911
|
+
tags: [...allTags].sort(),
|
|
6912
|
+
events: candidates.map((candidate) => ({
|
|
6913
|
+
eventId: candidate.eventId,
|
|
6914
|
+
payload: candidate.payload,
|
|
6915
|
+
tags: [...candidate.tags].sort()
|
|
6916
|
+
})).sort((left, right) => left.eventId < right.eventId ? -1 : left.eventId > right.eventId ? 1 : 0)
|
|
6917
|
+
});
|
|
6918
|
+
let hash = 2166136261;
|
|
6919
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
6920
|
+
hash ^= normalized.charCodeAt(index);
|
|
6921
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
6922
|
+
}
|
|
6923
|
+
return `fnv1a32:${hash.toString(16).padStart(8, "0")}`;
|
|
6924
|
+
}
|
|
6800
6925
|
var GLOBAL_ADMISSION_HEADER = "x-sdt-global-admission";
|
|
6801
6926
|
var runtimeCommitTraceVerifier = {
|
|
6802
6927
|
verify(snapshot) {
|
|
@@ -6850,19 +6975,19 @@ var CommitPayloadAdmissionError = class extends Error {
|
|
|
6850
6975
|
code;
|
|
6851
6976
|
};
|
|
6852
6977
|
function decodeBase64Utf8Json(value) {
|
|
6853
|
-
let
|
|
6978
|
+
let bytes2;
|
|
6854
6979
|
try {
|
|
6855
6980
|
const binary = atob(value);
|
|
6856
|
-
|
|
6981
|
+
bytes2 = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
6857
6982
|
} catch {
|
|
6858
6983
|
throw new CommitPayloadAdmissionError("invalid_payload_utf8", "Payload base64 could not be decoded");
|
|
6859
6984
|
}
|
|
6860
|
-
if (
|
|
6985
|
+
if (bytes2[0] === 239 && bytes2[1] === 187 && bytes2[2] === 191) {
|
|
6861
6986
|
throw new CommitPayloadAdmissionError("invalid_payload_utf8", "Payload must not start with a UTF-8 BOM");
|
|
6862
6987
|
}
|
|
6863
6988
|
let text;
|
|
6864
6989
|
try {
|
|
6865
|
-
text = new TextDecoder("utf-8", { fatal: true }).decode(
|
|
6990
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
6866
6991
|
} catch {
|
|
6867
6992
|
throw new CommitPayloadAdmissionError("invalid_payload_utf8", "Payload must be valid UTF-8 JSON text");
|
|
6868
6993
|
}
|
|
@@ -6908,9 +7033,9 @@ function assertExactMemberPaths(actual, registered, path = "$") {
|
|
|
6908
7033
|
}
|
|
6909
7034
|
}
|
|
6910
7035
|
function encodeBase64Utf8(value) {
|
|
6911
|
-
const
|
|
7036
|
+
const bytes2 = new TextEncoder().encode(value);
|
|
6912
7037
|
let binary = "";
|
|
6913
|
-
for (const byte of
|
|
7038
|
+
for (const byte of bytes2) binary += String.fromCharCode(byte);
|
|
6914
7039
|
return btoa(binary);
|
|
6915
7040
|
}
|
|
6916
7041
|
function faultFromRequest(request) {
|
|
@@ -7169,25 +7294,43 @@ var CommitWorker = class {
|
|
|
7169
7294
|
return responseWithAttempt(error3(409, "bootstrap_command_rejected", "Bootstrap admission could not be released"), attemptId, fault2 !== void 0);
|
|
7170
7295
|
}
|
|
7171
7296
|
await this.retiredJournalMilestone("S04", traceState?.scope);
|
|
7297
|
+
const permitDigest = commitPermitDigest(input.allTags, candidates);
|
|
7298
|
+
let permitHeld = false;
|
|
7299
|
+
const releasePermit = async () => {
|
|
7300
|
+
if (!permitHeld) return;
|
|
7301
|
+
permitHeld = false;
|
|
7302
|
+
await this.bootstrapPermitRelease(attemptId, permitDigest);
|
|
7303
|
+
};
|
|
7304
|
+
const permit = await this.bootstrapPermitAcquire(attemptId, permitDigest);
|
|
7305
|
+
if (!permit.ok) {
|
|
7306
|
+
if (permit.code === "bootstrap_permit_digest_mismatch") {
|
|
7307
|
+
return responseWithAttempt(error3(409, "bootstrap_permit_digest_mismatch", "Bootstrap write permit digest does not match"), attemptId, fault2 !== void 0);
|
|
7308
|
+
}
|
|
7309
|
+
return responseWithAttempt(error3(409, "bootstrap_command_rejected", "Bootstrap is importing; commit is unavailable"), attemptId, fault2 !== void 0);
|
|
7310
|
+
}
|
|
7311
|
+
permitHeld = this.env.BOOTSTRAP !== void 0;
|
|
7172
7312
|
const reservations = await this.acquireReservations(input, attemptId, fault2, traceState?.scope);
|
|
7173
7313
|
await this.retiredJournalMilestone("S05a", traceState?.scope, 0);
|
|
7174
7314
|
if (reservations.failure !== void 0) {
|
|
7175
|
-
|
|
7315
|
+
const failure3 = await this.finishReservationFailure(
|
|
7176
7316
|
[...reservations.successes.keys()],
|
|
7177
7317
|
attemptId,
|
|
7178
7318
|
reservations.failure,
|
|
7179
7319
|
fault2,
|
|
7180
7320
|
traceState?.scope
|
|
7181
7321
|
);
|
|
7322
|
+
if (failure3.resolved) await releasePermit();
|
|
7323
|
+
return failure3.response;
|
|
7182
7324
|
}
|
|
7183
7325
|
if (fault2 === "after-reservations-before-allocation") {
|
|
7184
|
-
await this.cancelReservations([...reservations.successes.keys()], attemptId, fault2, traceState?.scope);
|
|
7326
|
+
const cleanup = await this.cancelReservations([...reservations.successes.keys()], attemptId, fault2, traceState?.scope);
|
|
7327
|
+
if (cleanup.failedTags.length === 0) await releasePermit();
|
|
7185
7328
|
return this.noApplicationOutcome(attemptId, true);
|
|
7186
7329
|
}
|
|
7187
7330
|
await this.hooks.beforeBootstrapAllocation?.();
|
|
7188
7331
|
const allocation = await this.allocate(candidates, attemptId, fault2, bootstrapEpoch, traceState?.scope);
|
|
7189
7332
|
if (allocation === void 0) {
|
|
7190
|
-
|
|
7333
|
+
const failure3 = await this.finishReservationFailure(
|
|
7191
7334
|
[...reservations.successes.keys()],
|
|
7192
7335
|
attemptId,
|
|
7193
7336
|
{
|
|
@@ -7198,10 +7341,13 @@ var CommitWorker = class {
|
|
|
7198
7341
|
fault2,
|
|
7199
7342
|
traceState?.scope
|
|
7200
7343
|
);
|
|
7344
|
+
if (failure3.resolved) await releasePermit();
|
|
7345
|
+
return failure3.response;
|
|
7201
7346
|
}
|
|
7202
7347
|
const allocatedCandidates = this.withAllocatedSuids(candidates, allocation);
|
|
7203
7348
|
if (allocatedCandidates === void 0) {
|
|
7204
|
-
await this.cancelReservations([...reservations.successes.keys()], attemptId, fault2, traceState?.scope);
|
|
7349
|
+
const cleanup = await this.cancelReservations([...reservations.successes.keys()], attemptId, fault2, traceState?.scope);
|
|
7350
|
+
if (cleanup.failedTags.length === 0) await releasePermit();
|
|
7205
7351
|
return this.noApplicationOutcome(attemptId, fault2 !== void 0);
|
|
7206
7352
|
}
|
|
7207
7353
|
for (const candidate of allocatedCandidates) {
|
|
@@ -7216,15 +7362,18 @@ var CommitWorker = class {
|
|
|
7216
7362
|
}
|
|
7217
7363
|
await this.retiredJournalMilestone("S05b", traceState?.scope, 1);
|
|
7218
7364
|
if (fault2 === "journal-cas-after-allocator") {
|
|
7219
|
-
await this.cancelReservations(input.allTags, attemptId, fault2, traceState?.scope);
|
|
7365
|
+
const cleanup = await this.cancelReservations(input.allTags, attemptId, fault2, traceState?.scope);
|
|
7366
|
+
if (cleanup.failedTags.length === 0) await releasePermit();
|
|
7220
7367
|
return this.noApplicationOutcome(attemptId, true);
|
|
7221
7368
|
}
|
|
7222
7369
|
await this.retiredJournalMilestone("S05c", traceState?.scope, 2);
|
|
7223
7370
|
await this.hooks.beforeBootstrapFinalization?.();
|
|
7224
7371
|
if (await this.bootstrapCommand("finalize", attemptId, bootstrapEpoch, traceState?.scope, "S10") === void 0) {
|
|
7225
|
-
await this.cancelReservations(input.allTags, attemptId, fault2, traceState?.scope);
|
|
7372
|
+
const cleanup = await this.cancelReservations(input.allTags, attemptId, fault2, traceState?.scope);
|
|
7373
|
+
if (cleanup.failedTags.length === 0) await releasePermit();
|
|
7226
7374
|
return responseWithAttempt(error3(409, "bootstrap_command_rejected", "Bootstrap fencing epoch changed before commit write"), attemptId, fault2 !== void 0);
|
|
7227
7375
|
}
|
|
7376
|
+
await this.hooks.beforeAuthoritativeAppend?.();
|
|
7228
7377
|
const writes = await this.appendAllTags(
|
|
7229
7378
|
input,
|
|
7230
7379
|
allocatedCandidates,
|
|
@@ -7240,6 +7389,7 @@ var CommitWorker = class {
|
|
|
7240
7389
|
return this.noApplicationOutcome(attemptId, true);
|
|
7241
7390
|
}
|
|
7242
7391
|
if (writes.committedTags.size === 0 && writes.registrationUnavailableTags.length === writes.pendingTags.length) {
|
|
7392
|
+
await releasePermit();
|
|
7243
7393
|
return responseWithAttempt(
|
|
7244
7394
|
error3(
|
|
7245
7395
|
503,
|
|
@@ -7251,20 +7401,25 @@ var CommitWorker = class {
|
|
|
7251
7401
|
fault2 !== void 0
|
|
7252
7402
|
);
|
|
7253
7403
|
}
|
|
7404
|
+
await releasePermit();
|
|
7254
7405
|
return this.partialWriteOutcome(input, allocatedCandidates, writes, attemptId, fault2 !== void 0);
|
|
7255
7406
|
}
|
|
7407
|
+
await this.hooks.afterAppendBeforeResponse?.();
|
|
7256
7408
|
await this.retiredJournalMilestone("S05d", traceState?.scope, 3);
|
|
7257
7409
|
if (fault2 === "sealing-after-cas") {
|
|
7410
|
+
await releasePermit();
|
|
7258
7411
|
return this.noApplicationOutcome(attemptId, true);
|
|
7259
7412
|
}
|
|
7260
7413
|
try {
|
|
7261
|
-
const success = await this.successResponse(input, allocatedCandidates, attemptId, startedAt, fault2, traceState?.scope);
|
|
7414
|
+
const success = await this.successResponse(input, allocatedCandidates, attemptId, startedAt, fault2, traceState?.scope, writes);
|
|
7415
|
+
await releasePermit();
|
|
7262
7416
|
return responseWithAttempt(
|
|
7263
7417
|
withGlobalAdmission(json4(success, 200), writes.globalAdmission),
|
|
7264
7418
|
attemptId,
|
|
7265
7419
|
fault2 !== void 0
|
|
7266
7420
|
);
|
|
7267
7421
|
} catch {
|
|
7422
|
+
await releasePermit();
|
|
7268
7423
|
return responseWithAttempt(
|
|
7269
7424
|
error3(500, "internal_error", "Committed records could not be read while preparing the response"),
|
|
7270
7425
|
attemptId,
|
|
@@ -7311,6 +7466,51 @@ var CommitWorker = class {
|
|
|
7311
7466
|
const result2 = traceScope === void 0 || rowId === void 0 ? await invoke() : await traceScope.span(rowId, {}, invoke);
|
|
7312
7467
|
return result2.leaseEpoch;
|
|
7313
7468
|
}
|
|
7469
|
+
/**
|
|
7470
|
+
* SDT-G36 bootstrap write permit. Deliberately untraced: it must not add
|
|
7471
|
+
* `sdt.commit/v2` rows or new trace identities. Only a bootstrap barrier,
|
|
7472
|
+
* never commit-outcome authority. No JOURNAL, no alarm, no lease expiry.
|
|
7473
|
+
*/
|
|
7474
|
+
bootstrapStub() {
|
|
7475
|
+
if (this.env.BOOTSTRAP === void 0) return void 0;
|
|
7476
|
+
return this.env.BOOTSTRAP.get(scopeIdFor(this.env.BOOTSTRAP, {
|
|
7477
|
+
serviceId: this.serviceId,
|
|
7478
|
+
doClass: "bootstrap",
|
|
7479
|
+
identity: "coordinator"
|
|
7480
|
+
}));
|
|
7481
|
+
}
|
|
7482
|
+
async bootstrapPermitAcquire(commandId, digest) {
|
|
7483
|
+
const stub = this.bootstrapStub();
|
|
7484
|
+
if (stub === void 0) return { ok: true };
|
|
7485
|
+
try {
|
|
7486
|
+
const url = new URL(`https://commit-worker.internal/command/permit`);
|
|
7487
|
+
url.searchParams.set("__serviceId", this.serviceId);
|
|
7488
|
+
const response3 = await stub.fetch(new Request(url, {
|
|
7489
|
+
method: "POST",
|
|
7490
|
+
headers: { "content-type": "application/json" },
|
|
7491
|
+
body: JSON.stringify({ commandId, digest })
|
|
7492
|
+
}));
|
|
7493
|
+
if (response3.ok) return { ok: true };
|
|
7494
|
+
const body = await response3.clone().json().catch(() => void 0);
|
|
7495
|
+
return { ok: false, code: typeof body?.code === "string" ? body.code : void 0 };
|
|
7496
|
+
} catch {
|
|
7497
|
+
return { ok: false };
|
|
7498
|
+
}
|
|
7499
|
+
}
|
|
7500
|
+
async bootstrapPermitRelease(commandId, digest) {
|
|
7501
|
+
const stub = this.bootstrapStub();
|
|
7502
|
+
if (stub === void 0) return;
|
|
7503
|
+
try {
|
|
7504
|
+
const url = new URL(`https://commit-worker.internal/command/permit-release`);
|
|
7505
|
+
url.searchParams.set("__serviceId", this.serviceId);
|
|
7506
|
+
await stub.fetch(new Request(url, {
|
|
7507
|
+
method: "POST",
|
|
7508
|
+
headers: { "content-type": "application/json" },
|
|
7509
|
+
body: JSON.stringify({ commandId, digest })
|
|
7510
|
+
}));
|
|
7511
|
+
} catch {
|
|
7512
|
+
}
|
|
7513
|
+
}
|
|
7314
7514
|
async postJson(stub, path, body, traceScope, rowId, traceOptions = {}) {
|
|
7315
7515
|
const invoke = async () => {
|
|
7316
7516
|
const response3 = await stub.fetch(
|
|
@@ -7456,6 +7656,7 @@ var CommitWorker = class {
|
|
|
7456
7656
|
let pending = [...input.allTags];
|
|
7457
7657
|
const committedTags = /* @__PURE__ */ new Set();
|
|
7458
7658
|
const lastFailureCodeByTag = /* @__PURE__ */ new Map();
|
|
7659
|
+
const tagWriteFacts = /* @__PURE__ */ new Map();
|
|
7459
7660
|
let globalAdmission = "admitted";
|
|
7460
7661
|
for (let attempt = 0; attempt < MAX_WRITE_ATTEMPTS && pending.length > 0; attempt += 1) {
|
|
7461
7662
|
const append = (stageScope) => Promise.allSettled(
|
|
@@ -7482,12 +7683,26 @@ var CommitWorker = class {
|
|
|
7482
7683
|
retryIndex: attempt,
|
|
7483
7684
|
attemptId
|
|
7484
7685
|
});
|
|
7485
|
-
const
|
|
7686
|
+
const success = response3.status >= 200 && response3.status < 300;
|
|
7687
|
+
let appendVersion;
|
|
7688
|
+
let appendUpdatedAt;
|
|
7689
|
+
let code;
|
|
7690
|
+
try {
|
|
7691
|
+
const parsed = await response3.clone().json();
|
|
7692
|
+
if (isObject5(parsed)) {
|
|
7693
|
+
if (typeof parsed.version === "number" && Number.isSafeInteger(parsed.version)) appendVersion = parsed.version;
|
|
7694
|
+
if (typeof parsed.updatedAt === "string" && parsed.updatedAt.length > 0) appendUpdatedAt = parsed.updatedAt;
|
|
7695
|
+
if (typeof parsed.code === "string") code = parsed.code;
|
|
7696
|
+
}
|
|
7697
|
+
} catch {
|
|
7698
|
+
}
|
|
7486
7699
|
return {
|
|
7487
7700
|
tag,
|
|
7488
|
-
success
|
|
7701
|
+
success,
|
|
7489
7702
|
globalAdmission: readGlobalAdmission(response3),
|
|
7490
|
-
code
|
|
7703
|
+
code,
|
|
7704
|
+
appendVersion,
|
|
7705
|
+
appendUpdatedAt
|
|
7491
7706
|
};
|
|
7492
7707
|
})
|
|
7493
7708
|
);
|
|
@@ -7499,6 +7714,9 @@ var CommitWorker = class {
|
|
|
7499
7714
|
committedTags.add(tag);
|
|
7500
7715
|
lastFailureCodeByTag.delete(tag);
|
|
7501
7716
|
globalAdmission = mergeGlobalAdmission(globalAdmission, result2.value.globalAdmission);
|
|
7717
|
+
if (result2.value.appendVersion !== void 0 && result2.value.appendUpdatedAt !== void 0) {
|
|
7718
|
+
tagWriteFacts.set(tag, { version: result2.value.appendVersion, updatedAt: result2.value.appendUpdatedAt });
|
|
7719
|
+
}
|
|
7502
7720
|
} else {
|
|
7503
7721
|
lastFailureCodeByTag.set(
|
|
7504
7722
|
tag,
|
|
@@ -7515,7 +7733,8 @@ var CommitWorker = class {
|
|
|
7515
7733
|
registrationUnavailableTags: pending.filter(
|
|
7516
7734
|
(tag) => lastFailureCodeByTag.get(tag) === "partition_registration_unavailable"
|
|
7517
7735
|
),
|
|
7518
|
-
globalAdmission
|
|
7736
|
+
globalAdmission,
|
|
7737
|
+
tagWriteFacts
|
|
7519
7738
|
};
|
|
7520
7739
|
}
|
|
7521
7740
|
/**
|
|
@@ -7548,10 +7767,13 @@ var CommitWorker = class {
|
|
|
7548
7767
|
if (traceScope !== void 0) {
|
|
7549
7768
|
await traceScope.span("S17", {}, async () => void 0);
|
|
7550
7769
|
}
|
|
7551
|
-
await this.cancelReservations(tags, attemptId, fault2, traceScope);
|
|
7770
|
+
const cleanup = await this.cancelReservations(tags, attemptId, fault2, traceScope);
|
|
7552
7771
|
await this.retiredJournalMilestone("S05e", traceScope, 4);
|
|
7553
7772
|
const response3 = failure3.outcome === "REFUSED" ? error3(400, "consistency_conflict", "serialized commit was refused by a consistency reservation") : failure3.failureCause === "reservation-timeout" ? error3(504, "timeout", "serialized commit timed out") : error3(500, "internal_error", "serialized commit failed before writing requested records");
|
|
7554
|
-
return
|
|
7773
|
+
return {
|
|
7774
|
+
response: responseWithAttempt(response3, attemptId, fault2 !== void 0),
|
|
7775
|
+
resolved: cleanup.failedTags.length === 0
|
|
7776
|
+
};
|
|
7555
7777
|
}
|
|
7556
7778
|
/**
|
|
7557
7779
|
* Persist the local partial-write fact for each tag whose append did not
|
|
@@ -7588,22 +7810,25 @@ var CommitWorker = class {
|
|
|
7588
7810
|
}
|
|
7589
7811
|
}, 500), attemptId, exposeAttempt);
|
|
7590
7812
|
}
|
|
7591
|
-
async successResponse(input, candidates, attemptId, startedAt, fault2, traceScope) {
|
|
7592
|
-
const
|
|
7813
|
+
async successResponse(input, candidates, attemptId, startedAt, fault2, traceScope, writes) {
|
|
7814
|
+
const assemble = async (stageScope) => {
|
|
7593
7815
|
if (fault2 === "tag-state-unavailable") {
|
|
7594
7816
|
throw new Error("Test fault made the committed tag state unavailable");
|
|
7595
7817
|
}
|
|
7596
|
-
const
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7818
|
+
const results = [];
|
|
7819
|
+
for (const [memberIndex, tag] of input.allTags.entries()) {
|
|
7820
|
+
const read = () => {
|
|
7821
|
+
const fact = writes?.tagWriteFacts.get(tag);
|
|
7822
|
+
if (fact === void 0) {
|
|
7823
|
+
throw new Error("Committed tag state was unavailable while preparing the response");
|
|
7824
|
+
}
|
|
7825
|
+
return { tag, version: fact.version, writtenAt: fact.updatedAt };
|
|
7826
|
+
};
|
|
7827
|
+
results.push(stageScope === void 0 ? read() : await stageScope.fork().span("S14", { tag, memberIndex, attemptId }, async () => read()));
|
|
7603
7828
|
}
|
|
7604
|
-
return
|
|
7605
|
-
}
|
|
7606
|
-
const tagWriteResults = traceScope === void 0 ? await
|
|
7829
|
+
return results;
|
|
7830
|
+
};
|
|
7831
|
+
const tagWriteResults = traceScope === void 0 ? await assemble() : await traceScope.span("S13", {}, async (stage) => assemble(stage));
|
|
7607
7832
|
return {
|
|
7608
7833
|
writtenEvents: candidates.map((candidate) => ({
|
|
7609
7834
|
payload: encodeBase64Utf8(candidate.payload),
|
|
@@ -7817,15 +8042,15 @@ function observePostAdmission(observer, message, source, input) {
|
|
|
7817
8042
|
observedAt: Date.now()
|
|
7818
8043
|
});
|
|
7819
8044
|
}
|
|
7820
|
-
function errorText(
|
|
7821
|
-
if (
|
|
7822
|
-
return String(
|
|
8045
|
+
function errorText(error11) {
|
|
8046
|
+
if (error11 instanceof Error && error11.message.length > 0) return error11.message;
|
|
8047
|
+
return String(error11);
|
|
7823
8048
|
}
|
|
7824
8049
|
function deliveryCorrelationId(message, source, supplied) {
|
|
7825
8050
|
return supplied ?? `${source}:${message.serviceId}:${message.eventId}:${message.attemptId}`;
|
|
7826
8051
|
}
|
|
7827
|
-
function defaultFailureClass(
|
|
7828
|
-
const candidate =
|
|
8052
|
+
function defaultFailureClass(error11) {
|
|
8053
|
+
const candidate = error11;
|
|
7829
8054
|
if (candidate.failureClass === "duplicate-race") return "duplicate-race";
|
|
7830
8055
|
if (candidate.failureClass === "nonretryable-definition-poison") return "nonretryable-definition-poison";
|
|
7831
8056
|
if (candidate.failureClass === "retryable-transient") return "retryable-transient";
|
|
@@ -7884,8 +8109,8 @@ async function applyViewHandlers(views, message, storedEvent, arrivedAt, source,
|
|
|
7884
8109
|
result: { id: view.id, status, durationMs: Math.max(0, performance.now() - viewStartedAt) },
|
|
7885
8110
|
failure: void 0
|
|
7886
8111
|
};
|
|
7887
|
-
} catch (
|
|
7888
|
-
const failureClass = view.classifyError?.(
|
|
8112
|
+
} catch (error11) {
|
|
8113
|
+
const failureClass = view.classifyError?.(error11) ?? defaultFailureClass(error11);
|
|
7889
8114
|
observePostAdmission(observer, message, source, {
|
|
7890
8115
|
stage: "unsafe-view-apply",
|
|
7891
8116
|
boundary: "end",
|
|
@@ -7897,10 +8122,10 @@ async function applyViewHandlers(views, message, storedEvent, arrivedAt, source,
|
|
|
7897
8122
|
id: view.id,
|
|
7898
8123
|
status: "failed",
|
|
7899
8124
|
failureClass,
|
|
7900
|
-
error: errorText(
|
|
8125
|
+
error: errorText(error11),
|
|
7901
8126
|
durationMs: Math.max(0, performance.now() - viewStartedAt)
|
|
7902
8127
|
},
|
|
7903
|
-
failure: { phase: "view", class: failureClass, viewId: view.id, error: errorText(
|
|
8128
|
+
failure: { phase: "view", class: failureClass, viewId: view.id, error: errorText(error11) }
|
|
7904
8129
|
};
|
|
7905
8130
|
}
|
|
7906
8131
|
}));
|
|
@@ -7930,15 +8155,15 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
7930
8155
|
// settled; this is the durable post-batch observation boundary.
|
|
7931
8156
|
observedAt: Date.now()
|
|
7932
8157
|
});
|
|
7933
|
-
} catch (
|
|
7934
|
-
const failures2 = [{ phase: "recordDelivery", class: "retryable-transient", error: errorText(
|
|
8158
|
+
} catch (error11) {
|
|
8159
|
+
const failures2 = [{ phase: "recordDelivery", class: "retryable-transient", error: errorText(error11) }];
|
|
7935
8160
|
return result(source, message, "stored", arrivedAt, false, [], failures2, options.correlationId, startedAt);
|
|
7936
8161
|
}
|
|
7937
8162
|
if (outcome.outcome !== "stored") {
|
|
7938
8163
|
try {
|
|
7939
8164
|
await store.projectDeliveryIncidents?.(message.serviceId);
|
|
7940
|
-
} catch (
|
|
7941
|
-
const failures2 = [{ phase: "detector", class: "retryable-transient", error: errorText(
|
|
8165
|
+
} catch (error11) {
|
|
8166
|
+
const failures2 = [{ phase: "detector", class: "retryable-transient", error: errorText(error11) }];
|
|
7942
8167
|
return result(source, message, outcome.outcome, arrivedAt, false, [], failures2, options.correlationId, startedAt);
|
|
7943
8168
|
}
|
|
7944
8169
|
return result(source, message, outcome.outcome, arrivedAt, false, [], [], options.correlationId, startedAt);
|
|
@@ -7967,20 +8192,20 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
7967
8192
|
boundary: "end",
|
|
7968
8193
|
outcome: "available"
|
|
7969
8194
|
});
|
|
7970
|
-
} catch (
|
|
8195
|
+
} catch (error11) {
|
|
7971
8196
|
observePostAdmission(options.durableHopObserver, message, source, {
|
|
7972
8197
|
stage: "post-record-delivery-global-receipt-readback",
|
|
7973
8198
|
boundary: "end",
|
|
7974
8199
|
outcome: "error"
|
|
7975
8200
|
});
|
|
7976
|
-
failures.push({ phase: "recordDelivery", class: "retryable-transient", error: errorText(
|
|
8201
|
+
failures.push({ phase: "recordDelivery", class: "retryable-transient", error: errorText(error11) });
|
|
7977
8202
|
return result(source, message, "stored", arrivedAt, false, [], failures, options.correlationId, startedAt);
|
|
7978
8203
|
}
|
|
7979
8204
|
if (options.afterGlobalReceipt !== void 0) {
|
|
7980
8205
|
try {
|
|
7981
8206
|
await options.afterGlobalReceipt({ message, receipt, arrivedAt, source });
|
|
7982
|
-
} catch (
|
|
7983
|
-
failures.push({ phase: "drain", class: "retryable-transient", error: errorText(
|
|
8207
|
+
} catch (error11) {
|
|
8208
|
+
failures.push({ phase: "drain", class: "retryable-transient", error: errorText(error11) });
|
|
7984
8209
|
}
|
|
7985
8210
|
}
|
|
7986
8211
|
}
|
|
@@ -7997,8 +8222,8 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
7997
8222
|
if (options.beforeViews !== void 0) {
|
|
7998
8223
|
try {
|
|
7999
8224
|
await options.beforeViews({ message, event: storedEvent, arrivedAt, source });
|
|
8000
|
-
} catch (
|
|
8001
|
-
failures.push({ phase: "completeness", class: "retryable-transient", error: errorText(
|
|
8225
|
+
} catch (error11) {
|
|
8226
|
+
failures.push({ phase: "completeness", class: "retryable-transient", error: errorText(error11) });
|
|
8002
8227
|
return result(source, message, "stored", arrivedAt, false, viewResults, failures, options.correlationId, startedAt);
|
|
8003
8228
|
}
|
|
8004
8229
|
}
|
|
@@ -8018,14 +8243,14 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8018
8243
|
boundary: "end",
|
|
8019
8244
|
outcome: "applied"
|
|
8020
8245
|
});
|
|
8021
|
-
} catch (
|
|
8246
|
+
} catch (error11) {
|
|
8022
8247
|
observePostAdmission(options.durableHopObserver, message, source, {
|
|
8023
8248
|
stage: "detector",
|
|
8024
8249
|
boundary: "end",
|
|
8025
8250
|
outcome: "error"
|
|
8026
8251
|
});
|
|
8027
8252
|
try {
|
|
8028
|
-
await options.onDetectorFailure?.({ message, arrivedAt, source, error:
|
|
8253
|
+
await options.onDetectorFailure?.({ message, arrivedAt, source, error: error11 });
|
|
8029
8254
|
} catch (healthError) {
|
|
8030
8255
|
failures.push({
|
|
8031
8256
|
phase: "detector",
|
|
@@ -8033,7 +8258,7 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8033
8258
|
error: `detector_health_record_failed:${errorText(healthError)}`
|
|
8034
8259
|
});
|
|
8035
8260
|
}
|
|
8036
|
-
failures.push({ phase: "detector", class: "retryable-transient", error: errorText(
|
|
8261
|
+
failures.push({ phase: "detector", class: "retryable-transient", error: errorText(error11) });
|
|
8037
8262
|
return result(source, message, "stored", arrivedAt, false, [], failures, options.correlationId, startedAt);
|
|
8038
8263
|
}
|
|
8039
8264
|
const views = gatedViews;
|
|
@@ -8050,16 +8275,16 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8050
8275
|
if (options.onStored !== void 0) {
|
|
8051
8276
|
try {
|
|
8052
8277
|
await options.onStored({ message, event: storedEvent, arrivedAt, source });
|
|
8053
|
-
} catch (
|
|
8054
|
-
failures.push({ phase: "view", class: "retryable-transient", viewId: "legacy-onStored", error: errorText(
|
|
8278
|
+
} catch (error11) {
|
|
8279
|
+
failures.push({ phase: "view", class: "retryable-transient", viewId: "legacy-onStored", error: errorText(error11) });
|
|
8055
8280
|
}
|
|
8056
8281
|
}
|
|
8057
8282
|
const preliminary = result(source, message, "stored", arrivedAt, detectorApplied, viewResults, failures, options.correlationId, startedAt);
|
|
8058
8283
|
if (options.afterDelivery !== void 0 && failures.every((failure3) => failure3.class === "duplicate-race")) {
|
|
8059
8284
|
try {
|
|
8060
8285
|
await options.afterDelivery({ message, event: storedEvent, arrivedAt, source, result: preliminary });
|
|
8061
|
-
} catch (
|
|
8062
|
-
failures.push({ phase: "drain", class: "retryable-transient", error: errorText(
|
|
8286
|
+
} catch (error11) {
|
|
8287
|
+
failures.push({ phase: "drain", class: "retryable-transient", error: errorText(error11) });
|
|
8063
8288
|
}
|
|
8064
8289
|
}
|
|
8065
8290
|
return result(source, message, "stored", arrivedAt, detectorApplied, viewResults, failures, options.correlationId, startedAt);
|
|
@@ -8081,8 +8306,8 @@ function asNumber(value, name) {
|
|
|
8081
8306
|
if (!Number.isSafeInteger(number) || number < 0) throw new Error(`G44 ${name} must be a non-negative safe integer`);
|
|
8082
8307
|
return number;
|
|
8083
8308
|
}
|
|
8084
|
-
function errorText2(
|
|
8085
|
-
return
|
|
8309
|
+
function errorText2(error11) {
|
|
8310
|
+
return error11 instanceof Error && error11.message.length > 0 ? error11.message : String(error11);
|
|
8086
8311
|
}
|
|
8087
8312
|
function settledFrontierFromCursor(cursorJson) {
|
|
8088
8313
|
const cursor = settledCursorFromJson(cursorJson);
|
|
@@ -8186,8 +8411,8 @@ var GlobalCompletenessReconciler = class {
|
|
|
8186
8411
|
let snapshots;
|
|
8187
8412
|
try {
|
|
8188
8413
|
snapshots = await this.snapshotPartitions(serviceId);
|
|
8189
|
-
} catch (
|
|
8190
|
-
const message = errorText2(
|
|
8414
|
+
} catch (error11) {
|
|
8415
|
+
const message = errorText2(error11);
|
|
8191
8416
|
await this.appendFinding(serviceId, {
|
|
8192
8417
|
identity: scannerFailureIdentity(serviceId),
|
|
8193
8418
|
type: "GLOBAL_ARRAY_SCANNER_FAILURE",
|
|
@@ -8229,7 +8454,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8229
8454
|
let joined;
|
|
8230
8455
|
try {
|
|
8231
8456
|
joined = this.options.globalReceiptMatcher === void 0 ? await this.globalReceiptMatches(snapshot.serviceId, snapshot.tag, obligation) : await this.options.globalReceiptMatcher(snapshot.serviceId, snapshot.tag, obligation);
|
|
8232
|
-
} catch (
|
|
8457
|
+
} catch (error11) {
|
|
8233
8458
|
await this.appendFinding(snapshot.serviceId, {
|
|
8234
8459
|
identity: `GLOBAL_RECEIPT_UNAVAILABLE|${snapshot.serviceId}|${snapshot.tag}|${obligation.obligationSequence}|${obligation.eventId}|${obligation.eventDigest}`,
|
|
8235
8460
|
type: "GLOBAL_ARRAY_RECEIPT_UNAVAILABLE",
|
|
@@ -8238,7 +8463,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8238
8463
|
eventId: obligation.eventId,
|
|
8239
8464
|
eventDigest: obligation.eventDigest
|
|
8240
8465
|
}, nowMs).catch(() => void 0);
|
|
8241
|
-
throw
|
|
8466
|
+
throw error11;
|
|
8242
8467
|
}
|
|
8243
8468
|
if (!joined) {
|
|
8244
8469
|
await this.appendObligationFinding(snapshot.serviceId, snapshot.tag, obligation, nowMs);
|
|
@@ -8257,8 +8482,8 @@ var GlobalCompletenessReconciler = class {
|
|
|
8257
8482
|
if (afterSequence !== snapshot.upperBoundSequence) {
|
|
8258
8483
|
throw new Error(`source_partition_range_incomplete:${snapshot.tag}`);
|
|
8259
8484
|
}
|
|
8260
|
-
} catch (
|
|
8261
|
-
if (errorText2(
|
|
8485
|
+
} catch (error11) {
|
|
8486
|
+
if (errorText2(error11).startsWith("source_")) {
|
|
8262
8487
|
await this.appendFinding(snapshot.serviceId, {
|
|
8263
8488
|
identity: sourceUnavailableIdentity(snapshot.serviceId, snapshot.tag),
|
|
8264
8489
|
type: "GLOBAL_ARRAY_SOURCE_PARTITION_UNAVAILABLE",
|
|
@@ -8268,7 +8493,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8268
8493
|
eventDigest: null
|
|
8269
8494
|
}, nowMs).catch(() => void 0);
|
|
8270
8495
|
}
|
|
8271
|
-
throw
|
|
8496
|
+
throw error11;
|
|
8272
8497
|
}
|
|
8273
8498
|
}
|
|
8274
8499
|
const endSnapshots = await this.snapshotPartitions(serviceId);
|
|
@@ -8287,8 +8512,8 @@ var GlobalCompletenessReconciler = class {
|
|
|
8287
8512
|
}
|
|
8288
8513
|
await this.writeHealth(serviceId, "HEALTHY", JSON.stringify(cursor), nowMs, null, nowMs);
|
|
8289
8514
|
return { kind: "FULL", partitions: snapshots, scannedObligations: scanned };
|
|
8290
|
-
} catch (
|
|
8291
|
-
const message = errorText2(
|
|
8515
|
+
} catch (error11) {
|
|
8516
|
+
const message = errorText2(error11);
|
|
8292
8517
|
const health = message.startsWith("source_") ? "UNKNOWN" : "FAILED";
|
|
8293
8518
|
if (health === "FAILED") {
|
|
8294
8519
|
await this.appendFinding(serviceId, {
|
|
@@ -8430,8 +8655,8 @@ var GlobalCompletenessReconciler = class {
|
|
|
8430
8655
|
* without applying views. It shares the scanner's single health/finding
|
|
8431
8656
|
* authority rather than creating a second incident workflow.
|
|
8432
8657
|
*/
|
|
8433
|
-
async recordDetectorFailure(serviceId,
|
|
8434
|
-
const message = `detector_failure:${errorText2(
|
|
8658
|
+
async recordDetectorFailure(serviceId, error11, nowMs) {
|
|
8659
|
+
const message = `detector_failure:${errorText2(error11).slice(0, 256)}`;
|
|
8435
8660
|
await this.appendFinding(serviceId, {
|
|
8436
8661
|
identity: `GLOBAL_DETECTOR_FAILURE|${serviceId}`,
|
|
8437
8662
|
type: "GLOBAL_ARRAY_DETECTOR_FAILURE",
|
|
@@ -8570,7 +8795,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8570
8795
|
nowMs
|
|
8571
8796
|
).run();
|
|
8572
8797
|
}
|
|
8573
|
-
async writeHealth(serviceId, status, cursor, lastFullScanAt,
|
|
8798
|
+
async writeHealth(serviceId, status, cursor, lastFullScanAt, error11, nowMs) {
|
|
8574
8799
|
await this.database.prepare(
|
|
8575
8800
|
`INSERT INTO serialized_dcb_completeness_scanner_health
|
|
8576
8801
|
(service_id, scanner_version, status, cursor_json, last_full_scan_at, last_error, updated_at)
|
|
@@ -8582,7 +8807,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8582
8807
|
last_full_scan_at = COALESCE(excluded.last_full_scan_at, serialized_dcb_completeness_scanner_health.last_full_scan_at),
|
|
8583
8808
|
last_error = excluded.last_error,
|
|
8584
8809
|
updated_at = excluded.updated_at`
|
|
8585
|
-
).bind(serviceId, this.scannerVersion, status, cursor, lastFullScanAt,
|
|
8810
|
+
).bind(serviceId, this.scannerVersion, status, cursor, lastFullScanAt, error11, nowMs).run();
|
|
8586
8811
|
}
|
|
8587
8812
|
};
|
|
8588
8813
|
function globalReceiptAcknowledgement(message, receivedAt) {
|
|
@@ -8674,7 +8899,7 @@ function sourceAcknowledgementOptions(env, options) {
|
|
|
8674
8899
|
if (decision.kind !== "SETTLED") {
|
|
8675
8900
|
throw new Error(`global_completeness_${decision.kind}:${decision.health.status}`);
|
|
8676
8901
|
}
|
|
8677
|
-
} catch (
|
|
8902
|
+
} catch (error11) {
|
|
8678
8903
|
if (!completed) {
|
|
8679
8904
|
observeG60PostAdmission(options.durableHopObserver, {
|
|
8680
8905
|
stage: "completeness-coverage",
|
|
@@ -8689,7 +8914,7 @@ function sourceAcknowledgementOptions(env, options) {
|
|
|
8689
8914
|
observedAt: Date.now()
|
|
8690
8915
|
});
|
|
8691
8916
|
}
|
|
8692
|
-
throw
|
|
8917
|
+
throw error11;
|
|
8693
8918
|
}
|
|
8694
8919
|
} : options.beforeViews;
|
|
8695
8920
|
const observedSourceAcknowledgement = sourceAcknowledgement === void 0 ? void 0 : async (input) => {
|
|
@@ -8697,14 +8922,14 @@ function sourceAcknowledgementOptions(env, options) {
|
|
|
8697
8922
|
try {
|
|
8698
8923
|
await sourceAcknowledgement(input);
|
|
8699
8924
|
observeSourceSubstep(options.durableHopObserver, { ...input, boundary: "end", outcome: "acknowledged" });
|
|
8700
|
-
} catch (
|
|
8925
|
+
} catch (error11) {
|
|
8701
8926
|
observeSourceSubstep(options.durableHopObserver, { ...input, boundary: "end", outcome: "error" });
|
|
8702
|
-
throw
|
|
8927
|
+
throw error11;
|
|
8703
8928
|
}
|
|
8704
8929
|
};
|
|
8705
|
-
const detectorFailure = hasG44Authority ? async ({ message, arrivedAt, error:
|
|
8706
|
-
await options.onDetectorFailure?.({ message, arrivedAt, error:
|
|
8707
|
-
await new GlobalCompletenessReconciler(env.D1, env.TAG).recordDetectorFailure(message.serviceId,
|
|
8930
|
+
const detectorFailure = hasG44Authority ? async ({ message, arrivedAt, error: error11, source }) => {
|
|
8931
|
+
await options.onDetectorFailure?.({ message, arrivedAt, error: error11, source });
|
|
8932
|
+
await new GlobalCompletenessReconciler(env.D1, env.TAG).recordDetectorFailure(message.serviceId, error11, arrivedAt);
|
|
8708
8933
|
} : options.onDetectorFailure;
|
|
8709
8934
|
return {
|
|
8710
8935
|
...options,
|
|
@@ -8719,9 +8944,9 @@ function sharedStore(env, provider) {
|
|
|
8719
8944
|
async function admitBootstrapRoute(env, serviceId, route) {
|
|
8720
8945
|
if (env.BOOTSTRAP === void 0) {
|
|
8721
8946
|
if (env.G38_DOORBELL_DELIVERY_ROLE === "receiver") {
|
|
8722
|
-
const
|
|
8723
|
-
|
|
8724
|
-
throw
|
|
8947
|
+
const error11 = new Error(`bootstrap_route_binding_missing:${route}`);
|
|
8948
|
+
error11.name = "BootstrapRouteBindingMissingError";
|
|
8949
|
+
throw error11;
|
|
8725
8950
|
}
|
|
8726
8951
|
return;
|
|
8727
8952
|
}
|
|
@@ -8810,10 +9035,10 @@ async function handleDownstreamQueue(batch, env, options = {}) {
|
|
|
8810
9035
|
if (outcome.outcome === "stored" && !outcome.failures.some((failure3) => failure3.phase === "recordDelivery")) {
|
|
8811
9036
|
try {
|
|
8812
9037
|
options.afterStoredQueueDelivery?.({ message: queued.body, result: outcome });
|
|
8813
|
-
} catch (
|
|
9038
|
+
} catch (error11) {
|
|
8814
9039
|
console.warn("safe_lane_kick", {
|
|
8815
9040
|
status: "failed-to-schedule",
|
|
8816
|
-
error:
|
|
9041
|
+
error: error11 instanceof Error ? error11.message : String(error11),
|
|
8817
9042
|
serviceId: queued.body.serviceId,
|
|
8818
9043
|
eventId: queued.body.eventId,
|
|
8819
9044
|
attemptId: queued.body.attemptId
|
|
@@ -8835,6 +9060,36 @@ async function stabilizeDownstream(env, options = {}, serviceId, serviceIdentity
|
|
|
8835
9060
|
});
|
|
8836
9061
|
}
|
|
8837
9062
|
|
|
9063
|
+
// src/scope/ControlRouteScope.ts
|
|
9064
|
+
function response2(status, code, error11) {
|
|
9065
|
+
return new Response(JSON.stringify({ code, error: error11 }), {
|
|
9066
|
+
status,
|
|
9067
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
9068
|
+
});
|
|
9069
|
+
}
|
|
9070
|
+
function scopeIdentityMissingResponse() {
|
|
9071
|
+
return response2(503, "scope.identity_missing", "A deployment service identity is required for this scope");
|
|
9072
|
+
}
|
|
9073
|
+
function enforceControlRouteScope(input) {
|
|
9074
|
+
let actual;
|
|
9075
|
+
try {
|
|
9076
|
+
actual = input.provider.forRequest(input.request, input.requestOptions).serviceId;
|
|
9077
|
+
} catch (caught) {
|
|
9078
|
+
if (caught instanceof ServiceIdentityMissingError) {
|
|
9079
|
+
console.error({ schema: "sdt.scope/v1", code: "scope.identity_missing", route: input.route });
|
|
9080
|
+
return { response: scopeIdentityMissingResponse() };
|
|
9081
|
+
}
|
|
9082
|
+
throw caught;
|
|
9083
|
+
}
|
|
9084
|
+
if (input.pathServiceId !== actual) {
|
|
9085
|
+
console.warn({ schema: "sdt.scope/v1", code: "scope.mismatch", route: input.route });
|
|
9086
|
+
return {
|
|
9087
|
+
response: response2(403, "scope.mismatch", "The path service identity is not authorized for this deployment")
|
|
9088
|
+
};
|
|
9089
|
+
}
|
|
9090
|
+
return { serviceId: actual };
|
|
9091
|
+
}
|
|
9092
|
+
|
|
8838
9093
|
// src/downstream/OutboxDrain.ts
|
|
8839
9094
|
function json5(body, status = 200) {
|
|
8840
9095
|
return new Response(JSON.stringify(body), {
|
|
@@ -8931,15 +9186,32 @@ async function handleOutboxDrainRequest(request, env, options = {}) {
|
|
|
8931
9186
|
if (!isDrainRequest(body)) {
|
|
8932
9187
|
return json5({ error: "serviceId and unique non-empty tags are required", code: "invalid_outbox_drain_request" }, 400);
|
|
8933
9188
|
}
|
|
9189
|
+
const requestedServiceId = body.serviceId;
|
|
9190
|
+
const tags = body.tags;
|
|
9191
|
+
const provider = options.serviceIdentityProvider ?? envServiceIdentity(env);
|
|
9192
|
+
let authority;
|
|
9193
|
+
try {
|
|
9194
|
+
authority = provider.forRequest(request).serviceId;
|
|
9195
|
+
} catch (caught) {
|
|
9196
|
+
if (caught instanceof ServiceIdentityMissingError) return scopeIdentityMissingResponse();
|
|
9197
|
+
throw caught;
|
|
9198
|
+
}
|
|
9199
|
+
if (requestedServiceId !== authority) {
|
|
9200
|
+
console.warn({ schema: "sdt.scope/v1", code: "scope.mismatch", route: "outbox-drain" });
|
|
9201
|
+
return json5({
|
|
9202
|
+
code: "scope.mismatch",
|
|
9203
|
+
error: "The request service identity is not authorized for this deployment"
|
|
9204
|
+
}, 403);
|
|
9205
|
+
}
|
|
8934
9206
|
try {
|
|
8935
9207
|
const results = [];
|
|
8936
|
-
for (const tag of
|
|
8937
|
-
results.push(await drainTagOutbox({ serviceId:
|
|
9208
|
+
for (const tag of tags) {
|
|
9209
|
+
results.push(await drainTagOutbox({ serviceId: authority, tag }, env, systemPipelineClock, options));
|
|
8938
9210
|
}
|
|
8939
9211
|
return json5({ results });
|
|
8940
|
-
} catch (
|
|
9212
|
+
} catch (error11) {
|
|
8941
9213
|
return json5({
|
|
8942
|
-
error:
|
|
9214
|
+
error: error11 instanceof Error ? error11.message : "Outbox drain failed",
|
|
8943
9215
|
code: "outbox_drain_failed"
|
|
8944
9216
|
}, 503);
|
|
8945
9217
|
}
|
|
@@ -9384,9 +9656,9 @@ function isNonEmptyString7(value) {
|
|
|
9384
9656
|
return typeof value === "string" && value.length > 0;
|
|
9385
9657
|
}
|
|
9386
9658
|
function base64Json(value) {
|
|
9387
|
-
const
|
|
9659
|
+
const bytes2 = new TextEncoder().encode(JSON.stringify(value));
|
|
9388
9660
|
let binary = "";
|
|
9389
|
-
for (const byte of
|
|
9661
|
+
for (const byte of bytes2) {
|
|
9390
9662
|
binary += String.fromCharCode(byte);
|
|
9391
9663
|
}
|
|
9392
9664
|
return btoa(binary);
|
|
@@ -9564,9 +9836,9 @@ function decodeStoredJson(value) {
|
|
|
9564
9836
|
}
|
|
9565
9837
|
}
|
|
9566
9838
|
function jsonBytes(value) {
|
|
9567
|
-
const
|
|
9839
|
+
const bytes2 = new TextEncoder().encode(JSON.stringify(value));
|
|
9568
9840
|
let binary = "";
|
|
9569
|
-
for (const byte of
|
|
9841
|
+
for (const byte of bytes2) binary += String.fromCharCode(byte);
|
|
9570
9842
|
return btoa(binary);
|
|
9571
9843
|
}
|
|
9572
9844
|
function isRuntimeBridgedProjector(definition) {
|
|
@@ -9639,9 +9911,9 @@ function isRuntimeCommandLike(value) {
|
|
|
9639
9911
|
return typeof candidate.id === "string" && candidate.id.length > 0 && typeof candidate.name === "string" && typeof candidate.parseInput === "function" && typeof candidate.execute === "function" && typeof candidate.handle === "function";
|
|
9640
9912
|
}
|
|
9641
9913
|
function encodeJsonPayload(value) {
|
|
9642
|
-
const
|
|
9914
|
+
const bytes2 = new TextEncoder().encode(JSON.stringify(value));
|
|
9643
9915
|
let binary = "";
|
|
9644
|
-
for (const byte of
|
|
9916
|
+
for (const byte of bytes2) binary += String.fromCharCode(byte);
|
|
9645
9917
|
return btoa(binary);
|
|
9646
9918
|
}
|
|
9647
9919
|
function candidateToV1Envelope(candidate) {
|
|
@@ -9825,8 +10097,8 @@ function stateFromCheckpoint(projector, checkpoint) {
|
|
|
9825
10097
|
if (checkpoint === void 0) return projector.initialState();
|
|
9826
10098
|
try {
|
|
9827
10099
|
return projector.deserializeState(checkpoint.stateJson);
|
|
9828
|
-
} catch (
|
|
9829
|
-
throw new ProjectionCheckpointCorruption(
|
|
10100
|
+
} catch (error11) {
|
|
10101
|
+
throw new ProjectionCheckpointCorruption(error11);
|
|
9830
10102
|
}
|
|
9831
10103
|
}
|
|
9832
10104
|
function projectionEventFromStored(event) {
|
|
@@ -10050,8 +10322,8 @@ async function admitBootstrapRoute2(env, serviceId) {
|
|
|
10050
10322
|
})).fetch(new Request(url, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ route: "projection-rebuild" }) }));
|
|
10051
10323
|
if (!admitted.ok) throw new Error("bootstrap_route_rejected:projection-rebuild");
|
|
10052
10324
|
}
|
|
10053
|
-
function boundedErrorReason(
|
|
10054
|
-
const value =
|
|
10325
|
+
function boundedErrorReason(error11) {
|
|
10326
|
+
const value = error11 instanceof Error ? error11.message : String(error11);
|
|
10055
10327
|
return value.length > 256 ? `${value.slice(0, 253)}...` : value;
|
|
10056
10328
|
}
|
|
10057
10329
|
async function notifyObserver(observer, callback, input) {
|
|
@@ -10064,7 +10336,7 @@ async function notifyObserver(observer, callback, input) {
|
|
|
10064
10336
|
} catch {
|
|
10065
10337
|
}
|
|
10066
10338
|
}
|
|
10067
|
-
async function notifyOutcomes(observer, serviceId, projectorIds, attemptedAt, results, maximumSuid, env,
|
|
10339
|
+
async function notifyOutcomes(observer, serviceId, projectorIds, attemptedAt, results, maximumSuid, env, error11) {
|
|
10068
10340
|
if (observer?.onOutcome === void 0) return;
|
|
10069
10341
|
const byProjector = /* @__PURE__ */ new Map();
|
|
10070
10342
|
for (const result2 of results) {
|
|
@@ -10077,9 +10349,9 @@ async function notifyOutcomes(observer, serviceId, projectorIds, attemptedAt, re
|
|
|
10077
10349
|
let outcome = "invoked-but-no-work";
|
|
10078
10350
|
let reason = null;
|
|
10079
10351
|
let advancedSourceEvents = 0;
|
|
10080
|
-
if (
|
|
10352
|
+
if (error11 !== void 0) {
|
|
10081
10353
|
outcome = "invoked-and-threw";
|
|
10082
|
-
reason = boundedErrorReason(
|
|
10354
|
+
reason = boundedErrorReason(error11);
|
|
10083
10355
|
} else {
|
|
10084
10356
|
advancedSourceEvents = projectorResults.reduce((total, result2) => total + result2.advancedSourceEvents, 0);
|
|
10085
10357
|
if (advancedSourceEvents > 0) {
|
|
@@ -10181,9 +10453,9 @@ async function pollLiveProjections(env, options = {}) {
|
|
|
10181
10453
|
const results = await runtime.pollRegistered(serviceId, attemptedAt, options.maximumSuid);
|
|
10182
10454
|
await notifyOutcomes(options.observer, serviceId, projectorIds, attemptedAt, results, options.maximumSuid, env);
|
|
10183
10455
|
return results;
|
|
10184
|
-
} catch (
|
|
10185
|
-
await notifyOutcomes(options.observer, serviceId, projectorIds, attemptedAt, [], options.maximumSuid, env,
|
|
10186
|
-
throw
|
|
10456
|
+
} catch (error11) {
|
|
10457
|
+
await notifyOutcomes(options.observer, serviceId, projectorIds, attemptedAt, [], options.maximumSuid, env, error11);
|
|
10458
|
+
throw error11;
|
|
10187
10459
|
}
|
|
10188
10460
|
}
|
|
10189
10461
|
async function handleProjectionLag(request, env, registry = DEPLOYED_PROJECTOR_REGISTRY, storeProvider, serviceIdentityProvider) {
|
|
@@ -10421,15 +10693,15 @@ function integer(row, key) {
|
|
|
10421
10693
|
function operationId() {
|
|
10422
10694
|
return `mv-unsafe-${crypto.randomUUID()}`;
|
|
10423
10695
|
}
|
|
10424
|
-
function errorFrom(
|
|
10425
|
-
const value = String(
|
|
10696
|
+
function errorFrom(error11) {
|
|
10697
|
+
const value = String(error11);
|
|
10426
10698
|
if (value.includes("mv_unsafe_receipts") && (value.includes("UNIQUE") || value.includes("constraint"))) {
|
|
10427
|
-
return new UnsafeWindowMaterializedViewError("UNSAFE_DUPLICATE_RACE", false, "Concurrent unsafe receipt was committed by another delivery", { cause:
|
|
10699
|
+
return new UnsafeWindowMaterializedViewError("UNSAFE_DUPLICATE_RACE", false, "Concurrent unsafe receipt was committed by another delivery", { cause: error11 });
|
|
10428
10700
|
}
|
|
10429
|
-
if (value.includes("unsafe_row_cas")) return new UnsafeWindowMaterializedViewError("UNSAFE_ROW_CAS_MISMATCH", true, "Unsafe row version changed before atomic apply", { cause:
|
|
10430
|
-
if (value.includes("unsafe_suid_contradiction")) return new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Unsafe same-SUID payload contradiction", { cause:
|
|
10431
|
-
if (value.includes("unsafe_safe_ahead")) return new UnsafeWindowMaterializedViewError("UNSAFE_SAFE_AHEAD", false, "Safe head already covers unsafe event", { cause:
|
|
10432
|
-
if (value.includes("unsafe_behind_frontier")) return new UnsafeWindowMaterializedViewError("UNSAFE_BEHIND_FRONTIER", false, "Unsafe reads are fail-closed pending rebuild", { cause:
|
|
10701
|
+
if (value.includes("unsafe_row_cas")) return new UnsafeWindowMaterializedViewError("UNSAFE_ROW_CAS_MISMATCH", true, "Unsafe row version changed before atomic apply", { cause: error11 });
|
|
10702
|
+
if (value.includes("unsafe_suid_contradiction")) return new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Unsafe same-SUID payload contradiction", { cause: error11 });
|
|
10703
|
+
if (value.includes("unsafe_safe_ahead")) return new UnsafeWindowMaterializedViewError("UNSAFE_SAFE_AHEAD", false, "Safe head already covers unsafe event", { cause: error11 });
|
|
10704
|
+
if (value.includes("unsafe_behind_frontier")) return new UnsafeWindowMaterializedViewError("UNSAFE_BEHIND_FRONTIER", false, "Unsafe reads are fail-closed pending rebuild", { cause: error11 });
|
|
10433
10705
|
return void 0;
|
|
10434
10706
|
}
|
|
10435
10707
|
var UnsafeWindowMaterializedViewStore = class {
|
|
@@ -10562,8 +10834,8 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
10562
10834
|
await this.options.beforeApplyBatch?.(input, statements);
|
|
10563
10835
|
try {
|
|
10564
10836
|
await this.database.batch(statements);
|
|
10565
|
-
} catch (
|
|
10566
|
-
const text = String(
|
|
10837
|
+
} catch (error11) {
|
|
10838
|
+
const text = String(error11);
|
|
10567
10839
|
const guardFailure = text.includes("mv_atomic_guards") || text.includes("checkpoint_match") || text.includes("mv_unsafe_receipts") && (text.includes("UNIQUE") || text.includes("constraint"));
|
|
10568
10840
|
if (guardFailure) {
|
|
10569
10841
|
const receipt = await this.database.prepare(
|
|
@@ -10571,16 +10843,16 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
10571
10843
|
).bind(input.serviceId, input.viewId, input.eventId).first();
|
|
10572
10844
|
if (receipt !== null && receipt !== void 0) {
|
|
10573
10845
|
if (string2(receipt, "suid") !== input.suid) {
|
|
10574
|
-
throw new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Duplicate event identity has a contradictory SUID", { cause:
|
|
10846
|
+
throw new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Duplicate event identity has a contradictory SUID", { cause: error11 });
|
|
10575
10847
|
}
|
|
10576
10848
|
if (this.options.beforeApplyBatch !== void 0) {
|
|
10577
|
-
throw new UnsafeWindowMaterializedViewError("UNSAFE_DUPLICATE_RACE", false, "Concurrent unsafe receipt was committed by another delivery", { cause:
|
|
10849
|
+
throw new UnsafeWindowMaterializedViewError("UNSAFE_DUPLICATE_RACE", false, "Concurrent unsafe receipt was committed by another delivery", { cause: error11 });
|
|
10578
10850
|
}
|
|
10579
10851
|
return { outcome: "no-change", duplicate: true };
|
|
10580
10852
|
}
|
|
10581
10853
|
return this.apply(input, true);
|
|
10582
10854
|
}
|
|
10583
|
-
throw
|
|
10855
|
+
throw error11;
|
|
10584
10856
|
}
|
|
10585
10857
|
const result2 = { outcome: "applied", duplicate: false };
|
|
10586
10858
|
await this.options.afterApplyBatch?.(input, result2);
|
|
@@ -10713,13 +10985,13 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
10713
10985
|
await this.options.beforeApplyBatch?.(input, statements);
|
|
10714
10986
|
try {
|
|
10715
10987
|
await this.database.batch(statements);
|
|
10716
|
-
} catch (
|
|
10717
|
-
if (sameSuidContradiction) throw new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Unsafe same-SUID payload contradiction", { cause:
|
|
10718
|
-
if (rowCasMismatch) throw new UnsafeWindowMaterializedViewError("UNSAFE_ROW_CAS_MISMATCH", true, "Unsafe row version changed before atomic apply", { cause:
|
|
10719
|
-
if (safeAhead) throw new UnsafeWindowMaterializedViewError("UNSAFE_SAFE_AHEAD", false, "Safe head already covers unsafe event", { cause:
|
|
10720
|
-
const typed = errorFrom(
|
|
10988
|
+
} catch (error11) {
|
|
10989
|
+
if (sameSuidContradiction) throw new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Unsafe same-SUID payload contradiction", { cause: error11 });
|
|
10990
|
+
if (rowCasMismatch) throw new UnsafeWindowMaterializedViewError("UNSAFE_ROW_CAS_MISMATCH", true, "Unsafe row version changed before atomic apply", { cause: error11 });
|
|
10991
|
+
if (safeAhead) throw new UnsafeWindowMaterializedViewError("UNSAFE_SAFE_AHEAD", false, "Safe head already covers unsafe event", { cause: error11 });
|
|
10992
|
+
const typed = errorFrom(error11);
|
|
10721
10993
|
if (typed !== void 0) throw typed;
|
|
10722
|
-
throw
|
|
10994
|
+
throw error11;
|
|
10723
10995
|
}
|
|
10724
10996
|
const result2 = { outcome, duplicate: false };
|
|
10725
10997
|
await this.options.afterApplyBatch?.(input, result2);
|
|
@@ -10786,8 +11058,8 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
10786
11058
|
this.database.prepare(`UPDATE mv_unsafe_kicks SET lease_owner = ?, lease_until = ?, dirty = 0 WHERE service_id = ? AND view_id = ?`).bind(owner, nowMs + leaseMs, serviceId, viewId),
|
|
10787
11059
|
this.database.prepare("DELETE FROM mv_atomic_guards WHERE operation_id = ?").bind(operation)
|
|
10788
11060
|
]);
|
|
10789
|
-
} catch (
|
|
10790
|
-
throw new UnsafeWindowMaterializedViewError("UNSAFE_KICK_LEASE_HELD", true, "Unsafe kick is held by another lease owner", { cause:
|
|
11061
|
+
} catch (error11) {
|
|
11062
|
+
throw new UnsafeWindowMaterializedViewError("UNSAFE_KICK_LEASE_HELD", true, "Unsafe kick is held by another lease owner", { cause: error11 });
|
|
10791
11063
|
}
|
|
10792
11064
|
const row = await this.database.prepare(`SELECT target_suid, dirty FROM mv_unsafe_kicks WHERE service_id = ? AND view_id = ?`).bind(serviceId, viewId).first();
|
|
10793
11065
|
return row === null || row === void 0 ? void 0 : { targetSuid: string2(row, "target_suid"), dirty: integer(row, "dirty") === 1 };
|
|
@@ -10861,9 +11133,9 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
10861
11133
|
try {
|
|
10862
11134
|
await this.database.batch(statements);
|
|
10863
11135
|
return true;
|
|
10864
|
-
} catch (
|
|
10865
|
-
if (String(
|
|
10866
|
-
throw
|
|
11136
|
+
} catch (error11) {
|
|
11137
|
+
if (String(error11).includes("mv_atomic_guards") || String(error11).includes("checkpoint_match")) return false;
|
|
11138
|
+
throw error11;
|
|
10867
11139
|
}
|
|
10868
11140
|
}
|
|
10869
11141
|
/**
|
|
@@ -11029,12 +11301,12 @@ function indexValue(row, type) {
|
|
|
11029
11301
|
function operationId2() {
|
|
11030
11302
|
return `mv-${crypto.randomUUID()}`;
|
|
11031
11303
|
}
|
|
11032
|
-
function isCasFailure(
|
|
11033
|
-
const message = String(
|
|
11304
|
+
function isCasFailure(error11) {
|
|
11305
|
+
const message = String(error11);
|
|
11034
11306
|
return message.includes("mv_atomic_guards.checkpoint_match") || message.includes("MV_CAS_MISMATCH");
|
|
11035
11307
|
}
|
|
11036
|
-
function isPatchFailure(
|
|
11037
|
-
return String(
|
|
11308
|
+
function isPatchFailure(error11) {
|
|
11309
|
+
return String(error11).includes("CHECK constraint failed: checkpoint_match");
|
|
11038
11310
|
}
|
|
11039
11311
|
var D1MaterializedViewStore = class {
|
|
11040
11312
|
constructor(database) {
|
|
@@ -11070,8 +11342,8 @@ var D1MaterializedViewStore = class {
|
|
|
11070
11342
|
VALUES (?, ?, ?, ?)`
|
|
11071
11343
|
).bind(input.serviceId, input.viewId, generation, input.updatedAt)
|
|
11072
11344
|
]);
|
|
11073
|
-
} catch (
|
|
11074
|
-
throw new MaterializedViewStoreError("create-active", "MV_INSTANCE_EXISTS", `Active materialized view already exists: ${String(
|
|
11345
|
+
} catch (error11) {
|
|
11346
|
+
throw new MaterializedViewStoreError("create-active", "MV_INSTANCE_EXISTS", `Active materialized view already exists: ${String(error11)}`, { cause: error11 });
|
|
11075
11347
|
}
|
|
11076
11348
|
return this.readInstance(input.serviceId, input.viewId, generation);
|
|
11077
11349
|
}
|
|
@@ -11092,8 +11364,8 @@ var D1MaterializedViewStore = class {
|
|
|
11092
11364
|
input.definitionVersion,
|
|
11093
11365
|
input.updatedAt
|
|
11094
11366
|
).run();
|
|
11095
|
-
} catch (
|
|
11096
|
-
throw new MaterializedViewStoreError("create-candidate", "MV_INSTANCE_EXISTS", `Candidate generation already exists: ${String(
|
|
11367
|
+
} catch (error11) {
|
|
11368
|
+
throw new MaterializedViewStoreError("create-candidate", "MV_INSTANCE_EXISTS", `Candidate generation already exists: ${String(error11)}`, { cause: error11 });
|
|
11097
11369
|
}
|
|
11098
11370
|
return this.readInstance(input.serviceId, input.viewId, input.generation);
|
|
11099
11371
|
}
|
|
@@ -11231,10 +11503,10 @@ var D1MaterializedViewStore = class {
|
|
|
11231
11503
|
*/
|
|
11232
11504
|
async readListPage(serviceId, viewId, options = {}) {
|
|
11233
11505
|
this.ready("initialize");
|
|
11234
|
-
const
|
|
11235
|
-
if (
|
|
11236
|
-
const selected = options.generation ??
|
|
11237
|
-
const selectedInstance = selected ===
|
|
11506
|
+
const active2 = await this.readActive(serviceId, viewId);
|
|
11507
|
+
if (active2 === void 0) return { rows: [], totalCount: 0, readHead: "" };
|
|
11508
|
+
const selected = options.generation ?? active2.generation;
|
|
11509
|
+
const selectedInstance = selected === active2.generation ? active2 : await this.readInstance(serviceId, viewId, selected);
|
|
11238
11510
|
if (selectedInstance === void 0) {
|
|
11239
11511
|
throw new MaterializedViewStoreError("initialize", "MV_INSTANCE_MISSING", "Requested materialized-view generation is unavailable");
|
|
11240
11512
|
}
|
|
@@ -11751,10 +12023,10 @@ var D1MaterializedViewStore = class {
|
|
|
11751
12023
|
).bind(operation, `${operation}:%`));
|
|
11752
12024
|
try {
|
|
11753
12025
|
await this.database.batch(statements);
|
|
11754
|
-
} catch (
|
|
11755
|
-
if (isPatchFailure(
|
|
11756
|
-
if (isCasFailure(
|
|
11757
|
-
throw new MaterializedViewStoreError("apply", "MV_STORE_OPERATION_FAILED", `Materialized-view atomic apply failed: ${String(
|
|
12026
|
+
} catch (error11) {
|
|
12027
|
+
if (isPatchFailure(error11)) throw new MaterializedViewPatchError();
|
|
12028
|
+
if (isCasFailure(error11)) throw new MaterializedViewCasError();
|
|
12029
|
+
throw new MaterializedViewStoreError("apply", "MV_STORE_OPERATION_FAILED", `Materialized-view atomic apply failed: ${String(error11)}`, { cause: error11 });
|
|
11758
12030
|
}
|
|
11759
12031
|
const instance = await this.readInstance(input.serviceId, input.viewId, input.generation);
|
|
11760
12032
|
if (instance === void 0) throw new MaterializedViewStoreError("apply", "MV_INSTANCE_MISSING", "Materialized-view instance disappeared after apply");
|
|
@@ -11859,13 +12131,13 @@ var D1MaterializedViewStore = class {
|
|
|
11859
12131
|
];
|
|
11860
12132
|
try {
|
|
11861
12133
|
await this.database.batch(statements);
|
|
11862
|
-
} catch (
|
|
11863
|
-
if (isCasFailure(
|
|
11864
|
-
throw new MaterializedViewStoreError("promote", "MV_STORE_OPERATION_FAILED", `Materialized-view promotion failed: ${String(
|
|
12134
|
+
} catch (error11) {
|
|
12135
|
+
if (isCasFailure(error11)) throw new MaterializedViewPromotionCasError();
|
|
12136
|
+
throw new MaterializedViewStoreError("promote", "MV_STORE_OPERATION_FAILED", `Materialized-view promotion failed: ${String(error11)}`, { cause: error11 });
|
|
11865
12137
|
}
|
|
11866
|
-
const
|
|
11867
|
-
if (
|
|
11868
|
-
return
|
|
12138
|
+
const active2 = await this.readActive(input.serviceId, input.viewId);
|
|
12139
|
+
if (active2 === void 0) throw new MaterializedViewStoreError("promote", "MV_INSTANCE_MISSING", "Active generation disappeared after promotion");
|
|
12140
|
+
return active2;
|
|
11869
12141
|
}
|
|
11870
12142
|
/** Short aliases make the port convenient for runtime composition and tests. */
|
|
11871
12143
|
apply = this.applyMutationsAndAdvanceCheckpoint.bind(this);
|
|
@@ -11874,8 +12146,8 @@ var D1MaterializedViewStore = class {
|
|
|
11874
12146
|
let value;
|
|
11875
12147
|
try {
|
|
11876
12148
|
value = assertJsonValue(JSON.parse(asString2(row.value_json, "value_json")), "state-persistence");
|
|
11877
|
-
} catch (
|
|
11878
|
-
throw new MaterializedViewStoreError("initialize", "MV_VALUE_INVALID", `Materialized-view row JSON was invalid: ${String(
|
|
12149
|
+
} catch (error11) {
|
|
12150
|
+
throw new MaterializedViewStoreError("initialize", "MV_VALUE_INVALID", `Materialized-view row JSON was invalid: ${String(error11)}`, { cause: error11 });
|
|
11879
12151
|
}
|
|
11880
12152
|
return {
|
|
11881
12153
|
serviceId: asString2(row.service_id, "service_id"),
|
|
@@ -11956,6 +12228,33 @@ function parseRequest(value) {
|
|
|
11956
12228
|
}
|
|
11957
12229
|
};
|
|
11958
12230
|
}
|
|
12231
|
+
var QUERY_PARAMS_UNSUPPORTED_MESSAGE = "The selected query does not support these parameters.";
|
|
12232
|
+
var LIST_QUERY_PARAM_KEYS = /* @__PURE__ */ new Set(["PageNumber", "PageSize", "NewestFirst", "consistency"]);
|
|
12233
|
+
function unsupportedQueryParams(endpoint, queryParams) {
|
|
12234
|
+
if (endpoint === "query") {
|
|
12235
|
+
if (isObject9(queryParams) && Object.keys(queryParams).length === 0) {
|
|
12236
|
+
return void 0;
|
|
12237
|
+
}
|
|
12238
|
+
return {
|
|
12239
|
+
status: 400,
|
|
12240
|
+
code: "query_params_unsupported",
|
|
12241
|
+
error: QUERY_PARAMS_UNSUPPORTED_MESSAGE
|
|
12242
|
+
};
|
|
12243
|
+
}
|
|
12244
|
+
if (!isObject9(queryParams)) {
|
|
12245
|
+
return void 0;
|
|
12246
|
+
}
|
|
12247
|
+
for (const key of Object.keys(queryParams)) {
|
|
12248
|
+
if (!LIST_QUERY_PARAM_KEYS.has(key)) {
|
|
12249
|
+
return {
|
|
12250
|
+
status: 400,
|
|
12251
|
+
code: "query_params_unsupported",
|
|
12252
|
+
error: QUERY_PARAMS_UNSUPPORTED_MESSAGE
|
|
12253
|
+
};
|
|
12254
|
+
}
|
|
12255
|
+
}
|
|
12256
|
+
return void 0;
|
|
12257
|
+
}
|
|
11959
12258
|
function paginationFrom(value) {
|
|
11960
12259
|
if (!isObject9(value)) {
|
|
11961
12260
|
return { error: "list-query queryParamsJson must contain an object" };
|
|
@@ -12136,6 +12435,10 @@ async function handleSerializedQuery(request, env, options = {}) {
|
|
|
12136
12435
|
if (definition === void 0 || definition.endpoint !== endpoint) {
|
|
12137
12436
|
return error6(400, "validation_error", "queryType is not mapped for this endpoint");
|
|
12138
12437
|
}
|
|
12438
|
+
const unsupported = unsupportedQueryParams(endpoint, parsed.value.queryParams);
|
|
12439
|
+
if (unsupported !== void 0) {
|
|
12440
|
+
return error6(unsupported.status, unsupported.code, unsupported.error);
|
|
12441
|
+
}
|
|
12139
12442
|
if (!definition.enabled || (options.projectors ?? DEPLOYED_PROJECTOR_REGISTRY).resolve(definition.tagProjector) === void 0) {
|
|
12140
12443
|
return error6(503, "projection_unavailable", "The mapped query projection is unavailable");
|
|
12141
12444
|
}
|
|
@@ -12522,10 +12825,10 @@ var SerializedReadWorker = class {
|
|
|
12522
12825
|
});
|
|
12523
12826
|
}
|
|
12524
12827
|
async ensureWindowDeterminate() {
|
|
12525
|
-
if (this.storeProvider === void 0 || this.storeProvider.isConfigured?.(this.env) === false) {
|
|
12526
|
-
return;
|
|
12527
|
-
}
|
|
12528
12828
|
try {
|
|
12829
|
+
if (this.storeProvider === void 0 || this.storeProvider.isConfigured?.(this.env) === false) {
|
|
12830
|
+
return;
|
|
12831
|
+
}
|
|
12529
12832
|
const store = this.storeProvider.create(this.env);
|
|
12530
12833
|
await store.initialize();
|
|
12531
12834
|
const nowMs = Date.now();
|
|
@@ -12566,13 +12869,662 @@ async function handleSerializedRead(request, env, registry = DEPLOYED_PROJECTOR_
|
|
|
12566
12869
|
}), registry, storeProvider).handle(request);
|
|
12567
12870
|
}
|
|
12568
12871
|
|
|
12872
|
+
// src/completeness/IncidentLifecycle.ts
|
|
12873
|
+
var MAX_IDENTITY_BYTES = 256;
|
|
12874
|
+
var MAX_LONG_TEXT_BYTES = 2048;
|
|
12875
|
+
var SHA256_DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
12876
|
+
var IncidentLifecycleError = class extends Error {
|
|
12877
|
+
code;
|
|
12878
|
+
status;
|
|
12879
|
+
constructor(code, status, message) {
|
|
12880
|
+
super(message);
|
|
12881
|
+
this.name = "IncidentLifecycleError";
|
|
12882
|
+
this.code = code;
|
|
12883
|
+
this.status = status;
|
|
12884
|
+
}
|
|
12885
|
+
};
|
|
12886
|
+
function isObject11(value) {
|
|
12887
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12888
|
+
}
|
|
12889
|
+
function hasOnlyKeys(value, keys) {
|
|
12890
|
+
const allowed = new Set(keys);
|
|
12891
|
+
return Object.keys(value).every((key) => allowed.has(key));
|
|
12892
|
+
}
|
|
12893
|
+
function bytes(value) {
|
|
12894
|
+
return new TextEncoder().encode(value).byteLength;
|
|
12895
|
+
}
|
|
12896
|
+
function boundedString(value, name, maximum, required = true) {
|
|
12897
|
+
if (typeof value !== "string") {
|
|
12898
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, `${name} must be a string`);
|
|
12899
|
+
}
|
|
12900
|
+
const normalized = value.trim();
|
|
12901
|
+
if (required && normalized.length === 0) {
|
|
12902
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, `${name} must be non-empty`);
|
|
12903
|
+
}
|
|
12904
|
+
if (bytes(normalized) > maximum) {
|
|
12905
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, `${name} is too long`);
|
|
12906
|
+
}
|
|
12907
|
+
return normalized.length === 0 ? null : normalized;
|
|
12908
|
+
}
|
|
12909
|
+
function normalizeIncidentIdentity(value) {
|
|
12910
|
+
const identity2 = boundedString(value, "incidentIdentity", MAX_IDENTITY_BYTES);
|
|
12911
|
+
if (identity2 === null) {
|
|
12912
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "incidentIdentity must be non-empty");
|
|
12913
|
+
}
|
|
12914
|
+
return identity2;
|
|
12915
|
+
}
|
|
12916
|
+
function safeInteger(value, name) {
|
|
12917
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value)) {
|
|
12918
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, `${name} must be a safe integer`);
|
|
12919
|
+
}
|
|
12920
|
+
return value;
|
|
12921
|
+
}
|
|
12922
|
+
function normalizeCorrection(value) {
|
|
12923
|
+
if (!isObject11(value) || !hasOnlyKeys(value, ["kind", "reference", "digest"])) {
|
|
12924
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "correction has unknown or missing keys");
|
|
12925
|
+
}
|
|
12926
|
+
if (value.kind !== "event" && value.kind !== "receipt") {
|
|
12927
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "correction.kind must be event or receipt");
|
|
12928
|
+
}
|
|
12929
|
+
const reference = boundedString(value.reference, "correction.reference", MAX_LONG_TEXT_BYTES);
|
|
12930
|
+
const digest = boundedString(value.digest, "correction.digest", MAX_IDENTITY_BYTES);
|
|
12931
|
+
if (reference === null || digest === null || !SHA256_DIGEST.test(digest)) {
|
|
12932
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "correction.digest must be lowercase sha256");
|
|
12933
|
+
}
|
|
12934
|
+
return { kind: value.kind, reference, digest };
|
|
12935
|
+
}
|
|
12936
|
+
function normalizeRequest(value) {
|
|
12937
|
+
if (!isObject11(value) || !hasOnlyKeys(value, ["action", "incidentIdentity", "transitionKey", "expectedVersion", "reason", "ownerId", "deadlineAt", "correction", "resolution"])) {
|
|
12938
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "transition has unknown keys");
|
|
12939
|
+
}
|
|
12940
|
+
const action = value.action;
|
|
12941
|
+
if (typeof action !== "string" || !["ACKNOWLEDGE", "UPDATE_ASSIGNMENT", "RECORD_CORRECTION", "CLOSE", "REOPEN"].includes(action)) {
|
|
12942
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "transition action is invalid");
|
|
12943
|
+
}
|
|
12944
|
+
const actionKeys = action === "RECORD_CORRECTION" ? ["action", "incidentIdentity", "transitionKey", "expectedVersion", "reason", "correction"] : action === "CLOSE" ? ["action", "incidentIdentity", "transitionKey", "expectedVersion", "reason", "resolution"] : ["action", "incidentIdentity", "transitionKey", "expectedVersion", "reason", "ownerId", "deadlineAt"];
|
|
12945
|
+
if (!hasOnlyKeys(value, actionKeys)) {
|
|
12946
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "transition has keys not valid for its action");
|
|
12947
|
+
}
|
|
12948
|
+
const base = {
|
|
12949
|
+
incidentIdentity: normalizeIncidentIdentity(value.incidentIdentity),
|
|
12950
|
+
transitionKey: boundedString(value.transitionKey, "transitionKey", MAX_IDENTITY_BYTES),
|
|
12951
|
+
expectedVersion: safeInteger(value.expectedVersion, "expectedVersion"),
|
|
12952
|
+
reason: boundedString(value.reason, "reason", MAX_LONG_TEXT_BYTES)
|
|
12953
|
+
};
|
|
12954
|
+
if (base.expectedVersion < 0) {
|
|
12955
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "expectedVersion must not be negative");
|
|
12956
|
+
}
|
|
12957
|
+
if (action === "ACKNOWLEDGE" || action === "UPDATE_ASSIGNMENT" || action === "REOPEN") {
|
|
12958
|
+
const ownerId = boundedString(value.ownerId, "ownerId", MAX_IDENTITY_BYTES);
|
|
12959
|
+
const deadlineAt = safeInteger(value.deadlineAt, "deadlineAt");
|
|
12960
|
+
if (ownerId === null) throw new IncidentLifecycleError("incident_invalid_request", 400, "ownerId must be non-empty");
|
|
12961
|
+
return { ...base, action, ownerId, deadlineAt };
|
|
12962
|
+
}
|
|
12963
|
+
if (action === "RECORD_CORRECTION") {
|
|
12964
|
+
return { ...base, action, correction: normalizeCorrection(value.correction) };
|
|
12965
|
+
}
|
|
12966
|
+
if (!isObject11(value.resolution) || !hasOnlyKeys(value.resolution, ["kind", "explanation"])) {
|
|
12967
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "resolution has unknown or missing keys");
|
|
12968
|
+
}
|
|
12969
|
+
if (value.resolution.kind === "CORRECTED") {
|
|
12970
|
+
if (value.resolution.explanation !== void 0) {
|
|
12971
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "CORRECTED resolution has unknown keys");
|
|
12972
|
+
}
|
|
12973
|
+
return { ...base, action, resolution: { kind: "CORRECTED" } };
|
|
12974
|
+
}
|
|
12975
|
+
if (value.resolution.kind === "ACCEPTED_AS_IS") {
|
|
12976
|
+
const explanation = boundedString(value.resolution.explanation, "resolution.explanation", MAX_LONG_TEXT_BYTES);
|
|
12977
|
+
if (explanation === null) throw new IncidentLifecycleError("incident_invalid_request", 400, "resolution.explanation is required");
|
|
12978
|
+
return { ...base, action, resolution: { kind: "ACCEPTED_AS_IS", explanation } };
|
|
12979
|
+
}
|
|
12980
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "resolution.kind is invalid");
|
|
12981
|
+
}
|
|
12982
|
+
function canonicalize(value) {
|
|
12983
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
12984
|
+
if (!isObject11(value)) return value;
|
|
12985
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]));
|
|
12986
|
+
}
|
|
12987
|
+
async function digestFor(request, actorId) {
|
|
12988
|
+
const envelope = canonicalize({ action: request, actor: actorId });
|
|
12989
|
+
const encoded = new TextEncoder().encode(JSON.stringify(envelope));
|
|
12990
|
+
const digest = await crypto.subtle.digest("SHA-256", encoded);
|
|
12991
|
+
return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
12992
|
+
}
|
|
12993
|
+
function projectionFromRow(row, finding2) {
|
|
12994
|
+
if (row === null) {
|
|
12995
|
+
return {
|
|
12996
|
+
serviceId: finding2.service_id,
|
|
12997
|
+
incidentIdentity: finding2.incident_identity,
|
|
12998
|
+
lifecycleState: "OPEN",
|
|
12999
|
+
ownerId: null,
|
|
13000
|
+
deadlineAt: null,
|
|
13001
|
+
correction: null,
|
|
13002
|
+
closeResolution: null,
|
|
13003
|
+
closeReason: null,
|
|
13004
|
+
version: 0,
|
|
13005
|
+
lastTransitionKey: "",
|
|
13006
|
+
updatedAt: finding2.last_observed_at
|
|
13007
|
+
};
|
|
13008
|
+
}
|
|
13009
|
+
return {
|
|
13010
|
+
serviceId: row.service_id,
|
|
13011
|
+
incidentIdentity: row.incident_identity,
|
|
13012
|
+
lifecycleState: row.lifecycle_state,
|
|
13013
|
+
ownerId: row.owner_id,
|
|
13014
|
+
deadlineAt: row.deadline_at,
|
|
13015
|
+
correction: row.correction_kind === null ? null : {
|
|
13016
|
+
kind: row.correction_kind,
|
|
13017
|
+
reference: row.correction_reference,
|
|
13018
|
+
digest: row.correction_digest
|
|
13019
|
+
},
|
|
13020
|
+
closeResolution: row.close_resolution,
|
|
13021
|
+
closeReason: row.close_reason,
|
|
13022
|
+
version: row.version,
|
|
13023
|
+
lastTransitionKey: row.last_transition_key,
|
|
13024
|
+
updatedAt: row.updated_at
|
|
13025
|
+
};
|
|
13026
|
+
}
|
|
13027
|
+
function findingFromRow(row) {
|
|
13028
|
+
return {
|
|
13029
|
+
serviceId: row.service_id,
|
|
13030
|
+
incidentIdentity: row.incident_identity,
|
|
13031
|
+
incidentType: row.incident_type,
|
|
13032
|
+
partitionTag: row.partition_tag,
|
|
13033
|
+
obligationSequence: row.obligation_sequence,
|
|
13034
|
+
eventId: row.event_id,
|
|
13035
|
+
eventDigest: row.event_digest,
|
|
13036
|
+
state: row.finding_state,
|
|
13037
|
+
firstObservedAt: row.first_observed_at,
|
|
13038
|
+
lastObservedAt: row.last_observed_at
|
|
13039
|
+
};
|
|
13040
|
+
}
|
|
13041
|
+
function transitionFromRow(row) {
|
|
13042
|
+
const before = projectionFromAudit(row, "before");
|
|
13043
|
+
const after = projectionFromAudit(row, "after");
|
|
13044
|
+
return {
|
|
13045
|
+
transitionId: row.transition_id,
|
|
13046
|
+
serviceId: row.service_id,
|
|
13047
|
+
incidentIdentity: row.incident_identity,
|
|
13048
|
+
transitionKey: row.transition_key,
|
|
13049
|
+
requestDigest: row.request_digest,
|
|
13050
|
+
action: row.action,
|
|
13051
|
+
fromState: row.from_state,
|
|
13052
|
+
toState: row.to_state,
|
|
13053
|
+
fromVersion: row.from_version,
|
|
13054
|
+
toVersion: row.to_version,
|
|
13055
|
+
actorId: row.actor_id,
|
|
13056
|
+
before,
|
|
13057
|
+
after,
|
|
13058
|
+
reason: row.reason,
|
|
13059
|
+
occurredAt: row.occurred_at
|
|
13060
|
+
};
|
|
13061
|
+
}
|
|
13062
|
+
function projectionFromAudit(row, prefix) {
|
|
13063
|
+
const state = prefix === "before" ? row.from_state : row.to_state;
|
|
13064
|
+
const version = prefix === "before" ? row.from_version : row.to_version;
|
|
13065
|
+
const ownerId = row[`${prefix}_owner_id`];
|
|
13066
|
+
const correctionKind = row[`${prefix}_correction_kind`];
|
|
13067
|
+
return {
|
|
13068
|
+
serviceId: row.service_id,
|
|
13069
|
+
incidentIdentity: row.incident_identity,
|
|
13070
|
+
lifecycleState: state,
|
|
13071
|
+
ownerId,
|
|
13072
|
+
deadlineAt: row[`${prefix}_deadline_at`],
|
|
13073
|
+
correction: correctionKind === null ? null : {
|
|
13074
|
+
kind: correctionKind,
|
|
13075
|
+
reference: row[`${prefix}_correction_reference`],
|
|
13076
|
+
digest: row[`${prefix}_correction_digest`]
|
|
13077
|
+
},
|
|
13078
|
+
closeResolution: row[`${prefix}_close_resolution`],
|
|
13079
|
+
closeReason: row[`${prefix}_close_reason`],
|
|
13080
|
+
version,
|
|
13081
|
+
lastTransitionKey: prefix === "after" ? row.transition_key : "",
|
|
13082
|
+
updatedAt: row.occurred_at
|
|
13083
|
+
};
|
|
13084
|
+
}
|
|
13085
|
+
function active(state) {
|
|
13086
|
+
return state !== "CLOSED";
|
|
13087
|
+
}
|
|
13088
|
+
function compareItems(left, right) {
|
|
13089
|
+
if (left.overdue !== right.overdue) return left.overdue ? -1 : 1;
|
|
13090
|
+
if (left.unowned !== right.unowned) return left.unowned ? -1 : 1;
|
|
13091
|
+
if (left.finding.lastObservedAt !== right.finding.lastObservedAt) return right.finding.lastObservedAt - left.finding.lastObservedAt;
|
|
13092
|
+
return left.finding.incidentIdentity < right.finding.incidentIdentity ? -1 : left.finding.incidentIdentity > right.finding.incidentIdentity ? 1 : 0;
|
|
13093
|
+
}
|
|
13094
|
+
var IncidentLifecycle = class {
|
|
13095
|
+
constructor(database, now2 = () => Date.now()) {
|
|
13096
|
+
this.database = database;
|
|
13097
|
+
this.now = now2;
|
|
13098
|
+
}
|
|
13099
|
+
database;
|
|
13100
|
+
now;
|
|
13101
|
+
static parseTransition(value) {
|
|
13102
|
+
return normalizeRequest(value);
|
|
13103
|
+
}
|
|
13104
|
+
async finding(serviceId, incidentIdentity) {
|
|
13105
|
+
const row = await this.database.prepare(
|
|
13106
|
+
`SELECT service_id, incident_identity, incident_type, partition_tag,
|
|
13107
|
+
obligation_sequence, event_id, event_digest, state AS finding_state,
|
|
13108
|
+
first_observed_at, last_observed_at
|
|
13109
|
+
FROM serialized_dcb_completeness_findings
|
|
13110
|
+
WHERE service_id = ? AND incident_identity = ?`
|
|
13111
|
+
).bind(serviceId, incidentIdentity).first();
|
|
13112
|
+
if (row === null || row === void 0) {
|
|
13113
|
+
throw new IncidentLifecycleError("incident_not_found", 404, "The completeness incident was not found");
|
|
13114
|
+
}
|
|
13115
|
+
return row;
|
|
13116
|
+
}
|
|
13117
|
+
async lifecycle(serviceId, incidentIdentity) {
|
|
13118
|
+
const row = await this.database.prepare(
|
|
13119
|
+
`SELECT service_id, incident_identity, lifecycle_state, owner_id, deadline_at,
|
|
13120
|
+
correction_kind, correction_reference, correction_digest,
|
|
13121
|
+
close_resolution, close_reason, version, last_transition_key, updated_at
|
|
13122
|
+
FROM serialized_dcb_incident_lifecycles
|
|
13123
|
+
WHERE service_id = ? AND incident_identity = ?`
|
|
13124
|
+
).bind(serviceId, incidentIdentity).first();
|
|
13125
|
+
return row === void 0 ? null : row;
|
|
13126
|
+
}
|
|
13127
|
+
async transitions(serviceId, incidentIdentity) {
|
|
13128
|
+
const result2 = await this.database.prepare(
|
|
13129
|
+
`SELECT * FROM serialized_dcb_incident_transitions
|
|
13130
|
+
WHERE service_id = ? AND incident_identity = ?
|
|
13131
|
+
ORDER BY transition_id ASC`
|
|
13132
|
+
).bind(serviceId, incidentIdentity).all();
|
|
13133
|
+
return result2.results.map(transitionFromRow);
|
|
13134
|
+
}
|
|
13135
|
+
async closeTime(serviceId, incidentIdentity) {
|
|
13136
|
+
const row = await this.database.prepare(
|
|
13137
|
+
`SELECT occurred_at FROM serialized_dcb_incident_transitions
|
|
13138
|
+
WHERE service_id = ? AND incident_identity = ? AND to_state = 'CLOSED'
|
|
13139
|
+
ORDER BY transition_id DESC LIMIT 1`
|
|
13140
|
+
).bind(serviceId, incidentIdentity).first();
|
|
13141
|
+
return row?.occurred_at ?? null;
|
|
13142
|
+
}
|
|
13143
|
+
async item(finding2, row, at) {
|
|
13144
|
+
const lifecycle = projectionFromRow(row, finding2);
|
|
13145
|
+
const closeTime = lifecycle.lifecycleState === "OPEN" && row === null ? null : await this.closeTime(finding2.service_id, finding2.incident_identity);
|
|
13146
|
+
const observedAfterClose = closeTime !== null && finding2.last_observed_at > closeTime;
|
|
13147
|
+
const overdue = active(lifecycle.lifecycleState) && lifecycle.deadlineAt !== null && lifecycle.deadlineAt < at;
|
|
13148
|
+
return {
|
|
13149
|
+
finding: findingFromRow(finding2),
|
|
13150
|
+
lifecycle,
|
|
13151
|
+
overdue,
|
|
13152
|
+
unowned: lifecycle.ownerId === null,
|
|
13153
|
+
observedAfterClose
|
|
13154
|
+
};
|
|
13155
|
+
}
|
|
13156
|
+
async list(serviceId, filters = {}, at = this.now()) {
|
|
13157
|
+
const rows = await this.database.prepare(
|
|
13158
|
+
`SELECT service_id, incident_identity, incident_type, partition_tag,
|
|
13159
|
+
obligation_sequence, event_id, event_digest, state AS finding_state,
|
|
13160
|
+
first_observed_at, last_observed_at
|
|
13161
|
+
FROM serialized_dcb_completeness_findings
|
|
13162
|
+
WHERE service_id = ?`
|
|
13163
|
+
).bind(serviceId).all();
|
|
13164
|
+
const items = [];
|
|
13165
|
+
for (const finding2 of rows.results) {
|
|
13166
|
+
const item = await this.item(finding2, await this.lifecycle(serviceId, finding2.incident_identity), at);
|
|
13167
|
+
if (filters.state !== void 0 && item.lifecycle.lifecycleState !== filters.state) continue;
|
|
13168
|
+
if (filters.owner !== void 0 && item.lifecycle.ownerId !== filters.owner) continue;
|
|
13169
|
+
if (filters.unowned === true && !item.unowned) continue;
|
|
13170
|
+
if (filters.overdue === true && !item.overdue) continue;
|
|
13171
|
+
if (filters.observedAfterClose === true && !item.observedAfterClose) continue;
|
|
13172
|
+
items.push(item);
|
|
13173
|
+
}
|
|
13174
|
+
items.sort(compareItems);
|
|
13175
|
+
const byState = {
|
|
13176
|
+
OPEN: 0,
|
|
13177
|
+
ACKNOWLEDGED: 0,
|
|
13178
|
+
CORRECTION_RECORDED: 0,
|
|
13179
|
+
CLOSED: 0,
|
|
13180
|
+
REOPENED: 0
|
|
13181
|
+
};
|
|
13182
|
+
for (const item of items) byState[item.lifecycle.lifecycleState] += 1;
|
|
13183
|
+
return {
|
|
13184
|
+
items,
|
|
13185
|
+
summary: {
|
|
13186
|
+
total: items.length,
|
|
13187
|
+
byState,
|
|
13188
|
+
unowned: items.filter((item) => item.unowned).length,
|
|
13189
|
+
overdue: items.filter((item) => item.overdue).length,
|
|
13190
|
+
observedAfterClose: items.filter((item) => item.observedAfterClose).length
|
|
13191
|
+
}
|
|
13192
|
+
};
|
|
13193
|
+
}
|
|
13194
|
+
async detail(serviceId, incidentIdentity, at = this.now()) {
|
|
13195
|
+
const finding2 = await this.finding(serviceId, incidentIdentity);
|
|
13196
|
+
const lifecycle = await this.lifecycle(serviceId, incidentIdentity);
|
|
13197
|
+
const item = await this.item(finding2, lifecycle, at);
|
|
13198
|
+
return {
|
|
13199
|
+
finding: item.finding,
|
|
13200
|
+
lifecycle: item.lifecycle,
|
|
13201
|
+
flags: { overdue: item.overdue, unowned: item.unowned, observedAfterClose: item.observedAfterClose },
|
|
13202
|
+
transitions: await this.transitions(serviceId, incidentIdentity)
|
|
13203
|
+
};
|
|
13204
|
+
}
|
|
13205
|
+
async transition(serviceId, value, actor) {
|
|
13206
|
+
const request = normalizeRequest(value);
|
|
13207
|
+
let actorId;
|
|
13208
|
+
try {
|
|
13209
|
+
actorId = boundedString(actor, "x-sdt-maintainer", MAX_IDENTITY_BYTES);
|
|
13210
|
+
} catch (caught) {
|
|
13211
|
+
if (caught instanceof IncidentLifecycleError && caught.code === "incident_invalid_request") {
|
|
13212
|
+
throw new IncidentLifecycleError("incident_invalid_actor", 400, caught.message);
|
|
13213
|
+
}
|
|
13214
|
+
throw caught;
|
|
13215
|
+
}
|
|
13216
|
+
if (actorId === null) throw new IncidentLifecycleError("incident_invalid_actor", 400, "x-sdt-maintainer must be non-empty");
|
|
13217
|
+
const finding2 = await this.finding(serviceId, request.incidentIdentity);
|
|
13218
|
+
const digest = await digestFor(request, actorId);
|
|
13219
|
+
const existing = await this.database.prepare(
|
|
13220
|
+
`SELECT * FROM serialized_dcb_incident_transitions
|
|
13221
|
+
WHERE service_id = ? AND incident_identity = ? AND transition_key = ?`
|
|
13222
|
+
).bind(serviceId, request.incidentIdentity, request.transitionKey).first();
|
|
13223
|
+
if (existing !== null && existing !== void 0) {
|
|
13224
|
+
if (existing.request_digest !== digest) {
|
|
13225
|
+
throw new IncidentLifecycleError("incident_idempotency_conflict", 409, "The transition key was already used with different content");
|
|
13226
|
+
}
|
|
13227
|
+
return { projection: transitionFromRow(existing).after, transition: transitionFromRow(existing), idempotent: true };
|
|
13228
|
+
}
|
|
13229
|
+
const priorRow = await this.lifecycle(serviceId, request.incidentIdentity);
|
|
13230
|
+
const before = projectionFromRow(priorRow, finding2);
|
|
13231
|
+
if (before.version !== request.expectedVersion) {
|
|
13232
|
+
throw new IncidentLifecycleError("incident_version_conflict", 409, "The incident version is stale");
|
|
13233
|
+
}
|
|
13234
|
+
const at = this.now();
|
|
13235
|
+
const after = this.nextProjection(before, request, at);
|
|
13236
|
+
const newVersion = before.version + 1;
|
|
13237
|
+
const occurredAt = at;
|
|
13238
|
+
const seed = this.database.prepare(
|
|
13239
|
+
`INSERT INTO serialized_dcb_incident_lifecycles
|
|
13240
|
+
(service_id, incident_identity, lifecycle_state, version, last_transition_key, updated_at)
|
|
13241
|
+
VALUES (?, ?, 'OPEN', 0, '', ?)
|
|
13242
|
+
ON CONFLICT (service_id, incident_identity) DO NOTHING`
|
|
13243
|
+
).bind(serviceId, request.incidentIdentity, occurredAt);
|
|
13244
|
+
const update = this.database.prepare(
|
|
13245
|
+
`UPDATE serialized_dcb_incident_lifecycles
|
|
13246
|
+
SET lifecycle_state = ?, owner_id = ?, deadline_at = ?,
|
|
13247
|
+
correction_kind = ?, correction_reference = ?, correction_digest = ?,
|
|
13248
|
+
close_resolution = ?, close_reason = ?, version = ?,
|
|
13249
|
+
last_transition_key = ?, updated_at = ?
|
|
13250
|
+
WHERE service_id = ? AND incident_identity = ? AND version = ?
|
|
13251
|
+
AND last_transition_key <> ?`
|
|
13252
|
+
).bind(
|
|
13253
|
+
after.lifecycleState,
|
|
13254
|
+
after.ownerId,
|
|
13255
|
+
after.deadlineAt,
|
|
13256
|
+
after.correction?.kind ?? null,
|
|
13257
|
+
after.correction?.reference ?? null,
|
|
13258
|
+
after.correction?.digest ?? null,
|
|
13259
|
+
after.closeResolution,
|
|
13260
|
+
after.closeReason,
|
|
13261
|
+
newVersion,
|
|
13262
|
+
request.transitionKey,
|
|
13263
|
+
occurredAt,
|
|
13264
|
+
serviceId,
|
|
13265
|
+
request.incidentIdentity,
|
|
13266
|
+
before.version,
|
|
13267
|
+
request.transitionKey
|
|
13268
|
+
);
|
|
13269
|
+
const audit = this.database.prepare(
|
|
13270
|
+
`INSERT INTO serialized_dcb_incident_transitions
|
|
13271
|
+
(service_id, incident_identity, transition_key, request_digest, action,
|
|
13272
|
+
from_state, to_state, from_version, to_version, actor_id,
|
|
13273
|
+
before_owner_id, before_deadline_at, before_correction_kind,
|
|
13274
|
+
before_correction_reference, before_correction_digest, before_close_resolution,
|
|
13275
|
+
before_close_reason, after_owner_id, after_deadline_at, after_correction_kind,
|
|
13276
|
+
after_correction_reference, after_correction_digest, after_close_resolution,
|
|
13277
|
+
after_close_reason, reason, occurred_at)
|
|
13278
|
+
SELECT ?, ?, ?, ?, ?, ?, lifecycle_state, ?, version, ?,
|
|
13279
|
+
?, ?, ?, ?, ?, ?, ?, owner_id, deadline_at, correction_kind,
|
|
13280
|
+
correction_reference, correction_digest, close_resolution, close_reason, ?, ?
|
|
13281
|
+
FROM serialized_dcb_incident_lifecycles
|
|
13282
|
+
WHERE service_id = ? AND incident_identity = ? AND version = ? AND last_transition_key = ?`
|
|
13283
|
+
).bind(
|
|
13284
|
+
serviceId,
|
|
13285
|
+
request.incidentIdentity,
|
|
13286
|
+
request.transitionKey,
|
|
13287
|
+
digest,
|
|
13288
|
+
request.action,
|
|
13289
|
+
before.lifecycleState,
|
|
13290
|
+
before.version,
|
|
13291
|
+
actorId,
|
|
13292
|
+
before.ownerId,
|
|
13293
|
+
before.deadlineAt,
|
|
13294
|
+
before.correction?.kind ?? null,
|
|
13295
|
+
before.correction?.reference ?? null,
|
|
13296
|
+
before.correction?.digest ?? null,
|
|
13297
|
+
before.closeResolution,
|
|
13298
|
+
before.closeReason,
|
|
13299
|
+
request.reason,
|
|
13300
|
+
occurredAt,
|
|
13301
|
+
serviceId,
|
|
13302
|
+
request.incidentIdentity,
|
|
13303
|
+
newVersion,
|
|
13304
|
+
request.transitionKey
|
|
13305
|
+
);
|
|
13306
|
+
await this.database.batch([seed, update, audit]);
|
|
13307
|
+
const inserted = await this.database.prepare(
|
|
13308
|
+
`SELECT * FROM serialized_dcb_incident_transitions
|
|
13309
|
+
WHERE service_id = ? AND incident_identity = ? AND transition_key = ?`
|
|
13310
|
+
).bind(serviceId, request.incidentIdentity, request.transitionKey).first();
|
|
13311
|
+
if (inserted === null || inserted === void 0) {
|
|
13312
|
+
const competing = await this.database.prepare(
|
|
13313
|
+
`SELECT * FROM serialized_dcb_incident_transitions
|
|
13314
|
+
WHERE service_id = ? AND incident_identity = ? AND transition_key = ?`
|
|
13315
|
+
).bind(serviceId, request.incidentIdentity, request.transitionKey).first();
|
|
13316
|
+
if (competing !== null && competing !== void 0 && competing.request_digest !== digest) {
|
|
13317
|
+
throw new IncidentLifecycleError("incident_idempotency_conflict", 409, "The transition key was already used with different content");
|
|
13318
|
+
}
|
|
13319
|
+
if (competing !== null && competing !== void 0) {
|
|
13320
|
+
const transition2 = transitionFromRow(competing);
|
|
13321
|
+
return { projection: transition2.after, transition: transition2, idempotent: true };
|
|
13322
|
+
}
|
|
13323
|
+
throw new IncidentLifecycleError("incident_version_conflict", 409, "The incident version is stale");
|
|
13324
|
+
}
|
|
13325
|
+
const transition = transitionFromRow(inserted);
|
|
13326
|
+
return { projection: transition.after, transition, idempotent: false };
|
|
13327
|
+
}
|
|
13328
|
+
nextProjection(before, request, at) {
|
|
13329
|
+
const laterDeadline = (deadlineAt2) => {
|
|
13330
|
+
if (deadlineAt2 <= at) throw new IncidentLifecycleError("incident_invalid_deadline", 400, "deadlineAt must be in the future");
|
|
13331
|
+
return deadlineAt2;
|
|
13332
|
+
};
|
|
13333
|
+
let lifecycleState;
|
|
13334
|
+
let ownerId = before.ownerId;
|
|
13335
|
+
let deadlineAt = before.deadlineAt;
|
|
13336
|
+
let correction = before.correction;
|
|
13337
|
+
let closeResolution = before.closeResolution;
|
|
13338
|
+
let closeReason = before.closeReason;
|
|
13339
|
+
if (request.action === "ACKNOWLEDGE") {
|
|
13340
|
+
if (before.lifecycleState !== "OPEN" && before.lifecycleState !== "REOPENED") {
|
|
13341
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "The incident cannot be acknowledged from its current state");
|
|
13342
|
+
}
|
|
13343
|
+
lifecycleState = "ACKNOWLEDGED";
|
|
13344
|
+
ownerId = request.ownerId;
|
|
13345
|
+
deadlineAt = laterDeadline(request.deadlineAt);
|
|
13346
|
+
correction = null;
|
|
13347
|
+
closeResolution = null;
|
|
13348
|
+
closeReason = null;
|
|
13349
|
+
} else if (request.action === "UPDATE_ASSIGNMENT") {
|
|
13350
|
+
if (before.lifecycleState !== "ACKNOWLEDGED" && before.lifecycleState !== "CORRECTION_RECORDED") {
|
|
13351
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "The incident cannot be reassigned from its current state");
|
|
13352
|
+
}
|
|
13353
|
+
lifecycleState = before.lifecycleState;
|
|
13354
|
+
ownerId = request.ownerId;
|
|
13355
|
+
deadlineAt = laterDeadline(request.deadlineAt);
|
|
13356
|
+
} else if (request.action === "RECORD_CORRECTION") {
|
|
13357
|
+
if (before.lifecycleState !== "ACKNOWLEDGED") {
|
|
13358
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "The incident must be acknowledged before correction evidence");
|
|
13359
|
+
}
|
|
13360
|
+
lifecycleState = "CORRECTION_RECORDED";
|
|
13361
|
+
correction = request.correction;
|
|
13362
|
+
} else if (request.action === "CLOSE") {
|
|
13363
|
+
if (before.ownerId === null || before.deadlineAt === null) {
|
|
13364
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "The incident must have an owner and deadline before closure");
|
|
13365
|
+
}
|
|
13366
|
+
if (request.resolution.kind === "CORRECTED") {
|
|
13367
|
+
if (before.lifecycleState !== "CORRECTION_RECORDED") {
|
|
13368
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "A corrected closure requires recorded correction evidence");
|
|
13369
|
+
}
|
|
13370
|
+
lifecycleState = "CLOSED";
|
|
13371
|
+
closeResolution = "CORRECTED";
|
|
13372
|
+
closeReason = request.reason;
|
|
13373
|
+
} else {
|
|
13374
|
+
if (before.lifecycleState !== "ACKNOWLEDGED") {
|
|
13375
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "Accepted-as-is closure requires an acknowledged incident");
|
|
13376
|
+
}
|
|
13377
|
+
lifecycleState = "CLOSED";
|
|
13378
|
+
closeResolution = "ACCEPTED_AS_IS";
|
|
13379
|
+
closeReason = request.resolution.explanation;
|
|
13380
|
+
correction = null;
|
|
13381
|
+
}
|
|
13382
|
+
} else {
|
|
13383
|
+
if (before.lifecycleState !== "CLOSED") {
|
|
13384
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "Only a closed incident can be reopened");
|
|
13385
|
+
}
|
|
13386
|
+
lifecycleState = "REOPENED";
|
|
13387
|
+
ownerId = request.ownerId;
|
|
13388
|
+
deadlineAt = laterDeadline(request.deadlineAt);
|
|
13389
|
+
correction = null;
|
|
13390
|
+
closeResolution = null;
|
|
13391
|
+
closeReason = null;
|
|
13392
|
+
}
|
|
13393
|
+
return {
|
|
13394
|
+
...before,
|
|
13395
|
+
lifecycleState,
|
|
13396
|
+
ownerId,
|
|
13397
|
+
deadlineAt,
|
|
13398
|
+
correction,
|
|
13399
|
+
closeResolution,
|
|
13400
|
+
closeReason,
|
|
13401
|
+
version: before.version + 1,
|
|
13402
|
+
lastTransitionKey: request.transitionKey,
|
|
13403
|
+
updatedAt: at
|
|
13404
|
+
};
|
|
13405
|
+
}
|
|
13406
|
+
};
|
|
13407
|
+
|
|
13408
|
+
// src/http/IncidentMaintenance.ts
|
|
13409
|
+
function json10(body, status = 200) {
|
|
13410
|
+
return new Response(JSON.stringify(body), {
|
|
13411
|
+
status,
|
|
13412
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
13413
|
+
});
|
|
13414
|
+
}
|
|
13415
|
+
function error8(status, code, message) {
|
|
13416
|
+
return json10({ error: message, code }, status);
|
|
13417
|
+
}
|
|
13418
|
+
function boolFilter(value, name) {
|
|
13419
|
+
if (value === null) return void 0;
|
|
13420
|
+
if (value === "true") return true;
|
|
13421
|
+
if (value === "false") return false;
|
|
13422
|
+
throw new IncidentLifecycleError("incident_invalid_filter", 400, `${name} must be true or false`);
|
|
13423
|
+
}
|
|
13424
|
+
function parseFilters(url) {
|
|
13425
|
+
const allowed = /* @__PURE__ */ new Set(["state", "owner", "unowned", "overdue", "observedAfterClose"]);
|
|
13426
|
+
let unknown = false;
|
|
13427
|
+
url.searchParams.forEach((_value, key) => {
|
|
13428
|
+
if (!allowed.has(key)) unknown = true;
|
|
13429
|
+
});
|
|
13430
|
+
if (unknown) throw new IncidentLifecycleError("incident_invalid_filter", 400, "Unknown incident list filter");
|
|
13431
|
+
const state = url.searchParams.get("state");
|
|
13432
|
+
if (state !== null && !["OPEN", "ACKNOWLEDGED", "CORRECTION_RECORDED", "CLOSED", "REOPENED"].includes(state)) {
|
|
13433
|
+
throw new IncidentLifecycleError("incident_invalid_filter", 400, "state is invalid");
|
|
13434
|
+
}
|
|
13435
|
+
const owner = url.searchParams.get("owner");
|
|
13436
|
+
if (owner !== null) {
|
|
13437
|
+
const normalizedOwner = owner.trim();
|
|
13438
|
+
if (normalizedOwner.length === 0) {
|
|
13439
|
+
throw new IncidentLifecycleError("incident_invalid_filter", 400, "owner must be non-empty");
|
|
13440
|
+
}
|
|
13441
|
+
if (new TextEncoder().encode(normalizedOwner).byteLength > 256) {
|
|
13442
|
+
throw new IncidentLifecycleError("incident_invalid_filter", 400, "owner is too long");
|
|
13443
|
+
}
|
|
13444
|
+
}
|
|
13445
|
+
const unowned = boolFilter(url.searchParams.get("unowned"), "unowned");
|
|
13446
|
+
const overdue = boolFilter(url.searchParams.get("overdue"), "overdue");
|
|
13447
|
+
const observedAfterClose = boolFilter(url.searchParams.get("observedAfterClose"), "observedAfterClose");
|
|
13448
|
+
return {
|
|
13449
|
+
...state === null ? {} : { state },
|
|
13450
|
+
...owner === null ? {} : { owner: owner.trim() },
|
|
13451
|
+
...unowned === void 0 ? {} : { unowned },
|
|
13452
|
+
...overdue === void 0 ? {} : { overdue },
|
|
13453
|
+
...observedAfterClose === void 0 ? {} : { observedAfterClose }
|
|
13454
|
+
};
|
|
13455
|
+
}
|
|
13456
|
+
function incidentIdentityFromPath(pathname) {
|
|
13457
|
+
const encoded = pathname.slice("/maintenance/incidents/".length);
|
|
13458
|
+
if (encoded.length === 0 || encoded.includes("/")) {
|
|
13459
|
+
throw new IncidentLifecycleError("incident_route_not_found", 404, "Incident route not found");
|
|
13460
|
+
}
|
|
13461
|
+
try {
|
|
13462
|
+
return normalizeIncidentIdentity(decodeURIComponent(encoded));
|
|
13463
|
+
} catch {
|
|
13464
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "Incident identity must be URI encoded");
|
|
13465
|
+
}
|
|
13466
|
+
}
|
|
13467
|
+
function isIncidentMaintenancePath(pathname) {
|
|
13468
|
+
return pathname === "/maintenance/incidents" || pathname.startsWith("/maintenance/incidents/");
|
|
13469
|
+
}
|
|
13470
|
+
async function handleIncidentMaintenance(request, env, serviceIdentityProvider) {
|
|
13471
|
+
const decision = await checkConfiguredBearer(request, env.INCIDENT_MAINTAINER_TOKEN, {
|
|
13472
|
+
tokenNormalization: "trim"
|
|
13473
|
+
});
|
|
13474
|
+
if (decision === "unconfigured") {
|
|
13475
|
+
return error8(503, "incident_maintenance_unavailable", "Incident maintenance is not configured");
|
|
13476
|
+
}
|
|
13477
|
+
if (decision === "missing" || decision === "invalid") {
|
|
13478
|
+
return error8(401, "incident_auth_required", "A valid incident maintenance bearer token is required");
|
|
13479
|
+
}
|
|
13480
|
+
let serviceId;
|
|
13481
|
+
try {
|
|
13482
|
+
serviceId = requestServiceIdentity(request, serviceIdentityProvider, {
|
|
13483
|
+
allowG11Verification: env.G11_VERIFICATION_ENABLED === "true"
|
|
13484
|
+
});
|
|
13485
|
+
} catch {
|
|
13486
|
+
return error8(503, "scope.identity_missing", "A deployment service identity is required for this scope");
|
|
13487
|
+
}
|
|
13488
|
+
if (env.D1 === void 0) {
|
|
13489
|
+
return error8(503, "incident_maintenance_unavailable", "Incident maintenance storage is unavailable");
|
|
13490
|
+
}
|
|
13491
|
+
const lifecycle = new IncidentLifecycle(env.D1);
|
|
13492
|
+
const url = new URL(request.url);
|
|
13493
|
+
try {
|
|
13494
|
+
if (request.method === "GET" && url.pathname === "/maintenance/incidents") {
|
|
13495
|
+
return json10(await lifecycle.list(serviceId, parseFilters(url)));
|
|
13496
|
+
}
|
|
13497
|
+
if (request.method === "GET" && url.pathname.startsWith("/maintenance/incidents/")) {
|
|
13498
|
+
return json10(await lifecycle.detail(serviceId, incidentIdentityFromPath(url.pathname)));
|
|
13499
|
+
}
|
|
13500
|
+
if (request.method === "POST" && url.pathname === "/maintenance/incidents/transitions") {
|
|
13501
|
+
let body;
|
|
13502
|
+
try {
|
|
13503
|
+
body = await request.json();
|
|
13504
|
+
} catch {
|
|
13505
|
+
return error8(400, "incident_invalid_request", "Transition request must be JSON");
|
|
13506
|
+
}
|
|
13507
|
+
const requestValue = IncidentLifecycle.parseTransition(body);
|
|
13508
|
+
const actor = request.headers.get("x-sdt-maintainer");
|
|
13509
|
+
if (actor === null) {
|
|
13510
|
+
return error8(400, "incident_invalid_actor", "x-sdt-maintainer is required");
|
|
13511
|
+
}
|
|
13512
|
+
return json10(await lifecycle.transition(serviceId, requestValue, actor));
|
|
13513
|
+
}
|
|
13514
|
+
return error8(404, "incident_route_not_found", "Incident maintenance route not found");
|
|
13515
|
+
} catch (caught) {
|
|
13516
|
+
if (caught instanceof IncidentLifecycleError) return error8(caught.status, caught.code, caught.message);
|
|
13517
|
+
return error8(500, "incident_maintenance_failed", "Incident maintenance could not complete the request");
|
|
13518
|
+
}
|
|
13519
|
+
}
|
|
13520
|
+
|
|
12569
13521
|
// src/downstream/Doorbell.ts
|
|
12570
13522
|
var MAX_SERVICE_BINDING_INVOCATIONS_PER_REQUEST = 32;
|
|
12571
|
-
function classifyDirectDoorbellFailure(
|
|
12572
|
-
const structured = typeof
|
|
13523
|
+
function classifyDirectDoorbellFailure(error11) {
|
|
13524
|
+
const structured = typeof error11 === "object" && error11 !== null ? error11 : {};
|
|
12573
13525
|
const status = typeof structured.status === "number" ? structured.status : typeof structured.statusCode === "number" ? structured.statusCode : void 0;
|
|
12574
13526
|
if (status !== void 0 && (status < 200 || status >= 300)) return "non-2xx";
|
|
12575
|
-
const value = String(
|
|
13527
|
+
const value = String(error11).toLowerCase();
|
|
12576
13528
|
const code = `${String(structured.code ?? "")} ${String(structured.name ?? "")}`.toLowerCase();
|
|
12577
13529
|
if (value.includes("timeout") || value.includes("deadline") || code.includes("timeout")) return "timeout";
|
|
12578
13530
|
if (value.includes("cancel") || value.includes("abort") || code.includes("abort")) return "cancel";
|
|
@@ -12815,6 +13767,7 @@ var TAG_SQL_SCHEMA_DDL = `
|
|
|
12815
13767
|
committed_event_count INTEGER NOT NULL,
|
|
12816
13768
|
head_suid TEXT NOT NULL,
|
|
12817
13769
|
reservation_confirmed INTEGER NOT NULL CHECK (reservation_confirmed IN (0, 1)),
|
|
13770
|
+
written_version INTEGER NOT NULL,
|
|
12818
13771
|
PRIMARY KEY (attempt_id, epoch)
|
|
12819
13772
|
);
|
|
12820
13773
|
|
|
@@ -12865,6 +13818,12 @@ var TAG_READ_AFTER_THROUGH_SQL = `
|
|
|
12865
13818
|
`;
|
|
12866
13819
|
function initializeTagSqlSchema(sql) {
|
|
12867
13820
|
sql.exec(TAG_SQL_SCHEMA_DDL);
|
|
13821
|
+
ensureTagCommitReceiptWrittenVersion(sql);
|
|
13822
|
+
}
|
|
13823
|
+
function ensureTagCommitReceiptWrittenVersion(sql) {
|
|
13824
|
+
const columns = sql.exec("SELECT name FROM pragma_table_info('tag_commit_receipt')").toArray();
|
|
13825
|
+
if (columns.some((column) => column.name === "written_version")) return;
|
|
13826
|
+
sql.exec("ALTER TABLE tag_commit_receipt ADD COLUMN written_version INTEGER");
|
|
12868
13827
|
}
|
|
12869
13828
|
function hasTagSqlStorage(storage) {
|
|
12870
13829
|
return typeof storage.sql?.exec === "function";
|
|
@@ -13042,8 +14001,8 @@ function asNumber2(value, name) {
|
|
|
13042
14001
|
if (!Number.isSafeInteger(number)) throw new Error(`D1 ${name} was not a safe integer`);
|
|
13043
14002
|
return number;
|
|
13044
14003
|
}
|
|
13045
|
-
function errorText3(
|
|
13046
|
-
return (
|
|
14004
|
+
function errorText3(error11) {
|
|
14005
|
+
return (error11 instanceof Error ? error11.message : String(error11)).slice(0, 256);
|
|
13047
14006
|
}
|
|
13048
14007
|
function sortedUnique2(values) {
|
|
13049
14008
|
return [...new Set(values)].sort((left, right) => binaryCompare(left, right));
|
|
@@ -13230,8 +14189,8 @@ var D1EventStore = class {
|
|
|
13230
14189
|
let failure3;
|
|
13231
14190
|
try {
|
|
13232
14191
|
result2 = await this.recordDeliveryCore(message, arrivedAt, deliverySource, mutationAttemptId);
|
|
13233
|
-
} catch (
|
|
13234
|
-
failure3 = { error:
|
|
14192
|
+
} catch (error11) {
|
|
14193
|
+
failure3 = { error: error11 };
|
|
13235
14194
|
}
|
|
13236
14195
|
const retryReason = failure3 === void 0 ? null : errorText3(failure3.error);
|
|
13237
14196
|
const suppressDiagnostic = failure3?.error instanceof D1IdentityConflictError && failure3.error.beforeMutation;
|
|
@@ -13802,8 +14761,8 @@ var D1EventStore = class {
|
|
|
13802
14761
|
if (replay === null || replay === void 0) return finish({ kind: "miss" });
|
|
13803
14762
|
const event = await this.eventById(serviceId, asString3(replay.event_id, "replay.event_id"));
|
|
13804
14763
|
return event === void 0 ? finish({ kind: "unknown", reason: "replay-event-row-disappeared" }) : finish({ kind: "replay", event });
|
|
13805
|
-
} catch (
|
|
13806
|
-
return finish({ kind: "unknown", reason: errorText3(
|
|
14764
|
+
} catch (error11) {
|
|
14765
|
+
return finish({ kind: "unknown", reason: errorText3(error11) });
|
|
13807
14766
|
}
|
|
13808
14767
|
}
|
|
13809
14768
|
/**
|
|
@@ -14278,8 +15237,8 @@ var D1EventStore = class {
|
|
|
14278
15237
|
try {
|
|
14279
15238
|
try {
|
|
14280
15239
|
await this.options.beforeG69AdmissionAttempt?.();
|
|
14281
|
-
} catch (
|
|
14282
|
-
await appendFailedDiagnostic(errorText3(
|
|
15240
|
+
} catch (error11) {
|
|
15241
|
+
await appendFailedDiagnostic(errorText3(error11));
|
|
14283
15242
|
return;
|
|
14284
15243
|
}
|
|
14285
15244
|
const afterObservedAt = Date.now();
|
|
@@ -14353,14 +15312,14 @@ var TagIdentityConflict = class extends Error {
|
|
|
14353
15312
|
this.name = "TagIdentityConflict";
|
|
14354
15313
|
}
|
|
14355
15314
|
};
|
|
14356
|
-
function
|
|
15315
|
+
function json11(body, status = 200) {
|
|
14357
15316
|
return new Response(JSON.stringify(body), {
|
|
14358
15317
|
status,
|
|
14359
15318
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
14360
15319
|
});
|
|
14361
15320
|
}
|
|
14362
|
-
function
|
|
14363
|
-
return
|
|
15321
|
+
function error9(status, code, message, retryable = false) {
|
|
15322
|
+
return json11(retryable ? { error: message, code, retryable: true } : { error: message, code }, status);
|
|
14364
15323
|
}
|
|
14365
15324
|
function withGlobalAdmission2(response3, status) {
|
|
14366
15325
|
response3.headers.set(G65_GLOBAL_ADMISSION_HEADER, status);
|
|
@@ -14379,7 +15338,7 @@ function repairRejected(reason, status = 409) {
|
|
|
14379
15338
|
body: { code: "repair_operation_rejected", error: reason, reason }
|
|
14380
15339
|
};
|
|
14381
15340
|
}
|
|
14382
|
-
function
|
|
15341
|
+
function isObject12(value) {
|
|
14383
15342
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14384
15343
|
}
|
|
14385
15344
|
function isNonEmptyString11(value) {
|
|
@@ -14424,8 +15383,8 @@ function sqlJson(value, column) {
|
|
|
14424
15383
|
throw new Error(`Tag SQL column ${column} is not valid JSON`);
|
|
14425
15384
|
}
|
|
14426
15385
|
}
|
|
14427
|
-
function copyArrayBuffer(
|
|
14428
|
-
return
|
|
15386
|
+
function copyArrayBuffer(bytes2) {
|
|
15387
|
+
return bytes2.buffer.slice(bytes2.byteOffset, bytes2.byteOffset + bytes2.byteLength);
|
|
14429
15388
|
}
|
|
14430
15389
|
function arrayBufferBase642(value) {
|
|
14431
15390
|
let binary = "";
|
|
@@ -14436,7 +15395,7 @@ function nowIso() {
|
|
|
14436
15395
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
14437
15396
|
}
|
|
14438
15397
|
function epochFrom(value) {
|
|
14439
|
-
if (!
|
|
15398
|
+
if (!isObject12(value) || !isNonEmptyString11(value.attemptId) || !isEpoch(value.epoch)) {
|
|
14440
15399
|
return { error: "attemptId and a non-negative safe-integer epoch are required" };
|
|
14441
15400
|
}
|
|
14442
15401
|
return { value: { attemptId: value.attemptId, epoch: value.epoch } };
|
|
@@ -14456,7 +15415,7 @@ function consistencyTagsFrom(value) {
|
|
|
14456
15415
|
}
|
|
14457
15416
|
const entries = [];
|
|
14458
15417
|
for (const rawEntry of value) {
|
|
14459
|
-
if (!
|
|
15418
|
+
if (!isObject12(rawEntry) || !isNonEmptyString11(rawEntry.tag)) {
|
|
14460
15419
|
return { error: "each consistency tag needs a non-empty tag" };
|
|
14461
15420
|
}
|
|
14462
15421
|
if (!("lastSortableUniqueId" in rawEntry)) {
|
|
@@ -14484,7 +15443,7 @@ function consistencyTagsFrom(value) {
|
|
|
14484
15443
|
}
|
|
14485
15444
|
function acquireFrom(value, tag) {
|
|
14486
15445
|
const epoch2 = epochFrom(value);
|
|
14487
|
-
if (epoch2.value === void 0 || !
|
|
15446
|
+
if (epoch2.value === void 0 || !isObject12(value)) {
|
|
14488
15447
|
return { error: epoch2.error ?? "Invalid acquire request" };
|
|
14489
15448
|
}
|
|
14490
15449
|
const eventTags = stringArrayFrom(value.eventTags, "eventTags");
|
|
@@ -14507,7 +15466,7 @@ function acquireFrom(value, tag) {
|
|
|
14507
15466
|
}
|
|
14508
15467
|
function reservationFrom(value) {
|
|
14509
15468
|
const epoch2 = epochFrom(value);
|
|
14510
|
-
if (epoch2.value === void 0 || !
|
|
15469
|
+
if (epoch2.value === void 0 || !isObject12(value)) {
|
|
14511
15470
|
return { error: epoch2.error ?? "Invalid reservation request" };
|
|
14512
15471
|
}
|
|
14513
15472
|
if (value.reservationToken !== void 0 && !isNonEmptyString11(value.reservationToken)) {
|
|
@@ -14526,7 +15485,7 @@ function reservationFrom(value) {
|
|
|
14526
15485
|
}
|
|
14527
15486
|
function appendFrom(value, tag) {
|
|
14528
15487
|
const reservation = reservationFrom(value);
|
|
14529
|
-
if (reservation.value === void 0 || !
|
|
15488
|
+
if (reservation.value === void 0 || !isObject12(value) || !Array.isArray(value.candidates)) {
|
|
14530
15489
|
return { error: reservation.error ?? "candidates must be an array" };
|
|
14531
15490
|
}
|
|
14532
15491
|
if (value.candidates.length === 0) {
|
|
@@ -14534,7 +15493,7 @@ function appendFrom(value, tag) {
|
|
|
14534
15493
|
}
|
|
14535
15494
|
const candidates = [];
|
|
14536
15495
|
for (const rawCandidate of value.candidates) {
|
|
14537
|
-
if (!
|
|
15496
|
+
if (!isObject12(rawCandidate) || !isNonEmptyString11(rawCandidate.eventId) || !isNonEmptyString11(rawCandidate.suid) || typeof rawCandidate.payload !== "string") {
|
|
14538
15497
|
return { error: "each candidate needs eventId, suid, and payload" };
|
|
14539
15498
|
}
|
|
14540
15499
|
const eventTags = stringArrayFrom(rawCandidate.eventTags, "candidate eventTags");
|
|
@@ -14548,8 +15507,8 @@ function appendFrom(value, tag) {
|
|
|
14548
15507
|
assertSortableUniqueId(rawCandidate.suid);
|
|
14549
15508
|
if (!isUuidV7(rawCandidate.eventId)) return { error: "candidate eventId must be a UUID v7" };
|
|
14550
15509
|
JSON.parse(rawCandidate.payload);
|
|
14551
|
-
} catch (
|
|
14552
|
-
return { error:
|
|
15510
|
+
} catch (error11) {
|
|
15511
|
+
return { error: error11 instanceof Error ? error11.message : "candidate identity or payload is invalid" };
|
|
14553
15512
|
}
|
|
14554
15513
|
if (rawCandidate.provenance !== "g32") return { error: "candidate provenance must be g32" };
|
|
14555
15514
|
if (!isNonEmptyString11(rawCandidate.allocatorLineageId)) return { error: "candidate allocatorLineageId is required" };
|
|
@@ -14583,10 +15542,10 @@ function appendFrom(value, tag) {
|
|
|
14583
15542
|
return { value: { ...reservation.value, candidates: ordered, faultInjection } };
|
|
14584
15543
|
}
|
|
14585
15544
|
function bootstrapAppendFrom(value, tag) {
|
|
14586
|
-
if (!
|
|
15545
|
+
if (!isObject12(value) || !isNonEmptyString11(value.importId) || !isEpoch(value.leaseEpoch) || !isNonEmptyString11(value.manifestDigest) || !isNonEmptyString11(value.targetServiceId) || !Array.isArray(value.candidates) || value.candidates.length === 0) return { error: "bootstrap identity and candidates are required" };
|
|
14587
15546
|
const candidates = [];
|
|
14588
15547
|
for (const candidate of value.candidates) {
|
|
14589
|
-
if (!
|
|
15548
|
+
if (!isObject12(candidate) || !isNonEmptyString11(candidate.eventId) || !isNonEmptyString11(candidate.suid) || typeof candidate.payload !== "string") return { error: "bootstrap candidate is invalid" };
|
|
14590
15549
|
const tags = stringArrayFrom(candidate.eventTags, "bootstrap candidate eventTags");
|
|
14591
15550
|
if (tags.value === void 0 || !tags.value.includes(tag) || !isNonEmptyString11(candidate.allocatorLineageId)) return { error: "bootstrap candidate must include this tag and lineage" };
|
|
14592
15551
|
let eventType;
|
|
@@ -14596,8 +15555,8 @@ function bootstrapAppendFrom(value, tag) {
|
|
|
14596
15555
|
assertSortableUniqueId(candidate.suid);
|
|
14597
15556
|
if (!isRfc4122Uuid(candidate.eventId)) return { error: "bootstrap candidate eventId must be an RFC 4122 UUID" };
|
|
14598
15557
|
JSON.parse(candidate.payload);
|
|
14599
|
-
} catch (
|
|
14600
|
-
return { error:
|
|
15558
|
+
} catch (error11) {
|
|
15559
|
+
return { error: error11 instanceof Error ? error11.message : "bootstrap candidate identity or payload is invalid" };
|
|
14601
15560
|
}
|
|
14602
15561
|
if (candidate.provenance !== "g32") return { error: "bootstrap candidate provenance must be g32" };
|
|
14603
15562
|
if (!isNonEmptyString11(candidate.timestamp) || !CANONICAL_UTC_TIMESTAMP_PATTERN.test(candidate.timestamp)) return { error: "bootstrap candidate timestamp must be canonical UTC" };
|
|
@@ -14609,13 +15568,13 @@ function bootstrapAppendFrom(value, tag) {
|
|
|
14609
15568
|
}
|
|
14610
15569
|
function fenceFrom(value) {
|
|
14611
15570
|
const epoch2 = epochFrom(value);
|
|
14612
|
-
if (epoch2.value === void 0 || !
|
|
15571
|
+
if (epoch2.value === void 0 || !isObject12(value) || !isNonEmptyString11(value.reason)) {
|
|
14613
15572
|
return { error: epoch2.error ?? "reason is required" };
|
|
14614
15573
|
}
|
|
14615
15574
|
return { value: { ...epoch2.value, reason: value.reason } };
|
|
14616
15575
|
}
|
|
14617
15576
|
function repairScopeItemFrom(value, tag) {
|
|
14618
|
-
if (!
|
|
15577
|
+
if (!isObject12(value) || !isNonEmptyString11(value.attemptId) || !isNonEmptyString11(value.eventId) || !isNonEmptyString11(value.suid) || typeof value.payload !== "string" || !isNonEmptyString11(value.allocatorLineageId) || !isNonEmptyString11(value.eventType) || value.provenance !== "g32" || !isNonEmptyString11(value.timestamp)) {
|
|
14619
15578
|
return { error: "each repair scope item needs attemptId, eventId, suid, and payload" };
|
|
14620
15579
|
}
|
|
14621
15580
|
const eventTags = stringArrayFrom(value.eventTags, "repair eventTags");
|
|
@@ -14663,13 +15622,13 @@ function repairScopeFrom(value, tag) {
|
|
|
14663
15622
|
return { value: sortRepairScope(scope) };
|
|
14664
15623
|
}
|
|
14665
15624
|
function repairLeaseFrom(value) {
|
|
14666
|
-
if (!
|
|
15625
|
+
if (!isObject12(value) || !isNonEmptyString11(value.owner) || !isEpoch(value.epoch)) {
|
|
14667
15626
|
return { error: "repair owner and non-negative safe-integer epoch are required" };
|
|
14668
15627
|
}
|
|
14669
15628
|
return { value: { owner: value.owner, epoch: value.epoch } };
|
|
14670
15629
|
}
|
|
14671
15630
|
function repairAcquireFrom(value, tag) {
|
|
14672
|
-
if (!
|
|
15631
|
+
if (!isObject12(value) || !isNonEmptyString11(value.owner)) {
|
|
14673
15632
|
return { error: "repair owner is required" };
|
|
14674
15633
|
}
|
|
14675
15634
|
const leaseMs = value.leaseMs ?? DEFAULT_REPAIR_LEASE_MS;
|
|
@@ -14681,7 +15640,7 @@ function repairAcquireFrom(value, tag) {
|
|
|
14681
15640
|
}
|
|
14682
15641
|
function repairScopeUnionFrom(value, tag) {
|
|
14683
15642
|
const lease = repairLeaseFrom(value);
|
|
14684
|
-
if (lease.value === void 0 || !
|
|
15643
|
+
if (lease.value === void 0 || !isObject12(value)) {
|
|
14685
15644
|
return { error: lease.error };
|
|
14686
15645
|
}
|
|
14687
15646
|
const scope = repairScopeFrom(value.scope, tag);
|
|
@@ -14689,7 +15648,7 @@ function repairScopeUnionFrom(value, tag) {
|
|
|
14689
15648
|
}
|
|
14690
15649
|
function repairApplyFrom(value, tag) {
|
|
14691
15650
|
const lease = repairLeaseFrom(value);
|
|
14692
|
-
if (lease.value === void 0 || !
|
|
15651
|
+
if (lease.value === void 0 || !isObject12(value)) {
|
|
14693
15652
|
return { error: lease.error };
|
|
14694
15653
|
}
|
|
14695
15654
|
const item = repairScopeItemFrom(value.item, tag);
|
|
@@ -14697,20 +15656,20 @@ function repairApplyFrom(value, tag) {
|
|
|
14697
15656
|
}
|
|
14698
15657
|
function repairAuditFrom(value, tag) {
|
|
14699
15658
|
const apply = repairApplyFrom(value, tag);
|
|
14700
|
-
if (apply.value === void 0 || !
|
|
15659
|
+
if (apply.value === void 0 || !isObject12(value) || !isNonEmptyString11(value.actor)) {
|
|
14701
15660
|
return { error: apply.error ?? "audit actor is required" };
|
|
14702
15661
|
}
|
|
14703
15662
|
return { value: { ...apply.value, actor: value.actor } };
|
|
14704
15663
|
}
|
|
14705
15664
|
function repairClearFrom(value) {
|
|
14706
15665
|
const lease = repairLeaseFrom(value);
|
|
14707
|
-
if (lease.value === void 0 || !
|
|
15666
|
+
if (lease.value === void 0 || !isObject12(value) || !isNonEmptyString11(value.attemptId) || !isNonNegativeInteger4(value.scopeVersion)) {
|
|
14708
15667
|
return { error: lease.error ?? "attemptId and scopeVersion are required" };
|
|
14709
15668
|
}
|
|
14710
15669
|
return { value: { ...lease.value, attemptId: value.attemptId, scopeVersion: value.scopeVersion } };
|
|
14711
15670
|
}
|
|
14712
15671
|
function outboxPendingFrom(value) {
|
|
14713
|
-
if (!
|
|
15672
|
+
if (!isObject12(value)) {
|
|
14714
15673
|
return { error: "outbox pending request must be an object" };
|
|
14715
15674
|
}
|
|
14716
15675
|
const nowMs = value.nowMs ?? Date.now();
|
|
@@ -14728,22 +15687,22 @@ function outboxPendingFrom(value) {
|
|
|
14728
15687
|
return { value: { nowMs, limit, force } };
|
|
14729
15688
|
}
|
|
14730
15689
|
function outboxMarkFrom(value) {
|
|
14731
|
-
if (!
|
|
15690
|
+
if (!isObject12(value) || !Array.isArray(value.deliveries) || value.deliveries.length === 0) {
|
|
14732
15691
|
return { error: "deliveries must be a non-empty array" };
|
|
14733
15692
|
}
|
|
14734
15693
|
const deliveries = [];
|
|
14735
15694
|
for (const raw of value.deliveries) {
|
|
14736
|
-
if (!
|
|
15695
|
+
if (!isObject12(raw) || !isNonEmptyString11(raw.attemptId) || !isNonEmptyString11(raw.eventId) || !isNonEmptyString11(raw.suid) || typeof raw.payload !== "string" || !isSafeInteger(raw.enqueuedAt)) {
|
|
14737
15696
|
return { error: "each delivery needs attemptId, eventId, suid, payload, and enqueuedAt" };
|
|
14738
15697
|
}
|
|
14739
15698
|
if (!isNonEmptyString11(raw.eventType) || raw.provenance !== "g32" || !isNonEmptyString11(raw.timestamp) || !isNonEmptyString11(raw.allocatorLineageId)) return { error: "delivery must carry the complete G32 identity" };
|
|
14740
|
-
if (!
|
|
15699
|
+
if (!isObject12(raw.completeness)) {
|
|
14741
15700
|
return { error: "delivery must carry a readable global receipt/membership join proof" };
|
|
14742
15701
|
}
|
|
14743
15702
|
const completeness = raw.completeness;
|
|
14744
15703
|
const obligationSequence = completeness.obligationSequence;
|
|
14745
15704
|
const memberships = completeness.localCommittedMembership;
|
|
14746
|
-
if (!isNonEmptyString11(completeness.eventDigest) || !isSafeInteger(obligationSequence) || obligationSequence < 1 || !Array.isArray(memberships) || memberships.length !== 1 || !
|
|
15705
|
+
if (!isNonEmptyString11(completeness.eventDigest) || !isSafeInteger(obligationSequence) || obligationSequence < 1 || !Array.isArray(memberships) || memberships.length !== 1 || !isObject12(memberships[0]) || memberships[0].eventId !== raw.eventId || typeof memberships[0].tag !== "string") {
|
|
14747
15706
|
return { error: "delivery must carry a readable global receipt/membership join proof" };
|
|
14748
15707
|
}
|
|
14749
15708
|
const membership = memberships[0];
|
|
@@ -14782,7 +15741,7 @@ function outboxMarkFrom(value) {
|
|
|
14782
15741
|
return { value: { deliveries, nowMs } };
|
|
14783
15742
|
}
|
|
14784
15743
|
function g44SourceScanFrom(value) {
|
|
14785
|
-
if (!
|
|
15744
|
+
if (!isObject12(value) || !isNonEmptyString11(value.serviceId) || !isNonEmptyString11(value.tag) || !isSafeInteger(value.upperBoundSequence) || value.upperBoundSequence < 0 || !isSafeInteger(value.afterSequence) || value.afterSequence < 0 || !isSafeInteger(value.limit) || value.limit < 1 || value.limit > 256) {
|
|
14786
15745
|
return { error: "source scan needs serviceId, tag, and a bounded compound cursor" };
|
|
14787
15746
|
}
|
|
14788
15747
|
return {
|
|
@@ -14796,7 +15755,7 @@ function g44SourceScanFrom(value) {
|
|
|
14796
15755
|
};
|
|
14797
15756
|
}
|
|
14798
15757
|
function g46TagStateSourceFrom(value) {
|
|
14799
|
-
if (!
|
|
15758
|
+
if (!isObject12(value) || !isNonEmptyString11(value.tag) || typeof value.cursor !== "string" || !isSafeInteger(value.limit) || value.limit < 1 || value.limit > 256 || value.through !== void 0 && typeof value.through !== "string") {
|
|
14800
15759
|
return { error: "G46 source read needs tag, cursor, optional through, and a bounded limit" };
|
|
14801
15760
|
}
|
|
14802
15761
|
try {
|
|
@@ -14970,8 +15929,8 @@ function logicalNow(record) {
|
|
|
14970
15929
|
return record.clockNowMs ?? Date.now() + record.clockOffsetMs;
|
|
14971
15930
|
}
|
|
14972
15931
|
function expireReservation(record) {
|
|
14973
|
-
const
|
|
14974
|
-
if (
|
|
15932
|
+
const active2 = record.activeReservation;
|
|
15933
|
+
if (active2 === null || active2.expiresAt > logicalNow(record)) {
|
|
14975
15934
|
return { record, expired: false };
|
|
14976
15935
|
}
|
|
14977
15936
|
return {
|
|
@@ -15089,7 +16048,7 @@ var TagDurableObject = class {
|
|
|
15089
16048
|
const url = new URL(request.url);
|
|
15090
16049
|
const tag = url.searchParams.get("__tag");
|
|
15091
16050
|
if (!isNonEmptyString11(tag)) {
|
|
15092
|
-
return
|
|
16051
|
+
return error9(400, "tag_identity_required", "Tag identity is required");
|
|
15093
16052
|
}
|
|
15094
16053
|
const serviceId = url.searchParams.get("__serviceId");
|
|
15095
16054
|
if (request.method === "GET" && url.pathname === "/head-facts") {
|
|
@@ -15097,10 +16056,10 @@ var TagDurableObject = class {
|
|
|
15097
16056
|
observation.markFirstStorageRead();
|
|
15098
16057
|
try {
|
|
15099
16058
|
const facts = this.readHeadFacts(tag);
|
|
15100
|
-
return facts === void 0 ?
|
|
16059
|
+
return facts === void 0 ? error9(404, "tag_not_found", "Tag has no durable state yet") : json11(facts);
|
|
15101
16060
|
} catch (caught) {
|
|
15102
16061
|
if (caught instanceof TagIdentityConflict) {
|
|
15103
|
-
return
|
|
16062
|
+
return error9(409, "tag_identity_conflict", "Tag Durable Object identity changed");
|
|
15104
16063
|
}
|
|
15105
16064
|
throw caught;
|
|
15106
16065
|
}
|
|
@@ -15111,9 +16070,9 @@ var TagDurableObject = class {
|
|
|
15111
16070
|
observation.markFirstStorageRead();
|
|
15112
16071
|
const record = this.readStoredRecord(tag);
|
|
15113
16072
|
if (record === void 0) {
|
|
15114
|
-
return
|
|
16073
|
+
return error9(404, "tag_not_found", "Tag has no durable state yet");
|
|
15115
16074
|
}
|
|
15116
|
-
return record.tag === tag ?
|
|
16075
|
+
return record.tag === tag ? json11(requireG32TagRecord(record)) : error9(409, "tag_identity_conflict", "Tag Durable Object identity changed");
|
|
15117
16076
|
});
|
|
15118
16077
|
}
|
|
15119
16078
|
if (request.method === "GET" && url.pathname === "/repair/facts") {
|
|
@@ -15121,7 +16080,7 @@ var TagDurableObject = class {
|
|
|
15121
16080
|
}
|
|
15122
16081
|
if (request.method === "POST" && url.pathname === "/debug/alarm") {
|
|
15123
16082
|
const record = await this.runAlarm();
|
|
15124
|
-
return record === void 0 ?
|
|
16083
|
+
return record === void 0 ? error9(404, "tag_not_found", "Tag has no durable state yet") : json11(record);
|
|
15125
16084
|
}
|
|
15126
16085
|
if (request.method === "POST" && url.pathname === "/acquire") {
|
|
15127
16086
|
return this.traceCommitActor(request, tag, serviceId, activation, observation, (body2) => this.acquire(tag, body2, observation));
|
|
@@ -15144,37 +16103,37 @@ var TagDurableObject = class {
|
|
|
15144
16103
|
}
|
|
15145
16104
|
const body = await this.jsonBody(request);
|
|
15146
16105
|
if (body === void 0) {
|
|
15147
|
-
return
|
|
16106
|
+
return error9(400, "malformed_tag_request", "Request body must be JSON");
|
|
15148
16107
|
}
|
|
15149
16108
|
if (request.method === "POST" && url.pathname === "/__internal/g46/tag-state-incremental") {
|
|
15150
16109
|
if (request.headers.get("x-sdt-g46-source-read") !== "1") {
|
|
15151
|
-
return
|
|
16110
|
+
return error9(403, "g46_source_read_forbidden", "G46 source reads require the TagState adapter");
|
|
15152
16111
|
}
|
|
15153
16112
|
const parsed = g46TagStateSourceFrom(body);
|
|
15154
16113
|
if (parsed.value === void 0 || parsed.value.tag !== tag) {
|
|
15155
|
-
return
|
|
16114
|
+
return error9(400, "g46_source_read_invalid", parsed.error ?? "G46 source read is invalid");
|
|
15156
16115
|
}
|
|
15157
16116
|
try {
|
|
15158
|
-
return
|
|
16117
|
+
return json11(await this.g43TagStateIncrementalCatchUp(parsed.value));
|
|
15159
16118
|
} catch (caught) {
|
|
15160
16119
|
if (caught instanceof TagIdentityConflict) {
|
|
15161
|
-
return
|
|
16120
|
+
return error9(409, "tag_identity_conflict", "Tag Durable Object identity changed");
|
|
15162
16121
|
}
|
|
15163
|
-
return
|
|
16122
|
+
return error9(503, "g46_source_read_unavailable", caught instanceof Error ? caught.message : "G46 source read is unavailable");
|
|
15164
16123
|
}
|
|
15165
16124
|
}
|
|
15166
16125
|
if (request.method === "POST" && url.pathname === "/__internal/g44/source-obligations") {
|
|
15167
16126
|
if (request.headers.get("x-sdt-g44-source-scan") !== "1") {
|
|
15168
|
-
return
|
|
16127
|
+
return error9(403, "g44_source_scan_forbidden", "G44 source scan requires the internal scanner seam");
|
|
15169
16128
|
}
|
|
15170
16129
|
const parsed = g44SourceScanFrom(body);
|
|
15171
16130
|
if (parsed.value === void 0 || parsed.value.tag !== tag || parsed.value.serviceId !== serviceId) {
|
|
15172
|
-
return
|
|
16131
|
+
return error9(400, "g44_source_scan_invalid", parsed.error ?? "G44 source scan identity is invalid");
|
|
15173
16132
|
}
|
|
15174
16133
|
try {
|
|
15175
|
-
return
|
|
16134
|
+
return json11(await this.g44ReadSourceObligations(parsed.value));
|
|
15176
16135
|
} catch (failure3) {
|
|
15177
|
-
return
|
|
16136
|
+
return error9(503, "g44_source_scan_unavailable", failure3 instanceof Error ? failure3.message : "G44 source scan is unavailable");
|
|
15178
16137
|
}
|
|
15179
16138
|
}
|
|
15180
16139
|
if (request.method === "POST" && url.pathname === "/bootstrap/admit") {
|
|
@@ -15221,7 +16180,7 @@ var TagDurableObject = class {
|
|
|
15221
16180
|
}
|
|
15222
16181
|
if (request.method === "POST" && url.pathname === "/__internal/g77/inspect-target") {
|
|
15223
16182
|
if (!isNonEmptyString11(serviceId)) {
|
|
15224
|
-
return
|
|
16183
|
+
return error9(400, "service_identity_required", "Service identity is required");
|
|
15225
16184
|
}
|
|
15226
16185
|
return this.inspectG77Target(serviceId, tag, body);
|
|
15227
16186
|
}
|
|
@@ -15229,28 +16188,28 @@ var TagDurableObject = class {
|
|
|
15229
16188
|
return this.fenceAbsentG77Target(tag, body);
|
|
15230
16189
|
}
|
|
15231
16190
|
if (request.method === "POST" && url.pathname === "/__internal/g77/capabilities") {
|
|
15232
|
-
return
|
|
16191
|
+
return json11({ inspectTarget: true, fenceAbsentTarget: true });
|
|
15233
16192
|
}
|
|
15234
|
-
return
|
|
16193
|
+
return error9(404, "tag_route_not_found", "Tag route was not found");
|
|
15235
16194
|
}
|
|
15236
16195
|
/** Identity-bound installed/absent inspection for G77 issuance reconciliation. */
|
|
15237
16196
|
inspectG77Target(serviceId, tag, body) {
|
|
15238
|
-
if (!
|
|
15239
|
-
return
|
|
16197
|
+
if (!isObject12(body) || !isNonEmptyString11(body.eventId) || !isNonEmptyString11(body.suid)) {
|
|
16198
|
+
return error9(400, "invalid_inspect_target", "eventId and suid are required");
|
|
15240
16199
|
}
|
|
15241
16200
|
const pinnedWriterEpoch = isNonNegativeInteger4(body.pinnedWriterEpoch) ? body.pinnedWriterEpoch : 0;
|
|
15242
16201
|
const attemptId = isNonEmptyString11(body.attemptId) ? body.attemptId : void 0;
|
|
15243
16202
|
const sql = this.sqlStorage();
|
|
15244
|
-
if (sql === void 0) return
|
|
16203
|
+
if (sql === void 0) return error9(503, "tag_sql_unavailable", "Tag SQL storage is unavailable");
|
|
15245
16204
|
const identity2 = sql.exec("SELECT tag FROM tag_identity WHERE singleton = 1").toArray()[0];
|
|
15246
16205
|
if (identity2 === void 0) {
|
|
15247
16206
|
if (attemptId !== void 0) {
|
|
15248
|
-
return
|
|
16207
|
+
return json11({ inspectionStatus: "absent-never-contacted" });
|
|
15249
16208
|
}
|
|
15250
|
-
return
|
|
16209
|
+
return error9(409, "target_not_terminal", "Target is not terminal under pinned writer authority");
|
|
15251
16210
|
}
|
|
15252
16211
|
if (sqlString(identity2.tag, "tag_identity.tag") !== tag) {
|
|
15253
|
-
return
|
|
16212
|
+
return error9(409, "tag_identity_mismatch", "Tag identity does not match the inspected tag");
|
|
15254
16213
|
}
|
|
15255
16214
|
const event = sql.exec(`
|
|
15256
16215
|
SELECT attempt_id, event_id, suid FROM tag_event
|
|
@@ -15263,41 +16222,41 @@ var TagDurableObject = class {
|
|
|
15263
16222
|
`, serviceId, body.eventId).toArray()[0];
|
|
15264
16223
|
if (obligation !== void 0) {
|
|
15265
16224
|
const obligationDigest = sqlString(obligation.event_digest, "tag_outbox_obligation.event_digest");
|
|
15266
|
-
return
|
|
16225
|
+
return json11({
|
|
15267
16226
|
terminalStatus: "installed-and-covered",
|
|
15268
16227
|
eventId: body.eventId,
|
|
15269
16228
|
obligationDigest
|
|
15270
16229
|
});
|
|
15271
16230
|
}
|
|
15272
|
-
return
|
|
16231
|
+
return error9(409, "target_not_terminal", "Target append is in flight under pinned writer authority");
|
|
15273
16232
|
}
|
|
15274
16233
|
if (attemptId !== void 0) {
|
|
15275
16234
|
const tombstone = sql.exec("SELECT epoch FROM tag_tombstone WHERE attempt_id = ?", attemptId).toArray()[0];
|
|
15276
16235
|
const tombstoneEpoch = tombstone === void 0 ? void 0 : sqlNumber(tombstone.epoch, "tag_tombstone.epoch");
|
|
15277
16236
|
if (tombstoneEpoch !== void 0 && pinnedWriterEpoch <= tombstoneEpoch) {
|
|
15278
|
-
return
|
|
16237
|
+
return json11({ terminalStatus: "absent-and-irrevocably-fenced", tombstoneEpoch });
|
|
15279
16238
|
}
|
|
15280
16239
|
const reservation = sql.exec("SELECT attempt_id, expires_at FROM tag_reservation WHERE singleton = 1").toArray()[0];
|
|
15281
16240
|
if (reservation !== void 0 && sqlString(reservation.attempt_id, "tag_reservation.attempt_id") === attemptId) {
|
|
15282
|
-
return
|
|
16241
|
+
return error9(409, "target_not_terminal", "Active reservation holds writer authority");
|
|
15283
16242
|
}
|
|
15284
16243
|
const epoch2 = sql.exec("SELECT attempt_id FROM tag_epoch WHERE attempt_id = ?", attemptId).toArray()[0];
|
|
15285
16244
|
if (epoch2 !== void 0) {
|
|
15286
|
-
return
|
|
16245
|
+
return json11({ inspectionStatus: "absent-but-unfenced" });
|
|
15287
16246
|
}
|
|
15288
|
-
return
|
|
16247
|
+
return json11({ inspectionStatus: "absent-never-contacted" });
|
|
15289
16248
|
}
|
|
15290
|
-
return
|
|
16249
|
+
return error9(409, "target_not_terminal", "Target is not terminal under pinned writer authority");
|
|
15291
16250
|
}
|
|
15292
16251
|
/** Reconciler-only fence for never-contacted targets; creates durable tombstone state. */
|
|
15293
16252
|
async fenceAbsentG77Target(tag, body) {
|
|
15294
16253
|
const parsed = reservationFrom(body);
|
|
15295
16254
|
if (parsed.value === void 0) {
|
|
15296
|
-
return
|
|
16255
|
+
return error9(400, "invalid_fence_target", parsed.error ?? "attemptId and epoch are required");
|
|
15297
16256
|
}
|
|
15298
16257
|
this.ensureSqlTag(tag);
|
|
15299
16258
|
const result2 = await this.cancelSql(tag, { ...parsed.value, forceTombstone: true });
|
|
15300
|
-
return result2.status >= 200 && result2.status < 300 ?
|
|
16259
|
+
return result2.status >= 200 && result2.status < 300 ? json11(result2.body) : error9(result2.status, "fence_target_rejected", "Absent target fence was rejected");
|
|
15301
16260
|
}
|
|
15302
16261
|
async alarm() {
|
|
15303
16262
|
this.activation.beginHandler();
|
|
@@ -15644,21 +16603,21 @@ var TagDurableObject = class {
|
|
|
15644
16603
|
const result2 = await authority;
|
|
15645
16604
|
g44GlobalArrayAuthorityResultByD1.set(database, result2);
|
|
15646
16605
|
return result2;
|
|
15647
|
-
} catch (
|
|
16606
|
+
} catch (error11) {
|
|
15648
16607
|
if (g44GlobalArrayAuthorityByD1.get(database) === authority) {
|
|
15649
16608
|
g44GlobalArrayAuthorityByD1.delete(database);
|
|
15650
16609
|
}
|
|
15651
|
-
throw
|
|
16610
|
+
throw error11;
|
|
15652
16611
|
}
|
|
15653
16612
|
}
|
|
15654
16613
|
async readG44GlobalArrayAuthority(database) {
|
|
15655
16614
|
try {
|
|
15656
16615
|
await database.prepare('SELECT "EventDigest" FROM dcb_events WHERE 1 = 0').all();
|
|
15657
16616
|
return true;
|
|
15658
|
-
} catch (
|
|
15659
|
-
const message =
|
|
16617
|
+
} catch (error11) {
|
|
16618
|
+
const message = error11 instanceof Error ? error11.message : String(error11);
|
|
15660
16619
|
if (/no such (?:table|column):\s*(?:dcb_events|EventDigest)/i.test(message)) return false;
|
|
15661
|
-
throw
|
|
16620
|
+
throw error11;
|
|
15662
16621
|
}
|
|
15663
16622
|
}
|
|
15664
16623
|
async traceCommitActor(request, tag, serviceId, activation, observation, callback) {
|
|
@@ -15668,10 +16627,10 @@ var TagDurableObject = class {
|
|
|
15668
16627
|
{ actorClass: "TAG", actorKey: `tag:${serviceId}:${tag}`, activation, observation },
|
|
15669
16628
|
async () => {
|
|
15670
16629
|
body = await this.jsonBody(request);
|
|
15671
|
-
const attemptId =
|
|
16630
|
+
const attemptId = isObject12(body) && isNonEmptyString11(body.attemptId) ? body.attemptId : void 0;
|
|
15672
16631
|
return attemptId === void 0 || !isNonEmptyString11(serviceId) ? void 0 : { attemptId, serviceId };
|
|
15673
16632
|
},
|
|
15674
|
-
async () => body === void 0 ?
|
|
16633
|
+
async () => body === void 0 ? error9(400, "malformed_tag_request", "Request body must be JSON") : callback(body)
|
|
15675
16634
|
);
|
|
15676
16635
|
}
|
|
15677
16636
|
/**
|
|
@@ -15885,6 +16844,7 @@ var TagDurableObject = class {
|
|
|
15885
16844
|
ensureSqlTag(tag) {
|
|
15886
16845
|
const sql = this.sqlStorage();
|
|
15887
16846
|
if (sql === void 0) throw new Error("Tag SQL storage is unavailable");
|
|
16847
|
+
ensureTagCommitReceiptWrittenVersion(sql);
|
|
15888
16848
|
const existing = sql.exec("SELECT tag FROM tag_identity WHERE singleton = 1").toArray()[0];
|
|
15889
16849
|
if (existing !== void 0) {
|
|
15890
16850
|
if (sqlString(existing.tag, "tag_identity.tag") !== tag) throw new Error("Tag Durable Object identity changed");
|
|
@@ -15997,17 +16957,18 @@ var TagDurableObject = class {
|
|
|
15997
16957
|
sql.exec("UPDATE tag_control SET head_suid = ?, version = ?, updated_at = ? WHERE singleton = 1", head, version, committedAt);
|
|
15998
16958
|
sql.exec("UPDATE tag_head SET service_id = ?, head_suid = ? WHERE singleton = 1", serviceId, head);
|
|
15999
16959
|
}
|
|
16000
|
-
writeCommittedSqlReceipt(sql, input, committedAt, eventCount, head, confirmsReservation) {
|
|
16960
|
+
writeCommittedSqlReceipt(sql, input, committedAt, eventCount, head, confirmsReservation, writtenVersion) {
|
|
16001
16961
|
sql.exec(`
|
|
16002
16962
|
INSERT INTO tag_commit_receipt
|
|
16003
|
-
(attempt_id, epoch, committed_at, committed_event_count, head_suid, reservation_confirmed)
|
|
16004
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
16963
|
+
(attempt_id, epoch, committed_at, committed_event_count, head_suid, reservation_confirmed, written_version)
|
|
16964
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
16005
16965
|
ON CONFLICT(attempt_id, epoch) DO UPDATE SET
|
|
16006
16966
|
committed_at = excluded.committed_at,
|
|
16007
16967
|
committed_event_count = excluded.committed_event_count,
|
|
16008
16968
|
head_suid = excluded.head_suid,
|
|
16009
|
-
reservation_confirmed = excluded.reservation_confirmed
|
|
16010
|
-
|
|
16969
|
+
reservation_confirmed = excluded.reservation_confirmed,
|
|
16970
|
+
written_version = excluded.written_version
|
|
16971
|
+
`, input.attemptId, input.epoch, committedAt, eventCount, head, confirmsReservation ? 1 : 0, writtenVersion);
|
|
16011
16972
|
}
|
|
16012
16973
|
/**
|
|
16013
16974
|
* The normal append path deliberately probes only the indexed rows it needs
|
|
@@ -16049,11 +17010,29 @@ var TagDurableObject = class {
|
|
|
16049
17010
|
return { event, obligation, exact };
|
|
16050
17011
|
});
|
|
16051
17012
|
if (existingCandidates.every(({ exact }) => exact)) {
|
|
16052
|
-
|
|
17013
|
+
const receipts = sql.exec(`
|
|
17014
|
+
SELECT epoch, committed_at, written_version, head_suid FROM tag_commit_receipt
|
|
17015
|
+
WHERE attempt_id = ?
|
|
17016
|
+
`, input.attemptId).toArray();
|
|
17017
|
+
const sameEpoch = receipts.find((row) => sqlNumber(row.epoch, "tag_commit_receipt.epoch") === input.epoch);
|
|
17018
|
+
const replayHead = input.candidates.at(-1)?.suid;
|
|
17019
|
+
const byHead = replayHead === void 0 ? [] : receipts.filter((row) => sqlString(row.head_suid, "tag_commit_receipt.head_suid") === replayHead);
|
|
17020
|
+
const receipt = sameEpoch ?? (byHead.length === 1 ? byHead[0] : void 0) ?? (receipts.length === 1 ? receipts[0] : void 0);
|
|
17021
|
+
if (receipt === void 0) {
|
|
17022
|
+
await this.rearmScheduler(txn);
|
|
17023
|
+
return { ...rejected("duplicate_receipt_missing"), body: { ...rejected("duplicate_receipt_missing").body, version } };
|
|
17024
|
+
}
|
|
17025
|
+
const storedAt = sqlNullableString(receipt.committed_at, "tag_commit_receipt.committed_at");
|
|
17026
|
+
const storedVersion = sqlNullableNumber(receipt.written_version, "tag_commit_receipt.written_version");
|
|
17027
|
+
if (storedAt === null || storedVersion === null) {
|
|
17028
|
+
await this.rearmScheduler(txn);
|
|
17029
|
+
return { ...rejected("duplicate_receipt_unknown"), body: { ...rejected("duplicate_receipt_unknown").body, version } };
|
|
17030
|
+
}
|
|
17031
|
+
return { status: 200, body: { status: "duplicate", version: storedVersion, updatedAt: storedAt } };
|
|
16053
17032
|
}
|
|
16054
|
-
const
|
|
17033
|
+
const active2 = sql.exec("SELECT * FROM tag_reservation WHERE singleton = 1").toArray()[0];
|
|
16055
17034
|
const now2 = sqlNullableNumber(control.clock_now_ms, "tag_control.clock_now_ms") ?? Date.now() + sqlNumber(control.clock_offset_ms, "tag_control.clock_offset_ms");
|
|
16056
|
-
let reservation =
|
|
17035
|
+
let reservation = active2;
|
|
16057
17036
|
if (reservation !== void 0 && sqlNumber(reservation.expires_at, "tag_reservation.expires_at") <= now2) {
|
|
16058
17037
|
sql.exec("DELETE FROM tag_reservation WHERE singleton = 1");
|
|
16059
17038
|
version += 1;
|
|
@@ -16146,7 +17125,7 @@ var TagDurableObject = class {
|
|
|
16146
17125
|
if (input.faultInjection === "after-append-before-confirm") {
|
|
16147
17126
|
throw new AppendTransactionFault("Simulated interruption before atomic commit receipt");
|
|
16148
17127
|
}
|
|
16149
|
-
this.writeCommittedSqlReceipt(sql, input, committedAt, events.length, head, confirmsReservation);
|
|
17128
|
+
this.writeCommittedSqlReceipt(sql, input, committedAt, events.length, head, confirmsReservation, version + 1);
|
|
16150
17129
|
if (confirmsReservation) sql.exec("DELETE FROM tag_reservation WHERE singleton = 1");
|
|
16151
17130
|
await this.rearmScheduler(txn);
|
|
16152
17131
|
return {
|
|
@@ -16154,7 +17133,8 @@ var TagDurableObject = class {
|
|
|
16154
17133
|
body: {
|
|
16155
17134
|
status: "appended",
|
|
16156
17135
|
fenceGate: { checked: true, activeFenceCount: 0 },
|
|
16157
|
-
version: version + 1
|
|
17136
|
+
version: version + 1,
|
|
17137
|
+
updatedAt: committedAt
|
|
16158
17138
|
},
|
|
16159
17139
|
hopFacts
|
|
16160
17140
|
};
|
|
@@ -16555,18 +17535,20 @@ var TagDurableObject = class {
|
|
|
16555
17535
|
);
|
|
16556
17536
|
this.upsertSourcePartitionRegistration(sql, eventServiceId, record.tag, outbox.eventId);
|
|
16557
17537
|
}
|
|
17538
|
+
ensureTagCommitReceiptWrittenVersion(sql);
|
|
16558
17539
|
for (const confirmation of record.confirmations) {
|
|
16559
17540
|
sql.exec(
|
|
16560
17541
|
`
|
|
16561
17542
|
INSERT OR IGNORE INTO tag_commit_receipt
|
|
16562
|
-
(attempt_id, epoch, committed_at, committed_event_count, head_suid, reservation_confirmed)
|
|
16563
|
-
VALUES (?, ?, ?, ?, ?, 1)
|
|
17543
|
+
(attempt_id, epoch, committed_at, committed_event_count, head_suid, reservation_confirmed, written_version)
|
|
17544
|
+
VALUES (?, ?, ?, ?, ?, 1, ?)
|
|
16564
17545
|
`,
|
|
16565
17546
|
confirmation.attemptId,
|
|
16566
17547
|
confirmation.epoch,
|
|
16567
17548
|
record.updatedAt,
|
|
16568
17549
|
record.events.filter((event) => event.attemptId === confirmation.attemptId).length,
|
|
16569
|
-
record.head
|
|
17550
|
+
record.head,
|
|
17551
|
+
record.version
|
|
16570
17552
|
);
|
|
16571
17553
|
}
|
|
16572
17554
|
}
|
|
@@ -16615,13 +17597,13 @@ var TagDurableObject = class {
|
|
|
16615
17597
|
async acquire(tag, body, observation) {
|
|
16616
17598
|
const parsed = acquireFrom(body, tag);
|
|
16617
17599
|
if (parsed.value === void 0) {
|
|
16618
|
-
return
|
|
17600
|
+
return error9(400, "invalid_tag_acquire", parsed.error ?? "Invalid acquire request");
|
|
16619
17601
|
}
|
|
16620
17602
|
const input = parsed.value;
|
|
16621
17603
|
observation?.markFirstStorageRead();
|
|
16622
17604
|
if (this.sqlStorage() !== void 0) {
|
|
16623
17605
|
const result3 = await this.acquireSql(tag, input);
|
|
16624
|
-
return
|
|
17606
|
+
return json11(result3.body, result3.status);
|
|
16625
17607
|
}
|
|
16626
17608
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
16627
17609
|
const loaded = await this.recordFor(txn, tag);
|
|
@@ -16656,9 +17638,9 @@ var TagDurableObject = class {
|
|
|
16656
17638
|
body: { ...rejected("consistency_head_mismatch").body, version: record.version }
|
|
16657
17639
|
};
|
|
16658
17640
|
}
|
|
16659
|
-
const
|
|
16660
|
-
if (
|
|
16661
|
-
if (
|
|
17641
|
+
const active2 = record.activeReservation;
|
|
17642
|
+
if (active2 !== null) {
|
|
17643
|
+
if (active2.attemptId === input.attemptId && active2.epoch === input.epoch) {
|
|
16662
17644
|
return {
|
|
16663
17645
|
status: 200,
|
|
16664
17646
|
body: {
|
|
@@ -16703,18 +17685,18 @@ var TagDurableObject = class {
|
|
|
16703
17685
|
body: { status: "reserved", reservation, fenceGate, version: updated.version }
|
|
16704
17686
|
};
|
|
16705
17687
|
});
|
|
16706
|
-
return
|
|
17688
|
+
return json11(result2.body, result2.status);
|
|
16707
17689
|
}
|
|
16708
17690
|
async cancel(tag, body, observation) {
|
|
16709
17691
|
const parsed = reservationFrom(body);
|
|
16710
17692
|
if (parsed.value === void 0) {
|
|
16711
|
-
return
|
|
17693
|
+
return error9(400, "invalid_tag_cancel", parsed.error ?? "Invalid cancel request");
|
|
16712
17694
|
}
|
|
16713
17695
|
const input = parsed.value;
|
|
16714
17696
|
observation?.markFirstStorageRead();
|
|
16715
17697
|
if (this.sqlStorage() !== void 0) {
|
|
16716
17698
|
const result3 = await this.cancelSql(tag, input);
|
|
16717
|
-
return
|
|
17699
|
+
return json11(result3.body, result3.status);
|
|
16718
17700
|
}
|
|
16719
17701
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
16720
17702
|
const loaded = await this.recordFor(txn, tag);
|
|
@@ -16749,8 +17731,8 @@ var TagDurableObject = class {
|
|
|
16749
17731
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
16750
17732
|
return { ...rejected(epochError), body: { ...rejected(epochError).body, version: record.version } };
|
|
16751
17733
|
}
|
|
16752
|
-
const
|
|
16753
|
-
if (
|
|
17734
|
+
const active2 = record.activeReservation;
|
|
17735
|
+
if (active2 === null || active2.attemptId !== input.attemptId || active2.epoch !== input.epoch || active2.token !== input.reservationToken) {
|
|
16754
17736
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
16755
17737
|
return {
|
|
16756
17738
|
...rejected("reservation_token_required"),
|
|
@@ -16765,12 +17747,12 @@ var TagDurableObject = class {
|
|
|
16765
17747
|
});
|
|
16766
17748
|
return { status: 200, body: { status: "cancelled", idempotent: false, version: updated.version } };
|
|
16767
17749
|
});
|
|
16768
|
-
return
|
|
17750
|
+
return json11(result2.body, result2.status);
|
|
16769
17751
|
}
|
|
16770
17752
|
async seal(tag, body, observation) {
|
|
16771
17753
|
const parsed = epochFrom(body);
|
|
16772
17754
|
if (parsed.value === void 0) {
|
|
16773
|
-
return
|
|
17755
|
+
return error9(400, "invalid_tag_seal", parsed.error ?? "Invalid seal request");
|
|
16774
17756
|
}
|
|
16775
17757
|
const input = parsed.value;
|
|
16776
17758
|
observation?.markFirstStorageRead();
|
|
@@ -16805,7 +17787,7 @@ var TagDurableObject = class {
|
|
|
16805
17787
|
});
|
|
16806
17788
|
return { status: 200, body: { status: "sealed", highestEpoch: input.epoch, version: updated.version } };
|
|
16807
17789
|
});
|
|
16808
|
-
return
|
|
17790
|
+
return json11(result2.body, result2.status);
|
|
16809
17791
|
}
|
|
16810
17792
|
/**
|
|
16811
17793
|
* Coordinator-only import admission. This is deliberately not a flag on
|
|
@@ -16814,8 +17796,8 @@ var TagDurableObject = class {
|
|
|
16814
17796
|
*/
|
|
16815
17797
|
async bootstrapAppend(tag, body, serviceId) {
|
|
16816
17798
|
const parsed = bootstrapAppendFrom(body, tag);
|
|
16817
|
-
if (parsed.value === void 0) return
|
|
16818
|
-
if (serviceId !== parsed.value.targetServiceId) return
|
|
17799
|
+
if (parsed.value === void 0) return error9(400, "invalid_bootstrap_admission", parsed.error ?? "Invalid bootstrap admission");
|
|
17800
|
+
if (serviceId !== parsed.value.targetServiceId) return error9(409, "bootstrap_service_scope_mismatch", "Bootstrap target service does not match tag scope");
|
|
16819
17801
|
const input = parsed.value;
|
|
16820
17802
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
16821
17803
|
const loaded = await this.recordFor(txn, tag);
|
|
@@ -16836,10 +17818,10 @@ var TagDurableObject = class {
|
|
|
16836
17818
|
);
|
|
16837
17819
|
return { status: 201, body: { status: "bootstrap_admitted", version: updated.version } };
|
|
16838
17820
|
});
|
|
16839
|
-
return
|
|
17821
|
+
return json11(result2.body, result2.status);
|
|
16840
17822
|
}
|
|
16841
17823
|
async closeBootstrap(tag, body) {
|
|
16842
|
-
if (!
|
|
17824
|
+
if (!isObject12(body) || !isNonEmptyString11(body.importId) || !isEpoch(body.leaseEpoch)) return error9(400, "invalid_bootstrap_close", "bootstrap importId and leaseEpoch are required");
|
|
16843
17825
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
16844
17826
|
const loaded = await this.recordFor(txn, tag);
|
|
16845
17827
|
const record = loaded.record;
|
|
@@ -16849,7 +17831,7 @@ var TagDurableObject = class {
|
|
|
16849
17831
|
const updated = await this.commit(txn, record, { bootstrapAdmission: { ...admission, closed: true } });
|
|
16850
17832
|
return { status: 200, body: { status: "closed", version: updated.version } };
|
|
16851
17833
|
});
|
|
16852
|
-
return
|
|
17834
|
+
return json11(result2.body, result2.status);
|
|
16853
17835
|
}
|
|
16854
17836
|
directDoorbellPreflight(domainDeliveryClass) {
|
|
16855
17837
|
const domainClass = domainDeliveryClass === void 0 ? void 0 : readDomainDeliveryClass({ DOMAIN_DELIVERY_CLASS: domainDeliveryClass });
|
|
@@ -16868,7 +17850,7 @@ var TagDurableObject = class {
|
|
|
16868
17850
|
async append(tag, body, serviceId, domainDeliveryClass, observation) {
|
|
16869
17851
|
const parsed = appendFrom(body, tag);
|
|
16870
17852
|
if (parsed.value === void 0) {
|
|
16871
|
-
return
|
|
17853
|
+
return error9(400, "invalid_tag_append", parsed.error ?? "Invalid append request");
|
|
16872
17854
|
}
|
|
16873
17855
|
const input = parsed.value;
|
|
16874
17856
|
try {
|
|
@@ -16906,7 +17888,7 @@ var TagDurableObject = class {
|
|
|
16906
17888
|
if ((result3.status === 201 || result3.status === 200) && serviceId !== null && serviceId.length > 0 && sourcePartitionRegistration !== "unconfigured") {
|
|
16907
17889
|
this.scheduleSourcePartitionWatermark(tag, serviceId);
|
|
16908
17890
|
}
|
|
16909
|
-
const response4 =
|
|
17891
|
+
const response4 = json11(result3.body, result3.status);
|
|
16910
17892
|
if (result3.status === 201 && serviceId !== null && serviceId.length > 0 && this.env.AUTO_DRAIN_OUTBOX === "true" && (this.env.DOWNSTREAM_QUEUE !== void 0 || doorbellPreflight.status === "ready" && this.env.DOWNSTREAM_DOORBELL !== void 0)) {
|
|
16911
17893
|
const directRows = doorbellPreflight.status === "ready" && this.env.DOWNSTREAM_DOORBELL !== void 0 ? await this.directDeliveryBeforeResponse(tag, serviceId) : void 0;
|
|
16912
17894
|
if (sourcePartitionRegistration !== "unconfigured" || sourcePartitionWasRegistered) {
|
|
@@ -16923,7 +17905,19 @@ var TagDurableObject = class {
|
|
|
16923
17905
|
const loaded = await this.recordFor(txn, tag);
|
|
16924
17906
|
let record = loaded.record;
|
|
16925
17907
|
if (batchIsExactDuplicate(record, input)) {
|
|
16926
|
-
|
|
17908
|
+
const receipts = record.writeReceipts ?? {};
|
|
17909
|
+
const sameEpoch = receipts[`${input.attemptId}:${input.epoch}`];
|
|
17910
|
+
const forAttempt = Object.entries(receipts).filter(([key]) => key.startsWith(`${input.attemptId}:`));
|
|
17911
|
+
const replayHead = input.candidates.at(-1)?.suid;
|
|
17912
|
+
const byHead = replayHead === void 0 ? [] : forAttempt.filter(([, value]) => value.headSuid === replayHead);
|
|
17913
|
+
const receipt = sameEpoch ?? (byHead.length === 1 ? byHead[0]?.[1] : void 0) ?? (forAttempt.length === 1 ? forAttempt[0]?.[1] : void 0);
|
|
17914
|
+
if (receipt === void 0) {
|
|
17915
|
+
return {
|
|
17916
|
+
...rejected("duplicate_receipt_unknown"),
|
|
17917
|
+
body: { ...rejected("duplicate_receipt_unknown").body, version: record.version }
|
|
17918
|
+
};
|
|
17919
|
+
}
|
|
17920
|
+
return { status: 200, body: { status: "duplicate", version: receipt.version, updatedAt: receipt.updatedAt } };
|
|
16927
17921
|
}
|
|
16928
17922
|
const expiry = expireReservation(record);
|
|
16929
17923
|
record = expiry.record;
|
|
@@ -16936,9 +17930,9 @@ var TagDurableObject = class {
|
|
|
16936
17930
|
await this.commitExpiryIfNeeded(txn, expiry);
|
|
16937
17931
|
return fenceGateRejected();
|
|
16938
17932
|
}
|
|
16939
|
-
const
|
|
16940
|
-
const confirmsReservation =
|
|
16941
|
-
if (
|
|
17933
|
+
const active2 = record.activeReservation;
|
|
17934
|
+
const confirmsReservation = active2 !== null && active2.attemptId === input.attemptId && active2.epoch === input.epoch && active2.token === input.reservationToken;
|
|
17935
|
+
if (active2 !== null && (active2.attemptId !== input.attemptId || active2.epoch !== input.epoch || active2.token !== input.reservationToken)) {
|
|
16942
17936
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
16943
17937
|
return {
|
|
16944
17938
|
...rejected("reservation_token_required"),
|
|
@@ -17000,13 +17994,20 @@ var TagDurableObject = class {
|
|
|
17000
17994
|
alarmDueAt: null,
|
|
17001
17995
|
confirmations: confirmsReservation ? withMaxEpoch(record.confirmations, input.attemptId, input.epoch) : record.confirmations
|
|
17002
17996
|
});
|
|
17003
|
-
|
|
17997
|
+
const recorded = {
|
|
17998
|
+
...updated,
|
|
17999
|
+
writeReceipts: {
|
|
18000
|
+
...record.writeReceipts ?? {},
|
|
18001
|
+
[`${input.attemptId}:${input.epoch}`]: { version: updated.version, updatedAt: updated.updatedAt, headSuid: updated.head }
|
|
18002
|
+
}
|
|
18003
|
+
};
|
|
18004
|
+
await this.write(txn, recorded, serviceId ?? "");
|
|
17004
18005
|
return {
|
|
17005
18006
|
status: 201,
|
|
17006
|
-
body: { status: "appended", fenceGate, version:
|
|
18007
|
+
body: { status: "appended", fenceGate, version: recorded.version, updatedAt: recorded.updatedAt }
|
|
17007
18008
|
};
|
|
17008
18009
|
});
|
|
17009
|
-
const response3 =
|
|
18010
|
+
const response3 = json11(result2.body, result2.status);
|
|
17010
18011
|
if (result2.status === 201 && serviceId !== null && serviceId.length > 0 && this.env.AUTO_DRAIN_OUTBOX === "true" && (this.env.DOWNSTREAM_QUEUE !== void 0 || doorbellPreflight.status === "ready" && this.env.DOWNSTREAM_DOORBELL !== void 0)) {
|
|
17011
18012
|
const directRows = doorbellPreflight.status === "ready" && this.env.DOWNSTREAM_DOORBELL !== void 0 ? await this.directDeliveryBeforeResponse(tag, serviceId) : void 0;
|
|
17012
18013
|
withGlobalAdmission2(
|
|
@@ -17018,16 +18019,16 @@ var TagDurableObject = class {
|
|
|
17018
18019
|
return response3;
|
|
17019
18020
|
} catch (failure3) {
|
|
17020
18021
|
if (failure3 instanceof PartitionRegistrationUnavailableError) {
|
|
17021
|
-
return
|
|
18022
|
+
return error9(503, "partition_registration_unavailable", "Source partition registration is unavailable; retry the commit.", true);
|
|
17022
18023
|
}
|
|
17023
18024
|
if (failure3 instanceof AppendTransactionFault) {
|
|
17024
|
-
return
|
|
18025
|
+
return error9(
|
|
17025
18026
|
503,
|
|
17026
18027
|
"simulated_append_crash",
|
|
17027
18028
|
"Simulated interruption before atomic append and confirmation commit"
|
|
17028
18029
|
);
|
|
17029
18030
|
}
|
|
17030
|
-
return
|
|
18031
|
+
return error9(500, "tag_append_failure", "Tag append could not be persisted");
|
|
17031
18032
|
}
|
|
17032
18033
|
}
|
|
17033
18034
|
/**
|
|
@@ -17143,7 +18144,7 @@ var TagDurableObject = class {
|
|
|
17143
18144
|
let timer;
|
|
17144
18145
|
const operationResult = Promise.resolve().then(operation).then(
|
|
17145
18146
|
(value) => ({ status: "completed", value }),
|
|
17146
|
-
(
|
|
18147
|
+
(error11) => ({ status: "failed", error: error11 })
|
|
17147
18148
|
);
|
|
17148
18149
|
const timeout = new Promise((resolve) => {
|
|
17149
18150
|
timer = setTimeout(() => resolve({ status: "timeout" }), budgetMs);
|
|
@@ -17194,14 +18195,14 @@ var TagDurableObject = class {
|
|
|
17194
18195
|
envelopeBytes,
|
|
17195
18196
|
status: "success"
|
|
17196
18197
|
});
|
|
17197
|
-
} catch (
|
|
18198
|
+
} catch (error11) {
|
|
17198
18199
|
console.warn("direct_doorbell_status", {
|
|
17199
18200
|
status: "queued-degraded",
|
|
17200
18201
|
reason: "direct_doorbell_delivery_failed",
|
|
17201
18202
|
correlationId,
|
|
17202
18203
|
envelopeBytes,
|
|
17203
|
-
failureKind: classifyDirectDoorbellFailure(
|
|
17204
|
-
error: String(
|
|
18204
|
+
failureKind: classifyDirectDoorbellFailure(error11),
|
|
18205
|
+
error: String(error11)
|
|
17205
18206
|
});
|
|
17206
18207
|
}
|
|
17207
18208
|
}
|
|
@@ -17262,7 +18263,7 @@ var TagDurableObject = class {
|
|
|
17262
18263
|
async confirm(tag, body) {
|
|
17263
18264
|
const parsed = reservationFrom(body);
|
|
17264
18265
|
if (parsed.value === void 0) {
|
|
17265
|
-
return
|
|
18266
|
+
return error9(400, "invalid_tag_confirm", parsed.error ?? "Invalid confirm request");
|
|
17266
18267
|
}
|
|
17267
18268
|
const input = parsed.value;
|
|
17268
18269
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17278,8 +18279,8 @@ var TagDurableObject = class {
|
|
|
17278
18279
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
17279
18280
|
return { ...rejected(epochError), body: { ...rejected(epochError).body, version: record.version } };
|
|
17280
18281
|
}
|
|
17281
|
-
const
|
|
17282
|
-
if (
|
|
18282
|
+
const active2 = record.activeReservation;
|
|
18283
|
+
if (active2 !== null && active2.attemptId === input.attemptId && active2.epoch === input.epoch && active2.token === input.reservationToken) {
|
|
17283
18284
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
17284
18285
|
return {
|
|
17285
18286
|
...rejected("append_required_for_confirmation"),
|
|
@@ -17292,12 +18293,12 @@ var TagDurableObject = class {
|
|
|
17292
18293
|
body: { ...rejected("confirmation_not_found").body, version: record.version }
|
|
17293
18294
|
};
|
|
17294
18295
|
});
|
|
17295
|
-
return
|
|
18296
|
+
return json11(result2.body, result2.status);
|
|
17296
18297
|
}
|
|
17297
18298
|
async installFence(tag, body) {
|
|
17298
18299
|
const parsed = fenceFrom(body);
|
|
17299
18300
|
if (parsed.value === void 0) {
|
|
17300
|
-
return
|
|
18301
|
+
return error9(400, "invalid_tag_fence", parsed.error ?? "Invalid fence request");
|
|
17301
18302
|
}
|
|
17302
18303
|
const input = parsed.value;
|
|
17303
18304
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17334,12 +18335,12 @@ var TagDurableObject = class {
|
|
|
17334
18335
|
});
|
|
17335
18336
|
return { status: 201, body: { status: "fence-installed", idempotent: false, version: updated.version } };
|
|
17336
18337
|
});
|
|
17337
|
-
return
|
|
18338
|
+
return json11(result2.body, result2.status);
|
|
17338
18339
|
}
|
|
17339
18340
|
async clearFence(tag, body) {
|
|
17340
18341
|
const parsed = fenceFrom(body);
|
|
17341
18342
|
if (parsed.value === void 0) {
|
|
17342
|
-
return
|
|
18343
|
+
return error9(400, "invalid_tag_fence", parsed.error ?? "Invalid fence request");
|
|
17343
18344
|
}
|
|
17344
18345
|
const input = parsed.value;
|
|
17345
18346
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17365,7 +18366,7 @@ var TagDurableObject = class {
|
|
|
17365
18366
|
});
|
|
17366
18367
|
return { status: 200, body: { status: "fence-cleared", idempotent: false, version: updated.version } };
|
|
17367
18368
|
});
|
|
17368
|
-
return
|
|
18369
|
+
return json11(result2.body, result2.status);
|
|
17369
18370
|
}
|
|
17370
18371
|
/**
|
|
17371
18372
|
* Returns Tag-side repair facts only. Journal observations are deliberately
|
|
@@ -17401,7 +18402,7 @@ var TagDurableObject = class {
|
|
|
17401
18402
|
}
|
|
17402
18403
|
};
|
|
17403
18404
|
});
|
|
17404
|
-
return
|
|
18405
|
+
return json11(result2.body, result2.status);
|
|
17405
18406
|
}
|
|
17406
18407
|
/**
|
|
17407
18408
|
* Internal outbox handoff: enqueuedAt is persisted before any Queue send so
|
|
@@ -17411,10 +18412,10 @@ var TagDurableObject = class {
|
|
|
17411
18412
|
async pendingOutbox(tag, serviceId, body) {
|
|
17412
18413
|
const parsed = outboxPendingFrom(body);
|
|
17413
18414
|
if (parsed.value === void 0) {
|
|
17414
|
-
return
|
|
18415
|
+
return error9(400, "invalid_outbox_pending", parsed.error ?? "Invalid outbox pending request");
|
|
17415
18416
|
}
|
|
17416
18417
|
if (!isNonEmptyString11(serviceId)) {
|
|
17417
|
-
return
|
|
18418
|
+
return error9(400, "outbox_service_identity_required", "Outbox delivery requires a service identity");
|
|
17418
18419
|
}
|
|
17419
18420
|
const input = parsed.value;
|
|
17420
18421
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17490,7 +18491,7 @@ var TagDurableObject = class {
|
|
|
17490
18491
|
}
|
|
17491
18492
|
return { status: 200, body: { rows } };
|
|
17492
18493
|
});
|
|
17493
|
-
return
|
|
18494
|
+
return json11(result2.body, result2.status);
|
|
17494
18495
|
}
|
|
17495
18496
|
/**
|
|
17496
18497
|
* Source-side obligation scanner. It deliberately does not call the Queue,
|
|
@@ -17560,7 +18561,7 @@ var TagDurableObject = class {
|
|
|
17560
18561
|
}
|
|
17561
18562
|
};
|
|
17562
18563
|
});
|
|
17563
|
-
return
|
|
18564
|
+
return json11(result2.body, result2.status);
|
|
17564
18565
|
}
|
|
17565
18566
|
/** Record a retry or terminal poison disposition without changing the source event. */
|
|
17566
18567
|
async recordOutboxFailure(tag, delivery, failure3) {
|
|
@@ -17614,16 +18615,16 @@ var TagDurableObject = class {
|
|
|
17614
18615
|
async markOutboxDelivered(tag, body) {
|
|
17615
18616
|
const parsed = outboxMarkFrom(body);
|
|
17616
18617
|
if (parsed.value === void 0) {
|
|
17617
|
-
return
|
|
18618
|
+
return error9(400, "invalid_outbox_delivery_mark", parsed.error ?? "Invalid outbox delivery mark");
|
|
17618
18619
|
}
|
|
17619
18620
|
const input = parsed.value;
|
|
17620
18621
|
try {
|
|
17621
18622
|
const joined = await Promise.all(input.deliveries.map((delivery) => this.globalReceiptMatches(delivery)));
|
|
17622
18623
|
if (joined.some((value) => !value)) {
|
|
17623
|
-
return
|
|
18624
|
+
return error9(409, "outbox_global_receipt_unverified", "Global receipt and membership must be read back before source acknowledgement");
|
|
17624
18625
|
}
|
|
17625
18626
|
} catch {
|
|
17626
|
-
return
|
|
18627
|
+
return error9(503, "outbox_global_receipt_unavailable", "Global receipt verification is unavailable");
|
|
17627
18628
|
}
|
|
17628
18629
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
17629
18630
|
if (this.sqlStorage() !== void 0) {
|
|
@@ -17662,7 +18663,7 @@ var TagDurableObject = class {
|
|
|
17662
18663
|
}
|
|
17663
18664
|
return { status: 200, body: { marked, idempotent: marked === 0 } };
|
|
17664
18665
|
});
|
|
17665
|
-
return
|
|
18666
|
+
return json11(result2.body, result2.status);
|
|
17666
18667
|
}
|
|
17667
18668
|
/**
|
|
17668
18669
|
* The delivery handoff reads its universe from `tag_outbox_obligation`.
|
|
@@ -17801,7 +18802,7 @@ var TagDurableObject = class {
|
|
|
17801
18802
|
async acquireRepairLease(tag, body) {
|
|
17802
18803
|
const parsed = repairAcquireFrom(body, tag);
|
|
17803
18804
|
if (parsed.value === void 0) {
|
|
17804
|
-
return
|
|
18805
|
+
return error9(400, "invalid_repair_acquire", parsed.error ?? "Invalid repair acquire request");
|
|
17805
18806
|
}
|
|
17806
18807
|
const input = parsed.value;
|
|
17807
18808
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17847,12 +18848,12 @@ var TagDurableObject = class {
|
|
|
17847
18848
|
}
|
|
17848
18849
|
};
|
|
17849
18850
|
});
|
|
17850
|
-
return
|
|
18851
|
+
return json11(result2.body, result2.status);
|
|
17851
18852
|
}
|
|
17852
18853
|
async renewRepairLease(tag, body) {
|
|
17853
18854
|
const parsed = repairLeaseFrom(body);
|
|
17854
18855
|
if (parsed.value === void 0) {
|
|
17855
|
-
return
|
|
18856
|
+
return error9(400, "invalid_repair_renew", parsed.error ?? "Invalid repair renew request");
|
|
17856
18857
|
}
|
|
17857
18858
|
const input = parsed.value;
|
|
17858
18859
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17876,12 +18877,12 @@ var TagDurableObject = class {
|
|
|
17876
18877
|
}
|
|
17877
18878
|
};
|
|
17878
18879
|
});
|
|
17879
|
-
return
|
|
18880
|
+
return json11(result2.body, result2.status);
|
|
17880
18881
|
}
|
|
17881
18882
|
async unionRepairScope(tag, body) {
|
|
17882
18883
|
const parsed = repairScopeUnionFrom(body, tag);
|
|
17883
18884
|
if (parsed.value === void 0) {
|
|
17884
|
-
return
|
|
18885
|
+
return error9(400, "invalid_repair_scope_union", parsed.error ?? "Invalid repair scope union request");
|
|
17885
18886
|
}
|
|
17886
18887
|
const input = parsed.value;
|
|
17887
18888
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17917,12 +18918,12 @@ var TagDurableObject = class {
|
|
|
17917
18918
|
body: { status: "repair-scope-unioned", scopeVersion: updated.repairScopeVersion, scope: updated.repairScope }
|
|
17918
18919
|
};
|
|
17919
18920
|
});
|
|
17920
|
-
return
|
|
18921
|
+
return json11(result2.body, result2.status);
|
|
17921
18922
|
}
|
|
17922
18923
|
async applyRepair(tag, body) {
|
|
17923
18924
|
const parsed = repairApplyFrom(body, tag);
|
|
17924
18925
|
if (parsed.value === void 0) {
|
|
17925
|
-
return
|
|
18926
|
+
return error9(400, "invalid_repair_apply", parsed.error ?? "Invalid repair apply request");
|
|
17926
18927
|
}
|
|
17927
18928
|
const input = parsed.value;
|
|
17928
18929
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18003,12 +19004,12 @@ var TagDurableObject = class {
|
|
|
18003
19004
|
body: { status: branch, idempotent: false, head: (updatedRecord ?? record).head, version: (updatedRecord ?? record).version }
|
|
18004
19005
|
};
|
|
18005
19006
|
});
|
|
18006
|
-
return
|
|
19007
|
+
return json11(result2.body, result2.status);
|
|
18007
19008
|
}
|
|
18008
19009
|
async auditRepair(tag, body) {
|
|
18009
19010
|
const parsed = repairAuditFrom(body, tag);
|
|
18010
19011
|
if (parsed.value === void 0) {
|
|
18011
|
-
return
|
|
19012
|
+
return error9(400, "invalid_repair_audit", parsed.error ?? "Invalid repair audit request");
|
|
18012
19013
|
}
|
|
18013
19014
|
const input = parsed.value;
|
|
18014
19015
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18045,12 +19046,12 @@ var TagDurableObject = class {
|
|
|
18045
19046
|
await txn.put(REPAIR_FACTS_KEY, { ...facts, audits: [...facts.audits, audit] });
|
|
18046
19047
|
return { status: 200, body: { status: "repair-audited", idempotent: false, branch: audit.branch } };
|
|
18047
19048
|
});
|
|
18048
|
-
return
|
|
19049
|
+
return json11(result2.body, result2.status);
|
|
18049
19050
|
}
|
|
18050
19051
|
async clearRepairFence(tag, body) {
|
|
18051
19052
|
const parsed = repairClearFrom(body);
|
|
18052
19053
|
if (parsed.value === void 0) {
|
|
18053
|
-
return
|
|
19054
|
+
return error9(400, "invalid_repair_clear", parsed.error ?? "Invalid repair clear request");
|
|
18054
19055
|
}
|
|
18055
19056
|
const input = parsed.value;
|
|
18056
19057
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18081,16 +19082,16 @@ var TagDurableObject = class {
|
|
|
18081
19082
|
});
|
|
18082
19083
|
return { status: 200, body: { status: "repair-fence-cleared", idempotent: false, version: updated.version } };
|
|
18083
19084
|
});
|
|
18084
|
-
return
|
|
19085
|
+
return json11(result2.body, result2.status);
|
|
18085
19086
|
}
|
|
18086
19087
|
async setClockOffset(tag, body) {
|
|
18087
|
-
if (!
|
|
18088
|
-
return
|
|
19088
|
+
if (!isObject12(body)) {
|
|
19089
|
+
return error9(400, "invalid_tag_clock", "offsetMs or nowMs must be a safe integer");
|
|
18089
19090
|
}
|
|
18090
19091
|
const nowMs = body.nowMs;
|
|
18091
19092
|
const offsetMs = body.offsetMs;
|
|
18092
19093
|
if (!isSafeInteger(nowMs) && !isSafeInteger(offsetMs)) {
|
|
18093
|
-
return
|
|
19094
|
+
return error9(400, "invalid_tag_clock", "offsetMs or nowMs must be a safe integer");
|
|
18094
19095
|
}
|
|
18095
19096
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
18096
19097
|
const loaded = await this.recordFor(txn, tag);
|
|
@@ -18108,7 +19109,7 @@ var TagDurableObject = class {
|
|
|
18108
19109
|
}
|
|
18109
19110
|
};
|
|
18110
19111
|
});
|
|
18111
|
-
return
|
|
19112
|
+
return json11(result2.body, result2.status);
|
|
18112
19113
|
}
|
|
18113
19114
|
async runAlarm() {
|
|
18114
19115
|
const due = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18227,16 +19228,16 @@ var TagStateCheckpointFault = class extends Error {
|
|
|
18227
19228
|
this.name = "TagStateCheckpointFault";
|
|
18228
19229
|
}
|
|
18229
19230
|
};
|
|
18230
|
-
function
|
|
19231
|
+
function json12(body, status = 200) {
|
|
18231
19232
|
return new Response(JSON.stringify(body), {
|
|
18232
19233
|
status,
|
|
18233
19234
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
18234
19235
|
});
|
|
18235
19236
|
}
|
|
18236
|
-
function
|
|
18237
|
-
return
|
|
19237
|
+
function error10(status, code, message) {
|
|
19238
|
+
return json12({ code, error: message }, status);
|
|
18238
19239
|
}
|
|
18239
|
-
function
|
|
19240
|
+
function isObject13(value) {
|
|
18240
19241
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18241
19242
|
}
|
|
18242
19243
|
function isNonEmptyString12(value) {
|
|
@@ -18251,7 +19252,7 @@ function sqlNullableString2(value, column) {
|
|
|
18251
19252
|
return sqlString2(value, column);
|
|
18252
19253
|
}
|
|
18253
19254
|
function parseIdentity(value) {
|
|
18254
|
-
if (!
|
|
19255
|
+
if (!isObject13(value) || !isNonEmptyString12(value.serviceId) || !isNonEmptyString12(value.tag) || !isNonEmptyString12(value.projectorId)) {
|
|
18255
19256
|
return void 0;
|
|
18256
19257
|
}
|
|
18257
19258
|
return { serviceId: value.serviceId, tag: value.tag, projectorId: value.projectorId };
|
|
@@ -18313,17 +19314,17 @@ var TagStateDurableObject = class {
|
|
|
18313
19314
|
async fetch(request) {
|
|
18314
19315
|
const url = new URL(request.url);
|
|
18315
19316
|
if (request.method !== "POST" || url.pathname !== "/read") {
|
|
18316
|
-
return
|
|
19317
|
+
return error10(404, "tag_state_route_not_found", "TagState route was not found");
|
|
18317
19318
|
}
|
|
18318
19319
|
let body;
|
|
18319
19320
|
try {
|
|
18320
19321
|
body = await request.json();
|
|
18321
19322
|
} catch {
|
|
18322
|
-
return
|
|
19323
|
+
return error10(400, "tag_state_validation_error", "TagState request must be JSON");
|
|
18323
19324
|
}
|
|
18324
19325
|
const identity2 = parseIdentity(body);
|
|
18325
19326
|
if (identity2 === void 0) {
|
|
18326
|
-
return
|
|
19327
|
+
return error10(400, "tag_state_validation_error", "TagState identity requires serviceId, tag, and projectorId");
|
|
18327
19328
|
}
|
|
18328
19329
|
return await this.read(identity2);
|
|
18329
19330
|
}
|
|
@@ -18345,16 +19346,16 @@ var TagStateDurableObject = class {
|
|
|
18345
19346
|
return await this.continueRebuild(identity2, projector, cache);
|
|
18346
19347
|
} catch (caught) {
|
|
18347
19348
|
if (caught instanceof TagStateIdentityConflict) {
|
|
18348
|
-
return
|
|
19349
|
+
return error10(409, "tag_state_identity_conflict", "TagState Durable Object identity changed");
|
|
18349
19350
|
}
|
|
18350
19351
|
if (caught instanceof TagStateUnknownProjector) {
|
|
18351
|
-
return
|
|
19352
|
+
return error10(404, "tag_state_unknown_projector", "TagState projector is not registered");
|
|
18352
19353
|
}
|
|
18353
19354
|
if (caught instanceof TagStateRegistryFailure) {
|
|
18354
|
-
return
|
|
19355
|
+
return error10(503, "tag_state_projector_registry_failure", "TagState projector registry is unavailable");
|
|
18355
19356
|
}
|
|
18356
19357
|
if (caught instanceof TagStateSourceFailure) {
|
|
18357
|
-
return
|
|
19358
|
+
return error10(caught.status, "tag_state_source_frontier_failure", "TagState source frontier could not be read");
|
|
18358
19359
|
}
|
|
18359
19360
|
if (caught instanceof TagStateCacheCorruption) {
|
|
18360
19361
|
try {
|
|
@@ -18362,21 +19363,21 @@ var TagStateDurableObject = class {
|
|
|
18362
19363
|
this.beginRebuild(projector, "cache-corrupt");
|
|
18363
19364
|
} catch (rebuildFailure) {
|
|
18364
19365
|
if (rebuildFailure instanceof TagStateRegistryFailure) {
|
|
18365
|
-
return
|
|
19366
|
+
return error10(503, "tag_state_projector_registry_failure", "TagState projector registry is unavailable");
|
|
18366
19367
|
}
|
|
18367
19368
|
}
|
|
18368
|
-
return
|
|
19369
|
+
return error10(409, "tag_state_cache_corrupt", "TagState cache is corrupt and is rebuilding");
|
|
18369
19370
|
}
|
|
18370
19371
|
if (caught instanceof TagStateCheckpointFault) {
|
|
18371
|
-
return
|
|
19372
|
+
return error10(503, "tag_state_rebuild_interrupted", "TagState replay checkpoint was interrupted");
|
|
18372
19373
|
}
|
|
18373
|
-
return
|
|
19374
|
+
return error10(503, "tag_state_unavailable", "TagState cache is unavailable");
|
|
18374
19375
|
}
|
|
18375
19376
|
}
|
|
18376
19377
|
async beginDeltaOrRespondReady(identity2, projector, cache) {
|
|
18377
19378
|
const page = await this.readSource(identity2, cache.lastSuid, void 0);
|
|
18378
19379
|
if (page.events.length === 0 && page.completeThrough !== null) {
|
|
18379
|
-
return
|
|
19380
|
+
return json12(this.success(projector, cache.stateJson, cache.lastSuid));
|
|
18380
19381
|
}
|
|
18381
19382
|
const rebuild = this.beginRebuild(projector, "normal-delta", {
|
|
18382
19383
|
cursor: cache.lastSuid,
|
|
@@ -18425,9 +19426,9 @@ var TagStateDurableObject = class {
|
|
|
18425
19426
|
});
|
|
18426
19427
|
if (this.g46CheckpointFault === "after-checkpoint") throw new TagStateCheckpointFault();
|
|
18427
19428
|
if (!complete) {
|
|
18428
|
-
return
|
|
19429
|
+
return error10(503, "tag_state_rebuild_in_progress", "TagState replay is rebuilding to its frozen frontier");
|
|
18429
19430
|
}
|
|
18430
|
-
return
|
|
19431
|
+
return json12(this.success(projector, accumulatorJson, page.completeThrough));
|
|
18431
19432
|
}
|
|
18432
19433
|
success(projector, stateJson, lastSortedUniqueId) {
|
|
18433
19434
|
try {
|
|
@@ -18484,7 +19485,7 @@ var TagStateDurableObject = class {
|
|
|
18484
19485
|
}
|
|
18485
19486
|
}
|
|
18486
19487
|
assertSourcePage(cursor, expectedThrough, page) {
|
|
18487
|
-
if (!
|
|
19488
|
+
if (!isObject13(page) || !Array.isArray(page.events) || typeof page.lastSortableUniqueId !== "string" || typeof page.through !== "string" || page.completeThrough !== null && typeof page.completeThrough !== "string") {
|
|
18488
19489
|
throw new TagStateSourceFailure(503, new Error("TagState source response was malformed"));
|
|
18489
19490
|
}
|
|
18490
19491
|
if (expectedThrough !== void 0 && expectedThrough !== null && page.through !== expectedThrough) {
|
|
@@ -18909,9 +19910,9 @@ var PostgresEventStore = class {
|
|
|
18909
19910
|
await transaction.unsafe(SCHEMA, [], { prepare: false });
|
|
18910
19911
|
});
|
|
18911
19912
|
this.sql = sql;
|
|
18912
|
-
} catch (
|
|
19913
|
+
} catch (error11) {
|
|
18913
19914
|
await sql.end({ timeout: 1 });
|
|
18914
|
-
throw
|
|
19915
|
+
throw error11;
|
|
18915
19916
|
}
|
|
18916
19917
|
}
|
|
18917
19918
|
/** Close an owned request/test client without touching durable records. */
|
|
@@ -19102,6 +20103,65 @@ var PostgresEventStore = class {
|
|
|
19102
20103
|
}
|
|
19103
20104
|
return outcome === "stored" ? { outcome, kind: "stored", event: stored } : { outcome, kind: outcome, incident };
|
|
19104
20105
|
}
|
|
20106
|
+
/**
|
|
20107
|
+
* DDL columns only. C# rows have no provenance and no allocator lineage;
|
|
20108
|
+
* this read must not invent either, and it must not treat Tags as
|
|
20109
|
+
* committed membership.
|
|
20110
|
+
*/
|
|
20111
|
+
async readLogicalEvents(serviceId) {
|
|
20112
|
+
const rows = await this.query(
|
|
20113
|
+
`SELECT "ServiceId" AS service_id, "Id"::text AS event_id,
|
|
20114
|
+
"SortableUniqueId" AS suid, "Payload"::text AS payload,
|
|
20115
|
+
"EventType" AS event_type, "Tags" AS event_tags,
|
|
20116
|
+
"Timestamp"::text AS timestamp, "CausationId" AS causation_id,
|
|
20117
|
+
"CorrelationId" AS correlation_id, "ExecutedUser" AS executed_user
|
|
20118
|
+
FROM dcb_events
|
|
20119
|
+
WHERE "ServiceId" = $1
|
|
20120
|
+
ORDER BY "SortableUniqueId" ASC, "Id" ASC`,
|
|
20121
|
+
[serviceId]
|
|
20122
|
+
);
|
|
20123
|
+
return rows.map((row) => ({
|
|
20124
|
+
serviceId: asString4(row.service_id, "service_id"),
|
|
20125
|
+
id: asString4(row.event_id, "event_id"),
|
|
20126
|
+
sortableUniqueId: asString4(row.suid, "suid"),
|
|
20127
|
+
eventType: asString4(row.event_type, "event_type"),
|
|
20128
|
+
payload: asString4(row.payload, "payload"),
|
|
20129
|
+
tags: asStringArray2(row.event_tags, "event_tags"),
|
|
20130
|
+
timestamp: canonicalUtcTimestamp(row.timestamp, "timestamp"),
|
|
20131
|
+
causationId: nullableString2(row.causation_id, "causation_id"),
|
|
20132
|
+
correlationId: nullableString2(row.correlation_id, "correlation_id"),
|
|
20133
|
+
executedUser: nullableString2(row.executed_user, "executed_user")
|
|
20134
|
+
}));
|
|
20135
|
+
}
|
|
20136
|
+
/** Insert one DDL row. Does not write dcb_event_ops or a provenance stamp. */
|
|
20137
|
+
async appendLogicalEvent(event) {
|
|
20138
|
+
JSON.parse(event.payload);
|
|
20139
|
+
assertSortableUniqueId(event.sortableUniqueId);
|
|
20140
|
+
if (!isRfc4122Uuid(event.id)) {
|
|
20141
|
+
throw new CanonicalEventIdentityConflictError("postgres", event.id, "logical event id must be an RFC 4122 UUID");
|
|
20142
|
+
}
|
|
20143
|
+
assertUtcTimestamp(event.timestamp, event.id);
|
|
20144
|
+
const sql = this.requireSql();
|
|
20145
|
+
await sql.begin(async (transaction) => {
|
|
20146
|
+
await transaction.unsafe(
|
|
20147
|
+
`INSERT INTO dcb_events
|
|
20148
|
+
("ServiceId", "Id", "SortableUniqueId", "EventType", "Payload", "Tags", "Timestamp", "CausationId", "CorrelationId", "ExecutedUser")
|
|
20149
|
+
VALUES ($1, $2::uuid, $3, $4, $5::text::json, $6::text::jsonb, $7::timestamptz, $8, $9, $10)`,
|
|
20150
|
+
[
|
|
20151
|
+
event.serviceId,
|
|
20152
|
+
event.id,
|
|
20153
|
+
event.sortableUniqueId,
|
|
20154
|
+
event.eventType,
|
|
20155
|
+
event.payload,
|
|
20156
|
+
JSON.stringify(event.tags),
|
|
20157
|
+
event.timestamp,
|
|
20158
|
+
event.causationId,
|
|
20159
|
+
event.correlationId,
|
|
20160
|
+
event.executedUser
|
|
20161
|
+
]
|
|
20162
|
+
);
|
|
20163
|
+
});
|
|
20164
|
+
}
|
|
19105
20165
|
async readAllEvents(serviceId, since) {
|
|
19106
20166
|
if (since.length !== 0) assertSortableUniqueId(since);
|
|
19107
20167
|
const rows = await this.query(
|
|
@@ -19574,12 +20634,12 @@ function encodedCommitSnapshotLogBytes(record) {
|
|
|
19574
20634
|
function assertCommitSnapshotLogSize(record) {
|
|
19575
20635
|
const encodedBytes2 = encodedCommitSnapshotLogBytes(record);
|
|
19576
20636
|
if (encodedBytes2 > COMMIT_SNAPSHOT_LOG_BYTE_BUDGET) {
|
|
19577
|
-
const
|
|
20637
|
+
const error11 = new Error(
|
|
19578
20638
|
`Commit snapshot log is ${encodedBytes2} bytes; budget is ${COMMIT_SNAPSHOT_LOG_BYTE_BUDGET} bytes`
|
|
19579
20639
|
);
|
|
19580
|
-
|
|
19581
|
-
|
|
19582
|
-
throw
|
|
20640
|
+
error11.code = "commit-snapshot-log-size";
|
|
20641
|
+
error11.encodedBytes = encodedBytes2;
|
|
20642
|
+
throw error11;
|
|
19583
20643
|
}
|
|
19584
20644
|
return encodedBytes2;
|
|
19585
20645
|
}
|
|
@@ -19598,8 +20658,8 @@ function createCommitTraceConsoleSink(options = {}) {
|
|
|
19598
20658
|
if (record === void 0) return;
|
|
19599
20659
|
try {
|
|
19600
20660
|
assertCommitSnapshotLogSize(record);
|
|
19601
|
-
} catch (
|
|
19602
|
-
const encodedBytes2 = typeof
|
|
20661
|
+
} catch (error11) {
|
|
20662
|
+
const encodedBytes2 = typeof error11 === "object" && error11 !== null && "encodedBytes" in error11 && typeof error11.encodedBytes === "number" ? error11.encodedBytes : encodedCommitSnapshotLogBytes(record);
|
|
19603
20663
|
reportRejectedSize(Object.freeze({
|
|
19604
20664
|
schema: COMMIT_SNAPSHOT_LOG_SCHEMA,
|
|
19605
20665
|
event: "commit.snapshot-size-rejected",
|
|
@@ -19608,41 +20668,107 @@ function createCommitTraceConsoleSink(options = {}) {
|
|
|
19608
20668
|
encodedBytes: encodedBytes2,
|
|
19609
20669
|
byteBudget: COMMIT_SNAPSHOT_LOG_BYTE_BUDGET
|
|
19610
20670
|
}));
|
|
19611
|
-
throw
|
|
20671
|
+
throw error11;
|
|
19612
20672
|
}
|
|
19613
20673
|
log(record);
|
|
19614
20674
|
}
|
|
19615
20675
|
});
|
|
19616
20676
|
}
|
|
19617
20677
|
|
|
19618
|
-
// src/
|
|
19619
|
-
|
|
19620
|
-
|
|
19621
|
-
|
|
19622
|
-
|
|
19623
|
-
|
|
20678
|
+
// src/shard/ShardRotation.ts
|
|
20679
|
+
var ShardRotationError = class extends Error {
|
|
20680
|
+
diagnostic;
|
|
20681
|
+
constructor(diagnostic) {
|
|
20682
|
+
super(diagnostic.code);
|
|
20683
|
+
this.name = "ShardRotationError";
|
|
20684
|
+
this.diagnostic = diagnostic;
|
|
20685
|
+
}
|
|
20686
|
+
};
|
|
20687
|
+
function fail(code, path, reason) {
|
|
20688
|
+
throw new ShardRotationError({ code, path, reason });
|
|
19624
20689
|
}
|
|
19625
|
-
function
|
|
19626
|
-
|
|
20690
|
+
function createRotationState(active2) {
|
|
20691
|
+
if (typeof active2 !== "string" || active2.length === 0) fail("ACTIVE_COUNT", "active", "active-missing");
|
|
20692
|
+
return { sealed: [], active: active2, ledger: [] };
|
|
19627
20693
|
}
|
|
19628
|
-
function
|
|
19629
|
-
|
|
19630
|
-
|
|
19631
|
-
|
|
19632
|
-
|
|
19633
|
-
if (caught instanceof ServiceIdentityMissingError) {
|
|
19634
|
-
console.error({ schema: "sdt.scope/v1", code: "scope.identity_missing", route: input.route });
|
|
19635
|
-
return { response: scopeIdentityMissingResponse() };
|
|
19636
|
-
}
|
|
19637
|
-
throw caught;
|
|
20694
|
+
function assertStrictlyBefore(lastD1_1, firstD1_2) {
|
|
20695
|
+
assertSortableUniqueId(lastD1_1);
|
|
20696
|
+
assertSortableUniqueId(firstD1_2);
|
|
20697
|
+
if (compareSortableUniqueId(lastD1_1, firstD1_2) >= 0) {
|
|
20698
|
+
fail("ORDER_INVARIANT", "last(D1-1)<first(D1-2)", "order-invariant");
|
|
19638
20699
|
}
|
|
19639
|
-
|
|
19640
|
-
|
|
19641
|
-
|
|
19642
|
-
|
|
19643
|
-
|
|
20700
|
+
}
|
|
20701
|
+
function maxSortableUniqueId(values) {
|
|
20702
|
+
if (values.length === 0) fail("SEAL_MAX_MISSING", "d1-1", "seal-max-missing");
|
|
20703
|
+
let max = assertSortableUniqueId(values[0]).value;
|
|
20704
|
+
for (let i = 1; i < values.length; i += 1) {
|
|
20705
|
+
const next = assertSortableUniqueId(values[i]).value;
|
|
20706
|
+
if (next > max) max = next;
|
|
19644
20707
|
}
|
|
19645
|
-
return
|
|
20708
|
+
return max;
|
|
20709
|
+
}
|
|
20710
|
+
function rotateAppend(state, sealedId, shardSuids, nextActiveId) {
|
|
20711
|
+
if (sealedId !== state.active) fail("UNKNOWN_SHARD", sealedId, "unknown-shard");
|
|
20712
|
+
if (typeof nextActiveId !== "string" || nextActiveId.length === 0 || nextActiveId === sealedId) {
|
|
20713
|
+
fail("ACTIVE_COUNT", "active", "active-count");
|
|
20714
|
+
}
|
|
20715
|
+
if (state.sealed.some((entry) => entry.id === nextActiveId)) {
|
|
20716
|
+
fail("ACTIVE_COUNT", nextActiveId, "active-already-sealed");
|
|
20717
|
+
}
|
|
20718
|
+
if (state.sealed.some((entry) => entry.id === sealedId)) fail("IDENTITY_SWAP", sealedId, "identity-swap");
|
|
20719
|
+
const lastSuid = maxSortableUniqueId(shardSuids);
|
|
20720
|
+
return {
|
|
20721
|
+
sealed: [...state.sealed, { id: sealedId, lastSuid }],
|
|
20722
|
+
active: nextActiveId,
|
|
20723
|
+
ledger: state.ledger
|
|
20724
|
+
};
|
|
20725
|
+
}
|
|
20726
|
+
function replaceSealedIdentity(state, index, nextId) {
|
|
20727
|
+
if (index < 0 || index >= state.sealed.length) fail("UNKNOWN_SHARD", "sealed", "unknown-shard");
|
|
20728
|
+
fail("IDENTITY_SWAP", state.sealed[index].id, `identity-swap:${nextId}`);
|
|
20729
|
+
}
|
|
20730
|
+
function refuseSealedWrite(state, shardId) {
|
|
20731
|
+
if (state.sealed.some((entry) => entry.id === shardId)) fail("SEALED_WRITE", shardId, "sealed-write");
|
|
20732
|
+
}
|
|
20733
|
+
function admitActiveWrite(state, shardId, firstOrNextSuid) {
|
|
20734
|
+
assertSortableUniqueId(firstOrNextSuid);
|
|
20735
|
+
refuseSealedWrite(state, shardId);
|
|
20736
|
+
if (shardId !== state.active) fail("UNKNOWN_SHARD", shardId, "unknown-shard");
|
|
20737
|
+
if (state.sealed.length === 0) return;
|
|
20738
|
+
const previous = state.sealed[state.sealed.length - 1];
|
|
20739
|
+
assertStrictlyBefore(previous.lastSuid, firstOrNextSuid);
|
|
20740
|
+
}
|
|
20741
|
+
function recordLateArrival(state, sealedShardId, suid) {
|
|
20742
|
+
assertSortableUniqueId(suid);
|
|
20743
|
+
const sealed = state.sealed.find((entry) => entry.id === sealedShardId);
|
|
20744
|
+
if (sealed === void 0) fail("UNKNOWN_SHARD", sealedShardId, "unknown-shard");
|
|
20745
|
+
if (compareSortableUniqueId(suid, sealed.lastSuid) > 0) {
|
|
20746
|
+
fail("ORDER_INVARIANT", sealedShardId, "not-late-arrival");
|
|
20747
|
+
}
|
|
20748
|
+
return {
|
|
20749
|
+
...state,
|
|
20750
|
+
ledger: [...state.ledger, { shardId: sealed.id, suid, reason: "sealed-range" }]
|
|
20751
|
+
};
|
|
20752
|
+
}
|
|
20753
|
+
var SILENT_MERGE_TOKENS = [
|
|
20754
|
+
["merge", "Ledger", "Into", "Sealed"],
|
|
20755
|
+
["merge", "OnRead", "Sealed"],
|
|
20756
|
+
["silent", "Merge", "Ledger"],
|
|
20757
|
+
["ledger", ".concat(", "sealed"],
|
|
20758
|
+
["sealed", ".concat(", "ledger"]
|
|
20759
|
+
];
|
|
20760
|
+
function assertNoSilentLedgerMerge(source) {
|
|
20761
|
+
const lowered = source.toLowerCase();
|
|
20762
|
+
for (const token of SILENT_MERGE_TOKENS) {
|
|
20763
|
+
const needle = token.join("").toLowerCase();
|
|
20764
|
+
if (lowered.includes(needle)) fail("SILENT_MERGE", "ledger", "silent-merge");
|
|
20765
|
+
}
|
|
20766
|
+
}
|
|
20767
|
+
function refuseSilentLedgerMerge() {
|
|
20768
|
+
fail("SILENT_MERGE", "ledger", "silent-merge");
|
|
20769
|
+
}
|
|
20770
|
+
function refuseSafeWindowSeal() {
|
|
20771
|
+
fail("SAFEWINDOW_SEAL", "safe-window", "safewindow-seal");
|
|
19646
20772
|
}
|
|
19647
20773
|
|
|
19648
20774
|
// src/index.ts
|
|
@@ -19652,6 +20778,22 @@ function createRuntimeWorker(options = {}) {
|
|
|
19652
20778
|
const storeProvider = options.storeProvider ?? POSTGRES_STORE_PROVIDER;
|
|
19653
20779
|
return {
|
|
19654
20780
|
async fetch(request, env, ctx) {
|
|
20781
|
+
const url = new URL(request.url);
|
|
20782
|
+
if (isIncidentMaintenancePath(url.pathname)) {
|
|
20783
|
+
return handleIncidentMaintenance(request, env, options.serviceIdentityProvider ?? envServiceIdentity(env));
|
|
20784
|
+
}
|
|
20785
|
+
const operatorRepair = url.pathname === "/operator/repair";
|
|
20786
|
+
const operatorBootstrap = url.pathname.startsWith("/operator/bootstrap/");
|
|
20787
|
+
if (operatorRepair) {
|
|
20788
|
+
const methodDenied = nonPostOperatorRepairResponse(request);
|
|
20789
|
+
if (methodDenied !== void 0) return methodDenied;
|
|
20790
|
+
const denied = await authenticateOperatorRepair(request, env.REPAIR_OPERATOR_TOKEN);
|
|
20791
|
+
if (denied !== void 0) return denied;
|
|
20792
|
+
}
|
|
20793
|
+
if (operatorBootstrap) {
|
|
20794
|
+
const denied = await authenticateOperatorBootstrap(request, env.REPAIR_OPERATOR_TOKEN);
|
|
20795
|
+
if (denied !== void 0) return denied;
|
|
20796
|
+
}
|
|
19655
20797
|
const serviceIdentity = options.serviceIdentityProvider ?? envServiceIdentity(env);
|
|
19656
20798
|
const requestIdentityOptions = { allowG11Verification: env.G11_VERIFICATION_ENABLED === "true" };
|
|
19657
20799
|
let requestServiceId;
|
|
@@ -19660,7 +20802,6 @@ function createRuntimeWorker(options = {}) {
|
|
|
19660
20802
|
} catch {
|
|
19661
20803
|
return scopeIdentityMissingResponse();
|
|
19662
20804
|
}
|
|
19663
|
-
const url = new URL(request.url);
|
|
19664
20805
|
if (url.pathname === "/api/sekiban/serialized/commit") {
|
|
19665
20806
|
return handleSerializedCommit(request, env, {
|
|
19666
20807
|
domainDeliveryClass: options.config?.deliveryClass,
|
|
@@ -19682,14 +20823,19 @@ function createRuntimeWorker(options = {}) {
|
|
|
19682
20823
|
serviceIdentityProvider: serviceIdentity
|
|
19683
20824
|
});
|
|
19684
20825
|
}
|
|
19685
|
-
if (
|
|
19686
|
-
return
|
|
20826
|
+
if (operatorRepair) {
|
|
20827
|
+
return handleAuthorizedOperatorRepair(
|
|
20828
|
+
request,
|
|
20829
|
+
env,
|
|
20830
|
+
nativeTracingFromContext(ctx),
|
|
20831
|
+
serviceIdentity
|
|
20832
|
+
);
|
|
19687
20833
|
}
|
|
19688
|
-
if (
|
|
19689
|
-
return
|
|
20834
|
+
if (operatorBootstrap) {
|
|
20835
|
+
return handleAuthorizedOperatorBootstrap(request, env, storeProvider);
|
|
19690
20836
|
}
|
|
19691
20837
|
if (url.pathname === "/internal/downstream/drain" && request.method === "POST") {
|
|
19692
|
-
return handleOutboxDrainRequest(request, env);
|
|
20838
|
+
return handleOutboxDrainRequest(request, env, { serviceIdentityProvider: serviceIdentity });
|
|
19693
20839
|
}
|
|
19694
20840
|
if (url.pathname === "/internal/projection/lag") {
|
|
19695
20841
|
return handleProjectionLag(request, env, composition.projectors, storeProvider, serviceIdentity);
|
|
@@ -19822,6 +20968,8 @@ export {
|
|
|
19822
20968
|
GLOBAL_COMPLETENESS_INTERIM_DISPOSITION,
|
|
19823
20969
|
GlobalCompletenessReconciler,
|
|
19824
20970
|
IDLE_EXPERIMENT_SCHEDULE_MS,
|
|
20971
|
+
IncidentLifecycle,
|
|
20972
|
+
IncidentLifecycleError,
|
|
19825
20973
|
JournalDurableObject,
|
|
19826
20974
|
MAX_PUBLISHED_SAFE_WINDOW_MS,
|
|
19827
20975
|
MAX_SERVICE_BINDING_INVOCATIONS_PER_REQUEST,
|
|
@@ -19842,22 +20990,27 @@ export {
|
|
|
19842
20990
|
SORTABLE_UNIQUE_ID_TICKS_DIGITS,
|
|
19843
20991
|
ScopeNameError,
|
|
19844
20992
|
ServiceIdentityMissingError,
|
|
20993
|
+
ShardRotationError,
|
|
19845
20994
|
SortableUniqueIdError,
|
|
19846
20995
|
TEST_SERVICE_ID_HEADER,
|
|
19847
20996
|
TagDurableObject,
|
|
19848
20997
|
TagStateDurableObject,
|
|
19849
20998
|
UnsafeWindowMaterializedViewError,
|
|
19850
20999
|
UnsafeWindowMaterializedViewStore,
|
|
21000
|
+
admitActiveWrite,
|
|
19851
21001
|
allocateOrderRange,
|
|
19852
21002
|
assertCanonicalEventType,
|
|
19853
21003
|
assertCommitSnapshotLogSize,
|
|
19854
21004
|
assertDotNetTicks,
|
|
21005
|
+
assertNoSilentLedgerMerge,
|
|
19855
21006
|
assertSortableUniqueId,
|
|
21007
|
+
assertStrictlyBefore,
|
|
19856
21008
|
beginWorkerInvocationObservation,
|
|
19857
21009
|
bootstrapDigest,
|
|
19858
21010
|
buildScopeName,
|
|
19859
21011
|
calculateUnattributedRatio,
|
|
19860
21012
|
canonicalEventType,
|
|
21013
|
+
checkConfiguredBearer,
|
|
19861
21014
|
chooseQueryBacking,
|
|
19862
21015
|
classifyDirectDoorbellFailure,
|
|
19863
21016
|
classifyReactivationCause,
|
|
@@ -19869,6 +21022,7 @@ export {
|
|
|
19869
21022
|
createCommitTraceConsoleSink,
|
|
19870
21023
|
createPostgresBootstrapAdapter,
|
|
19871
21024
|
createPostgresStoreProvider,
|
|
21025
|
+
createRotationState,
|
|
19872
21026
|
createRuntimeCommitPort,
|
|
19873
21027
|
createRuntimeWorker,
|
|
19874
21028
|
createTraceCorrelationId,
|
|
@@ -19893,8 +21047,10 @@ export {
|
|
|
19893
21047
|
isScopeServiceId,
|
|
19894
21048
|
isSortableUniqueId,
|
|
19895
21049
|
isUuidV7,
|
|
21050
|
+
maxSortableUniqueId,
|
|
19896
21051
|
nativeTracingFromContext,
|
|
19897
21052
|
noOpNativeTracing,
|
|
21053
|
+
normalizeIncidentIdentity,
|
|
19898
21054
|
observeLegacySortableUniqueIdDecision,
|
|
19899
21055
|
observedIdleGapLowerBoundMs,
|
|
19900
21056
|
parseBootstrapDump,
|
|
@@ -19904,10 +21060,16 @@ export {
|
|
|
19904
21060
|
processDownstreamDoorbell,
|
|
19905
21061
|
readDirectDoorbellConfig,
|
|
19906
21062
|
readDomainDeliveryClass,
|
|
21063
|
+
recordLateArrival,
|
|
21064
|
+
refuseSafeWindowSeal,
|
|
21065
|
+
refuseSealedWrite,
|
|
21066
|
+
refuseSilentLedgerMerge,
|
|
19907
21067
|
registeredEventParsers,
|
|
21068
|
+
replaceSealedIdentity,
|
|
19908
21069
|
requestServiceIdentity,
|
|
19909
21070
|
requireServiceIdentity,
|
|
19910
21071
|
resolveDeliveryIdentity,
|
|
21072
|
+
rotateAppend,
|
|
19911
21073
|
safeWindowCeilingExceeded,
|
|
19912
21074
|
safeWindowCutoffSuid,
|
|
19913
21075
|
safeWindowMs,
|