@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/cloudflare.js
CHANGED
|
@@ -926,10 +926,10 @@ var systemOrderClock = Object.freeze({
|
|
|
926
926
|
}
|
|
927
927
|
});
|
|
928
928
|
var ORDER_SUID_LIMIT = DOTNET_MAX_TICKS + 1n;
|
|
929
|
-
function asOrderClockError(
|
|
930
|
-
if (
|
|
931
|
-
const detail =
|
|
932
|
-
return new OrderClockReadError(detail, { cause:
|
|
929
|
+
function asOrderClockError(error11) {
|
|
930
|
+
if (error11 instanceof OrderClockReadError) return error11;
|
|
931
|
+
const detail = error11 instanceof Error ? error11.message : "SortableUniqueId allocation failed";
|
|
932
|
+
return new OrderClockReadError(detail, { cause: error11 });
|
|
933
933
|
}
|
|
934
934
|
function allocateOrderRange(watermark, count, clockTick, options = {}) {
|
|
935
935
|
if (!Number.isSafeInteger(count) || count <= 0) {
|
|
@@ -958,9 +958,9 @@ function allocateOrderRange(watermark, count, clockTick, options = {}) {
|
|
|
958
958
|
watermark: suids.at(-1),
|
|
959
959
|
suids: Object.freeze(suids)
|
|
960
960
|
});
|
|
961
|
-
} catch (
|
|
962
|
-
if (
|
|
963
|
-
throw asOrderClockError(
|
|
961
|
+
} catch (error11) {
|
|
962
|
+
if (error11 instanceof SortableUniqueIdError) throw error11;
|
|
963
|
+
throw asOrderClockError(error11);
|
|
964
964
|
}
|
|
965
965
|
}
|
|
966
966
|
function diagnosticAllocatedAt(ticks) {
|
|
@@ -978,7 +978,7 @@ function diagnosticAllocatedAt(ticks) {
|
|
|
978
978
|
var commit_trace_manifest_default = {
|
|
979
979
|
schemaVersion: 2,
|
|
980
980
|
name: "commit-trace-manifest",
|
|
981
|
-
description: "
|
|
981
|
+
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.",
|
|
982
982
|
remoteInvocationUniverse: [
|
|
983
983
|
"S16"
|
|
984
984
|
],
|
|
@@ -3391,9 +3391,9 @@ var commit_trace_manifest_default = {
|
|
|
3391
3391
|
|
|
3392
3392
|
// src/trace/AttributionRatio.ts
|
|
3393
3393
|
function failure(code, message) {
|
|
3394
|
-
const
|
|
3395
|
-
|
|
3396
|
-
throw
|
|
3394
|
+
const error11 = new Error(`${code}:${message}`);
|
|
3395
|
+
error11.name = "CommitTraceAttributionError";
|
|
3396
|
+
throw error11;
|
|
3397
3397
|
}
|
|
3398
3398
|
function clip(interval, root) {
|
|
3399
3399
|
const start = Math.max(interval[0], root[0]);
|
|
@@ -3706,8 +3706,8 @@ var CommitTrace = class {
|
|
|
3706
3706
|
unattributed,
|
|
3707
3707
|
attributionGatePassed: unattributed.unattributedRatio <= 0.05
|
|
3708
3708
|
});
|
|
3709
|
-
} catch (
|
|
3710
|
-
this.noteObservationFailure(
|
|
3709
|
+
} catch (error11) {
|
|
3710
|
+
this.noteObservationFailure(error11);
|
|
3711
3711
|
return Object.freeze({
|
|
3712
3712
|
...base,
|
|
3713
3713
|
runtimeVerification: this.observationVerification()
|
|
@@ -3774,9 +3774,9 @@ var CommitTrace = class {
|
|
|
3774
3774
|
throw new Error(`Commit trace root ${rowId} is not active`);
|
|
3775
3775
|
}
|
|
3776
3776
|
attributes = this.attributesFor(row, options);
|
|
3777
|
-
} catch (
|
|
3778
|
-
if (!this.isFailOpen()) throw
|
|
3779
|
-
this.noteObservationFailure(
|
|
3777
|
+
} catch (error11) {
|
|
3778
|
+
if (!this.isFailOpen()) throw error11;
|
|
3779
|
+
this.noteObservationFailure(error11);
|
|
3780
3780
|
return callback(disabledChild());
|
|
3781
3781
|
}
|
|
3782
3782
|
const execute = async (native2) => {
|
|
@@ -3902,8 +3902,8 @@ var CommitTrace = class {
|
|
|
3902
3902
|
for (const [key, value] of Object.entries(attributes)) {
|
|
3903
3903
|
try {
|
|
3904
3904
|
native.setAttribute(key, value);
|
|
3905
|
-
} catch (
|
|
3906
|
-
this.noteObservationFailure(
|
|
3905
|
+
} catch (error11) {
|
|
3906
|
+
this.noteObservationFailure(error11);
|
|
3907
3907
|
}
|
|
3908
3908
|
}
|
|
3909
3909
|
}
|
|
@@ -3920,8 +3920,8 @@ var CommitTrace = class {
|
|
|
3920
3920
|
now() {
|
|
3921
3921
|
try {
|
|
3922
3922
|
return this.clock().now();
|
|
3923
|
-
} catch (
|
|
3924
|
-
this.noteObservationFailure(
|
|
3923
|
+
} catch (error11) {
|
|
3924
|
+
this.noteObservationFailure(error11);
|
|
3925
3925
|
return Date.now();
|
|
3926
3926
|
}
|
|
3927
3927
|
}
|
|
@@ -3929,8 +3929,8 @@ var CommitTrace = class {
|
|
|
3929
3929
|
let beforeVerification;
|
|
3930
3930
|
try {
|
|
3931
3931
|
beforeVerification = this.snapshot();
|
|
3932
|
-
} catch (
|
|
3933
|
-
this.noteObservationFailure(
|
|
3932
|
+
} catch (error11) {
|
|
3933
|
+
this.noteObservationFailure(error11);
|
|
3934
3934
|
return;
|
|
3935
3935
|
}
|
|
3936
3936
|
if (this.options.runtimeVerifier !== void 0) {
|
|
@@ -3941,15 +3941,15 @@ var CommitTrace = class {
|
|
|
3941
3941
|
this.options.runtimeVerifier.verify(beforeVerification);
|
|
3942
3942
|
this.runtimeVerification = Object.freeze({ passed: true });
|
|
3943
3943
|
}
|
|
3944
|
-
} catch (
|
|
3945
|
-
const code = typeof
|
|
3944
|
+
} catch (error11) {
|
|
3945
|
+
const code = typeof error11 === "object" && error11 !== null && "code" in error11 && typeof error11.code === "string" ? error11.code : "runtime-verifier-error";
|
|
3946
3946
|
this.runtimeVerification = Object.freeze({ passed: false, code });
|
|
3947
3947
|
}
|
|
3948
3948
|
}
|
|
3949
3949
|
try {
|
|
3950
3950
|
this.options.sink?.record(this.snapshot());
|
|
3951
|
-
} catch (
|
|
3952
|
-
this.noteObservationFailure(
|
|
3951
|
+
} catch (error11) {
|
|
3952
|
+
this.noteObservationFailure(error11);
|
|
3953
3953
|
}
|
|
3954
3954
|
}
|
|
3955
3955
|
/**
|
|
@@ -3961,8 +3961,8 @@ var CommitTrace = class {
|
|
|
3961
3961
|
async scheduleDetachedObservation() {
|
|
3962
3962
|
try {
|
|
3963
3963
|
await this.options.scheduleDetachedObservation?.();
|
|
3964
|
-
} catch (
|
|
3965
|
-
this.noteObservationFailure(
|
|
3964
|
+
} catch (error11) {
|
|
3965
|
+
this.noteObservationFailure(error11);
|
|
3966
3966
|
}
|
|
3967
3967
|
}
|
|
3968
3968
|
isFailOpen() {
|
|
@@ -3971,9 +3971,9 @@ var CommitTrace = class {
|
|
|
3971
3971
|
observationVerification() {
|
|
3972
3972
|
return Object.freeze({ passed: false, code: this.observationFailureCode ?? "trace-observation-error" });
|
|
3973
3973
|
}
|
|
3974
|
-
noteObservationFailure(
|
|
3974
|
+
noteObservationFailure(error11) {
|
|
3975
3975
|
if (this.observationFailureCode !== void 0) return;
|
|
3976
|
-
this.observationFailureCode = typeof
|
|
3976
|
+
this.observationFailureCode = typeof error11 === "object" && error11 !== null && "code" in error11 && typeof error11.code === "string" ? error11.code : "trace-observation-error";
|
|
3977
3977
|
this.runtimeVerification = this.observationVerification();
|
|
3978
3978
|
}
|
|
3979
3979
|
/**
|
|
@@ -3994,9 +3994,9 @@ var CommitTrace = class {
|
|
|
3994
3994
|
return callback(void 0);
|
|
3995
3995
|
}
|
|
3996
3996
|
return await result2;
|
|
3997
|
-
} catch (
|
|
3998
|
-
if (callbackStarted) throw
|
|
3999
|
-
this.noteObservationFailure(
|
|
3997
|
+
} catch (error11) {
|
|
3998
|
+
if (callbackStarted) throw error11;
|
|
3999
|
+
this.noteObservationFailure(error11);
|
|
4000
4000
|
return callback(void 0);
|
|
4001
4001
|
}
|
|
4002
4002
|
}
|
|
@@ -4085,8 +4085,8 @@ async function enterNativeSpanFailOpen(nativeTracing, name, callback) {
|
|
|
4085
4085
|
const result2 = nativeTracing.enterSpan(name, wrapped);
|
|
4086
4086
|
if (!callbackStarted) return callback(void 0);
|
|
4087
4087
|
return await result2;
|
|
4088
|
-
} catch (
|
|
4089
|
-
if (callbackStarted) throw
|
|
4088
|
+
} catch (error11) {
|
|
4089
|
+
if (callbackStarted) throw error11;
|
|
4090
4090
|
return callback(void 0);
|
|
4091
4091
|
}
|
|
4092
4092
|
}
|
|
@@ -4118,9 +4118,9 @@ async function enterNativeCommitSpan(nativeTracing, name, input, callback) {
|
|
|
4118
4118
|
if (response3 !== void 0 && input.face !== "reconcile-root") setNativeAttribute(span, "http.status", response3.status);
|
|
4119
4119
|
}
|
|
4120
4120
|
return result2;
|
|
4121
|
-
} catch (
|
|
4121
|
+
} catch (error11) {
|
|
4122
4122
|
setNativeAttribute(span, "outcome", "exception");
|
|
4123
|
-
throw
|
|
4123
|
+
throw error11;
|
|
4124
4124
|
}
|
|
4125
4125
|
});
|
|
4126
4126
|
}
|
|
@@ -4159,9 +4159,9 @@ async function enterNativeActorHandleSpan(nativeTracing, input, resolveIdentity,
|
|
|
4159
4159
|
if (response3 !== void 0) setNativeAttribute(span, "http.status", response3.status);
|
|
4160
4160
|
}
|
|
4161
4161
|
return result2;
|
|
4162
|
-
} catch (
|
|
4162
|
+
} catch (error11) {
|
|
4163
4163
|
if (identity2 !== void 0) setNativeAttribute(span, "outcome", "exception");
|
|
4164
|
-
throw
|
|
4164
|
+
throw error11;
|
|
4165
4165
|
} finally {
|
|
4166
4166
|
input.observation?.finish();
|
|
4167
4167
|
}
|
|
@@ -4289,6 +4289,8 @@ var ATTEMPT_KEY_PREFIX2 = "attempt:";
|
|
|
4289
4289
|
var ROLLBACK_WARNING_WINDOW_MS = 1000n;
|
|
4290
4290
|
var AllocationTransactionFault = class extends Error {
|
|
4291
4291
|
};
|
|
4292
|
+
var ImportSeedRequiredError = class extends Error {
|
|
4293
|
+
};
|
|
4292
4294
|
function json(body, status = 200) {
|
|
4293
4295
|
return new Response(JSON.stringify(body), {
|
|
4294
4296
|
status,
|
|
@@ -4328,7 +4330,17 @@ function seedFrom(value) {
|
|
|
4328
4330
|
} catch {
|
|
4329
4331
|
return { error: "highWatermark must be an allocator SUID" };
|
|
4330
4332
|
}
|
|
4331
|
-
|
|
4333
|
+
let storeMaximum;
|
|
4334
|
+
if (value.storeMaximum !== void 0) {
|
|
4335
|
+
if (!isNonEmptyString2(value.storeMaximum)) return { error: "storeMaximum must be an allocator SUID" };
|
|
4336
|
+
try {
|
|
4337
|
+
decodeSuid(value.storeMaximum);
|
|
4338
|
+
} catch {
|
|
4339
|
+
return { error: "storeMaximum must be an allocator SUID" };
|
|
4340
|
+
}
|
|
4341
|
+
storeMaximum = value.storeMaximum;
|
|
4342
|
+
}
|
|
4343
|
+
return { value: { importId: value.importId, leaseEpoch: value.leaseEpoch, highWatermark: value.highWatermark, ...storeMaximum !== void 0 ? { storeMaximum } : {} } };
|
|
4332
4344
|
}
|
|
4333
4345
|
function newAllocatorLineageId() {
|
|
4334
4346
|
return crypto.randomUUID();
|
|
@@ -4383,7 +4395,8 @@ function allocateFrom(value) {
|
|
|
4383
4395
|
faultInjection,
|
|
4384
4396
|
...serviceId !== void 0 ? { serviceId } : {},
|
|
4385
4397
|
...bootstrapCommandId !== void 0 ? { bootstrapCommandId } : {},
|
|
4386
|
-
...bootstrapEpoch !== void 0 ? { bootstrapEpoch } : {}
|
|
4398
|
+
...bootstrapEpoch !== void 0 ? { bootstrapEpoch } : {},
|
|
4399
|
+
...value.requiresImportSeed === true ? { requiresImportSeed: true } : {}
|
|
4387
4400
|
}
|
|
4388
4401
|
};
|
|
4389
4402
|
}
|
|
@@ -4768,11 +4781,12 @@ var AllocatorDurableObject = class {
|
|
|
4768
4781
|
};
|
|
4769
4782
|
}
|
|
4770
4783
|
const state = persistedState === void 0 ? currentState(lineage) : { ...persistedState, allocatorLineageId: lineage, schemaVersion: 5, bootstrapSeed: persistedState.bootstrapSeed ?? null, lastRollbackWarningFingerprint: persistedState.lastRollbackWarningFingerprint ?? null };
|
|
4784
|
+
if (input.requiresImportSeed === true && state.bootstrapSeed === null) throw new ImportSeedRequiredError();
|
|
4771
4785
|
let clockTick;
|
|
4772
4786
|
try {
|
|
4773
4787
|
clockTick = this.orderClock.tick();
|
|
4774
|
-
} catch (
|
|
4775
|
-
throw
|
|
4788
|
+
} catch (error11) {
|
|
4789
|
+
throw error11 instanceof OrderClockReadError ? error11 : new OrderClockReadError("Order clock failed before allocation write", { cause: error11 });
|
|
4776
4790
|
}
|
|
4777
4791
|
const range = allocateOrderRange(state.allocatedWatermark, input.candidates.length, clockTick);
|
|
4778
4792
|
const rollback = range.observedTicks !== null && range.physicalTicks < range.observedTicks;
|
|
@@ -4846,6 +4860,9 @@ var AllocatorDurableObject = class {
|
|
|
4846
4860
|
}
|
|
4847
4861
|
return json(result2.vector, result2.created ? 201 : 200);
|
|
4848
4862
|
} catch (failure3) {
|
|
4863
|
+
if (failure3 instanceof ImportSeedRequiredError) {
|
|
4864
|
+
return error(409, "allocator_seed_required", "commit before import seed");
|
|
4865
|
+
}
|
|
4849
4866
|
if (failure3 instanceof AllocationTransactionFault) {
|
|
4850
4867
|
return error(
|
|
4851
4868
|
503,
|
|
@@ -4886,7 +4903,10 @@ var AllocatorDurableObject = class {
|
|
|
4886
4903
|
return { status: 409, body: { code: "allocator_seed_rejected", error: "allocator already seeded" } };
|
|
4887
4904
|
}
|
|
4888
4905
|
if (state.allocatedWatermark !== null) return { status: 409, body: { code: "allocator_seed_rejected", error: "allocator already allocating" } };
|
|
4889
|
-
|
|
4906
|
+
if (input.storeMaximum !== void 0 && input.highWatermark < input.storeMaximum) {
|
|
4907
|
+
return { status: 409, body: { code: "allocator_seed_below_store_max", error: "seed below store maximum" } };
|
|
4908
|
+
}
|
|
4909
|
+
const updated = { ...state, allocatedWatermark: input.highWatermark, bootstrapSeed: { importId: input.importId, leaseEpoch: input.leaseEpoch, highWatermark: input.highWatermark } };
|
|
4890
4910
|
await txn.put(STATE_KEY, updated);
|
|
4891
4911
|
return { status: 201, body: updated };
|
|
4892
4912
|
});
|
|
@@ -4911,7 +4931,7 @@ var isPlainRecord = (value) => {
|
|
|
4911
4931
|
return prototype === Object.prototype || prototype === null;
|
|
4912
4932
|
};
|
|
4913
4933
|
function assertJsonValue(value, boundary = "value") {
|
|
4914
|
-
const
|
|
4934
|
+
const active2 = /* @__PURE__ */ new WeakSet();
|
|
4915
4935
|
const visit = (candidate, path) => {
|
|
4916
4936
|
if (candidate === null || typeof candidate === "string" || typeof candidate === "boolean") {
|
|
4917
4937
|
return candidate;
|
|
@@ -4923,10 +4943,10 @@ function assertJsonValue(value, boundary = "value") {
|
|
|
4923
4943
|
if (typeof candidate !== "object") {
|
|
4924
4944
|
throw new JsonValidationError(`Value at ${path} is not JSON serializable`, boundary, path);
|
|
4925
4945
|
}
|
|
4926
|
-
if (
|
|
4946
|
+
if (active2.has(candidate)) {
|
|
4927
4947
|
throw new JsonValidationError(`Cyclic JSON value at ${path}`, boundary, path);
|
|
4928
4948
|
}
|
|
4929
|
-
|
|
4949
|
+
active2.add(candidate);
|
|
4930
4950
|
try {
|
|
4931
4951
|
if (Array.isArray(candidate)) {
|
|
4932
4952
|
return candidate.map((item, index) => visit(item, `${path}[${index}]`));
|
|
@@ -4940,7 +4960,7 @@ function assertJsonValue(value, boundary = "value") {
|
|
|
4940
4960
|
}
|
|
4941
4961
|
return result2;
|
|
4942
4962
|
} finally {
|
|
4943
|
-
|
|
4963
|
+
active2.delete(candidate);
|
|
4944
4964
|
}
|
|
4945
4965
|
};
|
|
4946
4966
|
return visit(value, "$");
|
|
@@ -5035,16 +5055,16 @@ function createUuidV7(nowMs = Date.now()) {
|
|
|
5035
5055
|
if (typeof globalThis.crypto?.getRandomValues !== "function") {
|
|
5036
5056
|
throw new Error("crypto.getRandomValues is required for UUIDv7");
|
|
5037
5057
|
}
|
|
5038
|
-
const
|
|
5039
|
-
globalThis.crypto.getRandomValues(
|
|
5058
|
+
const bytes2 = new Uint8Array(16);
|
|
5059
|
+
globalThis.crypto.getRandomValues(bytes2);
|
|
5040
5060
|
let time = BigInt(nowMs);
|
|
5041
5061
|
for (let index = 5; index >= 0; index -= 1) {
|
|
5042
|
-
|
|
5062
|
+
bytes2[index] = Number(time & 0xffn);
|
|
5043
5063
|
time >>= 8n;
|
|
5044
5064
|
}
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
const hex = [...
|
|
5065
|
+
bytes2[6] = bytes2[6] & 15 | 112;
|
|
5066
|
+
bytes2[8] = bytes2[8] & 63 | 128;
|
|
5067
|
+
const hex = [...bytes2].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
5048
5068
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
5049
5069
|
}
|
|
5050
5070
|
function isRfc4122Uuid(value) {
|
|
@@ -5160,7 +5180,7 @@ var epoch = (value) => typeof value === "number" && Number.isSafeInteger(value)
|
|
|
5160
5180
|
var response = (body, status = 200) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json; charset=utf-8" } });
|
|
5161
5181
|
var reject = (code, message, status = 409) => response({ code, error: message }, status);
|
|
5162
5182
|
function empty(serviceId) {
|
|
5163
|
-
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 };
|
|
5183
|
+
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 };
|
|
5164
5184
|
}
|
|
5165
5185
|
function expired(control) {
|
|
5166
5186
|
return control.leaseUntil !== null && control.leaseUntil <= Date.now();
|
|
@@ -5171,12 +5191,22 @@ function commands(control) {
|
|
|
5171
5191
|
function released(control) {
|
|
5172
5192
|
return { ...control.releasedCommands ?? {} };
|
|
5173
5193
|
}
|
|
5194
|
+
function permits(control) {
|
|
5195
|
+
return { ...control.writePermits ?? {} };
|
|
5196
|
+
}
|
|
5174
5197
|
function fault(body, point) {
|
|
5175
5198
|
return object(body) && body.faultAt === point;
|
|
5176
5199
|
}
|
|
5177
5200
|
function encodedBytes(value) {
|
|
5178
5201
|
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
5179
5202
|
}
|
|
5203
|
+
async function readBody(request) {
|
|
5204
|
+
try {
|
|
5205
|
+
return await request.json();
|
|
5206
|
+
} catch {
|
|
5207
|
+
return void 0;
|
|
5208
|
+
}
|
|
5209
|
+
}
|
|
5180
5210
|
var BootstrapCoordinatorDurableObject = class {
|
|
5181
5211
|
constructor(ctx, env, nativeTracing = noOpNativeTracing) {
|
|
5182
5212
|
this.ctx = ctx;
|
|
@@ -5198,6 +5228,8 @@ var BootstrapCoordinatorDurableObject = class {
|
|
|
5198
5228
|
if (request.method === "POST" && url.pathname === "/command/admit") return this.traceCommand(request, serviceId, activation2, observation, "admit");
|
|
5199
5229
|
if (request.method === "POST" && url.pathname === "/command/finalize") return this.traceCommand(request, serviceId, activation2, observation, "finalize");
|
|
5200
5230
|
if (request.method === "POST" && url.pathname === "/command/release") return this.traceCommand(request, serviceId, activation2, observation, "release");
|
|
5231
|
+
if (request.method === "POST" && url.pathname === "/command/permit") return this.permit(serviceId, await readBody(request));
|
|
5232
|
+
if (request.method === "POST" && url.pathname === "/command/permit-release") return this.permitRelease(serviceId, await readBody(request));
|
|
5201
5233
|
let body;
|
|
5202
5234
|
try {
|
|
5203
5235
|
body = await request.json();
|
|
@@ -5254,6 +5286,7 @@ var BootstrapCoordinatorDurableObject = class {
|
|
|
5254
5286
|
const control = await txn.get(CONTROL) ?? empty(serviceId);
|
|
5255
5287
|
if (control.status === "READY") return { error: "bootstrap_ready_permanent" };
|
|
5256
5288
|
if (control.status !== "EMPTY") return control.importId === importId && control.digest === dump.manifest.contentDigest ? { control } : { error: "bootstrap_plan_conflict" };
|
|
5289
|
+
if (Object.keys(permits(control)).length !== 0) return { error: "bootstrap_write_permit_active" };
|
|
5257
5290
|
if (control.normalInFlight !== 0 || Object.keys(commands(control)).length !== 0) return { error: "bootstrap_normal_command_in_flight" };
|
|
5258
5291
|
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 };
|
|
5259
5292
|
await txn.put(CONTROL, next);
|
|
@@ -5266,17 +5299,17 @@ var BootstrapCoordinatorDurableObject = class {
|
|
|
5266
5299
|
chunks(events) {
|
|
5267
5300
|
const chunks = [];
|
|
5268
5301
|
let current = [];
|
|
5269
|
-
let
|
|
5302
|
+
let bytes2 = 0;
|
|
5270
5303
|
for (const event of events) {
|
|
5271
5304
|
const eventBytes = encodedBytes(event);
|
|
5272
5305
|
if (eventBytes > MAX_CHUNK_BYTES) throw new Error("bootstrap event exceeds encoded chunk bound");
|
|
5273
|
-
if (current.length === MAX_CHUNK_EVENTS ||
|
|
5306
|
+
if (current.length === MAX_CHUNK_EVENTS || bytes2 + eventBytes > MAX_CHUNK_BYTES) {
|
|
5274
5307
|
chunks.push(current);
|
|
5275
5308
|
current = [];
|
|
5276
|
-
|
|
5309
|
+
bytes2 = 0;
|
|
5277
5310
|
}
|
|
5278
5311
|
current.push(event);
|
|
5279
|
-
|
|
5312
|
+
bytes2 += eventBytes;
|
|
5280
5313
|
}
|
|
5281
5314
|
if (current.length > 0) chunks.push(current);
|
|
5282
5315
|
return chunks;
|
|
@@ -5422,6 +5455,45 @@ var BootstrapCoordinatorDurableObject = class {
|
|
|
5422
5455
|
const control = await this.control(serviceId);
|
|
5423
5456
|
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`);
|
|
5424
5457
|
}
|
|
5458
|
+
async permit(serviceId, body) {
|
|
5459
|
+
if (!object(body) || !string(body.commandId) || !string(body.digest)) return reject("bootstrap_permit_invalid", "commandId and digest are required", 400);
|
|
5460
|
+
const commandId = body.commandId;
|
|
5461
|
+
const digest2 = body.digest;
|
|
5462
|
+
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
5463
|
+
const control = await txn.get(CONTROL) ?? empty(serviceId);
|
|
5464
|
+
const active2 = permits(control);
|
|
5465
|
+
if (active2[commandId] !== void 0) {
|
|
5466
|
+
return active2[commandId] === digest2 ? { control } : { digestMismatch: true };
|
|
5467
|
+
}
|
|
5468
|
+
if (control.status !== "EMPTY" && control.status !== "READY") return { rejected: true };
|
|
5469
|
+
const updated = { ...control, writePermits: { ...active2, [commandId]: digest2 } };
|
|
5470
|
+
await txn.put(CONTROL, updated);
|
|
5471
|
+
return { control: updated };
|
|
5472
|
+
});
|
|
5473
|
+
if ("digestMismatch" in result2) return reject("bootstrap_permit_digest_mismatch", "bootstrap permit digest does not match the held permit");
|
|
5474
|
+
if ("rejected" in result2) return reject("bootstrap_command_rejected", "bootstrap epoch rejects normal durable mutation");
|
|
5475
|
+
return response({ leaseEpoch: result2.control.leaseEpoch, admitted: true });
|
|
5476
|
+
}
|
|
5477
|
+
async permitRelease(serviceId, body) {
|
|
5478
|
+
if (!object(body) || !string(body.commandId)) return reject("bootstrap_permit_invalid", "commandId is required", 400);
|
|
5479
|
+
const commandId = body.commandId;
|
|
5480
|
+
const digest2 = string(body.digest) ? body.digest : void 0;
|
|
5481
|
+
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
5482
|
+
const control = await txn.get(CONTROL) ?? empty(serviceId);
|
|
5483
|
+
const active2 = permits(control);
|
|
5484
|
+
if (active2[commandId] !== void 0 && digest2 !== void 0 && active2[commandId] !== digest2) {
|
|
5485
|
+
return { digestMismatch: true };
|
|
5486
|
+
}
|
|
5487
|
+
if (active2[commandId] === void 0) return { control };
|
|
5488
|
+
const next = { ...active2 };
|
|
5489
|
+
delete next[commandId];
|
|
5490
|
+
const updated = { ...control, writePermits: next };
|
|
5491
|
+
await txn.put(CONTROL, updated);
|
|
5492
|
+
return { control: updated };
|
|
5493
|
+
});
|
|
5494
|
+
if ("digestMismatch" in result2) return reject("bootstrap_permit_digest_mismatch", "bootstrap permit digest does not match the held permit");
|
|
5495
|
+
return response({ leaseEpoch: result2.control.leaseEpoch, admitted: true });
|
|
5496
|
+
}
|
|
5425
5497
|
async command(serviceId, body, action, observation) {
|
|
5426
5498
|
if (!object(body) || !string(body.commandId)) return reject("bootstrap_command_invalid", "commandId is required", 400);
|
|
5427
5499
|
const commandId = body.commandId;
|
|
@@ -5487,13 +5559,13 @@ function byteCompare(left, right) {
|
|
|
5487
5559
|
}
|
|
5488
5560
|
function concat(parts) {
|
|
5489
5561
|
const length = parts.reduce((total, part) => total + part.byteLength, 0);
|
|
5490
|
-
const
|
|
5562
|
+
const bytes2 = new Uint8Array(length);
|
|
5491
5563
|
let offset = 0;
|
|
5492
5564
|
for (const part of parts) {
|
|
5493
|
-
|
|
5565
|
+
bytes2.set(part, offset);
|
|
5494
5566
|
offset += part.byteLength;
|
|
5495
5567
|
}
|
|
5496
|
-
return
|
|
5568
|
+
return bytes2;
|
|
5497
5569
|
}
|
|
5498
5570
|
function lengthPrefix(length) {
|
|
5499
5571
|
if (!Number.isSafeInteger(length) || length < 0 || length > 4294967295) {
|
|
@@ -5522,8 +5594,8 @@ function requiredUtf8(input, name) {
|
|
|
5522
5594
|
function tagSetBytes(tags) {
|
|
5523
5595
|
const canonical2 = canonicalDeclaredTagSet(tags);
|
|
5524
5596
|
return concat(canonical2.map((tag) => {
|
|
5525
|
-
const
|
|
5526
|
-
return concat([lengthPrefix(
|
|
5597
|
+
const bytes2 = utf8(tag);
|
|
5598
|
+
return concat([lengthPrefix(bytes2.byteLength), bytes2]);
|
|
5527
5599
|
}));
|
|
5528
5600
|
}
|
|
5529
5601
|
function canonicalDeclaredTagSet(tags) {
|
|
@@ -5593,16 +5665,16 @@ function eventDigestBytes(input) {
|
|
|
5593
5665
|
]);
|
|
5594
5666
|
}
|
|
5595
5667
|
async function eventDigestHex(input) {
|
|
5596
|
-
const
|
|
5597
|
-
const source =
|
|
5598
|
-
const
|
|
5599
|
-
return [...new Uint8Array(
|
|
5668
|
+
const bytes2 = eventDigestBytes(input);
|
|
5669
|
+
const source = bytes2.buffer.slice(bytes2.byteOffset, bytes2.byteOffset + bytes2.byteLength);
|
|
5670
|
+
const digest2 = await crypto.subtle.digest("SHA-256", source);
|
|
5671
|
+
return [...new Uint8Array(digest2)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
5600
5672
|
}
|
|
5601
5673
|
|
|
5602
5674
|
// src/bootstrap/BootstrapStoreAdapter.ts
|
|
5603
|
-
function arrayBufferBase64(
|
|
5675
|
+
function arrayBufferBase64(bytes2) {
|
|
5604
5676
|
let binary = "";
|
|
5605
|
-
for (const byte of
|
|
5677
|
+
for (const byte of bytes2) binary += String.fromCharCode(byte);
|
|
5606
5678
|
return btoa(binary);
|
|
5607
5679
|
}
|
|
5608
5680
|
var BootstrapIdentityConflictError = class extends Error {
|
|
@@ -6713,6 +6785,22 @@ var COMMIT_TEST_FAULTS = [
|
|
|
6713
6785
|
var INITIAL_OWNER_EPOCH = 0;
|
|
6714
6786
|
var MAX_WRITE_ATTEMPTS = 2;
|
|
6715
6787
|
var OUTCOME_UNDETERMINED_ERROR = "Commit outcome is undetermined; reread tag heads and event/query state before retrying because blind retry may create duplicate events.";
|
|
6788
|
+
function commitPermitDigest(allTags, candidates) {
|
|
6789
|
+
const normalized = JSON.stringify({
|
|
6790
|
+
tags: [...allTags].sort(),
|
|
6791
|
+
events: candidates.map((candidate) => ({
|
|
6792
|
+
eventId: candidate.eventId,
|
|
6793
|
+
payload: candidate.payload,
|
|
6794
|
+
tags: [...candidate.tags].sort()
|
|
6795
|
+
})).sort((left, right) => left.eventId < right.eventId ? -1 : left.eventId > right.eventId ? 1 : 0)
|
|
6796
|
+
});
|
|
6797
|
+
let hash = 2166136261;
|
|
6798
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
6799
|
+
hash ^= normalized.charCodeAt(index);
|
|
6800
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
6801
|
+
}
|
|
6802
|
+
return `fnv1a32:${hash.toString(16).padStart(8, "0")}`;
|
|
6803
|
+
}
|
|
6716
6804
|
var GLOBAL_ADMISSION_HEADER = "x-sdt-global-admission";
|
|
6717
6805
|
var runtimeCommitTraceVerifier = {
|
|
6718
6806
|
verify(snapshot) {
|
|
@@ -6766,19 +6854,19 @@ var CommitPayloadAdmissionError = class extends Error {
|
|
|
6766
6854
|
code;
|
|
6767
6855
|
};
|
|
6768
6856
|
function decodeBase64Utf8Json(value) {
|
|
6769
|
-
let
|
|
6857
|
+
let bytes2;
|
|
6770
6858
|
try {
|
|
6771
6859
|
const binary = atob(value);
|
|
6772
|
-
|
|
6860
|
+
bytes2 = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
6773
6861
|
} catch {
|
|
6774
6862
|
throw new CommitPayloadAdmissionError("invalid_payload_utf8", "Payload base64 could not be decoded");
|
|
6775
6863
|
}
|
|
6776
|
-
if (
|
|
6864
|
+
if (bytes2[0] === 239 && bytes2[1] === 187 && bytes2[2] === 191) {
|
|
6777
6865
|
throw new CommitPayloadAdmissionError("invalid_payload_utf8", "Payload must not start with a UTF-8 BOM");
|
|
6778
6866
|
}
|
|
6779
6867
|
let text;
|
|
6780
6868
|
try {
|
|
6781
|
-
text = new TextDecoder("utf-8", { fatal: true }).decode(
|
|
6869
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
6782
6870
|
} catch {
|
|
6783
6871
|
throw new CommitPayloadAdmissionError("invalid_payload_utf8", "Payload must be valid UTF-8 JSON text");
|
|
6784
6872
|
}
|
|
@@ -6824,9 +6912,9 @@ function assertExactMemberPaths(actual, registered, path = "$") {
|
|
|
6824
6912
|
}
|
|
6825
6913
|
}
|
|
6826
6914
|
function encodeBase64Utf8(value) {
|
|
6827
|
-
const
|
|
6915
|
+
const bytes2 = new TextEncoder().encode(value);
|
|
6828
6916
|
let binary = "";
|
|
6829
|
-
for (const byte of
|
|
6917
|
+
for (const byte of bytes2) binary += String.fromCharCode(byte);
|
|
6830
6918
|
return btoa(binary);
|
|
6831
6919
|
}
|
|
6832
6920
|
function faultFromRequest(request) {
|
|
@@ -7085,25 +7173,43 @@ var CommitWorker = class {
|
|
|
7085
7173
|
return responseWithAttempt(error3(409, "bootstrap_command_rejected", "Bootstrap admission could not be released"), attemptId, fault2 !== void 0);
|
|
7086
7174
|
}
|
|
7087
7175
|
await this.retiredJournalMilestone("S04", traceState?.scope);
|
|
7176
|
+
const permitDigest = commitPermitDigest(input.allTags, candidates);
|
|
7177
|
+
let permitHeld = false;
|
|
7178
|
+
const releasePermit = async () => {
|
|
7179
|
+
if (!permitHeld) return;
|
|
7180
|
+
permitHeld = false;
|
|
7181
|
+
await this.bootstrapPermitRelease(attemptId, permitDigest);
|
|
7182
|
+
};
|
|
7183
|
+
const permit = await this.bootstrapPermitAcquire(attemptId, permitDigest);
|
|
7184
|
+
if (!permit.ok) {
|
|
7185
|
+
if (permit.code === "bootstrap_permit_digest_mismatch") {
|
|
7186
|
+
return responseWithAttempt(error3(409, "bootstrap_permit_digest_mismatch", "Bootstrap write permit digest does not match"), attemptId, fault2 !== void 0);
|
|
7187
|
+
}
|
|
7188
|
+
return responseWithAttempt(error3(409, "bootstrap_command_rejected", "Bootstrap is importing; commit is unavailable"), attemptId, fault2 !== void 0);
|
|
7189
|
+
}
|
|
7190
|
+
permitHeld = this.env.BOOTSTRAP !== void 0;
|
|
7088
7191
|
const reservations = await this.acquireReservations(input, attemptId, fault2, traceState?.scope);
|
|
7089
7192
|
await this.retiredJournalMilestone("S05a", traceState?.scope, 0);
|
|
7090
7193
|
if (reservations.failure !== void 0) {
|
|
7091
|
-
|
|
7194
|
+
const failure3 = await this.finishReservationFailure(
|
|
7092
7195
|
[...reservations.successes.keys()],
|
|
7093
7196
|
attemptId,
|
|
7094
7197
|
reservations.failure,
|
|
7095
7198
|
fault2,
|
|
7096
7199
|
traceState?.scope
|
|
7097
7200
|
);
|
|
7201
|
+
if (failure3.resolved) await releasePermit();
|
|
7202
|
+
return failure3.response;
|
|
7098
7203
|
}
|
|
7099
7204
|
if (fault2 === "after-reservations-before-allocation") {
|
|
7100
|
-
await this.cancelReservations([...reservations.successes.keys()], attemptId, fault2, traceState?.scope);
|
|
7205
|
+
const cleanup = await this.cancelReservations([...reservations.successes.keys()], attemptId, fault2, traceState?.scope);
|
|
7206
|
+
if (cleanup.failedTags.length === 0) await releasePermit();
|
|
7101
7207
|
return this.noApplicationOutcome(attemptId, true);
|
|
7102
7208
|
}
|
|
7103
7209
|
await this.hooks.beforeBootstrapAllocation?.();
|
|
7104
7210
|
const allocation = await this.allocate(candidates, attemptId, fault2, bootstrapEpoch, traceState?.scope);
|
|
7105
7211
|
if (allocation === void 0) {
|
|
7106
|
-
|
|
7212
|
+
const failure3 = await this.finishReservationFailure(
|
|
7107
7213
|
[...reservations.successes.keys()],
|
|
7108
7214
|
attemptId,
|
|
7109
7215
|
{
|
|
@@ -7114,10 +7220,13 @@ var CommitWorker = class {
|
|
|
7114
7220
|
fault2,
|
|
7115
7221
|
traceState?.scope
|
|
7116
7222
|
);
|
|
7223
|
+
if (failure3.resolved) await releasePermit();
|
|
7224
|
+
return failure3.response;
|
|
7117
7225
|
}
|
|
7118
7226
|
const allocatedCandidates = this.withAllocatedSuids(candidates, allocation);
|
|
7119
7227
|
if (allocatedCandidates === void 0) {
|
|
7120
|
-
await this.cancelReservations([...reservations.successes.keys()], attemptId, fault2, traceState?.scope);
|
|
7228
|
+
const cleanup = await this.cancelReservations([...reservations.successes.keys()], attemptId, fault2, traceState?.scope);
|
|
7229
|
+
if (cleanup.failedTags.length === 0) await releasePermit();
|
|
7121
7230
|
return this.noApplicationOutcome(attemptId, fault2 !== void 0);
|
|
7122
7231
|
}
|
|
7123
7232
|
for (const candidate of allocatedCandidates) {
|
|
@@ -7132,15 +7241,18 @@ var CommitWorker = class {
|
|
|
7132
7241
|
}
|
|
7133
7242
|
await this.retiredJournalMilestone("S05b", traceState?.scope, 1);
|
|
7134
7243
|
if (fault2 === "journal-cas-after-allocator") {
|
|
7135
|
-
await this.cancelReservations(input.allTags, attemptId, fault2, traceState?.scope);
|
|
7244
|
+
const cleanup = await this.cancelReservations(input.allTags, attemptId, fault2, traceState?.scope);
|
|
7245
|
+
if (cleanup.failedTags.length === 0) await releasePermit();
|
|
7136
7246
|
return this.noApplicationOutcome(attemptId, true);
|
|
7137
7247
|
}
|
|
7138
7248
|
await this.retiredJournalMilestone("S05c", traceState?.scope, 2);
|
|
7139
7249
|
await this.hooks.beforeBootstrapFinalization?.();
|
|
7140
7250
|
if (await this.bootstrapCommand("finalize", attemptId, bootstrapEpoch, traceState?.scope, "S10") === void 0) {
|
|
7141
|
-
await this.cancelReservations(input.allTags, attemptId, fault2, traceState?.scope);
|
|
7251
|
+
const cleanup = await this.cancelReservations(input.allTags, attemptId, fault2, traceState?.scope);
|
|
7252
|
+
if (cleanup.failedTags.length === 0) await releasePermit();
|
|
7142
7253
|
return responseWithAttempt(error3(409, "bootstrap_command_rejected", "Bootstrap fencing epoch changed before commit write"), attemptId, fault2 !== void 0);
|
|
7143
7254
|
}
|
|
7255
|
+
await this.hooks.beforeAuthoritativeAppend?.();
|
|
7144
7256
|
const writes = await this.appendAllTags(
|
|
7145
7257
|
input,
|
|
7146
7258
|
allocatedCandidates,
|
|
@@ -7156,6 +7268,7 @@ var CommitWorker = class {
|
|
|
7156
7268
|
return this.noApplicationOutcome(attemptId, true);
|
|
7157
7269
|
}
|
|
7158
7270
|
if (writes.committedTags.size === 0 && writes.registrationUnavailableTags.length === writes.pendingTags.length) {
|
|
7271
|
+
await releasePermit();
|
|
7159
7272
|
return responseWithAttempt(
|
|
7160
7273
|
error3(
|
|
7161
7274
|
503,
|
|
@@ -7167,20 +7280,25 @@ var CommitWorker = class {
|
|
|
7167
7280
|
fault2 !== void 0
|
|
7168
7281
|
);
|
|
7169
7282
|
}
|
|
7283
|
+
await releasePermit();
|
|
7170
7284
|
return this.partialWriteOutcome(input, allocatedCandidates, writes, attemptId, fault2 !== void 0);
|
|
7171
7285
|
}
|
|
7286
|
+
await this.hooks.afterAppendBeforeResponse?.();
|
|
7172
7287
|
await this.retiredJournalMilestone("S05d", traceState?.scope, 3);
|
|
7173
7288
|
if (fault2 === "sealing-after-cas") {
|
|
7289
|
+
await releasePermit();
|
|
7174
7290
|
return this.noApplicationOutcome(attemptId, true);
|
|
7175
7291
|
}
|
|
7176
7292
|
try {
|
|
7177
|
-
const success = await this.successResponse(input, allocatedCandidates, attemptId, startedAt, fault2, traceState?.scope);
|
|
7293
|
+
const success = await this.successResponse(input, allocatedCandidates, attemptId, startedAt, fault2, traceState?.scope, writes);
|
|
7294
|
+
await releasePermit();
|
|
7178
7295
|
return responseWithAttempt(
|
|
7179
7296
|
withGlobalAdmission(json4(success, 200), writes.globalAdmission),
|
|
7180
7297
|
attemptId,
|
|
7181
7298
|
fault2 !== void 0
|
|
7182
7299
|
);
|
|
7183
7300
|
} catch {
|
|
7301
|
+
await releasePermit();
|
|
7184
7302
|
return responseWithAttempt(
|
|
7185
7303
|
error3(500, "internal_error", "Committed records could not be read while preparing the response"),
|
|
7186
7304
|
attemptId,
|
|
@@ -7227,6 +7345,51 @@ var CommitWorker = class {
|
|
|
7227
7345
|
const result2 = traceScope === void 0 || rowId === void 0 ? await invoke() : await traceScope.span(rowId, {}, invoke);
|
|
7228
7346
|
return result2.leaseEpoch;
|
|
7229
7347
|
}
|
|
7348
|
+
/**
|
|
7349
|
+
* SDT-G36 bootstrap write permit. Deliberately untraced: it must not add
|
|
7350
|
+
* `sdt.commit/v2` rows or new trace identities. Only a bootstrap barrier,
|
|
7351
|
+
* never commit-outcome authority. No JOURNAL, no alarm, no lease expiry.
|
|
7352
|
+
*/
|
|
7353
|
+
bootstrapStub() {
|
|
7354
|
+
if (this.env.BOOTSTRAP === void 0) return void 0;
|
|
7355
|
+
return this.env.BOOTSTRAP.get(scopeIdFor(this.env.BOOTSTRAP, {
|
|
7356
|
+
serviceId: this.serviceId,
|
|
7357
|
+
doClass: "bootstrap",
|
|
7358
|
+
identity: "coordinator"
|
|
7359
|
+
}));
|
|
7360
|
+
}
|
|
7361
|
+
async bootstrapPermitAcquire(commandId, digest2) {
|
|
7362
|
+
const stub = this.bootstrapStub();
|
|
7363
|
+
if (stub === void 0) return { ok: true };
|
|
7364
|
+
try {
|
|
7365
|
+
const url = new URL(`https://commit-worker.internal/command/permit`);
|
|
7366
|
+
url.searchParams.set("__serviceId", this.serviceId);
|
|
7367
|
+
const response3 = await stub.fetch(new Request(url, {
|
|
7368
|
+
method: "POST",
|
|
7369
|
+
headers: { "content-type": "application/json" },
|
|
7370
|
+
body: JSON.stringify({ commandId, digest: digest2 })
|
|
7371
|
+
}));
|
|
7372
|
+
if (response3.ok) return { ok: true };
|
|
7373
|
+
const body = await response3.clone().json().catch(() => void 0);
|
|
7374
|
+
return { ok: false, code: typeof body?.code === "string" ? body.code : void 0 };
|
|
7375
|
+
} catch {
|
|
7376
|
+
return { ok: false };
|
|
7377
|
+
}
|
|
7378
|
+
}
|
|
7379
|
+
async bootstrapPermitRelease(commandId, digest2) {
|
|
7380
|
+
const stub = this.bootstrapStub();
|
|
7381
|
+
if (stub === void 0) return;
|
|
7382
|
+
try {
|
|
7383
|
+
const url = new URL(`https://commit-worker.internal/command/permit-release`);
|
|
7384
|
+
url.searchParams.set("__serviceId", this.serviceId);
|
|
7385
|
+
await stub.fetch(new Request(url, {
|
|
7386
|
+
method: "POST",
|
|
7387
|
+
headers: { "content-type": "application/json" },
|
|
7388
|
+
body: JSON.stringify({ commandId, digest: digest2 })
|
|
7389
|
+
}));
|
|
7390
|
+
} catch {
|
|
7391
|
+
}
|
|
7392
|
+
}
|
|
7230
7393
|
async postJson(stub, path, body, traceScope, rowId, traceOptions = {}) {
|
|
7231
7394
|
const invoke = async () => {
|
|
7232
7395
|
const response3 = await stub.fetch(
|
|
@@ -7372,6 +7535,7 @@ var CommitWorker = class {
|
|
|
7372
7535
|
let pending = [...input.allTags];
|
|
7373
7536
|
const committedTags = /* @__PURE__ */ new Set();
|
|
7374
7537
|
const lastFailureCodeByTag = /* @__PURE__ */ new Map();
|
|
7538
|
+
const tagWriteFacts = /* @__PURE__ */ new Map();
|
|
7375
7539
|
let globalAdmission = "admitted";
|
|
7376
7540
|
for (let attempt = 0; attempt < MAX_WRITE_ATTEMPTS && pending.length > 0; attempt += 1) {
|
|
7377
7541
|
const append = (stageScope) => Promise.allSettled(
|
|
@@ -7398,12 +7562,26 @@ var CommitWorker = class {
|
|
|
7398
7562
|
retryIndex: attempt,
|
|
7399
7563
|
attemptId
|
|
7400
7564
|
});
|
|
7401
|
-
const
|
|
7565
|
+
const success = response3.status >= 200 && response3.status < 300;
|
|
7566
|
+
let appendVersion;
|
|
7567
|
+
let appendUpdatedAt;
|
|
7568
|
+
let code;
|
|
7569
|
+
try {
|
|
7570
|
+
const parsed = await response3.clone().json();
|
|
7571
|
+
if (isObject5(parsed)) {
|
|
7572
|
+
if (typeof parsed.version === "number" && Number.isSafeInteger(parsed.version)) appendVersion = parsed.version;
|
|
7573
|
+
if (typeof parsed.updatedAt === "string" && parsed.updatedAt.length > 0) appendUpdatedAt = parsed.updatedAt;
|
|
7574
|
+
if (typeof parsed.code === "string") code = parsed.code;
|
|
7575
|
+
}
|
|
7576
|
+
} catch {
|
|
7577
|
+
}
|
|
7402
7578
|
return {
|
|
7403
7579
|
tag,
|
|
7404
|
-
success
|
|
7580
|
+
success,
|
|
7405
7581
|
globalAdmission: readGlobalAdmission(response3),
|
|
7406
|
-
code
|
|
7582
|
+
code,
|
|
7583
|
+
appendVersion,
|
|
7584
|
+
appendUpdatedAt
|
|
7407
7585
|
};
|
|
7408
7586
|
})
|
|
7409
7587
|
);
|
|
@@ -7415,6 +7593,9 @@ var CommitWorker = class {
|
|
|
7415
7593
|
committedTags.add(tag);
|
|
7416
7594
|
lastFailureCodeByTag.delete(tag);
|
|
7417
7595
|
globalAdmission = mergeGlobalAdmission(globalAdmission, result2.value.globalAdmission);
|
|
7596
|
+
if (result2.value.appendVersion !== void 0 && result2.value.appendUpdatedAt !== void 0) {
|
|
7597
|
+
tagWriteFacts.set(tag, { version: result2.value.appendVersion, updatedAt: result2.value.appendUpdatedAt });
|
|
7598
|
+
}
|
|
7418
7599
|
} else {
|
|
7419
7600
|
lastFailureCodeByTag.set(
|
|
7420
7601
|
tag,
|
|
@@ -7431,7 +7612,8 @@ var CommitWorker = class {
|
|
|
7431
7612
|
registrationUnavailableTags: pending.filter(
|
|
7432
7613
|
(tag) => lastFailureCodeByTag.get(tag) === "partition_registration_unavailable"
|
|
7433
7614
|
),
|
|
7434
|
-
globalAdmission
|
|
7615
|
+
globalAdmission,
|
|
7616
|
+
tagWriteFacts
|
|
7435
7617
|
};
|
|
7436
7618
|
}
|
|
7437
7619
|
/**
|
|
@@ -7464,10 +7646,13 @@ var CommitWorker = class {
|
|
|
7464
7646
|
if (traceScope !== void 0) {
|
|
7465
7647
|
await traceScope.span("S17", {}, async () => void 0);
|
|
7466
7648
|
}
|
|
7467
|
-
await this.cancelReservations(tags, attemptId, fault2, traceScope);
|
|
7649
|
+
const cleanup = await this.cancelReservations(tags, attemptId, fault2, traceScope);
|
|
7468
7650
|
await this.retiredJournalMilestone("S05e", traceScope, 4);
|
|
7469
7651
|
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");
|
|
7470
|
-
return
|
|
7652
|
+
return {
|
|
7653
|
+
response: responseWithAttempt(response3, attemptId, fault2 !== void 0),
|
|
7654
|
+
resolved: cleanup.failedTags.length === 0
|
|
7655
|
+
};
|
|
7471
7656
|
}
|
|
7472
7657
|
/**
|
|
7473
7658
|
* Persist the local partial-write fact for each tag whose append did not
|
|
@@ -7504,22 +7689,25 @@ var CommitWorker = class {
|
|
|
7504
7689
|
}
|
|
7505
7690
|
}, 500), attemptId, exposeAttempt);
|
|
7506
7691
|
}
|
|
7507
|
-
async successResponse(input, candidates, attemptId, startedAt, fault2, traceScope) {
|
|
7508
|
-
const
|
|
7692
|
+
async successResponse(input, candidates, attemptId, startedAt, fault2, traceScope, writes) {
|
|
7693
|
+
const assemble = async (stageScope) => {
|
|
7509
7694
|
if (fault2 === "tag-state-unavailable") {
|
|
7510
7695
|
throw new Error("Test fault made the committed tag state unavailable");
|
|
7511
7696
|
}
|
|
7512
|
-
const
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7697
|
+
const results = [];
|
|
7698
|
+
for (const [memberIndex, tag] of input.allTags.entries()) {
|
|
7699
|
+
const read = () => {
|
|
7700
|
+
const fact = writes?.tagWriteFacts.get(tag);
|
|
7701
|
+
if (fact === void 0) {
|
|
7702
|
+
throw new Error("Committed tag state was unavailable while preparing the response");
|
|
7703
|
+
}
|
|
7704
|
+
return { tag, version: fact.version, writtenAt: fact.updatedAt };
|
|
7705
|
+
};
|
|
7706
|
+
results.push(stageScope === void 0 ? read() : await stageScope.fork().span("S14", { tag, memberIndex, attemptId }, async () => read()));
|
|
7519
7707
|
}
|
|
7520
|
-
return
|
|
7521
|
-
}
|
|
7522
|
-
const tagWriteResults = traceScope === void 0 ? await
|
|
7708
|
+
return results;
|
|
7709
|
+
};
|
|
7710
|
+
const tagWriteResults = traceScope === void 0 ? await assemble() : await traceScope.span("S13", {}, async (stage) => assemble(stage));
|
|
7523
7711
|
return {
|
|
7524
7712
|
writtenEvents: candidates.map((candidate) => ({
|
|
7525
7713
|
payload: encodeBase64Utf8(candidate.payload),
|
|
@@ -7911,15 +8099,15 @@ function observePostAdmission(observer, message, source, input) {
|
|
|
7911
8099
|
observedAt: Date.now()
|
|
7912
8100
|
});
|
|
7913
8101
|
}
|
|
7914
|
-
function errorText(
|
|
7915
|
-
if (
|
|
7916
|
-
return String(
|
|
8102
|
+
function errorText(error11) {
|
|
8103
|
+
if (error11 instanceof Error && error11.message.length > 0) return error11.message;
|
|
8104
|
+
return String(error11);
|
|
7917
8105
|
}
|
|
7918
8106
|
function deliveryCorrelationId(message, source, supplied) {
|
|
7919
8107
|
return supplied ?? `${source}:${message.serviceId}:${message.eventId}:${message.attemptId}`;
|
|
7920
8108
|
}
|
|
7921
|
-
function defaultFailureClass(
|
|
7922
|
-
const candidate =
|
|
8109
|
+
function defaultFailureClass(error11) {
|
|
8110
|
+
const candidate = error11;
|
|
7923
8111
|
if (candidate.failureClass === "duplicate-race") return "duplicate-race";
|
|
7924
8112
|
if (candidate.failureClass === "nonretryable-definition-poison") return "nonretryable-definition-poison";
|
|
7925
8113
|
if (candidate.failureClass === "retryable-transient") return "retryable-transient";
|
|
@@ -7978,8 +8166,8 @@ async function applyViewHandlers(views, message, storedEvent, arrivedAt, source,
|
|
|
7978
8166
|
result: { id: view.id, status, durationMs: Math.max(0, performance.now() - viewStartedAt) },
|
|
7979
8167
|
failure: void 0
|
|
7980
8168
|
};
|
|
7981
|
-
} catch (
|
|
7982
|
-
const failureClass = view.classifyError?.(
|
|
8169
|
+
} catch (error11) {
|
|
8170
|
+
const failureClass = view.classifyError?.(error11) ?? defaultFailureClass(error11);
|
|
7983
8171
|
observePostAdmission(observer, message, source, {
|
|
7984
8172
|
stage: "unsafe-view-apply",
|
|
7985
8173
|
boundary: "end",
|
|
@@ -7991,10 +8179,10 @@ async function applyViewHandlers(views, message, storedEvent, arrivedAt, source,
|
|
|
7991
8179
|
id: view.id,
|
|
7992
8180
|
status: "failed",
|
|
7993
8181
|
failureClass,
|
|
7994
|
-
error: errorText(
|
|
8182
|
+
error: errorText(error11),
|
|
7995
8183
|
durationMs: Math.max(0, performance.now() - viewStartedAt)
|
|
7996
8184
|
},
|
|
7997
|
-
failure: { phase: "view", class: failureClass, viewId: view.id, error: errorText(
|
|
8185
|
+
failure: { phase: "view", class: failureClass, viewId: view.id, error: errorText(error11) }
|
|
7998
8186
|
};
|
|
7999
8187
|
}
|
|
8000
8188
|
}));
|
|
@@ -8024,15 +8212,15 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8024
8212
|
// settled; this is the durable post-batch observation boundary.
|
|
8025
8213
|
observedAt: Date.now()
|
|
8026
8214
|
});
|
|
8027
|
-
} catch (
|
|
8028
|
-
const failures2 = [{ phase: "recordDelivery", class: "retryable-transient", error: errorText(
|
|
8215
|
+
} catch (error11) {
|
|
8216
|
+
const failures2 = [{ phase: "recordDelivery", class: "retryable-transient", error: errorText(error11) }];
|
|
8029
8217
|
return result(source, message, "stored", arrivedAt, false, [], failures2, options.correlationId, startedAt);
|
|
8030
8218
|
}
|
|
8031
8219
|
if (outcome.outcome !== "stored") {
|
|
8032
8220
|
try {
|
|
8033
8221
|
await store.projectDeliveryIncidents?.(message.serviceId);
|
|
8034
|
-
} catch (
|
|
8035
|
-
const failures2 = [{ phase: "detector", class: "retryable-transient", error: errorText(
|
|
8222
|
+
} catch (error11) {
|
|
8223
|
+
const failures2 = [{ phase: "detector", class: "retryable-transient", error: errorText(error11) }];
|
|
8036
8224
|
return result(source, message, outcome.outcome, arrivedAt, false, [], failures2, options.correlationId, startedAt);
|
|
8037
8225
|
}
|
|
8038
8226
|
return result(source, message, outcome.outcome, arrivedAt, false, [], [], options.correlationId, startedAt);
|
|
@@ -8061,20 +8249,20 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8061
8249
|
boundary: "end",
|
|
8062
8250
|
outcome: "available"
|
|
8063
8251
|
});
|
|
8064
|
-
} catch (
|
|
8252
|
+
} catch (error11) {
|
|
8065
8253
|
observePostAdmission(options.durableHopObserver, message, source, {
|
|
8066
8254
|
stage: "post-record-delivery-global-receipt-readback",
|
|
8067
8255
|
boundary: "end",
|
|
8068
8256
|
outcome: "error"
|
|
8069
8257
|
});
|
|
8070
|
-
failures.push({ phase: "recordDelivery", class: "retryable-transient", error: errorText(
|
|
8258
|
+
failures.push({ phase: "recordDelivery", class: "retryable-transient", error: errorText(error11) });
|
|
8071
8259
|
return result(source, message, "stored", arrivedAt, false, [], failures, options.correlationId, startedAt);
|
|
8072
8260
|
}
|
|
8073
8261
|
if (options.afterGlobalReceipt !== void 0) {
|
|
8074
8262
|
try {
|
|
8075
8263
|
await options.afterGlobalReceipt({ message, receipt, arrivedAt, source });
|
|
8076
|
-
} catch (
|
|
8077
|
-
failures.push({ phase: "drain", class: "retryable-transient", error: errorText(
|
|
8264
|
+
} catch (error11) {
|
|
8265
|
+
failures.push({ phase: "drain", class: "retryable-transient", error: errorText(error11) });
|
|
8078
8266
|
}
|
|
8079
8267
|
}
|
|
8080
8268
|
}
|
|
@@ -8091,8 +8279,8 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8091
8279
|
if (options.beforeViews !== void 0) {
|
|
8092
8280
|
try {
|
|
8093
8281
|
await options.beforeViews({ message, event: storedEvent, arrivedAt, source });
|
|
8094
|
-
} catch (
|
|
8095
|
-
failures.push({ phase: "completeness", class: "retryable-transient", error: errorText(
|
|
8282
|
+
} catch (error11) {
|
|
8283
|
+
failures.push({ phase: "completeness", class: "retryable-transient", error: errorText(error11) });
|
|
8096
8284
|
return result(source, message, "stored", arrivedAt, false, viewResults, failures, options.correlationId, startedAt);
|
|
8097
8285
|
}
|
|
8098
8286
|
}
|
|
@@ -8112,14 +8300,14 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8112
8300
|
boundary: "end",
|
|
8113
8301
|
outcome: "applied"
|
|
8114
8302
|
});
|
|
8115
|
-
} catch (
|
|
8303
|
+
} catch (error11) {
|
|
8116
8304
|
observePostAdmission(options.durableHopObserver, message, source, {
|
|
8117
8305
|
stage: "detector",
|
|
8118
8306
|
boundary: "end",
|
|
8119
8307
|
outcome: "error"
|
|
8120
8308
|
});
|
|
8121
8309
|
try {
|
|
8122
|
-
await options.onDetectorFailure?.({ message, arrivedAt, source, error:
|
|
8310
|
+
await options.onDetectorFailure?.({ message, arrivedAt, source, error: error11 });
|
|
8123
8311
|
} catch (healthError) {
|
|
8124
8312
|
failures.push({
|
|
8125
8313
|
phase: "detector",
|
|
@@ -8127,7 +8315,7 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8127
8315
|
error: `detector_health_record_failed:${errorText(healthError)}`
|
|
8128
8316
|
});
|
|
8129
8317
|
}
|
|
8130
|
-
failures.push({ phase: "detector", class: "retryable-transient", error: errorText(
|
|
8318
|
+
failures.push({ phase: "detector", class: "retryable-transient", error: errorText(error11) });
|
|
8131
8319
|
return result(source, message, "stored", arrivedAt, false, [], failures, options.correlationId, startedAt);
|
|
8132
8320
|
}
|
|
8133
8321
|
const views = gatedViews;
|
|
@@ -8144,16 +8332,16 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8144
8332
|
if (options.onStored !== void 0) {
|
|
8145
8333
|
try {
|
|
8146
8334
|
await options.onStored({ message, event: storedEvent, arrivedAt, source });
|
|
8147
|
-
} catch (
|
|
8148
|
-
failures.push({ phase: "view", class: "retryable-transient", viewId: "legacy-onStored", error: errorText(
|
|
8335
|
+
} catch (error11) {
|
|
8336
|
+
failures.push({ phase: "view", class: "retryable-transient", viewId: "legacy-onStored", error: errorText(error11) });
|
|
8149
8337
|
}
|
|
8150
8338
|
}
|
|
8151
8339
|
const preliminary = result(source, message, "stored", arrivedAt, detectorApplied, viewResults, failures, options.correlationId, startedAt);
|
|
8152
8340
|
if (options.afterDelivery !== void 0 && failures.every((failure3) => failure3.class === "duplicate-race")) {
|
|
8153
8341
|
try {
|
|
8154
8342
|
await options.afterDelivery({ message, event: storedEvent, arrivedAt, source, result: preliminary });
|
|
8155
|
-
} catch (
|
|
8156
|
-
failures.push({ phase: "drain", class: "retryable-transient", error: errorText(
|
|
8343
|
+
} catch (error11) {
|
|
8344
|
+
failures.push({ phase: "drain", class: "retryable-transient", error: errorText(error11) });
|
|
8157
8345
|
}
|
|
8158
8346
|
}
|
|
8159
8347
|
return result(source, message, "stored", arrivedAt, detectorApplied, viewResults, failures, options.correlationId, startedAt);
|
|
@@ -8175,8 +8363,8 @@ function asNumber(value, name) {
|
|
|
8175
8363
|
if (!Number.isSafeInteger(number) || number < 0) throw new Error(`G44 ${name} must be a non-negative safe integer`);
|
|
8176
8364
|
return number;
|
|
8177
8365
|
}
|
|
8178
|
-
function errorText2(
|
|
8179
|
-
return
|
|
8366
|
+
function errorText2(error11) {
|
|
8367
|
+
return error11 instanceof Error && error11.message.length > 0 ? error11.message : String(error11);
|
|
8180
8368
|
}
|
|
8181
8369
|
function settledFrontierFromCursor(cursorJson) {
|
|
8182
8370
|
const cursor = settledCursorFromJson(cursorJson);
|
|
@@ -8280,8 +8468,8 @@ var GlobalCompletenessReconciler = class {
|
|
|
8280
8468
|
let snapshots;
|
|
8281
8469
|
try {
|
|
8282
8470
|
snapshots = await this.snapshotPartitions(serviceId);
|
|
8283
|
-
} catch (
|
|
8284
|
-
const message = errorText2(
|
|
8471
|
+
} catch (error11) {
|
|
8472
|
+
const message = errorText2(error11);
|
|
8285
8473
|
await this.appendFinding(serviceId, {
|
|
8286
8474
|
identity: scannerFailureIdentity(serviceId),
|
|
8287
8475
|
type: "GLOBAL_ARRAY_SCANNER_FAILURE",
|
|
@@ -8323,7 +8511,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8323
8511
|
let joined;
|
|
8324
8512
|
try {
|
|
8325
8513
|
joined = this.options.globalReceiptMatcher === void 0 ? await this.globalReceiptMatches(snapshot.serviceId, snapshot.tag, obligation) : await this.options.globalReceiptMatcher(snapshot.serviceId, snapshot.tag, obligation);
|
|
8326
|
-
} catch (
|
|
8514
|
+
} catch (error11) {
|
|
8327
8515
|
await this.appendFinding(snapshot.serviceId, {
|
|
8328
8516
|
identity: `GLOBAL_RECEIPT_UNAVAILABLE|${snapshot.serviceId}|${snapshot.tag}|${obligation.obligationSequence}|${obligation.eventId}|${obligation.eventDigest}`,
|
|
8329
8517
|
type: "GLOBAL_ARRAY_RECEIPT_UNAVAILABLE",
|
|
@@ -8332,7 +8520,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8332
8520
|
eventId: obligation.eventId,
|
|
8333
8521
|
eventDigest: obligation.eventDigest
|
|
8334
8522
|
}, nowMs).catch(() => void 0);
|
|
8335
|
-
throw
|
|
8523
|
+
throw error11;
|
|
8336
8524
|
}
|
|
8337
8525
|
if (!joined) {
|
|
8338
8526
|
await this.appendObligationFinding(snapshot.serviceId, snapshot.tag, obligation, nowMs);
|
|
@@ -8351,8 +8539,8 @@ var GlobalCompletenessReconciler = class {
|
|
|
8351
8539
|
if (afterSequence !== snapshot.upperBoundSequence) {
|
|
8352
8540
|
throw new Error(`source_partition_range_incomplete:${snapshot.tag}`);
|
|
8353
8541
|
}
|
|
8354
|
-
} catch (
|
|
8355
|
-
if (errorText2(
|
|
8542
|
+
} catch (error11) {
|
|
8543
|
+
if (errorText2(error11).startsWith("source_")) {
|
|
8356
8544
|
await this.appendFinding(snapshot.serviceId, {
|
|
8357
8545
|
identity: sourceUnavailableIdentity(snapshot.serviceId, snapshot.tag),
|
|
8358
8546
|
type: "GLOBAL_ARRAY_SOURCE_PARTITION_UNAVAILABLE",
|
|
@@ -8362,7 +8550,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8362
8550
|
eventDigest: null
|
|
8363
8551
|
}, nowMs).catch(() => void 0);
|
|
8364
8552
|
}
|
|
8365
|
-
throw
|
|
8553
|
+
throw error11;
|
|
8366
8554
|
}
|
|
8367
8555
|
}
|
|
8368
8556
|
const endSnapshots = await this.snapshotPartitions(serviceId);
|
|
@@ -8381,8 +8569,8 @@ var GlobalCompletenessReconciler = class {
|
|
|
8381
8569
|
}
|
|
8382
8570
|
await this.writeHealth(serviceId, "HEALTHY", JSON.stringify(cursor), nowMs, null, nowMs);
|
|
8383
8571
|
return { kind: "FULL", partitions: snapshots, scannedObligations: scanned };
|
|
8384
|
-
} catch (
|
|
8385
|
-
const message = errorText2(
|
|
8572
|
+
} catch (error11) {
|
|
8573
|
+
const message = errorText2(error11);
|
|
8386
8574
|
const health = message.startsWith("source_") ? "UNKNOWN" : "FAILED";
|
|
8387
8575
|
if (health === "FAILED") {
|
|
8388
8576
|
await this.appendFinding(serviceId, {
|
|
@@ -8524,8 +8712,8 @@ var GlobalCompletenessReconciler = class {
|
|
|
8524
8712
|
* without applying views. It shares the scanner's single health/finding
|
|
8525
8713
|
* authority rather than creating a second incident workflow.
|
|
8526
8714
|
*/
|
|
8527
|
-
async recordDetectorFailure(serviceId,
|
|
8528
|
-
const message = `detector_failure:${errorText2(
|
|
8715
|
+
async recordDetectorFailure(serviceId, error11, nowMs) {
|
|
8716
|
+
const message = `detector_failure:${errorText2(error11).slice(0, 256)}`;
|
|
8529
8717
|
await this.appendFinding(serviceId, {
|
|
8530
8718
|
identity: `GLOBAL_DETECTOR_FAILURE|${serviceId}`,
|
|
8531
8719
|
type: "GLOBAL_ARRAY_DETECTOR_FAILURE",
|
|
@@ -8664,7 +8852,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8664
8852
|
nowMs
|
|
8665
8853
|
).run();
|
|
8666
8854
|
}
|
|
8667
|
-
async writeHealth(serviceId, status, cursor, lastFullScanAt,
|
|
8855
|
+
async writeHealth(serviceId, status, cursor, lastFullScanAt, error11, nowMs) {
|
|
8668
8856
|
await this.database.prepare(
|
|
8669
8857
|
`INSERT INTO serialized_dcb_completeness_scanner_health
|
|
8670
8858
|
(service_id, scanner_version, status, cursor_json, last_full_scan_at, last_error, updated_at)
|
|
@@ -8676,7 +8864,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8676
8864
|
last_full_scan_at = COALESCE(excluded.last_full_scan_at, serialized_dcb_completeness_scanner_health.last_full_scan_at),
|
|
8677
8865
|
last_error = excluded.last_error,
|
|
8678
8866
|
updated_at = excluded.updated_at`
|
|
8679
|
-
).bind(serviceId, this.scannerVersion, status, cursor, lastFullScanAt,
|
|
8867
|
+
).bind(serviceId, this.scannerVersion, status, cursor, lastFullScanAt, error11, nowMs).run();
|
|
8680
8868
|
}
|
|
8681
8869
|
};
|
|
8682
8870
|
function globalReceiptAcknowledgement(message, receivedAt) {
|
|
@@ -8768,7 +8956,7 @@ function sourceAcknowledgementOptions(env, options) {
|
|
|
8768
8956
|
if (decision.kind !== "SETTLED") {
|
|
8769
8957
|
throw new Error(`global_completeness_${decision.kind}:${decision.health.status}`);
|
|
8770
8958
|
}
|
|
8771
|
-
} catch (
|
|
8959
|
+
} catch (error11) {
|
|
8772
8960
|
if (!completed) {
|
|
8773
8961
|
observeG60PostAdmission(options.durableHopObserver, {
|
|
8774
8962
|
stage: "completeness-coverage",
|
|
@@ -8783,7 +8971,7 @@ function sourceAcknowledgementOptions(env, options) {
|
|
|
8783
8971
|
observedAt: Date.now()
|
|
8784
8972
|
});
|
|
8785
8973
|
}
|
|
8786
|
-
throw
|
|
8974
|
+
throw error11;
|
|
8787
8975
|
}
|
|
8788
8976
|
} : options.beforeViews;
|
|
8789
8977
|
const observedSourceAcknowledgement = sourceAcknowledgement === void 0 ? void 0 : async (input) => {
|
|
@@ -8791,14 +8979,14 @@ function sourceAcknowledgementOptions(env, options) {
|
|
|
8791
8979
|
try {
|
|
8792
8980
|
await sourceAcknowledgement(input);
|
|
8793
8981
|
observeSourceSubstep(options.durableHopObserver, { ...input, boundary: "end", outcome: "acknowledged" });
|
|
8794
|
-
} catch (
|
|
8982
|
+
} catch (error11) {
|
|
8795
8983
|
observeSourceSubstep(options.durableHopObserver, { ...input, boundary: "end", outcome: "error" });
|
|
8796
|
-
throw
|
|
8984
|
+
throw error11;
|
|
8797
8985
|
}
|
|
8798
8986
|
};
|
|
8799
|
-
const detectorFailure = hasG44Authority ? async ({ message, arrivedAt, error:
|
|
8800
|
-
await options.onDetectorFailure?.({ message, arrivedAt, error:
|
|
8801
|
-
await new GlobalCompletenessReconciler(env.D1, env.TAG).recordDetectorFailure(message.serviceId,
|
|
8987
|
+
const detectorFailure = hasG44Authority ? async ({ message, arrivedAt, error: error11, source }) => {
|
|
8988
|
+
await options.onDetectorFailure?.({ message, arrivedAt, error: error11, source });
|
|
8989
|
+
await new GlobalCompletenessReconciler(env.D1, env.TAG).recordDetectorFailure(message.serviceId, error11, arrivedAt);
|
|
8802
8990
|
} : options.onDetectorFailure;
|
|
8803
8991
|
return {
|
|
8804
8992
|
...options,
|
|
@@ -8813,9 +9001,9 @@ function sharedStore(env, provider) {
|
|
|
8813
9001
|
async function admitBootstrapRoute(env, serviceId, route) {
|
|
8814
9002
|
if (env.BOOTSTRAP === void 0) {
|
|
8815
9003
|
if (env.G38_DOORBELL_DELIVERY_ROLE === "receiver") {
|
|
8816
|
-
const
|
|
8817
|
-
|
|
8818
|
-
throw
|
|
9004
|
+
const error11 = new Error(`bootstrap_route_binding_missing:${route}`);
|
|
9005
|
+
error11.name = "BootstrapRouteBindingMissingError";
|
|
9006
|
+
throw error11;
|
|
8819
9007
|
}
|
|
8820
9008
|
return;
|
|
8821
9009
|
}
|
|
@@ -8904,10 +9092,10 @@ async function handleDownstreamQueue(batch, env, options = {}) {
|
|
|
8904
9092
|
if (outcome.outcome === "stored" && !outcome.failures.some((failure3) => failure3.phase === "recordDelivery")) {
|
|
8905
9093
|
try {
|
|
8906
9094
|
options.afterStoredQueueDelivery?.({ message: queued.body, result: outcome });
|
|
8907
|
-
} catch (
|
|
9095
|
+
} catch (error11) {
|
|
8908
9096
|
console.warn("safe_lane_kick", {
|
|
8909
9097
|
status: "failed-to-schedule",
|
|
8910
|
-
error:
|
|
9098
|
+
error: error11 instanceof Error ? error11.message : String(error11),
|
|
8911
9099
|
serviceId: queued.body.serviceId,
|
|
8912
9100
|
eventId: queued.body.eventId,
|
|
8913
9101
|
attemptId: queued.body.attemptId
|
|
@@ -8929,6 +9117,36 @@ async function stabilizeDownstream(env, options = {}, serviceId, serviceIdentity
|
|
|
8929
9117
|
});
|
|
8930
9118
|
}
|
|
8931
9119
|
|
|
9120
|
+
// src/scope/ControlRouteScope.ts
|
|
9121
|
+
function response2(status, code, error11) {
|
|
9122
|
+
return new Response(JSON.stringify({ code, error: error11 }), {
|
|
9123
|
+
status,
|
|
9124
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
9125
|
+
});
|
|
9126
|
+
}
|
|
9127
|
+
function scopeIdentityMissingResponse() {
|
|
9128
|
+
return response2(503, "scope.identity_missing", "A deployment service identity is required for this scope");
|
|
9129
|
+
}
|
|
9130
|
+
function enforceControlRouteScope(input) {
|
|
9131
|
+
let actual;
|
|
9132
|
+
try {
|
|
9133
|
+
actual = input.provider.forRequest(input.request, input.requestOptions).serviceId;
|
|
9134
|
+
} catch (caught) {
|
|
9135
|
+
if (caught instanceof ServiceIdentityMissingError) {
|
|
9136
|
+
console.error({ schema: "sdt.scope/v1", code: "scope.identity_missing", route: input.route });
|
|
9137
|
+
return { response: scopeIdentityMissingResponse() };
|
|
9138
|
+
}
|
|
9139
|
+
throw caught;
|
|
9140
|
+
}
|
|
9141
|
+
if (input.pathServiceId !== actual) {
|
|
9142
|
+
console.warn({ schema: "sdt.scope/v1", code: "scope.mismatch", route: input.route });
|
|
9143
|
+
return {
|
|
9144
|
+
response: response2(403, "scope.mismatch", "The path service identity is not authorized for this deployment")
|
|
9145
|
+
};
|
|
9146
|
+
}
|
|
9147
|
+
return { serviceId: actual };
|
|
9148
|
+
}
|
|
9149
|
+
|
|
8932
9150
|
// src/downstream/OutboxDrain.ts
|
|
8933
9151
|
function json5(body, status = 200) {
|
|
8934
9152
|
return new Response(JSON.stringify(body), {
|
|
@@ -9025,15 +9243,32 @@ async function handleOutboxDrainRequest(request, env, options = {}) {
|
|
|
9025
9243
|
if (!isDrainRequest(body)) {
|
|
9026
9244
|
return json5({ error: "serviceId and unique non-empty tags are required", code: "invalid_outbox_drain_request" }, 400);
|
|
9027
9245
|
}
|
|
9246
|
+
const requestedServiceId = body.serviceId;
|
|
9247
|
+
const tags = body.tags;
|
|
9248
|
+
const provider = options.serviceIdentityProvider ?? envServiceIdentity(env);
|
|
9249
|
+
let authority;
|
|
9250
|
+
try {
|
|
9251
|
+
authority = provider.forRequest(request).serviceId;
|
|
9252
|
+
} catch (caught) {
|
|
9253
|
+
if (caught instanceof ServiceIdentityMissingError) return scopeIdentityMissingResponse();
|
|
9254
|
+
throw caught;
|
|
9255
|
+
}
|
|
9256
|
+
if (requestedServiceId !== authority) {
|
|
9257
|
+
console.warn({ schema: "sdt.scope/v1", code: "scope.mismatch", route: "outbox-drain" });
|
|
9258
|
+
return json5({
|
|
9259
|
+
code: "scope.mismatch",
|
|
9260
|
+
error: "The request service identity is not authorized for this deployment"
|
|
9261
|
+
}, 403);
|
|
9262
|
+
}
|
|
9028
9263
|
try {
|
|
9029
9264
|
const results = [];
|
|
9030
|
-
for (const tag of
|
|
9031
|
-
results.push(await drainTagOutbox({ serviceId:
|
|
9265
|
+
for (const tag of tags) {
|
|
9266
|
+
results.push(await drainTagOutbox({ serviceId: authority, tag }, env, systemPipelineClock, options));
|
|
9032
9267
|
}
|
|
9033
9268
|
return json5({ results });
|
|
9034
|
-
} catch (
|
|
9269
|
+
} catch (error11) {
|
|
9035
9270
|
return json5({
|
|
9036
|
-
error:
|
|
9271
|
+
error: error11 instanceof Error ? error11.message : "Outbox drain failed",
|
|
9037
9272
|
code: "outbox_drain_failed"
|
|
9038
9273
|
}, 503);
|
|
9039
9274
|
}
|
|
@@ -9783,9 +10018,9 @@ function isNonEmptyString7(value) {
|
|
|
9783
10018
|
return typeof value === "string" && value.length > 0;
|
|
9784
10019
|
}
|
|
9785
10020
|
function base64Json(value) {
|
|
9786
|
-
const
|
|
10021
|
+
const bytes2 = new TextEncoder().encode(JSON.stringify(value));
|
|
9787
10022
|
let binary = "";
|
|
9788
|
-
for (const byte of
|
|
10023
|
+
for (const byte of bytes2) {
|
|
9789
10024
|
binary += String.fromCharCode(byte);
|
|
9790
10025
|
}
|
|
9791
10026
|
return btoa(binary);
|
|
@@ -9963,9 +10198,9 @@ function decodeStoredJson(value) {
|
|
|
9963
10198
|
}
|
|
9964
10199
|
}
|
|
9965
10200
|
function jsonBytes(value) {
|
|
9966
|
-
const
|
|
10201
|
+
const bytes2 = new TextEncoder().encode(JSON.stringify(value));
|
|
9967
10202
|
let binary = "";
|
|
9968
|
-
for (const byte of
|
|
10203
|
+
for (const byte of bytes2) binary += String.fromCharCode(byte);
|
|
9969
10204
|
return btoa(binary);
|
|
9970
10205
|
}
|
|
9971
10206
|
function isRuntimeBridgedProjector(definition) {
|
|
@@ -10158,8 +10393,8 @@ function asNumber2(value, name) {
|
|
|
10158
10393
|
if (!Number.isSafeInteger(number)) throw new Error(`D1 ${name} was not a safe integer`);
|
|
10159
10394
|
return number;
|
|
10160
10395
|
}
|
|
10161
|
-
function errorText3(
|
|
10162
|
-
return (
|
|
10396
|
+
function errorText3(error11) {
|
|
10397
|
+
return (error11 instanceof Error ? error11.message : String(error11)).slice(0, 256);
|
|
10163
10398
|
}
|
|
10164
10399
|
function sortedUnique2(values) {
|
|
10165
10400
|
return [...new Set(values)].sort((left, right) => binaryCompare(left, right));
|
|
@@ -10346,8 +10581,8 @@ var D1EventStore = class {
|
|
|
10346
10581
|
let failure3;
|
|
10347
10582
|
try {
|
|
10348
10583
|
result2 = await this.recordDeliveryCore(message, arrivedAt, deliverySource, mutationAttemptId);
|
|
10349
|
-
} catch (
|
|
10350
|
-
failure3 = { error:
|
|
10584
|
+
} catch (error11) {
|
|
10585
|
+
failure3 = { error: error11 };
|
|
10351
10586
|
}
|
|
10352
10587
|
const retryReason = failure3 === void 0 ? null : errorText3(failure3.error);
|
|
10353
10588
|
const suppressDiagnostic = failure3?.error instanceof D1IdentityConflictError && failure3.error.beforeMutation;
|
|
@@ -10918,8 +11153,8 @@ var D1EventStore = class {
|
|
|
10918
11153
|
if (replay === null || replay === void 0) return finish({ kind: "miss" });
|
|
10919
11154
|
const event = await this.eventById(serviceId, asString3(replay.event_id, "replay.event_id"));
|
|
10920
11155
|
return event === void 0 ? finish({ kind: "unknown", reason: "replay-event-row-disappeared" }) : finish({ kind: "replay", event });
|
|
10921
|
-
} catch (
|
|
10922
|
-
return finish({ kind: "unknown", reason: errorText3(
|
|
11156
|
+
} catch (error11) {
|
|
11157
|
+
return finish({ kind: "unknown", reason: errorText3(error11) });
|
|
10923
11158
|
}
|
|
10924
11159
|
}
|
|
10925
11160
|
/**
|
|
@@ -11394,8 +11629,8 @@ var D1EventStore = class {
|
|
|
11394
11629
|
try {
|
|
11395
11630
|
try {
|
|
11396
11631
|
await this.options.beforeG69AdmissionAttempt?.();
|
|
11397
|
-
} catch (
|
|
11398
|
-
await appendFailedDiagnostic(errorText3(
|
|
11632
|
+
} catch (error11) {
|
|
11633
|
+
await appendFailedDiagnostic(errorText3(error11));
|
|
11399
11634
|
return;
|
|
11400
11635
|
}
|
|
11401
11636
|
const afterObservedAt = Date.now();
|
|
@@ -11469,15 +11704,15 @@ function integer(row, key) {
|
|
|
11469
11704
|
function operationId() {
|
|
11470
11705
|
return `mv-unsafe-${crypto.randomUUID()}`;
|
|
11471
11706
|
}
|
|
11472
|
-
function errorFrom(
|
|
11473
|
-
const value = String(
|
|
11707
|
+
function errorFrom(error11) {
|
|
11708
|
+
const value = String(error11);
|
|
11474
11709
|
if (value.includes("mv_unsafe_receipts") && (value.includes("UNIQUE") || value.includes("constraint"))) {
|
|
11475
|
-
return new UnsafeWindowMaterializedViewError("UNSAFE_DUPLICATE_RACE", false, "Concurrent unsafe receipt was committed by another delivery", { cause:
|
|
11710
|
+
return new UnsafeWindowMaterializedViewError("UNSAFE_DUPLICATE_RACE", false, "Concurrent unsafe receipt was committed by another delivery", { cause: error11 });
|
|
11476
11711
|
}
|
|
11477
|
-
if (value.includes("unsafe_row_cas")) return new UnsafeWindowMaterializedViewError("UNSAFE_ROW_CAS_MISMATCH", true, "Unsafe row version changed before atomic apply", { cause:
|
|
11478
|
-
if (value.includes("unsafe_suid_contradiction")) return new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Unsafe same-SUID payload contradiction", { cause:
|
|
11479
|
-
if (value.includes("unsafe_safe_ahead")) return new UnsafeWindowMaterializedViewError("UNSAFE_SAFE_AHEAD", false, "Safe head already covers unsafe event", { cause:
|
|
11480
|
-
if (value.includes("unsafe_behind_frontier")) return new UnsafeWindowMaterializedViewError("UNSAFE_BEHIND_FRONTIER", false, "Unsafe reads are fail-closed pending rebuild", { cause:
|
|
11712
|
+
if (value.includes("unsafe_row_cas")) return new UnsafeWindowMaterializedViewError("UNSAFE_ROW_CAS_MISMATCH", true, "Unsafe row version changed before atomic apply", { cause: error11 });
|
|
11713
|
+
if (value.includes("unsafe_suid_contradiction")) return new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Unsafe same-SUID payload contradiction", { cause: error11 });
|
|
11714
|
+
if (value.includes("unsafe_safe_ahead")) return new UnsafeWindowMaterializedViewError("UNSAFE_SAFE_AHEAD", false, "Safe head already covers unsafe event", { cause: error11 });
|
|
11715
|
+
if (value.includes("unsafe_behind_frontier")) return new UnsafeWindowMaterializedViewError("UNSAFE_BEHIND_FRONTIER", false, "Unsafe reads are fail-closed pending rebuild", { cause: error11 });
|
|
11481
11716
|
return void 0;
|
|
11482
11717
|
}
|
|
11483
11718
|
var UnsafeWindowMaterializedViewStore = class {
|
|
@@ -11610,8 +11845,8 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
11610
11845
|
await this.options.beforeApplyBatch?.(input, statements);
|
|
11611
11846
|
try {
|
|
11612
11847
|
await this.database.batch(statements);
|
|
11613
|
-
} catch (
|
|
11614
|
-
const text = String(
|
|
11848
|
+
} catch (error11) {
|
|
11849
|
+
const text = String(error11);
|
|
11615
11850
|
const guardFailure = text.includes("mv_atomic_guards") || text.includes("checkpoint_match") || text.includes("mv_unsafe_receipts") && (text.includes("UNIQUE") || text.includes("constraint"));
|
|
11616
11851
|
if (guardFailure) {
|
|
11617
11852
|
const receipt = await this.database.prepare(
|
|
@@ -11619,16 +11854,16 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
11619
11854
|
).bind(input.serviceId, input.viewId, input.eventId).first();
|
|
11620
11855
|
if (receipt !== null && receipt !== void 0) {
|
|
11621
11856
|
if (string2(receipt, "suid") !== input.suid) {
|
|
11622
|
-
throw new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Duplicate event identity has a contradictory SUID", { cause:
|
|
11857
|
+
throw new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Duplicate event identity has a contradictory SUID", { cause: error11 });
|
|
11623
11858
|
}
|
|
11624
11859
|
if (this.options.beforeApplyBatch !== void 0) {
|
|
11625
|
-
throw new UnsafeWindowMaterializedViewError("UNSAFE_DUPLICATE_RACE", false, "Concurrent unsafe receipt was committed by another delivery", { cause:
|
|
11860
|
+
throw new UnsafeWindowMaterializedViewError("UNSAFE_DUPLICATE_RACE", false, "Concurrent unsafe receipt was committed by another delivery", { cause: error11 });
|
|
11626
11861
|
}
|
|
11627
11862
|
return { outcome: "no-change", duplicate: true };
|
|
11628
11863
|
}
|
|
11629
11864
|
return this.apply(input, true);
|
|
11630
11865
|
}
|
|
11631
|
-
throw
|
|
11866
|
+
throw error11;
|
|
11632
11867
|
}
|
|
11633
11868
|
const result2 = { outcome: "applied", duplicate: false };
|
|
11634
11869
|
await this.options.afterApplyBatch?.(input, result2);
|
|
@@ -11761,13 +11996,13 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
11761
11996
|
await this.options.beforeApplyBatch?.(input, statements);
|
|
11762
11997
|
try {
|
|
11763
11998
|
await this.database.batch(statements);
|
|
11764
|
-
} catch (
|
|
11765
|
-
if (sameSuidContradiction) throw new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Unsafe same-SUID payload contradiction", { cause:
|
|
11766
|
-
if (rowCasMismatch) throw new UnsafeWindowMaterializedViewError("UNSAFE_ROW_CAS_MISMATCH", true, "Unsafe row version changed before atomic apply", { cause:
|
|
11767
|
-
if (safeAhead) throw new UnsafeWindowMaterializedViewError("UNSAFE_SAFE_AHEAD", false, "Safe head already covers unsafe event", { cause:
|
|
11768
|
-
const typed = errorFrom(
|
|
11999
|
+
} catch (error11) {
|
|
12000
|
+
if (sameSuidContradiction) throw new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Unsafe same-SUID payload contradiction", { cause: error11 });
|
|
12001
|
+
if (rowCasMismatch) throw new UnsafeWindowMaterializedViewError("UNSAFE_ROW_CAS_MISMATCH", true, "Unsafe row version changed before atomic apply", { cause: error11 });
|
|
12002
|
+
if (safeAhead) throw new UnsafeWindowMaterializedViewError("UNSAFE_SAFE_AHEAD", false, "Safe head already covers unsafe event", { cause: error11 });
|
|
12003
|
+
const typed = errorFrom(error11);
|
|
11769
12004
|
if (typed !== void 0) throw typed;
|
|
11770
|
-
throw
|
|
12005
|
+
throw error11;
|
|
11771
12006
|
}
|
|
11772
12007
|
const result2 = { outcome, duplicate: false };
|
|
11773
12008
|
await this.options.afterApplyBatch?.(input, result2);
|
|
@@ -11834,8 +12069,8 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
11834
12069
|
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),
|
|
11835
12070
|
this.database.prepare("DELETE FROM mv_atomic_guards WHERE operation_id = ?").bind(operation2)
|
|
11836
12071
|
]);
|
|
11837
|
-
} catch (
|
|
11838
|
-
throw new UnsafeWindowMaterializedViewError("UNSAFE_KICK_LEASE_HELD", true, "Unsafe kick is held by another lease owner", { cause:
|
|
12072
|
+
} catch (error11) {
|
|
12073
|
+
throw new UnsafeWindowMaterializedViewError("UNSAFE_KICK_LEASE_HELD", true, "Unsafe kick is held by another lease owner", { cause: error11 });
|
|
11839
12074
|
}
|
|
11840
12075
|
const row = await this.database.prepare(`SELECT target_suid, dirty FROM mv_unsafe_kicks WHERE service_id = ? AND view_id = ?`).bind(serviceId, viewId).first();
|
|
11841
12076
|
return row === null || row === void 0 ? void 0 : { targetSuid: string2(row, "target_suid"), dirty: integer(row, "dirty") === 1 };
|
|
@@ -11909,9 +12144,9 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
11909
12144
|
try {
|
|
11910
12145
|
await this.database.batch(statements);
|
|
11911
12146
|
return true;
|
|
11912
|
-
} catch (
|
|
11913
|
-
if (String(
|
|
11914
|
-
throw
|
|
12147
|
+
} catch (error11) {
|
|
12148
|
+
if (String(error11).includes("mv_atomic_guards") || String(error11).includes("checkpoint_match")) return false;
|
|
12149
|
+
throw error11;
|
|
11915
12150
|
}
|
|
11916
12151
|
}
|
|
11917
12152
|
/**
|
|
@@ -12077,12 +12312,12 @@ function indexValue(row, type) {
|
|
|
12077
12312
|
function operationId2() {
|
|
12078
12313
|
return `mv-${crypto.randomUUID()}`;
|
|
12079
12314
|
}
|
|
12080
|
-
function isCasFailure(
|
|
12081
|
-
const message = String(
|
|
12315
|
+
function isCasFailure(error11) {
|
|
12316
|
+
const message = String(error11);
|
|
12082
12317
|
return message.includes("mv_atomic_guards.checkpoint_match") || message.includes("MV_CAS_MISMATCH");
|
|
12083
12318
|
}
|
|
12084
|
-
function isPatchFailure(
|
|
12085
|
-
return String(
|
|
12319
|
+
function isPatchFailure(error11) {
|
|
12320
|
+
return String(error11).includes("CHECK constraint failed: checkpoint_match");
|
|
12086
12321
|
}
|
|
12087
12322
|
var D1MaterializedViewStore = class {
|
|
12088
12323
|
constructor(database) {
|
|
@@ -12118,8 +12353,8 @@ var D1MaterializedViewStore = class {
|
|
|
12118
12353
|
VALUES (?, ?, ?, ?)`
|
|
12119
12354
|
).bind(input.serviceId, input.viewId, generation, input.updatedAt)
|
|
12120
12355
|
]);
|
|
12121
|
-
} catch (
|
|
12122
|
-
throw new MaterializedViewStoreError("create-active", "MV_INSTANCE_EXISTS", `Active materialized view already exists: ${String(
|
|
12356
|
+
} catch (error11) {
|
|
12357
|
+
throw new MaterializedViewStoreError("create-active", "MV_INSTANCE_EXISTS", `Active materialized view already exists: ${String(error11)}`, { cause: error11 });
|
|
12123
12358
|
}
|
|
12124
12359
|
return this.readInstance(input.serviceId, input.viewId, generation);
|
|
12125
12360
|
}
|
|
@@ -12140,8 +12375,8 @@ var D1MaterializedViewStore = class {
|
|
|
12140
12375
|
input.definitionVersion,
|
|
12141
12376
|
input.updatedAt
|
|
12142
12377
|
).run();
|
|
12143
|
-
} catch (
|
|
12144
|
-
throw new MaterializedViewStoreError("create-candidate", "MV_INSTANCE_EXISTS", `Candidate generation already exists: ${String(
|
|
12378
|
+
} catch (error11) {
|
|
12379
|
+
throw new MaterializedViewStoreError("create-candidate", "MV_INSTANCE_EXISTS", `Candidate generation already exists: ${String(error11)}`, { cause: error11 });
|
|
12145
12380
|
}
|
|
12146
12381
|
return this.readInstance(input.serviceId, input.viewId, input.generation);
|
|
12147
12382
|
}
|
|
@@ -12279,10 +12514,10 @@ var D1MaterializedViewStore = class {
|
|
|
12279
12514
|
*/
|
|
12280
12515
|
async readListPage(serviceId, viewId, options = {}) {
|
|
12281
12516
|
this.ready("initialize");
|
|
12282
|
-
const
|
|
12283
|
-
if (
|
|
12284
|
-
const selected = options.generation ??
|
|
12285
|
-
const selectedInstance = selected ===
|
|
12517
|
+
const active2 = await this.readActive(serviceId, viewId);
|
|
12518
|
+
if (active2 === void 0) return { rows: [], totalCount: 0, readHead: "" };
|
|
12519
|
+
const selected = options.generation ?? active2.generation;
|
|
12520
|
+
const selectedInstance = selected === active2.generation ? active2 : await this.readInstance(serviceId, viewId, selected);
|
|
12286
12521
|
if (selectedInstance === void 0) {
|
|
12287
12522
|
throw new MaterializedViewStoreError("initialize", "MV_INSTANCE_MISSING", "Requested materialized-view generation is unavailable");
|
|
12288
12523
|
}
|
|
@@ -12799,10 +13034,10 @@ var D1MaterializedViewStore = class {
|
|
|
12799
13034
|
).bind(operation2, `${operation2}:%`));
|
|
12800
13035
|
try {
|
|
12801
13036
|
await this.database.batch(statements);
|
|
12802
|
-
} catch (
|
|
12803
|
-
if (isPatchFailure(
|
|
12804
|
-
if (isCasFailure(
|
|
12805
|
-
throw new MaterializedViewStoreError("apply", "MV_STORE_OPERATION_FAILED", `Materialized-view atomic apply failed: ${String(
|
|
13037
|
+
} catch (error11) {
|
|
13038
|
+
if (isPatchFailure(error11)) throw new MaterializedViewPatchError();
|
|
13039
|
+
if (isCasFailure(error11)) throw new MaterializedViewCasError();
|
|
13040
|
+
throw new MaterializedViewStoreError("apply", "MV_STORE_OPERATION_FAILED", `Materialized-view atomic apply failed: ${String(error11)}`, { cause: error11 });
|
|
12806
13041
|
}
|
|
12807
13042
|
const instance = await this.readInstance(input.serviceId, input.viewId, input.generation);
|
|
12808
13043
|
if (instance === void 0) throw new MaterializedViewStoreError("apply", "MV_INSTANCE_MISSING", "Materialized-view instance disappeared after apply");
|
|
@@ -12907,13 +13142,13 @@ var D1MaterializedViewStore = class {
|
|
|
12907
13142
|
];
|
|
12908
13143
|
try {
|
|
12909
13144
|
await this.database.batch(statements);
|
|
12910
|
-
} catch (
|
|
12911
|
-
if (isCasFailure(
|
|
12912
|
-
throw new MaterializedViewStoreError("promote", "MV_STORE_OPERATION_FAILED", `Materialized-view promotion failed: ${String(
|
|
13145
|
+
} catch (error11) {
|
|
13146
|
+
if (isCasFailure(error11)) throw new MaterializedViewPromotionCasError();
|
|
13147
|
+
throw new MaterializedViewStoreError("promote", "MV_STORE_OPERATION_FAILED", `Materialized-view promotion failed: ${String(error11)}`, { cause: error11 });
|
|
12913
13148
|
}
|
|
12914
|
-
const
|
|
12915
|
-
if (
|
|
12916
|
-
return
|
|
13149
|
+
const active2 = await this.readActive(input.serviceId, input.viewId);
|
|
13150
|
+
if (active2 === void 0) throw new MaterializedViewStoreError("promote", "MV_INSTANCE_MISSING", "Active generation disappeared after promotion");
|
|
13151
|
+
return active2;
|
|
12917
13152
|
}
|
|
12918
13153
|
/** Short aliases make the port convenient for runtime composition and tests. */
|
|
12919
13154
|
apply = this.applyMutationsAndAdvanceCheckpoint.bind(this);
|
|
@@ -12922,8 +13157,8 @@ var D1MaterializedViewStore = class {
|
|
|
12922
13157
|
let value;
|
|
12923
13158
|
try {
|
|
12924
13159
|
value = assertJsonValue(JSON.parse(asString4(row.value_json, "value_json")), "state-persistence");
|
|
12925
|
-
} catch (
|
|
12926
|
-
throw new MaterializedViewStoreError("initialize", "MV_VALUE_INVALID", `Materialized-view row JSON was invalid: ${String(
|
|
13160
|
+
} catch (error11) {
|
|
13161
|
+
throw new MaterializedViewStoreError("initialize", "MV_VALUE_INVALID", `Materialized-view row JSON was invalid: ${String(error11)}`, { cause: error11 });
|
|
12927
13162
|
}
|
|
12928
13163
|
return {
|
|
12929
13164
|
serviceId: asString4(row.service_id, "service_id"),
|
|
@@ -13047,8 +13282,8 @@ function stateFromCheckpoint(projector, checkpoint) {
|
|
|
13047
13282
|
if (checkpoint === void 0) return projector.initialState();
|
|
13048
13283
|
try {
|
|
13049
13284
|
return projector.deserializeState(checkpoint.stateJson);
|
|
13050
|
-
} catch (
|
|
13051
|
-
throw new ProjectionCheckpointCorruption(
|
|
13285
|
+
} catch (error11) {
|
|
13286
|
+
throw new ProjectionCheckpointCorruption(error11);
|
|
13052
13287
|
}
|
|
13053
13288
|
}
|
|
13054
13289
|
function projectionEventFromStored(event) {
|
|
@@ -13279,8 +13514,8 @@ async function admitBootstrapRoute2(env, serviceId) {
|
|
|
13279
13514
|
})).fetch(new Request(url, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ route: "projection-rebuild" }) }));
|
|
13280
13515
|
if (!admitted.ok) throw new Error("bootstrap_route_rejected:projection-rebuild");
|
|
13281
13516
|
}
|
|
13282
|
-
function boundedErrorReason(
|
|
13283
|
-
const value =
|
|
13517
|
+
function boundedErrorReason(error11) {
|
|
13518
|
+
const value = error11 instanceof Error ? error11.message : String(error11);
|
|
13284
13519
|
return value.length > 256 ? `${value.slice(0, 253)}...` : value;
|
|
13285
13520
|
}
|
|
13286
13521
|
async function notifyObserver(observer, callback, input) {
|
|
@@ -13293,7 +13528,7 @@ async function notifyObserver(observer, callback, input) {
|
|
|
13293
13528
|
} catch {
|
|
13294
13529
|
}
|
|
13295
13530
|
}
|
|
13296
|
-
async function notifyOutcomes(observer, serviceId, projectorIds, attemptedAt, results, maximumSuid, env,
|
|
13531
|
+
async function notifyOutcomes(observer, serviceId, projectorIds, attemptedAt, results, maximumSuid, env, error11) {
|
|
13297
13532
|
if (observer?.onOutcome === void 0) return;
|
|
13298
13533
|
const byProjector = /* @__PURE__ */ new Map();
|
|
13299
13534
|
for (const result2 of results) {
|
|
@@ -13306,9 +13541,9 @@ async function notifyOutcomes(observer, serviceId, projectorIds, attemptedAt, re
|
|
|
13306
13541
|
let outcome = "invoked-but-no-work";
|
|
13307
13542
|
let reason = null;
|
|
13308
13543
|
let advancedSourceEvents = 0;
|
|
13309
|
-
if (
|
|
13544
|
+
if (error11 !== void 0) {
|
|
13310
13545
|
outcome = "invoked-and-threw";
|
|
13311
|
-
reason = boundedErrorReason(
|
|
13546
|
+
reason = boundedErrorReason(error11);
|
|
13312
13547
|
} else {
|
|
13313
13548
|
advancedSourceEvents = projectorResults.reduce((total, result2) => total + result2.advancedSourceEvents, 0);
|
|
13314
13549
|
if (advancedSourceEvents > 0) {
|
|
@@ -13410,9 +13645,9 @@ async function pollLiveProjections(env, options = {}) {
|
|
|
13410
13645
|
const results = await runtime.pollRegistered(serviceId, attemptedAt, options.maximumSuid);
|
|
13411
13646
|
await notifyOutcomes(options.observer, serviceId, projectorIds, attemptedAt, results, options.maximumSuid, env);
|
|
13412
13647
|
return results;
|
|
13413
|
-
} catch (
|
|
13414
|
-
await notifyOutcomes(options.observer, serviceId, projectorIds, attemptedAt, [], options.maximumSuid, env,
|
|
13415
|
-
throw
|
|
13648
|
+
} catch (error11) {
|
|
13649
|
+
await notifyOutcomes(options.observer, serviceId, projectorIds, attemptedAt, [], options.maximumSuid, env, error11);
|
|
13650
|
+
throw error11;
|
|
13416
13651
|
}
|
|
13417
13652
|
}
|
|
13418
13653
|
async function handleProjectionLag(request, env, registry = DEPLOYED_PROJECTOR_REGISTRY, storeProvider, serviceIdentityProvider) {
|
|
@@ -14230,10 +14465,10 @@ var SerializedReadWorker = class {
|
|
|
14230
14465
|
});
|
|
14231
14466
|
}
|
|
14232
14467
|
async ensureWindowDeterminate() {
|
|
14233
|
-
if (this.storeProvider === void 0 || this.storeProvider.isConfigured?.(this.env) === false) {
|
|
14234
|
-
return;
|
|
14235
|
-
}
|
|
14236
14468
|
try {
|
|
14469
|
+
if (this.storeProvider === void 0 || this.storeProvider.isConfigured?.(this.env) === false) {
|
|
14470
|
+
return;
|
|
14471
|
+
}
|
|
14237
14472
|
const store = this.storeProvider.create(this.env);
|
|
14238
14473
|
await store.initialize();
|
|
14239
14474
|
const nowMs = Date.now();
|
|
@@ -14274,13 +14509,683 @@ async function handleSerializedRead(request, env, registry = DEPLOYED_PROJECTOR_
|
|
|
14274
14509
|
}), registry, storeProvider).handle(request);
|
|
14275
14510
|
}
|
|
14276
14511
|
|
|
14512
|
+
// src/completeness/IncidentLifecycle.ts
|
|
14513
|
+
var MAX_IDENTITY_BYTES = 256;
|
|
14514
|
+
var MAX_LONG_TEXT_BYTES = 2048;
|
|
14515
|
+
var SHA256_DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
14516
|
+
var IncidentLifecycleError = class extends Error {
|
|
14517
|
+
code;
|
|
14518
|
+
status;
|
|
14519
|
+
constructor(code, status, message) {
|
|
14520
|
+
super(message);
|
|
14521
|
+
this.name = "IncidentLifecycleError";
|
|
14522
|
+
this.code = code;
|
|
14523
|
+
this.status = status;
|
|
14524
|
+
}
|
|
14525
|
+
};
|
|
14526
|
+
function isObject11(value) {
|
|
14527
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14528
|
+
}
|
|
14529
|
+
function hasOnlyKeys(value, keys) {
|
|
14530
|
+
const allowed = new Set(keys);
|
|
14531
|
+
return Object.keys(value).every((key) => allowed.has(key));
|
|
14532
|
+
}
|
|
14533
|
+
function bytes(value) {
|
|
14534
|
+
return new TextEncoder().encode(value).byteLength;
|
|
14535
|
+
}
|
|
14536
|
+
function boundedString(value, name, maximum, required = true) {
|
|
14537
|
+
if (typeof value !== "string") {
|
|
14538
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, `${name} must be a string`);
|
|
14539
|
+
}
|
|
14540
|
+
const normalized = value.trim();
|
|
14541
|
+
if (required && normalized.length === 0) {
|
|
14542
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, `${name} must be non-empty`);
|
|
14543
|
+
}
|
|
14544
|
+
if (bytes(normalized) > maximum) {
|
|
14545
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, `${name} is too long`);
|
|
14546
|
+
}
|
|
14547
|
+
return normalized.length === 0 ? null : normalized;
|
|
14548
|
+
}
|
|
14549
|
+
function normalizeIncidentIdentity(value) {
|
|
14550
|
+
const identity2 = boundedString(value, "incidentIdentity", MAX_IDENTITY_BYTES);
|
|
14551
|
+
if (identity2 === null) {
|
|
14552
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "incidentIdentity must be non-empty");
|
|
14553
|
+
}
|
|
14554
|
+
return identity2;
|
|
14555
|
+
}
|
|
14556
|
+
function safeInteger(value, name) {
|
|
14557
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value)) {
|
|
14558
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, `${name} must be a safe integer`);
|
|
14559
|
+
}
|
|
14560
|
+
return value;
|
|
14561
|
+
}
|
|
14562
|
+
function normalizeCorrection(value) {
|
|
14563
|
+
if (!isObject11(value) || !hasOnlyKeys(value, ["kind", "reference", "digest"])) {
|
|
14564
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "correction has unknown or missing keys");
|
|
14565
|
+
}
|
|
14566
|
+
if (value.kind !== "event" && value.kind !== "receipt") {
|
|
14567
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "correction.kind must be event or receipt");
|
|
14568
|
+
}
|
|
14569
|
+
const reference = boundedString(value.reference, "correction.reference", MAX_LONG_TEXT_BYTES);
|
|
14570
|
+
const digest2 = boundedString(value.digest, "correction.digest", MAX_IDENTITY_BYTES);
|
|
14571
|
+
if (reference === null || digest2 === null || !SHA256_DIGEST.test(digest2)) {
|
|
14572
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "correction.digest must be lowercase sha256");
|
|
14573
|
+
}
|
|
14574
|
+
return { kind: value.kind, reference, digest: digest2 };
|
|
14575
|
+
}
|
|
14576
|
+
function normalizeRequest(value) {
|
|
14577
|
+
if (!isObject11(value) || !hasOnlyKeys(value, ["action", "incidentIdentity", "transitionKey", "expectedVersion", "reason", "ownerId", "deadlineAt", "correction", "resolution"])) {
|
|
14578
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "transition has unknown keys");
|
|
14579
|
+
}
|
|
14580
|
+
const action = value.action;
|
|
14581
|
+
if (typeof action !== "string" || !["ACKNOWLEDGE", "UPDATE_ASSIGNMENT", "RECORD_CORRECTION", "CLOSE", "REOPEN"].includes(action)) {
|
|
14582
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "transition action is invalid");
|
|
14583
|
+
}
|
|
14584
|
+
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"];
|
|
14585
|
+
if (!hasOnlyKeys(value, actionKeys)) {
|
|
14586
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "transition has keys not valid for its action");
|
|
14587
|
+
}
|
|
14588
|
+
const base = {
|
|
14589
|
+
incidentIdentity: normalizeIncidentIdentity(value.incidentIdentity),
|
|
14590
|
+
transitionKey: boundedString(value.transitionKey, "transitionKey", MAX_IDENTITY_BYTES),
|
|
14591
|
+
expectedVersion: safeInteger(value.expectedVersion, "expectedVersion"),
|
|
14592
|
+
reason: boundedString(value.reason, "reason", MAX_LONG_TEXT_BYTES)
|
|
14593
|
+
};
|
|
14594
|
+
if (base.expectedVersion < 0) {
|
|
14595
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "expectedVersion must not be negative");
|
|
14596
|
+
}
|
|
14597
|
+
if (action === "ACKNOWLEDGE" || action === "UPDATE_ASSIGNMENT" || action === "REOPEN") {
|
|
14598
|
+
const ownerId = boundedString(value.ownerId, "ownerId", MAX_IDENTITY_BYTES);
|
|
14599
|
+
const deadlineAt = safeInteger(value.deadlineAt, "deadlineAt");
|
|
14600
|
+
if (ownerId === null) throw new IncidentLifecycleError("incident_invalid_request", 400, "ownerId must be non-empty");
|
|
14601
|
+
return { ...base, action, ownerId, deadlineAt };
|
|
14602
|
+
}
|
|
14603
|
+
if (action === "RECORD_CORRECTION") {
|
|
14604
|
+
return { ...base, action, correction: normalizeCorrection(value.correction) };
|
|
14605
|
+
}
|
|
14606
|
+
if (!isObject11(value.resolution) || !hasOnlyKeys(value.resolution, ["kind", "explanation"])) {
|
|
14607
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "resolution has unknown or missing keys");
|
|
14608
|
+
}
|
|
14609
|
+
if (value.resolution.kind === "CORRECTED") {
|
|
14610
|
+
if (value.resolution.explanation !== void 0) {
|
|
14611
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "CORRECTED resolution has unknown keys");
|
|
14612
|
+
}
|
|
14613
|
+
return { ...base, action, resolution: { kind: "CORRECTED" } };
|
|
14614
|
+
}
|
|
14615
|
+
if (value.resolution.kind === "ACCEPTED_AS_IS") {
|
|
14616
|
+
const explanation = boundedString(value.resolution.explanation, "resolution.explanation", MAX_LONG_TEXT_BYTES);
|
|
14617
|
+
if (explanation === null) throw new IncidentLifecycleError("incident_invalid_request", 400, "resolution.explanation is required");
|
|
14618
|
+
return { ...base, action, resolution: { kind: "ACCEPTED_AS_IS", explanation } };
|
|
14619
|
+
}
|
|
14620
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "resolution.kind is invalid");
|
|
14621
|
+
}
|
|
14622
|
+
function canonicalize(value) {
|
|
14623
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
14624
|
+
if (!isObject11(value)) return value;
|
|
14625
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]));
|
|
14626
|
+
}
|
|
14627
|
+
async function digestFor(request, actorId) {
|
|
14628
|
+
const envelope = canonicalize({ action: request, actor: actorId });
|
|
14629
|
+
const encoded = new TextEncoder().encode(JSON.stringify(envelope));
|
|
14630
|
+
const digest2 = await crypto.subtle.digest("SHA-256", encoded);
|
|
14631
|
+
return `sha256:${Array.from(new Uint8Array(digest2), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
14632
|
+
}
|
|
14633
|
+
function projectionFromRow(row, finding2) {
|
|
14634
|
+
if (row === null) {
|
|
14635
|
+
return {
|
|
14636
|
+
serviceId: finding2.service_id,
|
|
14637
|
+
incidentIdentity: finding2.incident_identity,
|
|
14638
|
+
lifecycleState: "OPEN",
|
|
14639
|
+
ownerId: null,
|
|
14640
|
+
deadlineAt: null,
|
|
14641
|
+
correction: null,
|
|
14642
|
+
closeResolution: null,
|
|
14643
|
+
closeReason: null,
|
|
14644
|
+
version: 0,
|
|
14645
|
+
lastTransitionKey: "",
|
|
14646
|
+
updatedAt: finding2.last_observed_at
|
|
14647
|
+
};
|
|
14648
|
+
}
|
|
14649
|
+
return {
|
|
14650
|
+
serviceId: row.service_id,
|
|
14651
|
+
incidentIdentity: row.incident_identity,
|
|
14652
|
+
lifecycleState: row.lifecycle_state,
|
|
14653
|
+
ownerId: row.owner_id,
|
|
14654
|
+
deadlineAt: row.deadline_at,
|
|
14655
|
+
correction: row.correction_kind === null ? null : {
|
|
14656
|
+
kind: row.correction_kind,
|
|
14657
|
+
reference: row.correction_reference,
|
|
14658
|
+
digest: row.correction_digest
|
|
14659
|
+
},
|
|
14660
|
+
closeResolution: row.close_resolution,
|
|
14661
|
+
closeReason: row.close_reason,
|
|
14662
|
+
version: row.version,
|
|
14663
|
+
lastTransitionKey: row.last_transition_key,
|
|
14664
|
+
updatedAt: row.updated_at
|
|
14665
|
+
};
|
|
14666
|
+
}
|
|
14667
|
+
function findingFromRow(row) {
|
|
14668
|
+
return {
|
|
14669
|
+
serviceId: row.service_id,
|
|
14670
|
+
incidentIdentity: row.incident_identity,
|
|
14671
|
+
incidentType: row.incident_type,
|
|
14672
|
+
partitionTag: row.partition_tag,
|
|
14673
|
+
obligationSequence: row.obligation_sequence,
|
|
14674
|
+
eventId: row.event_id,
|
|
14675
|
+
eventDigest: row.event_digest,
|
|
14676
|
+
state: row.finding_state,
|
|
14677
|
+
firstObservedAt: row.first_observed_at,
|
|
14678
|
+
lastObservedAt: row.last_observed_at
|
|
14679
|
+
};
|
|
14680
|
+
}
|
|
14681
|
+
function transitionFromRow(row) {
|
|
14682
|
+
const before = projectionFromAudit(row, "before");
|
|
14683
|
+
const after = projectionFromAudit(row, "after");
|
|
14684
|
+
return {
|
|
14685
|
+
transitionId: row.transition_id,
|
|
14686
|
+
serviceId: row.service_id,
|
|
14687
|
+
incidentIdentity: row.incident_identity,
|
|
14688
|
+
transitionKey: row.transition_key,
|
|
14689
|
+
requestDigest: row.request_digest,
|
|
14690
|
+
action: row.action,
|
|
14691
|
+
fromState: row.from_state,
|
|
14692
|
+
toState: row.to_state,
|
|
14693
|
+
fromVersion: row.from_version,
|
|
14694
|
+
toVersion: row.to_version,
|
|
14695
|
+
actorId: row.actor_id,
|
|
14696
|
+
before,
|
|
14697
|
+
after,
|
|
14698
|
+
reason: row.reason,
|
|
14699
|
+
occurredAt: row.occurred_at
|
|
14700
|
+
};
|
|
14701
|
+
}
|
|
14702
|
+
function projectionFromAudit(row, prefix) {
|
|
14703
|
+
const state = prefix === "before" ? row.from_state : row.to_state;
|
|
14704
|
+
const version = prefix === "before" ? row.from_version : row.to_version;
|
|
14705
|
+
const ownerId = row[`${prefix}_owner_id`];
|
|
14706
|
+
const correctionKind = row[`${prefix}_correction_kind`];
|
|
14707
|
+
return {
|
|
14708
|
+
serviceId: row.service_id,
|
|
14709
|
+
incidentIdentity: row.incident_identity,
|
|
14710
|
+
lifecycleState: state,
|
|
14711
|
+
ownerId,
|
|
14712
|
+
deadlineAt: row[`${prefix}_deadline_at`],
|
|
14713
|
+
correction: correctionKind === null ? null : {
|
|
14714
|
+
kind: correctionKind,
|
|
14715
|
+
reference: row[`${prefix}_correction_reference`],
|
|
14716
|
+
digest: row[`${prefix}_correction_digest`]
|
|
14717
|
+
},
|
|
14718
|
+
closeResolution: row[`${prefix}_close_resolution`],
|
|
14719
|
+
closeReason: row[`${prefix}_close_reason`],
|
|
14720
|
+
version,
|
|
14721
|
+
lastTransitionKey: prefix === "after" ? row.transition_key : "",
|
|
14722
|
+
updatedAt: row.occurred_at
|
|
14723
|
+
};
|
|
14724
|
+
}
|
|
14725
|
+
function active(state) {
|
|
14726
|
+
return state !== "CLOSED";
|
|
14727
|
+
}
|
|
14728
|
+
function compareItems(left, right) {
|
|
14729
|
+
if (left.overdue !== right.overdue) return left.overdue ? -1 : 1;
|
|
14730
|
+
if (left.unowned !== right.unowned) return left.unowned ? -1 : 1;
|
|
14731
|
+
if (left.finding.lastObservedAt !== right.finding.lastObservedAt) return right.finding.lastObservedAt - left.finding.lastObservedAt;
|
|
14732
|
+
return left.finding.incidentIdentity < right.finding.incidentIdentity ? -1 : left.finding.incidentIdentity > right.finding.incidentIdentity ? 1 : 0;
|
|
14733
|
+
}
|
|
14734
|
+
var IncidentLifecycle = class {
|
|
14735
|
+
constructor(database, now2 = () => Date.now()) {
|
|
14736
|
+
this.database = database;
|
|
14737
|
+
this.now = now2;
|
|
14738
|
+
}
|
|
14739
|
+
database;
|
|
14740
|
+
now;
|
|
14741
|
+
static parseTransition(value) {
|
|
14742
|
+
return normalizeRequest(value);
|
|
14743
|
+
}
|
|
14744
|
+
async finding(serviceId, incidentIdentity) {
|
|
14745
|
+
const row = await this.database.prepare(
|
|
14746
|
+
`SELECT service_id, incident_identity, incident_type, partition_tag,
|
|
14747
|
+
obligation_sequence, event_id, event_digest, state AS finding_state,
|
|
14748
|
+
first_observed_at, last_observed_at
|
|
14749
|
+
FROM serialized_dcb_completeness_findings
|
|
14750
|
+
WHERE service_id = ? AND incident_identity = ?`
|
|
14751
|
+
).bind(serviceId, incidentIdentity).first();
|
|
14752
|
+
if (row === null || row === void 0) {
|
|
14753
|
+
throw new IncidentLifecycleError("incident_not_found", 404, "The completeness incident was not found");
|
|
14754
|
+
}
|
|
14755
|
+
return row;
|
|
14756
|
+
}
|
|
14757
|
+
async lifecycle(serviceId, incidentIdentity) {
|
|
14758
|
+
const row = await this.database.prepare(
|
|
14759
|
+
`SELECT service_id, incident_identity, lifecycle_state, owner_id, deadline_at,
|
|
14760
|
+
correction_kind, correction_reference, correction_digest,
|
|
14761
|
+
close_resolution, close_reason, version, last_transition_key, updated_at
|
|
14762
|
+
FROM serialized_dcb_incident_lifecycles
|
|
14763
|
+
WHERE service_id = ? AND incident_identity = ?`
|
|
14764
|
+
).bind(serviceId, incidentIdentity).first();
|
|
14765
|
+
return row === void 0 ? null : row;
|
|
14766
|
+
}
|
|
14767
|
+
async transitions(serviceId, incidentIdentity) {
|
|
14768
|
+
const result2 = await this.database.prepare(
|
|
14769
|
+
`SELECT * FROM serialized_dcb_incident_transitions
|
|
14770
|
+
WHERE service_id = ? AND incident_identity = ?
|
|
14771
|
+
ORDER BY transition_id ASC`
|
|
14772
|
+
).bind(serviceId, incidentIdentity).all();
|
|
14773
|
+
return result2.results.map(transitionFromRow);
|
|
14774
|
+
}
|
|
14775
|
+
async closeTime(serviceId, incidentIdentity) {
|
|
14776
|
+
const row = await this.database.prepare(
|
|
14777
|
+
`SELECT occurred_at FROM serialized_dcb_incident_transitions
|
|
14778
|
+
WHERE service_id = ? AND incident_identity = ? AND to_state = 'CLOSED'
|
|
14779
|
+
ORDER BY transition_id DESC LIMIT 1`
|
|
14780
|
+
).bind(serviceId, incidentIdentity).first();
|
|
14781
|
+
return row?.occurred_at ?? null;
|
|
14782
|
+
}
|
|
14783
|
+
async item(finding2, row, at) {
|
|
14784
|
+
const lifecycle = projectionFromRow(row, finding2);
|
|
14785
|
+
const closeTime = lifecycle.lifecycleState === "OPEN" && row === null ? null : await this.closeTime(finding2.service_id, finding2.incident_identity);
|
|
14786
|
+
const observedAfterClose = closeTime !== null && finding2.last_observed_at > closeTime;
|
|
14787
|
+
const overdue = active(lifecycle.lifecycleState) && lifecycle.deadlineAt !== null && lifecycle.deadlineAt < at;
|
|
14788
|
+
return {
|
|
14789
|
+
finding: findingFromRow(finding2),
|
|
14790
|
+
lifecycle,
|
|
14791
|
+
overdue,
|
|
14792
|
+
unowned: lifecycle.ownerId === null,
|
|
14793
|
+
observedAfterClose
|
|
14794
|
+
};
|
|
14795
|
+
}
|
|
14796
|
+
async list(serviceId, filters = {}, at = this.now()) {
|
|
14797
|
+
const rows = await this.database.prepare(
|
|
14798
|
+
`SELECT service_id, incident_identity, incident_type, partition_tag,
|
|
14799
|
+
obligation_sequence, event_id, event_digest, state AS finding_state,
|
|
14800
|
+
first_observed_at, last_observed_at
|
|
14801
|
+
FROM serialized_dcb_completeness_findings
|
|
14802
|
+
WHERE service_id = ?`
|
|
14803
|
+
).bind(serviceId).all();
|
|
14804
|
+
const items = [];
|
|
14805
|
+
for (const finding2 of rows.results) {
|
|
14806
|
+
const item = await this.item(finding2, await this.lifecycle(serviceId, finding2.incident_identity), at);
|
|
14807
|
+
if (filters.state !== void 0 && item.lifecycle.lifecycleState !== filters.state) continue;
|
|
14808
|
+
if (filters.owner !== void 0 && item.lifecycle.ownerId !== filters.owner) continue;
|
|
14809
|
+
if (filters.unowned === true && !item.unowned) continue;
|
|
14810
|
+
if (filters.overdue === true && !item.overdue) continue;
|
|
14811
|
+
if (filters.observedAfterClose === true && !item.observedAfterClose) continue;
|
|
14812
|
+
items.push(item);
|
|
14813
|
+
}
|
|
14814
|
+
items.sort(compareItems);
|
|
14815
|
+
const byState = {
|
|
14816
|
+
OPEN: 0,
|
|
14817
|
+
ACKNOWLEDGED: 0,
|
|
14818
|
+
CORRECTION_RECORDED: 0,
|
|
14819
|
+
CLOSED: 0,
|
|
14820
|
+
REOPENED: 0
|
|
14821
|
+
};
|
|
14822
|
+
for (const item of items) byState[item.lifecycle.lifecycleState] += 1;
|
|
14823
|
+
return {
|
|
14824
|
+
items,
|
|
14825
|
+
summary: {
|
|
14826
|
+
total: items.length,
|
|
14827
|
+
byState,
|
|
14828
|
+
unowned: items.filter((item) => item.unowned).length,
|
|
14829
|
+
overdue: items.filter((item) => item.overdue).length,
|
|
14830
|
+
observedAfterClose: items.filter((item) => item.observedAfterClose).length
|
|
14831
|
+
}
|
|
14832
|
+
};
|
|
14833
|
+
}
|
|
14834
|
+
async detail(serviceId, incidentIdentity, at = this.now()) {
|
|
14835
|
+
const finding2 = await this.finding(serviceId, incidentIdentity);
|
|
14836
|
+
const lifecycle = await this.lifecycle(serviceId, incidentIdentity);
|
|
14837
|
+
const item = await this.item(finding2, lifecycle, at);
|
|
14838
|
+
return {
|
|
14839
|
+
finding: item.finding,
|
|
14840
|
+
lifecycle: item.lifecycle,
|
|
14841
|
+
flags: { overdue: item.overdue, unowned: item.unowned, observedAfterClose: item.observedAfterClose },
|
|
14842
|
+
transitions: await this.transitions(serviceId, incidentIdentity)
|
|
14843
|
+
};
|
|
14844
|
+
}
|
|
14845
|
+
async transition(serviceId, value, actor) {
|
|
14846
|
+
const request = normalizeRequest(value);
|
|
14847
|
+
let actorId;
|
|
14848
|
+
try {
|
|
14849
|
+
actorId = boundedString(actor, "x-sdt-maintainer", MAX_IDENTITY_BYTES);
|
|
14850
|
+
} catch (caught) {
|
|
14851
|
+
if (caught instanceof IncidentLifecycleError && caught.code === "incident_invalid_request") {
|
|
14852
|
+
throw new IncidentLifecycleError("incident_invalid_actor", 400, caught.message);
|
|
14853
|
+
}
|
|
14854
|
+
throw caught;
|
|
14855
|
+
}
|
|
14856
|
+
if (actorId === null) throw new IncidentLifecycleError("incident_invalid_actor", 400, "x-sdt-maintainer must be non-empty");
|
|
14857
|
+
const finding2 = await this.finding(serviceId, request.incidentIdentity);
|
|
14858
|
+
const digest2 = await digestFor(request, actorId);
|
|
14859
|
+
const existing = await this.database.prepare(
|
|
14860
|
+
`SELECT * FROM serialized_dcb_incident_transitions
|
|
14861
|
+
WHERE service_id = ? AND incident_identity = ? AND transition_key = ?`
|
|
14862
|
+
).bind(serviceId, request.incidentIdentity, request.transitionKey).first();
|
|
14863
|
+
if (existing !== null && existing !== void 0) {
|
|
14864
|
+
if (existing.request_digest !== digest2) {
|
|
14865
|
+
throw new IncidentLifecycleError("incident_idempotency_conflict", 409, "The transition key was already used with different content");
|
|
14866
|
+
}
|
|
14867
|
+
return { projection: transitionFromRow(existing).after, transition: transitionFromRow(existing), idempotent: true };
|
|
14868
|
+
}
|
|
14869
|
+
const priorRow = await this.lifecycle(serviceId, request.incidentIdentity);
|
|
14870
|
+
const before = projectionFromRow(priorRow, finding2);
|
|
14871
|
+
if (before.version !== request.expectedVersion) {
|
|
14872
|
+
throw new IncidentLifecycleError("incident_version_conflict", 409, "The incident version is stale");
|
|
14873
|
+
}
|
|
14874
|
+
const at = this.now();
|
|
14875
|
+
const after = this.nextProjection(before, request, at);
|
|
14876
|
+
const newVersion = before.version + 1;
|
|
14877
|
+
const occurredAt = at;
|
|
14878
|
+
const seed = this.database.prepare(
|
|
14879
|
+
`INSERT INTO serialized_dcb_incident_lifecycles
|
|
14880
|
+
(service_id, incident_identity, lifecycle_state, version, last_transition_key, updated_at)
|
|
14881
|
+
VALUES (?, ?, 'OPEN', 0, '', ?)
|
|
14882
|
+
ON CONFLICT (service_id, incident_identity) DO NOTHING`
|
|
14883
|
+
).bind(serviceId, request.incidentIdentity, occurredAt);
|
|
14884
|
+
const update = this.database.prepare(
|
|
14885
|
+
`UPDATE serialized_dcb_incident_lifecycles
|
|
14886
|
+
SET lifecycle_state = ?, owner_id = ?, deadline_at = ?,
|
|
14887
|
+
correction_kind = ?, correction_reference = ?, correction_digest = ?,
|
|
14888
|
+
close_resolution = ?, close_reason = ?, version = ?,
|
|
14889
|
+
last_transition_key = ?, updated_at = ?
|
|
14890
|
+
WHERE service_id = ? AND incident_identity = ? AND version = ?
|
|
14891
|
+
AND last_transition_key <> ?`
|
|
14892
|
+
).bind(
|
|
14893
|
+
after.lifecycleState,
|
|
14894
|
+
after.ownerId,
|
|
14895
|
+
after.deadlineAt,
|
|
14896
|
+
after.correction?.kind ?? null,
|
|
14897
|
+
after.correction?.reference ?? null,
|
|
14898
|
+
after.correction?.digest ?? null,
|
|
14899
|
+
after.closeResolution,
|
|
14900
|
+
after.closeReason,
|
|
14901
|
+
newVersion,
|
|
14902
|
+
request.transitionKey,
|
|
14903
|
+
occurredAt,
|
|
14904
|
+
serviceId,
|
|
14905
|
+
request.incidentIdentity,
|
|
14906
|
+
before.version,
|
|
14907
|
+
request.transitionKey
|
|
14908
|
+
);
|
|
14909
|
+
const audit = this.database.prepare(
|
|
14910
|
+
`INSERT INTO serialized_dcb_incident_transitions
|
|
14911
|
+
(service_id, incident_identity, transition_key, request_digest, action,
|
|
14912
|
+
from_state, to_state, from_version, to_version, actor_id,
|
|
14913
|
+
before_owner_id, before_deadline_at, before_correction_kind,
|
|
14914
|
+
before_correction_reference, before_correction_digest, before_close_resolution,
|
|
14915
|
+
before_close_reason, after_owner_id, after_deadline_at, after_correction_kind,
|
|
14916
|
+
after_correction_reference, after_correction_digest, after_close_resolution,
|
|
14917
|
+
after_close_reason, reason, occurred_at)
|
|
14918
|
+
SELECT ?, ?, ?, ?, ?, ?, lifecycle_state, ?, version, ?,
|
|
14919
|
+
?, ?, ?, ?, ?, ?, ?, owner_id, deadline_at, correction_kind,
|
|
14920
|
+
correction_reference, correction_digest, close_resolution, close_reason, ?, ?
|
|
14921
|
+
FROM serialized_dcb_incident_lifecycles
|
|
14922
|
+
WHERE service_id = ? AND incident_identity = ? AND version = ? AND last_transition_key = ?`
|
|
14923
|
+
).bind(
|
|
14924
|
+
serviceId,
|
|
14925
|
+
request.incidentIdentity,
|
|
14926
|
+
request.transitionKey,
|
|
14927
|
+
digest2,
|
|
14928
|
+
request.action,
|
|
14929
|
+
before.lifecycleState,
|
|
14930
|
+
before.version,
|
|
14931
|
+
actorId,
|
|
14932
|
+
before.ownerId,
|
|
14933
|
+
before.deadlineAt,
|
|
14934
|
+
before.correction?.kind ?? null,
|
|
14935
|
+
before.correction?.reference ?? null,
|
|
14936
|
+
before.correction?.digest ?? null,
|
|
14937
|
+
before.closeResolution,
|
|
14938
|
+
before.closeReason,
|
|
14939
|
+
request.reason,
|
|
14940
|
+
occurredAt,
|
|
14941
|
+
serviceId,
|
|
14942
|
+
request.incidentIdentity,
|
|
14943
|
+
newVersion,
|
|
14944
|
+
request.transitionKey
|
|
14945
|
+
);
|
|
14946
|
+
await this.database.batch([seed, update, audit]);
|
|
14947
|
+
const inserted = await this.database.prepare(
|
|
14948
|
+
`SELECT * FROM serialized_dcb_incident_transitions
|
|
14949
|
+
WHERE service_id = ? AND incident_identity = ? AND transition_key = ?`
|
|
14950
|
+
).bind(serviceId, request.incidentIdentity, request.transitionKey).first();
|
|
14951
|
+
if (inserted === null || inserted === void 0) {
|
|
14952
|
+
const competing = await this.database.prepare(
|
|
14953
|
+
`SELECT * FROM serialized_dcb_incident_transitions
|
|
14954
|
+
WHERE service_id = ? AND incident_identity = ? AND transition_key = ?`
|
|
14955
|
+
).bind(serviceId, request.incidentIdentity, request.transitionKey).first();
|
|
14956
|
+
if (competing !== null && competing !== void 0 && competing.request_digest !== digest2) {
|
|
14957
|
+
throw new IncidentLifecycleError("incident_idempotency_conflict", 409, "The transition key was already used with different content");
|
|
14958
|
+
}
|
|
14959
|
+
if (competing !== null && competing !== void 0) {
|
|
14960
|
+
const transition2 = transitionFromRow(competing);
|
|
14961
|
+
return { projection: transition2.after, transition: transition2, idempotent: true };
|
|
14962
|
+
}
|
|
14963
|
+
throw new IncidentLifecycleError("incident_version_conflict", 409, "The incident version is stale");
|
|
14964
|
+
}
|
|
14965
|
+
const transition = transitionFromRow(inserted);
|
|
14966
|
+
return { projection: transition.after, transition, idempotent: false };
|
|
14967
|
+
}
|
|
14968
|
+
nextProjection(before, request, at) {
|
|
14969
|
+
const laterDeadline = (deadlineAt2) => {
|
|
14970
|
+
if (deadlineAt2 <= at) throw new IncidentLifecycleError("incident_invalid_deadline", 400, "deadlineAt must be in the future");
|
|
14971
|
+
return deadlineAt2;
|
|
14972
|
+
};
|
|
14973
|
+
let lifecycleState;
|
|
14974
|
+
let ownerId = before.ownerId;
|
|
14975
|
+
let deadlineAt = before.deadlineAt;
|
|
14976
|
+
let correction = before.correction;
|
|
14977
|
+
let closeResolution = before.closeResolution;
|
|
14978
|
+
let closeReason = before.closeReason;
|
|
14979
|
+
if (request.action === "ACKNOWLEDGE") {
|
|
14980
|
+
if (before.lifecycleState !== "OPEN" && before.lifecycleState !== "REOPENED") {
|
|
14981
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "The incident cannot be acknowledged from its current state");
|
|
14982
|
+
}
|
|
14983
|
+
lifecycleState = "ACKNOWLEDGED";
|
|
14984
|
+
ownerId = request.ownerId;
|
|
14985
|
+
deadlineAt = laterDeadline(request.deadlineAt);
|
|
14986
|
+
correction = null;
|
|
14987
|
+
closeResolution = null;
|
|
14988
|
+
closeReason = null;
|
|
14989
|
+
} else if (request.action === "UPDATE_ASSIGNMENT") {
|
|
14990
|
+
if (before.lifecycleState !== "ACKNOWLEDGED" && before.lifecycleState !== "CORRECTION_RECORDED") {
|
|
14991
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "The incident cannot be reassigned from its current state");
|
|
14992
|
+
}
|
|
14993
|
+
lifecycleState = before.lifecycleState;
|
|
14994
|
+
ownerId = request.ownerId;
|
|
14995
|
+
deadlineAt = laterDeadline(request.deadlineAt);
|
|
14996
|
+
} else if (request.action === "RECORD_CORRECTION") {
|
|
14997
|
+
if (before.lifecycleState !== "ACKNOWLEDGED") {
|
|
14998
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "The incident must be acknowledged before correction evidence");
|
|
14999
|
+
}
|
|
15000
|
+
lifecycleState = "CORRECTION_RECORDED";
|
|
15001
|
+
correction = request.correction;
|
|
15002
|
+
} else if (request.action === "CLOSE") {
|
|
15003
|
+
if (before.ownerId === null || before.deadlineAt === null) {
|
|
15004
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "The incident must have an owner and deadline before closure");
|
|
15005
|
+
}
|
|
15006
|
+
if (request.resolution.kind === "CORRECTED") {
|
|
15007
|
+
if (before.lifecycleState !== "CORRECTION_RECORDED") {
|
|
15008
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "A corrected closure requires recorded correction evidence");
|
|
15009
|
+
}
|
|
15010
|
+
lifecycleState = "CLOSED";
|
|
15011
|
+
closeResolution = "CORRECTED";
|
|
15012
|
+
closeReason = request.reason;
|
|
15013
|
+
} else {
|
|
15014
|
+
if (before.lifecycleState !== "ACKNOWLEDGED") {
|
|
15015
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "Accepted-as-is closure requires an acknowledged incident");
|
|
15016
|
+
}
|
|
15017
|
+
lifecycleState = "CLOSED";
|
|
15018
|
+
closeResolution = "ACCEPTED_AS_IS";
|
|
15019
|
+
closeReason = request.resolution.explanation;
|
|
15020
|
+
correction = null;
|
|
15021
|
+
}
|
|
15022
|
+
} else {
|
|
15023
|
+
if (before.lifecycleState !== "CLOSED") {
|
|
15024
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "Only a closed incident can be reopened");
|
|
15025
|
+
}
|
|
15026
|
+
lifecycleState = "REOPENED";
|
|
15027
|
+
ownerId = request.ownerId;
|
|
15028
|
+
deadlineAt = laterDeadline(request.deadlineAt);
|
|
15029
|
+
correction = null;
|
|
15030
|
+
closeResolution = null;
|
|
15031
|
+
closeReason = null;
|
|
15032
|
+
}
|
|
15033
|
+
return {
|
|
15034
|
+
...before,
|
|
15035
|
+
lifecycleState,
|
|
15036
|
+
ownerId,
|
|
15037
|
+
deadlineAt,
|
|
15038
|
+
correction,
|
|
15039
|
+
closeResolution,
|
|
15040
|
+
closeReason,
|
|
15041
|
+
version: before.version + 1,
|
|
15042
|
+
lastTransitionKey: request.transitionKey,
|
|
15043
|
+
updatedAt: at
|
|
15044
|
+
};
|
|
15045
|
+
}
|
|
15046
|
+
};
|
|
15047
|
+
|
|
15048
|
+
// src/http/IncidentMaintenance.ts
|
|
15049
|
+
function json10(body, status = 200) {
|
|
15050
|
+
return new Response(JSON.stringify(body), {
|
|
15051
|
+
status,
|
|
15052
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
15053
|
+
});
|
|
15054
|
+
}
|
|
15055
|
+
function error8(status, code, message) {
|
|
15056
|
+
return json10({ error: message, code }, status);
|
|
15057
|
+
}
|
|
15058
|
+
function configuredToken(value) {
|
|
15059
|
+
if (typeof value !== "string") return void 0;
|
|
15060
|
+
const token = value.trim();
|
|
15061
|
+
return token.length === 0 ? void 0 : token;
|
|
15062
|
+
}
|
|
15063
|
+
async function digest(value) {
|
|
15064
|
+
const bytes2 = new TextEncoder().encode(value);
|
|
15065
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", bytes2));
|
|
15066
|
+
}
|
|
15067
|
+
function fixedLengthEqual(left, right) {
|
|
15068
|
+
let difference = left.length ^ right.length;
|
|
15069
|
+
const length = Math.max(left.length, right.length);
|
|
15070
|
+
for (let index = 0; index < length; index += 1) {
|
|
15071
|
+
difference |= (left[index] ?? 0) ^ (right[index] ?? 0);
|
|
15072
|
+
}
|
|
15073
|
+
return difference === 0;
|
|
15074
|
+
}
|
|
15075
|
+
async function authenticated(request, token) {
|
|
15076
|
+
const credential = request.headers.get("authorization") ?? "";
|
|
15077
|
+
const suppliedToken = credential.startsWith("Bearer ") ? credential.slice("Bearer ".length) : "";
|
|
15078
|
+
const [expectedDigest, suppliedDigest] = await Promise.all([digest(token), digest(suppliedToken)]);
|
|
15079
|
+
return fixedLengthEqual(expectedDigest, suppliedDigest);
|
|
15080
|
+
}
|
|
15081
|
+
function boolFilter(value, name) {
|
|
15082
|
+
if (value === null) return void 0;
|
|
15083
|
+
if (value === "true") return true;
|
|
15084
|
+
if (value === "false") return false;
|
|
15085
|
+
throw new IncidentLifecycleError("incident_invalid_filter", 400, `${name} must be true or false`);
|
|
15086
|
+
}
|
|
15087
|
+
function parseFilters(url) {
|
|
15088
|
+
const allowed = /* @__PURE__ */ new Set(["state", "owner", "unowned", "overdue", "observedAfterClose"]);
|
|
15089
|
+
let unknown = false;
|
|
15090
|
+
url.searchParams.forEach((_value, key) => {
|
|
15091
|
+
if (!allowed.has(key)) unknown = true;
|
|
15092
|
+
});
|
|
15093
|
+
if (unknown) throw new IncidentLifecycleError("incident_invalid_filter", 400, "Unknown incident list filter");
|
|
15094
|
+
const state = url.searchParams.get("state");
|
|
15095
|
+
if (state !== null && !["OPEN", "ACKNOWLEDGED", "CORRECTION_RECORDED", "CLOSED", "REOPENED"].includes(state)) {
|
|
15096
|
+
throw new IncidentLifecycleError("incident_invalid_filter", 400, "state is invalid");
|
|
15097
|
+
}
|
|
15098
|
+
const owner = url.searchParams.get("owner");
|
|
15099
|
+
if (owner !== null) {
|
|
15100
|
+
const normalizedOwner = owner.trim();
|
|
15101
|
+
if (normalizedOwner.length === 0) {
|
|
15102
|
+
throw new IncidentLifecycleError("incident_invalid_filter", 400, "owner must be non-empty");
|
|
15103
|
+
}
|
|
15104
|
+
if (new TextEncoder().encode(normalizedOwner).byteLength > 256) {
|
|
15105
|
+
throw new IncidentLifecycleError("incident_invalid_filter", 400, "owner is too long");
|
|
15106
|
+
}
|
|
15107
|
+
}
|
|
15108
|
+
const unowned = boolFilter(url.searchParams.get("unowned"), "unowned");
|
|
15109
|
+
const overdue = boolFilter(url.searchParams.get("overdue"), "overdue");
|
|
15110
|
+
const observedAfterClose = boolFilter(url.searchParams.get("observedAfterClose"), "observedAfterClose");
|
|
15111
|
+
return {
|
|
15112
|
+
...state === null ? {} : { state },
|
|
15113
|
+
...owner === null ? {} : { owner: owner.trim() },
|
|
15114
|
+
...unowned === void 0 ? {} : { unowned },
|
|
15115
|
+
...overdue === void 0 ? {} : { overdue },
|
|
15116
|
+
...observedAfterClose === void 0 ? {} : { observedAfterClose }
|
|
15117
|
+
};
|
|
15118
|
+
}
|
|
15119
|
+
function incidentIdentityFromPath(pathname) {
|
|
15120
|
+
const encoded = pathname.slice("/maintenance/incidents/".length);
|
|
15121
|
+
if (encoded.length === 0 || encoded.includes("/")) {
|
|
15122
|
+
throw new IncidentLifecycleError("incident_route_not_found", 404, "Incident route not found");
|
|
15123
|
+
}
|
|
15124
|
+
try {
|
|
15125
|
+
return normalizeIncidentIdentity(decodeURIComponent(encoded));
|
|
15126
|
+
} catch {
|
|
15127
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "Incident identity must be URI encoded");
|
|
15128
|
+
}
|
|
15129
|
+
}
|
|
15130
|
+
function isIncidentMaintenancePath(pathname) {
|
|
15131
|
+
return pathname === "/maintenance/incidents" || pathname.startsWith("/maintenance/incidents/");
|
|
15132
|
+
}
|
|
15133
|
+
async function handleIncidentMaintenance(request, env, serviceIdentityProvider) {
|
|
15134
|
+
const token = configuredToken(env.INCIDENT_MAINTAINER_TOKEN);
|
|
15135
|
+
if (token === void 0) {
|
|
15136
|
+
return error8(503, "incident_maintenance_unavailable", "Incident maintenance is not configured");
|
|
15137
|
+
}
|
|
15138
|
+
if (!await authenticated(request, token)) {
|
|
15139
|
+
return error8(401, "incident_auth_required", "A valid incident maintenance bearer token is required");
|
|
15140
|
+
}
|
|
15141
|
+
let serviceId;
|
|
15142
|
+
try {
|
|
15143
|
+
serviceId = requestServiceIdentity(request, serviceIdentityProvider, {
|
|
15144
|
+
allowG11Verification: env.G11_VERIFICATION_ENABLED === "true"
|
|
15145
|
+
});
|
|
15146
|
+
} catch {
|
|
15147
|
+
return error8(503, "scope.identity_missing", "A deployment service identity is required for this scope");
|
|
15148
|
+
}
|
|
15149
|
+
if (env.D1 === void 0) {
|
|
15150
|
+
return error8(503, "incident_maintenance_unavailable", "Incident maintenance storage is unavailable");
|
|
15151
|
+
}
|
|
15152
|
+
const lifecycle = new IncidentLifecycle(env.D1);
|
|
15153
|
+
const url = new URL(request.url);
|
|
15154
|
+
try {
|
|
15155
|
+
if (request.method === "GET" && url.pathname === "/maintenance/incidents") {
|
|
15156
|
+
return json10(await lifecycle.list(serviceId, parseFilters(url)));
|
|
15157
|
+
}
|
|
15158
|
+
if (request.method === "GET" && url.pathname.startsWith("/maintenance/incidents/")) {
|
|
15159
|
+
return json10(await lifecycle.detail(serviceId, incidentIdentityFromPath(url.pathname)));
|
|
15160
|
+
}
|
|
15161
|
+
if (request.method === "POST" && url.pathname === "/maintenance/incidents/transitions") {
|
|
15162
|
+
let body;
|
|
15163
|
+
try {
|
|
15164
|
+
body = await request.json();
|
|
15165
|
+
} catch {
|
|
15166
|
+
return error8(400, "incident_invalid_request", "Transition request must be JSON");
|
|
15167
|
+
}
|
|
15168
|
+
const requestValue = IncidentLifecycle.parseTransition(body);
|
|
15169
|
+
const actor = request.headers.get("x-sdt-maintainer");
|
|
15170
|
+
if (actor === null) {
|
|
15171
|
+
return error8(400, "incident_invalid_actor", "x-sdt-maintainer is required");
|
|
15172
|
+
}
|
|
15173
|
+
return json10(await lifecycle.transition(serviceId, requestValue, actor));
|
|
15174
|
+
}
|
|
15175
|
+
return error8(404, "incident_route_not_found", "Incident maintenance route not found");
|
|
15176
|
+
} catch (caught) {
|
|
15177
|
+
if (caught instanceof IncidentLifecycleError) return error8(caught.status, caught.code, caught.message);
|
|
15178
|
+
return error8(500, "incident_maintenance_failed", "Incident maintenance could not complete the request");
|
|
15179
|
+
}
|
|
15180
|
+
}
|
|
15181
|
+
|
|
14277
15182
|
// src/downstream/Doorbell.ts
|
|
14278
15183
|
var MAX_SERVICE_BINDING_INVOCATIONS_PER_REQUEST = 32;
|
|
14279
|
-
function classifyDirectDoorbellFailure(
|
|
14280
|
-
const structured = typeof
|
|
15184
|
+
function classifyDirectDoorbellFailure(error11) {
|
|
15185
|
+
const structured = typeof error11 === "object" && error11 !== null ? error11 : {};
|
|
14281
15186
|
const status = typeof structured.status === "number" ? structured.status : typeof structured.statusCode === "number" ? structured.statusCode : void 0;
|
|
14282
15187
|
if (status !== void 0 && (status < 200 || status >= 300)) return "non-2xx";
|
|
14283
|
-
const value = String(
|
|
15188
|
+
const value = String(error11).toLowerCase();
|
|
14284
15189
|
const code = `${String(structured.code ?? "")} ${String(structured.name ?? "")}`.toLowerCase();
|
|
14285
15190
|
if (value.includes("timeout") || value.includes("deadline") || code.includes("timeout")) return "timeout";
|
|
14286
15191
|
if (value.includes("cancel") || value.includes("abort") || code.includes("abort")) return "cancel";
|
|
@@ -14523,6 +15428,7 @@ var TAG_SQL_SCHEMA_DDL = `
|
|
|
14523
15428
|
committed_event_count INTEGER NOT NULL,
|
|
14524
15429
|
head_suid TEXT NOT NULL,
|
|
14525
15430
|
reservation_confirmed INTEGER NOT NULL CHECK (reservation_confirmed IN (0, 1)),
|
|
15431
|
+
written_version INTEGER NOT NULL,
|
|
14526
15432
|
PRIMARY KEY (attempt_id, epoch)
|
|
14527
15433
|
);
|
|
14528
15434
|
|
|
@@ -14573,6 +15479,12 @@ var TAG_READ_AFTER_THROUGH_SQL = `
|
|
|
14573
15479
|
`;
|
|
14574
15480
|
function initializeTagSqlSchema(sql) {
|
|
14575
15481
|
sql.exec(TAG_SQL_SCHEMA_DDL);
|
|
15482
|
+
ensureTagCommitReceiptWrittenVersion(sql);
|
|
15483
|
+
}
|
|
15484
|
+
function ensureTagCommitReceiptWrittenVersion(sql) {
|
|
15485
|
+
const columns = sql.exec("SELECT name FROM pragma_table_info('tag_commit_receipt')").toArray();
|
|
15486
|
+
if (columns.some((column) => column.name === "written_version")) return;
|
|
15487
|
+
sql.exec("ALTER TABLE tag_commit_receipt ADD COLUMN written_version INTEGER");
|
|
14576
15488
|
}
|
|
14577
15489
|
function hasTagSqlStorage(storage) {
|
|
14578
15490
|
return typeof storage.sql?.exec === "function";
|
|
@@ -14699,14 +15611,14 @@ var TagIdentityConflict = class extends Error {
|
|
|
14699
15611
|
this.name = "TagIdentityConflict";
|
|
14700
15612
|
}
|
|
14701
15613
|
};
|
|
14702
|
-
function
|
|
15614
|
+
function json11(body, status = 200) {
|
|
14703
15615
|
return new Response(JSON.stringify(body), {
|
|
14704
15616
|
status,
|
|
14705
15617
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
14706
15618
|
});
|
|
14707
15619
|
}
|
|
14708
|
-
function
|
|
14709
|
-
return
|
|
15620
|
+
function error9(status, code, message, retryable = false) {
|
|
15621
|
+
return json11(retryable ? { error: message, code, retryable: true } : { error: message, code }, status);
|
|
14710
15622
|
}
|
|
14711
15623
|
function withGlobalAdmission2(response3, status) {
|
|
14712
15624
|
response3.headers.set(G65_GLOBAL_ADMISSION_HEADER, status);
|
|
@@ -14725,7 +15637,7 @@ function repairRejected(reason, status = 409) {
|
|
|
14725
15637
|
body: { code: "repair_operation_rejected", error: reason, reason }
|
|
14726
15638
|
};
|
|
14727
15639
|
}
|
|
14728
|
-
function
|
|
15640
|
+
function isObject12(value) {
|
|
14729
15641
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14730
15642
|
}
|
|
14731
15643
|
function isNonEmptyString11(value) {
|
|
@@ -14770,8 +15682,8 @@ function sqlJson(value, column) {
|
|
|
14770
15682
|
throw new Error(`Tag SQL column ${column} is not valid JSON`);
|
|
14771
15683
|
}
|
|
14772
15684
|
}
|
|
14773
|
-
function copyArrayBuffer(
|
|
14774
|
-
return
|
|
15685
|
+
function copyArrayBuffer(bytes2) {
|
|
15686
|
+
return bytes2.buffer.slice(bytes2.byteOffset, bytes2.byteOffset + bytes2.byteLength);
|
|
14775
15687
|
}
|
|
14776
15688
|
function arrayBufferBase642(value) {
|
|
14777
15689
|
let binary = "";
|
|
@@ -14782,7 +15694,7 @@ function nowIso() {
|
|
|
14782
15694
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
14783
15695
|
}
|
|
14784
15696
|
function epochFrom(value) {
|
|
14785
|
-
if (!
|
|
15697
|
+
if (!isObject12(value) || !isNonEmptyString11(value.attemptId) || !isEpoch(value.epoch)) {
|
|
14786
15698
|
return { error: "attemptId and a non-negative safe-integer epoch are required" };
|
|
14787
15699
|
}
|
|
14788
15700
|
return { value: { attemptId: value.attemptId, epoch: value.epoch } };
|
|
@@ -14802,7 +15714,7 @@ function consistencyTagsFrom(value) {
|
|
|
14802
15714
|
}
|
|
14803
15715
|
const entries = [];
|
|
14804
15716
|
for (const rawEntry of value) {
|
|
14805
|
-
if (!
|
|
15717
|
+
if (!isObject12(rawEntry) || !isNonEmptyString11(rawEntry.tag)) {
|
|
14806
15718
|
return { error: "each consistency tag needs a non-empty tag" };
|
|
14807
15719
|
}
|
|
14808
15720
|
if (!("lastSortableUniqueId" in rawEntry)) {
|
|
@@ -14830,7 +15742,7 @@ function consistencyTagsFrom(value) {
|
|
|
14830
15742
|
}
|
|
14831
15743
|
function acquireFrom(value, tag) {
|
|
14832
15744
|
const epoch2 = epochFrom(value);
|
|
14833
|
-
if (epoch2.value === void 0 || !
|
|
15745
|
+
if (epoch2.value === void 0 || !isObject12(value)) {
|
|
14834
15746
|
return { error: epoch2.error ?? "Invalid acquire request" };
|
|
14835
15747
|
}
|
|
14836
15748
|
const eventTags = stringArrayFrom(value.eventTags, "eventTags");
|
|
@@ -14853,7 +15765,7 @@ function acquireFrom(value, tag) {
|
|
|
14853
15765
|
}
|
|
14854
15766
|
function reservationFrom(value) {
|
|
14855
15767
|
const epoch2 = epochFrom(value);
|
|
14856
|
-
if (epoch2.value === void 0 || !
|
|
15768
|
+
if (epoch2.value === void 0 || !isObject12(value)) {
|
|
14857
15769
|
return { error: epoch2.error ?? "Invalid reservation request" };
|
|
14858
15770
|
}
|
|
14859
15771
|
if (value.reservationToken !== void 0 && !isNonEmptyString11(value.reservationToken)) {
|
|
@@ -14872,7 +15784,7 @@ function reservationFrom(value) {
|
|
|
14872
15784
|
}
|
|
14873
15785
|
function appendFrom(value, tag) {
|
|
14874
15786
|
const reservation = reservationFrom(value);
|
|
14875
|
-
if (reservation.value === void 0 || !
|
|
15787
|
+
if (reservation.value === void 0 || !isObject12(value) || !Array.isArray(value.candidates)) {
|
|
14876
15788
|
return { error: reservation.error ?? "candidates must be an array" };
|
|
14877
15789
|
}
|
|
14878
15790
|
if (value.candidates.length === 0) {
|
|
@@ -14880,7 +15792,7 @@ function appendFrom(value, tag) {
|
|
|
14880
15792
|
}
|
|
14881
15793
|
const candidates = [];
|
|
14882
15794
|
for (const rawCandidate of value.candidates) {
|
|
14883
|
-
if (!
|
|
15795
|
+
if (!isObject12(rawCandidate) || !isNonEmptyString11(rawCandidate.eventId) || !isNonEmptyString11(rawCandidate.suid) || typeof rawCandidate.payload !== "string") {
|
|
14884
15796
|
return { error: "each candidate needs eventId, suid, and payload" };
|
|
14885
15797
|
}
|
|
14886
15798
|
const eventTags = stringArrayFrom(rawCandidate.eventTags, "candidate eventTags");
|
|
@@ -14894,8 +15806,8 @@ function appendFrom(value, tag) {
|
|
|
14894
15806
|
assertSortableUniqueId(rawCandidate.suid);
|
|
14895
15807
|
if (!isUuidV7(rawCandidate.eventId)) return { error: "candidate eventId must be a UUID v7" };
|
|
14896
15808
|
JSON.parse(rawCandidate.payload);
|
|
14897
|
-
} catch (
|
|
14898
|
-
return { error:
|
|
15809
|
+
} catch (error11) {
|
|
15810
|
+
return { error: error11 instanceof Error ? error11.message : "candidate identity or payload is invalid" };
|
|
14899
15811
|
}
|
|
14900
15812
|
if (rawCandidate.provenance !== "g32") return { error: "candidate provenance must be g32" };
|
|
14901
15813
|
if (!isNonEmptyString11(rawCandidate.allocatorLineageId)) return { error: "candidate allocatorLineageId is required" };
|
|
@@ -14929,10 +15841,10 @@ function appendFrom(value, tag) {
|
|
|
14929
15841
|
return { value: { ...reservation.value, candidates: ordered, faultInjection } };
|
|
14930
15842
|
}
|
|
14931
15843
|
function bootstrapAppendFrom(value, tag) {
|
|
14932
|
-
if (!
|
|
15844
|
+
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" };
|
|
14933
15845
|
const candidates = [];
|
|
14934
15846
|
for (const candidate of value.candidates) {
|
|
14935
|
-
if (!
|
|
15847
|
+
if (!isObject12(candidate) || !isNonEmptyString11(candidate.eventId) || !isNonEmptyString11(candidate.suid) || typeof candidate.payload !== "string") return { error: "bootstrap candidate is invalid" };
|
|
14936
15848
|
const tags = stringArrayFrom(candidate.eventTags, "bootstrap candidate eventTags");
|
|
14937
15849
|
if (tags.value === void 0 || !tags.value.includes(tag) || !isNonEmptyString11(candidate.allocatorLineageId)) return { error: "bootstrap candidate must include this tag and lineage" };
|
|
14938
15850
|
let eventType;
|
|
@@ -14942,8 +15854,8 @@ function bootstrapAppendFrom(value, tag) {
|
|
|
14942
15854
|
assertSortableUniqueId(candidate.suid);
|
|
14943
15855
|
if (!isRfc4122Uuid(candidate.eventId)) return { error: "bootstrap candidate eventId must be an RFC 4122 UUID" };
|
|
14944
15856
|
JSON.parse(candidate.payload);
|
|
14945
|
-
} catch (
|
|
14946
|
-
return { error:
|
|
15857
|
+
} catch (error11) {
|
|
15858
|
+
return { error: error11 instanceof Error ? error11.message : "bootstrap candidate identity or payload is invalid" };
|
|
14947
15859
|
}
|
|
14948
15860
|
if (candidate.provenance !== "g32") return { error: "bootstrap candidate provenance must be g32" };
|
|
14949
15861
|
if (!isNonEmptyString11(candidate.timestamp) || !CANONICAL_UTC_TIMESTAMP_PATTERN.test(candidate.timestamp)) return { error: "bootstrap candidate timestamp must be canonical UTC" };
|
|
@@ -14955,13 +15867,13 @@ function bootstrapAppendFrom(value, tag) {
|
|
|
14955
15867
|
}
|
|
14956
15868
|
function fenceFrom(value) {
|
|
14957
15869
|
const epoch2 = epochFrom(value);
|
|
14958
|
-
if (epoch2.value === void 0 || !
|
|
15870
|
+
if (epoch2.value === void 0 || !isObject12(value) || !isNonEmptyString11(value.reason)) {
|
|
14959
15871
|
return { error: epoch2.error ?? "reason is required" };
|
|
14960
15872
|
}
|
|
14961
15873
|
return { value: { ...epoch2.value, reason: value.reason } };
|
|
14962
15874
|
}
|
|
14963
15875
|
function repairScopeItemFrom(value, tag) {
|
|
14964
|
-
if (!
|
|
15876
|
+
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)) {
|
|
14965
15877
|
return { error: "each repair scope item needs attemptId, eventId, suid, and payload" };
|
|
14966
15878
|
}
|
|
14967
15879
|
const eventTags = stringArrayFrom(value.eventTags, "repair eventTags");
|
|
@@ -15009,13 +15921,13 @@ function repairScopeFrom(value, tag) {
|
|
|
15009
15921
|
return { value: sortRepairScope(scope) };
|
|
15010
15922
|
}
|
|
15011
15923
|
function repairLeaseFrom(value) {
|
|
15012
|
-
if (!
|
|
15924
|
+
if (!isObject12(value) || !isNonEmptyString11(value.owner) || !isEpoch(value.epoch)) {
|
|
15013
15925
|
return { error: "repair owner and non-negative safe-integer epoch are required" };
|
|
15014
15926
|
}
|
|
15015
15927
|
return { value: { owner: value.owner, epoch: value.epoch } };
|
|
15016
15928
|
}
|
|
15017
15929
|
function repairAcquireFrom(value, tag) {
|
|
15018
|
-
if (!
|
|
15930
|
+
if (!isObject12(value) || !isNonEmptyString11(value.owner)) {
|
|
15019
15931
|
return { error: "repair owner is required" };
|
|
15020
15932
|
}
|
|
15021
15933
|
const leaseMs = value.leaseMs ?? DEFAULT_REPAIR_LEASE_MS;
|
|
@@ -15027,7 +15939,7 @@ function repairAcquireFrom(value, tag) {
|
|
|
15027
15939
|
}
|
|
15028
15940
|
function repairScopeUnionFrom(value, tag) {
|
|
15029
15941
|
const lease = repairLeaseFrom(value);
|
|
15030
|
-
if (lease.value === void 0 || !
|
|
15942
|
+
if (lease.value === void 0 || !isObject12(value)) {
|
|
15031
15943
|
return { error: lease.error };
|
|
15032
15944
|
}
|
|
15033
15945
|
const scope = repairScopeFrom(value.scope, tag);
|
|
@@ -15035,7 +15947,7 @@ function repairScopeUnionFrom(value, tag) {
|
|
|
15035
15947
|
}
|
|
15036
15948
|
function repairApplyFrom(value, tag) {
|
|
15037
15949
|
const lease = repairLeaseFrom(value);
|
|
15038
|
-
if (lease.value === void 0 || !
|
|
15950
|
+
if (lease.value === void 0 || !isObject12(value)) {
|
|
15039
15951
|
return { error: lease.error };
|
|
15040
15952
|
}
|
|
15041
15953
|
const item = repairScopeItemFrom(value.item, tag);
|
|
@@ -15043,20 +15955,20 @@ function repairApplyFrom(value, tag) {
|
|
|
15043
15955
|
}
|
|
15044
15956
|
function repairAuditFrom(value, tag) {
|
|
15045
15957
|
const apply = repairApplyFrom(value, tag);
|
|
15046
|
-
if (apply.value === void 0 || !
|
|
15958
|
+
if (apply.value === void 0 || !isObject12(value) || !isNonEmptyString11(value.actor)) {
|
|
15047
15959
|
return { error: apply.error ?? "audit actor is required" };
|
|
15048
15960
|
}
|
|
15049
15961
|
return { value: { ...apply.value, actor: value.actor } };
|
|
15050
15962
|
}
|
|
15051
15963
|
function repairClearFrom(value) {
|
|
15052
15964
|
const lease = repairLeaseFrom(value);
|
|
15053
|
-
if (lease.value === void 0 || !
|
|
15965
|
+
if (lease.value === void 0 || !isObject12(value) || !isNonEmptyString11(value.attemptId) || !isNonNegativeInteger4(value.scopeVersion)) {
|
|
15054
15966
|
return { error: lease.error ?? "attemptId and scopeVersion are required" };
|
|
15055
15967
|
}
|
|
15056
15968
|
return { value: { ...lease.value, attemptId: value.attemptId, scopeVersion: value.scopeVersion } };
|
|
15057
15969
|
}
|
|
15058
15970
|
function outboxPendingFrom(value) {
|
|
15059
|
-
if (!
|
|
15971
|
+
if (!isObject12(value)) {
|
|
15060
15972
|
return { error: "outbox pending request must be an object" };
|
|
15061
15973
|
}
|
|
15062
15974
|
const nowMs = value.nowMs ?? Date.now();
|
|
@@ -15074,22 +15986,22 @@ function outboxPendingFrom(value) {
|
|
|
15074
15986
|
return { value: { nowMs, limit, force } };
|
|
15075
15987
|
}
|
|
15076
15988
|
function outboxMarkFrom(value) {
|
|
15077
|
-
if (!
|
|
15989
|
+
if (!isObject12(value) || !Array.isArray(value.deliveries) || value.deliveries.length === 0) {
|
|
15078
15990
|
return { error: "deliveries must be a non-empty array" };
|
|
15079
15991
|
}
|
|
15080
15992
|
const deliveries = [];
|
|
15081
15993
|
for (const raw of value.deliveries) {
|
|
15082
|
-
if (!
|
|
15994
|
+
if (!isObject12(raw) || !isNonEmptyString11(raw.attemptId) || !isNonEmptyString11(raw.eventId) || !isNonEmptyString11(raw.suid) || typeof raw.payload !== "string" || !isSafeInteger(raw.enqueuedAt)) {
|
|
15083
15995
|
return { error: "each delivery needs attemptId, eventId, suid, payload, and enqueuedAt" };
|
|
15084
15996
|
}
|
|
15085
15997
|
if (!isNonEmptyString11(raw.eventType) || raw.provenance !== "g32" || !isNonEmptyString11(raw.timestamp) || !isNonEmptyString11(raw.allocatorLineageId)) return { error: "delivery must carry the complete G32 identity" };
|
|
15086
|
-
if (!
|
|
15998
|
+
if (!isObject12(raw.completeness)) {
|
|
15087
15999
|
return { error: "delivery must carry a readable global receipt/membership join proof" };
|
|
15088
16000
|
}
|
|
15089
16001
|
const completeness = raw.completeness;
|
|
15090
16002
|
const obligationSequence = completeness.obligationSequence;
|
|
15091
16003
|
const memberships = completeness.localCommittedMembership;
|
|
15092
|
-
if (!isNonEmptyString11(completeness.eventDigest) || !isSafeInteger(obligationSequence) || obligationSequence < 1 || !Array.isArray(memberships) || memberships.length !== 1 || !
|
|
16004
|
+
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") {
|
|
15093
16005
|
return { error: "delivery must carry a readable global receipt/membership join proof" };
|
|
15094
16006
|
}
|
|
15095
16007
|
const membership = memberships[0];
|
|
@@ -15128,7 +16040,7 @@ function outboxMarkFrom(value) {
|
|
|
15128
16040
|
return { value: { deliveries, nowMs } };
|
|
15129
16041
|
}
|
|
15130
16042
|
function g44SourceScanFrom(value) {
|
|
15131
|
-
if (!
|
|
16043
|
+
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) {
|
|
15132
16044
|
return { error: "source scan needs serviceId, tag, and a bounded compound cursor" };
|
|
15133
16045
|
}
|
|
15134
16046
|
return {
|
|
@@ -15142,7 +16054,7 @@ function g44SourceScanFrom(value) {
|
|
|
15142
16054
|
};
|
|
15143
16055
|
}
|
|
15144
16056
|
function g46TagStateSourceFrom(value) {
|
|
15145
|
-
if (!
|
|
16057
|
+
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") {
|
|
15146
16058
|
return { error: "G46 source read needs tag, cursor, optional through, and a bounded limit" };
|
|
15147
16059
|
}
|
|
15148
16060
|
try {
|
|
@@ -15316,8 +16228,8 @@ function logicalNow(record) {
|
|
|
15316
16228
|
return record.clockNowMs ?? Date.now() + record.clockOffsetMs;
|
|
15317
16229
|
}
|
|
15318
16230
|
function expireReservation(record) {
|
|
15319
|
-
const
|
|
15320
|
-
if (
|
|
16231
|
+
const active2 = record.activeReservation;
|
|
16232
|
+
if (active2 === null || active2.expiresAt > logicalNow(record)) {
|
|
15321
16233
|
return { record, expired: false };
|
|
15322
16234
|
}
|
|
15323
16235
|
return {
|
|
@@ -15435,7 +16347,7 @@ var TagDurableObject = class {
|
|
|
15435
16347
|
const url = new URL(request.url);
|
|
15436
16348
|
const tag = url.searchParams.get("__tag");
|
|
15437
16349
|
if (!isNonEmptyString11(tag)) {
|
|
15438
|
-
return
|
|
16350
|
+
return error9(400, "tag_identity_required", "Tag identity is required");
|
|
15439
16351
|
}
|
|
15440
16352
|
const serviceId = url.searchParams.get("__serviceId");
|
|
15441
16353
|
if (request.method === "GET" && url.pathname === "/head-facts") {
|
|
@@ -15443,10 +16355,10 @@ var TagDurableObject = class {
|
|
|
15443
16355
|
observation.markFirstStorageRead();
|
|
15444
16356
|
try {
|
|
15445
16357
|
const facts = this.readHeadFacts(tag);
|
|
15446
|
-
return facts === void 0 ?
|
|
16358
|
+
return facts === void 0 ? error9(404, "tag_not_found", "Tag has no durable state yet") : json11(facts);
|
|
15447
16359
|
} catch (caught) {
|
|
15448
16360
|
if (caught instanceof TagIdentityConflict) {
|
|
15449
|
-
return
|
|
16361
|
+
return error9(409, "tag_identity_conflict", "Tag Durable Object identity changed");
|
|
15450
16362
|
}
|
|
15451
16363
|
throw caught;
|
|
15452
16364
|
}
|
|
@@ -15457,9 +16369,9 @@ var TagDurableObject = class {
|
|
|
15457
16369
|
observation.markFirstStorageRead();
|
|
15458
16370
|
const record = this.readStoredRecord(tag);
|
|
15459
16371
|
if (record === void 0) {
|
|
15460
|
-
return
|
|
16372
|
+
return error9(404, "tag_not_found", "Tag has no durable state yet");
|
|
15461
16373
|
}
|
|
15462
|
-
return record.tag === tag ?
|
|
16374
|
+
return record.tag === tag ? json11(requireG32TagRecord(record)) : error9(409, "tag_identity_conflict", "Tag Durable Object identity changed");
|
|
15463
16375
|
});
|
|
15464
16376
|
}
|
|
15465
16377
|
if (request.method === "GET" && url.pathname === "/repair/facts") {
|
|
@@ -15467,7 +16379,7 @@ var TagDurableObject = class {
|
|
|
15467
16379
|
}
|
|
15468
16380
|
if (request.method === "POST" && url.pathname === "/debug/alarm") {
|
|
15469
16381
|
const record = await this.runAlarm();
|
|
15470
|
-
return record === void 0 ?
|
|
16382
|
+
return record === void 0 ? error9(404, "tag_not_found", "Tag has no durable state yet") : json11(record);
|
|
15471
16383
|
}
|
|
15472
16384
|
if (request.method === "POST" && url.pathname === "/acquire") {
|
|
15473
16385
|
return this.traceCommitActor(request, tag, serviceId, activation2, observation, (body2) => this.acquire(tag, body2, observation));
|
|
@@ -15490,37 +16402,37 @@ var TagDurableObject = class {
|
|
|
15490
16402
|
}
|
|
15491
16403
|
const body = await this.jsonBody(request);
|
|
15492
16404
|
if (body === void 0) {
|
|
15493
|
-
return
|
|
16405
|
+
return error9(400, "malformed_tag_request", "Request body must be JSON");
|
|
15494
16406
|
}
|
|
15495
16407
|
if (request.method === "POST" && url.pathname === "/__internal/g46/tag-state-incremental") {
|
|
15496
16408
|
if (request.headers.get("x-sdt-g46-source-read") !== "1") {
|
|
15497
|
-
return
|
|
16409
|
+
return error9(403, "g46_source_read_forbidden", "G46 source reads require the TagState adapter");
|
|
15498
16410
|
}
|
|
15499
16411
|
const parsed = g46TagStateSourceFrom(body);
|
|
15500
16412
|
if (parsed.value === void 0 || parsed.value.tag !== tag) {
|
|
15501
|
-
return
|
|
16413
|
+
return error9(400, "g46_source_read_invalid", parsed.error ?? "G46 source read is invalid");
|
|
15502
16414
|
}
|
|
15503
16415
|
try {
|
|
15504
|
-
return
|
|
16416
|
+
return json11(await this.g43TagStateIncrementalCatchUp(parsed.value));
|
|
15505
16417
|
} catch (caught) {
|
|
15506
16418
|
if (caught instanceof TagIdentityConflict) {
|
|
15507
|
-
return
|
|
16419
|
+
return error9(409, "tag_identity_conflict", "Tag Durable Object identity changed");
|
|
15508
16420
|
}
|
|
15509
|
-
return
|
|
16421
|
+
return error9(503, "g46_source_read_unavailable", caught instanceof Error ? caught.message : "G46 source read is unavailable");
|
|
15510
16422
|
}
|
|
15511
16423
|
}
|
|
15512
16424
|
if (request.method === "POST" && url.pathname === "/__internal/g44/source-obligations") {
|
|
15513
16425
|
if (request.headers.get("x-sdt-g44-source-scan") !== "1") {
|
|
15514
|
-
return
|
|
16426
|
+
return error9(403, "g44_source_scan_forbidden", "G44 source scan requires the internal scanner seam");
|
|
15515
16427
|
}
|
|
15516
16428
|
const parsed = g44SourceScanFrom(body);
|
|
15517
16429
|
if (parsed.value === void 0 || parsed.value.tag !== tag || parsed.value.serviceId !== serviceId) {
|
|
15518
|
-
return
|
|
16430
|
+
return error9(400, "g44_source_scan_invalid", parsed.error ?? "G44 source scan identity is invalid");
|
|
15519
16431
|
}
|
|
15520
16432
|
try {
|
|
15521
|
-
return
|
|
16433
|
+
return json11(await this.g44ReadSourceObligations(parsed.value));
|
|
15522
16434
|
} catch (failure3) {
|
|
15523
|
-
return
|
|
16435
|
+
return error9(503, "g44_source_scan_unavailable", failure3 instanceof Error ? failure3.message : "G44 source scan is unavailable");
|
|
15524
16436
|
}
|
|
15525
16437
|
}
|
|
15526
16438
|
if (request.method === "POST" && url.pathname === "/bootstrap/admit") {
|
|
@@ -15567,7 +16479,7 @@ var TagDurableObject = class {
|
|
|
15567
16479
|
}
|
|
15568
16480
|
if (request.method === "POST" && url.pathname === "/__internal/g77/inspect-target") {
|
|
15569
16481
|
if (!isNonEmptyString11(serviceId)) {
|
|
15570
|
-
return
|
|
16482
|
+
return error9(400, "service_identity_required", "Service identity is required");
|
|
15571
16483
|
}
|
|
15572
16484
|
return this.inspectG77Target(serviceId, tag, body);
|
|
15573
16485
|
}
|
|
@@ -15575,28 +16487,28 @@ var TagDurableObject = class {
|
|
|
15575
16487
|
return this.fenceAbsentG77Target(tag, body);
|
|
15576
16488
|
}
|
|
15577
16489
|
if (request.method === "POST" && url.pathname === "/__internal/g77/capabilities") {
|
|
15578
|
-
return
|
|
16490
|
+
return json11({ inspectTarget: true, fenceAbsentTarget: true });
|
|
15579
16491
|
}
|
|
15580
|
-
return
|
|
16492
|
+
return error9(404, "tag_route_not_found", "Tag route was not found");
|
|
15581
16493
|
}
|
|
15582
16494
|
/** Identity-bound installed/absent inspection for G77 issuance reconciliation. */
|
|
15583
16495
|
inspectG77Target(serviceId, tag, body) {
|
|
15584
|
-
if (!
|
|
15585
|
-
return
|
|
16496
|
+
if (!isObject12(body) || !isNonEmptyString11(body.eventId) || !isNonEmptyString11(body.suid)) {
|
|
16497
|
+
return error9(400, "invalid_inspect_target", "eventId and suid are required");
|
|
15586
16498
|
}
|
|
15587
16499
|
const pinnedWriterEpoch = isNonNegativeInteger4(body.pinnedWriterEpoch) ? body.pinnedWriterEpoch : 0;
|
|
15588
16500
|
const attemptId = isNonEmptyString11(body.attemptId) ? body.attemptId : void 0;
|
|
15589
16501
|
const sql = this.sqlStorage();
|
|
15590
|
-
if (sql === void 0) return
|
|
16502
|
+
if (sql === void 0) return error9(503, "tag_sql_unavailable", "Tag SQL storage is unavailable");
|
|
15591
16503
|
const identity2 = sql.exec("SELECT tag FROM tag_identity WHERE singleton = 1").toArray()[0];
|
|
15592
16504
|
if (identity2 === void 0) {
|
|
15593
16505
|
if (attemptId !== void 0) {
|
|
15594
|
-
return
|
|
16506
|
+
return json11({ inspectionStatus: "absent-never-contacted" });
|
|
15595
16507
|
}
|
|
15596
|
-
return
|
|
16508
|
+
return error9(409, "target_not_terminal", "Target is not terminal under pinned writer authority");
|
|
15597
16509
|
}
|
|
15598
16510
|
if (sqlString(identity2.tag, "tag_identity.tag") !== tag) {
|
|
15599
|
-
return
|
|
16511
|
+
return error9(409, "tag_identity_mismatch", "Tag identity does not match the inspected tag");
|
|
15600
16512
|
}
|
|
15601
16513
|
const event = sql.exec(`
|
|
15602
16514
|
SELECT attempt_id, event_id, suid FROM tag_event
|
|
@@ -15609,41 +16521,41 @@ var TagDurableObject = class {
|
|
|
15609
16521
|
`, serviceId, body.eventId).toArray()[0];
|
|
15610
16522
|
if (obligation !== void 0) {
|
|
15611
16523
|
const obligationDigest = sqlString(obligation.event_digest, "tag_outbox_obligation.event_digest");
|
|
15612
|
-
return
|
|
16524
|
+
return json11({
|
|
15613
16525
|
terminalStatus: "installed-and-covered",
|
|
15614
16526
|
eventId: body.eventId,
|
|
15615
16527
|
obligationDigest
|
|
15616
16528
|
});
|
|
15617
16529
|
}
|
|
15618
|
-
return
|
|
16530
|
+
return error9(409, "target_not_terminal", "Target append is in flight under pinned writer authority");
|
|
15619
16531
|
}
|
|
15620
16532
|
if (attemptId !== void 0) {
|
|
15621
16533
|
const tombstone = sql.exec("SELECT epoch FROM tag_tombstone WHERE attempt_id = ?", attemptId).toArray()[0];
|
|
15622
16534
|
const tombstoneEpoch = tombstone === void 0 ? void 0 : sqlNumber(tombstone.epoch, "tag_tombstone.epoch");
|
|
15623
16535
|
if (tombstoneEpoch !== void 0 && pinnedWriterEpoch <= tombstoneEpoch) {
|
|
15624
|
-
return
|
|
16536
|
+
return json11({ terminalStatus: "absent-and-irrevocably-fenced", tombstoneEpoch });
|
|
15625
16537
|
}
|
|
15626
16538
|
const reservation = sql.exec("SELECT attempt_id, expires_at FROM tag_reservation WHERE singleton = 1").toArray()[0];
|
|
15627
16539
|
if (reservation !== void 0 && sqlString(reservation.attempt_id, "tag_reservation.attempt_id") === attemptId) {
|
|
15628
|
-
return
|
|
16540
|
+
return error9(409, "target_not_terminal", "Active reservation holds writer authority");
|
|
15629
16541
|
}
|
|
15630
16542
|
const epoch2 = sql.exec("SELECT attempt_id FROM tag_epoch WHERE attempt_id = ?", attemptId).toArray()[0];
|
|
15631
16543
|
if (epoch2 !== void 0) {
|
|
15632
|
-
return
|
|
16544
|
+
return json11({ inspectionStatus: "absent-but-unfenced" });
|
|
15633
16545
|
}
|
|
15634
|
-
return
|
|
16546
|
+
return json11({ inspectionStatus: "absent-never-contacted" });
|
|
15635
16547
|
}
|
|
15636
|
-
return
|
|
16548
|
+
return error9(409, "target_not_terminal", "Target is not terminal under pinned writer authority");
|
|
15637
16549
|
}
|
|
15638
16550
|
/** Reconciler-only fence for never-contacted targets; creates durable tombstone state. */
|
|
15639
16551
|
async fenceAbsentG77Target(tag, body) {
|
|
15640
16552
|
const parsed = reservationFrom(body);
|
|
15641
16553
|
if (parsed.value === void 0) {
|
|
15642
|
-
return
|
|
16554
|
+
return error9(400, "invalid_fence_target", parsed.error ?? "attemptId and epoch are required");
|
|
15643
16555
|
}
|
|
15644
16556
|
this.ensureSqlTag(tag);
|
|
15645
16557
|
const result2 = await this.cancelSql(tag, { ...parsed.value, forceTombstone: true });
|
|
15646
|
-
return result2.status >= 200 && result2.status < 300 ?
|
|
16558
|
+
return result2.status >= 200 && result2.status < 300 ? json11(result2.body) : error9(result2.status, "fence_target_rejected", "Absent target fence was rejected");
|
|
15647
16559
|
}
|
|
15648
16560
|
async alarm() {
|
|
15649
16561
|
this.activation.beginHandler();
|
|
@@ -15990,21 +16902,21 @@ var TagDurableObject = class {
|
|
|
15990
16902
|
const result2 = await authority;
|
|
15991
16903
|
g44GlobalArrayAuthorityResultByD1.set(database, result2);
|
|
15992
16904
|
return result2;
|
|
15993
|
-
} catch (
|
|
16905
|
+
} catch (error11) {
|
|
15994
16906
|
if (g44GlobalArrayAuthorityByD1.get(database) === authority) {
|
|
15995
16907
|
g44GlobalArrayAuthorityByD1.delete(database);
|
|
15996
16908
|
}
|
|
15997
|
-
throw
|
|
16909
|
+
throw error11;
|
|
15998
16910
|
}
|
|
15999
16911
|
}
|
|
16000
16912
|
async readG44GlobalArrayAuthority(database) {
|
|
16001
16913
|
try {
|
|
16002
16914
|
await database.prepare('SELECT "EventDigest" FROM dcb_events WHERE 1 = 0').all();
|
|
16003
16915
|
return true;
|
|
16004
|
-
} catch (
|
|
16005
|
-
const message =
|
|
16916
|
+
} catch (error11) {
|
|
16917
|
+
const message = error11 instanceof Error ? error11.message : String(error11);
|
|
16006
16918
|
if (/no such (?:table|column):\s*(?:dcb_events|EventDigest)/i.test(message)) return false;
|
|
16007
|
-
throw
|
|
16919
|
+
throw error11;
|
|
16008
16920
|
}
|
|
16009
16921
|
}
|
|
16010
16922
|
async traceCommitActor(request, tag, serviceId, activation2, observation, callback) {
|
|
@@ -16014,10 +16926,10 @@ var TagDurableObject = class {
|
|
|
16014
16926
|
{ actorClass: "TAG", actorKey: `tag:${serviceId}:${tag}`, activation: activation2, observation },
|
|
16015
16927
|
async () => {
|
|
16016
16928
|
body = await this.jsonBody(request);
|
|
16017
|
-
const attemptId =
|
|
16929
|
+
const attemptId = isObject12(body) && isNonEmptyString11(body.attemptId) ? body.attemptId : void 0;
|
|
16018
16930
|
return attemptId === void 0 || !isNonEmptyString11(serviceId) ? void 0 : { attemptId, serviceId };
|
|
16019
16931
|
},
|
|
16020
|
-
async () => body === void 0 ?
|
|
16932
|
+
async () => body === void 0 ? error9(400, "malformed_tag_request", "Request body must be JSON") : callback(body)
|
|
16021
16933
|
);
|
|
16022
16934
|
}
|
|
16023
16935
|
/**
|
|
@@ -16231,6 +17143,7 @@ var TagDurableObject = class {
|
|
|
16231
17143
|
ensureSqlTag(tag) {
|
|
16232
17144
|
const sql = this.sqlStorage();
|
|
16233
17145
|
if (sql === void 0) throw new Error("Tag SQL storage is unavailable");
|
|
17146
|
+
ensureTagCommitReceiptWrittenVersion(sql);
|
|
16234
17147
|
const existing = sql.exec("SELECT tag FROM tag_identity WHERE singleton = 1").toArray()[0];
|
|
16235
17148
|
if (existing !== void 0) {
|
|
16236
17149
|
if (sqlString(existing.tag, "tag_identity.tag") !== tag) throw new Error("Tag Durable Object identity changed");
|
|
@@ -16343,17 +17256,18 @@ var TagDurableObject = class {
|
|
|
16343
17256
|
sql.exec("UPDATE tag_control SET head_suid = ?, version = ?, updated_at = ? WHERE singleton = 1", head, version, committedAt);
|
|
16344
17257
|
sql.exec("UPDATE tag_head SET service_id = ?, head_suid = ? WHERE singleton = 1", serviceId, head);
|
|
16345
17258
|
}
|
|
16346
|
-
writeCommittedSqlReceipt(sql, input, committedAt, eventCount, head, confirmsReservation) {
|
|
17259
|
+
writeCommittedSqlReceipt(sql, input, committedAt, eventCount, head, confirmsReservation, writtenVersion) {
|
|
16347
17260
|
sql.exec(`
|
|
16348
17261
|
INSERT INTO tag_commit_receipt
|
|
16349
|
-
(attempt_id, epoch, committed_at, committed_event_count, head_suid, reservation_confirmed)
|
|
16350
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
17262
|
+
(attempt_id, epoch, committed_at, committed_event_count, head_suid, reservation_confirmed, written_version)
|
|
17263
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
16351
17264
|
ON CONFLICT(attempt_id, epoch) DO UPDATE SET
|
|
16352
17265
|
committed_at = excluded.committed_at,
|
|
16353
17266
|
committed_event_count = excluded.committed_event_count,
|
|
16354
17267
|
head_suid = excluded.head_suid,
|
|
16355
|
-
reservation_confirmed = excluded.reservation_confirmed
|
|
16356
|
-
|
|
17268
|
+
reservation_confirmed = excluded.reservation_confirmed,
|
|
17269
|
+
written_version = excluded.written_version
|
|
17270
|
+
`, input.attemptId, input.epoch, committedAt, eventCount, head, confirmsReservation ? 1 : 0, writtenVersion);
|
|
16357
17271
|
}
|
|
16358
17272
|
/**
|
|
16359
17273
|
* The normal append path deliberately probes only the indexed rows it needs
|
|
@@ -16395,11 +17309,29 @@ var TagDurableObject = class {
|
|
|
16395
17309
|
return { event, obligation, exact };
|
|
16396
17310
|
});
|
|
16397
17311
|
if (existingCandidates.every(({ exact }) => exact)) {
|
|
16398
|
-
|
|
17312
|
+
const receipts = sql.exec(`
|
|
17313
|
+
SELECT epoch, committed_at, written_version, head_suid FROM tag_commit_receipt
|
|
17314
|
+
WHERE attempt_id = ?
|
|
17315
|
+
`, input.attemptId).toArray();
|
|
17316
|
+
const sameEpoch = receipts.find((row) => sqlNumber(row.epoch, "tag_commit_receipt.epoch") === input.epoch);
|
|
17317
|
+
const replayHead = input.candidates.at(-1)?.suid;
|
|
17318
|
+
const byHead = replayHead === void 0 ? [] : receipts.filter((row) => sqlString(row.head_suid, "tag_commit_receipt.head_suid") === replayHead);
|
|
17319
|
+
const receipt = sameEpoch ?? (byHead.length === 1 ? byHead[0] : void 0) ?? (receipts.length === 1 ? receipts[0] : void 0);
|
|
17320
|
+
if (receipt === void 0) {
|
|
17321
|
+
await this.rearmScheduler(txn);
|
|
17322
|
+
return { ...rejected("duplicate_receipt_missing"), body: { ...rejected("duplicate_receipt_missing").body, version } };
|
|
17323
|
+
}
|
|
17324
|
+
const storedAt = sqlNullableString(receipt.committed_at, "tag_commit_receipt.committed_at");
|
|
17325
|
+
const storedVersion = sqlNullableNumber(receipt.written_version, "tag_commit_receipt.written_version");
|
|
17326
|
+
if (storedAt === null || storedVersion === null) {
|
|
17327
|
+
await this.rearmScheduler(txn);
|
|
17328
|
+
return { ...rejected("duplicate_receipt_unknown"), body: { ...rejected("duplicate_receipt_unknown").body, version } };
|
|
17329
|
+
}
|
|
17330
|
+
return { status: 200, body: { status: "duplicate", version: storedVersion, updatedAt: storedAt } };
|
|
16399
17331
|
}
|
|
16400
|
-
const
|
|
17332
|
+
const active2 = sql.exec("SELECT * FROM tag_reservation WHERE singleton = 1").toArray()[0];
|
|
16401
17333
|
const now2 = sqlNullableNumber(control.clock_now_ms, "tag_control.clock_now_ms") ?? Date.now() + sqlNumber(control.clock_offset_ms, "tag_control.clock_offset_ms");
|
|
16402
|
-
let reservation =
|
|
17334
|
+
let reservation = active2;
|
|
16403
17335
|
if (reservation !== void 0 && sqlNumber(reservation.expires_at, "tag_reservation.expires_at") <= now2) {
|
|
16404
17336
|
sql.exec("DELETE FROM tag_reservation WHERE singleton = 1");
|
|
16405
17337
|
version += 1;
|
|
@@ -16492,7 +17424,7 @@ var TagDurableObject = class {
|
|
|
16492
17424
|
if (input.faultInjection === "after-append-before-confirm") {
|
|
16493
17425
|
throw new AppendTransactionFault("Simulated interruption before atomic commit receipt");
|
|
16494
17426
|
}
|
|
16495
|
-
this.writeCommittedSqlReceipt(sql, input, committedAt, events.length, head, confirmsReservation);
|
|
17427
|
+
this.writeCommittedSqlReceipt(sql, input, committedAt, events.length, head, confirmsReservation, version + 1);
|
|
16496
17428
|
if (confirmsReservation) sql.exec("DELETE FROM tag_reservation WHERE singleton = 1");
|
|
16497
17429
|
await this.rearmScheduler(txn);
|
|
16498
17430
|
return {
|
|
@@ -16500,7 +17432,8 @@ var TagDurableObject = class {
|
|
|
16500
17432
|
body: {
|
|
16501
17433
|
status: "appended",
|
|
16502
17434
|
fenceGate: { checked: true, activeFenceCount: 0 },
|
|
16503
|
-
version: version + 1
|
|
17435
|
+
version: version + 1,
|
|
17436
|
+
updatedAt: committedAt
|
|
16504
17437
|
},
|
|
16505
17438
|
hopFacts
|
|
16506
17439
|
};
|
|
@@ -16901,18 +17834,20 @@ var TagDurableObject = class {
|
|
|
16901
17834
|
);
|
|
16902
17835
|
this.upsertSourcePartitionRegistration(sql, eventServiceId, record.tag, outbox.eventId);
|
|
16903
17836
|
}
|
|
17837
|
+
ensureTagCommitReceiptWrittenVersion(sql);
|
|
16904
17838
|
for (const confirmation of record.confirmations) {
|
|
16905
17839
|
sql.exec(
|
|
16906
17840
|
`
|
|
16907
17841
|
INSERT OR IGNORE INTO tag_commit_receipt
|
|
16908
|
-
(attempt_id, epoch, committed_at, committed_event_count, head_suid, reservation_confirmed)
|
|
16909
|
-
VALUES (?, ?, ?, ?, ?, 1)
|
|
17842
|
+
(attempt_id, epoch, committed_at, committed_event_count, head_suid, reservation_confirmed, written_version)
|
|
17843
|
+
VALUES (?, ?, ?, ?, ?, 1, ?)
|
|
16910
17844
|
`,
|
|
16911
17845
|
confirmation.attemptId,
|
|
16912
17846
|
confirmation.epoch,
|
|
16913
17847
|
record.updatedAt,
|
|
16914
17848
|
record.events.filter((event) => event.attemptId === confirmation.attemptId).length,
|
|
16915
|
-
record.head
|
|
17849
|
+
record.head,
|
|
17850
|
+
record.version
|
|
16916
17851
|
);
|
|
16917
17852
|
}
|
|
16918
17853
|
}
|
|
@@ -16961,13 +17896,13 @@ var TagDurableObject = class {
|
|
|
16961
17896
|
async acquire(tag, body, observation) {
|
|
16962
17897
|
const parsed = acquireFrom(body, tag);
|
|
16963
17898
|
if (parsed.value === void 0) {
|
|
16964
|
-
return
|
|
17899
|
+
return error9(400, "invalid_tag_acquire", parsed.error ?? "Invalid acquire request");
|
|
16965
17900
|
}
|
|
16966
17901
|
const input = parsed.value;
|
|
16967
17902
|
observation?.markFirstStorageRead();
|
|
16968
17903
|
if (this.sqlStorage() !== void 0) {
|
|
16969
17904
|
const result3 = await this.acquireSql(tag, input);
|
|
16970
|
-
return
|
|
17905
|
+
return json11(result3.body, result3.status);
|
|
16971
17906
|
}
|
|
16972
17907
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
16973
17908
|
const loaded = await this.recordFor(txn, tag);
|
|
@@ -17002,9 +17937,9 @@ var TagDurableObject = class {
|
|
|
17002
17937
|
body: { ...rejected("consistency_head_mismatch").body, version: record.version }
|
|
17003
17938
|
};
|
|
17004
17939
|
}
|
|
17005
|
-
const
|
|
17006
|
-
if (
|
|
17007
|
-
if (
|
|
17940
|
+
const active2 = record.activeReservation;
|
|
17941
|
+
if (active2 !== null) {
|
|
17942
|
+
if (active2.attemptId === input.attemptId && active2.epoch === input.epoch) {
|
|
17008
17943
|
return {
|
|
17009
17944
|
status: 200,
|
|
17010
17945
|
body: {
|
|
@@ -17049,18 +17984,18 @@ var TagDurableObject = class {
|
|
|
17049
17984
|
body: { status: "reserved", reservation, fenceGate, version: updated.version }
|
|
17050
17985
|
};
|
|
17051
17986
|
});
|
|
17052
|
-
return
|
|
17987
|
+
return json11(result2.body, result2.status);
|
|
17053
17988
|
}
|
|
17054
17989
|
async cancel(tag, body, observation) {
|
|
17055
17990
|
const parsed = reservationFrom(body);
|
|
17056
17991
|
if (parsed.value === void 0) {
|
|
17057
|
-
return
|
|
17992
|
+
return error9(400, "invalid_tag_cancel", parsed.error ?? "Invalid cancel request");
|
|
17058
17993
|
}
|
|
17059
17994
|
const input = parsed.value;
|
|
17060
17995
|
observation?.markFirstStorageRead();
|
|
17061
17996
|
if (this.sqlStorage() !== void 0) {
|
|
17062
17997
|
const result3 = await this.cancelSql(tag, input);
|
|
17063
|
-
return
|
|
17998
|
+
return json11(result3.body, result3.status);
|
|
17064
17999
|
}
|
|
17065
18000
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
17066
18001
|
const loaded = await this.recordFor(txn, tag);
|
|
@@ -17095,8 +18030,8 @@ var TagDurableObject = class {
|
|
|
17095
18030
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
17096
18031
|
return { ...rejected(epochError), body: { ...rejected(epochError).body, version: record.version } };
|
|
17097
18032
|
}
|
|
17098
|
-
const
|
|
17099
|
-
if (
|
|
18033
|
+
const active2 = record.activeReservation;
|
|
18034
|
+
if (active2 === null || active2.attemptId !== input.attemptId || active2.epoch !== input.epoch || active2.token !== input.reservationToken) {
|
|
17100
18035
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
17101
18036
|
return {
|
|
17102
18037
|
...rejected("reservation_token_required"),
|
|
@@ -17111,12 +18046,12 @@ var TagDurableObject = class {
|
|
|
17111
18046
|
});
|
|
17112
18047
|
return { status: 200, body: { status: "cancelled", idempotent: false, version: updated.version } };
|
|
17113
18048
|
});
|
|
17114
|
-
return
|
|
18049
|
+
return json11(result2.body, result2.status);
|
|
17115
18050
|
}
|
|
17116
18051
|
async seal(tag, body, observation) {
|
|
17117
18052
|
const parsed = epochFrom(body);
|
|
17118
18053
|
if (parsed.value === void 0) {
|
|
17119
|
-
return
|
|
18054
|
+
return error9(400, "invalid_tag_seal", parsed.error ?? "Invalid seal request");
|
|
17120
18055
|
}
|
|
17121
18056
|
const input = parsed.value;
|
|
17122
18057
|
observation?.markFirstStorageRead();
|
|
@@ -17151,7 +18086,7 @@ var TagDurableObject = class {
|
|
|
17151
18086
|
});
|
|
17152
18087
|
return { status: 200, body: { status: "sealed", highestEpoch: input.epoch, version: updated.version } };
|
|
17153
18088
|
});
|
|
17154
|
-
return
|
|
18089
|
+
return json11(result2.body, result2.status);
|
|
17155
18090
|
}
|
|
17156
18091
|
/**
|
|
17157
18092
|
* Coordinator-only import admission. This is deliberately not a flag on
|
|
@@ -17160,8 +18095,8 @@ var TagDurableObject = class {
|
|
|
17160
18095
|
*/
|
|
17161
18096
|
async bootstrapAppend(tag, body, serviceId) {
|
|
17162
18097
|
const parsed = bootstrapAppendFrom(body, tag);
|
|
17163
|
-
if (parsed.value === void 0) return
|
|
17164
|
-
if (serviceId !== parsed.value.targetServiceId) return
|
|
18098
|
+
if (parsed.value === void 0) return error9(400, "invalid_bootstrap_admission", parsed.error ?? "Invalid bootstrap admission");
|
|
18099
|
+
if (serviceId !== parsed.value.targetServiceId) return error9(409, "bootstrap_service_scope_mismatch", "Bootstrap target service does not match tag scope");
|
|
17165
18100
|
const input = parsed.value;
|
|
17166
18101
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
17167
18102
|
const loaded = await this.recordFor(txn, tag);
|
|
@@ -17182,10 +18117,10 @@ var TagDurableObject = class {
|
|
|
17182
18117
|
);
|
|
17183
18118
|
return { status: 201, body: { status: "bootstrap_admitted", version: updated.version } };
|
|
17184
18119
|
});
|
|
17185
|
-
return
|
|
18120
|
+
return json11(result2.body, result2.status);
|
|
17186
18121
|
}
|
|
17187
18122
|
async closeBootstrap(tag, body) {
|
|
17188
|
-
if (!
|
|
18123
|
+
if (!isObject12(body) || !isNonEmptyString11(body.importId) || !isEpoch(body.leaseEpoch)) return error9(400, "invalid_bootstrap_close", "bootstrap importId and leaseEpoch are required");
|
|
17189
18124
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
17190
18125
|
const loaded = await this.recordFor(txn, tag);
|
|
17191
18126
|
const record = loaded.record;
|
|
@@ -17195,7 +18130,7 @@ var TagDurableObject = class {
|
|
|
17195
18130
|
const updated = await this.commit(txn, record, { bootstrapAdmission: { ...admission, closed: true } });
|
|
17196
18131
|
return { status: 200, body: { status: "closed", version: updated.version } };
|
|
17197
18132
|
});
|
|
17198
|
-
return
|
|
18133
|
+
return json11(result2.body, result2.status);
|
|
17199
18134
|
}
|
|
17200
18135
|
directDoorbellPreflight(domainDeliveryClass) {
|
|
17201
18136
|
const domainClass = domainDeliveryClass === void 0 ? void 0 : readDomainDeliveryClass({ DOMAIN_DELIVERY_CLASS: domainDeliveryClass });
|
|
@@ -17214,7 +18149,7 @@ var TagDurableObject = class {
|
|
|
17214
18149
|
async append(tag, body, serviceId, domainDeliveryClass, observation) {
|
|
17215
18150
|
const parsed = appendFrom(body, tag);
|
|
17216
18151
|
if (parsed.value === void 0) {
|
|
17217
|
-
return
|
|
18152
|
+
return error9(400, "invalid_tag_append", parsed.error ?? "Invalid append request");
|
|
17218
18153
|
}
|
|
17219
18154
|
const input = parsed.value;
|
|
17220
18155
|
try {
|
|
@@ -17252,7 +18187,7 @@ var TagDurableObject = class {
|
|
|
17252
18187
|
if ((result3.status === 201 || result3.status === 200) && serviceId !== null && serviceId.length > 0 && sourcePartitionRegistration !== "unconfigured") {
|
|
17253
18188
|
this.scheduleSourcePartitionWatermark(tag, serviceId);
|
|
17254
18189
|
}
|
|
17255
|
-
const response4 =
|
|
18190
|
+
const response4 = json11(result3.body, result3.status);
|
|
17256
18191
|
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)) {
|
|
17257
18192
|
const directRows = doorbellPreflight.status === "ready" && this.env.DOWNSTREAM_DOORBELL !== void 0 ? await this.directDeliveryBeforeResponse(tag, serviceId) : void 0;
|
|
17258
18193
|
if (sourcePartitionRegistration !== "unconfigured" || sourcePartitionWasRegistered) {
|
|
@@ -17269,7 +18204,19 @@ var TagDurableObject = class {
|
|
|
17269
18204
|
const loaded = await this.recordFor(txn, tag);
|
|
17270
18205
|
let record = loaded.record;
|
|
17271
18206
|
if (batchIsExactDuplicate(record, input)) {
|
|
17272
|
-
|
|
18207
|
+
const receipts = record.writeReceipts ?? {};
|
|
18208
|
+
const sameEpoch = receipts[`${input.attemptId}:${input.epoch}`];
|
|
18209
|
+
const forAttempt = Object.entries(receipts).filter(([key]) => key.startsWith(`${input.attemptId}:`));
|
|
18210
|
+
const replayHead = input.candidates.at(-1)?.suid;
|
|
18211
|
+
const byHead = replayHead === void 0 ? [] : forAttempt.filter(([, value]) => value.headSuid === replayHead);
|
|
18212
|
+
const receipt = sameEpoch ?? (byHead.length === 1 ? byHead[0]?.[1] : void 0) ?? (forAttempt.length === 1 ? forAttempt[0]?.[1] : void 0);
|
|
18213
|
+
if (receipt === void 0) {
|
|
18214
|
+
return {
|
|
18215
|
+
...rejected("duplicate_receipt_unknown"),
|
|
18216
|
+
body: { ...rejected("duplicate_receipt_unknown").body, version: record.version }
|
|
18217
|
+
};
|
|
18218
|
+
}
|
|
18219
|
+
return { status: 200, body: { status: "duplicate", version: receipt.version, updatedAt: receipt.updatedAt } };
|
|
17273
18220
|
}
|
|
17274
18221
|
const expiry = expireReservation(record);
|
|
17275
18222
|
record = expiry.record;
|
|
@@ -17282,9 +18229,9 @@ var TagDurableObject = class {
|
|
|
17282
18229
|
await this.commitExpiryIfNeeded(txn, expiry);
|
|
17283
18230
|
return fenceGateRejected();
|
|
17284
18231
|
}
|
|
17285
|
-
const
|
|
17286
|
-
const confirmsReservation =
|
|
17287
|
-
if (
|
|
18232
|
+
const active2 = record.activeReservation;
|
|
18233
|
+
const confirmsReservation = active2 !== null && active2.attemptId === input.attemptId && active2.epoch === input.epoch && active2.token === input.reservationToken;
|
|
18234
|
+
if (active2 !== null && (active2.attemptId !== input.attemptId || active2.epoch !== input.epoch || active2.token !== input.reservationToken)) {
|
|
17288
18235
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
17289
18236
|
return {
|
|
17290
18237
|
...rejected("reservation_token_required"),
|
|
@@ -17346,13 +18293,20 @@ var TagDurableObject = class {
|
|
|
17346
18293
|
alarmDueAt: null,
|
|
17347
18294
|
confirmations: confirmsReservation ? withMaxEpoch(record.confirmations, input.attemptId, input.epoch) : record.confirmations
|
|
17348
18295
|
});
|
|
17349
|
-
|
|
18296
|
+
const recorded = {
|
|
18297
|
+
...updated,
|
|
18298
|
+
writeReceipts: {
|
|
18299
|
+
...record.writeReceipts ?? {},
|
|
18300
|
+
[`${input.attemptId}:${input.epoch}`]: { version: updated.version, updatedAt: updated.updatedAt, headSuid: updated.head }
|
|
18301
|
+
}
|
|
18302
|
+
};
|
|
18303
|
+
await this.write(txn, recorded, serviceId ?? "");
|
|
17350
18304
|
return {
|
|
17351
18305
|
status: 201,
|
|
17352
|
-
body: { status: "appended", fenceGate, version:
|
|
18306
|
+
body: { status: "appended", fenceGate, version: recorded.version, updatedAt: recorded.updatedAt }
|
|
17353
18307
|
};
|
|
17354
18308
|
});
|
|
17355
|
-
const response3 =
|
|
18309
|
+
const response3 = json11(result2.body, result2.status);
|
|
17356
18310
|
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)) {
|
|
17357
18311
|
const directRows = doorbellPreflight.status === "ready" && this.env.DOWNSTREAM_DOORBELL !== void 0 ? await this.directDeliveryBeforeResponse(tag, serviceId) : void 0;
|
|
17358
18312
|
withGlobalAdmission2(
|
|
@@ -17364,16 +18318,16 @@ var TagDurableObject = class {
|
|
|
17364
18318
|
return response3;
|
|
17365
18319
|
} catch (failure3) {
|
|
17366
18320
|
if (failure3 instanceof PartitionRegistrationUnavailableError) {
|
|
17367
|
-
return
|
|
18321
|
+
return error9(503, "partition_registration_unavailable", "Source partition registration is unavailable; retry the commit.", true);
|
|
17368
18322
|
}
|
|
17369
18323
|
if (failure3 instanceof AppendTransactionFault) {
|
|
17370
|
-
return
|
|
18324
|
+
return error9(
|
|
17371
18325
|
503,
|
|
17372
18326
|
"simulated_append_crash",
|
|
17373
18327
|
"Simulated interruption before atomic append and confirmation commit"
|
|
17374
18328
|
);
|
|
17375
18329
|
}
|
|
17376
|
-
return
|
|
18330
|
+
return error9(500, "tag_append_failure", "Tag append could not be persisted");
|
|
17377
18331
|
}
|
|
17378
18332
|
}
|
|
17379
18333
|
/**
|
|
@@ -17489,7 +18443,7 @@ var TagDurableObject = class {
|
|
|
17489
18443
|
let timer;
|
|
17490
18444
|
const operationResult = Promise.resolve().then(operation2).then(
|
|
17491
18445
|
(value) => ({ status: "completed", value }),
|
|
17492
|
-
(
|
|
18446
|
+
(error11) => ({ status: "failed", error: error11 })
|
|
17493
18447
|
);
|
|
17494
18448
|
const timeout = new Promise((resolve) => {
|
|
17495
18449
|
timer = setTimeout(() => resolve({ status: "timeout" }), budgetMs);
|
|
@@ -17540,14 +18494,14 @@ var TagDurableObject = class {
|
|
|
17540
18494
|
envelopeBytes,
|
|
17541
18495
|
status: "success"
|
|
17542
18496
|
});
|
|
17543
|
-
} catch (
|
|
18497
|
+
} catch (error11) {
|
|
17544
18498
|
console.warn("direct_doorbell_status", {
|
|
17545
18499
|
status: "queued-degraded",
|
|
17546
18500
|
reason: "direct_doorbell_delivery_failed",
|
|
17547
18501
|
correlationId,
|
|
17548
18502
|
envelopeBytes,
|
|
17549
|
-
failureKind: classifyDirectDoorbellFailure(
|
|
17550
|
-
error: String(
|
|
18503
|
+
failureKind: classifyDirectDoorbellFailure(error11),
|
|
18504
|
+
error: String(error11)
|
|
17551
18505
|
});
|
|
17552
18506
|
}
|
|
17553
18507
|
}
|
|
@@ -17608,7 +18562,7 @@ var TagDurableObject = class {
|
|
|
17608
18562
|
async confirm(tag, body) {
|
|
17609
18563
|
const parsed = reservationFrom(body);
|
|
17610
18564
|
if (parsed.value === void 0) {
|
|
17611
|
-
return
|
|
18565
|
+
return error9(400, "invalid_tag_confirm", parsed.error ?? "Invalid confirm request");
|
|
17612
18566
|
}
|
|
17613
18567
|
const input = parsed.value;
|
|
17614
18568
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17624,8 +18578,8 @@ var TagDurableObject = class {
|
|
|
17624
18578
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
17625
18579
|
return { ...rejected(epochError), body: { ...rejected(epochError).body, version: record.version } };
|
|
17626
18580
|
}
|
|
17627
|
-
const
|
|
17628
|
-
if (
|
|
18581
|
+
const active2 = record.activeReservation;
|
|
18582
|
+
if (active2 !== null && active2.attemptId === input.attemptId && active2.epoch === input.epoch && active2.token === input.reservationToken) {
|
|
17629
18583
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
17630
18584
|
return {
|
|
17631
18585
|
...rejected("append_required_for_confirmation"),
|
|
@@ -17638,12 +18592,12 @@ var TagDurableObject = class {
|
|
|
17638
18592
|
body: { ...rejected("confirmation_not_found").body, version: record.version }
|
|
17639
18593
|
};
|
|
17640
18594
|
});
|
|
17641
|
-
return
|
|
18595
|
+
return json11(result2.body, result2.status);
|
|
17642
18596
|
}
|
|
17643
18597
|
async installFence(tag, body) {
|
|
17644
18598
|
const parsed = fenceFrom(body);
|
|
17645
18599
|
if (parsed.value === void 0) {
|
|
17646
|
-
return
|
|
18600
|
+
return error9(400, "invalid_tag_fence", parsed.error ?? "Invalid fence request");
|
|
17647
18601
|
}
|
|
17648
18602
|
const input = parsed.value;
|
|
17649
18603
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17680,12 +18634,12 @@ var TagDurableObject = class {
|
|
|
17680
18634
|
});
|
|
17681
18635
|
return { status: 201, body: { status: "fence-installed", idempotent: false, version: updated.version } };
|
|
17682
18636
|
});
|
|
17683
|
-
return
|
|
18637
|
+
return json11(result2.body, result2.status);
|
|
17684
18638
|
}
|
|
17685
18639
|
async clearFence(tag, body) {
|
|
17686
18640
|
const parsed = fenceFrom(body);
|
|
17687
18641
|
if (parsed.value === void 0) {
|
|
17688
|
-
return
|
|
18642
|
+
return error9(400, "invalid_tag_fence", parsed.error ?? "Invalid fence request");
|
|
17689
18643
|
}
|
|
17690
18644
|
const input = parsed.value;
|
|
17691
18645
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17711,7 +18665,7 @@ var TagDurableObject = class {
|
|
|
17711
18665
|
});
|
|
17712
18666
|
return { status: 200, body: { status: "fence-cleared", idempotent: false, version: updated.version } };
|
|
17713
18667
|
});
|
|
17714
|
-
return
|
|
18668
|
+
return json11(result2.body, result2.status);
|
|
17715
18669
|
}
|
|
17716
18670
|
/**
|
|
17717
18671
|
* Returns Tag-side repair facts only. Journal observations are deliberately
|
|
@@ -17747,7 +18701,7 @@ var TagDurableObject = class {
|
|
|
17747
18701
|
}
|
|
17748
18702
|
};
|
|
17749
18703
|
});
|
|
17750
|
-
return
|
|
18704
|
+
return json11(result2.body, result2.status);
|
|
17751
18705
|
}
|
|
17752
18706
|
/**
|
|
17753
18707
|
* Internal outbox handoff: enqueuedAt is persisted before any Queue send so
|
|
@@ -17757,10 +18711,10 @@ var TagDurableObject = class {
|
|
|
17757
18711
|
async pendingOutbox(tag, serviceId, body) {
|
|
17758
18712
|
const parsed = outboxPendingFrom(body);
|
|
17759
18713
|
if (parsed.value === void 0) {
|
|
17760
|
-
return
|
|
18714
|
+
return error9(400, "invalid_outbox_pending", parsed.error ?? "Invalid outbox pending request");
|
|
17761
18715
|
}
|
|
17762
18716
|
if (!isNonEmptyString11(serviceId)) {
|
|
17763
|
-
return
|
|
18717
|
+
return error9(400, "outbox_service_identity_required", "Outbox delivery requires a service identity");
|
|
17764
18718
|
}
|
|
17765
18719
|
const input = parsed.value;
|
|
17766
18720
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17836,7 +18790,7 @@ var TagDurableObject = class {
|
|
|
17836
18790
|
}
|
|
17837
18791
|
return { status: 200, body: { rows } };
|
|
17838
18792
|
});
|
|
17839
|
-
return
|
|
18793
|
+
return json11(result2.body, result2.status);
|
|
17840
18794
|
}
|
|
17841
18795
|
/**
|
|
17842
18796
|
* Source-side obligation scanner. It deliberately does not call the Queue,
|
|
@@ -17906,7 +18860,7 @@ var TagDurableObject = class {
|
|
|
17906
18860
|
}
|
|
17907
18861
|
};
|
|
17908
18862
|
});
|
|
17909
|
-
return
|
|
18863
|
+
return json11(result2.body, result2.status);
|
|
17910
18864
|
}
|
|
17911
18865
|
/** Record a retry or terminal poison disposition without changing the source event. */
|
|
17912
18866
|
async recordOutboxFailure(tag, delivery, failure3) {
|
|
@@ -17960,16 +18914,16 @@ var TagDurableObject = class {
|
|
|
17960
18914
|
async markOutboxDelivered(tag, body) {
|
|
17961
18915
|
const parsed = outboxMarkFrom(body);
|
|
17962
18916
|
if (parsed.value === void 0) {
|
|
17963
|
-
return
|
|
18917
|
+
return error9(400, "invalid_outbox_delivery_mark", parsed.error ?? "Invalid outbox delivery mark");
|
|
17964
18918
|
}
|
|
17965
18919
|
const input = parsed.value;
|
|
17966
18920
|
try {
|
|
17967
18921
|
const joined = await Promise.all(input.deliveries.map((delivery) => this.globalReceiptMatches(delivery)));
|
|
17968
18922
|
if (joined.some((value) => !value)) {
|
|
17969
|
-
return
|
|
18923
|
+
return error9(409, "outbox_global_receipt_unverified", "Global receipt and membership must be read back before source acknowledgement");
|
|
17970
18924
|
}
|
|
17971
18925
|
} catch {
|
|
17972
|
-
return
|
|
18926
|
+
return error9(503, "outbox_global_receipt_unavailable", "Global receipt verification is unavailable");
|
|
17973
18927
|
}
|
|
17974
18928
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
17975
18929
|
if (this.sqlStorage() !== void 0) {
|
|
@@ -18008,7 +18962,7 @@ var TagDurableObject = class {
|
|
|
18008
18962
|
}
|
|
18009
18963
|
return { status: 200, body: { marked, idempotent: marked === 0 } };
|
|
18010
18964
|
});
|
|
18011
|
-
return
|
|
18965
|
+
return json11(result2.body, result2.status);
|
|
18012
18966
|
}
|
|
18013
18967
|
/**
|
|
18014
18968
|
* The delivery handoff reads its universe from `tag_outbox_obligation`.
|
|
@@ -18147,7 +19101,7 @@ var TagDurableObject = class {
|
|
|
18147
19101
|
async acquireRepairLease(tag, body) {
|
|
18148
19102
|
const parsed = repairAcquireFrom(body, tag);
|
|
18149
19103
|
if (parsed.value === void 0) {
|
|
18150
|
-
return
|
|
19104
|
+
return error9(400, "invalid_repair_acquire", parsed.error ?? "Invalid repair acquire request");
|
|
18151
19105
|
}
|
|
18152
19106
|
const input = parsed.value;
|
|
18153
19107
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18193,12 +19147,12 @@ var TagDurableObject = class {
|
|
|
18193
19147
|
}
|
|
18194
19148
|
};
|
|
18195
19149
|
});
|
|
18196
|
-
return
|
|
19150
|
+
return json11(result2.body, result2.status);
|
|
18197
19151
|
}
|
|
18198
19152
|
async renewRepairLease(tag, body) {
|
|
18199
19153
|
const parsed = repairLeaseFrom(body);
|
|
18200
19154
|
if (parsed.value === void 0) {
|
|
18201
|
-
return
|
|
19155
|
+
return error9(400, "invalid_repair_renew", parsed.error ?? "Invalid repair renew request");
|
|
18202
19156
|
}
|
|
18203
19157
|
const input = parsed.value;
|
|
18204
19158
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18222,12 +19176,12 @@ var TagDurableObject = class {
|
|
|
18222
19176
|
}
|
|
18223
19177
|
};
|
|
18224
19178
|
});
|
|
18225
|
-
return
|
|
19179
|
+
return json11(result2.body, result2.status);
|
|
18226
19180
|
}
|
|
18227
19181
|
async unionRepairScope(tag, body) {
|
|
18228
19182
|
const parsed = repairScopeUnionFrom(body, tag);
|
|
18229
19183
|
if (parsed.value === void 0) {
|
|
18230
|
-
return
|
|
19184
|
+
return error9(400, "invalid_repair_scope_union", parsed.error ?? "Invalid repair scope union request");
|
|
18231
19185
|
}
|
|
18232
19186
|
const input = parsed.value;
|
|
18233
19187
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18263,12 +19217,12 @@ var TagDurableObject = class {
|
|
|
18263
19217
|
body: { status: "repair-scope-unioned", scopeVersion: updated.repairScopeVersion, scope: updated.repairScope }
|
|
18264
19218
|
};
|
|
18265
19219
|
});
|
|
18266
|
-
return
|
|
19220
|
+
return json11(result2.body, result2.status);
|
|
18267
19221
|
}
|
|
18268
19222
|
async applyRepair(tag, body) {
|
|
18269
19223
|
const parsed = repairApplyFrom(body, tag);
|
|
18270
19224
|
if (parsed.value === void 0) {
|
|
18271
|
-
return
|
|
19225
|
+
return error9(400, "invalid_repair_apply", parsed.error ?? "Invalid repair apply request");
|
|
18272
19226
|
}
|
|
18273
19227
|
const input = parsed.value;
|
|
18274
19228
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18349,12 +19303,12 @@ var TagDurableObject = class {
|
|
|
18349
19303
|
body: { status: branch, idempotent: false, head: (updatedRecord ?? record).head, version: (updatedRecord ?? record).version }
|
|
18350
19304
|
};
|
|
18351
19305
|
});
|
|
18352
|
-
return
|
|
19306
|
+
return json11(result2.body, result2.status);
|
|
18353
19307
|
}
|
|
18354
19308
|
async auditRepair(tag, body) {
|
|
18355
19309
|
const parsed = repairAuditFrom(body, tag);
|
|
18356
19310
|
if (parsed.value === void 0) {
|
|
18357
|
-
return
|
|
19311
|
+
return error9(400, "invalid_repair_audit", parsed.error ?? "Invalid repair audit request");
|
|
18358
19312
|
}
|
|
18359
19313
|
const input = parsed.value;
|
|
18360
19314
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18391,12 +19345,12 @@ var TagDurableObject = class {
|
|
|
18391
19345
|
await txn.put(REPAIR_FACTS_KEY, { ...facts, audits: [...facts.audits, audit] });
|
|
18392
19346
|
return { status: 200, body: { status: "repair-audited", idempotent: false, branch: audit.branch } };
|
|
18393
19347
|
});
|
|
18394
|
-
return
|
|
19348
|
+
return json11(result2.body, result2.status);
|
|
18395
19349
|
}
|
|
18396
19350
|
async clearRepairFence(tag, body) {
|
|
18397
19351
|
const parsed = repairClearFrom(body);
|
|
18398
19352
|
if (parsed.value === void 0) {
|
|
18399
|
-
return
|
|
19353
|
+
return error9(400, "invalid_repair_clear", parsed.error ?? "Invalid repair clear request");
|
|
18400
19354
|
}
|
|
18401
19355
|
const input = parsed.value;
|
|
18402
19356
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18427,16 +19381,16 @@ var TagDurableObject = class {
|
|
|
18427
19381
|
});
|
|
18428
19382
|
return { status: 200, body: { status: "repair-fence-cleared", idempotent: false, version: updated.version } };
|
|
18429
19383
|
});
|
|
18430
|
-
return
|
|
19384
|
+
return json11(result2.body, result2.status);
|
|
18431
19385
|
}
|
|
18432
19386
|
async setClockOffset(tag, body) {
|
|
18433
|
-
if (!
|
|
18434
|
-
return
|
|
19387
|
+
if (!isObject12(body)) {
|
|
19388
|
+
return error9(400, "invalid_tag_clock", "offsetMs or nowMs must be a safe integer");
|
|
18435
19389
|
}
|
|
18436
19390
|
const nowMs = body.nowMs;
|
|
18437
19391
|
const offsetMs = body.offsetMs;
|
|
18438
19392
|
if (!isSafeInteger(nowMs) && !isSafeInteger(offsetMs)) {
|
|
18439
|
-
return
|
|
19393
|
+
return error9(400, "invalid_tag_clock", "offsetMs or nowMs must be a safe integer");
|
|
18440
19394
|
}
|
|
18441
19395
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
18442
19396
|
const loaded = await this.recordFor(txn, tag);
|
|
@@ -18454,7 +19408,7 @@ var TagDurableObject = class {
|
|
|
18454
19408
|
}
|
|
18455
19409
|
};
|
|
18456
19410
|
});
|
|
18457
|
-
return
|
|
19411
|
+
return json11(result2.body, result2.status);
|
|
18458
19412
|
}
|
|
18459
19413
|
async runAlarm() {
|
|
18460
19414
|
const due = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18573,16 +19527,16 @@ var TagStateCheckpointFault = class extends Error {
|
|
|
18573
19527
|
this.name = "TagStateCheckpointFault";
|
|
18574
19528
|
}
|
|
18575
19529
|
};
|
|
18576
|
-
function
|
|
19530
|
+
function json12(body, status = 200) {
|
|
18577
19531
|
return new Response(JSON.stringify(body), {
|
|
18578
19532
|
status,
|
|
18579
19533
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
18580
19534
|
});
|
|
18581
19535
|
}
|
|
18582
|
-
function
|
|
18583
|
-
return
|
|
19536
|
+
function error10(status, code, message) {
|
|
19537
|
+
return json12({ code, error: message }, status);
|
|
18584
19538
|
}
|
|
18585
|
-
function
|
|
19539
|
+
function isObject13(value) {
|
|
18586
19540
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18587
19541
|
}
|
|
18588
19542
|
function isNonEmptyString12(value) {
|
|
@@ -18597,7 +19551,7 @@ function sqlNullableString2(value, column) {
|
|
|
18597
19551
|
return sqlString2(value, column);
|
|
18598
19552
|
}
|
|
18599
19553
|
function parseIdentity(value) {
|
|
18600
|
-
if (!
|
|
19554
|
+
if (!isObject13(value) || !isNonEmptyString12(value.serviceId) || !isNonEmptyString12(value.tag) || !isNonEmptyString12(value.projectorId)) {
|
|
18601
19555
|
return void 0;
|
|
18602
19556
|
}
|
|
18603
19557
|
return { serviceId: value.serviceId, tag: value.tag, projectorId: value.projectorId };
|
|
@@ -18659,17 +19613,17 @@ var TagStateDurableObject = class {
|
|
|
18659
19613
|
async fetch(request) {
|
|
18660
19614
|
const url = new URL(request.url);
|
|
18661
19615
|
if (request.method !== "POST" || url.pathname !== "/read") {
|
|
18662
|
-
return
|
|
19616
|
+
return error10(404, "tag_state_route_not_found", "TagState route was not found");
|
|
18663
19617
|
}
|
|
18664
19618
|
let body;
|
|
18665
19619
|
try {
|
|
18666
19620
|
body = await request.json();
|
|
18667
19621
|
} catch {
|
|
18668
|
-
return
|
|
19622
|
+
return error10(400, "tag_state_validation_error", "TagState request must be JSON");
|
|
18669
19623
|
}
|
|
18670
19624
|
const identity2 = parseIdentity(body);
|
|
18671
19625
|
if (identity2 === void 0) {
|
|
18672
|
-
return
|
|
19626
|
+
return error10(400, "tag_state_validation_error", "TagState identity requires serviceId, tag, and projectorId");
|
|
18673
19627
|
}
|
|
18674
19628
|
return await this.read(identity2);
|
|
18675
19629
|
}
|
|
@@ -18691,16 +19645,16 @@ var TagStateDurableObject = class {
|
|
|
18691
19645
|
return await this.continueRebuild(identity2, projector, cache);
|
|
18692
19646
|
} catch (caught) {
|
|
18693
19647
|
if (caught instanceof TagStateIdentityConflict) {
|
|
18694
|
-
return
|
|
19648
|
+
return error10(409, "tag_state_identity_conflict", "TagState Durable Object identity changed");
|
|
18695
19649
|
}
|
|
18696
19650
|
if (caught instanceof TagStateUnknownProjector) {
|
|
18697
|
-
return
|
|
19651
|
+
return error10(404, "tag_state_unknown_projector", "TagState projector is not registered");
|
|
18698
19652
|
}
|
|
18699
19653
|
if (caught instanceof TagStateRegistryFailure) {
|
|
18700
|
-
return
|
|
19654
|
+
return error10(503, "tag_state_projector_registry_failure", "TagState projector registry is unavailable");
|
|
18701
19655
|
}
|
|
18702
19656
|
if (caught instanceof TagStateSourceFailure) {
|
|
18703
|
-
return
|
|
19657
|
+
return error10(caught.status, "tag_state_source_frontier_failure", "TagState source frontier could not be read");
|
|
18704
19658
|
}
|
|
18705
19659
|
if (caught instanceof TagStateCacheCorruption) {
|
|
18706
19660
|
try {
|
|
@@ -18708,21 +19662,21 @@ var TagStateDurableObject = class {
|
|
|
18708
19662
|
this.beginRebuild(projector, "cache-corrupt");
|
|
18709
19663
|
} catch (rebuildFailure) {
|
|
18710
19664
|
if (rebuildFailure instanceof TagStateRegistryFailure) {
|
|
18711
|
-
return
|
|
19665
|
+
return error10(503, "tag_state_projector_registry_failure", "TagState projector registry is unavailable");
|
|
18712
19666
|
}
|
|
18713
19667
|
}
|
|
18714
|
-
return
|
|
19668
|
+
return error10(409, "tag_state_cache_corrupt", "TagState cache is corrupt and is rebuilding");
|
|
18715
19669
|
}
|
|
18716
19670
|
if (caught instanceof TagStateCheckpointFault) {
|
|
18717
|
-
return
|
|
19671
|
+
return error10(503, "tag_state_rebuild_interrupted", "TagState replay checkpoint was interrupted");
|
|
18718
19672
|
}
|
|
18719
|
-
return
|
|
19673
|
+
return error10(503, "tag_state_unavailable", "TagState cache is unavailable");
|
|
18720
19674
|
}
|
|
18721
19675
|
}
|
|
18722
19676
|
async beginDeltaOrRespondReady(identity2, projector, cache) {
|
|
18723
19677
|
const page = await this.readSource(identity2, cache.lastSuid, void 0);
|
|
18724
19678
|
if (page.events.length === 0 && page.completeThrough !== null) {
|
|
18725
|
-
return
|
|
19679
|
+
return json12(this.success(projector, cache.stateJson, cache.lastSuid));
|
|
18726
19680
|
}
|
|
18727
19681
|
const rebuild = this.beginRebuild(projector, "normal-delta", {
|
|
18728
19682
|
cursor: cache.lastSuid,
|
|
@@ -18771,9 +19725,9 @@ var TagStateDurableObject = class {
|
|
|
18771
19725
|
});
|
|
18772
19726
|
if (this.g46CheckpointFault === "after-checkpoint") throw new TagStateCheckpointFault();
|
|
18773
19727
|
if (!complete) {
|
|
18774
|
-
return
|
|
19728
|
+
return error10(503, "tag_state_rebuild_in_progress", "TagState replay is rebuilding to its frozen frontier");
|
|
18775
19729
|
}
|
|
18776
|
-
return
|
|
19730
|
+
return json12(this.success(projector, accumulatorJson, page.completeThrough));
|
|
18777
19731
|
}
|
|
18778
19732
|
success(projector, stateJson, lastSortedUniqueId) {
|
|
18779
19733
|
try {
|
|
@@ -18830,7 +19784,7 @@ var TagStateDurableObject = class {
|
|
|
18830
19784
|
}
|
|
18831
19785
|
}
|
|
18832
19786
|
assertSourcePage(cursor, expectedThrough, page) {
|
|
18833
|
-
if (!
|
|
19787
|
+
if (!isObject13(page) || !Array.isArray(page.events) || typeof page.lastSortableUniqueId !== "string" || typeof page.through !== "string" || page.completeThrough !== null && typeof page.completeThrough !== "string") {
|
|
18834
19788
|
throw new TagStateSourceFailure(503, new Error("TagState source response was malformed"));
|
|
18835
19789
|
}
|
|
18836
19790
|
if (expectedThrough !== void 0 && expectedThrough !== null && page.through !== expectedThrough) {
|
|
@@ -19029,12 +19983,12 @@ function encodedCommitSnapshotLogBytes(record) {
|
|
|
19029
19983
|
function assertCommitSnapshotLogSize(record) {
|
|
19030
19984
|
const encodedBytes2 = encodedCommitSnapshotLogBytes(record);
|
|
19031
19985
|
if (encodedBytes2 > COMMIT_SNAPSHOT_LOG_BYTE_BUDGET) {
|
|
19032
|
-
const
|
|
19986
|
+
const error11 = new Error(
|
|
19033
19987
|
`Commit snapshot log is ${encodedBytes2} bytes; budget is ${COMMIT_SNAPSHOT_LOG_BYTE_BUDGET} bytes`
|
|
19034
19988
|
);
|
|
19035
|
-
|
|
19036
|
-
|
|
19037
|
-
throw
|
|
19989
|
+
error11.code = "commit-snapshot-log-size";
|
|
19990
|
+
error11.encodedBytes = encodedBytes2;
|
|
19991
|
+
throw error11;
|
|
19038
19992
|
}
|
|
19039
19993
|
return encodedBytes2;
|
|
19040
19994
|
}
|
|
@@ -19053,8 +20007,8 @@ function createCommitTraceConsoleSink(options = {}) {
|
|
|
19053
20007
|
if (record === void 0) return;
|
|
19054
20008
|
try {
|
|
19055
20009
|
assertCommitSnapshotLogSize(record);
|
|
19056
|
-
} catch (
|
|
19057
|
-
const encodedBytes2 = typeof
|
|
20010
|
+
} catch (error11) {
|
|
20011
|
+
const encodedBytes2 = typeof error11 === "object" && error11 !== null && "encodedBytes" in error11 && typeof error11.encodedBytes === "number" ? error11.encodedBytes : encodedCommitSnapshotLogBytes(record);
|
|
19058
20012
|
reportRejectedSize(Object.freeze({
|
|
19059
20013
|
schema: COMMIT_SNAPSHOT_LOG_SCHEMA,
|
|
19060
20014
|
event: "commit.snapshot-size-rejected",
|
|
@@ -19063,43 +20017,13 @@ function createCommitTraceConsoleSink(options = {}) {
|
|
|
19063
20017
|
encodedBytes: encodedBytes2,
|
|
19064
20018
|
byteBudget: COMMIT_SNAPSHOT_LOG_BYTE_BUDGET
|
|
19065
20019
|
}));
|
|
19066
|
-
throw
|
|
20020
|
+
throw error11;
|
|
19067
20021
|
}
|
|
19068
20022
|
log(record);
|
|
19069
20023
|
}
|
|
19070
20024
|
});
|
|
19071
20025
|
}
|
|
19072
20026
|
|
|
19073
|
-
// src/scope/ControlRouteScope.ts
|
|
19074
|
-
function response2(status, code, error10) {
|
|
19075
|
-
return new Response(JSON.stringify({ code, error: error10 }), {
|
|
19076
|
-
status,
|
|
19077
|
-
headers: { "content-type": "application/json; charset=utf-8" }
|
|
19078
|
-
});
|
|
19079
|
-
}
|
|
19080
|
-
function scopeIdentityMissingResponse() {
|
|
19081
|
-
return response2(503, "scope.identity_missing", "A deployment service identity is required for this scope");
|
|
19082
|
-
}
|
|
19083
|
-
function enforceControlRouteScope(input) {
|
|
19084
|
-
let actual;
|
|
19085
|
-
try {
|
|
19086
|
-
actual = input.provider.forRequest(input.request, input.requestOptions).serviceId;
|
|
19087
|
-
} catch (caught) {
|
|
19088
|
-
if (caught instanceof ServiceIdentityMissingError) {
|
|
19089
|
-
console.error({ schema: "sdt.scope/v1", code: "scope.identity_missing", route: input.route });
|
|
19090
|
-
return { response: scopeIdentityMissingResponse() };
|
|
19091
|
-
}
|
|
19092
|
-
throw caught;
|
|
19093
|
-
}
|
|
19094
|
-
if (input.pathServiceId !== actual) {
|
|
19095
|
-
console.warn({ schema: "sdt.scope/v1", code: "scope.mismatch", route: input.route });
|
|
19096
|
-
return {
|
|
19097
|
-
response: response2(403, "scope.mismatch", "The path service identity is not authorized for this deployment")
|
|
19098
|
-
};
|
|
19099
|
-
}
|
|
19100
|
-
return { serviceId: actual };
|
|
19101
|
-
}
|
|
19102
|
-
|
|
19103
20027
|
// src/diagnostics/G65DirectRing.ts
|
|
19104
20028
|
var G65_DIRECT_RING_BUDGET_MS = 100;
|
|
19105
20029
|
var G65_DIRECT_RING_CLOCK_ORIGIN = "Date.now epoch ms";
|
|
@@ -19165,7 +20089,7 @@ async function markG65DirectApplyStarted(database, message, observedAt) {
|
|
|
19165
20089
|
WHERE service_id = ? AND event_id = ? AND attempt_id = ?
|
|
19166
20090
|
`).bind(observedAt, message.serviceId, message.eventId, message.attemptId).run();
|
|
19167
20091
|
}
|
|
19168
|
-
async function markG65DirectApplyFinished(database, message, observedAt, outcome,
|
|
20092
|
+
async function markG65DirectApplyFinished(database, message, observedAt, outcome, error11) {
|
|
19169
20093
|
validateTimestamp(observedAt, "apply finish");
|
|
19170
20094
|
if (outcome.length === 0) throw new Error("G65 direct apply outcome must be non-empty");
|
|
19171
20095
|
await database.prepare(`
|
|
@@ -19177,13 +20101,109 @@ async function markG65DirectApplyFinished(database, message, observedAt, outcome
|
|
|
19177
20101
|
`).bind(
|
|
19178
20102
|
observedAt,
|
|
19179
20103
|
outcome,
|
|
19180
|
-
|
|
20104
|
+
error11 ?? null,
|
|
19181
20105
|
message.serviceId,
|
|
19182
20106
|
message.eventId,
|
|
19183
20107
|
message.attemptId
|
|
19184
20108
|
).run();
|
|
19185
20109
|
}
|
|
19186
20110
|
|
|
20111
|
+
// src/shard/ShardRotation.ts
|
|
20112
|
+
var ShardRotationError = class extends Error {
|
|
20113
|
+
diagnostic;
|
|
20114
|
+
constructor(diagnostic) {
|
|
20115
|
+
super(diagnostic.code);
|
|
20116
|
+
this.name = "ShardRotationError";
|
|
20117
|
+
this.diagnostic = diagnostic;
|
|
20118
|
+
}
|
|
20119
|
+
};
|
|
20120
|
+
function fail(code, path, reason) {
|
|
20121
|
+
throw new ShardRotationError({ code, path, reason });
|
|
20122
|
+
}
|
|
20123
|
+
function createRotationState(active2) {
|
|
20124
|
+
if (typeof active2 !== "string" || active2.length === 0) fail("ACTIVE_COUNT", "active", "active-missing");
|
|
20125
|
+
return { sealed: [], active: active2, ledger: [] };
|
|
20126
|
+
}
|
|
20127
|
+
function assertStrictlyBefore(lastD1_1, firstD1_2) {
|
|
20128
|
+
assertSortableUniqueId(lastD1_1);
|
|
20129
|
+
assertSortableUniqueId(firstD1_2);
|
|
20130
|
+
if (compareSortableUniqueId(lastD1_1, firstD1_2) >= 0) {
|
|
20131
|
+
fail("ORDER_INVARIANT", "last(D1-1)<first(D1-2)", "order-invariant");
|
|
20132
|
+
}
|
|
20133
|
+
}
|
|
20134
|
+
function maxSortableUniqueId(values) {
|
|
20135
|
+
if (values.length === 0) fail("SEAL_MAX_MISSING", "d1-1", "seal-max-missing");
|
|
20136
|
+
let max = assertSortableUniqueId(values[0]).value;
|
|
20137
|
+
for (let i = 1; i < values.length; i += 1) {
|
|
20138
|
+
const next = assertSortableUniqueId(values[i]).value;
|
|
20139
|
+
if (next > max) max = next;
|
|
20140
|
+
}
|
|
20141
|
+
return max;
|
|
20142
|
+
}
|
|
20143
|
+
function rotateAppend(state, sealedId, shardSuids, nextActiveId) {
|
|
20144
|
+
if (sealedId !== state.active) fail("UNKNOWN_SHARD", sealedId, "unknown-shard");
|
|
20145
|
+
if (typeof nextActiveId !== "string" || nextActiveId.length === 0 || nextActiveId === sealedId) {
|
|
20146
|
+
fail("ACTIVE_COUNT", "active", "active-count");
|
|
20147
|
+
}
|
|
20148
|
+
if (state.sealed.some((entry) => entry.id === nextActiveId)) {
|
|
20149
|
+
fail("ACTIVE_COUNT", nextActiveId, "active-already-sealed");
|
|
20150
|
+
}
|
|
20151
|
+
if (state.sealed.some((entry) => entry.id === sealedId)) fail("IDENTITY_SWAP", sealedId, "identity-swap");
|
|
20152
|
+
const lastSuid = maxSortableUniqueId(shardSuids);
|
|
20153
|
+
return {
|
|
20154
|
+
sealed: [...state.sealed, { id: sealedId, lastSuid }],
|
|
20155
|
+
active: nextActiveId,
|
|
20156
|
+
ledger: state.ledger
|
|
20157
|
+
};
|
|
20158
|
+
}
|
|
20159
|
+
function replaceSealedIdentity(state, index, nextId) {
|
|
20160
|
+
if (index < 0 || index >= state.sealed.length) fail("UNKNOWN_SHARD", "sealed", "unknown-shard");
|
|
20161
|
+
fail("IDENTITY_SWAP", state.sealed[index].id, `identity-swap:${nextId}`);
|
|
20162
|
+
}
|
|
20163
|
+
function refuseSealedWrite(state, shardId) {
|
|
20164
|
+
if (state.sealed.some((entry) => entry.id === shardId)) fail("SEALED_WRITE", shardId, "sealed-write");
|
|
20165
|
+
}
|
|
20166
|
+
function admitActiveWrite(state, shardId, firstOrNextSuid) {
|
|
20167
|
+
assertSortableUniqueId(firstOrNextSuid);
|
|
20168
|
+
refuseSealedWrite(state, shardId);
|
|
20169
|
+
if (shardId !== state.active) fail("UNKNOWN_SHARD", shardId, "unknown-shard");
|
|
20170
|
+
if (state.sealed.length === 0) return;
|
|
20171
|
+
const previous = state.sealed[state.sealed.length - 1];
|
|
20172
|
+
assertStrictlyBefore(previous.lastSuid, firstOrNextSuid);
|
|
20173
|
+
}
|
|
20174
|
+
function recordLateArrival(state, sealedShardId, suid) {
|
|
20175
|
+
assertSortableUniqueId(suid);
|
|
20176
|
+
const sealed = state.sealed.find((entry) => entry.id === sealedShardId);
|
|
20177
|
+
if (sealed === void 0) fail("UNKNOWN_SHARD", sealedShardId, "unknown-shard");
|
|
20178
|
+
if (compareSortableUniqueId(suid, sealed.lastSuid) > 0) {
|
|
20179
|
+
fail("ORDER_INVARIANT", sealedShardId, "not-late-arrival");
|
|
20180
|
+
}
|
|
20181
|
+
return {
|
|
20182
|
+
...state,
|
|
20183
|
+
ledger: [...state.ledger, { shardId: sealed.id, suid, reason: "sealed-range" }]
|
|
20184
|
+
};
|
|
20185
|
+
}
|
|
20186
|
+
var SILENT_MERGE_TOKENS = [
|
|
20187
|
+
["merge", "Ledger", "Into", "Sealed"],
|
|
20188
|
+
["merge", "OnRead", "Sealed"],
|
|
20189
|
+
["silent", "Merge", "Ledger"],
|
|
20190
|
+
["ledger", ".concat(", "sealed"],
|
|
20191
|
+
["sealed", ".concat(", "ledger"]
|
|
20192
|
+
];
|
|
20193
|
+
function assertNoSilentLedgerMerge(source) {
|
|
20194
|
+
const lowered = source.toLowerCase();
|
|
20195
|
+
for (const token of SILENT_MERGE_TOKENS) {
|
|
20196
|
+
const needle = token.join("").toLowerCase();
|
|
20197
|
+
if (lowered.includes(needle)) fail("SILENT_MERGE", "ledger", "silent-merge");
|
|
20198
|
+
}
|
|
20199
|
+
}
|
|
20200
|
+
function refuseSilentLedgerMerge() {
|
|
20201
|
+
fail("SILENT_MERGE", "ledger", "silent-merge");
|
|
20202
|
+
}
|
|
20203
|
+
function refuseSafeWindowSeal() {
|
|
20204
|
+
fail("SAFEWINDOW_SEAL", "safe-window", "safewindow-seal");
|
|
20205
|
+
}
|
|
20206
|
+
|
|
19187
20207
|
// src/cloudflare.ts
|
|
19188
20208
|
function cloudflareCommitTraceProvider(request, env) {
|
|
19189
20209
|
const versionId = env.WORKER_VERSION?.id;
|
|
@@ -19224,6 +20244,10 @@ function createCloudflareOnlyRuntimeWorker(options = {}) {
|
|
|
19224
20244
|
const storeProvider = createD1StoreProvider();
|
|
19225
20245
|
return {
|
|
19226
20246
|
async fetch(request, env, ctx) {
|
|
20247
|
+
const url = new URL(request.url);
|
|
20248
|
+
if (isIncidentMaintenancePath(url.pathname)) {
|
|
20249
|
+
return handleIncidentMaintenance(request, env, options.serviceIdentityProvider ?? envServiceIdentity(env));
|
|
20250
|
+
}
|
|
19227
20251
|
const serviceIdentity = options.serviceIdentityProvider ?? envServiceIdentity(env);
|
|
19228
20252
|
const requestIdentityOptions = { allowG11Verification: env.G11_VERIFICATION_ENABLED === "true" };
|
|
19229
20253
|
let requestServiceId;
|
|
@@ -19233,7 +20257,6 @@ function createCloudflareOnlyRuntimeWorker(options = {}) {
|
|
|
19233
20257
|
return scopeIdentityMissingResponse();
|
|
19234
20258
|
}
|
|
19235
20259
|
const durableHopObserver = createG60DurableHopObserver(env.D1, (promise) => ctx.waitUntil(promise));
|
|
19236
|
-
const url = new URL(request.url);
|
|
19237
20260
|
if (url.pathname === "/api/sekiban/serialized/commit") {
|
|
19238
20261
|
return handleSerializedCommit(request, env, {
|
|
19239
20262
|
domainDeliveryClass: options.config?.deliveryClass,
|
|
@@ -19287,9 +20310,16 @@ function createCloudflareOnlyRuntimeWorker(options = {}) {
|
|
|
19287
20310
|
}
|
|
19288
20311
|
if (url.pathname === "/internal/downstream/drain" && request.method === "POST") {
|
|
19289
20312
|
if (durableHopObserver === void 0) {
|
|
19290
|
-
return handleOutboxDrainRequest(request, env, {
|
|
20313
|
+
return handleOutboxDrainRequest(request, env, {
|
|
20314
|
+
acknowledgement: "global-receipt",
|
|
20315
|
+
serviceIdentityProvider: serviceIdentity
|
|
20316
|
+
});
|
|
19291
20317
|
}
|
|
19292
|
-
return handleOutboxDrainRequest(request, env, {
|
|
20318
|
+
return handleOutboxDrainRequest(request, env, {
|
|
20319
|
+
acknowledgement: "global-receipt",
|
|
20320
|
+
durableHopObserver,
|
|
20321
|
+
serviceIdentityProvider: serviceIdentity
|
|
20322
|
+
});
|
|
19293
20323
|
}
|
|
19294
20324
|
if (url.pathname === "/internal/projection/lag") {
|
|
19295
20325
|
return handleProjectionLag(request, env, composition.projectors, storeProvider, serviceIdentity);
|
|
@@ -19440,14 +20470,19 @@ export {
|
|
|
19440
20470
|
MAX_SERVICE_BINDING_INVOCATIONS_PER_REQUEST,
|
|
19441
20471
|
ScopeNameError,
|
|
19442
20472
|
ServiceIdentityMissingError,
|
|
20473
|
+
ShardRotationError,
|
|
19443
20474
|
TEST_SERVICE_ID_HEADER,
|
|
19444
20475
|
TagDurableObject2 as TagDurableObject,
|
|
19445
20476
|
TagStateDurableObject2 as TagStateDurableObject,
|
|
20477
|
+
admitActiveWrite,
|
|
20478
|
+
assertNoSilentLedgerMerge,
|
|
20479
|
+
assertStrictlyBefore,
|
|
19446
20480
|
buildScopeName,
|
|
19447
20481
|
classifyDirectDoorbellFailure,
|
|
19448
20482
|
cleanupG42JournalProbeTrial,
|
|
19449
20483
|
createCloudflareOnlyRuntimeWorker,
|
|
19450
20484
|
createG60DurableHopObserver,
|
|
20485
|
+
createRotationState,
|
|
19451
20486
|
cloudflare_default as default,
|
|
19452
20487
|
deliveryCorrelationId,
|
|
19453
20488
|
downstreamEnvelopeBytes,
|
|
@@ -19460,6 +20495,7 @@ export {
|
|
|
19460
20495
|
isScopeServiceId,
|
|
19461
20496
|
markG65DirectApplyFinished,
|
|
19462
20497
|
markG65DirectApplyStarted,
|
|
20498
|
+
maxSortableUniqueId,
|
|
19463
20499
|
measureG42JournalProbeTrial,
|
|
19464
20500
|
observeFaultBarrier,
|
|
19465
20501
|
observeG60UnsafeWriter,
|
|
@@ -19477,8 +20513,14 @@ export {
|
|
|
19477
20513
|
recordDurableUnsafeWriterBoundary,
|
|
19478
20514
|
recordFirstUnsafeVisibleRead,
|
|
19479
20515
|
recordG65DirectRing,
|
|
20516
|
+
recordLateArrival,
|
|
20517
|
+
refuseSafeWindowSeal,
|
|
20518
|
+
refuseSealedWrite,
|
|
20519
|
+
refuseSilentLedgerMerge,
|
|
20520
|
+
replaceSealedIdentity,
|
|
19480
20521
|
requestServiceIdentity,
|
|
19481
20522
|
requireServiceIdentity,
|
|
20523
|
+
rotateAppend,
|
|
19482
20524
|
runG42JournalProbeTrial,
|
|
19483
20525
|
scheduledLiveProjectionMaximumSuid,
|
|
19484
20526
|
scopeIdFor,
|