@sekiban/dcb-runtime 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/allocator/AllocatorDurableObject.js +25 -2
- package/dist/bootstrap/BootstrapCoordinatorDurableObject.d.ts +2 -0
- package/dist/bootstrap/BootstrapCoordinatorDurableObject.js +64 -1
- package/dist/bootstrap/types.d.ts +6 -0
- package/dist/cloudflare.d.ts +3 -0
- package/dist/cloudflare.js +1493 -451
- package/dist/commit/CommitWorker.d.ts +19 -0
- package/dist/commit/CommitWorker.js +176 -27
- package/dist/completeness/IncidentLifecycle.d.ts +75 -0
- package/dist/completeness/IncidentLifecycle.js +503 -0
- package/dist/completeness/types.d.ts +67 -0
- package/dist/cosmos.d.ts +34 -4
- package/dist/cosmos.js +214 -49
- package/dist/downstream/OutboxDrain.d.ts +4 -1
- package/dist/downstream/OutboxDrain.js +24 -2
- package/dist/generated/cosmos-layout.d.ts +87 -0
- package/dist/generated/cosmos-layout.js +99 -0
- package/dist/http/IncidentMaintenance.d.ts +8 -0
- package/dist/http/IncidentMaintenance.js +145 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1548 -451
- package/dist/read/SerializedReadWorker.d.ts +3 -0
- package/dist/read/SerializedReadWorker.js +3 -3
- package/dist/shard/ShardRotation.d.ts +48 -0
- package/dist/shard/ShardRotation.js +120 -0
- package/dist/store/CosmosEventStore.js +56 -46
- package/dist/store/PostgresEventStore.d.ts +20 -0
- package/dist/store/PostgresEventStore.js +53 -0
- package/dist/store/provider.d.ts +4 -0
- package/dist/tag/TagDurableObject.js +66 -14
- package/dist/tag/TagSqlSchema.d.ts +6 -1
- package/dist/tag/TagSqlSchema.js +15 -0
- package/dist/tag/types.d.ts +9 -0
- package/dist/trace/CommitTrace.d.ts +3 -3
- package/dist/trace/CommitTraceVerifier.js +1 -1
- package/package.json +6 -15
package/dist/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 digest2 = 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] === digest2 ? { control } : { digestMismatch: true };
|
|
5551
|
+
}
|
|
5552
|
+
if (control.status !== "EMPTY" && control.status !== "READY") return { rejected: true };
|
|
5553
|
+
const updated = { ...control, writePermits: { ...active2, [commandId]: digest2 } };
|
|
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 digest2 = 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 && digest2 !== void 0 && active2[commandId] !== digest2) {
|
|
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 =
|
|
5682
|
-
const
|
|
5683
|
-
return [...new Uint8Array(
|
|
5752
|
+
const bytes2 = eventDigestBytes(input);
|
|
5753
|
+
const source = bytes2.buffer.slice(bytes2.byteOffset, bytes2.byteOffset + bytes2.byteLength);
|
|
5754
|
+
const digest2 = await crypto.subtle.digest("SHA-256", source);
|
|
5755
|
+
return [...new Uint8Array(digest2)].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 {
|
|
@@ -6797,6 +6869,22 @@ var COMMIT_TEST_FAULTS = [
|
|
|
6797
6869
|
var INITIAL_OWNER_EPOCH = 0;
|
|
6798
6870
|
var MAX_WRITE_ATTEMPTS = 2;
|
|
6799
6871
|
var OUTCOME_UNDETERMINED_ERROR = "Commit outcome is undetermined; reread tag heads and event/query state before retrying because blind retry may create duplicate events.";
|
|
6872
|
+
function commitPermitDigest(allTags, candidates) {
|
|
6873
|
+
const normalized = JSON.stringify({
|
|
6874
|
+
tags: [...allTags].sort(),
|
|
6875
|
+
events: candidates.map((candidate) => ({
|
|
6876
|
+
eventId: candidate.eventId,
|
|
6877
|
+
payload: candidate.payload,
|
|
6878
|
+
tags: [...candidate.tags].sort()
|
|
6879
|
+
})).sort((left, right) => left.eventId < right.eventId ? -1 : left.eventId > right.eventId ? 1 : 0)
|
|
6880
|
+
});
|
|
6881
|
+
let hash = 2166136261;
|
|
6882
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
6883
|
+
hash ^= normalized.charCodeAt(index);
|
|
6884
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
6885
|
+
}
|
|
6886
|
+
return `fnv1a32:${hash.toString(16).padStart(8, "0")}`;
|
|
6887
|
+
}
|
|
6800
6888
|
var GLOBAL_ADMISSION_HEADER = "x-sdt-global-admission";
|
|
6801
6889
|
var runtimeCommitTraceVerifier = {
|
|
6802
6890
|
verify(snapshot) {
|
|
@@ -6850,19 +6938,19 @@ var CommitPayloadAdmissionError = class extends Error {
|
|
|
6850
6938
|
code;
|
|
6851
6939
|
};
|
|
6852
6940
|
function decodeBase64Utf8Json(value) {
|
|
6853
|
-
let
|
|
6941
|
+
let bytes2;
|
|
6854
6942
|
try {
|
|
6855
6943
|
const binary = atob(value);
|
|
6856
|
-
|
|
6944
|
+
bytes2 = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
6857
6945
|
} catch {
|
|
6858
6946
|
throw new CommitPayloadAdmissionError("invalid_payload_utf8", "Payload base64 could not be decoded");
|
|
6859
6947
|
}
|
|
6860
|
-
if (
|
|
6948
|
+
if (bytes2[0] === 239 && bytes2[1] === 187 && bytes2[2] === 191) {
|
|
6861
6949
|
throw new CommitPayloadAdmissionError("invalid_payload_utf8", "Payload must not start with a UTF-8 BOM");
|
|
6862
6950
|
}
|
|
6863
6951
|
let text;
|
|
6864
6952
|
try {
|
|
6865
|
-
text = new TextDecoder("utf-8", { fatal: true }).decode(
|
|
6953
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
6866
6954
|
} catch {
|
|
6867
6955
|
throw new CommitPayloadAdmissionError("invalid_payload_utf8", "Payload must be valid UTF-8 JSON text");
|
|
6868
6956
|
}
|
|
@@ -6908,9 +6996,9 @@ function assertExactMemberPaths(actual, registered, path = "$") {
|
|
|
6908
6996
|
}
|
|
6909
6997
|
}
|
|
6910
6998
|
function encodeBase64Utf8(value) {
|
|
6911
|
-
const
|
|
6999
|
+
const bytes2 = new TextEncoder().encode(value);
|
|
6912
7000
|
let binary = "";
|
|
6913
|
-
for (const byte of
|
|
7001
|
+
for (const byte of bytes2) binary += String.fromCharCode(byte);
|
|
6914
7002
|
return btoa(binary);
|
|
6915
7003
|
}
|
|
6916
7004
|
function faultFromRequest(request) {
|
|
@@ -7169,25 +7257,43 @@ var CommitWorker = class {
|
|
|
7169
7257
|
return responseWithAttempt(error3(409, "bootstrap_command_rejected", "Bootstrap admission could not be released"), attemptId, fault2 !== void 0);
|
|
7170
7258
|
}
|
|
7171
7259
|
await this.retiredJournalMilestone("S04", traceState?.scope);
|
|
7260
|
+
const permitDigest = commitPermitDigest(input.allTags, candidates);
|
|
7261
|
+
let permitHeld = false;
|
|
7262
|
+
const releasePermit = async () => {
|
|
7263
|
+
if (!permitHeld) return;
|
|
7264
|
+
permitHeld = false;
|
|
7265
|
+
await this.bootstrapPermitRelease(attemptId, permitDigest);
|
|
7266
|
+
};
|
|
7267
|
+
const permit = await this.bootstrapPermitAcquire(attemptId, permitDigest);
|
|
7268
|
+
if (!permit.ok) {
|
|
7269
|
+
if (permit.code === "bootstrap_permit_digest_mismatch") {
|
|
7270
|
+
return responseWithAttempt(error3(409, "bootstrap_permit_digest_mismatch", "Bootstrap write permit digest does not match"), attemptId, fault2 !== void 0);
|
|
7271
|
+
}
|
|
7272
|
+
return responseWithAttempt(error3(409, "bootstrap_command_rejected", "Bootstrap is importing; commit is unavailable"), attemptId, fault2 !== void 0);
|
|
7273
|
+
}
|
|
7274
|
+
permitHeld = this.env.BOOTSTRAP !== void 0;
|
|
7172
7275
|
const reservations = await this.acquireReservations(input, attemptId, fault2, traceState?.scope);
|
|
7173
7276
|
await this.retiredJournalMilestone("S05a", traceState?.scope, 0);
|
|
7174
7277
|
if (reservations.failure !== void 0) {
|
|
7175
|
-
|
|
7278
|
+
const failure3 = await this.finishReservationFailure(
|
|
7176
7279
|
[...reservations.successes.keys()],
|
|
7177
7280
|
attemptId,
|
|
7178
7281
|
reservations.failure,
|
|
7179
7282
|
fault2,
|
|
7180
7283
|
traceState?.scope
|
|
7181
7284
|
);
|
|
7285
|
+
if (failure3.resolved) await releasePermit();
|
|
7286
|
+
return failure3.response;
|
|
7182
7287
|
}
|
|
7183
7288
|
if (fault2 === "after-reservations-before-allocation") {
|
|
7184
|
-
await this.cancelReservations([...reservations.successes.keys()], attemptId, fault2, traceState?.scope);
|
|
7289
|
+
const cleanup = await this.cancelReservations([...reservations.successes.keys()], attemptId, fault2, traceState?.scope);
|
|
7290
|
+
if (cleanup.failedTags.length === 0) await releasePermit();
|
|
7185
7291
|
return this.noApplicationOutcome(attemptId, true);
|
|
7186
7292
|
}
|
|
7187
7293
|
await this.hooks.beforeBootstrapAllocation?.();
|
|
7188
7294
|
const allocation = await this.allocate(candidates, attemptId, fault2, bootstrapEpoch, traceState?.scope);
|
|
7189
7295
|
if (allocation === void 0) {
|
|
7190
|
-
|
|
7296
|
+
const failure3 = await this.finishReservationFailure(
|
|
7191
7297
|
[...reservations.successes.keys()],
|
|
7192
7298
|
attemptId,
|
|
7193
7299
|
{
|
|
@@ -7198,10 +7304,13 @@ var CommitWorker = class {
|
|
|
7198
7304
|
fault2,
|
|
7199
7305
|
traceState?.scope
|
|
7200
7306
|
);
|
|
7307
|
+
if (failure3.resolved) await releasePermit();
|
|
7308
|
+
return failure3.response;
|
|
7201
7309
|
}
|
|
7202
7310
|
const allocatedCandidates = this.withAllocatedSuids(candidates, allocation);
|
|
7203
7311
|
if (allocatedCandidates === void 0) {
|
|
7204
|
-
await this.cancelReservations([...reservations.successes.keys()], attemptId, fault2, traceState?.scope);
|
|
7312
|
+
const cleanup = await this.cancelReservations([...reservations.successes.keys()], attemptId, fault2, traceState?.scope);
|
|
7313
|
+
if (cleanup.failedTags.length === 0) await releasePermit();
|
|
7205
7314
|
return this.noApplicationOutcome(attemptId, fault2 !== void 0);
|
|
7206
7315
|
}
|
|
7207
7316
|
for (const candidate of allocatedCandidates) {
|
|
@@ -7216,15 +7325,18 @@ var CommitWorker = class {
|
|
|
7216
7325
|
}
|
|
7217
7326
|
await this.retiredJournalMilestone("S05b", traceState?.scope, 1);
|
|
7218
7327
|
if (fault2 === "journal-cas-after-allocator") {
|
|
7219
|
-
await this.cancelReservations(input.allTags, attemptId, fault2, traceState?.scope);
|
|
7328
|
+
const cleanup = await this.cancelReservations(input.allTags, attemptId, fault2, traceState?.scope);
|
|
7329
|
+
if (cleanup.failedTags.length === 0) await releasePermit();
|
|
7220
7330
|
return this.noApplicationOutcome(attemptId, true);
|
|
7221
7331
|
}
|
|
7222
7332
|
await this.retiredJournalMilestone("S05c", traceState?.scope, 2);
|
|
7223
7333
|
await this.hooks.beforeBootstrapFinalization?.();
|
|
7224
7334
|
if (await this.bootstrapCommand("finalize", attemptId, bootstrapEpoch, traceState?.scope, "S10") === void 0) {
|
|
7225
|
-
await this.cancelReservations(input.allTags, attemptId, fault2, traceState?.scope);
|
|
7335
|
+
const cleanup = await this.cancelReservations(input.allTags, attemptId, fault2, traceState?.scope);
|
|
7336
|
+
if (cleanup.failedTags.length === 0) await releasePermit();
|
|
7226
7337
|
return responseWithAttempt(error3(409, "bootstrap_command_rejected", "Bootstrap fencing epoch changed before commit write"), attemptId, fault2 !== void 0);
|
|
7227
7338
|
}
|
|
7339
|
+
await this.hooks.beforeAuthoritativeAppend?.();
|
|
7228
7340
|
const writes = await this.appendAllTags(
|
|
7229
7341
|
input,
|
|
7230
7342
|
allocatedCandidates,
|
|
@@ -7240,6 +7352,7 @@ var CommitWorker = class {
|
|
|
7240
7352
|
return this.noApplicationOutcome(attemptId, true);
|
|
7241
7353
|
}
|
|
7242
7354
|
if (writes.committedTags.size === 0 && writes.registrationUnavailableTags.length === writes.pendingTags.length) {
|
|
7355
|
+
await releasePermit();
|
|
7243
7356
|
return responseWithAttempt(
|
|
7244
7357
|
error3(
|
|
7245
7358
|
503,
|
|
@@ -7251,20 +7364,25 @@ var CommitWorker = class {
|
|
|
7251
7364
|
fault2 !== void 0
|
|
7252
7365
|
);
|
|
7253
7366
|
}
|
|
7367
|
+
await releasePermit();
|
|
7254
7368
|
return this.partialWriteOutcome(input, allocatedCandidates, writes, attemptId, fault2 !== void 0);
|
|
7255
7369
|
}
|
|
7370
|
+
await this.hooks.afterAppendBeforeResponse?.();
|
|
7256
7371
|
await this.retiredJournalMilestone("S05d", traceState?.scope, 3);
|
|
7257
7372
|
if (fault2 === "sealing-after-cas") {
|
|
7373
|
+
await releasePermit();
|
|
7258
7374
|
return this.noApplicationOutcome(attemptId, true);
|
|
7259
7375
|
}
|
|
7260
7376
|
try {
|
|
7261
|
-
const success = await this.successResponse(input, allocatedCandidates, attemptId, startedAt, fault2, traceState?.scope);
|
|
7377
|
+
const success = await this.successResponse(input, allocatedCandidates, attemptId, startedAt, fault2, traceState?.scope, writes);
|
|
7378
|
+
await releasePermit();
|
|
7262
7379
|
return responseWithAttempt(
|
|
7263
7380
|
withGlobalAdmission(json4(success, 200), writes.globalAdmission),
|
|
7264
7381
|
attemptId,
|
|
7265
7382
|
fault2 !== void 0
|
|
7266
7383
|
);
|
|
7267
7384
|
} catch {
|
|
7385
|
+
await releasePermit();
|
|
7268
7386
|
return responseWithAttempt(
|
|
7269
7387
|
error3(500, "internal_error", "Committed records could not be read while preparing the response"),
|
|
7270
7388
|
attemptId,
|
|
@@ -7311,6 +7429,51 @@ var CommitWorker = class {
|
|
|
7311
7429
|
const result2 = traceScope === void 0 || rowId === void 0 ? await invoke() : await traceScope.span(rowId, {}, invoke);
|
|
7312
7430
|
return result2.leaseEpoch;
|
|
7313
7431
|
}
|
|
7432
|
+
/**
|
|
7433
|
+
* SDT-G36 bootstrap write permit. Deliberately untraced: it must not add
|
|
7434
|
+
* `sdt.commit/v2` rows or new trace identities. Only a bootstrap barrier,
|
|
7435
|
+
* never commit-outcome authority. No JOURNAL, no alarm, no lease expiry.
|
|
7436
|
+
*/
|
|
7437
|
+
bootstrapStub() {
|
|
7438
|
+
if (this.env.BOOTSTRAP === void 0) return void 0;
|
|
7439
|
+
return this.env.BOOTSTRAP.get(scopeIdFor(this.env.BOOTSTRAP, {
|
|
7440
|
+
serviceId: this.serviceId,
|
|
7441
|
+
doClass: "bootstrap",
|
|
7442
|
+
identity: "coordinator"
|
|
7443
|
+
}));
|
|
7444
|
+
}
|
|
7445
|
+
async bootstrapPermitAcquire(commandId, digest2) {
|
|
7446
|
+
const stub = this.bootstrapStub();
|
|
7447
|
+
if (stub === void 0) return { ok: true };
|
|
7448
|
+
try {
|
|
7449
|
+
const url = new URL(`https://commit-worker.internal/command/permit`);
|
|
7450
|
+
url.searchParams.set("__serviceId", this.serviceId);
|
|
7451
|
+
const response3 = await stub.fetch(new Request(url, {
|
|
7452
|
+
method: "POST",
|
|
7453
|
+
headers: { "content-type": "application/json" },
|
|
7454
|
+
body: JSON.stringify({ commandId, digest: digest2 })
|
|
7455
|
+
}));
|
|
7456
|
+
if (response3.ok) return { ok: true };
|
|
7457
|
+
const body = await response3.clone().json().catch(() => void 0);
|
|
7458
|
+
return { ok: false, code: typeof body?.code === "string" ? body.code : void 0 };
|
|
7459
|
+
} catch {
|
|
7460
|
+
return { ok: false };
|
|
7461
|
+
}
|
|
7462
|
+
}
|
|
7463
|
+
async bootstrapPermitRelease(commandId, digest2) {
|
|
7464
|
+
const stub = this.bootstrapStub();
|
|
7465
|
+
if (stub === void 0) return;
|
|
7466
|
+
try {
|
|
7467
|
+
const url = new URL(`https://commit-worker.internal/command/permit-release`);
|
|
7468
|
+
url.searchParams.set("__serviceId", this.serviceId);
|
|
7469
|
+
await stub.fetch(new Request(url, {
|
|
7470
|
+
method: "POST",
|
|
7471
|
+
headers: { "content-type": "application/json" },
|
|
7472
|
+
body: JSON.stringify({ commandId, digest: digest2 })
|
|
7473
|
+
}));
|
|
7474
|
+
} catch {
|
|
7475
|
+
}
|
|
7476
|
+
}
|
|
7314
7477
|
async postJson(stub, path, body, traceScope, rowId, traceOptions = {}) {
|
|
7315
7478
|
const invoke = async () => {
|
|
7316
7479
|
const response3 = await stub.fetch(
|
|
@@ -7456,6 +7619,7 @@ var CommitWorker = class {
|
|
|
7456
7619
|
let pending = [...input.allTags];
|
|
7457
7620
|
const committedTags = /* @__PURE__ */ new Set();
|
|
7458
7621
|
const lastFailureCodeByTag = /* @__PURE__ */ new Map();
|
|
7622
|
+
const tagWriteFacts = /* @__PURE__ */ new Map();
|
|
7459
7623
|
let globalAdmission = "admitted";
|
|
7460
7624
|
for (let attempt = 0; attempt < MAX_WRITE_ATTEMPTS && pending.length > 0; attempt += 1) {
|
|
7461
7625
|
const append = (stageScope) => Promise.allSettled(
|
|
@@ -7482,12 +7646,26 @@ var CommitWorker = class {
|
|
|
7482
7646
|
retryIndex: attempt,
|
|
7483
7647
|
attemptId
|
|
7484
7648
|
});
|
|
7485
|
-
const
|
|
7649
|
+
const success = response3.status >= 200 && response3.status < 300;
|
|
7650
|
+
let appendVersion;
|
|
7651
|
+
let appendUpdatedAt;
|
|
7652
|
+
let code;
|
|
7653
|
+
try {
|
|
7654
|
+
const parsed = await response3.clone().json();
|
|
7655
|
+
if (isObject5(parsed)) {
|
|
7656
|
+
if (typeof parsed.version === "number" && Number.isSafeInteger(parsed.version)) appendVersion = parsed.version;
|
|
7657
|
+
if (typeof parsed.updatedAt === "string" && parsed.updatedAt.length > 0) appendUpdatedAt = parsed.updatedAt;
|
|
7658
|
+
if (typeof parsed.code === "string") code = parsed.code;
|
|
7659
|
+
}
|
|
7660
|
+
} catch {
|
|
7661
|
+
}
|
|
7486
7662
|
return {
|
|
7487
7663
|
tag,
|
|
7488
|
-
success
|
|
7664
|
+
success,
|
|
7489
7665
|
globalAdmission: readGlobalAdmission(response3),
|
|
7490
|
-
code
|
|
7666
|
+
code,
|
|
7667
|
+
appendVersion,
|
|
7668
|
+
appendUpdatedAt
|
|
7491
7669
|
};
|
|
7492
7670
|
})
|
|
7493
7671
|
);
|
|
@@ -7499,6 +7677,9 @@ var CommitWorker = class {
|
|
|
7499
7677
|
committedTags.add(tag);
|
|
7500
7678
|
lastFailureCodeByTag.delete(tag);
|
|
7501
7679
|
globalAdmission = mergeGlobalAdmission(globalAdmission, result2.value.globalAdmission);
|
|
7680
|
+
if (result2.value.appendVersion !== void 0 && result2.value.appendUpdatedAt !== void 0) {
|
|
7681
|
+
tagWriteFacts.set(tag, { version: result2.value.appendVersion, updatedAt: result2.value.appendUpdatedAt });
|
|
7682
|
+
}
|
|
7502
7683
|
} else {
|
|
7503
7684
|
lastFailureCodeByTag.set(
|
|
7504
7685
|
tag,
|
|
@@ -7515,7 +7696,8 @@ var CommitWorker = class {
|
|
|
7515
7696
|
registrationUnavailableTags: pending.filter(
|
|
7516
7697
|
(tag) => lastFailureCodeByTag.get(tag) === "partition_registration_unavailable"
|
|
7517
7698
|
),
|
|
7518
|
-
globalAdmission
|
|
7699
|
+
globalAdmission,
|
|
7700
|
+
tagWriteFacts
|
|
7519
7701
|
};
|
|
7520
7702
|
}
|
|
7521
7703
|
/**
|
|
@@ -7548,10 +7730,13 @@ var CommitWorker = class {
|
|
|
7548
7730
|
if (traceScope !== void 0) {
|
|
7549
7731
|
await traceScope.span("S17", {}, async () => void 0);
|
|
7550
7732
|
}
|
|
7551
|
-
await this.cancelReservations(tags, attemptId, fault2, traceScope);
|
|
7733
|
+
const cleanup = await this.cancelReservations(tags, attemptId, fault2, traceScope);
|
|
7552
7734
|
await this.retiredJournalMilestone("S05e", traceScope, 4);
|
|
7553
7735
|
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
|
|
7736
|
+
return {
|
|
7737
|
+
response: responseWithAttempt(response3, attemptId, fault2 !== void 0),
|
|
7738
|
+
resolved: cleanup.failedTags.length === 0
|
|
7739
|
+
};
|
|
7555
7740
|
}
|
|
7556
7741
|
/**
|
|
7557
7742
|
* Persist the local partial-write fact for each tag whose append did not
|
|
@@ -7588,22 +7773,25 @@ var CommitWorker = class {
|
|
|
7588
7773
|
}
|
|
7589
7774
|
}, 500), attemptId, exposeAttempt);
|
|
7590
7775
|
}
|
|
7591
|
-
async successResponse(input, candidates, attemptId, startedAt, fault2, traceScope) {
|
|
7592
|
-
const
|
|
7776
|
+
async successResponse(input, candidates, attemptId, startedAt, fault2, traceScope, writes) {
|
|
7777
|
+
const assemble = async (stageScope) => {
|
|
7593
7778
|
if (fault2 === "tag-state-unavailable") {
|
|
7594
7779
|
throw new Error("Test fault made the committed tag state unavailable");
|
|
7595
7780
|
}
|
|
7596
|
-
const
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7781
|
+
const results = [];
|
|
7782
|
+
for (const [memberIndex, tag] of input.allTags.entries()) {
|
|
7783
|
+
const read = () => {
|
|
7784
|
+
const fact = writes?.tagWriteFacts.get(tag);
|
|
7785
|
+
if (fact === void 0) {
|
|
7786
|
+
throw new Error("Committed tag state was unavailable while preparing the response");
|
|
7787
|
+
}
|
|
7788
|
+
return { tag, version: fact.version, writtenAt: fact.updatedAt };
|
|
7789
|
+
};
|
|
7790
|
+
results.push(stageScope === void 0 ? read() : await stageScope.fork().span("S14", { tag, memberIndex, attemptId }, async () => read()));
|
|
7603
7791
|
}
|
|
7604
|
-
return
|
|
7605
|
-
}
|
|
7606
|
-
const tagWriteResults = traceScope === void 0 ? await
|
|
7792
|
+
return results;
|
|
7793
|
+
};
|
|
7794
|
+
const tagWriteResults = traceScope === void 0 ? await assemble() : await traceScope.span("S13", {}, async (stage) => assemble(stage));
|
|
7607
7795
|
return {
|
|
7608
7796
|
writtenEvents: candidates.map((candidate) => ({
|
|
7609
7797
|
payload: encodeBase64Utf8(candidate.payload),
|
|
@@ -7817,15 +8005,15 @@ function observePostAdmission(observer, message, source, input) {
|
|
|
7817
8005
|
observedAt: Date.now()
|
|
7818
8006
|
});
|
|
7819
8007
|
}
|
|
7820
|
-
function errorText(
|
|
7821
|
-
if (
|
|
7822
|
-
return String(
|
|
8008
|
+
function errorText(error11) {
|
|
8009
|
+
if (error11 instanceof Error && error11.message.length > 0) return error11.message;
|
|
8010
|
+
return String(error11);
|
|
7823
8011
|
}
|
|
7824
8012
|
function deliveryCorrelationId(message, source, supplied) {
|
|
7825
8013
|
return supplied ?? `${source}:${message.serviceId}:${message.eventId}:${message.attemptId}`;
|
|
7826
8014
|
}
|
|
7827
|
-
function defaultFailureClass(
|
|
7828
|
-
const candidate =
|
|
8015
|
+
function defaultFailureClass(error11) {
|
|
8016
|
+
const candidate = error11;
|
|
7829
8017
|
if (candidate.failureClass === "duplicate-race") return "duplicate-race";
|
|
7830
8018
|
if (candidate.failureClass === "nonretryable-definition-poison") return "nonretryable-definition-poison";
|
|
7831
8019
|
if (candidate.failureClass === "retryable-transient") return "retryable-transient";
|
|
@@ -7884,8 +8072,8 @@ async function applyViewHandlers(views, message, storedEvent, arrivedAt, source,
|
|
|
7884
8072
|
result: { id: view.id, status, durationMs: Math.max(0, performance.now() - viewStartedAt) },
|
|
7885
8073
|
failure: void 0
|
|
7886
8074
|
};
|
|
7887
|
-
} catch (
|
|
7888
|
-
const failureClass = view.classifyError?.(
|
|
8075
|
+
} catch (error11) {
|
|
8076
|
+
const failureClass = view.classifyError?.(error11) ?? defaultFailureClass(error11);
|
|
7889
8077
|
observePostAdmission(observer, message, source, {
|
|
7890
8078
|
stage: "unsafe-view-apply",
|
|
7891
8079
|
boundary: "end",
|
|
@@ -7897,10 +8085,10 @@ async function applyViewHandlers(views, message, storedEvent, arrivedAt, source,
|
|
|
7897
8085
|
id: view.id,
|
|
7898
8086
|
status: "failed",
|
|
7899
8087
|
failureClass,
|
|
7900
|
-
error: errorText(
|
|
8088
|
+
error: errorText(error11),
|
|
7901
8089
|
durationMs: Math.max(0, performance.now() - viewStartedAt)
|
|
7902
8090
|
},
|
|
7903
|
-
failure: { phase: "view", class: failureClass, viewId: view.id, error: errorText(
|
|
8091
|
+
failure: { phase: "view", class: failureClass, viewId: view.id, error: errorText(error11) }
|
|
7904
8092
|
};
|
|
7905
8093
|
}
|
|
7906
8094
|
}));
|
|
@@ -7930,15 +8118,15 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
7930
8118
|
// settled; this is the durable post-batch observation boundary.
|
|
7931
8119
|
observedAt: Date.now()
|
|
7932
8120
|
});
|
|
7933
|
-
} catch (
|
|
7934
|
-
const failures2 = [{ phase: "recordDelivery", class: "retryable-transient", error: errorText(
|
|
8121
|
+
} catch (error11) {
|
|
8122
|
+
const failures2 = [{ phase: "recordDelivery", class: "retryable-transient", error: errorText(error11) }];
|
|
7935
8123
|
return result(source, message, "stored", arrivedAt, false, [], failures2, options.correlationId, startedAt);
|
|
7936
8124
|
}
|
|
7937
8125
|
if (outcome.outcome !== "stored") {
|
|
7938
8126
|
try {
|
|
7939
8127
|
await store.projectDeliveryIncidents?.(message.serviceId);
|
|
7940
|
-
} catch (
|
|
7941
|
-
const failures2 = [{ phase: "detector", class: "retryable-transient", error: errorText(
|
|
8128
|
+
} catch (error11) {
|
|
8129
|
+
const failures2 = [{ phase: "detector", class: "retryable-transient", error: errorText(error11) }];
|
|
7942
8130
|
return result(source, message, outcome.outcome, arrivedAt, false, [], failures2, options.correlationId, startedAt);
|
|
7943
8131
|
}
|
|
7944
8132
|
return result(source, message, outcome.outcome, arrivedAt, false, [], [], options.correlationId, startedAt);
|
|
@@ -7967,20 +8155,20 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
7967
8155
|
boundary: "end",
|
|
7968
8156
|
outcome: "available"
|
|
7969
8157
|
});
|
|
7970
|
-
} catch (
|
|
8158
|
+
} catch (error11) {
|
|
7971
8159
|
observePostAdmission(options.durableHopObserver, message, source, {
|
|
7972
8160
|
stage: "post-record-delivery-global-receipt-readback",
|
|
7973
8161
|
boundary: "end",
|
|
7974
8162
|
outcome: "error"
|
|
7975
8163
|
});
|
|
7976
|
-
failures.push({ phase: "recordDelivery", class: "retryable-transient", error: errorText(
|
|
8164
|
+
failures.push({ phase: "recordDelivery", class: "retryable-transient", error: errorText(error11) });
|
|
7977
8165
|
return result(source, message, "stored", arrivedAt, false, [], failures, options.correlationId, startedAt);
|
|
7978
8166
|
}
|
|
7979
8167
|
if (options.afterGlobalReceipt !== void 0) {
|
|
7980
8168
|
try {
|
|
7981
8169
|
await options.afterGlobalReceipt({ message, receipt, arrivedAt, source });
|
|
7982
|
-
} catch (
|
|
7983
|
-
failures.push({ phase: "drain", class: "retryable-transient", error: errorText(
|
|
8170
|
+
} catch (error11) {
|
|
8171
|
+
failures.push({ phase: "drain", class: "retryable-transient", error: errorText(error11) });
|
|
7984
8172
|
}
|
|
7985
8173
|
}
|
|
7986
8174
|
}
|
|
@@ -7997,8 +8185,8 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
7997
8185
|
if (options.beforeViews !== void 0) {
|
|
7998
8186
|
try {
|
|
7999
8187
|
await options.beforeViews({ message, event: storedEvent, arrivedAt, source });
|
|
8000
|
-
} catch (
|
|
8001
|
-
failures.push({ phase: "completeness", class: "retryable-transient", error: errorText(
|
|
8188
|
+
} catch (error11) {
|
|
8189
|
+
failures.push({ phase: "completeness", class: "retryable-transient", error: errorText(error11) });
|
|
8002
8190
|
return result(source, message, "stored", arrivedAt, false, viewResults, failures, options.correlationId, startedAt);
|
|
8003
8191
|
}
|
|
8004
8192
|
}
|
|
@@ -8018,14 +8206,14 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8018
8206
|
boundary: "end",
|
|
8019
8207
|
outcome: "applied"
|
|
8020
8208
|
});
|
|
8021
|
-
} catch (
|
|
8209
|
+
} catch (error11) {
|
|
8022
8210
|
observePostAdmission(options.durableHopObserver, message, source, {
|
|
8023
8211
|
stage: "detector",
|
|
8024
8212
|
boundary: "end",
|
|
8025
8213
|
outcome: "error"
|
|
8026
8214
|
});
|
|
8027
8215
|
try {
|
|
8028
|
-
await options.onDetectorFailure?.({ message, arrivedAt, source, error:
|
|
8216
|
+
await options.onDetectorFailure?.({ message, arrivedAt, source, error: error11 });
|
|
8029
8217
|
} catch (healthError) {
|
|
8030
8218
|
failures.push({
|
|
8031
8219
|
phase: "detector",
|
|
@@ -8033,7 +8221,7 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8033
8221
|
error: `detector_health_record_failed:${errorText(healthError)}`
|
|
8034
8222
|
});
|
|
8035
8223
|
}
|
|
8036
|
-
failures.push({ phase: "detector", class: "retryable-transient", error: errorText(
|
|
8224
|
+
failures.push({ phase: "detector", class: "retryable-transient", error: errorText(error11) });
|
|
8037
8225
|
return result(source, message, "stored", arrivedAt, false, [], failures, options.correlationId, startedAt);
|
|
8038
8226
|
}
|
|
8039
8227
|
const views = gatedViews;
|
|
@@ -8050,16 +8238,16 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8050
8238
|
if (options.onStored !== void 0) {
|
|
8051
8239
|
try {
|
|
8052
8240
|
await options.onStored({ message, event: storedEvent, arrivedAt, source });
|
|
8053
|
-
} catch (
|
|
8054
|
-
failures.push({ phase: "view", class: "retryable-transient", viewId: "legacy-onStored", error: errorText(
|
|
8241
|
+
} catch (error11) {
|
|
8242
|
+
failures.push({ phase: "view", class: "retryable-transient", viewId: "legacy-onStored", error: errorText(error11) });
|
|
8055
8243
|
}
|
|
8056
8244
|
}
|
|
8057
8245
|
const preliminary = result(source, message, "stored", arrivedAt, detectorApplied, viewResults, failures, options.correlationId, startedAt);
|
|
8058
8246
|
if (options.afterDelivery !== void 0 && failures.every((failure3) => failure3.class === "duplicate-race")) {
|
|
8059
8247
|
try {
|
|
8060
8248
|
await options.afterDelivery({ message, event: storedEvent, arrivedAt, source, result: preliminary });
|
|
8061
|
-
} catch (
|
|
8062
|
-
failures.push({ phase: "drain", class: "retryable-transient", error: errorText(
|
|
8249
|
+
} catch (error11) {
|
|
8250
|
+
failures.push({ phase: "drain", class: "retryable-transient", error: errorText(error11) });
|
|
8063
8251
|
}
|
|
8064
8252
|
}
|
|
8065
8253
|
return result(source, message, "stored", arrivedAt, detectorApplied, viewResults, failures, options.correlationId, startedAt);
|
|
@@ -8081,8 +8269,8 @@ function asNumber(value, name) {
|
|
|
8081
8269
|
if (!Number.isSafeInteger(number) || number < 0) throw new Error(`G44 ${name} must be a non-negative safe integer`);
|
|
8082
8270
|
return number;
|
|
8083
8271
|
}
|
|
8084
|
-
function errorText2(
|
|
8085
|
-
return
|
|
8272
|
+
function errorText2(error11) {
|
|
8273
|
+
return error11 instanceof Error && error11.message.length > 0 ? error11.message : String(error11);
|
|
8086
8274
|
}
|
|
8087
8275
|
function settledFrontierFromCursor(cursorJson) {
|
|
8088
8276
|
const cursor = settledCursorFromJson(cursorJson);
|
|
@@ -8186,8 +8374,8 @@ var GlobalCompletenessReconciler = class {
|
|
|
8186
8374
|
let snapshots;
|
|
8187
8375
|
try {
|
|
8188
8376
|
snapshots = await this.snapshotPartitions(serviceId);
|
|
8189
|
-
} catch (
|
|
8190
|
-
const message = errorText2(
|
|
8377
|
+
} catch (error11) {
|
|
8378
|
+
const message = errorText2(error11);
|
|
8191
8379
|
await this.appendFinding(serviceId, {
|
|
8192
8380
|
identity: scannerFailureIdentity(serviceId),
|
|
8193
8381
|
type: "GLOBAL_ARRAY_SCANNER_FAILURE",
|
|
@@ -8229,7 +8417,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8229
8417
|
let joined;
|
|
8230
8418
|
try {
|
|
8231
8419
|
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 (
|
|
8420
|
+
} catch (error11) {
|
|
8233
8421
|
await this.appendFinding(snapshot.serviceId, {
|
|
8234
8422
|
identity: `GLOBAL_RECEIPT_UNAVAILABLE|${snapshot.serviceId}|${snapshot.tag}|${obligation.obligationSequence}|${obligation.eventId}|${obligation.eventDigest}`,
|
|
8235
8423
|
type: "GLOBAL_ARRAY_RECEIPT_UNAVAILABLE",
|
|
@@ -8238,7 +8426,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8238
8426
|
eventId: obligation.eventId,
|
|
8239
8427
|
eventDigest: obligation.eventDigest
|
|
8240
8428
|
}, nowMs).catch(() => void 0);
|
|
8241
|
-
throw
|
|
8429
|
+
throw error11;
|
|
8242
8430
|
}
|
|
8243
8431
|
if (!joined) {
|
|
8244
8432
|
await this.appendObligationFinding(snapshot.serviceId, snapshot.tag, obligation, nowMs);
|
|
@@ -8257,8 +8445,8 @@ var GlobalCompletenessReconciler = class {
|
|
|
8257
8445
|
if (afterSequence !== snapshot.upperBoundSequence) {
|
|
8258
8446
|
throw new Error(`source_partition_range_incomplete:${snapshot.tag}`);
|
|
8259
8447
|
}
|
|
8260
|
-
} catch (
|
|
8261
|
-
if (errorText2(
|
|
8448
|
+
} catch (error11) {
|
|
8449
|
+
if (errorText2(error11).startsWith("source_")) {
|
|
8262
8450
|
await this.appendFinding(snapshot.serviceId, {
|
|
8263
8451
|
identity: sourceUnavailableIdentity(snapshot.serviceId, snapshot.tag),
|
|
8264
8452
|
type: "GLOBAL_ARRAY_SOURCE_PARTITION_UNAVAILABLE",
|
|
@@ -8268,7 +8456,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8268
8456
|
eventDigest: null
|
|
8269
8457
|
}, nowMs).catch(() => void 0);
|
|
8270
8458
|
}
|
|
8271
|
-
throw
|
|
8459
|
+
throw error11;
|
|
8272
8460
|
}
|
|
8273
8461
|
}
|
|
8274
8462
|
const endSnapshots = await this.snapshotPartitions(serviceId);
|
|
@@ -8287,8 +8475,8 @@ var GlobalCompletenessReconciler = class {
|
|
|
8287
8475
|
}
|
|
8288
8476
|
await this.writeHealth(serviceId, "HEALTHY", JSON.stringify(cursor), nowMs, null, nowMs);
|
|
8289
8477
|
return { kind: "FULL", partitions: snapshots, scannedObligations: scanned };
|
|
8290
|
-
} catch (
|
|
8291
|
-
const message = errorText2(
|
|
8478
|
+
} catch (error11) {
|
|
8479
|
+
const message = errorText2(error11);
|
|
8292
8480
|
const health = message.startsWith("source_") ? "UNKNOWN" : "FAILED";
|
|
8293
8481
|
if (health === "FAILED") {
|
|
8294
8482
|
await this.appendFinding(serviceId, {
|
|
@@ -8430,8 +8618,8 @@ var GlobalCompletenessReconciler = class {
|
|
|
8430
8618
|
* without applying views. It shares the scanner's single health/finding
|
|
8431
8619
|
* authority rather than creating a second incident workflow.
|
|
8432
8620
|
*/
|
|
8433
|
-
async recordDetectorFailure(serviceId,
|
|
8434
|
-
const message = `detector_failure:${errorText2(
|
|
8621
|
+
async recordDetectorFailure(serviceId, error11, nowMs) {
|
|
8622
|
+
const message = `detector_failure:${errorText2(error11).slice(0, 256)}`;
|
|
8435
8623
|
await this.appendFinding(serviceId, {
|
|
8436
8624
|
identity: `GLOBAL_DETECTOR_FAILURE|${serviceId}`,
|
|
8437
8625
|
type: "GLOBAL_ARRAY_DETECTOR_FAILURE",
|
|
@@ -8570,7 +8758,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8570
8758
|
nowMs
|
|
8571
8759
|
).run();
|
|
8572
8760
|
}
|
|
8573
|
-
async writeHealth(serviceId, status, cursor, lastFullScanAt,
|
|
8761
|
+
async writeHealth(serviceId, status, cursor, lastFullScanAt, error11, nowMs) {
|
|
8574
8762
|
await this.database.prepare(
|
|
8575
8763
|
`INSERT INTO serialized_dcb_completeness_scanner_health
|
|
8576
8764
|
(service_id, scanner_version, status, cursor_json, last_full_scan_at, last_error, updated_at)
|
|
@@ -8582,7 +8770,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8582
8770
|
last_full_scan_at = COALESCE(excluded.last_full_scan_at, serialized_dcb_completeness_scanner_health.last_full_scan_at),
|
|
8583
8771
|
last_error = excluded.last_error,
|
|
8584
8772
|
updated_at = excluded.updated_at`
|
|
8585
|
-
).bind(serviceId, this.scannerVersion, status, cursor, lastFullScanAt,
|
|
8773
|
+
).bind(serviceId, this.scannerVersion, status, cursor, lastFullScanAt, error11, nowMs).run();
|
|
8586
8774
|
}
|
|
8587
8775
|
};
|
|
8588
8776
|
function globalReceiptAcknowledgement(message, receivedAt) {
|
|
@@ -8674,7 +8862,7 @@ function sourceAcknowledgementOptions(env, options) {
|
|
|
8674
8862
|
if (decision.kind !== "SETTLED") {
|
|
8675
8863
|
throw new Error(`global_completeness_${decision.kind}:${decision.health.status}`);
|
|
8676
8864
|
}
|
|
8677
|
-
} catch (
|
|
8865
|
+
} catch (error11) {
|
|
8678
8866
|
if (!completed) {
|
|
8679
8867
|
observeG60PostAdmission(options.durableHopObserver, {
|
|
8680
8868
|
stage: "completeness-coverage",
|
|
@@ -8689,7 +8877,7 @@ function sourceAcknowledgementOptions(env, options) {
|
|
|
8689
8877
|
observedAt: Date.now()
|
|
8690
8878
|
});
|
|
8691
8879
|
}
|
|
8692
|
-
throw
|
|
8880
|
+
throw error11;
|
|
8693
8881
|
}
|
|
8694
8882
|
} : options.beforeViews;
|
|
8695
8883
|
const observedSourceAcknowledgement = sourceAcknowledgement === void 0 ? void 0 : async (input) => {
|
|
@@ -8697,14 +8885,14 @@ function sourceAcknowledgementOptions(env, options) {
|
|
|
8697
8885
|
try {
|
|
8698
8886
|
await sourceAcknowledgement(input);
|
|
8699
8887
|
observeSourceSubstep(options.durableHopObserver, { ...input, boundary: "end", outcome: "acknowledged" });
|
|
8700
|
-
} catch (
|
|
8888
|
+
} catch (error11) {
|
|
8701
8889
|
observeSourceSubstep(options.durableHopObserver, { ...input, boundary: "end", outcome: "error" });
|
|
8702
|
-
throw
|
|
8890
|
+
throw error11;
|
|
8703
8891
|
}
|
|
8704
8892
|
};
|
|
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,
|
|
8893
|
+
const detectorFailure = hasG44Authority ? async ({ message, arrivedAt, error: error11, source }) => {
|
|
8894
|
+
await options.onDetectorFailure?.({ message, arrivedAt, error: error11, source });
|
|
8895
|
+
await new GlobalCompletenessReconciler(env.D1, env.TAG).recordDetectorFailure(message.serviceId, error11, arrivedAt);
|
|
8708
8896
|
} : options.onDetectorFailure;
|
|
8709
8897
|
return {
|
|
8710
8898
|
...options,
|
|
@@ -8719,9 +8907,9 @@ function sharedStore(env, provider) {
|
|
|
8719
8907
|
async function admitBootstrapRoute(env, serviceId, route) {
|
|
8720
8908
|
if (env.BOOTSTRAP === void 0) {
|
|
8721
8909
|
if (env.G38_DOORBELL_DELIVERY_ROLE === "receiver") {
|
|
8722
|
-
const
|
|
8723
|
-
|
|
8724
|
-
throw
|
|
8910
|
+
const error11 = new Error(`bootstrap_route_binding_missing:${route}`);
|
|
8911
|
+
error11.name = "BootstrapRouteBindingMissingError";
|
|
8912
|
+
throw error11;
|
|
8725
8913
|
}
|
|
8726
8914
|
return;
|
|
8727
8915
|
}
|
|
@@ -8810,10 +8998,10 @@ async function handleDownstreamQueue(batch, env, options = {}) {
|
|
|
8810
8998
|
if (outcome.outcome === "stored" && !outcome.failures.some((failure3) => failure3.phase === "recordDelivery")) {
|
|
8811
8999
|
try {
|
|
8812
9000
|
options.afterStoredQueueDelivery?.({ message: queued.body, result: outcome });
|
|
8813
|
-
} catch (
|
|
9001
|
+
} catch (error11) {
|
|
8814
9002
|
console.warn("safe_lane_kick", {
|
|
8815
9003
|
status: "failed-to-schedule",
|
|
8816
|
-
error:
|
|
9004
|
+
error: error11 instanceof Error ? error11.message : String(error11),
|
|
8817
9005
|
serviceId: queued.body.serviceId,
|
|
8818
9006
|
eventId: queued.body.eventId,
|
|
8819
9007
|
attemptId: queued.body.attemptId
|
|
@@ -8835,6 +9023,36 @@ async function stabilizeDownstream(env, options = {}, serviceId, serviceIdentity
|
|
|
8835
9023
|
});
|
|
8836
9024
|
}
|
|
8837
9025
|
|
|
9026
|
+
// src/scope/ControlRouteScope.ts
|
|
9027
|
+
function response2(status, code, error11) {
|
|
9028
|
+
return new Response(JSON.stringify({ code, error: error11 }), {
|
|
9029
|
+
status,
|
|
9030
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
9031
|
+
});
|
|
9032
|
+
}
|
|
9033
|
+
function scopeIdentityMissingResponse() {
|
|
9034
|
+
return response2(503, "scope.identity_missing", "A deployment service identity is required for this scope");
|
|
9035
|
+
}
|
|
9036
|
+
function enforceControlRouteScope(input) {
|
|
9037
|
+
let actual;
|
|
9038
|
+
try {
|
|
9039
|
+
actual = input.provider.forRequest(input.request, input.requestOptions).serviceId;
|
|
9040
|
+
} catch (caught) {
|
|
9041
|
+
if (caught instanceof ServiceIdentityMissingError) {
|
|
9042
|
+
console.error({ schema: "sdt.scope/v1", code: "scope.identity_missing", route: input.route });
|
|
9043
|
+
return { response: scopeIdentityMissingResponse() };
|
|
9044
|
+
}
|
|
9045
|
+
throw caught;
|
|
9046
|
+
}
|
|
9047
|
+
if (input.pathServiceId !== actual) {
|
|
9048
|
+
console.warn({ schema: "sdt.scope/v1", code: "scope.mismatch", route: input.route });
|
|
9049
|
+
return {
|
|
9050
|
+
response: response2(403, "scope.mismatch", "The path service identity is not authorized for this deployment")
|
|
9051
|
+
};
|
|
9052
|
+
}
|
|
9053
|
+
return { serviceId: actual };
|
|
9054
|
+
}
|
|
9055
|
+
|
|
8838
9056
|
// src/downstream/OutboxDrain.ts
|
|
8839
9057
|
function json5(body, status = 200) {
|
|
8840
9058
|
return new Response(JSON.stringify(body), {
|
|
@@ -8931,15 +9149,32 @@ async function handleOutboxDrainRequest(request, env, options = {}) {
|
|
|
8931
9149
|
if (!isDrainRequest(body)) {
|
|
8932
9150
|
return json5({ error: "serviceId and unique non-empty tags are required", code: "invalid_outbox_drain_request" }, 400);
|
|
8933
9151
|
}
|
|
9152
|
+
const requestedServiceId = body.serviceId;
|
|
9153
|
+
const tags = body.tags;
|
|
9154
|
+
const provider = options.serviceIdentityProvider ?? envServiceIdentity(env);
|
|
9155
|
+
let authority;
|
|
9156
|
+
try {
|
|
9157
|
+
authority = provider.forRequest(request).serviceId;
|
|
9158
|
+
} catch (caught) {
|
|
9159
|
+
if (caught instanceof ServiceIdentityMissingError) return scopeIdentityMissingResponse();
|
|
9160
|
+
throw caught;
|
|
9161
|
+
}
|
|
9162
|
+
if (requestedServiceId !== authority) {
|
|
9163
|
+
console.warn({ schema: "sdt.scope/v1", code: "scope.mismatch", route: "outbox-drain" });
|
|
9164
|
+
return json5({
|
|
9165
|
+
code: "scope.mismatch",
|
|
9166
|
+
error: "The request service identity is not authorized for this deployment"
|
|
9167
|
+
}, 403);
|
|
9168
|
+
}
|
|
8934
9169
|
try {
|
|
8935
9170
|
const results = [];
|
|
8936
|
-
for (const tag of
|
|
8937
|
-
results.push(await drainTagOutbox({ serviceId:
|
|
9171
|
+
for (const tag of tags) {
|
|
9172
|
+
results.push(await drainTagOutbox({ serviceId: authority, tag }, env, systemPipelineClock, options));
|
|
8938
9173
|
}
|
|
8939
9174
|
return json5({ results });
|
|
8940
|
-
} catch (
|
|
9175
|
+
} catch (error11) {
|
|
8941
9176
|
return json5({
|
|
8942
|
-
error:
|
|
9177
|
+
error: error11 instanceof Error ? error11.message : "Outbox drain failed",
|
|
8943
9178
|
code: "outbox_drain_failed"
|
|
8944
9179
|
}, 503);
|
|
8945
9180
|
}
|
|
@@ -9384,9 +9619,9 @@ function isNonEmptyString7(value) {
|
|
|
9384
9619
|
return typeof value === "string" && value.length > 0;
|
|
9385
9620
|
}
|
|
9386
9621
|
function base64Json(value) {
|
|
9387
|
-
const
|
|
9622
|
+
const bytes2 = new TextEncoder().encode(JSON.stringify(value));
|
|
9388
9623
|
let binary = "";
|
|
9389
|
-
for (const byte of
|
|
9624
|
+
for (const byte of bytes2) {
|
|
9390
9625
|
binary += String.fromCharCode(byte);
|
|
9391
9626
|
}
|
|
9392
9627
|
return btoa(binary);
|
|
@@ -9564,9 +9799,9 @@ function decodeStoredJson(value) {
|
|
|
9564
9799
|
}
|
|
9565
9800
|
}
|
|
9566
9801
|
function jsonBytes(value) {
|
|
9567
|
-
const
|
|
9802
|
+
const bytes2 = new TextEncoder().encode(JSON.stringify(value));
|
|
9568
9803
|
let binary = "";
|
|
9569
|
-
for (const byte of
|
|
9804
|
+
for (const byte of bytes2) binary += String.fromCharCode(byte);
|
|
9570
9805
|
return btoa(binary);
|
|
9571
9806
|
}
|
|
9572
9807
|
function isRuntimeBridgedProjector(definition) {
|
|
@@ -9639,9 +9874,9 @@ function isRuntimeCommandLike(value) {
|
|
|
9639
9874
|
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
9875
|
}
|
|
9641
9876
|
function encodeJsonPayload(value) {
|
|
9642
|
-
const
|
|
9877
|
+
const bytes2 = new TextEncoder().encode(JSON.stringify(value));
|
|
9643
9878
|
let binary = "";
|
|
9644
|
-
for (const byte of
|
|
9879
|
+
for (const byte of bytes2) binary += String.fromCharCode(byte);
|
|
9645
9880
|
return btoa(binary);
|
|
9646
9881
|
}
|
|
9647
9882
|
function candidateToV1Envelope(candidate) {
|
|
@@ -9825,8 +10060,8 @@ function stateFromCheckpoint(projector, checkpoint) {
|
|
|
9825
10060
|
if (checkpoint === void 0) return projector.initialState();
|
|
9826
10061
|
try {
|
|
9827
10062
|
return projector.deserializeState(checkpoint.stateJson);
|
|
9828
|
-
} catch (
|
|
9829
|
-
throw new ProjectionCheckpointCorruption(
|
|
10063
|
+
} catch (error11) {
|
|
10064
|
+
throw new ProjectionCheckpointCorruption(error11);
|
|
9830
10065
|
}
|
|
9831
10066
|
}
|
|
9832
10067
|
function projectionEventFromStored(event) {
|
|
@@ -10050,8 +10285,8 @@ async function admitBootstrapRoute2(env, serviceId) {
|
|
|
10050
10285
|
})).fetch(new Request(url, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ route: "projection-rebuild" }) }));
|
|
10051
10286
|
if (!admitted.ok) throw new Error("bootstrap_route_rejected:projection-rebuild");
|
|
10052
10287
|
}
|
|
10053
|
-
function boundedErrorReason(
|
|
10054
|
-
const value =
|
|
10288
|
+
function boundedErrorReason(error11) {
|
|
10289
|
+
const value = error11 instanceof Error ? error11.message : String(error11);
|
|
10055
10290
|
return value.length > 256 ? `${value.slice(0, 253)}...` : value;
|
|
10056
10291
|
}
|
|
10057
10292
|
async function notifyObserver(observer, callback, input) {
|
|
@@ -10064,7 +10299,7 @@ async function notifyObserver(observer, callback, input) {
|
|
|
10064
10299
|
} catch {
|
|
10065
10300
|
}
|
|
10066
10301
|
}
|
|
10067
|
-
async function notifyOutcomes(observer, serviceId, projectorIds, attemptedAt, results, maximumSuid, env,
|
|
10302
|
+
async function notifyOutcomes(observer, serviceId, projectorIds, attemptedAt, results, maximumSuid, env, error11) {
|
|
10068
10303
|
if (observer?.onOutcome === void 0) return;
|
|
10069
10304
|
const byProjector = /* @__PURE__ */ new Map();
|
|
10070
10305
|
for (const result2 of results) {
|
|
@@ -10077,9 +10312,9 @@ async function notifyOutcomes(observer, serviceId, projectorIds, attemptedAt, re
|
|
|
10077
10312
|
let outcome = "invoked-but-no-work";
|
|
10078
10313
|
let reason = null;
|
|
10079
10314
|
let advancedSourceEvents = 0;
|
|
10080
|
-
if (
|
|
10315
|
+
if (error11 !== void 0) {
|
|
10081
10316
|
outcome = "invoked-and-threw";
|
|
10082
|
-
reason = boundedErrorReason(
|
|
10317
|
+
reason = boundedErrorReason(error11);
|
|
10083
10318
|
} else {
|
|
10084
10319
|
advancedSourceEvents = projectorResults.reduce((total, result2) => total + result2.advancedSourceEvents, 0);
|
|
10085
10320
|
if (advancedSourceEvents > 0) {
|
|
@@ -10181,9 +10416,9 @@ async function pollLiveProjections(env, options = {}) {
|
|
|
10181
10416
|
const results = await runtime.pollRegistered(serviceId, attemptedAt, options.maximumSuid);
|
|
10182
10417
|
await notifyOutcomes(options.observer, serviceId, projectorIds, attemptedAt, results, options.maximumSuid, env);
|
|
10183
10418
|
return results;
|
|
10184
|
-
} catch (
|
|
10185
|
-
await notifyOutcomes(options.observer, serviceId, projectorIds, attemptedAt, [], options.maximumSuid, env,
|
|
10186
|
-
throw
|
|
10419
|
+
} catch (error11) {
|
|
10420
|
+
await notifyOutcomes(options.observer, serviceId, projectorIds, attemptedAt, [], options.maximumSuid, env, error11);
|
|
10421
|
+
throw error11;
|
|
10187
10422
|
}
|
|
10188
10423
|
}
|
|
10189
10424
|
async function handleProjectionLag(request, env, registry = DEPLOYED_PROJECTOR_REGISTRY, storeProvider, serviceIdentityProvider) {
|
|
@@ -10421,15 +10656,15 @@ function integer(row, key) {
|
|
|
10421
10656
|
function operationId() {
|
|
10422
10657
|
return `mv-unsafe-${crypto.randomUUID()}`;
|
|
10423
10658
|
}
|
|
10424
|
-
function errorFrom(
|
|
10425
|
-
const value = String(
|
|
10659
|
+
function errorFrom(error11) {
|
|
10660
|
+
const value = String(error11);
|
|
10426
10661
|
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:
|
|
10662
|
+
return new UnsafeWindowMaterializedViewError("UNSAFE_DUPLICATE_RACE", false, "Concurrent unsafe receipt was committed by another delivery", { cause: error11 });
|
|
10428
10663
|
}
|
|
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:
|
|
10664
|
+
if (value.includes("unsafe_row_cas")) return new UnsafeWindowMaterializedViewError("UNSAFE_ROW_CAS_MISMATCH", true, "Unsafe row version changed before atomic apply", { cause: error11 });
|
|
10665
|
+
if (value.includes("unsafe_suid_contradiction")) return new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Unsafe same-SUID payload contradiction", { cause: error11 });
|
|
10666
|
+
if (value.includes("unsafe_safe_ahead")) return new UnsafeWindowMaterializedViewError("UNSAFE_SAFE_AHEAD", false, "Safe head already covers unsafe event", { cause: error11 });
|
|
10667
|
+
if (value.includes("unsafe_behind_frontier")) return new UnsafeWindowMaterializedViewError("UNSAFE_BEHIND_FRONTIER", false, "Unsafe reads are fail-closed pending rebuild", { cause: error11 });
|
|
10433
10668
|
return void 0;
|
|
10434
10669
|
}
|
|
10435
10670
|
var UnsafeWindowMaterializedViewStore = class {
|
|
@@ -10562,8 +10797,8 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
10562
10797
|
await this.options.beforeApplyBatch?.(input, statements);
|
|
10563
10798
|
try {
|
|
10564
10799
|
await this.database.batch(statements);
|
|
10565
|
-
} catch (
|
|
10566
|
-
const text = String(
|
|
10800
|
+
} catch (error11) {
|
|
10801
|
+
const text = String(error11);
|
|
10567
10802
|
const guardFailure = text.includes("mv_atomic_guards") || text.includes("checkpoint_match") || text.includes("mv_unsafe_receipts") && (text.includes("UNIQUE") || text.includes("constraint"));
|
|
10568
10803
|
if (guardFailure) {
|
|
10569
10804
|
const receipt = await this.database.prepare(
|
|
@@ -10571,16 +10806,16 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
10571
10806
|
).bind(input.serviceId, input.viewId, input.eventId).first();
|
|
10572
10807
|
if (receipt !== null && receipt !== void 0) {
|
|
10573
10808
|
if (string2(receipt, "suid") !== input.suid) {
|
|
10574
|
-
throw new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Duplicate event identity has a contradictory SUID", { cause:
|
|
10809
|
+
throw new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Duplicate event identity has a contradictory SUID", { cause: error11 });
|
|
10575
10810
|
}
|
|
10576
10811
|
if (this.options.beforeApplyBatch !== void 0) {
|
|
10577
|
-
throw new UnsafeWindowMaterializedViewError("UNSAFE_DUPLICATE_RACE", false, "Concurrent unsafe receipt was committed by another delivery", { cause:
|
|
10812
|
+
throw new UnsafeWindowMaterializedViewError("UNSAFE_DUPLICATE_RACE", false, "Concurrent unsafe receipt was committed by another delivery", { cause: error11 });
|
|
10578
10813
|
}
|
|
10579
10814
|
return { outcome: "no-change", duplicate: true };
|
|
10580
10815
|
}
|
|
10581
10816
|
return this.apply(input, true);
|
|
10582
10817
|
}
|
|
10583
|
-
throw
|
|
10818
|
+
throw error11;
|
|
10584
10819
|
}
|
|
10585
10820
|
const result2 = { outcome: "applied", duplicate: false };
|
|
10586
10821
|
await this.options.afterApplyBatch?.(input, result2);
|
|
@@ -10713,13 +10948,13 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
10713
10948
|
await this.options.beforeApplyBatch?.(input, statements);
|
|
10714
10949
|
try {
|
|
10715
10950
|
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(
|
|
10951
|
+
} catch (error11) {
|
|
10952
|
+
if (sameSuidContradiction) throw new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Unsafe same-SUID payload contradiction", { cause: error11 });
|
|
10953
|
+
if (rowCasMismatch) throw new UnsafeWindowMaterializedViewError("UNSAFE_ROW_CAS_MISMATCH", true, "Unsafe row version changed before atomic apply", { cause: error11 });
|
|
10954
|
+
if (safeAhead) throw new UnsafeWindowMaterializedViewError("UNSAFE_SAFE_AHEAD", false, "Safe head already covers unsafe event", { cause: error11 });
|
|
10955
|
+
const typed = errorFrom(error11);
|
|
10721
10956
|
if (typed !== void 0) throw typed;
|
|
10722
|
-
throw
|
|
10957
|
+
throw error11;
|
|
10723
10958
|
}
|
|
10724
10959
|
const result2 = { outcome, duplicate: false };
|
|
10725
10960
|
await this.options.afterApplyBatch?.(input, result2);
|
|
@@ -10786,8 +11021,8 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
10786
11021
|
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
11022
|
this.database.prepare("DELETE FROM mv_atomic_guards WHERE operation_id = ?").bind(operation)
|
|
10788
11023
|
]);
|
|
10789
|
-
} catch (
|
|
10790
|
-
throw new UnsafeWindowMaterializedViewError("UNSAFE_KICK_LEASE_HELD", true, "Unsafe kick is held by another lease owner", { cause:
|
|
11024
|
+
} catch (error11) {
|
|
11025
|
+
throw new UnsafeWindowMaterializedViewError("UNSAFE_KICK_LEASE_HELD", true, "Unsafe kick is held by another lease owner", { cause: error11 });
|
|
10791
11026
|
}
|
|
10792
11027
|
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
11028
|
return row === null || row === void 0 ? void 0 : { targetSuid: string2(row, "target_suid"), dirty: integer(row, "dirty") === 1 };
|
|
@@ -10861,9 +11096,9 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
10861
11096
|
try {
|
|
10862
11097
|
await this.database.batch(statements);
|
|
10863
11098
|
return true;
|
|
10864
|
-
} catch (
|
|
10865
|
-
if (String(
|
|
10866
|
-
throw
|
|
11099
|
+
} catch (error11) {
|
|
11100
|
+
if (String(error11).includes("mv_atomic_guards") || String(error11).includes("checkpoint_match")) return false;
|
|
11101
|
+
throw error11;
|
|
10867
11102
|
}
|
|
10868
11103
|
}
|
|
10869
11104
|
/**
|
|
@@ -11029,12 +11264,12 @@ function indexValue(row, type) {
|
|
|
11029
11264
|
function operationId2() {
|
|
11030
11265
|
return `mv-${crypto.randomUUID()}`;
|
|
11031
11266
|
}
|
|
11032
|
-
function isCasFailure(
|
|
11033
|
-
const message = String(
|
|
11267
|
+
function isCasFailure(error11) {
|
|
11268
|
+
const message = String(error11);
|
|
11034
11269
|
return message.includes("mv_atomic_guards.checkpoint_match") || message.includes("MV_CAS_MISMATCH");
|
|
11035
11270
|
}
|
|
11036
|
-
function isPatchFailure(
|
|
11037
|
-
return String(
|
|
11271
|
+
function isPatchFailure(error11) {
|
|
11272
|
+
return String(error11).includes("CHECK constraint failed: checkpoint_match");
|
|
11038
11273
|
}
|
|
11039
11274
|
var D1MaterializedViewStore = class {
|
|
11040
11275
|
constructor(database) {
|
|
@@ -11070,8 +11305,8 @@ var D1MaterializedViewStore = class {
|
|
|
11070
11305
|
VALUES (?, ?, ?, ?)`
|
|
11071
11306
|
).bind(input.serviceId, input.viewId, generation, input.updatedAt)
|
|
11072
11307
|
]);
|
|
11073
|
-
} catch (
|
|
11074
|
-
throw new MaterializedViewStoreError("create-active", "MV_INSTANCE_EXISTS", `Active materialized view already exists: ${String(
|
|
11308
|
+
} catch (error11) {
|
|
11309
|
+
throw new MaterializedViewStoreError("create-active", "MV_INSTANCE_EXISTS", `Active materialized view already exists: ${String(error11)}`, { cause: error11 });
|
|
11075
11310
|
}
|
|
11076
11311
|
return this.readInstance(input.serviceId, input.viewId, generation);
|
|
11077
11312
|
}
|
|
@@ -11092,8 +11327,8 @@ var D1MaterializedViewStore = class {
|
|
|
11092
11327
|
input.definitionVersion,
|
|
11093
11328
|
input.updatedAt
|
|
11094
11329
|
).run();
|
|
11095
|
-
} catch (
|
|
11096
|
-
throw new MaterializedViewStoreError("create-candidate", "MV_INSTANCE_EXISTS", `Candidate generation already exists: ${String(
|
|
11330
|
+
} catch (error11) {
|
|
11331
|
+
throw new MaterializedViewStoreError("create-candidate", "MV_INSTANCE_EXISTS", `Candidate generation already exists: ${String(error11)}`, { cause: error11 });
|
|
11097
11332
|
}
|
|
11098
11333
|
return this.readInstance(input.serviceId, input.viewId, input.generation);
|
|
11099
11334
|
}
|
|
@@ -11231,10 +11466,10 @@ var D1MaterializedViewStore = class {
|
|
|
11231
11466
|
*/
|
|
11232
11467
|
async readListPage(serviceId, viewId, options = {}) {
|
|
11233
11468
|
this.ready("initialize");
|
|
11234
|
-
const
|
|
11235
|
-
if (
|
|
11236
|
-
const selected = options.generation ??
|
|
11237
|
-
const selectedInstance = selected ===
|
|
11469
|
+
const active2 = await this.readActive(serviceId, viewId);
|
|
11470
|
+
if (active2 === void 0) return { rows: [], totalCount: 0, readHead: "" };
|
|
11471
|
+
const selected = options.generation ?? active2.generation;
|
|
11472
|
+
const selectedInstance = selected === active2.generation ? active2 : await this.readInstance(serviceId, viewId, selected);
|
|
11238
11473
|
if (selectedInstance === void 0) {
|
|
11239
11474
|
throw new MaterializedViewStoreError("initialize", "MV_INSTANCE_MISSING", "Requested materialized-view generation is unavailable");
|
|
11240
11475
|
}
|
|
@@ -11751,10 +11986,10 @@ var D1MaterializedViewStore = class {
|
|
|
11751
11986
|
).bind(operation, `${operation}:%`));
|
|
11752
11987
|
try {
|
|
11753
11988
|
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(
|
|
11989
|
+
} catch (error11) {
|
|
11990
|
+
if (isPatchFailure(error11)) throw new MaterializedViewPatchError();
|
|
11991
|
+
if (isCasFailure(error11)) throw new MaterializedViewCasError();
|
|
11992
|
+
throw new MaterializedViewStoreError("apply", "MV_STORE_OPERATION_FAILED", `Materialized-view atomic apply failed: ${String(error11)}`, { cause: error11 });
|
|
11758
11993
|
}
|
|
11759
11994
|
const instance = await this.readInstance(input.serviceId, input.viewId, input.generation);
|
|
11760
11995
|
if (instance === void 0) throw new MaterializedViewStoreError("apply", "MV_INSTANCE_MISSING", "Materialized-view instance disappeared after apply");
|
|
@@ -11859,13 +12094,13 @@ var D1MaterializedViewStore = class {
|
|
|
11859
12094
|
];
|
|
11860
12095
|
try {
|
|
11861
12096
|
await this.database.batch(statements);
|
|
11862
|
-
} catch (
|
|
11863
|
-
if (isCasFailure(
|
|
11864
|
-
throw new MaterializedViewStoreError("promote", "MV_STORE_OPERATION_FAILED", `Materialized-view promotion failed: ${String(
|
|
12097
|
+
} catch (error11) {
|
|
12098
|
+
if (isCasFailure(error11)) throw new MaterializedViewPromotionCasError();
|
|
12099
|
+
throw new MaterializedViewStoreError("promote", "MV_STORE_OPERATION_FAILED", `Materialized-view promotion failed: ${String(error11)}`, { cause: error11 });
|
|
11865
12100
|
}
|
|
11866
|
-
const
|
|
11867
|
-
if (
|
|
11868
|
-
return
|
|
12101
|
+
const active2 = await this.readActive(input.serviceId, input.viewId);
|
|
12102
|
+
if (active2 === void 0) throw new MaterializedViewStoreError("promote", "MV_INSTANCE_MISSING", "Active generation disappeared after promotion");
|
|
12103
|
+
return active2;
|
|
11869
12104
|
}
|
|
11870
12105
|
/** Short aliases make the port convenient for runtime composition and tests. */
|
|
11871
12106
|
apply = this.applyMutationsAndAdvanceCheckpoint.bind(this);
|
|
@@ -11874,8 +12109,8 @@ var D1MaterializedViewStore = class {
|
|
|
11874
12109
|
let value;
|
|
11875
12110
|
try {
|
|
11876
12111
|
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(
|
|
12112
|
+
} catch (error11) {
|
|
12113
|
+
throw new MaterializedViewStoreError("initialize", "MV_VALUE_INVALID", `Materialized-view row JSON was invalid: ${String(error11)}`, { cause: error11 });
|
|
11879
12114
|
}
|
|
11880
12115
|
return {
|
|
11881
12116
|
serviceId: asString2(row.service_id, "service_id"),
|
|
@@ -12522,10 +12757,10 @@ var SerializedReadWorker = class {
|
|
|
12522
12757
|
});
|
|
12523
12758
|
}
|
|
12524
12759
|
async ensureWindowDeterminate() {
|
|
12525
|
-
if (this.storeProvider === void 0 || this.storeProvider.isConfigured?.(this.env) === false) {
|
|
12526
|
-
return;
|
|
12527
|
-
}
|
|
12528
12760
|
try {
|
|
12761
|
+
if (this.storeProvider === void 0 || this.storeProvider.isConfigured?.(this.env) === false) {
|
|
12762
|
+
return;
|
|
12763
|
+
}
|
|
12529
12764
|
const store = this.storeProvider.create(this.env);
|
|
12530
12765
|
await store.initialize();
|
|
12531
12766
|
const nowMs = Date.now();
|
|
@@ -12566,13 +12801,683 @@ async function handleSerializedRead(request, env, registry = DEPLOYED_PROJECTOR_
|
|
|
12566
12801
|
}), registry, storeProvider).handle(request);
|
|
12567
12802
|
}
|
|
12568
12803
|
|
|
12804
|
+
// src/completeness/IncidentLifecycle.ts
|
|
12805
|
+
var MAX_IDENTITY_BYTES = 256;
|
|
12806
|
+
var MAX_LONG_TEXT_BYTES = 2048;
|
|
12807
|
+
var SHA256_DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
12808
|
+
var IncidentLifecycleError = class extends Error {
|
|
12809
|
+
code;
|
|
12810
|
+
status;
|
|
12811
|
+
constructor(code, status, message) {
|
|
12812
|
+
super(message);
|
|
12813
|
+
this.name = "IncidentLifecycleError";
|
|
12814
|
+
this.code = code;
|
|
12815
|
+
this.status = status;
|
|
12816
|
+
}
|
|
12817
|
+
};
|
|
12818
|
+
function isObject11(value) {
|
|
12819
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12820
|
+
}
|
|
12821
|
+
function hasOnlyKeys(value, keys) {
|
|
12822
|
+
const allowed = new Set(keys);
|
|
12823
|
+
return Object.keys(value).every((key) => allowed.has(key));
|
|
12824
|
+
}
|
|
12825
|
+
function bytes(value) {
|
|
12826
|
+
return new TextEncoder().encode(value).byteLength;
|
|
12827
|
+
}
|
|
12828
|
+
function boundedString(value, name, maximum, required = true) {
|
|
12829
|
+
if (typeof value !== "string") {
|
|
12830
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, `${name} must be a string`);
|
|
12831
|
+
}
|
|
12832
|
+
const normalized = value.trim();
|
|
12833
|
+
if (required && normalized.length === 0) {
|
|
12834
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, `${name} must be non-empty`);
|
|
12835
|
+
}
|
|
12836
|
+
if (bytes(normalized) > maximum) {
|
|
12837
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, `${name} is too long`);
|
|
12838
|
+
}
|
|
12839
|
+
return normalized.length === 0 ? null : normalized;
|
|
12840
|
+
}
|
|
12841
|
+
function normalizeIncidentIdentity(value) {
|
|
12842
|
+
const identity2 = boundedString(value, "incidentIdentity", MAX_IDENTITY_BYTES);
|
|
12843
|
+
if (identity2 === null) {
|
|
12844
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "incidentIdentity must be non-empty");
|
|
12845
|
+
}
|
|
12846
|
+
return identity2;
|
|
12847
|
+
}
|
|
12848
|
+
function safeInteger(value, name) {
|
|
12849
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value)) {
|
|
12850
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, `${name} must be a safe integer`);
|
|
12851
|
+
}
|
|
12852
|
+
return value;
|
|
12853
|
+
}
|
|
12854
|
+
function normalizeCorrection(value) {
|
|
12855
|
+
if (!isObject11(value) || !hasOnlyKeys(value, ["kind", "reference", "digest"])) {
|
|
12856
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "correction has unknown or missing keys");
|
|
12857
|
+
}
|
|
12858
|
+
if (value.kind !== "event" && value.kind !== "receipt") {
|
|
12859
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "correction.kind must be event or receipt");
|
|
12860
|
+
}
|
|
12861
|
+
const reference = boundedString(value.reference, "correction.reference", MAX_LONG_TEXT_BYTES);
|
|
12862
|
+
const digest2 = boundedString(value.digest, "correction.digest", MAX_IDENTITY_BYTES);
|
|
12863
|
+
if (reference === null || digest2 === null || !SHA256_DIGEST.test(digest2)) {
|
|
12864
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "correction.digest must be lowercase sha256");
|
|
12865
|
+
}
|
|
12866
|
+
return { kind: value.kind, reference, digest: digest2 };
|
|
12867
|
+
}
|
|
12868
|
+
function normalizeRequest(value) {
|
|
12869
|
+
if (!isObject11(value) || !hasOnlyKeys(value, ["action", "incidentIdentity", "transitionKey", "expectedVersion", "reason", "ownerId", "deadlineAt", "correction", "resolution"])) {
|
|
12870
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "transition has unknown keys");
|
|
12871
|
+
}
|
|
12872
|
+
const action = value.action;
|
|
12873
|
+
if (typeof action !== "string" || !["ACKNOWLEDGE", "UPDATE_ASSIGNMENT", "RECORD_CORRECTION", "CLOSE", "REOPEN"].includes(action)) {
|
|
12874
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "transition action is invalid");
|
|
12875
|
+
}
|
|
12876
|
+
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"];
|
|
12877
|
+
if (!hasOnlyKeys(value, actionKeys)) {
|
|
12878
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "transition has keys not valid for its action");
|
|
12879
|
+
}
|
|
12880
|
+
const base = {
|
|
12881
|
+
incidentIdentity: normalizeIncidentIdentity(value.incidentIdentity),
|
|
12882
|
+
transitionKey: boundedString(value.transitionKey, "transitionKey", MAX_IDENTITY_BYTES),
|
|
12883
|
+
expectedVersion: safeInteger(value.expectedVersion, "expectedVersion"),
|
|
12884
|
+
reason: boundedString(value.reason, "reason", MAX_LONG_TEXT_BYTES)
|
|
12885
|
+
};
|
|
12886
|
+
if (base.expectedVersion < 0) {
|
|
12887
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "expectedVersion must not be negative");
|
|
12888
|
+
}
|
|
12889
|
+
if (action === "ACKNOWLEDGE" || action === "UPDATE_ASSIGNMENT" || action === "REOPEN") {
|
|
12890
|
+
const ownerId = boundedString(value.ownerId, "ownerId", MAX_IDENTITY_BYTES);
|
|
12891
|
+
const deadlineAt = safeInteger(value.deadlineAt, "deadlineAt");
|
|
12892
|
+
if (ownerId === null) throw new IncidentLifecycleError("incident_invalid_request", 400, "ownerId must be non-empty");
|
|
12893
|
+
return { ...base, action, ownerId, deadlineAt };
|
|
12894
|
+
}
|
|
12895
|
+
if (action === "RECORD_CORRECTION") {
|
|
12896
|
+
return { ...base, action, correction: normalizeCorrection(value.correction) };
|
|
12897
|
+
}
|
|
12898
|
+
if (!isObject11(value.resolution) || !hasOnlyKeys(value.resolution, ["kind", "explanation"])) {
|
|
12899
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "resolution has unknown or missing keys");
|
|
12900
|
+
}
|
|
12901
|
+
if (value.resolution.kind === "CORRECTED") {
|
|
12902
|
+
if (value.resolution.explanation !== void 0) {
|
|
12903
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "CORRECTED resolution has unknown keys");
|
|
12904
|
+
}
|
|
12905
|
+
return { ...base, action, resolution: { kind: "CORRECTED" } };
|
|
12906
|
+
}
|
|
12907
|
+
if (value.resolution.kind === "ACCEPTED_AS_IS") {
|
|
12908
|
+
const explanation = boundedString(value.resolution.explanation, "resolution.explanation", MAX_LONG_TEXT_BYTES);
|
|
12909
|
+
if (explanation === null) throw new IncidentLifecycleError("incident_invalid_request", 400, "resolution.explanation is required");
|
|
12910
|
+
return { ...base, action, resolution: { kind: "ACCEPTED_AS_IS", explanation } };
|
|
12911
|
+
}
|
|
12912
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "resolution.kind is invalid");
|
|
12913
|
+
}
|
|
12914
|
+
function canonicalize(value) {
|
|
12915
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
12916
|
+
if (!isObject11(value)) return value;
|
|
12917
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]));
|
|
12918
|
+
}
|
|
12919
|
+
async function digestFor(request, actorId) {
|
|
12920
|
+
const envelope = canonicalize({ action: request, actor: actorId });
|
|
12921
|
+
const encoded = new TextEncoder().encode(JSON.stringify(envelope));
|
|
12922
|
+
const digest2 = await crypto.subtle.digest("SHA-256", encoded);
|
|
12923
|
+
return `sha256:${Array.from(new Uint8Array(digest2), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
12924
|
+
}
|
|
12925
|
+
function projectionFromRow(row, finding2) {
|
|
12926
|
+
if (row === null) {
|
|
12927
|
+
return {
|
|
12928
|
+
serviceId: finding2.service_id,
|
|
12929
|
+
incidentIdentity: finding2.incident_identity,
|
|
12930
|
+
lifecycleState: "OPEN",
|
|
12931
|
+
ownerId: null,
|
|
12932
|
+
deadlineAt: null,
|
|
12933
|
+
correction: null,
|
|
12934
|
+
closeResolution: null,
|
|
12935
|
+
closeReason: null,
|
|
12936
|
+
version: 0,
|
|
12937
|
+
lastTransitionKey: "",
|
|
12938
|
+
updatedAt: finding2.last_observed_at
|
|
12939
|
+
};
|
|
12940
|
+
}
|
|
12941
|
+
return {
|
|
12942
|
+
serviceId: row.service_id,
|
|
12943
|
+
incidentIdentity: row.incident_identity,
|
|
12944
|
+
lifecycleState: row.lifecycle_state,
|
|
12945
|
+
ownerId: row.owner_id,
|
|
12946
|
+
deadlineAt: row.deadline_at,
|
|
12947
|
+
correction: row.correction_kind === null ? null : {
|
|
12948
|
+
kind: row.correction_kind,
|
|
12949
|
+
reference: row.correction_reference,
|
|
12950
|
+
digest: row.correction_digest
|
|
12951
|
+
},
|
|
12952
|
+
closeResolution: row.close_resolution,
|
|
12953
|
+
closeReason: row.close_reason,
|
|
12954
|
+
version: row.version,
|
|
12955
|
+
lastTransitionKey: row.last_transition_key,
|
|
12956
|
+
updatedAt: row.updated_at
|
|
12957
|
+
};
|
|
12958
|
+
}
|
|
12959
|
+
function findingFromRow(row) {
|
|
12960
|
+
return {
|
|
12961
|
+
serviceId: row.service_id,
|
|
12962
|
+
incidentIdentity: row.incident_identity,
|
|
12963
|
+
incidentType: row.incident_type,
|
|
12964
|
+
partitionTag: row.partition_tag,
|
|
12965
|
+
obligationSequence: row.obligation_sequence,
|
|
12966
|
+
eventId: row.event_id,
|
|
12967
|
+
eventDigest: row.event_digest,
|
|
12968
|
+
state: row.finding_state,
|
|
12969
|
+
firstObservedAt: row.first_observed_at,
|
|
12970
|
+
lastObservedAt: row.last_observed_at
|
|
12971
|
+
};
|
|
12972
|
+
}
|
|
12973
|
+
function transitionFromRow(row) {
|
|
12974
|
+
const before = projectionFromAudit(row, "before");
|
|
12975
|
+
const after = projectionFromAudit(row, "after");
|
|
12976
|
+
return {
|
|
12977
|
+
transitionId: row.transition_id,
|
|
12978
|
+
serviceId: row.service_id,
|
|
12979
|
+
incidentIdentity: row.incident_identity,
|
|
12980
|
+
transitionKey: row.transition_key,
|
|
12981
|
+
requestDigest: row.request_digest,
|
|
12982
|
+
action: row.action,
|
|
12983
|
+
fromState: row.from_state,
|
|
12984
|
+
toState: row.to_state,
|
|
12985
|
+
fromVersion: row.from_version,
|
|
12986
|
+
toVersion: row.to_version,
|
|
12987
|
+
actorId: row.actor_id,
|
|
12988
|
+
before,
|
|
12989
|
+
after,
|
|
12990
|
+
reason: row.reason,
|
|
12991
|
+
occurredAt: row.occurred_at
|
|
12992
|
+
};
|
|
12993
|
+
}
|
|
12994
|
+
function projectionFromAudit(row, prefix) {
|
|
12995
|
+
const state = prefix === "before" ? row.from_state : row.to_state;
|
|
12996
|
+
const version = prefix === "before" ? row.from_version : row.to_version;
|
|
12997
|
+
const ownerId = row[`${prefix}_owner_id`];
|
|
12998
|
+
const correctionKind = row[`${prefix}_correction_kind`];
|
|
12999
|
+
return {
|
|
13000
|
+
serviceId: row.service_id,
|
|
13001
|
+
incidentIdentity: row.incident_identity,
|
|
13002
|
+
lifecycleState: state,
|
|
13003
|
+
ownerId,
|
|
13004
|
+
deadlineAt: row[`${prefix}_deadline_at`],
|
|
13005
|
+
correction: correctionKind === null ? null : {
|
|
13006
|
+
kind: correctionKind,
|
|
13007
|
+
reference: row[`${prefix}_correction_reference`],
|
|
13008
|
+
digest: row[`${prefix}_correction_digest`]
|
|
13009
|
+
},
|
|
13010
|
+
closeResolution: row[`${prefix}_close_resolution`],
|
|
13011
|
+
closeReason: row[`${prefix}_close_reason`],
|
|
13012
|
+
version,
|
|
13013
|
+
lastTransitionKey: prefix === "after" ? row.transition_key : "",
|
|
13014
|
+
updatedAt: row.occurred_at
|
|
13015
|
+
};
|
|
13016
|
+
}
|
|
13017
|
+
function active(state) {
|
|
13018
|
+
return state !== "CLOSED";
|
|
13019
|
+
}
|
|
13020
|
+
function compareItems(left, right) {
|
|
13021
|
+
if (left.overdue !== right.overdue) return left.overdue ? -1 : 1;
|
|
13022
|
+
if (left.unowned !== right.unowned) return left.unowned ? -1 : 1;
|
|
13023
|
+
if (left.finding.lastObservedAt !== right.finding.lastObservedAt) return right.finding.lastObservedAt - left.finding.lastObservedAt;
|
|
13024
|
+
return left.finding.incidentIdentity < right.finding.incidentIdentity ? -1 : left.finding.incidentIdentity > right.finding.incidentIdentity ? 1 : 0;
|
|
13025
|
+
}
|
|
13026
|
+
var IncidentLifecycle = class {
|
|
13027
|
+
constructor(database, now2 = () => Date.now()) {
|
|
13028
|
+
this.database = database;
|
|
13029
|
+
this.now = now2;
|
|
13030
|
+
}
|
|
13031
|
+
database;
|
|
13032
|
+
now;
|
|
13033
|
+
static parseTransition(value) {
|
|
13034
|
+
return normalizeRequest(value);
|
|
13035
|
+
}
|
|
13036
|
+
async finding(serviceId, incidentIdentity) {
|
|
13037
|
+
const row = await this.database.prepare(
|
|
13038
|
+
`SELECT service_id, incident_identity, incident_type, partition_tag,
|
|
13039
|
+
obligation_sequence, event_id, event_digest, state AS finding_state,
|
|
13040
|
+
first_observed_at, last_observed_at
|
|
13041
|
+
FROM serialized_dcb_completeness_findings
|
|
13042
|
+
WHERE service_id = ? AND incident_identity = ?`
|
|
13043
|
+
).bind(serviceId, incidentIdentity).first();
|
|
13044
|
+
if (row === null || row === void 0) {
|
|
13045
|
+
throw new IncidentLifecycleError("incident_not_found", 404, "The completeness incident was not found");
|
|
13046
|
+
}
|
|
13047
|
+
return row;
|
|
13048
|
+
}
|
|
13049
|
+
async lifecycle(serviceId, incidentIdentity) {
|
|
13050
|
+
const row = await this.database.prepare(
|
|
13051
|
+
`SELECT service_id, incident_identity, lifecycle_state, owner_id, deadline_at,
|
|
13052
|
+
correction_kind, correction_reference, correction_digest,
|
|
13053
|
+
close_resolution, close_reason, version, last_transition_key, updated_at
|
|
13054
|
+
FROM serialized_dcb_incident_lifecycles
|
|
13055
|
+
WHERE service_id = ? AND incident_identity = ?`
|
|
13056
|
+
).bind(serviceId, incidentIdentity).first();
|
|
13057
|
+
return row === void 0 ? null : row;
|
|
13058
|
+
}
|
|
13059
|
+
async transitions(serviceId, incidentIdentity) {
|
|
13060
|
+
const result2 = await this.database.prepare(
|
|
13061
|
+
`SELECT * FROM serialized_dcb_incident_transitions
|
|
13062
|
+
WHERE service_id = ? AND incident_identity = ?
|
|
13063
|
+
ORDER BY transition_id ASC`
|
|
13064
|
+
).bind(serviceId, incidentIdentity).all();
|
|
13065
|
+
return result2.results.map(transitionFromRow);
|
|
13066
|
+
}
|
|
13067
|
+
async closeTime(serviceId, incidentIdentity) {
|
|
13068
|
+
const row = await this.database.prepare(
|
|
13069
|
+
`SELECT occurred_at FROM serialized_dcb_incident_transitions
|
|
13070
|
+
WHERE service_id = ? AND incident_identity = ? AND to_state = 'CLOSED'
|
|
13071
|
+
ORDER BY transition_id DESC LIMIT 1`
|
|
13072
|
+
).bind(serviceId, incidentIdentity).first();
|
|
13073
|
+
return row?.occurred_at ?? null;
|
|
13074
|
+
}
|
|
13075
|
+
async item(finding2, row, at) {
|
|
13076
|
+
const lifecycle = projectionFromRow(row, finding2);
|
|
13077
|
+
const closeTime = lifecycle.lifecycleState === "OPEN" && row === null ? null : await this.closeTime(finding2.service_id, finding2.incident_identity);
|
|
13078
|
+
const observedAfterClose = closeTime !== null && finding2.last_observed_at > closeTime;
|
|
13079
|
+
const overdue = active(lifecycle.lifecycleState) && lifecycle.deadlineAt !== null && lifecycle.deadlineAt < at;
|
|
13080
|
+
return {
|
|
13081
|
+
finding: findingFromRow(finding2),
|
|
13082
|
+
lifecycle,
|
|
13083
|
+
overdue,
|
|
13084
|
+
unowned: lifecycle.ownerId === null,
|
|
13085
|
+
observedAfterClose
|
|
13086
|
+
};
|
|
13087
|
+
}
|
|
13088
|
+
async list(serviceId, filters = {}, at = this.now()) {
|
|
13089
|
+
const rows = await this.database.prepare(
|
|
13090
|
+
`SELECT service_id, incident_identity, incident_type, partition_tag,
|
|
13091
|
+
obligation_sequence, event_id, event_digest, state AS finding_state,
|
|
13092
|
+
first_observed_at, last_observed_at
|
|
13093
|
+
FROM serialized_dcb_completeness_findings
|
|
13094
|
+
WHERE service_id = ?`
|
|
13095
|
+
).bind(serviceId).all();
|
|
13096
|
+
const items = [];
|
|
13097
|
+
for (const finding2 of rows.results) {
|
|
13098
|
+
const item = await this.item(finding2, await this.lifecycle(serviceId, finding2.incident_identity), at);
|
|
13099
|
+
if (filters.state !== void 0 && item.lifecycle.lifecycleState !== filters.state) continue;
|
|
13100
|
+
if (filters.owner !== void 0 && item.lifecycle.ownerId !== filters.owner) continue;
|
|
13101
|
+
if (filters.unowned === true && !item.unowned) continue;
|
|
13102
|
+
if (filters.overdue === true && !item.overdue) continue;
|
|
13103
|
+
if (filters.observedAfterClose === true && !item.observedAfterClose) continue;
|
|
13104
|
+
items.push(item);
|
|
13105
|
+
}
|
|
13106
|
+
items.sort(compareItems);
|
|
13107
|
+
const byState = {
|
|
13108
|
+
OPEN: 0,
|
|
13109
|
+
ACKNOWLEDGED: 0,
|
|
13110
|
+
CORRECTION_RECORDED: 0,
|
|
13111
|
+
CLOSED: 0,
|
|
13112
|
+
REOPENED: 0
|
|
13113
|
+
};
|
|
13114
|
+
for (const item of items) byState[item.lifecycle.lifecycleState] += 1;
|
|
13115
|
+
return {
|
|
13116
|
+
items,
|
|
13117
|
+
summary: {
|
|
13118
|
+
total: items.length,
|
|
13119
|
+
byState,
|
|
13120
|
+
unowned: items.filter((item) => item.unowned).length,
|
|
13121
|
+
overdue: items.filter((item) => item.overdue).length,
|
|
13122
|
+
observedAfterClose: items.filter((item) => item.observedAfterClose).length
|
|
13123
|
+
}
|
|
13124
|
+
};
|
|
13125
|
+
}
|
|
13126
|
+
async detail(serviceId, incidentIdentity, at = this.now()) {
|
|
13127
|
+
const finding2 = await this.finding(serviceId, incidentIdentity);
|
|
13128
|
+
const lifecycle = await this.lifecycle(serviceId, incidentIdentity);
|
|
13129
|
+
const item = await this.item(finding2, lifecycle, at);
|
|
13130
|
+
return {
|
|
13131
|
+
finding: item.finding,
|
|
13132
|
+
lifecycle: item.lifecycle,
|
|
13133
|
+
flags: { overdue: item.overdue, unowned: item.unowned, observedAfterClose: item.observedAfterClose },
|
|
13134
|
+
transitions: await this.transitions(serviceId, incidentIdentity)
|
|
13135
|
+
};
|
|
13136
|
+
}
|
|
13137
|
+
async transition(serviceId, value, actor) {
|
|
13138
|
+
const request = normalizeRequest(value);
|
|
13139
|
+
let actorId;
|
|
13140
|
+
try {
|
|
13141
|
+
actorId = boundedString(actor, "x-sdt-maintainer", MAX_IDENTITY_BYTES);
|
|
13142
|
+
} catch (caught) {
|
|
13143
|
+
if (caught instanceof IncidentLifecycleError && caught.code === "incident_invalid_request") {
|
|
13144
|
+
throw new IncidentLifecycleError("incident_invalid_actor", 400, caught.message);
|
|
13145
|
+
}
|
|
13146
|
+
throw caught;
|
|
13147
|
+
}
|
|
13148
|
+
if (actorId === null) throw new IncidentLifecycleError("incident_invalid_actor", 400, "x-sdt-maintainer must be non-empty");
|
|
13149
|
+
const finding2 = await this.finding(serviceId, request.incidentIdentity);
|
|
13150
|
+
const digest2 = await digestFor(request, actorId);
|
|
13151
|
+
const existing = await this.database.prepare(
|
|
13152
|
+
`SELECT * FROM serialized_dcb_incident_transitions
|
|
13153
|
+
WHERE service_id = ? AND incident_identity = ? AND transition_key = ?`
|
|
13154
|
+
).bind(serviceId, request.incidentIdentity, request.transitionKey).first();
|
|
13155
|
+
if (existing !== null && existing !== void 0) {
|
|
13156
|
+
if (existing.request_digest !== digest2) {
|
|
13157
|
+
throw new IncidentLifecycleError("incident_idempotency_conflict", 409, "The transition key was already used with different content");
|
|
13158
|
+
}
|
|
13159
|
+
return { projection: transitionFromRow(existing).after, transition: transitionFromRow(existing), idempotent: true };
|
|
13160
|
+
}
|
|
13161
|
+
const priorRow = await this.lifecycle(serviceId, request.incidentIdentity);
|
|
13162
|
+
const before = projectionFromRow(priorRow, finding2);
|
|
13163
|
+
if (before.version !== request.expectedVersion) {
|
|
13164
|
+
throw new IncidentLifecycleError("incident_version_conflict", 409, "The incident version is stale");
|
|
13165
|
+
}
|
|
13166
|
+
const at = this.now();
|
|
13167
|
+
const after = this.nextProjection(before, request, at);
|
|
13168
|
+
const newVersion = before.version + 1;
|
|
13169
|
+
const occurredAt = at;
|
|
13170
|
+
const seed = this.database.prepare(
|
|
13171
|
+
`INSERT INTO serialized_dcb_incident_lifecycles
|
|
13172
|
+
(service_id, incident_identity, lifecycle_state, version, last_transition_key, updated_at)
|
|
13173
|
+
VALUES (?, ?, 'OPEN', 0, '', ?)
|
|
13174
|
+
ON CONFLICT (service_id, incident_identity) DO NOTHING`
|
|
13175
|
+
).bind(serviceId, request.incidentIdentity, occurredAt);
|
|
13176
|
+
const update = this.database.prepare(
|
|
13177
|
+
`UPDATE serialized_dcb_incident_lifecycles
|
|
13178
|
+
SET lifecycle_state = ?, owner_id = ?, deadline_at = ?,
|
|
13179
|
+
correction_kind = ?, correction_reference = ?, correction_digest = ?,
|
|
13180
|
+
close_resolution = ?, close_reason = ?, version = ?,
|
|
13181
|
+
last_transition_key = ?, updated_at = ?
|
|
13182
|
+
WHERE service_id = ? AND incident_identity = ? AND version = ?
|
|
13183
|
+
AND last_transition_key <> ?`
|
|
13184
|
+
).bind(
|
|
13185
|
+
after.lifecycleState,
|
|
13186
|
+
after.ownerId,
|
|
13187
|
+
after.deadlineAt,
|
|
13188
|
+
after.correction?.kind ?? null,
|
|
13189
|
+
after.correction?.reference ?? null,
|
|
13190
|
+
after.correction?.digest ?? null,
|
|
13191
|
+
after.closeResolution,
|
|
13192
|
+
after.closeReason,
|
|
13193
|
+
newVersion,
|
|
13194
|
+
request.transitionKey,
|
|
13195
|
+
occurredAt,
|
|
13196
|
+
serviceId,
|
|
13197
|
+
request.incidentIdentity,
|
|
13198
|
+
before.version,
|
|
13199
|
+
request.transitionKey
|
|
13200
|
+
);
|
|
13201
|
+
const audit = this.database.prepare(
|
|
13202
|
+
`INSERT INTO serialized_dcb_incident_transitions
|
|
13203
|
+
(service_id, incident_identity, transition_key, request_digest, action,
|
|
13204
|
+
from_state, to_state, from_version, to_version, actor_id,
|
|
13205
|
+
before_owner_id, before_deadline_at, before_correction_kind,
|
|
13206
|
+
before_correction_reference, before_correction_digest, before_close_resolution,
|
|
13207
|
+
before_close_reason, after_owner_id, after_deadline_at, after_correction_kind,
|
|
13208
|
+
after_correction_reference, after_correction_digest, after_close_resolution,
|
|
13209
|
+
after_close_reason, reason, occurred_at)
|
|
13210
|
+
SELECT ?, ?, ?, ?, ?, ?, lifecycle_state, ?, version, ?,
|
|
13211
|
+
?, ?, ?, ?, ?, ?, ?, owner_id, deadline_at, correction_kind,
|
|
13212
|
+
correction_reference, correction_digest, close_resolution, close_reason, ?, ?
|
|
13213
|
+
FROM serialized_dcb_incident_lifecycles
|
|
13214
|
+
WHERE service_id = ? AND incident_identity = ? AND version = ? AND last_transition_key = ?`
|
|
13215
|
+
).bind(
|
|
13216
|
+
serviceId,
|
|
13217
|
+
request.incidentIdentity,
|
|
13218
|
+
request.transitionKey,
|
|
13219
|
+
digest2,
|
|
13220
|
+
request.action,
|
|
13221
|
+
before.lifecycleState,
|
|
13222
|
+
before.version,
|
|
13223
|
+
actorId,
|
|
13224
|
+
before.ownerId,
|
|
13225
|
+
before.deadlineAt,
|
|
13226
|
+
before.correction?.kind ?? null,
|
|
13227
|
+
before.correction?.reference ?? null,
|
|
13228
|
+
before.correction?.digest ?? null,
|
|
13229
|
+
before.closeResolution,
|
|
13230
|
+
before.closeReason,
|
|
13231
|
+
request.reason,
|
|
13232
|
+
occurredAt,
|
|
13233
|
+
serviceId,
|
|
13234
|
+
request.incidentIdentity,
|
|
13235
|
+
newVersion,
|
|
13236
|
+
request.transitionKey
|
|
13237
|
+
);
|
|
13238
|
+
await this.database.batch([seed, update, audit]);
|
|
13239
|
+
const inserted = await this.database.prepare(
|
|
13240
|
+
`SELECT * FROM serialized_dcb_incident_transitions
|
|
13241
|
+
WHERE service_id = ? AND incident_identity = ? AND transition_key = ?`
|
|
13242
|
+
).bind(serviceId, request.incidentIdentity, request.transitionKey).first();
|
|
13243
|
+
if (inserted === null || inserted === void 0) {
|
|
13244
|
+
const competing = await this.database.prepare(
|
|
13245
|
+
`SELECT * FROM serialized_dcb_incident_transitions
|
|
13246
|
+
WHERE service_id = ? AND incident_identity = ? AND transition_key = ?`
|
|
13247
|
+
).bind(serviceId, request.incidentIdentity, request.transitionKey).first();
|
|
13248
|
+
if (competing !== null && competing !== void 0 && competing.request_digest !== digest2) {
|
|
13249
|
+
throw new IncidentLifecycleError("incident_idempotency_conflict", 409, "The transition key was already used with different content");
|
|
13250
|
+
}
|
|
13251
|
+
if (competing !== null && competing !== void 0) {
|
|
13252
|
+
const transition2 = transitionFromRow(competing);
|
|
13253
|
+
return { projection: transition2.after, transition: transition2, idempotent: true };
|
|
13254
|
+
}
|
|
13255
|
+
throw new IncidentLifecycleError("incident_version_conflict", 409, "The incident version is stale");
|
|
13256
|
+
}
|
|
13257
|
+
const transition = transitionFromRow(inserted);
|
|
13258
|
+
return { projection: transition.after, transition, idempotent: false };
|
|
13259
|
+
}
|
|
13260
|
+
nextProjection(before, request, at) {
|
|
13261
|
+
const laterDeadline = (deadlineAt2) => {
|
|
13262
|
+
if (deadlineAt2 <= at) throw new IncidentLifecycleError("incident_invalid_deadline", 400, "deadlineAt must be in the future");
|
|
13263
|
+
return deadlineAt2;
|
|
13264
|
+
};
|
|
13265
|
+
let lifecycleState;
|
|
13266
|
+
let ownerId = before.ownerId;
|
|
13267
|
+
let deadlineAt = before.deadlineAt;
|
|
13268
|
+
let correction = before.correction;
|
|
13269
|
+
let closeResolution = before.closeResolution;
|
|
13270
|
+
let closeReason = before.closeReason;
|
|
13271
|
+
if (request.action === "ACKNOWLEDGE") {
|
|
13272
|
+
if (before.lifecycleState !== "OPEN" && before.lifecycleState !== "REOPENED") {
|
|
13273
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "The incident cannot be acknowledged from its current state");
|
|
13274
|
+
}
|
|
13275
|
+
lifecycleState = "ACKNOWLEDGED";
|
|
13276
|
+
ownerId = request.ownerId;
|
|
13277
|
+
deadlineAt = laterDeadline(request.deadlineAt);
|
|
13278
|
+
correction = null;
|
|
13279
|
+
closeResolution = null;
|
|
13280
|
+
closeReason = null;
|
|
13281
|
+
} else if (request.action === "UPDATE_ASSIGNMENT") {
|
|
13282
|
+
if (before.lifecycleState !== "ACKNOWLEDGED" && before.lifecycleState !== "CORRECTION_RECORDED") {
|
|
13283
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "The incident cannot be reassigned from its current state");
|
|
13284
|
+
}
|
|
13285
|
+
lifecycleState = before.lifecycleState;
|
|
13286
|
+
ownerId = request.ownerId;
|
|
13287
|
+
deadlineAt = laterDeadline(request.deadlineAt);
|
|
13288
|
+
} else if (request.action === "RECORD_CORRECTION") {
|
|
13289
|
+
if (before.lifecycleState !== "ACKNOWLEDGED") {
|
|
13290
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "The incident must be acknowledged before correction evidence");
|
|
13291
|
+
}
|
|
13292
|
+
lifecycleState = "CORRECTION_RECORDED";
|
|
13293
|
+
correction = request.correction;
|
|
13294
|
+
} else if (request.action === "CLOSE") {
|
|
13295
|
+
if (before.ownerId === null || before.deadlineAt === null) {
|
|
13296
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "The incident must have an owner and deadline before closure");
|
|
13297
|
+
}
|
|
13298
|
+
if (request.resolution.kind === "CORRECTED") {
|
|
13299
|
+
if (before.lifecycleState !== "CORRECTION_RECORDED") {
|
|
13300
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "A corrected closure requires recorded correction evidence");
|
|
13301
|
+
}
|
|
13302
|
+
lifecycleState = "CLOSED";
|
|
13303
|
+
closeResolution = "CORRECTED";
|
|
13304
|
+
closeReason = request.reason;
|
|
13305
|
+
} else {
|
|
13306
|
+
if (before.lifecycleState !== "ACKNOWLEDGED") {
|
|
13307
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "Accepted-as-is closure requires an acknowledged incident");
|
|
13308
|
+
}
|
|
13309
|
+
lifecycleState = "CLOSED";
|
|
13310
|
+
closeResolution = "ACCEPTED_AS_IS";
|
|
13311
|
+
closeReason = request.resolution.explanation;
|
|
13312
|
+
correction = null;
|
|
13313
|
+
}
|
|
13314
|
+
} else {
|
|
13315
|
+
if (before.lifecycleState !== "CLOSED") {
|
|
13316
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "Only a closed incident can be reopened");
|
|
13317
|
+
}
|
|
13318
|
+
lifecycleState = "REOPENED";
|
|
13319
|
+
ownerId = request.ownerId;
|
|
13320
|
+
deadlineAt = laterDeadline(request.deadlineAt);
|
|
13321
|
+
correction = null;
|
|
13322
|
+
closeResolution = null;
|
|
13323
|
+
closeReason = null;
|
|
13324
|
+
}
|
|
13325
|
+
return {
|
|
13326
|
+
...before,
|
|
13327
|
+
lifecycleState,
|
|
13328
|
+
ownerId,
|
|
13329
|
+
deadlineAt,
|
|
13330
|
+
correction,
|
|
13331
|
+
closeResolution,
|
|
13332
|
+
closeReason,
|
|
13333
|
+
version: before.version + 1,
|
|
13334
|
+
lastTransitionKey: request.transitionKey,
|
|
13335
|
+
updatedAt: at
|
|
13336
|
+
};
|
|
13337
|
+
}
|
|
13338
|
+
};
|
|
13339
|
+
|
|
13340
|
+
// src/http/IncidentMaintenance.ts
|
|
13341
|
+
function json10(body, status = 200) {
|
|
13342
|
+
return new Response(JSON.stringify(body), {
|
|
13343
|
+
status,
|
|
13344
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
13345
|
+
});
|
|
13346
|
+
}
|
|
13347
|
+
function error8(status, code, message) {
|
|
13348
|
+
return json10({ error: message, code }, status);
|
|
13349
|
+
}
|
|
13350
|
+
function configuredToken(value) {
|
|
13351
|
+
if (typeof value !== "string") return void 0;
|
|
13352
|
+
const token = value.trim();
|
|
13353
|
+
return token.length === 0 ? void 0 : token;
|
|
13354
|
+
}
|
|
13355
|
+
async function digest(value) {
|
|
13356
|
+
const bytes2 = new TextEncoder().encode(value);
|
|
13357
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", bytes2));
|
|
13358
|
+
}
|
|
13359
|
+
function fixedLengthEqual(left, right) {
|
|
13360
|
+
let difference = left.length ^ right.length;
|
|
13361
|
+
const length = Math.max(left.length, right.length);
|
|
13362
|
+
for (let index = 0; index < length; index += 1) {
|
|
13363
|
+
difference |= (left[index] ?? 0) ^ (right[index] ?? 0);
|
|
13364
|
+
}
|
|
13365
|
+
return difference === 0;
|
|
13366
|
+
}
|
|
13367
|
+
async function authenticated(request, token) {
|
|
13368
|
+
const credential = request.headers.get("authorization") ?? "";
|
|
13369
|
+
const suppliedToken = credential.startsWith("Bearer ") ? credential.slice("Bearer ".length) : "";
|
|
13370
|
+
const [expectedDigest, suppliedDigest] = await Promise.all([digest(token), digest(suppliedToken)]);
|
|
13371
|
+
return fixedLengthEqual(expectedDigest, suppliedDigest);
|
|
13372
|
+
}
|
|
13373
|
+
function boolFilter(value, name) {
|
|
13374
|
+
if (value === null) return void 0;
|
|
13375
|
+
if (value === "true") return true;
|
|
13376
|
+
if (value === "false") return false;
|
|
13377
|
+
throw new IncidentLifecycleError("incident_invalid_filter", 400, `${name} must be true or false`);
|
|
13378
|
+
}
|
|
13379
|
+
function parseFilters(url) {
|
|
13380
|
+
const allowed = /* @__PURE__ */ new Set(["state", "owner", "unowned", "overdue", "observedAfterClose"]);
|
|
13381
|
+
let unknown = false;
|
|
13382
|
+
url.searchParams.forEach((_value, key) => {
|
|
13383
|
+
if (!allowed.has(key)) unknown = true;
|
|
13384
|
+
});
|
|
13385
|
+
if (unknown) throw new IncidentLifecycleError("incident_invalid_filter", 400, "Unknown incident list filter");
|
|
13386
|
+
const state = url.searchParams.get("state");
|
|
13387
|
+
if (state !== null && !["OPEN", "ACKNOWLEDGED", "CORRECTION_RECORDED", "CLOSED", "REOPENED"].includes(state)) {
|
|
13388
|
+
throw new IncidentLifecycleError("incident_invalid_filter", 400, "state is invalid");
|
|
13389
|
+
}
|
|
13390
|
+
const owner = url.searchParams.get("owner");
|
|
13391
|
+
if (owner !== null) {
|
|
13392
|
+
const normalizedOwner = owner.trim();
|
|
13393
|
+
if (normalizedOwner.length === 0) {
|
|
13394
|
+
throw new IncidentLifecycleError("incident_invalid_filter", 400, "owner must be non-empty");
|
|
13395
|
+
}
|
|
13396
|
+
if (new TextEncoder().encode(normalizedOwner).byteLength > 256) {
|
|
13397
|
+
throw new IncidentLifecycleError("incident_invalid_filter", 400, "owner is too long");
|
|
13398
|
+
}
|
|
13399
|
+
}
|
|
13400
|
+
const unowned = boolFilter(url.searchParams.get("unowned"), "unowned");
|
|
13401
|
+
const overdue = boolFilter(url.searchParams.get("overdue"), "overdue");
|
|
13402
|
+
const observedAfterClose = boolFilter(url.searchParams.get("observedAfterClose"), "observedAfterClose");
|
|
13403
|
+
return {
|
|
13404
|
+
...state === null ? {} : { state },
|
|
13405
|
+
...owner === null ? {} : { owner: owner.trim() },
|
|
13406
|
+
...unowned === void 0 ? {} : { unowned },
|
|
13407
|
+
...overdue === void 0 ? {} : { overdue },
|
|
13408
|
+
...observedAfterClose === void 0 ? {} : { observedAfterClose }
|
|
13409
|
+
};
|
|
13410
|
+
}
|
|
13411
|
+
function incidentIdentityFromPath(pathname) {
|
|
13412
|
+
const encoded = pathname.slice("/maintenance/incidents/".length);
|
|
13413
|
+
if (encoded.length === 0 || encoded.includes("/")) {
|
|
13414
|
+
throw new IncidentLifecycleError("incident_route_not_found", 404, "Incident route not found");
|
|
13415
|
+
}
|
|
13416
|
+
try {
|
|
13417
|
+
return normalizeIncidentIdentity(decodeURIComponent(encoded));
|
|
13418
|
+
} catch {
|
|
13419
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "Incident identity must be URI encoded");
|
|
13420
|
+
}
|
|
13421
|
+
}
|
|
13422
|
+
function isIncidentMaintenancePath(pathname) {
|
|
13423
|
+
return pathname === "/maintenance/incidents" || pathname.startsWith("/maintenance/incidents/");
|
|
13424
|
+
}
|
|
13425
|
+
async function handleIncidentMaintenance(request, env, serviceIdentityProvider) {
|
|
13426
|
+
const token = configuredToken(env.INCIDENT_MAINTAINER_TOKEN);
|
|
13427
|
+
if (token === void 0) {
|
|
13428
|
+
return error8(503, "incident_maintenance_unavailable", "Incident maintenance is not configured");
|
|
13429
|
+
}
|
|
13430
|
+
if (!await authenticated(request, token)) {
|
|
13431
|
+
return error8(401, "incident_auth_required", "A valid incident maintenance bearer token is required");
|
|
13432
|
+
}
|
|
13433
|
+
let serviceId;
|
|
13434
|
+
try {
|
|
13435
|
+
serviceId = requestServiceIdentity(request, serviceIdentityProvider, {
|
|
13436
|
+
allowG11Verification: env.G11_VERIFICATION_ENABLED === "true"
|
|
13437
|
+
});
|
|
13438
|
+
} catch {
|
|
13439
|
+
return error8(503, "scope.identity_missing", "A deployment service identity is required for this scope");
|
|
13440
|
+
}
|
|
13441
|
+
if (env.D1 === void 0) {
|
|
13442
|
+
return error8(503, "incident_maintenance_unavailable", "Incident maintenance storage is unavailable");
|
|
13443
|
+
}
|
|
13444
|
+
const lifecycle = new IncidentLifecycle(env.D1);
|
|
13445
|
+
const url = new URL(request.url);
|
|
13446
|
+
try {
|
|
13447
|
+
if (request.method === "GET" && url.pathname === "/maintenance/incidents") {
|
|
13448
|
+
return json10(await lifecycle.list(serviceId, parseFilters(url)));
|
|
13449
|
+
}
|
|
13450
|
+
if (request.method === "GET" && url.pathname.startsWith("/maintenance/incidents/")) {
|
|
13451
|
+
return json10(await lifecycle.detail(serviceId, incidentIdentityFromPath(url.pathname)));
|
|
13452
|
+
}
|
|
13453
|
+
if (request.method === "POST" && url.pathname === "/maintenance/incidents/transitions") {
|
|
13454
|
+
let body;
|
|
13455
|
+
try {
|
|
13456
|
+
body = await request.json();
|
|
13457
|
+
} catch {
|
|
13458
|
+
return error8(400, "incident_invalid_request", "Transition request must be JSON");
|
|
13459
|
+
}
|
|
13460
|
+
const requestValue = IncidentLifecycle.parseTransition(body);
|
|
13461
|
+
const actor = request.headers.get("x-sdt-maintainer");
|
|
13462
|
+
if (actor === null) {
|
|
13463
|
+
return error8(400, "incident_invalid_actor", "x-sdt-maintainer is required");
|
|
13464
|
+
}
|
|
13465
|
+
return json10(await lifecycle.transition(serviceId, requestValue, actor));
|
|
13466
|
+
}
|
|
13467
|
+
return error8(404, "incident_route_not_found", "Incident maintenance route not found");
|
|
13468
|
+
} catch (caught) {
|
|
13469
|
+
if (caught instanceof IncidentLifecycleError) return error8(caught.status, caught.code, caught.message);
|
|
13470
|
+
return error8(500, "incident_maintenance_failed", "Incident maintenance could not complete the request");
|
|
13471
|
+
}
|
|
13472
|
+
}
|
|
13473
|
+
|
|
12569
13474
|
// src/downstream/Doorbell.ts
|
|
12570
13475
|
var MAX_SERVICE_BINDING_INVOCATIONS_PER_REQUEST = 32;
|
|
12571
|
-
function classifyDirectDoorbellFailure(
|
|
12572
|
-
const structured = typeof
|
|
13476
|
+
function classifyDirectDoorbellFailure(error11) {
|
|
13477
|
+
const structured = typeof error11 === "object" && error11 !== null ? error11 : {};
|
|
12573
13478
|
const status = typeof structured.status === "number" ? structured.status : typeof structured.statusCode === "number" ? structured.statusCode : void 0;
|
|
12574
13479
|
if (status !== void 0 && (status < 200 || status >= 300)) return "non-2xx";
|
|
12575
|
-
const value = String(
|
|
13480
|
+
const value = String(error11).toLowerCase();
|
|
12576
13481
|
const code = `${String(structured.code ?? "")} ${String(structured.name ?? "")}`.toLowerCase();
|
|
12577
13482
|
if (value.includes("timeout") || value.includes("deadline") || code.includes("timeout")) return "timeout";
|
|
12578
13483
|
if (value.includes("cancel") || value.includes("abort") || code.includes("abort")) return "cancel";
|
|
@@ -12815,6 +13720,7 @@ var TAG_SQL_SCHEMA_DDL = `
|
|
|
12815
13720
|
committed_event_count INTEGER NOT NULL,
|
|
12816
13721
|
head_suid TEXT NOT NULL,
|
|
12817
13722
|
reservation_confirmed INTEGER NOT NULL CHECK (reservation_confirmed IN (0, 1)),
|
|
13723
|
+
written_version INTEGER NOT NULL,
|
|
12818
13724
|
PRIMARY KEY (attempt_id, epoch)
|
|
12819
13725
|
);
|
|
12820
13726
|
|
|
@@ -12865,6 +13771,12 @@ var TAG_READ_AFTER_THROUGH_SQL = `
|
|
|
12865
13771
|
`;
|
|
12866
13772
|
function initializeTagSqlSchema(sql) {
|
|
12867
13773
|
sql.exec(TAG_SQL_SCHEMA_DDL);
|
|
13774
|
+
ensureTagCommitReceiptWrittenVersion(sql);
|
|
13775
|
+
}
|
|
13776
|
+
function ensureTagCommitReceiptWrittenVersion(sql) {
|
|
13777
|
+
const columns = sql.exec("SELECT name FROM pragma_table_info('tag_commit_receipt')").toArray();
|
|
13778
|
+
if (columns.some((column) => column.name === "written_version")) return;
|
|
13779
|
+
sql.exec("ALTER TABLE tag_commit_receipt ADD COLUMN written_version INTEGER");
|
|
12868
13780
|
}
|
|
12869
13781
|
function hasTagSqlStorage(storage) {
|
|
12870
13782
|
return typeof storage.sql?.exec === "function";
|
|
@@ -13042,8 +13954,8 @@ function asNumber2(value, name) {
|
|
|
13042
13954
|
if (!Number.isSafeInteger(number)) throw new Error(`D1 ${name} was not a safe integer`);
|
|
13043
13955
|
return number;
|
|
13044
13956
|
}
|
|
13045
|
-
function errorText3(
|
|
13046
|
-
return (
|
|
13957
|
+
function errorText3(error11) {
|
|
13958
|
+
return (error11 instanceof Error ? error11.message : String(error11)).slice(0, 256);
|
|
13047
13959
|
}
|
|
13048
13960
|
function sortedUnique2(values) {
|
|
13049
13961
|
return [...new Set(values)].sort((left, right) => binaryCompare(left, right));
|
|
@@ -13230,8 +14142,8 @@ var D1EventStore = class {
|
|
|
13230
14142
|
let failure3;
|
|
13231
14143
|
try {
|
|
13232
14144
|
result2 = await this.recordDeliveryCore(message, arrivedAt, deliverySource, mutationAttemptId);
|
|
13233
|
-
} catch (
|
|
13234
|
-
failure3 = { error:
|
|
14145
|
+
} catch (error11) {
|
|
14146
|
+
failure3 = { error: error11 };
|
|
13235
14147
|
}
|
|
13236
14148
|
const retryReason = failure3 === void 0 ? null : errorText3(failure3.error);
|
|
13237
14149
|
const suppressDiagnostic = failure3?.error instanceof D1IdentityConflictError && failure3.error.beforeMutation;
|
|
@@ -13802,8 +14714,8 @@ var D1EventStore = class {
|
|
|
13802
14714
|
if (replay === null || replay === void 0) return finish({ kind: "miss" });
|
|
13803
14715
|
const event = await this.eventById(serviceId, asString3(replay.event_id, "replay.event_id"));
|
|
13804
14716
|
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(
|
|
14717
|
+
} catch (error11) {
|
|
14718
|
+
return finish({ kind: "unknown", reason: errorText3(error11) });
|
|
13807
14719
|
}
|
|
13808
14720
|
}
|
|
13809
14721
|
/**
|
|
@@ -14278,8 +15190,8 @@ var D1EventStore = class {
|
|
|
14278
15190
|
try {
|
|
14279
15191
|
try {
|
|
14280
15192
|
await this.options.beforeG69AdmissionAttempt?.();
|
|
14281
|
-
} catch (
|
|
14282
|
-
await appendFailedDiagnostic(errorText3(
|
|
15193
|
+
} catch (error11) {
|
|
15194
|
+
await appendFailedDiagnostic(errorText3(error11));
|
|
14283
15195
|
return;
|
|
14284
15196
|
}
|
|
14285
15197
|
const afterObservedAt = Date.now();
|
|
@@ -14353,14 +15265,14 @@ var TagIdentityConflict = class extends Error {
|
|
|
14353
15265
|
this.name = "TagIdentityConflict";
|
|
14354
15266
|
}
|
|
14355
15267
|
};
|
|
14356
|
-
function
|
|
15268
|
+
function json11(body, status = 200) {
|
|
14357
15269
|
return new Response(JSON.stringify(body), {
|
|
14358
15270
|
status,
|
|
14359
15271
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
14360
15272
|
});
|
|
14361
15273
|
}
|
|
14362
|
-
function
|
|
14363
|
-
return
|
|
15274
|
+
function error9(status, code, message, retryable = false) {
|
|
15275
|
+
return json11(retryable ? { error: message, code, retryable: true } : { error: message, code }, status);
|
|
14364
15276
|
}
|
|
14365
15277
|
function withGlobalAdmission2(response3, status) {
|
|
14366
15278
|
response3.headers.set(G65_GLOBAL_ADMISSION_HEADER, status);
|
|
@@ -14379,7 +15291,7 @@ function repairRejected(reason, status = 409) {
|
|
|
14379
15291
|
body: { code: "repair_operation_rejected", error: reason, reason }
|
|
14380
15292
|
};
|
|
14381
15293
|
}
|
|
14382
|
-
function
|
|
15294
|
+
function isObject12(value) {
|
|
14383
15295
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14384
15296
|
}
|
|
14385
15297
|
function isNonEmptyString11(value) {
|
|
@@ -14424,8 +15336,8 @@ function sqlJson(value, column) {
|
|
|
14424
15336
|
throw new Error(`Tag SQL column ${column} is not valid JSON`);
|
|
14425
15337
|
}
|
|
14426
15338
|
}
|
|
14427
|
-
function copyArrayBuffer(
|
|
14428
|
-
return
|
|
15339
|
+
function copyArrayBuffer(bytes2) {
|
|
15340
|
+
return bytes2.buffer.slice(bytes2.byteOffset, bytes2.byteOffset + bytes2.byteLength);
|
|
14429
15341
|
}
|
|
14430
15342
|
function arrayBufferBase642(value) {
|
|
14431
15343
|
let binary = "";
|
|
@@ -14436,7 +15348,7 @@ function nowIso() {
|
|
|
14436
15348
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
14437
15349
|
}
|
|
14438
15350
|
function epochFrom(value) {
|
|
14439
|
-
if (!
|
|
15351
|
+
if (!isObject12(value) || !isNonEmptyString11(value.attemptId) || !isEpoch(value.epoch)) {
|
|
14440
15352
|
return { error: "attemptId and a non-negative safe-integer epoch are required" };
|
|
14441
15353
|
}
|
|
14442
15354
|
return { value: { attemptId: value.attemptId, epoch: value.epoch } };
|
|
@@ -14456,7 +15368,7 @@ function consistencyTagsFrom(value) {
|
|
|
14456
15368
|
}
|
|
14457
15369
|
const entries = [];
|
|
14458
15370
|
for (const rawEntry of value) {
|
|
14459
|
-
if (!
|
|
15371
|
+
if (!isObject12(rawEntry) || !isNonEmptyString11(rawEntry.tag)) {
|
|
14460
15372
|
return { error: "each consistency tag needs a non-empty tag" };
|
|
14461
15373
|
}
|
|
14462
15374
|
if (!("lastSortableUniqueId" in rawEntry)) {
|
|
@@ -14484,7 +15396,7 @@ function consistencyTagsFrom(value) {
|
|
|
14484
15396
|
}
|
|
14485
15397
|
function acquireFrom(value, tag) {
|
|
14486
15398
|
const epoch2 = epochFrom(value);
|
|
14487
|
-
if (epoch2.value === void 0 || !
|
|
15399
|
+
if (epoch2.value === void 0 || !isObject12(value)) {
|
|
14488
15400
|
return { error: epoch2.error ?? "Invalid acquire request" };
|
|
14489
15401
|
}
|
|
14490
15402
|
const eventTags = stringArrayFrom(value.eventTags, "eventTags");
|
|
@@ -14507,7 +15419,7 @@ function acquireFrom(value, tag) {
|
|
|
14507
15419
|
}
|
|
14508
15420
|
function reservationFrom(value) {
|
|
14509
15421
|
const epoch2 = epochFrom(value);
|
|
14510
|
-
if (epoch2.value === void 0 || !
|
|
15422
|
+
if (epoch2.value === void 0 || !isObject12(value)) {
|
|
14511
15423
|
return { error: epoch2.error ?? "Invalid reservation request" };
|
|
14512
15424
|
}
|
|
14513
15425
|
if (value.reservationToken !== void 0 && !isNonEmptyString11(value.reservationToken)) {
|
|
@@ -14526,7 +15438,7 @@ function reservationFrom(value) {
|
|
|
14526
15438
|
}
|
|
14527
15439
|
function appendFrom(value, tag) {
|
|
14528
15440
|
const reservation = reservationFrom(value);
|
|
14529
|
-
if (reservation.value === void 0 || !
|
|
15441
|
+
if (reservation.value === void 0 || !isObject12(value) || !Array.isArray(value.candidates)) {
|
|
14530
15442
|
return { error: reservation.error ?? "candidates must be an array" };
|
|
14531
15443
|
}
|
|
14532
15444
|
if (value.candidates.length === 0) {
|
|
@@ -14534,7 +15446,7 @@ function appendFrom(value, tag) {
|
|
|
14534
15446
|
}
|
|
14535
15447
|
const candidates = [];
|
|
14536
15448
|
for (const rawCandidate of value.candidates) {
|
|
14537
|
-
if (!
|
|
15449
|
+
if (!isObject12(rawCandidate) || !isNonEmptyString11(rawCandidate.eventId) || !isNonEmptyString11(rawCandidate.suid) || typeof rawCandidate.payload !== "string") {
|
|
14538
15450
|
return { error: "each candidate needs eventId, suid, and payload" };
|
|
14539
15451
|
}
|
|
14540
15452
|
const eventTags = stringArrayFrom(rawCandidate.eventTags, "candidate eventTags");
|
|
@@ -14548,8 +15460,8 @@ function appendFrom(value, tag) {
|
|
|
14548
15460
|
assertSortableUniqueId(rawCandidate.suid);
|
|
14549
15461
|
if (!isUuidV7(rawCandidate.eventId)) return { error: "candidate eventId must be a UUID v7" };
|
|
14550
15462
|
JSON.parse(rawCandidate.payload);
|
|
14551
|
-
} catch (
|
|
14552
|
-
return { error:
|
|
15463
|
+
} catch (error11) {
|
|
15464
|
+
return { error: error11 instanceof Error ? error11.message : "candidate identity or payload is invalid" };
|
|
14553
15465
|
}
|
|
14554
15466
|
if (rawCandidate.provenance !== "g32") return { error: "candidate provenance must be g32" };
|
|
14555
15467
|
if (!isNonEmptyString11(rawCandidate.allocatorLineageId)) return { error: "candidate allocatorLineageId is required" };
|
|
@@ -14583,10 +15495,10 @@ function appendFrom(value, tag) {
|
|
|
14583
15495
|
return { value: { ...reservation.value, candidates: ordered, faultInjection } };
|
|
14584
15496
|
}
|
|
14585
15497
|
function bootstrapAppendFrom(value, tag) {
|
|
14586
|
-
if (!
|
|
15498
|
+
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
15499
|
const candidates = [];
|
|
14588
15500
|
for (const candidate of value.candidates) {
|
|
14589
|
-
if (!
|
|
15501
|
+
if (!isObject12(candidate) || !isNonEmptyString11(candidate.eventId) || !isNonEmptyString11(candidate.suid) || typeof candidate.payload !== "string") return { error: "bootstrap candidate is invalid" };
|
|
14590
15502
|
const tags = stringArrayFrom(candidate.eventTags, "bootstrap candidate eventTags");
|
|
14591
15503
|
if (tags.value === void 0 || !tags.value.includes(tag) || !isNonEmptyString11(candidate.allocatorLineageId)) return { error: "bootstrap candidate must include this tag and lineage" };
|
|
14592
15504
|
let eventType;
|
|
@@ -14596,8 +15508,8 @@ function bootstrapAppendFrom(value, tag) {
|
|
|
14596
15508
|
assertSortableUniqueId(candidate.suid);
|
|
14597
15509
|
if (!isRfc4122Uuid(candidate.eventId)) return { error: "bootstrap candidate eventId must be an RFC 4122 UUID" };
|
|
14598
15510
|
JSON.parse(candidate.payload);
|
|
14599
|
-
} catch (
|
|
14600
|
-
return { error:
|
|
15511
|
+
} catch (error11) {
|
|
15512
|
+
return { error: error11 instanceof Error ? error11.message : "bootstrap candidate identity or payload is invalid" };
|
|
14601
15513
|
}
|
|
14602
15514
|
if (candidate.provenance !== "g32") return { error: "bootstrap candidate provenance must be g32" };
|
|
14603
15515
|
if (!isNonEmptyString11(candidate.timestamp) || !CANONICAL_UTC_TIMESTAMP_PATTERN.test(candidate.timestamp)) return { error: "bootstrap candidate timestamp must be canonical UTC" };
|
|
@@ -14609,13 +15521,13 @@ function bootstrapAppendFrom(value, tag) {
|
|
|
14609
15521
|
}
|
|
14610
15522
|
function fenceFrom(value) {
|
|
14611
15523
|
const epoch2 = epochFrom(value);
|
|
14612
|
-
if (epoch2.value === void 0 || !
|
|
15524
|
+
if (epoch2.value === void 0 || !isObject12(value) || !isNonEmptyString11(value.reason)) {
|
|
14613
15525
|
return { error: epoch2.error ?? "reason is required" };
|
|
14614
15526
|
}
|
|
14615
15527
|
return { value: { ...epoch2.value, reason: value.reason } };
|
|
14616
15528
|
}
|
|
14617
15529
|
function repairScopeItemFrom(value, tag) {
|
|
14618
|
-
if (!
|
|
15530
|
+
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
15531
|
return { error: "each repair scope item needs attemptId, eventId, suid, and payload" };
|
|
14620
15532
|
}
|
|
14621
15533
|
const eventTags = stringArrayFrom(value.eventTags, "repair eventTags");
|
|
@@ -14663,13 +15575,13 @@ function repairScopeFrom(value, tag) {
|
|
|
14663
15575
|
return { value: sortRepairScope(scope) };
|
|
14664
15576
|
}
|
|
14665
15577
|
function repairLeaseFrom(value) {
|
|
14666
|
-
if (!
|
|
15578
|
+
if (!isObject12(value) || !isNonEmptyString11(value.owner) || !isEpoch(value.epoch)) {
|
|
14667
15579
|
return { error: "repair owner and non-negative safe-integer epoch are required" };
|
|
14668
15580
|
}
|
|
14669
15581
|
return { value: { owner: value.owner, epoch: value.epoch } };
|
|
14670
15582
|
}
|
|
14671
15583
|
function repairAcquireFrom(value, tag) {
|
|
14672
|
-
if (!
|
|
15584
|
+
if (!isObject12(value) || !isNonEmptyString11(value.owner)) {
|
|
14673
15585
|
return { error: "repair owner is required" };
|
|
14674
15586
|
}
|
|
14675
15587
|
const leaseMs = value.leaseMs ?? DEFAULT_REPAIR_LEASE_MS;
|
|
@@ -14681,7 +15593,7 @@ function repairAcquireFrom(value, tag) {
|
|
|
14681
15593
|
}
|
|
14682
15594
|
function repairScopeUnionFrom(value, tag) {
|
|
14683
15595
|
const lease = repairLeaseFrom(value);
|
|
14684
|
-
if (lease.value === void 0 || !
|
|
15596
|
+
if (lease.value === void 0 || !isObject12(value)) {
|
|
14685
15597
|
return { error: lease.error };
|
|
14686
15598
|
}
|
|
14687
15599
|
const scope = repairScopeFrom(value.scope, tag);
|
|
@@ -14689,7 +15601,7 @@ function repairScopeUnionFrom(value, tag) {
|
|
|
14689
15601
|
}
|
|
14690
15602
|
function repairApplyFrom(value, tag) {
|
|
14691
15603
|
const lease = repairLeaseFrom(value);
|
|
14692
|
-
if (lease.value === void 0 || !
|
|
15604
|
+
if (lease.value === void 0 || !isObject12(value)) {
|
|
14693
15605
|
return { error: lease.error };
|
|
14694
15606
|
}
|
|
14695
15607
|
const item = repairScopeItemFrom(value.item, tag);
|
|
@@ -14697,20 +15609,20 @@ function repairApplyFrom(value, tag) {
|
|
|
14697
15609
|
}
|
|
14698
15610
|
function repairAuditFrom(value, tag) {
|
|
14699
15611
|
const apply = repairApplyFrom(value, tag);
|
|
14700
|
-
if (apply.value === void 0 || !
|
|
15612
|
+
if (apply.value === void 0 || !isObject12(value) || !isNonEmptyString11(value.actor)) {
|
|
14701
15613
|
return { error: apply.error ?? "audit actor is required" };
|
|
14702
15614
|
}
|
|
14703
15615
|
return { value: { ...apply.value, actor: value.actor } };
|
|
14704
15616
|
}
|
|
14705
15617
|
function repairClearFrom(value) {
|
|
14706
15618
|
const lease = repairLeaseFrom(value);
|
|
14707
|
-
if (lease.value === void 0 || !
|
|
15619
|
+
if (lease.value === void 0 || !isObject12(value) || !isNonEmptyString11(value.attemptId) || !isNonNegativeInteger4(value.scopeVersion)) {
|
|
14708
15620
|
return { error: lease.error ?? "attemptId and scopeVersion are required" };
|
|
14709
15621
|
}
|
|
14710
15622
|
return { value: { ...lease.value, attemptId: value.attemptId, scopeVersion: value.scopeVersion } };
|
|
14711
15623
|
}
|
|
14712
15624
|
function outboxPendingFrom(value) {
|
|
14713
|
-
if (!
|
|
15625
|
+
if (!isObject12(value)) {
|
|
14714
15626
|
return { error: "outbox pending request must be an object" };
|
|
14715
15627
|
}
|
|
14716
15628
|
const nowMs = value.nowMs ?? Date.now();
|
|
@@ -14728,22 +15640,22 @@ function outboxPendingFrom(value) {
|
|
|
14728
15640
|
return { value: { nowMs, limit, force } };
|
|
14729
15641
|
}
|
|
14730
15642
|
function outboxMarkFrom(value) {
|
|
14731
|
-
if (!
|
|
15643
|
+
if (!isObject12(value) || !Array.isArray(value.deliveries) || value.deliveries.length === 0) {
|
|
14732
15644
|
return { error: "deliveries must be a non-empty array" };
|
|
14733
15645
|
}
|
|
14734
15646
|
const deliveries = [];
|
|
14735
15647
|
for (const raw of value.deliveries) {
|
|
14736
|
-
if (!
|
|
15648
|
+
if (!isObject12(raw) || !isNonEmptyString11(raw.attemptId) || !isNonEmptyString11(raw.eventId) || !isNonEmptyString11(raw.suid) || typeof raw.payload !== "string" || !isSafeInteger(raw.enqueuedAt)) {
|
|
14737
15649
|
return { error: "each delivery needs attemptId, eventId, suid, payload, and enqueuedAt" };
|
|
14738
15650
|
}
|
|
14739
15651
|
if (!isNonEmptyString11(raw.eventType) || raw.provenance !== "g32" || !isNonEmptyString11(raw.timestamp) || !isNonEmptyString11(raw.allocatorLineageId)) return { error: "delivery must carry the complete G32 identity" };
|
|
14740
|
-
if (!
|
|
15652
|
+
if (!isObject12(raw.completeness)) {
|
|
14741
15653
|
return { error: "delivery must carry a readable global receipt/membership join proof" };
|
|
14742
15654
|
}
|
|
14743
15655
|
const completeness = raw.completeness;
|
|
14744
15656
|
const obligationSequence = completeness.obligationSequence;
|
|
14745
15657
|
const memberships = completeness.localCommittedMembership;
|
|
14746
|
-
if (!isNonEmptyString11(completeness.eventDigest) || !isSafeInteger(obligationSequence) || obligationSequence < 1 || !Array.isArray(memberships) || memberships.length !== 1 || !
|
|
15658
|
+
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
15659
|
return { error: "delivery must carry a readable global receipt/membership join proof" };
|
|
14748
15660
|
}
|
|
14749
15661
|
const membership = memberships[0];
|
|
@@ -14782,7 +15694,7 @@ function outboxMarkFrom(value) {
|
|
|
14782
15694
|
return { value: { deliveries, nowMs } };
|
|
14783
15695
|
}
|
|
14784
15696
|
function g44SourceScanFrom(value) {
|
|
14785
|
-
if (!
|
|
15697
|
+
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
15698
|
return { error: "source scan needs serviceId, tag, and a bounded compound cursor" };
|
|
14787
15699
|
}
|
|
14788
15700
|
return {
|
|
@@ -14796,7 +15708,7 @@ function g44SourceScanFrom(value) {
|
|
|
14796
15708
|
};
|
|
14797
15709
|
}
|
|
14798
15710
|
function g46TagStateSourceFrom(value) {
|
|
14799
|
-
if (!
|
|
15711
|
+
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
15712
|
return { error: "G46 source read needs tag, cursor, optional through, and a bounded limit" };
|
|
14801
15713
|
}
|
|
14802
15714
|
try {
|
|
@@ -14970,8 +15882,8 @@ function logicalNow(record) {
|
|
|
14970
15882
|
return record.clockNowMs ?? Date.now() + record.clockOffsetMs;
|
|
14971
15883
|
}
|
|
14972
15884
|
function expireReservation(record) {
|
|
14973
|
-
const
|
|
14974
|
-
if (
|
|
15885
|
+
const active2 = record.activeReservation;
|
|
15886
|
+
if (active2 === null || active2.expiresAt > logicalNow(record)) {
|
|
14975
15887
|
return { record, expired: false };
|
|
14976
15888
|
}
|
|
14977
15889
|
return {
|
|
@@ -15089,7 +16001,7 @@ var TagDurableObject = class {
|
|
|
15089
16001
|
const url = new URL(request.url);
|
|
15090
16002
|
const tag = url.searchParams.get("__tag");
|
|
15091
16003
|
if (!isNonEmptyString11(tag)) {
|
|
15092
|
-
return
|
|
16004
|
+
return error9(400, "tag_identity_required", "Tag identity is required");
|
|
15093
16005
|
}
|
|
15094
16006
|
const serviceId = url.searchParams.get("__serviceId");
|
|
15095
16007
|
if (request.method === "GET" && url.pathname === "/head-facts") {
|
|
@@ -15097,10 +16009,10 @@ var TagDurableObject = class {
|
|
|
15097
16009
|
observation.markFirstStorageRead();
|
|
15098
16010
|
try {
|
|
15099
16011
|
const facts = this.readHeadFacts(tag);
|
|
15100
|
-
return facts === void 0 ?
|
|
16012
|
+
return facts === void 0 ? error9(404, "tag_not_found", "Tag has no durable state yet") : json11(facts);
|
|
15101
16013
|
} catch (caught) {
|
|
15102
16014
|
if (caught instanceof TagIdentityConflict) {
|
|
15103
|
-
return
|
|
16015
|
+
return error9(409, "tag_identity_conflict", "Tag Durable Object identity changed");
|
|
15104
16016
|
}
|
|
15105
16017
|
throw caught;
|
|
15106
16018
|
}
|
|
@@ -15111,9 +16023,9 @@ var TagDurableObject = class {
|
|
|
15111
16023
|
observation.markFirstStorageRead();
|
|
15112
16024
|
const record = this.readStoredRecord(tag);
|
|
15113
16025
|
if (record === void 0) {
|
|
15114
|
-
return
|
|
16026
|
+
return error9(404, "tag_not_found", "Tag has no durable state yet");
|
|
15115
16027
|
}
|
|
15116
|
-
return record.tag === tag ?
|
|
16028
|
+
return record.tag === tag ? json11(requireG32TagRecord(record)) : error9(409, "tag_identity_conflict", "Tag Durable Object identity changed");
|
|
15117
16029
|
});
|
|
15118
16030
|
}
|
|
15119
16031
|
if (request.method === "GET" && url.pathname === "/repair/facts") {
|
|
@@ -15121,7 +16033,7 @@ var TagDurableObject = class {
|
|
|
15121
16033
|
}
|
|
15122
16034
|
if (request.method === "POST" && url.pathname === "/debug/alarm") {
|
|
15123
16035
|
const record = await this.runAlarm();
|
|
15124
|
-
return record === void 0 ?
|
|
16036
|
+
return record === void 0 ? error9(404, "tag_not_found", "Tag has no durable state yet") : json11(record);
|
|
15125
16037
|
}
|
|
15126
16038
|
if (request.method === "POST" && url.pathname === "/acquire") {
|
|
15127
16039
|
return this.traceCommitActor(request, tag, serviceId, activation, observation, (body2) => this.acquire(tag, body2, observation));
|
|
@@ -15144,37 +16056,37 @@ var TagDurableObject = class {
|
|
|
15144
16056
|
}
|
|
15145
16057
|
const body = await this.jsonBody(request);
|
|
15146
16058
|
if (body === void 0) {
|
|
15147
|
-
return
|
|
16059
|
+
return error9(400, "malformed_tag_request", "Request body must be JSON");
|
|
15148
16060
|
}
|
|
15149
16061
|
if (request.method === "POST" && url.pathname === "/__internal/g46/tag-state-incremental") {
|
|
15150
16062
|
if (request.headers.get("x-sdt-g46-source-read") !== "1") {
|
|
15151
|
-
return
|
|
16063
|
+
return error9(403, "g46_source_read_forbidden", "G46 source reads require the TagState adapter");
|
|
15152
16064
|
}
|
|
15153
16065
|
const parsed = g46TagStateSourceFrom(body);
|
|
15154
16066
|
if (parsed.value === void 0 || parsed.value.tag !== tag) {
|
|
15155
|
-
return
|
|
16067
|
+
return error9(400, "g46_source_read_invalid", parsed.error ?? "G46 source read is invalid");
|
|
15156
16068
|
}
|
|
15157
16069
|
try {
|
|
15158
|
-
return
|
|
16070
|
+
return json11(await this.g43TagStateIncrementalCatchUp(parsed.value));
|
|
15159
16071
|
} catch (caught) {
|
|
15160
16072
|
if (caught instanceof TagIdentityConflict) {
|
|
15161
|
-
return
|
|
16073
|
+
return error9(409, "tag_identity_conflict", "Tag Durable Object identity changed");
|
|
15162
16074
|
}
|
|
15163
|
-
return
|
|
16075
|
+
return error9(503, "g46_source_read_unavailable", caught instanceof Error ? caught.message : "G46 source read is unavailable");
|
|
15164
16076
|
}
|
|
15165
16077
|
}
|
|
15166
16078
|
if (request.method === "POST" && url.pathname === "/__internal/g44/source-obligations") {
|
|
15167
16079
|
if (request.headers.get("x-sdt-g44-source-scan") !== "1") {
|
|
15168
|
-
return
|
|
16080
|
+
return error9(403, "g44_source_scan_forbidden", "G44 source scan requires the internal scanner seam");
|
|
15169
16081
|
}
|
|
15170
16082
|
const parsed = g44SourceScanFrom(body);
|
|
15171
16083
|
if (parsed.value === void 0 || parsed.value.tag !== tag || parsed.value.serviceId !== serviceId) {
|
|
15172
|
-
return
|
|
16084
|
+
return error9(400, "g44_source_scan_invalid", parsed.error ?? "G44 source scan identity is invalid");
|
|
15173
16085
|
}
|
|
15174
16086
|
try {
|
|
15175
|
-
return
|
|
16087
|
+
return json11(await this.g44ReadSourceObligations(parsed.value));
|
|
15176
16088
|
} catch (failure3) {
|
|
15177
|
-
return
|
|
16089
|
+
return error9(503, "g44_source_scan_unavailable", failure3 instanceof Error ? failure3.message : "G44 source scan is unavailable");
|
|
15178
16090
|
}
|
|
15179
16091
|
}
|
|
15180
16092
|
if (request.method === "POST" && url.pathname === "/bootstrap/admit") {
|
|
@@ -15221,7 +16133,7 @@ var TagDurableObject = class {
|
|
|
15221
16133
|
}
|
|
15222
16134
|
if (request.method === "POST" && url.pathname === "/__internal/g77/inspect-target") {
|
|
15223
16135
|
if (!isNonEmptyString11(serviceId)) {
|
|
15224
|
-
return
|
|
16136
|
+
return error9(400, "service_identity_required", "Service identity is required");
|
|
15225
16137
|
}
|
|
15226
16138
|
return this.inspectG77Target(serviceId, tag, body);
|
|
15227
16139
|
}
|
|
@@ -15229,28 +16141,28 @@ var TagDurableObject = class {
|
|
|
15229
16141
|
return this.fenceAbsentG77Target(tag, body);
|
|
15230
16142
|
}
|
|
15231
16143
|
if (request.method === "POST" && url.pathname === "/__internal/g77/capabilities") {
|
|
15232
|
-
return
|
|
16144
|
+
return json11({ inspectTarget: true, fenceAbsentTarget: true });
|
|
15233
16145
|
}
|
|
15234
|
-
return
|
|
16146
|
+
return error9(404, "tag_route_not_found", "Tag route was not found");
|
|
15235
16147
|
}
|
|
15236
16148
|
/** Identity-bound installed/absent inspection for G77 issuance reconciliation. */
|
|
15237
16149
|
inspectG77Target(serviceId, tag, body) {
|
|
15238
|
-
if (!
|
|
15239
|
-
return
|
|
16150
|
+
if (!isObject12(body) || !isNonEmptyString11(body.eventId) || !isNonEmptyString11(body.suid)) {
|
|
16151
|
+
return error9(400, "invalid_inspect_target", "eventId and suid are required");
|
|
15240
16152
|
}
|
|
15241
16153
|
const pinnedWriterEpoch = isNonNegativeInteger4(body.pinnedWriterEpoch) ? body.pinnedWriterEpoch : 0;
|
|
15242
16154
|
const attemptId = isNonEmptyString11(body.attemptId) ? body.attemptId : void 0;
|
|
15243
16155
|
const sql = this.sqlStorage();
|
|
15244
|
-
if (sql === void 0) return
|
|
16156
|
+
if (sql === void 0) return error9(503, "tag_sql_unavailable", "Tag SQL storage is unavailable");
|
|
15245
16157
|
const identity2 = sql.exec("SELECT tag FROM tag_identity WHERE singleton = 1").toArray()[0];
|
|
15246
16158
|
if (identity2 === void 0) {
|
|
15247
16159
|
if (attemptId !== void 0) {
|
|
15248
|
-
return
|
|
16160
|
+
return json11({ inspectionStatus: "absent-never-contacted" });
|
|
15249
16161
|
}
|
|
15250
|
-
return
|
|
16162
|
+
return error9(409, "target_not_terminal", "Target is not terminal under pinned writer authority");
|
|
15251
16163
|
}
|
|
15252
16164
|
if (sqlString(identity2.tag, "tag_identity.tag") !== tag) {
|
|
15253
|
-
return
|
|
16165
|
+
return error9(409, "tag_identity_mismatch", "Tag identity does not match the inspected tag");
|
|
15254
16166
|
}
|
|
15255
16167
|
const event = sql.exec(`
|
|
15256
16168
|
SELECT attempt_id, event_id, suid FROM tag_event
|
|
@@ -15263,41 +16175,41 @@ var TagDurableObject = class {
|
|
|
15263
16175
|
`, serviceId, body.eventId).toArray()[0];
|
|
15264
16176
|
if (obligation !== void 0) {
|
|
15265
16177
|
const obligationDigest = sqlString(obligation.event_digest, "tag_outbox_obligation.event_digest");
|
|
15266
|
-
return
|
|
16178
|
+
return json11({
|
|
15267
16179
|
terminalStatus: "installed-and-covered",
|
|
15268
16180
|
eventId: body.eventId,
|
|
15269
16181
|
obligationDigest
|
|
15270
16182
|
});
|
|
15271
16183
|
}
|
|
15272
|
-
return
|
|
16184
|
+
return error9(409, "target_not_terminal", "Target append is in flight under pinned writer authority");
|
|
15273
16185
|
}
|
|
15274
16186
|
if (attemptId !== void 0) {
|
|
15275
16187
|
const tombstone = sql.exec("SELECT epoch FROM tag_tombstone WHERE attempt_id = ?", attemptId).toArray()[0];
|
|
15276
16188
|
const tombstoneEpoch = tombstone === void 0 ? void 0 : sqlNumber(tombstone.epoch, "tag_tombstone.epoch");
|
|
15277
16189
|
if (tombstoneEpoch !== void 0 && pinnedWriterEpoch <= tombstoneEpoch) {
|
|
15278
|
-
return
|
|
16190
|
+
return json11({ terminalStatus: "absent-and-irrevocably-fenced", tombstoneEpoch });
|
|
15279
16191
|
}
|
|
15280
16192
|
const reservation = sql.exec("SELECT attempt_id, expires_at FROM tag_reservation WHERE singleton = 1").toArray()[0];
|
|
15281
16193
|
if (reservation !== void 0 && sqlString(reservation.attempt_id, "tag_reservation.attempt_id") === attemptId) {
|
|
15282
|
-
return
|
|
16194
|
+
return error9(409, "target_not_terminal", "Active reservation holds writer authority");
|
|
15283
16195
|
}
|
|
15284
16196
|
const epoch2 = sql.exec("SELECT attempt_id FROM tag_epoch WHERE attempt_id = ?", attemptId).toArray()[0];
|
|
15285
16197
|
if (epoch2 !== void 0) {
|
|
15286
|
-
return
|
|
16198
|
+
return json11({ inspectionStatus: "absent-but-unfenced" });
|
|
15287
16199
|
}
|
|
15288
|
-
return
|
|
16200
|
+
return json11({ inspectionStatus: "absent-never-contacted" });
|
|
15289
16201
|
}
|
|
15290
|
-
return
|
|
16202
|
+
return error9(409, "target_not_terminal", "Target is not terminal under pinned writer authority");
|
|
15291
16203
|
}
|
|
15292
16204
|
/** Reconciler-only fence for never-contacted targets; creates durable tombstone state. */
|
|
15293
16205
|
async fenceAbsentG77Target(tag, body) {
|
|
15294
16206
|
const parsed = reservationFrom(body);
|
|
15295
16207
|
if (parsed.value === void 0) {
|
|
15296
|
-
return
|
|
16208
|
+
return error9(400, "invalid_fence_target", parsed.error ?? "attemptId and epoch are required");
|
|
15297
16209
|
}
|
|
15298
16210
|
this.ensureSqlTag(tag);
|
|
15299
16211
|
const result2 = await this.cancelSql(tag, { ...parsed.value, forceTombstone: true });
|
|
15300
|
-
return result2.status >= 200 && result2.status < 300 ?
|
|
16212
|
+
return result2.status >= 200 && result2.status < 300 ? json11(result2.body) : error9(result2.status, "fence_target_rejected", "Absent target fence was rejected");
|
|
15301
16213
|
}
|
|
15302
16214
|
async alarm() {
|
|
15303
16215
|
this.activation.beginHandler();
|
|
@@ -15644,21 +16556,21 @@ var TagDurableObject = class {
|
|
|
15644
16556
|
const result2 = await authority;
|
|
15645
16557
|
g44GlobalArrayAuthorityResultByD1.set(database, result2);
|
|
15646
16558
|
return result2;
|
|
15647
|
-
} catch (
|
|
16559
|
+
} catch (error11) {
|
|
15648
16560
|
if (g44GlobalArrayAuthorityByD1.get(database) === authority) {
|
|
15649
16561
|
g44GlobalArrayAuthorityByD1.delete(database);
|
|
15650
16562
|
}
|
|
15651
|
-
throw
|
|
16563
|
+
throw error11;
|
|
15652
16564
|
}
|
|
15653
16565
|
}
|
|
15654
16566
|
async readG44GlobalArrayAuthority(database) {
|
|
15655
16567
|
try {
|
|
15656
16568
|
await database.prepare('SELECT "EventDigest" FROM dcb_events WHERE 1 = 0').all();
|
|
15657
16569
|
return true;
|
|
15658
|
-
} catch (
|
|
15659
|
-
const message =
|
|
16570
|
+
} catch (error11) {
|
|
16571
|
+
const message = error11 instanceof Error ? error11.message : String(error11);
|
|
15660
16572
|
if (/no such (?:table|column):\s*(?:dcb_events|EventDigest)/i.test(message)) return false;
|
|
15661
|
-
throw
|
|
16573
|
+
throw error11;
|
|
15662
16574
|
}
|
|
15663
16575
|
}
|
|
15664
16576
|
async traceCommitActor(request, tag, serviceId, activation, observation, callback) {
|
|
@@ -15668,10 +16580,10 @@ var TagDurableObject = class {
|
|
|
15668
16580
|
{ actorClass: "TAG", actorKey: `tag:${serviceId}:${tag}`, activation, observation },
|
|
15669
16581
|
async () => {
|
|
15670
16582
|
body = await this.jsonBody(request);
|
|
15671
|
-
const attemptId =
|
|
16583
|
+
const attemptId = isObject12(body) && isNonEmptyString11(body.attemptId) ? body.attemptId : void 0;
|
|
15672
16584
|
return attemptId === void 0 || !isNonEmptyString11(serviceId) ? void 0 : { attemptId, serviceId };
|
|
15673
16585
|
},
|
|
15674
|
-
async () => body === void 0 ?
|
|
16586
|
+
async () => body === void 0 ? error9(400, "malformed_tag_request", "Request body must be JSON") : callback(body)
|
|
15675
16587
|
);
|
|
15676
16588
|
}
|
|
15677
16589
|
/**
|
|
@@ -15885,6 +16797,7 @@ var TagDurableObject = class {
|
|
|
15885
16797
|
ensureSqlTag(tag) {
|
|
15886
16798
|
const sql = this.sqlStorage();
|
|
15887
16799
|
if (sql === void 0) throw new Error("Tag SQL storage is unavailable");
|
|
16800
|
+
ensureTagCommitReceiptWrittenVersion(sql);
|
|
15888
16801
|
const existing = sql.exec("SELECT tag FROM tag_identity WHERE singleton = 1").toArray()[0];
|
|
15889
16802
|
if (existing !== void 0) {
|
|
15890
16803
|
if (sqlString(existing.tag, "tag_identity.tag") !== tag) throw new Error("Tag Durable Object identity changed");
|
|
@@ -15997,17 +16910,18 @@ var TagDurableObject = class {
|
|
|
15997
16910
|
sql.exec("UPDATE tag_control SET head_suid = ?, version = ?, updated_at = ? WHERE singleton = 1", head, version, committedAt);
|
|
15998
16911
|
sql.exec("UPDATE tag_head SET service_id = ?, head_suid = ? WHERE singleton = 1", serviceId, head);
|
|
15999
16912
|
}
|
|
16000
|
-
writeCommittedSqlReceipt(sql, input, committedAt, eventCount, head, confirmsReservation) {
|
|
16913
|
+
writeCommittedSqlReceipt(sql, input, committedAt, eventCount, head, confirmsReservation, writtenVersion) {
|
|
16001
16914
|
sql.exec(`
|
|
16002
16915
|
INSERT INTO tag_commit_receipt
|
|
16003
|
-
(attempt_id, epoch, committed_at, committed_event_count, head_suid, reservation_confirmed)
|
|
16004
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
16916
|
+
(attempt_id, epoch, committed_at, committed_event_count, head_suid, reservation_confirmed, written_version)
|
|
16917
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
16005
16918
|
ON CONFLICT(attempt_id, epoch) DO UPDATE SET
|
|
16006
16919
|
committed_at = excluded.committed_at,
|
|
16007
16920
|
committed_event_count = excluded.committed_event_count,
|
|
16008
16921
|
head_suid = excluded.head_suid,
|
|
16009
|
-
reservation_confirmed = excluded.reservation_confirmed
|
|
16010
|
-
|
|
16922
|
+
reservation_confirmed = excluded.reservation_confirmed,
|
|
16923
|
+
written_version = excluded.written_version
|
|
16924
|
+
`, input.attemptId, input.epoch, committedAt, eventCount, head, confirmsReservation ? 1 : 0, writtenVersion);
|
|
16011
16925
|
}
|
|
16012
16926
|
/**
|
|
16013
16927
|
* The normal append path deliberately probes only the indexed rows it needs
|
|
@@ -16049,11 +16963,29 @@ var TagDurableObject = class {
|
|
|
16049
16963
|
return { event, obligation, exact };
|
|
16050
16964
|
});
|
|
16051
16965
|
if (existingCandidates.every(({ exact }) => exact)) {
|
|
16052
|
-
|
|
16966
|
+
const receipts = sql.exec(`
|
|
16967
|
+
SELECT epoch, committed_at, written_version, head_suid FROM tag_commit_receipt
|
|
16968
|
+
WHERE attempt_id = ?
|
|
16969
|
+
`, input.attemptId).toArray();
|
|
16970
|
+
const sameEpoch = receipts.find((row) => sqlNumber(row.epoch, "tag_commit_receipt.epoch") === input.epoch);
|
|
16971
|
+
const replayHead = input.candidates.at(-1)?.suid;
|
|
16972
|
+
const byHead = replayHead === void 0 ? [] : receipts.filter((row) => sqlString(row.head_suid, "tag_commit_receipt.head_suid") === replayHead);
|
|
16973
|
+
const receipt = sameEpoch ?? (byHead.length === 1 ? byHead[0] : void 0) ?? (receipts.length === 1 ? receipts[0] : void 0);
|
|
16974
|
+
if (receipt === void 0) {
|
|
16975
|
+
await this.rearmScheduler(txn);
|
|
16976
|
+
return { ...rejected("duplicate_receipt_missing"), body: { ...rejected("duplicate_receipt_missing").body, version } };
|
|
16977
|
+
}
|
|
16978
|
+
const storedAt = sqlNullableString(receipt.committed_at, "tag_commit_receipt.committed_at");
|
|
16979
|
+
const storedVersion = sqlNullableNumber(receipt.written_version, "tag_commit_receipt.written_version");
|
|
16980
|
+
if (storedAt === null || storedVersion === null) {
|
|
16981
|
+
await this.rearmScheduler(txn);
|
|
16982
|
+
return { ...rejected("duplicate_receipt_unknown"), body: { ...rejected("duplicate_receipt_unknown").body, version } };
|
|
16983
|
+
}
|
|
16984
|
+
return { status: 200, body: { status: "duplicate", version: storedVersion, updatedAt: storedAt } };
|
|
16053
16985
|
}
|
|
16054
|
-
const
|
|
16986
|
+
const active2 = sql.exec("SELECT * FROM tag_reservation WHERE singleton = 1").toArray()[0];
|
|
16055
16987
|
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 =
|
|
16988
|
+
let reservation = active2;
|
|
16057
16989
|
if (reservation !== void 0 && sqlNumber(reservation.expires_at, "tag_reservation.expires_at") <= now2) {
|
|
16058
16990
|
sql.exec("DELETE FROM tag_reservation WHERE singleton = 1");
|
|
16059
16991
|
version += 1;
|
|
@@ -16146,7 +17078,7 @@ var TagDurableObject = class {
|
|
|
16146
17078
|
if (input.faultInjection === "after-append-before-confirm") {
|
|
16147
17079
|
throw new AppendTransactionFault("Simulated interruption before atomic commit receipt");
|
|
16148
17080
|
}
|
|
16149
|
-
this.writeCommittedSqlReceipt(sql, input, committedAt, events.length, head, confirmsReservation);
|
|
17081
|
+
this.writeCommittedSqlReceipt(sql, input, committedAt, events.length, head, confirmsReservation, version + 1);
|
|
16150
17082
|
if (confirmsReservation) sql.exec("DELETE FROM tag_reservation WHERE singleton = 1");
|
|
16151
17083
|
await this.rearmScheduler(txn);
|
|
16152
17084
|
return {
|
|
@@ -16154,7 +17086,8 @@ var TagDurableObject = class {
|
|
|
16154
17086
|
body: {
|
|
16155
17087
|
status: "appended",
|
|
16156
17088
|
fenceGate: { checked: true, activeFenceCount: 0 },
|
|
16157
|
-
version: version + 1
|
|
17089
|
+
version: version + 1,
|
|
17090
|
+
updatedAt: committedAt
|
|
16158
17091
|
},
|
|
16159
17092
|
hopFacts
|
|
16160
17093
|
};
|
|
@@ -16555,18 +17488,20 @@ var TagDurableObject = class {
|
|
|
16555
17488
|
);
|
|
16556
17489
|
this.upsertSourcePartitionRegistration(sql, eventServiceId, record.tag, outbox.eventId);
|
|
16557
17490
|
}
|
|
17491
|
+
ensureTagCommitReceiptWrittenVersion(sql);
|
|
16558
17492
|
for (const confirmation of record.confirmations) {
|
|
16559
17493
|
sql.exec(
|
|
16560
17494
|
`
|
|
16561
17495
|
INSERT OR IGNORE INTO tag_commit_receipt
|
|
16562
|
-
(attempt_id, epoch, committed_at, committed_event_count, head_suid, reservation_confirmed)
|
|
16563
|
-
VALUES (?, ?, ?, ?, ?, 1)
|
|
17496
|
+
(attempt_id, epoch, committed_at, committed_event_count, head_suid, reservation_confirmed, written_version)
|
|
17497
|
+
VALUES (?, ?, ?, ?, ?, 1, ?)
|
|
16564
17498
|
`,
|
|
16565
17499
|
confirmation.attemptId,
|
|
16566
17500
|
confirmation.epoch,
|
|
16567
17501
|
record.updatedAt,
|
|
16568
17502
|
record.events.filter((event) => event.attemptId === confirmation.attemptId).length,
|
|
16569
|
-
record.head
|
|
17503
|
+
record.head,
|
|
17504
|
+
record.version
|
|
16570
17505
|
);
|
|
16571
17506
|
}
|
|
16572
17507
|
}
|
|
@@ -16615,13 +17550,13 @@ var TagDurableObject = class {
|
|
|
16615
17550
|
async acquire(tag, body, observation) {
|
|
16616
17551
|
const parsed = acquireFrom(body, tag);
|
|
16617
17552
|
if (parsed.value === void 0) {
|
|
16618
|
-
return
|
|
17553
|
+
return error9(400, "invalid_tag_acquire", parsed.error ?? "Invalid acquire request");
|
|
16619
17554
|
}
|
|
16620
17555
|
const input = parsed.value;
|
|
16621
17556
|
observation?.markFirstStorageRead();
|
|
16622
17557
|
if (this.sqlStorage() !== void 0) {
|
|
16623
17558
|
const result3 = await this.acquireSql(tag, input);
|
|
16624
|
-
return
|
|
17559
|
+
return json11(result3.body, result3.status);
|
|
16625
17560
|
}
|
|
16626
17561
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
16627
17562
|
const loaded = await this.recordFor(txn, tag);
|
|
@@ -16656,9 +17591,9 @@ var TagDurableObject = class {
|
|
|
16656
17591
|
body: { ...rejected("consistency_head_mismatch").body, version: record.version }
|
|
16657
17592
|
};
|
|
16658
17593
|
}
|
|
16659
|
-
const
|
|
16660
|
-
if (
|
|
16661
|
-
if (
|
|
17594
|
+
const active2 = record.activeReservation;
|
|
17595
|
+
if (active2 !== null) {
|
|
17596
|
+
if (active2.attemptId === input.attemptId && active2.epoch === input.epoch) {
|
|
16662
17597
|
return {
|
|
16663
17598
|
status: 200,
|
|
16664
17599
|
body: {
|
|
@@ -16703,18 +17638,18 @@ var TagDurableObject = class {
|
|
|
16703
17638
|
body: { status: "reserved", reservation, fenceGate, version: updated.version }
|
|
16704
17639
|
};
|
|
16705
17640
|
});
|
|
16706
|
-
return
|
|
17641
|
+
return json11(result2.body, result2.status);
|
|
16707
17642
|
}
|
|
16708
17643
|
async cancel(tag, body, observation) {
|
|
16709
17644
|
const parsed = reservationFrom(body);
|
|
16710
17645
|
if (parsed.value === void 0) {
|
|
16711
|
-
return
|
|
17646
|
+
return error9(400, "invalid_tag_cancel", parsed.error ?? "Invalid cancel request");
|
|
16712
17647
|
}
|
|
16713
17648
|
const input = parsed.value;
|
|
16714
17649
|
observation?.markFirstStorageRead();
|
|
16715
17650
|
if (this.sqlStorage() !== void 0) {
|
|
16716
17651
|
const result3 = await this.cancelSql(tag, input);
|
|
16717
|
-
return
|
|
17652
|
+
return json11(result3.body, result3.status);
|
|
16718
17653
|
}
|
|
16719
17654
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
16720
17655
|
const loaded = await this.recordFor(txn, tag);
|
|
@@ -16749,8 +17684,8 @@ var TagDurableObject = class {
|
|
|
16749
17684
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
16750
17685
|
return { ...rejected(epochError), body: { ...rejected(epochError).body, version: record.version } };
|
|
16751
17686
|
}
|
|
16752
|
-
const
|
|
16753
|
-
if (
|
|
17687
|
+
const active2 = record.activeReservation;
|
|
17688
|
+
if (active2 === null || active2.attemptId !== input.attemptId || active2.epoch !== input.epoch || active2.token !== input.reservationToken) {
|
|
16754
17689
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
16755
17690
|
return {
|
|
16756
17691
|
...rejected("reservation_token_required"),
|
|
@@ -16765,12 +17700,12 @@ var TagDurableObject = class {
|
|
|
16765
17700
|
});
|
|
16766
17701
|
return { status: 200, body: { status: "cancelled", idempotent: false, version: updated.version } };
|
|
16767
17702
|
});
|
|
16768
|
-
return
|
|
17703
|
+
return json11(result2.body, result2.status);
|
|
16769
17704
|
}
|
|
16770
17705
|
async seal(tag, body, observation) {
|
|
16771
17706
|
const parsed = epochFrom(body);
|
|
16772
17707
|
if (parsed.value === void 0) {
|
|
16773
|
-
return
|
|
17708
|
+
return error9(400, "invalid_tag_seal", parsed.error ?? "Invalid seal request");
|
|
16774
17709
|
}
|
|
16775
17710
|
const input = parsed.value;
|
|
16776
17711
|
observation?.markFirstStorageRead();
|
|
@@ -16805,7 +17740,7 @@ var TagDurableObject = class {
|
|
|
16805
17740
|
});
|
|
16806
17741
|
return { status: 200, body: { status: "sealed", highestEpoch: input.epoch, version: updated.version } };
|
|
16807
17742
|
});
|
|
16808
|
-
return
|
|
17743
|
+
return json11(result2.body, result2.status);
|
|
16809
17744
|
}
|
|
16810
17745
|
/**
|
|
16811
17746
|
* Coordinator-only import admission. This is deliberately not a flag on
|
|
@@ -16814,8 +17749,8 @@ var TagDurableObject = class {
|
|
|
16814
17749
|
*/
|
|
16815
17750
|
async bootstrapAppend(tag, body, serviceId) {
|
|
16816
17751
|
const parsed = bootstrapAppendFrom(body, tag);
|
|
16817
|
-
if (parsed.value === void 0) return
|
|
16818
|
-
if (serviceId !== parsed.value.targetServiceId) return
|
|
17752
|
+
if (parsed.value === void 0) return error9(400, "invalid_bootstrap_admission", parsed.error ?? "Invalid bootstrap admission");
|
|
17753
|
+
if (serviceId !== parsed.value.targetServiceId) return error9(409, "bootstrap_service_scope_mismatch", "Bootstrap target service does not match tag scope");
|
|
16819
17754
|
const input = parsed.value;
|
|
16820
17755
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
16821
17756
|
const loaded = await this.recordFor(txn, tag);
|
|
@@ -16836,10 +17771,10 @@ var TagDurableObject = class {
|
|
|
16836
17771
|
);
|
|
16837
17772
|
return { status: 201, body: { status: "bootstrap_admitted", version: updated.version } };
|
|
16838
17773
|
});
|
|
16839
|
-
return
|
|
17774
|
+
return json11(result2.body, result2.status);
|
|
16840
17775
|
}
|
|
16841
17776
|
async closeBootstrap(tag, body) {
|
|
16842
|
-
if (!
|
|
17777
|
+
if (!isObject12(body) || !isNonEmptyString11(body.importId) || !isEpoch(body.leaseEpoch)) return error9(400, "invalid_bootstrap_close", "bootstrap importId and leaseEpoch are required");
|
|
16843
17778
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
16844
17779
|
const loaded = await this.recordFor(txn, tag);
|
|
16845
17780
|
const record = loaded.record;
|
|
@@ -16849,7 +17784,7 @@ var TagDurableObject = class {
|
|
|
16849
17784
|
const updated = await this.commit(txn, record, { bootstrapAdmission: { ...admission, closed: true } });
|
|
16850
17785
|
return { status: 200, body: { status: "closed", version: updated.version } };
|
|
16851
17786
|
});
|
|
16852
|
-
return
|
|
17787
|
+
return json11(result2.body, result2.status);
|
|
16853
17788
|
}
|
|
16854
17789
|
directDoorbellPreflight(domainDeliveryClass) {
|
|
16855
17790
|
const domainClass = domainDeliveryClass === void 0 ? void 0 : readDomainDeliveryClass({ DOMAIN_DELIVERY_CLASS: domainDeliveryClass });
|
|
@@ -16868,7 +17803,7 @@ var TagDurableObject = class {
|
|
|
16868
17803
|
async append(tag, body, serviceId, domainDeliveryClass, observation) {
|
|
16869
17804
|
const parsed = appendFrom(body, tag);
|
|
16870
17805
|
if (parsed.value === void 0) {
|
|
16871
|
-
return
|
|
17806
|
+
return error9(400, "invalid_tag_append", parsed.error ?? "Invalid append request");
|
|
16872
17807
|
}
|
|
16873
17808
|
const input = parsed.value;
|
|
16874
17809
|
try {
|
|
@@ -16906,7 +17841,7 @@ var TagDurableObject = class {
|
|
|
16906
17841
|
if ((result3.status === 201 || result3.status === 200) && serviceId !== null && serviceId.length > 0 && sourcePartitionRegistration !== "unconfigured") {
|
|
16907
17842
|
this.scheduleSourcePartitionWatermark(tag, serviceId);
|
|
16908
17843
|
}
|
|
16909
|
-
const response4 =
|
|
17844
|
+
const response4 = json11(result3.body, result3.status);
|
|
16910
17845
|
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
17846
|
const directRows = doorbellPreflight.status === "ready" && this.env.DOWNSTREAM_DOORBELL !== void 0 ? await this.directDeliveryBeforeResponse(tag, serviceId) : void 0;
|
|
16912
17847
|
if (sourcePartitionRegistration !== "unconfigured" || sourcePartitionWasRegistered) {
|
|
@@ -16923,7 +17858,19 @@ var TagDurableObject = class {
|
|
|
16923
17858
|
const loaded = await this.recordFor(txn, tag);
|
|
16924
17859
|
let record = loaded.record;
|
|
16925
17860
|
if (batchIsExactDuplicate(record, input)) {
|
|
16926
|
-
|
|
17861
|
+
const receipts = record.writeReceipts ?? {};
|
|
17862
|
+
const sameEpoch = receipts[`${input.attemptId}:${input.epoch}`];
|
|
17863
|
+
const forAttempt = Object.entries(receipts).filter(([key]) => key.startsWith(`${input.attemptId}:`));
|
|
17864
|
+
const replayHead = input.candidates.at(-1)?.suid;
|
|
17865
|
+
const byHead = replayHead === void 0 ? [] : forAttempt.filter(([, value]) => value.headSuid === replayHead);
|
|
17866
|
+
const receipt = sameEpoch ?? (byHead.length === 1 ? byHead[0]?.[1] : void 0) ?? (forAttempt.length === 1 ? forAttempt[0]?.[1] : void 0);
|
|
17867
|
+
if (receipt === void 0) {
|
|
17868
|
+
return {
|
|
17869
|
+
...rejected("duplicate_receipt_unknown"),
|
|
17870
|
+
body: { ...rejected("duplicate_receipt_unknown").body, version: record.version }
|
|
17871
|
+
};
|
|
17872
|
+
}
|
|
17873
|
+
return { status: 200, body: { status: "duplicate", version: receipt.version, updatedAt: receipt.updatedAt } };
|
|
16927
17874
|
}
|
|
16928
17875
|
const expiry = expireReservation(record);
|
|
16929
17876
|
record = expiry.record;
|
|
@@ -16936,9 +17883,9 @@ var TagDurableObject = class {
|
|
|
16936
17883
|
await this.commitExpiryIfNeeded(txn, expiry);
|
|
16937
17884
|
return fenceGateRejected();
|
|
16938
17885
|
}
|
|
16939
|
-
const
|
|
16940
|
-
const confirmsReservation =
|
|
16941
|
-
if (
|
|
17886
|
+
const active2 = record.activeReservation;
|
|
17887
|
+
const confirmsReservation = active2 !== null && active2.attemptId === input.attemptId && active2.epoch === input.epoch && active2.token === input.reservationToken;
|
|
17888
|
+
if (active2 !== null && (active2.attemptId !== input.attemptId || active2.epoch !== input.epoch || active2.token !== input.reservationToken)) {
|
|
16942
17889
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
16943
17890
|
return {
|
|
16944
17891
|
...rejected("reservation_token_required"),
|
|
@@ -17000,13 +17947,20 @@ var TagDurableObject = class {
|
|
|
17000
17947
|
alarmDueAt: null,
|
|
17001
17948
|
confirmations: confirmsReservation ? withMaxEpoch(record.confirmations, input.attemptId, input.epoch) : record.confirmations
|
|
17002
17949
|
});
|
|
17003
|
-
|
|
17950
|
+
const recorded = {
|
|
17951
|
+
...updated,
|
|
17952
|
+
writeReceipts: {
|
|
17953
|
+
...record.writeReceipts ?? {},
|
|
17954
|
+
[`${input.attemptId}:${input.epoch}`]: { version: updated.version, updatedAt: updated.updatedAt, headSuid: updated.head }
|
|
17955
|
+
}
|
|
17956
|
+
};
|
|
17957
|
+
await this.write(txn, recorded, serviceId ?? "");
|
|
17004
17958
|
return {
|
|
17005
17959
|
status: 201,
|
|
17006
|
-
body: { status: "appended", fenceGate, version:
|
|
17960
|
+
body: { status: "appended", fenceGate, version: recorded.version, updatedAt: recorded.updatedAt }
|
|
17007
17961
|
};
|
|
17008
17962
|
});
|
|
17009
|
-
const response3 =
|
|
17963
|
+
const response3 = json11(result2.body, result2.status);
|
|
17010
17964
|
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
17965
|
const directRows = doorbellPreflight.status === "ready" && this.env.DOWNSTREAM_DOORBELL !== void 0 ? await this.directDeliveryBeforeResponse(tag, serviceId) : void 0;
|
|
17012
17966
|
withGlobalAdmission2(
|
|
@@ -17018,16 +17972,16 @@ var TagDurableObject = class {
|
|
|
17018
17972
|
return response3;
|
|
17019
17973
|
} catch (failure3) {
|
|
17020
17974
|
if (failure3 instanceof PartitionRegistrationUnavailableError) {
|
|
17021
|
-
return
|
|
17975
|
+
return error9(503, "partition_registration_unavailable", "Source partition registration is unavailable; retry the commit.", true);
|
|
17022
17976
|
}
|
|
17023
17977
|
if (failure3 instanceof AppendTransactionFault) {
|
|
17024
|
-
return
|
|
17978
|
+
return error9(
|
|
17025
17979
|
503,
|
|
17026
17980
|
"simulated_append_crash",
|
|
17027
17981
|
"Simulated interruption before atomic append and confirmation commit"
|
|
17028
17982
|
);
|
|
17029
17983
|
}
|
|
17030
|
-
return
|
|
17984
|
+
return error9(500, "tag_append_failure", "Tag append could not be persisted");
|
|
17031
17985
|
}
|
|
17032
17986
|
}
|
|
17033
17987
|
/**
|
|
@@ -17143,7 +18097,7 @@ var TagDurableObject = class {
|
|
|
17143
18097
|
let timer;
|
|
17144
18098
|
const operationResult = Promise.resolve().then(operation).then(
|
|
17145
18099
|
(value) => ({ status: "completed", value }),
|
|
17146
|
-
(
|
|
18100
|
+
(error11) => ({ status: "failed", error: error11 })
|
|
17147
18101
|
);
|
|
17148
18102
|
const timeout = new Promise((resolve) => {
|
|
17149
18103
|
timer = setTimeout(() => resolve({ status: "timeout" }), budgetMs);
|
|
@@ -17194,14 +18148,14 @@ var TagDurableObject = class {
|
|
|
17194
18148
|
envelopeBytes,
|
|
17195
18149
|
status: "success"
|
|
17196
18150
|
});
|
|
17197
|
-
} catch (
|
|
18151
|
+
} catch (error11) {
|
|
17198
18152
|
console.warn("direct_doorbell_status", {
|
|
17199
18153
|
status: "queued-degraded",
|
|
17200
18154
|
reason: "direct_doorbell_delivery_failed",
|
|
17201
18155
|
correlationId,
|
|
17202
18156
|
envelopeBytes,
|
|
17203
|
-
failureKind: classifyDirectDoorbellFailure(
|
|
17204
|
-
error: String(
|
|
18157
|
+
failureKind: classifyDirectDoorbellFailure(error11),
|
|
18158
|
+
error: String(error11)
|
|
17205
18159
|
});
|
|
17206
18160
|
}
|
|
17207
18161
|
}
|
|
@@ -17262,7 +18216,7 @@ var TagDurableObject = class {
|
|
|
17262
18216
|
async confirm(tag, body) {
|
|
17263
18217
|
const parsed = reservationFrom(body);
|
|
17264
18218
|
if (parsed.value === void 0) {
|
|
17265
|
-
return
|
|
18219
|
+
return error9(400, "invalid_tag_confirm", parsed.error ?? "Invalid confirm request");
|
|
17266
18220
|
}
|
|
17267
18221
|
const input = parsed.value;
|
|
17268
18222
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17278,8 +18232,8 @@ var TagDurableObject = class {
|
|
|
17278
18232
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
17279
18233
|
return { ...rejected(epochError), body: { ...rejected(epochError).body, version: record.version } };
|
|
17280
18234
|
}
|
|
17281
|
-
const
|
|
17282
|
-
if (
|
|
18235
|
+
const active2 = record.activeReservation;
|
|
18236
|
+
if (active2 !== null && active2.attemptId === input.attemptId && active2.epoch === input.epoch && active2.token === input.reservationToken) {
|
|
17283
18237
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
17284
18238
|
return {
|
|
17285
18239
|
...rejected("append_required_for_confirmation"),
|
|
@@ -17292,12 +18246,12 @@ var TagDurableObject = class {
|
|
|
17292
18246
|
body: { ...rejected("confirmation_not_found").body, version: record.version }
|
|
17293
18247
|
};
|
|
17294
18248
|
});
|
|
17295
|
-
return
|
|
18249
|
+
return json11(result2.body, result2.status);
|
|
17296
18250
|
}
|
|
17297
18251
|
async installFence(tag, body) {
|
|
17298
18252
|
const parsed = fenceFrom(body);
|
|
17299
18253
|
if (parsed.value === void 0) {
|
|
17300
|
-
return
|
|
18254
|
+
return error9(400, "invalid_tag_fence", parsed.error ?? "Invalid fence request");
|
|
17301
18255
|
}
|
|
17302
18256
|
const input = parsed.value;
|
|
17303
18257
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17334,12 +18288,12 @@ var TagDurableObject = class {
|
|
|
17334
18288
|
});
|
|
17335
18289
|
return { status: 201, body: { status: "fence-installed", idempotent: false, version: updated.version } };
|
|
17336
18290
|
});
|
|
17337
|
-
return
|
|
18291
|
+
return json11(result2.body, result2.status);
|
|
17338
18292
|
}
|
|
17339
18293
|
async clearFence(tag, body) {
|
|
17340
18294
|
const parsed = fenceFrom(body);
|
|
17341
18295
|
if (parsed.value === void 0) {
|
|
17342
|
-
return
|
|
18296
|
+
return error9(400, "invalid_tag_fence", parsed.error ?? "Invalid fence request");
|
|
17343
18297
|
}
|
|
17344
18298
|
const input = parsed.value;
|
|
17345
18299
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17365,7 +18319,7 @@ var TagDurableObject = class {
|
|
|
17365
18319
|
});
|
|
17366
18320
|
return { status: 200, body: { status: "fence-cleared", idempotent: false, version: updated.version } };
|
|
17367
18321
|
});
|
|
17368
|
-
return
|
|
18322
|
+
return json11(result2.body, result2.status);
|
|
17369
18323
|
}
|
|
17370
18324
|
/**
|
|
17371
18325
|
* Returns Tag-side repair facts only. Journal observations are deliberately
|
|
@@ -17401,7 +18355,7 @@ var TagDurableObject = class {
|
|
|
17401
18355
|
}
|
|
17402
18356
|
};
|
|
17403
18357
|
});
|
|
17404
|
-
return
|
|
18358
|
+
return json11(result2.body, result2.status);
|
|
17405
18359
|
}
|
|
17406
18360
|
/**
|
|
17407
18361
|
* Internal outbox handoff: enqueuedAt is persisted before any Queue send so
|
|
@@ -17411,10 +18365,10 @@ var TagDurableObject = class {
|
|
|
17411
18365
|
async pendingOutbox(tag, serviceId, body) {
|
|
17412
18366
|
const parsed = outboxPendingFrom(body);
|
|
17413
18367
|
if (parsed.value === void 0) {
|
|
17414
|
-
return
|
|
18368
|
+
return error9(400, "invalid_outbox_pending", parsed.error ?? "Invalid outbox pending request");
|
|
17415
18369
|
}
|
|
17416
18370
|
if (!isNonEmptyString11(serviceId)) {
|
|
17417
|
-
return
|
|
18371
|
+
return error9(400, "outbox_service_identity_required", "Outbox delivery requires a service identity");
|
|
17418
18372
|
}
|
|
17419
18373
|
const input = parsed.value;
|
|
17420
18374
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17490,7 +18444,7 @@ var TagDurableObject = class {
|
|
|
17490
18444
|
}
|
|
17491
18445
|
return { status: 200, body: { rows } };
|
|
17492
18446
|
});
|
|
17493
|
-
return
|
|
18447
|
+
return json11(result2.body, result2.status);
|
|
17494
18448
|
}
|
|
17495
18449
|
/**
|
|
17496
18450
|
* Source-side obligation scanner. It deliberately does not call the Queue,
|
|
@@ -17560,7 +18514,7 @@ var TagDurableObject = class {
|
|
|
17560
18514
|
}
|
|
17561
18515
|
};
|
|
17562
18516
|
});
|
|
17563
|
-
return
|
|
18517
|
+
return json11(result2.body, result2.status);
|
|
17564
18518
|
}
|
|
17565
18519
|
/** Record a retry or terminal poison disposition without changing the source event. */
|
|
17566
18520
|
async recordOutboxFailure(tag, delivery, failure3) {
|
|
@@ -17614,16 +18568,16 @@ var TagDurableObject = class {
|
|
|
17614
18568
|
async markOutboxDelivered(tag, body) {
|
|
17615
18569
|
const parsed = outboxMarkFrom(body);
|
|
17616
18570
|
if (parsed.value === void 0) {
|
|
17617
|
-
return
|
|
18571
|
+
return error9(400, "invalid_outbox_delivery_mark", parsed.error ?? "Invalid outbox delivery mark");
|
|
17618
18572
|
}
|
|
17619
18573
|
const input = parsed.value;
|
|
17620
18574
|
try {
|
|
17621
18575
|
const joined = await Promise.all(input.deliveries.map((delivery) => this.globalReceiptMatches(delivery)));
|
|
17622
18576
|
if (joined.some((value) => !value)) {
|
|
17623
|
-
return
|
|
18577
|
+
return error9(409, "outbox_global_receipt_unverified", "Global receipt and membership must be read back before source acknowledgement");
|
|
17624
18578
|
}
|
|
17625
18579
|
} catch {
|
|
17626
|
-
return
|
|
18580
|
+
return error9(503, "outbox_global_receipt_unavailable", "Global receipt verification is unavailable");
|
|
17627
18581
|
}
|
|
17628
18582
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
17629
18583
|
if (this.sqlStorage() !== void 0) {
|
|
@@ -17662,7 +18616,7 @@ var TagDurableObject = class {
|
|
|
17662
18616
|
}
|
|
17663
18617
|
return { status: 200, body: { marked, idempotent: marked === 0 } };
|
|
17664
18618
|
});
|
|
17665
|
-
return
|
|
18619
|
+
return json11(result2.body, result2.status);
|
|
17666
18620
|
}
|
|
17667
18621
|
/**
|
|
17668
18622
|
* The delivery handoff reads its universe from `tag_outbox_obligation`.
|
|
@@ -17801,7 +18755,7 @@ var TagDurableObject = class {
|
|
|
17801
18755
|
async acquireRepairLease(tag, body) {
|
|
17802
18756
|
const parsed = repairAcquireFrom(body, tag);
|
|
17803
18757
|
if (parsed.value === void 0) {
|
|
17804
|
-
return
|
|
18758
|
+
return error9(400, "invalid_repair_acquire", parsed.error ?? "Invalid repair acquire request");
|
|
17805
18759
|
}
|
|
17806
18760
|
const input = parsed.value;
|
|
17807
18761
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17847,12 +18801,12 @@ var TagDurableObject = class {
|
|
|
17847
18801
|
}
|
|
17848
18802
|
};
|
|
17849
18803
|
});
|
|
17850
|
-
return
|
|
18804
|
+
return json11(result2.body, result2.status);
|
|
17851
18805
|
}
|
|
17852
18806
|
async renewRepairLease(tag, body) {
|
|
17853
18807
|
const parsed = repairLeaseFrom(body);
|
|
17854
18808
|
if (parsed.value === void 0) {
|
|
17855
|
-
return
|
|
18809
|
+
return error9(400, "invalid_repair_renew", parsed.error ?? "Invalid repair renew request");
|
|
17856
18810
|
}
|
|
17857
18811
|
const input = parsed.value;
|
|
17858
18812
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17876,12 +18830,12 @@ var TagDurableObject = class {
|
|
|
17876
18830
|
}
|
|
17877
18831
|
};
|
|
17878
18832
|
});
|
|
17879
|
-
return
|
|
18833
|
+
return json11(result2.body, result2.status);
|
|
17880
18834
|
}
|
|
17881
18835
|
async unionRepairScope(tag, body) {
|
|
17882
18836
|
const parsed = repairScopeUnionFrom(body, tag);
|
|
17883
18837
|
if (parsed.value === void 0) {
|
|
17884
|
-
return
|
|
18838
|
+
return error9(400, "invalid_repair_scope_union", parsed.error ?? "Invalid repair scope union request");
|
|
17885
18839
|
}
|
|
17886
18840
|
const input = parsed.value;
|
|
17887
18841
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17917,12 +18871,12 @@ var TagDurableObject = class {
|
|
|
17917
18871
|
body: { status: "repair-scope-unioned", scopeVersion: updated.repairScopeVersion, scope: updated.repairScope }
|
|
17918
18872
|
};
|
|
17919
18873
|
});
|
|
17920
|
-
return
|
|
18874
|
+
return json11(result2.body, result2.status);
|
|
17921
18875
|
}
|
|
17922
18876
|
async applyRepair(tag, body) {
|
|
17923
18877
|
const parsed = repairApplyFrom(body, tag);
|
|
17924
18878
|
if (parsed.value === void 0) {
|
|
17925
|
-
return
|
|
18879
|
+
return error9(400, "invalid_repair_apply", parsed.error ?? "Invalid repair apply request");
|
|
17926
18880
|
}
|
|
17927
18881
|
const input = parsed.value;
|
|
17928
18882
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18003,12 +18957,12 @@ var TagDurableObject = class {
|
|
|
18003
18957
|
body: { status: branch, idempotent: false, head: (updatedRecord ?? record).head, version: (updatedRecord ?? record).version }
|
|
18004
18958
|
};
|
|
18005
18959
|
});
|
|
18006
|
-
return
|
|
18960
|
+
return json11(result2.body, result2.status);
|
|
18007
18961
|
}
|
|
18008
18962
|
async auditRepair(tag, body) {
|
|
18009
18963
|
const parsed = repairAuditFrom(body, tag);
|
|
18010
18964
|
if (parsed.value === void 0) {
|
|
18011
|
-
return
|
|
18965
|
+
return error9(400, "invalid_repair_audit", parsed.error ?? "Invalid repair audit request");
|
|
18012
18966
|
}
|
|
18013
18967
|
const input = parsed.value;
|
|
18014
18968
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18045,12 +18999,12 @@ var TagDurableObject = class {
|
|
|
18045
18999
|
await txn.put(REPAIR_FACTS_KEY, { ...facts, audits: [...facts.audits, audit] });
|
|
18046
19000
|
return { status: 200, body: { status: "repair-audited", idempotent: false, branch: audit.branch } };
|
|
18047
19001
|
});
|
|
18048
|
-
return
|
|
19002
|
+
return json11(result2.body, result2.status);
|
|
18049
19003
|
}
|
|
18050
19004
|
async clearRepairFence(tag, body) {
|
|
18051
19005
|
const parsed = repairClearFrom(body);
|
|
18052
19006
|
if (parsed.value === void 0) {
|
|
18053
|
-
return
|
|
19007
|
+
return error9(400, "invalid_repair_clear", parsed.error ?? "Invalid repair clear request");
|
|
18054
19008
|
}
|
|
18055
19009
|
const input = parsed.value;
|
|
18056
19010
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18081,16 +19035,16 @@ var TagDurableObject = class {
|
|
|
18081
19035
|
});
|
|
18082
19036
|
return { status: 200, body: { status: "repair-fence-cleared", idempotent: false, version: updated.version } };
|
|
18083
19037
|
});
|
|
18084
|
-
return
|
|
19038
|
+
return json11(result2.body, result2.status);
|
|
18085
19039
|
}
|
|
18086
19040
|
async setClockOffset(tag, body) {
|
|
18087
|
-
if (!
|
|
18088
|
-
return
|
|
19041
|
+
if (!isObject12(body)) {
|
|
19042
|
+
return error9(400, "invalid_tag_clock", "offsetMs or nowMs must be a safe integer");
|
|
18089
19043
|
}
|
|
18090
19044
|
const nowMs = body.nowMs;
|
|
18091
19045
|
const offsetMs = body.offsetMs;
|
|
18092
19046
|
if (!isSafeInteger(nowMs) && !isSafeInteger(offsetMs)) {
|
|
18093
|
-
return
|
|
19047
|
+
return error9(400, "invalid_tag_clock", "offsetMs or nowMs must be a safe integer");
|
|
18094
19048
|
}
|
|
18095
19049
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
18096
19050
|
const loaded = await this.recordFor(txn, tag);
|
|
@@ -18108,7 +19062,7 @@ var TagDurableObject = class {
|
|
|
18108
19062
|
}
|
|
18109
19063
|
};
|
|
18110
19064
|
});
|
|
18111
|
-
return
|
|
19065
|
+
return json11(result2.body, result2.status);
|
|
18112
19066
|
}
|
|
18113
19067
|
async runAlarm() {
|
|
18114
19068
|
const due = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18227,16 +19181,16 @@ var TagStateCheckpointFault = class extends Error {
|
|
|
18227
19181
|
this.name = "TagStateCheckpointFault";
|
|
18228
19182
|
}
|
|
18229
19183
|
};
|
|
18230
|
-
function
|
|
19184
|
+
function json12(body, status = 200) {
|
|
18231
19185
|
return new Response(JSON.stringify(body), {
|
|
18232
19186
|
status,
|
|
18233
19187
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
18234
19188
|
});
|
|
18235
19189
|
}
|
|
18236
|
-
function
|
|
18237
|
-
return
|
|
19190
|
+
function error10(status, code, message) {
|
|
19191
|
+
return json12({ code, error: message }, status);
|
|
18238
19192
|
}
|
|
18239
|
-
function
|
|
19193
|
+
function isObject13(value) {
|
|
18240
19194
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18241
19195
|
}
|
|
18242
19196
|
function isNonEmptyString12(value) {
|
|
@@ -18251,7 +19205,7 @@ function sqlNullableString2(value, column) {
|
|
|
18251
19205
|
return sqlString2(value, column);
|
|
18252
19206
|
}
|
|
18253
19207
|
function parseIdentity(value) {
|
|
18254
|
-
if (!
|
|
19208
|
+
if (!isObject13(value) || !isNonEmptyString12(value.serviceId) || !isNonEmptyString12(value.tag) || !isNonEmptyString12(value.projectorId)) {
|
|
18255
19209
|
return void 0;
|
|
18256
19210
|
}
|
|
18257
19211
|
return { serviceId: value.serviceId, tag: value.tag, projectorId: value.projectorId };
|
|
@@ -18313,17 +19267,17 @@ var TagStateDurableObject = class {
|
|
|
18313
19267
|
async fetch(request) {
|
|
18314
19268
|
const url = new URL(request.url);
|
|
18315
19269
|
if (request.method !== "POST" || url.pathname !== "/read") {
|
|
18316
|
-
return
|
|
19270
|
+
return error10(404, "tag_state_route_not_found", "TagState route was not found");
|
|
18317
19271
|
}
|
|
18318
19272
|
let body;
|
|
18319
19273
|
try {
|
|
18320
19274
|
body = await request.json();
|
|
18321
19275
|
} catch {
|
|
18322
|
-
return
|
|
19276
|
+
return error10(400, "tag_state_validation_error", "TagState request must be JSON");
|
|
18323
19277
|
}
|
|
18324
19278
|
const identity2 = parseIdentity(body);
|
|
18325
19279
|
if (identity2 === void 0) {
|
|
18326
|
-
return
|
|
19280
|
+
return error10(400, "tag_state_validation_error", "TagState identity requires serviceId, tag, and projectorId");
|
|
18327
19281
|
}
|
|
18328
19282
|
return await this.read(identity2);
|
|
18329
19283
|
}
|
|
@@ -18345,16 +19299,16 @@ var TagStateDurableObject = class {
|
|
|
18345
19299
|
return await this.continueRebuild(identity2, projector, cache);
|
|
18346
19300
|
} catch (caught) {
|
|
18347
19301
|
if (caught instanceof TagStateIdentityConflict) {
|
|
18348
|
-
return
|
|
19302
|
+
return error10(409, "tag_state_identity_conflict", "TagState Durable Object identity changed");
|
|
18349
19303
|
}
|
|
18350
19304
|
if (caught instanceof TagStateUnknownProjector) {
|
|
18351
|
-
return
|
|
19305
|
+
return error10(404, "tag_state_unknown_projector", "TagState projector is not registered");
|
|
18352
19306
|
}
|
|
18353
19307
|
if (caught instanceof TagStateRegistryFailure) {
|
|
18354
|
-
return
|
|
19308
|
+
return error10(503, "tag_state_projector_registry_failure", "TagState projector registry is unavailable");
|
|
18355
19309
|
}
|
|
18356
19310
|
if (caught instanceof TagStateSourceFailure) {
|
|
18357
|
-
return
|
|
19311
|
+
return error10(caught.status, "tag_state_source_frontier_failure", "TagState source frontier could not be read");
|
|
18358
19312
|
}
|
|
18359
19313
|
if (caught instanceof TagStateCacheCorruption) {
|
|
18360
19314
|
try {
|
|
@@ -18362,21 +19316,21 @@ var TagStateDurableObject = class {
|
|
|
18362
19316
|
this.beginRebuild(projector, "cache-corrupt");
|
|
18363
19317
|
} catch (rebuildFailure) {
|
|
18364
19318
|
if (rebuildFailure instanceof TagStateRegistryFailure) {
|
|
18365
|
-
return
|
|
19319
|
+
return error10(503, "tag_state_projector_registry_failure", "TagState projector registry is unavailable");
|
|
18366
19320
|
}
|
|
18367
19321
|
}
|
|
18368
|
-
return
|
|
19322
|
+
return error10(409, "tag_state_cache_corrupt", "TagState cache is corrupt and is rebuilding");
|
|
18369
19323
|
}
|
|
18370
19324
|
if (caught instanceof TagStateCheckpointFault) {
|
|
18371
|
-
return
|
|
19325
|
+
return error10(503, "tag_state_rebuild_interrupted", "TagState replay checkpoint was interrupted");
|
|
18372
19326
|
}
|
|
18373
|
-
return
|
|
19327
|
+
return error10(503, "tag_state_unavailable", "TagState cache is unavailable");
|
|
18374
19328
|
}
|
|
18375
19329
|
}
|
|
18376
19330
|
async beginDeltaOrRespondReady(identity2, projector, cache) {
|
|
18377
19331
|
const page = await this.readSource(identity2, cache.lastSuid, void 0);
|
|
18378
19332
|
if (page.events.length === 0 && page.completeThrough !== null) {
|
|
18379
|
-
return
|
|
19333
|
+
return json12(this.success(projector, cache.stateJson, cache.lastSuid));
|
|
18380
19334
|
}
|
|
18381
19335
|
const rebuild = this.beginRebuild(projector, "normal-delta", {
|
|
18382
19336
|
cursor: cache.lastSuid,
|
|
@@ -18425,9 +19379,9 @@ var TagStateDurableObject = class {
|
|
|
18425
19379
|
});
|
|
18426
19380
|
if (this.g46CheckpointFault === "after-checkpoint") throw new TagStateCheckpointFault();
|
|
18427
19381
|
if (!complete) {
|
|
18428
|
-
return
|
|
19382
|
+
return error10(503, "tag_state_rebuild_in_progress", "TagState replay is rebuilding to its frozen frontier");
|
|
18429
19383
|
}
|
|
18430
|
-
return
|
|
19384
|
+
return json12(this.success(projector, accumulatorJson, page.completeThrough));
|
|
18431
19385
|
}
|
|
18432
19386
|
success(projector, stateJson, lastSortedUniqueId) {
|
|
18433
19387
|
try {
|
|
@@ -18484,7 +19438,7 @@ var TagStateDurableObject = class {
|
|
|
18484
19438
|
}
|
|
18485
19439
|
}
|
|
18486
19440
|
assertSourcePage(cursor, expectedThrough, page) {
|
|
18487
|
-
if (!
|
|
19441
|
+
if (!isObject13(page) || !Array.isArray(page.events) || typeof page.lastSortableUniqueId !== "string" || typeof page.through !== "string" || page.completeThrough !== null && typeof page.completeThrough !== "string") {
|
|
18488
19442
|
throw new TagStateSourceFailure(503, new Error("TagState source response was malformed"));
|
|
18489
19443
|
}
|
|
18490
19444
|
if (expectedThrough !== void 0 && expectedThrough !== null && page.through !== expectedThrough) {
|
|
@@ -18909,9 +19863,9 @@ var PostgresEventStore = class {
|
|
|
18909
19863
|
await transaction.unsafe(SCHEMA, [], { prepare: false });
|
|
18910
19864
|
});
|
|
18911
19865
|
this.sql = sql;
|
|
18912
|
-
} catch (
|
|
19866
|
+
} catch (error11) {
|
|
18913
19867
|
await sql.end({ timeout: 1 });
|
|
18914
|
-
throw
|
|
19868
|
+
throw error11;
|
|
18915
19869
|
}
|
|
18916
19870
|
}
|
|
18917
19871
|
/** Close an owned request/test client without touching durable records. */
|
|
@@ -19102,6 +20056,65 @@ var PostgresEventStore = class {
|
|
|
19102
20056
|
}
|
|
19103
20057
|
return outcome === "stored" ? { outcome, kind: "stored", event: stored } : { outcome, kind: outcome, incident };
|
|
19104
20058
|
}
|
|
20059
|
+
/**
|
|
20060
|
+
* DDL columns only. C# rows have no provenance and no allocator lineage;
|
|
20061
|
+
* this read must not invent either, and it must not treat Tags as
|
|
20062
|
+
* committed membership.
|
|
20063
|
+
*/
|
|
20064
|
+
async readLogicalEvents(serviceId) {
|
|
20065
|
+
const rows = await this.query(
|
|
20066
|
+
`SELECT "ServiceId" AS service_id, "Id"::text AS event_id,
|
|
20067
|
+
"SortableUniqueId" AS suid, "Payload"::text AS payload,
|
|
20068
|
+
"EventType" AS event_type, "Tags" AS event_tags,
|
|
20069
|
+
"Timestamp"::text AS timestamp, "CausationId" AS causation_id,
|
|
20070
|
+
"CorrelationId" AS correlation_id, "ExecutedUser" AS executed_user
|
|
20071
|
+
FROM dcb_events
|
|
20072
|
+
WHERE "ServiceId" = $1
|
|
20073
|
+
ORDER BY "SortableUniqueId" ASC, "Id" ASC`,
|
|
20074
|
+
[serviceId]
|
|
20075
|
+
);
|
|
20076
|
+
return rows.map((row) => ({
|
|
20077
|
+
serviceId: asString4(row.service_id, "service_id"),
|
|
20078
|
+
id: asString4(row.event_id, "event_id"),
|
|
20079
|
+
sortableUniqueId: asString4(row.suid, "suid"),
|
|
20080
|
+
eventType: asString4(row.event_type, "event_type"),
|
|
20081
|
+
payload: asString4(row.payload, "payload"),
|
|
20082
|
+
tags: asStringArray2(row.event_tags, "event_tags"),
|
|
20083
|
+
timestamp: canonicalUtcTimestamp(row.timestamp, "timestamp"),
|
|
20084
|
+
causationId: nullableString2(row.causation_id, "causation_id"),
|
|
20085
|
+
correlationId: nullableString2(row.correlation_id, "correlation_id"),
|
|
20086
|
+
executedUser: nullableString2(row.executed_user, "executed_user")
|
|
20087
|
+
}));
|
|
20088
|
+
}
|
|
20089
|
+
/** Insert one DDL row. Does not write dcb_event_ops or a provenance stamp. */
|
|
20090
|
+
async appendLogicalEvent(event) {
|
|
20091
|
+
JSON.parse(event.payload);
|
|
20092
|
+
assertSortableUniqueId(event.sortableUniqueId);
|
|
20093
|
+
if (!isRfc4122Uuid(event.id)) {
|
|
20094
|
+
throw new CanonicalEventIdentityConflictError("postgres", event.id, "logical event id must be an RFC 4122 UUID");
|
|
20095
|
+
}
|
|
20096
|
+
assertUtcTimestamp(event.timestamp, event.id);
|
|
20097
|
+
const sql = this.requireSql();
|
|
20098
|
+
await sql.begin(async (transaction) => {
|
|
20099
|
+
await transaction.unsafe(
|
|
20100
|
+
`INSERT INTO dcb_events
|
|
20101
|
+
("ServiceId", "Id", "SortableUniqueId", "EventType", "Payload", "Tags", "Timestamp", "CausationId", "CorrelationId", "ExecutedUser")
|
|
20102
|
+
VALUES ($1, $2::uuid, $3, $4, $5::text::json, $6::text::jsonb, $7::timestamptz, $8, $9, $10)`,
|
|
20103
|
+
[
|
|
20104
|
+
event.serviceId,
|
|
20105
|
+
event.id,
|
|
20106
|
+
event.sortableUniqueId,
|
|
20107
|
+
event.eventType,
|
|
20108
|
+
event.payload,
|
|
20109
|
+
JSON.stringify(event.tags),
|
|
20110
|
+
event.timestamp,
|
|
20111
|
+
event.causationId,
|
|
20112
|
+
event.correlationId,
|
|
20113
|
+
event.executedUser
|
|
20114
|
+
]
|
|
20115
|
+
);
|
|
20116
|
+
});
|
|
20117
|
+
}
|
|
19105
20118
|
async readAllEvents(serviceId, since) {
|
|
19106
20119
|
if (since.length !== 0) assertSortableUniqueId(since);
|
|
19107
20120
|
const rows = await this.query(
|
|
@@ -19574,12 +20587,12 @@ function encodedCommitSnapshotLogBytes(record) {
|
|
|
19574
20587
|
function assertCommitSnapshotLogSize(record) {
|
|
19575
20588
|
const encodedBytes2 = encodedCommitSnapshotLogBytes(record);
|
|
19576
20589
|
if (encodedBytes2 > COMMIT_SNAPSHOT_LOG_BYTE_BUDGET) {
|
|
19577
|
-
const
|
|
20590
|
+
const error11 = new Error(
|
|
19578
20591
|
`Commit snapshot log is ${encodedBytes2} bytes; budget is ${COMMIT_SNAPSHOT_LOG_BYTE_BUDGET} bytes`
|
|
19579
20592
|
);
|
|
19580
|
-
|
|
19581
|
-
|
|
19582
|
-
throw
|
|
20593
|
+
error11.code = "commit-snapshot-log-size";
|
|
20594
|
+
error11.encodedBytes = encodedBytes2;
|
|
20595
|
+
throw error11;
|
|
19583
20596
|
}
|
|
19584
20597
|
return encodedBytes2;
|
|
19585
20598
|
}
|
|
@@ -19598,8 +20611,8 @@ function createCommitTraceConsoleSink(options = {}) {
|
|
|
19598
20611
|
if (record === void 0) return;
|
|
19599
20612
|
try {
|
|
19600
20613
|
assertCommitSnapshotLogSize(record);
|
|
19601
|
-
} catch (
|
|
19602
|
-
const encodedBytes2 = typeof
|
|
20614
|
+
} catch (error11) {
|
|
20615
|
+
const encodedBytes2 = typeof error11 === "object" && error11 !== null && "encodedBytes" in error11 && typeof error11.encodedBytes === "number" ? error11.encodedBytes : encodedCommitSnapshotLogBytes(record);
|
|
19603
20616
|
reportRejectedSize(Object.freeze({
|
|
19604
20617
|
schema: COMMIT_SNAPSHOT_LOG_SCHEMA,
|
|
19605
20618
|
event: "commit.snapshot-size-rejected",
|
|
@@ -19608,41 +20621,107 @@ function createCommitTraceConsoleSink(options = {}) {
|
|
|
19608
20621
|
encodedBytes: encodedBytes2,
|
|
19609
20622
|
byteBudget: COMMIT_SNAPSHOT_LOG_BYTE_BUDGET
|
|
19610
20623
|
}));
|
|
19611
|
-
throw
|
|
20624
|
+
throw error11;
|
|
19612
20625
|
}
|
|
19613
20626
|
log(record);
|
|
19614
20627
|
}
|
|
19615
20628
|
});
|
|
19616
20629
|
}
|
|
19617
20630
|
|
|
19618
|
-
// src/
|
|
19619
|
-
|
|
19620
|
-
|
|
19621
|
-
|
|
19622
|
-
|
|
19623
|
-
|
|
20631
|
+
// src/shard/ShardRotation.ts
|
|
20632
|
+
var ShardRotationError = class extends Error {
|
|
20633
|
+
diagnostic;
|
|
20634
|
+
constructor(diagnostic) {
|
|
20635
|
+
super(diagnostic.code);
|
|
20636
|
+
this.name = "ShardRotationError";
|
|
20637
|
+
this.diagnostic = diagnostic;
|
|
20638
|
+
}
|
|
20639
|
+
};
|
|
20640
|
+
function fail(code, path, reason) {
|
|
20641
|
+
throw new ShardRotationError({ code, path, reason });
|
|
19624
20642
|
}
|
|
19625
|
-
function
|
|
19626
|
-
|
|
20643
|
+
function createRotationState(active2) {
|
|
20644
|
+
if (typeof active2 !== "string" || active2.length === 0) fail("ACTIVE_COUNT", "active", "active-missing");
|
|
20645
|
+
return { sealed: [], active: active2, ledger: [] };
|
|
19627
20646
|
}
|
|
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;
|
|
20647
|
+
function assertStrictlyBefore(lastD1_1, firstD1_2) {
|
|
20648
|
+
assertSortableUniqueId(lastD1_1);
|
|
20649
|
+
assertSortableUniqueId(firstD1_2);
|
|
20650
|
+
if (compareSortableUniqueId(lastD1_1, firstD1_2) >= 0) {
|
|
20651
|
+
fail("ORDER_INVARIANT", "last(D1-1)<first(D1-2)", "order-invariant");
|
|
19638
20652
|
}
|
|
19639
|
-
|
|
19640
|
-
|
|
19641
|
-
|
|
19642
|
-
|
|
19643
|
-
|
|
20653
|
+
}
|
|
20654
|
+
function maxSortableUniqueId(values) {
|
|
20655
|
+
if (values.length === 0) fail("SEAL_MAX_MISSING", "d1-1", "seal-max-missing");
|
|
20656
|
+
let max = assertSortableUniqueId(values[0]).value;
|
|
20657
|
+
for (let i = 1; i < values.length; i += 1) {
|
|
20658
|
+
const next = assertSortableUniqueId(values[i]).value;
|
|
20659
|
+
if (next > max) max = next;
|
|
19644
20660
|
}
|
|
19645
|
-
return
|
|
20661
|
+
return max;
|
|
20662
|
+
}
|
|
20663
|
+
function rotateAppend(state, sealedId, shardSuids, nextActiveId) {
|
|
20664
|
+
if (sealedId !== state.active) fail("UNKNOWN_SHARD", sealedId, "unknown-shard");
|
|
20665
|
+
if (typeof nextActiveId !== "string" || nextActiveId.length === 0 || nextActiveId === sealedId) {
|
|
20666
|
+
fail("ACTIVE_COUNT", "active", "active-count");
|
|
20667
|
+
}
|
|
20668
|
+
if (state.sealed.some((entry) => entry.id === nextActiveId)) {
|
|
20669
|
+
fail("ACTIVE_COUNT", nextActiveId, "active-already-sealed");
|
|
20670
|
+
}
|
|
20671
|
+
if (state.sealed.some((entry) => entry.id === sealedId)) fail("IDENTITY_SWAP", sealedId, "identity-swap");
|
|
20672
|
+
const lastSuid = maxSortableUniqueId(shardSuids);
|
|
20673
|
+
return {
|
|
20674
|
+
sealed: [...state.sealed, { id: sealedId, lastSuid }],
|
|
20675
|
+
active: nextActiveId,
|
|
20676
|
+
ledger: state.ledger
|
|
20677
|
+
};
|
|
20678
|
+
}
|
|
20679
|
+
function replaceSealedIdentity(state, index, nextId) {
|
|
20680
|
+
if (index < 0 || index >= state.sealed.length) fail("UNKNOWN_SHARD", "sealed", "unknown-shard");
|
|
20681
|
+
fail("IDENTITY_SWAP", state.sealed[index].id, `identity-swap:${nextId}`);
|
|
20682
|
+
}
|
|
20683
|
+
function refuseSealedWrite(state, shardId) {
|
|
20684
|
+
if (state.sealed.some((entry) => entry.id === shardId)) fail("SEALED_WRITE", shardId, "sealed-write");
|
|
20685
|
+
}
|
|
20686
|
+
function admitActiveWrite(state, shardId, firstOrNextSuid) {
|
|
20687
|
+
assertSortableUniqueId(firstOrNextSuid);
|
|
20688
|
+
refuseSealedWrite(state, shardId);
|
|
20689
|
+
if (shardId !== state.active) fail("UNKNOWN_SHARD", shardId, "unknown-shard");
|
|
20690
|
+
if (state.sealed.length === 0) return;
|
|
20691
|
+
const previous = state.sealed[state.sealed.length - 1];
|
|
20692
|
+
assertStrictlyBefore(previous.lastSuid, firstOrNextSuid);
|
|
20693
|
+
}
|
|
20694
|
+
function recordLateArrival(state, sealedShardId, suid) {
|
|
20695
|
+
assertSortableUniqueId(suid);
|
|
20696
|
+
const sealed = state.sealed.find((entry) => entry.id === sealedShardId);
|
|
20697
|
+
if (sealed === void 0) fail("UNKNOWN_SHARD", sealedShardId, "unknown-shard");
|
|
20698
|
+
if (compareSortableUniqueId(suid, sealed.lastSuid) > 0) {
|
|
20699
|
+
fail("ORDER_INVARIANT", sealedShardId, "not-late-arrival");
|
|
20700
|
+
}
|
|
20701
|
+
return {
|
|
20702
|
+
...state,
|
|
20703
|
+
ledger: [...state.ledger, { shardId: sealed.id, suid, reason: "sealed-range" }]
|
|
20704
|
+
};
|
|
20705
|
+
}
|
|
20706
|
+
var SILENT_MERGE_TOKENS = [
|
|
20707
|
+
["merge", "Ledger", "Into", "Sealed"],
|
|
20708
|
+
["merge", "OnRead", "Sealed"],
|
|
20709
|
+
["silent", "Merge", "Ledger"],
|
|
20710
|
+
["ledger", ".concat(", "sealed"],
|
|
20711
|
+
["sealed", ".concat(", "ledger"]
|
|
20712
|
+
];
|
|
20713
|
+
function assertNoSilentLedgerMerge(source) {
|
|
20714
|
+
const lowered = source.toLowerCase();
|
|
20715
|
+
for (const token of SILENT_MERGE_TOKENS) {
|
|
20716
|
+
const needle = token.join("").toLowerCase();
|
|
20717
|
+
if (lowered.includes(needle)) fail("SILENT_MERGE", "ledger", "silent-merge");
|
|
20718
|
+
}
|
|
20719
|
+
}
|
|
20720
|
+
function refuseSilentLedgerMerge() {
|
|
20721
|
+
fail("SILENT_MERGE", "ledger", "silent-merge");
|
|
20722
|
+
}
|
|
20723
|
+
function refuseSafeWindowSeal() {
|
|
20724
|
+
fail("SAFEWINDOW_SEAL", "safe-window", "safewindow-seal");
|
|
19646
20725
|
}
|
|
19647
20726
|
|
|
19648
20727
|
// src/index.ts
|
|
@@ -19652,6 +20731,10 @@ function createRuntimeWorker(options = {}) {
|
|
|
19652
20731
|
const storeProvider = options.storeProvider ?? POSTGRES_STORE_PROVIDER;
|
|
19653
20732
|
return {
|
|
19654
20733
|
async fetch(request, env, ctx) {
|
|
20734
|
+
const url = new URL(request.url);
|
|
20735
|
+
if (isIncidentMaintenancePath(url.pathname)) {
|
|
20736
|
+
return handleIncidentMaintenance(request, env, options.serviceIdentityProvider ?? envServiceIdentity(env));
|
|
20737
|
+
}
|
|
19655
20738
|
const serviceIdentity = options.serviceIdentityProvider ?? envServiceIdentity(env);
|
|
19656
20739
|
const requestIdentityOptions = { allowG11Verification: env.G11_VERIFICATION_ENABLED === "true" };
|
|
19657
20740
|
let requestServiceId;
|
|
@@ -19660,7 +20743,6 @@ function createRuntimeWorker(options = {}) {
|
|
|
19660
20743
|
} catch {
|
|
19661
20744
|
return scopeIdentityMissingResponse();
|
|
19662
20745
|
}
|
|
19663
|
-
const url = new URL(request.url);
|
|
19664
20746
|
if (url.pathname === "/api/sekiban/serialized/commit") {
|
|
19665
20747
|
return handleSerializedCommit(request, env, {
|
|
19666
20748
|
domainDeliveryClass: options.config?.deliveryClass,
|
|
@@ -19689,7 +20771,7 @@ function createRuntimeWorker(options = {}) {
|
|
|
19689
20771
|
return handleOperatorBootstrap(request, env, storeProvider);
|
|
19690
20772
|
}
|
|
19691
20773
|
if (url.pathname === "/internal/downstream/drain" && request.method === "POST") {
|
|
19692
|
-
return handleOutboxDrainRequest(request, env);
|
|
20774
|
+
return handleOutboxDrainRequest(request, env, { serviceIdentityProvider: serviceIdentity });
|
|
19693
20775
|
}
|
|
19694
20776
|
if (url.pathname === "/internal/projection/lag") {
|
|
19695
20777
|
return handleProjectionLag(request, env, composition.projectors, storeProvider, serviceIdentity);
|
|
@@ -19822,6 +20904,8 @@ export {
|
|
|
19822
20904
|
GLOBAL_COMPLETENESS_INTERIM_DISPOSITION,
|
|
19823
20905
|
GlobalCompletenessReconciler,
|
|
19824
20906
|
IDLE_EXPERIMENT_SCHEDULE_MS,
|
|
20907
|
+
IncidentLifecycle,
|
|
20908
|
+
IncidentLifecycleError,
|
|
19825
20909
|
JournalDurableObject,
|
|
19826
20910
|
MAX_PUBLISHED_SAFE_WINDOW_MS,
|
|
19827
20911
|
MAX_SERVICE_BINDING_INVOCATIONS_PER_REQUEST,
|
|
@@ -19842,17 +20926,21 @@ export {
|
|
|
19842
20926
|
SORTABLE_UNIQUE_ID_TICKS_DIGITS,
|
|
19843
20927
|
ScopeNameError,
|
|
19844
20928
|
ServiceIdentityMissingError,
|
|
20929
|
+
ShardRotationError,
|
|
19845
20930
|
SortableUniqueIdError,
|
|
19846
20931
|
TEST_SERVICE_ID_HEADER,
|
|
19847
20932
|
TagDurableObject,
|
|
19848
20933
|
TagStateDurableObject,
|
|
19849
20934
|
UnsafeWindowMaterializedViewError,
|
|
19850
20935
|
UnsafeWindowMaterializedViewStore,
|
|
20936
|
+
admitActiveWrite,
|
|
19851
20937
|
allocateOrderRange,
|
|
19852
20938
|
assertCanonicalEventType,
|
|
19853
20939
|
assertCommitSnapshotLogSize,
|
|
19854
20940
|
assertDotNetTicks,
|
|
20941
|
+
assertNoSilentLedgerMerge,
|
|
19855
20942
|
assertSortableUniqueId,
|
|
20943
|
+
assertStrictlyBefore,
|
|
19856
20944
|
beginWorkerInvocationObservation,
|
|
19857
20945
|
bootstrapDigest,
|
|
19858
20946
|
buildScopeName,
|
|
@@ -19869,6 +20957,7 @@ export {
|
|
|
19869
20957
|
createCommitTraceConsoleSink,
|
|
19870
20958
|
createPostgresBootstrapAdapter,
|
|
19871
20959
|
createPostgresStoreProvider,
|
|
20960
|
+
createRotationState,
|
|
19872
20961
|
createRuntimeCommitPort,
|
|
19873
20962
|
createRuntimeWorker,
|
|
19874
20963
|
createTraceCorrelationId,
|
|
@@ -19893,8 +20982,10 @@ export {
|
|
|
19893
20982
|
isScopeServiceId,
|
|
19894
20983
|
isSortableUniqueId,
|
|
19895
20984
|
isUuidV7,
|
|
20985
|
+
maxSortableUniqueId,
|
|
19896
20986
|
nativeTracingFromContext,
|
|
19897
20987
|
noOpNativeTracing,
|
|
20988
|
+
normalizeIncidentIdentity,
|
|
19898
20989
|
observeLegacySortableUniqueIdDecision,
|
|
19899
20990
|
observedIdleGapLowerBoundMs,
|
|
19900
20991
|
parseBootstrapDump,
|
|
@@ -19904,10 +20995,16 @@ export {
|
|
|
19904
20995
|
processDownstreamDoorbell,
|
|
19905
20996
|
readDirectDoorbellConfig,
|
|
19906
20997
|
readDomainDeliveryClass,
|
|
20998
|
+
recordLateArrival,
|
|
20999
|
+
refuseSafeWindowSeal,
|
|
21000
|
+
refuseSealedWrite,
|
|
21001
|
+
refuseSilentLedgerMerge,
|
|
19907
21002
|
registeredEventParsers,
|
|
21003
|
+
replaceSealedIdentity,
|
|
19908
21004
|
requestServiceIdentity,
|
|
19909
21005
|
requireServiceIdentity,
|
|
19910
21006
|
resolveDeliveryIdentity,
|
|
21007
|
+
rotateAppend,
|
|
19911
21008
|
safeWindowCeilingExceeded,
|
|
19912
21009
|
safeWindowCutoffSuid,
|
|
19913
21010
|
safeWindowMs,
|