@sekiban/dcb-runtime 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -1
- package/dist/allocator/AllocatorDurableObject.js +25 -2
- package/dist/bootstrap/BootstrapCoordinatorDurableObject.d.ts +2 -0
- package/dist/bootstrap/BootstrapCoordinatorDurableObject.js +64 -1
- package/dist/bootstrap/OperatorBootstrap.d.ts +5 -1
- package/dist/bootstrap/OperatorBootstrap.js +17 -10
- package/dist/bootstrap/types.d.ts +6 -0
- package/dist/cli/OperatorRepairCli.d.ts +6 -1
- package/dist/cli/OperatorRepairCli.js +26 -6
- package/dist/cloudflare.d.ts +5 -0
- package/dist/cloudflare.js +1567 -465
- package/dist/commit/CommitWorker.d.ts +19 -0
- package/dist/commit/CommitWorker.js +176 -27
- package/dist/completeness/IncidentLifecycle.d.ts +75 -0
- package/dist/completeness/IncidentLifecycle.js +503 -0
- package/dist/completeness/types.d.ts +67 -0
- package/dist/cosmos.d.ts +34 -4
- package/dist/cosmos.js +214 -49
- package/dist/downstream/OutboxDrain.d.ts +4 -1
- package/dist/downstream/OutboxDrain.js +24 -2
- package/dist/generated/cosmos-layout.d.ts +87 -0
- package/dist/generated/cosmos-layout.js +99 -0
- package/dist/http/ConfiguredBearer.d.ts +12 -0
- package/dist/http/ConfiguredBearer.js +33 -0
- package/dist/http/IncidentMaintenance.d.ts +8 -0
- package/dist/http/IncidentMaintenance.js +124 -0
- package/dist/http/SerializedQueryWorker.js +37 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +1627 -465
- package/dist/read/SerializedReadWorker.d.ts +3 -0
- package/dist/read/SerializedReadWorker.js +3 -3
- package/dist/shard/ShardRotation.d.ts +48 -0
- package/dist/shard/ShardRotation.js +120 -0
- package/dist/store/CosmosEventStore.js +56 -46
- package/dist/store/PostgresEventStore.d.ts +20 -0
- package/dist/store/PostgresEventStore.js +53 -0
- package/dist/store/provider.d.ts +4 -0
- package/dist/tag/TagDurableObject.js +66 -14
- package/dist/tag/TagSqlSchema.d.ts +6 -1
- package/dist/tag/TagSqlSchema.js +15 -0
- package/dist/tag/types.d.ts +9 -0
- package/dist/trace/CommitTrace.d.ts +3 -3
- package/dist/trace/CommitTraceVerifier.js +1 -1
- package/package.json +6 -15
package/dist/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 digest = 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] === digest ? { control } : { digestMismatch: true };
|
|
5467
|
+
}
|
|
5468
|
+
if (control.status !== "EMPTY" && control.status !== "READY") return { rejected: true };
|
|
5469
|
+
const updated = { ...control, writePermits: { ...active2, [commandId]: digest } };
|
|
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 digest = 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 && digest !== void 0 && active2[commandId] !== digest) {
|
|
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 =
|
|
5668
|
+
const bytes2 = eventDigestBytes(input);
|
|
5669
|
+
const source = bytes2.buffer.slice(bytes2.byteOffset, bytes2.byteOffset + bytes2.byteLength);
|
|
5598
5670
|
const digest = await crypto.subtle.digest("SHA-256", source);
|
|
5599
5671
|
return [...new Uint8Array(digest)].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 {
|
|
@@ -5742,6 +5814,34 @@ function createBootstrapStoreAdapter(provider, store, identityGuard) {
|
|
|
5742
5814
|
return new BootstrapStoreAdapter(provider, store, identityGuard);
|
|
5743
5815
|
}
|
|
5744
5816
|
|
|
5817
|
+
// src/http/ConfiguredBearer.ts
|
|
5818
|
+
async function sha256(value) {
|
|
5819
|
+
const encoded = new TextEncoder().encode(value);
|
|
5820
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", encoded));
|
|
5821
|
+
}
|
|
5822
|
+
function digestsMatch(expected, supplied) {
|
|
5823
|
+
let difference = expected.length ^ supplied.length;
|
|
5824
|
+
const length = Math.max(expected.length, supplied.length);
|
|
5825
|
+
for (let index = 0; index < length; index += 1) {
|
|
5826
|
+
difference |= (expected[index] ?? 0) ^ (supplied[index] ?? 0);
|
|
5827
|
+
}
|
|
5828
|
+
return difference === 0;
|
|
5829
|
+
}
|
|
5830
|
+
async function checkConfiguredBearer(request, binding, options) {
|
|
5831
|
+
if (typeof binding !== "string" || binding.trim().length === 0) {
|
|
5832
|
+
return "unconfigured";
|
|
5833
|
+
}
|
|
5834
|
+
const effectiveToken = options?.tokenNormalization === "trim" ? binding.trim() : binding;
|
|
5835
|
+
const supplied = request.headers.get("authorization");
|
|
5836
|
+
if (supplied === null) return "missing";
|
|
5837
|
+
const expected = `Bearer ${effectiveToken}`;
|
|
5838
|
+
const [expectedDigest, suppliedDigest] = await Promise.all([
|
|
5839
|
+
sha256(expected),
|
|
5840
|
+
sha256(supplied)
|
|
5841
|
+
]);
|
|
5842
|
+
return digestsMatch(expectedDigest, suppliedDigest) ? "authorized" : "invalid";
|
|
5843
|
+
}
|
|
5844
|
+
|
|
5745
5845
|
// src/bootstrap/OperatorBootstrap.ts
|
|
5746
5846
|
function json2(body, status = 200) {
|
|
5747
5847
|
return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json; charset=utf-8" } });
|
|
@@ -5749,15 +5849,17 @@ function json2(body, status = 200) {
|
|
|
5749
5849
|
function object2(value) {
|
|
5750
5850
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5751
5851
|
}
|
|
5752
|
-
function
|
|
5753
|
-
const
|
|
5754
|
-
if (
|
|
5755
|
-
|
|
5852
|
+
async function authenticateOperatorBootstrap(request, binding) {
|
|
5853
|
+
const decision = await checkConfiguredBearer(request, binding);
|
|
5854
|
+
if (decision === "unconfigured" || decision === "missing") {
|
|
5855
|
+
return json2({ code: "not_found", error: "not found" }, 404);
|
|
5856
|
+
}
|
|
5857
|
+
if (decision === "invalid") {
|
|
5858
|
+
return json2({ code: "operator_auth_required", error: "valid bearer required" }, 403);
|
|
5859
|
+
}
|
|
5756
5860
|
return void 0;
|
|
5757
5861
|
}
|
|
5758
|
-
async function
|
|
5759
|
-
const denied = bearer(request, env.REPAIR_OPERATOR_TOKEN);
|
|
5760
|
-
if (denied !== void 0) return denied;
|
|
5862
|
+
async function handleAuthorizedOperatorBootstrap(request, env, storeProvider, hooks = {}) {
|
|
5761
5863
|
const url = new URL(request.url);
|
|
5762
5864
|
const match = url.pathname.match(/^\/operator\/bootstrap\/([^/]+)\/(plan|import|status|abort|export)$/);
|
|
5763
5865
|
if (match === null) return json2({ code: "not_found", error: "not found" }, 404);
|
|
@@ -6656,14 +6758,21 @@ function testFaultFromRequest(request) {
|
|
|
6656
6758
|
const value = request.headers.get("x-sdt-g6-test-fault");
|
|
6657
6759
|
return value === "after-lease-before-prepare" || value === "after-prepare-before-apply" || value === "after-apply-before-observation" || value === "after-verify-before-audit" || value === "after-audit-before-clear" || value === "after-clear-before-final-observation" ? value : void 0;
|
|
6658
6760
|
}
|
|
6659
|
-
|
|
6660
|
-
if (request.method
|
|
6661
|
-
|
|
6761
|
+
function nonPostOperatorRepairResponse(request) {
|
|
6762
|
+
if (request.method === "POST") return void 0;
|
|
6763
|
+
return error2(404, "operator_repair_route_not_found", "Operator repair requires POST");
|
|
6764
|
+
}
|
|
6765
|
+
async function authenticateOperatorRepair(request, binding) {
|
|
6766
|
+
const decision = await checkConfiguredBearer(request, binding);
|
|
6767
|
+
if (decision === "unconfigured") {
|
|
6768
|
+
return error2(404, "operator_repair_route_not_found", "Operator repair route not found");
|
|
6662
6769
|
}
|
|
6663
|
-
|
|
6664
|
-
if (!isNonEmptyString3(token) || request.headers.get("authorization") !== `Bearer ${token}`) {
|
|
6770
|
+
if (decision === "missing" || decision === "invalid") {
|
|
6665
6771
|
return error2(401, "operator_auth_required", "A valid operator repair bearer token is required");
|
|
6666
6772
|
}
|
|
6773
|
+
return void 0;
|
|
6774
|
+
}
|
|
6775
|
+
async function handleAuthorizedOperatorRepair(request, env, nativeTracing = noOpNativeTracing, serviceIdentityProvider) {
|
|
6667
6776
|
let body;
|
|
6668
6777
|
try {
|
|
6669
6778
|
body = await request.json();
|
|
@@ -6713,6 +6822,22 @@ var COMMIT_TEST_FAULTS = [
|
|
|
6713
6822
|
var INITIAL_OWNER_EPOCH = 0;
|
|
6714
6823
|
var MAX_WRITE_ATTEMPTS = 2;
|
|
6715
6824
|
var OUTCOME_UNDETERMINED_ERROR = "Commit outcome is undetermined; reread tag heads and event/query state before retrying because blind retry may create duplicate events.";
|
|
6825
|
+
function commitPermitDigest(allTags, candidates) {
|
|
6826
|
+
const normalized = JSON.stringify({
|
|
6827
|
+
tags: [...allTags].sort(),
|
|
6828
|
+
events: candidates.map((candidate) => ({
|
|
6829
|
+
eventId: candidate.eventId,
|
|
6830
|
+
payload: candidate.payload,
|
|
6831
|
+
tags: [...candidate.tags].sort()
|
|
6832
|
+
})).sort((left, right) => left.eventId < right.eventId ? -1 : left.eventId > right.eventId ? 1 : 0)
|
|
6833
|
+
});
|
|
6834
|
+
let hash = 2166136261;
|
|
6835
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
6836
|
+
hash ^= normalized.charCodeAt(index);
|
|
6837
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
6838
|
+
}
|
|
6839
|
+
return `fnv1a32:${hash.toString(16).padStart(8, "0")}`;
|
|
6840
|
+
}
|
|
6716
6841
|
var GLOBAL_ADMISSION_HEADER = "x-sdt-global-admission";
|
|
6717
6842
|
var runtimeCommitTraceVerifier = {
|
|
6718
6843
|
verify(snapshot) {
|
|
@@ -6766,19 +6891,19 @@ var CommitPayloadAdmissionError = class extends Error {
|
|
|
6766
6891
|
code;
|
|
6767
6892
|
};
|
|
6768
6893
|
function decodeBase64Utf8Json(value) {
|
|
6769
|
-
let
|
|
6894
|
+
let bytes2;
|
|
6770
6895
|
try {
|
|
6771
6896
|
const binary = atob(value);
|
|
6772
|
-
|
|
6897
|
+
bytes2 = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
6773
6898
|
} catch {
|
|
6774
6899
|
throw new CommitPayloadAdmissionError("invalid_payload_utf8", "Payload base64 could not be decoded");
|
|
6775
6900
|
}
|
|
6776
|
-
if (
|
|
6901
|
+
if (bytes2[0] === 239 && bytes2[1] === 187 && bytes2[2] === 191) {
|
|
6777
6902
|
throw new CommitPayloadAdmissionError("invalid_payload_utf8", "Payload must not start with a UTF-8 BOM");
|
|
6778
6903
|
}
|
|
6779
6904
|
let text;
|
|
6780
6905
|
try {
|
|
6781
|
-
text = new TextDecoder("utf-8", { fatal: true }).decode(
|
|
6906
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
6782
6907
|
} catch {
|
|
6783
6908
|
throw new CommitPayloadAdmissionError("invalid_payload_utf8", "Payload must be valid UTF-8 JSON text");
|
|
6784
6909
|
}
|
|
@@ -6824,9 +6949,9 @@ function assertExactMemberPaths(actual, registered, path = "$") {
|
|
|
6824
6949
|
}
|
|
6825
6950
|
}
|
|
6826
6951
|
function encodeBase64Utf8(value) {
|
|
6827
|
-
const
|
|
6952
|
+
const bytes2 = new TextEncoder().encode(value);
|
|
6828
6953
|
let binary = "";
|
|
6829
|
-
for (const byte of
|
|
6954
|
+
for (const byte of bytes2) binary += String.fromCharCode(byte);
|
|
6830
6955
|
return btoa(binary);
|
|
6831
6956
|
}
|
|
6832
6957
|
function faultFromRequest(request) {
|
|
@@ -7085,25 +7210,43 @@ var CommitWorker = class {
|
|
|
7085
7210
|
return responseWithAttempt(error3(409, "bootstrap_command_rejected", "Bootstrap admission could not be released"), attemptId, fault2 !== void 0);
|
|
7086
7211
|
}
|
|
7087
7212
|
await this.retiredJournalMilestone("S04", traceState?.scope);
|
|
7213
|
+
const permitDigest = commitPermitDigest(input.allTags, candidates);
|
|
7214
|
+
let permitHeld = false;
|
|
7215
|
+
const releasePermit = async () => {
|
|
7216
|
+
if (!permitHeld) return;
|
|
7217
|
+
permitHeld = false;
|
|
7218
|
+
await this.bootstrapPermitRelease(attemptId, permitDigest);
|
|
7219
|
+
};
|
|
7220
|
+
const permit = await this.bootstrapPermitAcquire(attemptId, permitDigest);
|
|
7221
|
+
if (!permit.ok) {
|
|
7222
|
+
if (permit.code === "bootstrap_permit_digest_mismatch") {
|
|
7223
|
+
return responseWithAttempt(error3(409, "bootstrap_permit_digest_mismatch", "Bootstrap write permit digest does not match"), attemptId, fault2 !== void 0);
|
|
7224
|
+
}
|
|
7225
|
+
return responseWithAttempt(error3(409, "bootstrap_command_rejected", "Bootstrap is importing; commit is unavailable"), attemptId, fault2 !== void 0);
|
|
7226
|
+
}
|
|
7227
|
+
permitHeld = this.env.BOOTSTRAP !== void 0;
|
|
7088
7228
|
const reservations = await this.acquireReservations(input, attemptId, fault2, traceState?.scope);
|
|
7089
7229
|
await this.retiredJournalMilestone("S05a", traceState?.scope, 0);
|
|
7090
7230
|
if (reservations.failure !== void 0) {
|
|
7091
|
-
|
|
7231
|
+
const failure3 = await this.finishReservationFailure(
|
|
7092
7232
|
[...reservations.successes.keys()],
|
|
7093
7233
|
attemptId,
|
|
7094
7234
|
reservations.failure,
|
|
7095
7235
|
fault2,
|
|
7096
7236
|
traceState?.scope
|
|
7097
7237
|
);
|
|
7238
|
+
if (failure3.resolved) await releasePermit();
|
|
7239
|
+
return failure3.response;
|
|
7098
7240
|
}
|
|
7099
7241
|
if (fault2 === "after-reservations-before-allocation") {
|
|
7100
|
-
await this.cancelReservations([...reservations.successes.keys()], attemptId, fault2, traceState?.scope);
|
|
7242
|
+
const cleanup = await this.cancelReservations([...reservations.successes.keys()], attemptId, fault2, traceState?.scope);
|
|
7243
|
+
if (cleanup.failedTags.length === 0) await releasePermit();
|
|
7101
7244
|
return this.noApplicationOutcome(attemptId, true);
|
|
7102
7245
|
}
|
|
7103
7246
|
await this.hooks.beforeBootstrapAllocation?.();
|
|
7104
7247
|
const allocation = await this.allocate(candidates, attemptId, fault2, bootstrapEpoch, traceState?.scope);
|
|
7105
7248
|
if (allocation === void 0) {
|
|
7106
|
-
|
|
7249
|
+
const failure3 = await this.finishReservationFailure(
|
|
7107
7250
|
[...reservations.successes.keys()],
|
|
7108
7251
|
attemptId,
|
|
7109
7252
|
{
|
|
@@ -7114,10 +7257,13 @@ var CommitWorker = class {
|
|
|
7114
7257
|
fault2,
|
|
7115
7258
|
traceState?.scope
|
|
7116
7259
|
);
|
|
7260
|
+
if (failure3.resolved) await releasePermit();
|
|
7261
|
+
return failure3.response;
|
|
7117
7262
|
}
|
|
7118
7263
|
const allocatedCandidates = this.withAllocatedSuids(candidates, allocation);
|
|
7119
7264
|
if (allocatedCandidates === void 0) {
|
|
7120
|
-
await this.cancelReservations([...reservations.successes.keys()], attemptId, fault2, traceState?.scope);
|
|
7265
|
+
const cleanup = await this.cancelReservations([...reservations.successes.keys()], attemptId, fault2, traceState?.scope);
|
|
7266
|
+
if (cleanup.failedTags.length === 0) await releasePermit();
|
|
7121
7267
|
return this.noApplicationOutcome(attemptId, fault2 !== void 0);
|
|
7122
7268
|
}
|
|
7123
7269
|
for (const candidate of allocatedCandidates) {
|
|
@@ -7132,15 +7278,18 @@ var CommitWorker = class {
|
|
|
7132
7278
|
}
|
|
7133
7279
|
await this.retiredJournalMilestone("S05b", traceState?.scope, 1);
|
|
7134
7280
|
if (fault2 === "journal-cas-after-allocator") {
|
|
7135
|
-
await this.cancelReservations(input.allTags, attemptId, fault2, traceState?.scope);
|
|
7281
|
+
const cleanup = await this.cancelReservations(input.allTags, attemptId, fault2, traceState?.scope);
|
|
7282
|
+
if (cleanup.failedTags.length === 0) await releasePermit();
|
|
7136
7283
|
return this.noApplicationOutcome(attemptId, true);
|
|
7137
7284
|
}
|
|
7138
7285
|
await this.retiredJournalMilestone("S05c", traceState?.scope, 2);
|
|
7139
7286
|
await this.hooks.beforeBootstrapFinalization?.();
|
|
7140
7287
|
if (await this.bootstrapCommand("finalize", attemptId, bootstrapEpoch, traceState?.scope, "S10") === void 0) {
|
|
7141
|
-
await this.cancelReservations(input.allTags, attemptId, fault2, traceState?.scope);
|
|
7288
|
+
const cleanup = await this.cancelReservations(input.allTags, attemptId, fault2, traceState?.scope);
|
|
7289
|
+
if (cleanup.failedTags.length === 0) await releasePermit();
|
|
7142
7290
|
return responseWithAttempt(error3(409, "bootstrap_command_rejected", "Bootstrap fencing epoch changed before commit write"), attemptId, fault2 !== void 0);
|
|
7143
7291
|
}
|
|
7292
|
+
await this.hooks.beforeAuthoritativeAppend?.();
|
|
7144
7293
|
const writes = await this.appendAllTags(
|
|
7145
7294
|
input,
|
|
7146
7295
|
allocatedCandidates,
|
|
@@ -7156,6 +7305,7 @@ var CommitWorker = class {
|
|
|
7156
7305
|
return this.noApplicationOutcome(attemptId, true);
|
|
7157
7306
|
}
|
|
7158
7307
|
if (writes.committedTags.size === 0 && writes.registrationUnavailableTags.length === writes.pendingTags.length) {
|
|
7308
|
+
await releasePermit();
|
|
7159
7309
|
return responseWithAttempt(
|
|
7160
7310
|
error3(
|
|
7161
7311
|
503,
|
|
@@ -7167,20 +7317,25 @@ var CommitWorker = class {
|
|
|
7167
7317
|
fault2 !== void 0
|
|
7168
7318
|
);
|
|
7169
7319
|
}
|
|
7320
|
+
await releasePermit();
|
|
7170
7321
|
return this.partialWriteOutcome(input, allocatedCandidates, writes, attemptId, fault2 !== void 0);
|
|
7171
7322
|
}
|
|
7323
|
+
await this.hooks.afterAppendBeforeResponse?.();
|
|
7172
7324
|
await this.retiredJournalMilestone("S05d", traceState?.scope, 3);
|
|
7173
7325
|
if (fault2 === "sealing-after-cas") {
|
|
7326
|
+
await releasePermit();
|
|
7174
7327
|
return this.noApplicationOutcome(attemptId, true);
|
|
7175
7328
|
}
|
|
7176
7329
|
try {
|
|
7177
|
-
const success = await this.successResponse(input, allocatedCandidates, attemptId, startedAt, fault2, traceState?.scope);
|
|
7330
|
+
const success = await this.successResponse(input, allocatedCandidates, attemptId, startedAt, fault2, traceState?.scope, writes);
|
|
7331
|
+
await releasePermit();
|
|
7178
7332
|
return responseWithAttempt(
|
|
7179
7333
|
withGlobalAdmission(json4(success, 200), writes.globalAdmission),
|
|
7180
7334
|
attemptId,
|
|
7181
7335
|
fault2 !== void 0
|
|
7182
7336
|
);
|
|
7183
7337
|
} catch {
|
|
7338
|
+
await releasePermit();
|
|
7184
7339
|
return responseWithAttempt(
|
|
7185
7340
|
error3(500, "internal_error", "Committed records could not be read while preparing the response"),
|
|
7186
7341
|
attemptId,
|
|
@@ -7227,6 +7382,51 @@ var CommitWorker = class {
|
|
|
7227
7382
|
const result2 = traceScope === void 0 || rowId === void 0 ? await invoke() : await traceScope.span(rowId, {}, invoke);
|
|
7228
7383
|
return result2.leaseEpoch;
|
|
7229
7384
|
}
|
|
7385
|
+
/**
|
|
7386
|
+
* SDT-G36 bootstrap write permit. Deliberately untraced: it must not add
|
|
7387
|
+
* `sdt.commit/v2` rows or new trace identities. Only a bootstrap barrier,
|
|
7388
|
+
* never commit-outcome authority. No JOURNAL, no alarm, no lease expiry.
|
|
7389
|
+
*/
|
|
7390
|
+
bootstrapStub() {
|
|
7391
|
+
if (this.env.BOOTSTRAP === void 0) return void 0;
|
|
7392
|
+
return this.env.BOOTSTRAP.get(scopeIdFor(this.env.BOOTSTRAP, {
|
|
7393
|
+
serviceId: this.serviceId,
|
|
7394
|
+
doClass: "bootstrap",
|
|
7395
|
+
identity: "coordinator"
|
|
7396
|
+
}));
|
|
7397
|
+
}
|
|
7398
|
+
async bootstrapPermitAcquire(commandId, digest) {
|
|
7399
|
+
const stub = this.bootstrapStub();
|
|
7400
|
+
if (stub === void 0) return { ok: true };
|
|
7401
|
+
try {
|
|
7402
|
+
const url = new URL(`https://commit-worker.internal/command/permit`);
|
|
7403
|
+
url.searchParams.set("__serviceId", this.serviceId);
|
|
7404
|
+
const response3 = await stub.fetch(new Request(url, {
|
|
7405
|
+
method: "POST",
|
|
7406
|
+
headers: { "content-type": "application/json" },
|
|
7407
|
+
body: JSON.stringify({ commandId, digest })
|
|
7408
|
+
}));
|
|
7409
|
+
if (response3.ok) return { ok: true };
|
|
7410
|
+
const body = await response3.clone().json().catch(() => void 0);
|
|
7411
|
+
return { ok: false, code: typeof body?.code === "string" ? body.code : void 0 };
|
|
7412
|
+
} catch {
|
|
7413
|
+
return { ok: false };
|
|
7414
|
+
}
|
|
7415
|
+
}
|
|
7416
|
+
async bootstrapPermitRelease(commandId, digest) {
|
|
7417
|
+
const stub = this.bootstrapStub();
|
|
7418
|
+
if (stub === void 0) return;
|
|
7419
|
+
try {
|
|
7420
|
+
const url = new URL(`https://commit-worker.internal/command/permit-release`);
|
|
7421
|
+
url.searchParams.set("__serviceId", this.serviceId);
|
|
7422
|
+
await stub.fetch(new Request(url, {
|
|
7423
|
+
method: "POST",
|
|
7424
|
+
headers: { "content-type": "application/json" },
|
|
7425
|
+
body: JSON.stringify({ commandId, digest })
|
|
7426
|
+
}));
|
|
7427
|
+
} catch {
|
|
7428
|
+
}
|
|
7429
|
+
}
|
|
7230
7430
|
async postJson(stub, path, body, traceScope, rowId, traceOptions = {}) {
|
|
7231
7431
|
const invoke = async () => {
|
|
7232
7432
|
const response3 = await stub.fetch(
|
|
@@ -7372,6 +7572,7 @@ var CommitWorker = class {
|
|
|
7372
7572
|
let pending = [...input.allTags];
|
|
7373
7573
|
const committedTags = /* @__PURE__ */ new Set();
|
|
7374
7574
|
const lastFailureCodeByTag = /* @__PURE__ */ new Map();
|
|
7575
|
+
const tagWriteFacts = /* @__PURE__ */ new Map();
|
|
7375
7576
|
let globalAdmission = "admitted";
|
|
7376
7577
|
for (let attempt = 0; attempt < MAX_WRITE_ATTEMPTS && pending.length > 0; attempt += 1) {
|
|
7377
7578
|
const append = (stageScope) => Promise.allSettled(
|
|
@@ -7398,12 +7599,26 @@ var CommitWorker = class {
|
|
|
7398
7599
|
retryIndex: attempt,
|
|
7399
7600
|
attemptId
|
|
7400
7601
|
});
|
|
7401
|
-
const
|
|
7602
|
+
const success = response3.status >= 200 && response3.status < 300;
|
|
7603
|
+
let appendVersion;
|
|
7604
|
+
let appendUpdatedAt;
|
|
7605
|
+
let code;
|
|
7606
|
+
try {
|
|
7607
|
+
const parsed = await response3.clone().json();
|
|
7608
|
+
if (isObject5(parsed)) {
|
|
7609
|
+
if (typeof parsed.version === "number" && Number.isSafeInteger(parsed.version)) appendVersion = parsed.version;
|
|
7610
|
+
if (typeof parsed.updatedAt === "string" && parsed.updatedAt.length > 0) appendUpdatedAt = parsed.updatedAt;
|
|
7611
|
+
if (typeof parsed.code === "string") code = parsed.code;
|
|
7612
|
+
}
|
|
7613
|
+
} catch {
|
|
7614
|
+
}
|
|
7402
7615
|
return {
|
|
7403
7616
|
tag,
|
|
7404
|
-
success
|
|
7617
|
+
success,
|
|
7405
7618
|
globalAdmission: readGlobalAdmission(response3),
|
|
7406
|
-
code
|
|
7619
|
+
code,
|
|
7620
|
+
appendVersion,
|
|
7621
|
+
appendUpdatedAt
|
|
7407
7622
|
};
|
|
7408
7623
|
})
|
|
7409
7624
|
);
|
|
@@ -7415,6 +7630,9 @@ var CommitWorker = class {
|
|
|
7415
7630
|
committedTags.add(tag);
|
|
7416
7631
|
lastFailureCodeByTag.delete(tag);
|
|
7417
7632
|
globalAdmission = mergeGlobalAdmission(globalAdmission, result2.value.globalAdmission);
|
|
7633
|
+
if (result2.value.appendVersion !== void 0 && result2.value.appendUpdatedAt !== void 0) {
|
|
7634
|
+
tagWriteFacts.set(tag, { version: result2.value.appendVersion, updatedAt: result2.value.appendUpdatedAt });
|
|
7635
|
+
}
|
|
7418
7636
|
} else {
|
|
7419
7637
|
lastFailureCodeByTag.set(
|
|
7420
7638
|
tag,
|
|
@@ -7431,7 +7649,8 @@ var CommitWorker = class {
|
|
|
7431
7649
|
registrationUnavailableTags: pending.filter(
|
|
7432
7650
|
(tag) => lastFailureCodeByTag.get(tag) === "partition_registration_unavailable"
|
|
7433
7651
|
),
|
|
7434
|
-
globalAdmission
|
|
7652
|
+
globalAdmission,
|
|
7653
|
+
tagWriteFacts
|
|
7435
7654
|
};
|
|
7436
7655
|
}
|
|
7437
7656
|
/**
|
|
@@ -7464,10 +7683,13 @@ var CommitWorker = class {
|
|
|
7464
7683
|
if (traceScope !== void 0) {
|
|
7465
7684
|
await traceScope.span("S17", {}, async () => void 0);
|
|
7466
7685
|
}
|
|
7467
|
-
await this.cancelReservations(tags, attemptId, fault2, traceScope);
|
|
7686
|
+
const cleanup = await this.cancelReservations(tags, attemptId, fault2, traceScope);
|
|
7468
7687
|
await this.retiredJournalMilestone("S05e", traceScope, 4);
|
|
7469
7688
|
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
|
|
7689
|
+
return {
|
|
7690
|
+
response: responseWithAttempt(response3, attemptId, fault2 !== void 0),
|
|
7691
|
+
resolved: cleanup.failedTags.length === 0
|
|
7692
|
+
};
|
|
7471
7693
|
}
|
|
7472
7694
|
/**
|
|
7473
7695
|
* Persist the local partial-write fact for each tag whose append did not
|
|
@@ -7504,22 +7726,25 @@ var CommitWorker = class {
|
|
|
7504
7726
|
}
|
|
7505
7727
|
}, 500), attemptId, exposeAttempt);
|
|
7506
7728
|
}
|
|
7507
|
-
async successResponse(input, candidates, attemptId, startedAt, fault2, traceScope) {
|
|
7508
|
-
const
|
|
7729
|
+
async successResponse(input, candidates, attemptId, startedAt, fault2, traceScope, writes) {
|
|
7730
|
+
const assemble = async (stageScope) => {
|
|
7509
7731
|
if (fault2 === "tag-state-unavailable") {
|
|
7510
7732
|
throw new Error("Test fault made the committed tag state unavailable");
|
|
7511
7733
|
}
|
|
7512
|
-
const
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7734
|
+
const results = [];
|
|
7735
|
+
for (const [memberIndex, tag] of input.allTags.entries()) {
|
|
7736
|
+
const read = () => {
|
|
7737
|
+
const fact = writes?.tagWriteFacts.get(tag);
|
|
7738
|
+
if (fact === void 0) {
|
|
7739
|
+
throw new Error("Committed tag state was unavailable while preparing the response");
|
|
7740
|
+
}
|
|
7741
|
+
return { tag, version: fact.version, writtenAt: fact.updatedAt };
|
|
7742
|
+
};
|
|
7743
|
+
results.push(stageScope === void 0 ? read() : await stageScope.fork().span("S14", { tag, memberIndex, attemptId }, async () => read()));
|
|
7519
7744
|
}
|
|
7520
|
-
return
|
|
7521
|
-
}
|
|
7522
|
-
const tagWriteResults = traceScope === void 0 ? await
|
|
7745
|
+
return results;
|
|
7746
|
+
};
|
|
7747
|
+
const tagWriteResults = traceScope === void 0 ? await assemble() : await traceScope.span("S13", {}, async (stage) => assemble(stage));
|
|
7523
7748
|
return {
|
|
7524
7749
|
writtenEvents: candidates.map((candidate) => ({
|
|
7525
7750
|
payload: encodeBase64Utf8(candidate.payload),
|
|
@@ -7911,15 +8136,15 @@ function observePostAdmission(observer, message, source, input) {
|
|
|
7911
8136
|
observedAt: Date.now()
|
|
7912
8137
|
});
|
|
7913
8138
|
}
|
|
7914
|
-
function errorText(
|
|
7915
|
-
if (
|
|
7916
|
-
return String(
|
|
8139
|
+
function errorText(error11) {
|
|
8140
|
+
if (error11 instanceof Error && error11.message.length > 0) return error11.message;
|
|
8141
|
+
return String(error11);
|
|
7917
8142
|
}
|
|
7918
8143
|
function deliveryCorrelationId(message, source, supplied) {
|
|
7919
8144
|
return supplied ?? `${source}:${message.serviceId}:${message.eventId}:${message.attemptId}`;
|
|
7920
8145
|
}
|
|
7921
|
-
function defaultFailureClass(
|
|
7922
|
-
const candidate =
|
|
8146
|
+
function defaultFailureClass(error11) {
|
|
8147
|
+
const candidate = error11;
|
|
7923
8148
|
if (candidate.failureClass === "duplicate-race") return "duplicate-race";
|
|
7924
8149
|
if (candidate.failureClass === "nonretryable-definition-poison") return "nonretryable-definition-poison";
|
|
7925
8150
|
if (candidate.failureClass === "retryable-transient") return "retryable-transient";
|
|
@@ -7978,8 +8203,8 @@ async function applyViewHandlers(views, message, storedEvent, arrivedAt, source,
|
|
|
7978
8203
|
result: { id: view.id, status, durationMs: Math.max(0, performance.now() - viewStartedAt) },
|
|
7979
8204
|
failure: void 0
|
|
7980
8205
|
};
|
|
7981
|
-
} catch (
|
|
7982
|
-
const failureClass = view.classifyError?.(
|
|
8206
|
+
} catch (error11) {
|
|
8207
|
+
const failureClass = view.classifyError?.(error11) ?? defaultFailureClass(error11);
|
|
7983
8208
|
observePostAdmission(observer, message, source, {
|
|
7984
8209
|
stage: "unsafe-view-apply",
|
|
7985
8210
|
boundary: "end",
|
|
@@ -7991,10 +8216,10 @@ async function applyViewHandlers(views, message, storedEvent, arrivedAt, source,
|
|
|
7991
8216
|
id: view.id,
|
|
7992
8217
|
status: "failed",
|
|
7993
8218
|
failureClass,
|
|
7994
|
-
error: errorText(
|
|
8219
|
+
error: errorText(error11),
|
|
7995
8220
|
durationMs: Math.max(0, performance.now() - viewStartedAt)
|
|
7996
8221
|
},
|
|
7997
|
-
failure: { phase: "view", class: failureClass, viewId: view.id, error: errorText(
|
|
8222
|
+
failure: { phase: "view", class: failureClass, viewId: view.id, error: errorText(error11) }
|
|
7998
8223
|
};
|
|
7999
8224
|
}
|
|
8000
8225
|
}));
|
|
@@ -8024,15 +8249,15 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8024
8249
|
// settled; this is the durable post-batch observation boundary.
|
|
8025
8250
|
observedAt: Date.now()
|
|
8026
8251
|
});
|
|
8027
|
-
} catch (
|
|
8028
|
-
const failures2 = [{ phase: "recordDelivery", class: "retryable-transient", error: errorText(
|
|
8252
|
+
} catch (error11) {
|
|
8253
|
+
const failures2 = [{ phase: "recordDelivery", class: "retryable-transient", error: errorText(error11) }];
|
|
8029
8254
|
return result(source, message, "stored", arrivedAt, false, [], failures2, options.correlationId, startedAt);
|
|
8030
8255
|
}
|
|
8031
8256
|
if (outcome.outcome !== "stored") {
|
|
8032
8257
|
try {
|
|
8033
8258
|
await store.projectDeliveryIncidents?.(message.serviceId);
|
|
8034
|
-
} catch (
|
|
8035
|
-
const failures2 = [{ phase: "detector", class: "retryable-transient", error: errorText(
|
|
8259
|
+
} catch (error11) {
|
|
8260
|
+
const failures2 = [{ phase: "detector", class: "retryable-transient", error: errorText(error11) }];
|
|
8036
8261
|
return result(source, message, outcome.outcome, arrivedAt, false, [], failures2, options.correlationId, startedAt);
|
|
8037
8262
|
}
|
|
8038
8263
|
return result(source, message, outcome.outcome, arrivedAt, false, [], [], options.correlationId, startedAt);
|
|
@@ -8061,20 +8286,20 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8061
8286
|
boundary: "end",
|
|
8062
8287
|
outcome: "available"
|
|
8063
8288
|
});
|
|
8064
|
-
} catch (
|
|
8289
|
+
} catch (error11) {
|
|
8065
8290
|
observePostAdmission(options.durableHopObserver, message, source, {
|
|
8066
8291
|
stage: "post-record-delivery-global-receipt-readback",
|
|
8067
8292
|
boundary: "end",
|
|
8068
8293
|
outcome: "error"
|
|
8069
8294
|
});
|
|
8070
|
-
failures.push({ phase: "recordDelivery", class: "retryable-transient", error: errorText(
|
|
8295
|
+
failures.push({ phase: "recordDelivery", class: "retryable-transient", error: errorText(error11) });
|
|
8071
8296
|
return result(source, message, "stored", arrivedAt, false, [], failures, options.correlationId, startedAt);
|
|
8072
8297
|
}
|
|
8073
8298
|
if (options.afterGlobalReceipt !== void 0) {
|
|
8074
8299
|
try {
|
|
8075
8300
|
await options.afterGlobalReceipt({ message, receipt, arrivedAt, source });
|
|
8076
|
-
} catch (
|
|
8077
|
-
failures.push({ phase: "drain", class: "retryable-transient", error: errorText(
|
|
8301
|
+
} catch (error11) {
|
|
8302
|
+
failures.push({ phase: "drain", class: "retryable-transient", error: errorText(error11) });
|
|
8078
8303
|
}
|
|
8079
8304
|
}
|
|
8080
8305
|
}
|
|
@@ -8091,8 +8316,8 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8091
8316
|
if (options.beforeViews !== void 0) {
|
|
8092
8317
|
try {
|
|
8093
8318
|
await options.beforeViews({ message, event: storedEvent, arrivedAt, source });
|
|
8094
|
-
} catch (
|
|
8095
|
-
failures.push({ phase: "completeness", class: "retryable-transient", error: errorText(
|
|
8319
|
+
} catch (error11) {
|
|
8320
|
+
failures.push({ phase: "completeness", class: "retryable-transient", error: errorText(error11) });
|
|
8096
8321
|
return result(source, message, "stored", arrivedAt, false, viewResults, failures, options.correlationId, startedAt);
|
|
8097
8322
|
}
|
|
8098
8323
|
}
|
|
@@ -8112,14 +8337,14 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8112
8337
|
boundary: "end",
|
|
8113
8338
|
outcome: "applied"
|
|
8114
8339
|
});
|
|
8115
|
-
} catch (
|
|
8340
|
+
} catch (error11) {
|
|
8116
8341
|
observePostAdmission(options.durableHopObserver, message, source, {
|
|
8117
8342
|
stage: "detector",
|
|
8118
8343
|
boundary: "end",
|
|
8119
8344
|
outcome: "error"
|
|
8120
8345
|
});
|
|
8121
8346
|
try {
|
|
8122
|
-
await options.onDetectorFailure?.({ message, arrivedAt, source, error:
|
|
8347
|
+
await options.onDetectorFailure?.({ message, arrivedAt, source, error: error11 });
|
|
8123
8348
|
} catch (healthError) {
|
|
8124
8349
|
failures.push({
|
|
8125
8350
|
phase: "detector",
|
|
@@ -8127,7 +8352,7 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8127
8352
|
error: `detector_health_record_failed:${errorText(healthError)}`
|
|
8128
8353
|
});
|
|
8129
8354
|
}
|
|
8130
|
-
failures.push({ phase: "detector", class: "retryable-transient", error: errorText(
|
|
8355
|
+
failures.push({ phase: "detector", class: "retryable-transient", error: errorText(error11) });
|
|
8131
8356
|
return result(source, message, "stored", arrivedAt, false, [], failures, options.correlationId, startedAt);
|
|
8132
8357
|
}
|
|
8133
8358
|
const views = gatedViews;
|
|
@@ -8144,16 +8369,16 @@ async function processDeliveryCore(message, source, env, options = {}) {
|
|
|
8144
8369
|
if (options.onStored !== void 0) {
|
|
8145
8370
|
try {
|
|
8146
8371
|
await options.onStored({ message, event: storedEvent, arrivedAt, source });
|
|
8147
|
-
} catch (
|
|
8148
|
-
failures.push({ phase: "view", class: "retryable-transient", viewId: "legacy-onStored", error: errorText(
|
|
8372
|
+
} catch (error11) {
|
|
8373
|
+
failures.push({ phase: "view", class: "retryable-transient", viewId: "legacy-onStored", error: errorText(error11) });
|
|
8149
8374
|
}
|
|
8150
8375
|
}
|
|
8151
8376
|
const preliminary = result(source, message, "stored", arrivedAt, detectorApplied, viewResults, failures, options.correlationId, startedAt);
|
|
8152
8377
|
if (options.afterDelivery !== void 0 && failures.every((failure3) => failure3.class === "duplicate-race")) {
|
|
8153
8378
|
try {
|
|
8154
8379
|
await options.afterDelivery({ message, event: storedEvent, arrivedAt, source, result: preliminary });
|
|
8155
|
-
} catch (
|
|
8156
|
-
failures.push({ phase: "drain", class: "retryable-transient", error: errorText(
|
|
8380
|
+
} catch (error11) {
|
|
8381
|
+
failures.push({ phase: "drain", class: "retryable-transient", error: errorText(error11) });
|
|
8157
8382
|
}
|
|
8158
8383
|
}
|
|
8159
8384
|
return result(source, message, "stored", arrivedAt, detectorApplied, viewResults, failures, options.correlationId, startedAt);
|
|
@@ -8175,8 +8400,8 @@ function asNumber(value, name) {
|
|
|
8175
8400
|
if (!Number.isSafeInteger(number) || number < 0) throw new Error(`G44 ${name} must be a non-negative safe integer`);
|
|
8176
8401
|
return number;
|
|
8177
8402
|
}
|
|
8178
|
-
function errorText2(
|
|
8179
|
-
return
|
|
8403
|
+
function errorText2(error11) {
|
|
8404
|
+
return error11 instanceof Error && error11.message.length > 0 ? error11.message : String(error11);
|
|
8180
8405
|
}
|
|
8181
8406
|
function settledFrontierFromCursor(cursorJson) {
|
|
8182
8407
|
const cursor = settledCursorFromJson(cursorJson);
|
|
@@ -8280,8 +8505,8 @@ var GlobalCompletenessReconciler = class {
|
|
|
8280
8505
|
let snapshots;
|
|
8281
8506
|
try {
|
|
8282
8507
|
snapshots = await this.snapshotPartitions(serviceId);
|
|
8283
|
-
} catch (
|
|
8284
|
-
const message = errorText2(
|
|
8508
|
+
} catch (error11) {
|
|
8509
|
+
const message = errorText2(error11);
|
|
8285
8510
|
await this.appendFinding(serviceId, {
|
|
8286
8511
|
identity: scannerFailureIdentity(serviceId),
|
|
8287
8512
|
type: "GLOBAL_ARRAY_SCANNER_FAILURE",
|
|
@@ -8323,7 +8548,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8323
8548
|
let joined;
|
|
8324
8549
|
try {
|
|
8325
8550
|
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 (
|
|
8551
|
+
} catch (error11) {
|
|
8327
8552
|
await this.appendFinding(snapshot.serviceId, {
|
|
8328
8553
|
identity: `GLOBAL_RECEIPT_UNAVAILABLE|${snapshot.serviceId}|${snapshot.tag}|${obligation.obligationSequence}|${obligation.eventId}|${obligation.eventDigest}`,
|
|
8329
8554
|
type: "GLOBAL_ARRAY_RECEIPT_UNAVAILABLE",
|
|
@@ -8332,7 +8557,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8332
8557
|
eventId: obligation.eventId,
|
|
8333
8558
|
eventDigest: obligation.eventDigest
|
|
8334
8559
|
}, nowMs).catch(() => void 0);
|
|
8335
|
-
throw
|
|
8560
|
+
throw error11;
|
|
8336
8561
|
}
|
|
8337
8562
|
if (!joined) {
|
|
8338
8563
|
await this.appendObligationFinding(snapshot.serviceId, snapshot.tag, obligation, nowMs);
|
|
@@ -8351,8 +8576,8 @@ var GlobalCompletenessReconciler = class {
|
|
|
8351
8576
|
if (afterSequence !== snapshot.upperBoundSequence) {
|
|
8352
8577
|
throw new Error(`source_partition_range_incomplete:${snapshot.tag}`);
|
|
8353
8578
|
}
|
|
8354
|
-
} catch (
|
|
8355
|
-
if (errorText2(
|
|
8579
|
+
} catch (error11) {
|
|
8580
|
+
if (errorText2(error11).startsWith("source_")) {
|
|
8356
8581
|
await this.appendFinding(snapshot.serviceId, {
|
|
8357
8582
|
identity: sourceUnavailableIdentity(snapshot.serviceId, snapshot.tag),
|
|
8358
8583
|
type: "GLOBAL_ARRAY_SOURCE_PARTITION_UNAVAILABLE",
|
|
@@ -8362,7 +8587,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8362
8587
|
eventDigest: null
|
|
8363
8588
|
}, nowMs).catch(() => void 0);
|
|
8364
8589
|
}
|
|
8365
|
-
throw
|
|
8590
|
+
throw error11;
|
|
8366
8591
|
}
|
|
8367
8592
|
}
|
|
8368
8593
|
const endSnapshots = await this.snapshotPartitions(serviceId);
|
|
@@ -8381,8 +8606,8 @@ var GlobalCompletenessReconciler = class {
|
|
|
8381
8606
|
}
|
|
8382
8607
|
await this.writeHealth(serviceId, "HEALTHY", JSON.stringify(cursor), nowMs, null, nowMs);
|
|
8383
8608
|
return { kind: "FULL", partitions: snapshots, scannedObligations: scanned };
|
|
8384
|
-
} catch (
|
|
8385
|
-
const message = errorText2(
|
|
8609
|
+
} catch (error11) {
|
|
8610
|
+
const message = errorText2(error11);
|
|
8386
8611
|
const health = message.startsWith("source_") ? "UNKNOWN" : "FAILED";
|
|
8387
8612
|
if (health === "FAILED") {
|
|
8388
8613
|
await this.appendFinding(serviceId, {
|
|
@@ -8524,8 +8749,8 @@ var GlobalCompletenessReconciler = class {
|
|
|
8524
8749
|
* without applying views. It shares the scanner's single health/finding
|
|
8525
8750
|
* authority rather than creating a second incident workflow.
|
|
8526
8751
|
*/
|
|
8527
|
-
async recordDetectorFailure(serviceId,
|
|
8528
|
-
const message = `detector_failure:${errorText2(
|
|
8752
|
+
async recordDetectorFailure(serviceId, error11, nowMs) {
|
|
8753
|
+
const message = `detector_failure:${errorText2(error11).slice(0, 256)}`;
|
|
8529
8754
|
await this.appendFinding(serviceId, {
|
|
8530
8755
|
identity: `GLOBAL_DETECTOR_FAILURE|${serviceId}`,
|
|
8531
8756
|
type: "GLOBAL_ARRAY_DETECTOR_FAILURE",
|
|
@@ -8664,7 +8889,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8664
8889
|
nowMs
|
|
8665
8890
|
).run();
|
|
8666
8891
|
}
|
|
8667
|
-
async writeHealth(serviceId, status, cursor, lastFullScanAt,
|
|
8892
|
+
async writeHealth(serviceId, status, cursor, lastFullScanAt, error11, nowMs) {
|
|
8668
8893
|
await this.database.prepare(
|
|
8669
8894
|
`INSERT INTO serialized_dcb_completeness_scanner_health
|
|
8670
8895
|
(service_id, scanner_version, status, cursor_json, last_full_scan_at, last_error, updated_at)
|
|
@@ -8676,7 +8901,7 @@ var GlobalCompletenessReconciler = class {
|
|
|
8676
8901
|
last_full_scan_at = COALESCE(excluded.last_full_scan_at, serialized_dcb_completeness_scanner_health.last_full_scan_at),
|
|
8677
8902
|
last_error = excluded.last_error,
|
|
8678
8903
|
updated_at = excluded.updated_at`
|
|
8679
|
-
).bind(serviceId, this.scannerVersion, status, cursor, lastFullScanAt,
|
|
8904
|
+
).bind(serviceId, this.scannerVersion, status, cursor, lastFullScanAt, error11, nowMs).run();
|
|
8680
8905
|
}
|
|
8681
8906
|
};
|
|
8682
8907
|
function globalReceiptAcknowledgement(message, receivedAt) {
|
|
@@ -8768,7 +8993,7 @@ function sourceAcknowledgementOptions(env, options) {
|
|
|
8768
8993
|
if (decision.kind !== "SETTLED") {
|
|
8769
8994
|
throw new Error(`global_completeness_${decision.kind}:${decision.health.status}`);
|
|
8770
8995
|
}
|
|
8771
|
-
} catch (
|
|
8996
|
+
} catch (error11) {
|
|
8772
8997
|
if (!completed) {
|
|
8773
8998
|
observeG60PostAdmission(options.durableHopObserver, {
|
|
8774
8999
|
stage: "completeness-coverage",
|
|
@@ -8783,7 +9008,7 @@ function sourceAcknowledgementOptions(env, options) {
|
|
|
8783
9008
|
observedAt: Date.now()
|
|
8784
9009
|
});
|
|
8785
9010
|
}
|
|
8786
|
-
throw
|
|
9011
|
+
throw error11;
|
|
8787
9012
|
}
|
|
8788
9013
|
} : options.beforeViews;
|
|
8789
9014
|
const observedSourceAcknowledgement = sourceAcknowledgement === void 0 ? void 0 : async (input) => {
|
|
@@ -8791,14 +9016,14 @@ function sourceAcknowledgementOptions(env, options) {
|
|
|
8791
9016
|
try {
|
|
8792
9017
|
await sourceAcknowledgement(input);
|
|
8793
9018
|
observeSourceSubstep(options.durableHopObserver, { ...input, boundary: "end", outcome: "acknowledged" });
|
|
8794
|
-
} catch (
|
|
9019
|
+
} catch (error11) {
|
|
8795
9020
|
observeSourceSubstep(options.durableHopObserver, { ...input, boundary: "end", outcome: "error" });
|
|
8796
|
-
throw
|
|
9021
|
+
throw error11;
|
|
8797
9022
|
}
|
|
8798
9023
|
};
|
|
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,
|
|
9024
|
+
const detectorFailure = hasG44Authority ? async ({ message, arrivedAt, error: error11, source }) => {
|
|
9025
|
+
await options.onDetectorFailure?.({ message, arrivedAt, error: error11, source });
|
|
9026
|
+
await new GlobalCompletenessReconciler(env.D1, env.TAG).recordDetectorFailure(message.serviceId, error11, arrivedAt);
|
|
8802
9027
|
} : options.onDetectorFailure;
|
|
8803
9028
|
return {
|
|
8804
9029
|
...options,
|
|
@@ -8813,9 +9038,9 @@ function sharedStore(env, provider) {
|
|
|
8813
9038
|
async function admitBootstrapRoute(env, serviceId, route) {
|
|
8814
9039
|
if (env.BOOTSTRAP === void 0) {
|
|
8815
9040
|
if (env.G38_DOORBELL_DELIVERY_ROLE === "receiver") {
|
|
8816
|
-
const
|
|
8817
|
-
|
|
8818
|
-
throw
|
|
9041
|
+
const error11 = new Error(`bootstrap_route_binding_missing:${route}`);
|
|
9042
|
+
error11.name = "BootstrapRouteBindingMissingError";
|
|
9043
|
+
throw error11;
|
|
8819
9044
|
}
|
|
8820
9045
|
return;
|
|
8821
9046
|
}
|
|
@@ -8904,10 +9129,10 @@ async function handleDownstreamQueue(batch, env, options = {}) {
|
|
|
8904
9129
|
if (outcome.outcome === "stored" && !outcome.failures.some((failure3) => failure3.phase === "recordDelivery")) {
|
|
8905
9130
|
try {
|
|
8906
9131
|
options.afterStoredQueueDelivery?.({ message: queued.body, result: outcome });
|
|
8907
|
-
} catch (
|
|
9132
|
+
} catch (error11) {
|
|
8908
9133
|
console.warn("safe_lane_kick", {
|
|
8909
9134
|
status: "failed-to-schedule",
|
|
8910
|
-
error:
|
|
9135
|
+
error: error11 instanceof Error ? error11.message : String(error11),
|
|
8911
9136
|
serviceId: queued.body.serviceId,
|
|
8912
9137
|
eventId: queued.body.eventId,
|
|
8913
9138
|
attemptId: queued.body.attemptId
|
|
@@ -8929,6 +9154,36 @@ async function stabilizeDownstream(env, options = {}, serviceId, serviceIdentity
|
|
|
8929
9154
|
});
|
|
8930
9155
|
}
|
|
8931
9156
|
|
|
9157
|
+
// src/scope/ControlRouteScope.ts
|
|
9158
|
+
function response2(status, code, error11) {
|
|
9159
|
+
return new Response(JSON.stringify({ code, error: error11 }), {
|
|
9160
|
+
status,
|
|
9161
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
9162
|
+
});
|
|
9163
|
+
}
|
|
9164
|
+
function scopeIdentityMissingResponse() {
|
|
9165
|
+
return response2(503, "scope.identity_missing", "A deployment service identity is required for this scope");
|
|
9166
|
+
}
|
|
9167
|
+
function enforceControlRouteScope(input) {
|
|
9168
|
+
let actual;
|
|
9169
|
+
try {
|
|
9170
|
+
actual = input.provider.forRequest(input.request, input.requestOptions).serviceId;
|
|
9171
|
+
} catch (caught) {
|
|
9172
|
+
if (caught instanceof ServiceIdentityMissingError) {
|
|
9173
|
+
console.error({ schema: "sdt.scope/v1", code: "scope.identity_missing", route: input.route });
|
|
9174
|
+
return { response: scopeIdentityMissingResponse() };
|
|
9175
|
+
}
|
|
9176
|
+
throw caught;
|
|
9177
|
+
}
|
|
9178
|
+
if (input.pathServiceId !== actual) {
|
|
9179
|
+
console.warn({ schema: "sdt.scope/v1", code: "scope.mismatch", route: input.route });
|
|
9180
|
+
return {
|
|
9181
|
+
response: response2(403, "scope.mismatch", "The path service identity is not authorized for this deployment")
|
|
9182
|
+
};
|
|
9183
|
+
}
|
|
9184
|
+
return { serviceId: actual };
|
|
9185
|
+
}
|
|
9186
|
+
|
|
8932
9187
|
// src/downstream/OutboxDrain.ts
|
|
8933
9188
|
function json5(body, status = 200) {
|
|
8934
9189
|
return new Response(JSON.stringify(body), {
|
|
@@ -9025,15 +9280,32 @@ async function handleOutboxDrainRequest(request, env, options = {}) {
|
|
|
9025
9280
|
if (!isDrainRequest(body)) {
|
|
9026
9281
|
return json5({ error: "serviceId and unique non-empty tags are required", code: "invalid_outbox_drain_request" }, 400);
|
|
9027
9282
|
}
|
|
9283
|
+
const requestedServiceId = body.serviceId;
|
|
9284
|
+
const tags = body.tags;
|
|
9285
|
+
const provider = options.serviceIdentityProvider ?? envServiceIdentity(env);
|
|
9286
|
+
let authority;
|
|
9287
|
+
try {
|
|
9288
|
+
authority = provider.forRequest(request).serviceId;
|
|
9289
|
+
} catch (caught) {
|
|
9290
|
+
if (caught instanceof ServiceIdentityMissingError) return scopeIdentityMissingResponse();
|
|
9291
|
+
throw caught;
|
|
9292
|
+
}
|
|
9293
|
+
if (requestedServiceId !== authority) {
|
|
9294
|
+
console.warn({ schema: "sdt.scope/v1", code: "scope.mismatch", route: "outbox-drain" });
|
|
9295
|
+
return json5({
|
|
9296
|
+
code: "scope.mismatch",
|
|
9297
|
+
error: "The request service identity is not authorized for this deployment"
|
|
9298
|
+
}, 403);
|
|
9299
|
+
}
|
|
9028
9300
|
try {
|
|
9029
9301
|
const results = [];
|
|
9030
|
-
for (const tag of
|
|
9031
|
-
results.push(await drainTagOutbox({ serviceId:
|
|
9302
|
+
for (const tag of tags) {
|
|
9303
|
+
results.push(await drainTagOutbox({ serviceId: authority, tag }, env, systemPipelineClock, options));
|
|
9032
9304
|
}
|
|
9033
9305
|
return json5({ results });
|
|
9034
|
-
} catch (
|
|
9306
|
+
} catch (error11) {
|
|
9035
9307
|
return json5({
|
|
9036
|
-
error:
|
|
9308
|
+
error: error11 instanceof Error ? error11.message : "Outbox drain failed",
|
|
9037
9309
|
code: "outbox_drain_failed"
|
|
9038
9310
|
}, 503);
|
|
9039
9311
|
}
|
|
@@ -9783,9 +10055,9 @@ function isNonEmptyString7(value) {
|
|
|
9783
10055
|
return typeof value === "string" && value.length > 0;
|
|
9784
10056
|
}
|
|
9785
10057
|
function base64Json(value) {
|
|
9786
|
-
const
|
|
10058
|
+
const bytes2 = new TextEncoder().encode(JSON.stringify(value));
|
|
9787
10059
|
let binary = "";
|
|
9788
|
-
for (const byte of
|
|
10060
|
+
for (const byte of bytes2) {
|
|
9789
10061
|
binary += String.fromCharCode(byte);
|
|
9790
10062
|
}
|
|
9791
10063
|
return btoa(binary);
|
|
@@ -9963,9 +10235,9 @@ function decodeStoredJson(value) {
|
|
|
9963
10235
|
}
|
|
9964
10236
|
}
|
|
9965
10237
|
function jsonBytes(value) {
|
|
9966
|
-
const
|
|
10238
|
+
const bytes2 = new TextEncoder().encode(JSON.stringify(value));
|
|
9967
10239
|
let binary = "";
|
|
9968
|
-
for (const byte of
|
|
10240
|
+
for (const byte of bytes2) binary += String.fromCharCode(byte);
|
|
9969
10241
|
return btoa(binary);
|
|
9970
10242
|
}
|
|
9971
10243
|
function isRuntimeBridgedProjector(definition) {
|
|
@@ -10158,8 +10430,8 @@ function asNumber2(value, name) {
|
|
|
10158
10430
|
if (!Number.isSafeInteger(number)) throw new Error(`D1 ${name} was not a safe integer`);
|
|
10159
10431
|
return number;
|
|
10160
10432
|
}
|
|
10161
|
-
function errorText3(
|
|
10162
|
-
return (
|
|
10433
|
+
function errorText3(error11) {
|
|
10434
|
+
return (error11 instanceof Error ? error11.message : String(error11)).slice(0, 256);
|
|
10163
10435
|
}
|
|
10164
10436
|
function sortedUnique2(values) {
|
|
10165
10437
|
return [...new Set(values)].sort((left, right) => binaryCompare(left, right));
|
|
@@ -10346,8 +10618,8 @@ var D1EventStore = class {
|
|
|
10346
10618
|
let failure3;
|
|
10347
10619
|
try {
|
|
10348
10620
|
result2 = await this.recordDeliveryCore(message, arrivedAt, deliverySource, mutationAttemptId);
|
|
10349
|
-
} catch (
|
|
10350
|
-
failure3 = { error:
|
|
10621
|
+
} catch (error11) {
|
|
10622
|
+
failure3 = { error: error11 };
|
|
10351
10623
|
}
|
|
10352
10624
|
const retryReason = failure3 === void 0 ? null : errorText3(failure3.error);
|
|
10353
10625
|
const suppressDiagnostic = failure3?.error instanceof D1IdentityConflictError && failure3.error.beforeMutation;
|
|
@@ -10918,8 +11190,8 @@ var D1EventStore = class {
|
|
|
10918
11190
|
if (replay === null || replay === void 0) return finish({ kind: "miss" });
|
|
10919
11191
|
const event = await this.eventById(serviceId, asString3(replay.event_id, "replay.event_id"));
|
|
10920
11192
|
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(
|
|
11193
|
+
} catch (error11) {
|
|
11194
|
+
return finish({ kind: "unknown", reason: errorText3(error11) });
|
|
10923
11195
|
}
|
|
10924
11196
|
}
|
|
10925
11197
|
/**
|
|
@@ -11394,8 +11666,8 @@ var D1EventStore = class {
|
|
|
11394
11666
|
try {
|
|
11395
11667
|
try {
|
|
11396
11668
|
await this.options.beforeG69AdmissionAttempt?.();
|
|
11397
|
-
} catch (
|
|
11398
|
-
await appendFailedDiagnostic(errorText3(
|
|
11669
|
+
} catch (error11) {
|
|
11670
|
+
await appendFailedDiagnostic(errorText3(error11));
|
|
11399
11671
|
return;
|
|
11400
11672
|
}
|
|
11401
11673
|
const afterObservedAt = Date.now();
|
|
@@ -11469,15 +11741,15 @@ function integer(row, key) {
|
|
|
11469
11741
|
function operationId() {
|
|
11470
11742
|
return `mv-unsafe-${crypto.randomUUID()}`;
|
|
11471
11743
|
}
|
|
11472
|
-
function errorFrom(
|
|
11473
|
-
const value = String(
|
|
11744
|
+
function errorFrom(error11) {
|
|
11745
|
+
const value = String(error11);
|
|
11474
11746
|
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:
|
|
11747
|
+
return new UnsafeWindowMaterializedViewError("UNSAFE_DUPLICATE_RACE", false, "Concurrent unsafe receipt was committed by another delivery", { cause: error11 });
|
|
11476
11748
|
}
|
|
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:
|
|
11749
|
+
if (value.includes("unsafe_row_cas")) return new UnsafeWindowMaterializedViewError("UNSAFE_ROW_CAS_MISMATCH", true, "Unsafe row version changed before atomic apply", { cause: error11 });
|
|
11750
|
+
if (value.includes("unsafe_suid_contradiction")) return new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Unsafe same-SUID payload contradiction", { cause: error11 });
|
|
11751
|
+
if (value.includes("unsafe_safe_ahead")) return new UnsafeWindowMaterializedViewError("UNSAFE_SAFE_AHEAD", false, "Safe head already covers unsafe event", { cause: error11 });
|
|
11752
|
+
if (value.includes("unsafe_behind_frontier")) return new UnsafeWindowMaterializedViewError("UNSAFE_BEHIND_FRONTIER", false, "Unsafe reads are fail-closed pending rebuild", { cause: error11 });
|
|
11481
11753
|
return void 0;
|
|
11482
11754
|
}
|
|
11483
11755
|
var UnsafeWindowMaterializedViewStore = class {
|
|
@@ -11610,8 +11882,8 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
11610
11882
|
await this.options.beforeApplyBatch?.(input, statements);
|
|
11611
11883
|
try {
|
|
11612
11884
|
await this.database.batch(statements);
|
|
11613
|
-
} catch (
|
|
11614
|
-
const text = String(
|
|
11885
|
+
} catch (error11) {
|
|
11886
|
+
const text = String(error11);
|
|
11615
11887
|
const guardFailure = text.includes("mv_atomic_guards") || text.includes("checkpoint_match") || text.includes("mv_unsafe_receipts") && (text.includes("UNIQUE") || text.includes("constraint"));
|
|
11616
11888
|
if (guardFailure) {
|
|
11617
11889
|
const receipt = await this.database.prepare(
|
|
@@ -11619,16 +11891,16 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
11619
11891
|
).bind(input.serviceId, input.viewId, input.eventId).first();
|
|
11620
11892
|
if (receipt !== null && receipt !== void 0) {
|
|
11621
11893
|
if (string2(receipt, "suid") !== input.suid) {
|
|
11622
|
-
throw new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Duplicate event identity has a contradictory SUID", { cause:
|
|
11894
|
+
throw new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Duplicate event identity has a contradictory SUID", { cause: error11 });
|
|
11623
11895
|
}
|
|
11624
11896
|
if (this.options.beforeApplyBatch !== void 0) {
|
|
11625
|
-
throw new UnsafeWindowMaterializedViewError("UNSAFE_DUPLICATE_RACE", false, "Concurrent unsafe receipt was committed by another delivery", { cause:
|
|
11897
|
+
throw new UnsafeWindowMaterializedViewError("UNSAFE_DUPLICATE_RACE", false, "Concurrent unsafe receipt was committed by another delivery", { cause: error11 });
|
|
11626
11898
|
}
|
|
11627
11899
|
return { outcome: "no-change", duplicate: true };
|
|
11628
11900
|
}
|
|
11629
11901
|
return this.apply(input, true);
|
|
11630
11902
|
}
|
|
11631
|
-
throw
|
|
11903
|
+
throw error11;
|
|
11632
11904
|
}
|
|
11633
11905
|
const result2 = { outcome: "applied", duplicate: false };
|
|
11634
11906
|
await this.options.afterApplyBatch?.(input, result2);
|
|
@@ -11761,13 +12033,13 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
11761
12033
|
await this.options.beforeApplyBatch?.(input, statements);
|
|
11762
12034
|
try {
|
|
11763
12035
|
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(
|
|
12036
|
+
} catch (error11) {
|
|
12037
|
+
if (sameSuidContradiction) throw new UnsafeWindowMaterializedViewError("UNSAFE_SUID_CONTRADICTION", false, "Unsafe same-SUID payload contradiction", { cause: error11 });
|
|
12038
|
+
if (rowCasMismatch) throw new UnsafeWindowMaterializedViewError("UNSAFE_ROW_CAS_MISMATCH", true, "Unsafe row version changed before atomic apply", { cause: error11 });
|
|
12039
|
+
if (safeAhead) throw new UnsafeWindowMaterializedViewError("UNSAFE_SAFE_AHEAD", false, "Safe head already covers unsafe event", { cause: error11 });
|
|
12040
|
+
const typed = errorFrom(error11);
|
|
11769
12041
|
if (typed !== void 0) throw typed;
|
|
11770
|
-
throw
|
|
12042
|
+
throw error11;
|
|
11771
12043
|
}
|
|
11772
12044
|
const result2 = { outcome, duplicate: false };
|
|
11773
12045
|
await this.options.afterApplyBatch?.(input, result2);
|
|
@@ -11834,8 +12106,8 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
11834
12106
|
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
12107
|
this.database.prepare("DELETE FROM mv_atomic_guards WHERE operation_id = ?").bind(operation2)
|
|
11836
12108
|
]);
|
|
11837
|
-
} catch (
|
|
11838
|
-
throw new UnsafeWindowMaterializedViewError("UNSAFE_KICK_LEASE_HELD", true, "Unsafe kick is held by another lease owner", { cause:
|
|
12109
|
+
} catch (error11) {
|
|
12110
|
+
throw new UnsafeWindowMaterializedViewError("UNSAFE_KICK_LEASE_HELD", true, "Unsafe kick is held by another lease owner", { cause: error11 });
|
|
11839
12111
|
}
|
|
11840
12112
|
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
12113
|
return row === null || row === void 0 ? void 0 : { targetSuid: string2(row, "target_suid"), dirty: integer(row, "dirty") === 1 };
|
|
@@ -11909,9 +12181,9 @@ var UnsafeWindowMaterializedViewStore = class {
|
|
|
11909
12181
|
try {
|
|
11910
12182
|
await this.database.batch(statements);
|
|
11911
12183
|
return true;
|
|
11912
|
-
} catch (
|
|
11913
|
-
if (String(
|
|
11914
|
-
throw
|
|
12184
|
+
} catch (error11) {
|
|
12185
|
+
if (String(error11).includes("mv_atomic_guards") || String(error11).includes("checkpoint_match")) return false;
|
|
12186
|
+
throw error11;
|
|
11915
12187
|
}
|
|
11916
12188
|
}
|
|
11917
12189
|
/**
|
|
@@ -12077,12 +12349,12 @@ function indexValue(row, type) {
|
|
|
12077
12349
|
function operationId2() {
|
|
12078
12350
|
return `mv-${crypto.randomUUID()}`;
|
|
12079
12351
|
}
|
|
12080
|
-
function isCasFailure(
|
|
12081
|
-
const message = String(
|
|
12352
|
+
function isCasFailure(error11) {
|
|
12353
|
+
const message = String(error11);
|
|
12082
12354
|
return message.includes("mv_atomic_guards.checkpoint_match") || message.includes("MV_CAS_MISMATCH");
|
|
12083
12355
|
}
|
|
12084
|
-
function isPatchFailure(
|
|
12085
|
-
return String(
|
|
12356
|
+
function isPatchFailure(error11) {
|
|
12357
|
+
return String(error11).includes("CHECK constraint failed: checkpoint_match");
|
|
12086
12358
|
}
|
|
12087
12359
|
var D1MaterializedViewStore = class {
|
|
12088
12360
|
constructor(database) {
|
|
@@ -12118,8 +12390,8 @@ var D1MaterializedViewStore = class {
|
|
|
12118
12390
|
VALUES (?, ?, ?, ?)`
|
|
12119
12391
|
).bind(input.serviceId, input.viewId, generation, input.updatedAt)
|
|
12120
12392
|
]);
|
|
12121
|
-
} catch (
|
|
12122
|
-
throw new MaterializedViewStoreError("create-active", "MV_INSTANCE_EXISTS", `Active materialized view already exists: ${String(
|
|
12393
|
+
} catch (error11) {
|
|
12394
|
+
throw new MaterializedViewStoreError("create-active", "MV_INSTANCE_EXISTS", `Active materialized view already exists: ${String(error11)}`, { cause: error11 });
|
|
12123
12395
|
}
|
|
12124
12396
|
return this.readInstance(input.serviceId, input.viewId, generation);
|
|
12125
12397
|
}
|
|
@@ -12140,8 +12412,8 @@ var D1MaterializedViewStore = class {
|
|
|
12140
12412
|
input.definitionVersion,
|
|
12141
12413
|
input.updatedAt
|
|
12142
12414
|
).run();
|
|
12143
|
-
} catch (
|
|
12144
|
-
throw new MaterializedViewStoreError("create-candidate", "MV_INSTANCE_EXISTS", `Candidate generation already exists: ${String(
|
|
12415
|
+
} catch (error11) {
|
|
12416
|
+
throw new MaterializedViewStoreError("create-candidate", "MV_INSTANCE_EXISTS", `Candidate generation already exists: ${String(error11)}`, { cause: error11 });
|
|
12145
12417
|
}
|
|
12146
12418
|
return this.readInstance(input.serviceId, input.viewId, input.generation);
|
|
12147
12419
|
}
|
|
@@ -12279,10 +12551,10 @@ var D1MaterializedViewStore = class {
|
|
|
12279
12551
|
*/
|
|
12280
12552
|
async readListPage(serviceId, viewId, options = {}) {
|
|
12281
12553
|
this.ready("initialize");
|
|
12282
|
-
const
|
|
12283
|
-
if (
|
|
12284
|
-
const selected = options.generation ??
|
|
12285
|
-
const selectedInstance = selected ===
|
|
12554
|
+
const active2 = await this.readActive(serviceId, viewId);
|
|
12555
|
+
if (active2 === void 0) return { rows: [], totalCount: 0, readHead: "" };
|
|
12556
|
+
const selected = options.generation ?? active2.generation;
|
|
12557
|
+
const selectedInstance = selected === active2.generation ? active2 : await this.readInstance(serviceId, viewId, selected);
|
|
12286
12558
|
if (selectedInstance === void 0) {
|
|
12287
12559
|
throw new MaterializedViewStoreError("initialize", "MV_INSTANCE_MISSING", "Requested materialized-view generation is unavailable");
|
|
12288
12560
|
}
|
|
@@ -12799,10 +13071,10 @@ var D1MaterializedViewStore = class {
|
|
|
12799
13071
|
).bind(operation2, `${operation2}:%`));
|
|
12800
13072
|
try {
|
|
12801
13073
|
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(
|
|
13074
|
+
} catch (error11) {
|
|
13075
|
+
if (isPatchFailure(error11)) throw new MaterializedViewPatchError();
|
|
13076
|
+
if (isCasFailure(error11)) throw new MaterializedViewCasError();
|
|
13077
|
+
throw new MaterializedViewStoreError("apply", "MV_STORE_OPERATION_FAILED", `Materialized-view atomic apply failed: ${String(error11)}`, { cause: error11 });
|
|
12806
13078
|
}
|
|
12807
13079
|
const instance = await this.readInstance(input.serviceId, input.viewId, input.generation);
|
|
12808
13080
|
if (instance === void 0) throw new MaterializedViewStoreError("apply", "MV_INSTANCE_MISSING", "Materialized-view instance disappeared after apply");
|
|
@@ -12907,13 +13179,13 @@ var D1MaterializedViewStore = class {
|
|
|
12907
13179
|
];
|
|
12908
13180
|
try {
|
|
12909
13181
|
await this.database.batch(statements);
|
|
12910
|
-
} catch (
|
|
12911
|
-
if (isCasFailure(
|
|
12912
|
-
throw new MaterializedViewStoreError("promote", "MV_STORE_OPERATION_FAILED", `Materialized-view promotion failed: ${String(
|
|
13182
|
+
} catch (error11) {
|
|
13183
|
+
if (isCasFailure(error11)) throw new MaterializedViewPromotionCasError();
|
|
13184
|
+
throw new MaterializedViewStoreError("promote", "MV_STORE_OPERATION_FAILED", `Materialized-view promotion failed: ${String(error11)}`, { cause: error11 });
|
|
12913
13185
|
}
|
|
12914
|
-
const
|
|
12915
|
-
if (
|
|
12916
|
-
return
|
|
13186
|
+
const active2 = await this.readActive(input.serviceId, input.viewId);
|
|
13187
|
+
if (active2 === void 0) throw new MaterializedViewStoreError("promote", "MV_INSTANCE_MISSING", "Active generation disappeared after promotion");
|
|
13188
|
+
return active2;
|
|
12917
13189
|
}
|
|
12918
13190
|
/** Short aliases make the port convenient for runtime composition and tests. */
|
|
12919
13191
|
apply = this.applyMutationsAndAdvanceCheckpoint.bind(this);
|
|
@@ -12922,8 +13194,8 @@ var D1MaterializedViewStore = class {
|
|
|
12922
13194
|
let value;
|
|
12923
13195
|
try {
|
|
12924
13196
|
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(
|
|
13197
|
+
} catch (error11) {
|
|
13198
|
+
throw new MaterializedViewStoreError("initialize", "MV_VALUE_INVALID", `Materialized-view row JSON was invalid: ${String(error11)}`, { cause: error11 });
|
|
12927
13199
|
}
|
|
12928
13200
|
return {
|
|
12929
13201
|
serviceId: asString4(row.service_id, "service_id"),
|
|
@@ -13047,8 +13319,8 @@ function stateFromCheckpoint(projector, checkpoint) {
|
|
|
13047
13319
|
if (checkpoint === void 0) return projector.initialState();
|
|
13048
13320
|
try {
|
|
13049
13321
|
return projector.deserializeState(checkpoint.stateJson);
|
|
13050
|
-
} catch (
|
|
13051
|
-
throw new ProjectionCheckpointCorruption(
|
|
13322
|
+
} catch (error11) {
|
|
13323
|
+
throw new ProjectionCheckpointCorruption(error11);
|
|
13052
13324
|
}
|
|
13053
13325
|
}
|
|
13054
13326
|
function projectionEventFromStored(event) {
|
|
@@ -13279,8 +13551,8 @@ async function admitBootstrapRoute2(env, serviceId) {
|
|
|
13279
13551
|
})).fetch(new Request(url, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ route: "projection-rebuild" }) }));
|
|
13280
13552
|
if (!admitted.ok) throw new Error("bootstrap_route_rejected:projection-rebuild");
|
|
13281
13553
|
}
|
|
13282
|
-
function boundedErrorReason(
|
|
13283
|
-
const value =
|
|
13554
|
+
function boundedErrorReason(error11) {
|
|
13555
|
+
const value = error11 instanceof Error ? error11.message : String(error11);
|
|
13284
13556
|
return value.length > 256 ? `${value.slice(0, 253)}...` : value;
|
|
13285
13557
|
}
|
|
13286
13558
|
async function notifyObserver(observer, callback, input) {
|
|
@@ -13293,7 +13565,7 @@ async function notifyObserver(observer, callback, input) {
|
|
|
13293
13565
|
} catch {
|
|
13294
13566
|
}
|
|
13295
13567
|
}
|
|
13296
|
-
async function notifyOutcomes(observer, serviceId, projectorIds, attemptedAt, results, maximumSuid, env,
|
|
13568
|
+
async function notifyOutcomes(observer, serviceId, projectorIds, attemptedAt, results, maximumSuid, env, error11) {
|
|
13297
13569
|
if (observer?.onOutcome === void 0) return;
|
|
13298
13570
|
const byProjector = /* @__PURE__ */ new Map();
|
|
13299
13571
|
for (const result2 of results) {
|
|
@@ -13306,9 +13578,9 @@ async function notifyOutcomes(observer, serviceId, projectorIds, attemptedAt, re
|
|
|
13306
13578
|
let outcome = "invoked-but-no-work";
|
|
13307
13579
|
let reason = null;
|
|
13308
13580
|
let advancedSourceEvents = 0;
|
|
13309
|
-
if (
|
|
13581
|
+
if (error11 !== void 0) {
|
|
13310
13582
|
outcome = "invoked-and-threw";
|
|
13311
|
-
reason = boundedErrorReason(
|
|
13583
|
+
reason = boundedErrorReason(error11);
|
|
13312
13584
|
} else {
|
|
13313
13585
|
advancedSourceEvents = projectorResults.reduce((total, result2) => total + result2.advancedSourceEvents, 0);
|
|
13314
13586
|
if (advancedSourceEvents > 0) {
|
|
@@ -13410,9 +13682,9 @@ async function pollLiveProjections(env, options = {}) {
|
|
|
13410
13682
|
const results = await runtime.pollRegistered(serviceId, attemptedAt, options.maximumSuid);
|
|
13411
13683
|
await notifyOutcomes(options.observer, serviceId, projectorIds, attemptedAt, results, options.maximumSuid, env);
|
|
13412
13684
|
return results;
|
|
13413
|
-
} catch (
|
|
13414
|
-
await notifyOutcomes(options.observer, serviceId, projectorIds, attemptedAt, [], options.maximumSuid, env,
|
|
13415
|
-
throw
|
|
13685
|
+
} catch (error11) {
|
|
13686
|
+
await notifyOutcomes(options.observer, serviceId, projectorIds, attemptedAt, [], options.maximumSuid, env, error11);
|
|
13687
|
+
throw error11;
|
|
13416
13688
|
}
|
|
13417
13689
|
}
|
|
13418
13690
|
async function handleProjectionLag(request, env, registry = DEPLOYED_PROJECTOR_REGISTRY, storeProvider, serviceIdentityProvider) {
|
|
@@ -13664,6 +13936,33 @@ function parseRequest(value) {
|
|
|
13664
13936
|
}
|
|
13665
13937
|
};
|
|
13666
13938
|
}
|
|
13939
|
+
var QUERY_PARAMS_UNSUPPORTED_MESSAGE = "The selected query does not support these parameters.";
|
|
13940
|
+
var LIST_QUERY_PARAM_KEYS = /* @__PURE__ */ new Set(["PageNumber", "PageSize", "NewestFirst", "consistency"]);
|
|
13941
|
+
function unsupportedQueryParams(endpoint, queryParams) {
|
|
13942
|
+
if (endpoint === "query") {
|
|
13943
|
+
if (isObject9(queryParams) && Object.keys(queryParams).length === 0) {
|
|
13944
|
+
return void 0;
|
|
13945
|
+
}
|
|
13946
|
+
return {
|
|
13947
|
+
status: 400,
|
|
13948
|
+
code: "query_params_unsupported",
|
|
13949
|
+
error: QUERY_PARAMS_UNSUPPORTED_MESSAGE
|
|
13950
|
+
};
|
|
13951
|
+
}
|
|
13952
|
+
if (!isObject9(queryParams)) {
|
|
13953
|
+
return void 0;
|
|
13954
|
+
}
|
|
13955
|
+
for (const key of Object.keys(queryParams)) {
|
|
13956
|
+
if (!LIST_QUERY_PARAM_KEYS.has(key)) {
|
|
13957
|
+
return {
|
|
13958
|
+
status: 400,
|
|
13959
|
+
code: "query_params_unsupported",
|
|
13960
|
+
error: QUERY_PARAMS_UNSUPPORTED_MESSAGE
|
|
13961
|
+
};
|
|
13962
|
+
}
|
|
13963
|
+
}
|
|
13964
|
+
return void 0;
|
|
13965
|
+
}
|
|
13667
13966
|
function paginationFrom(value) {
|
|
13668
13967
|
if (!isObject9(value)) {
|
|
13669
13968
|
return { error: "list-query queryParamsJson must contain an object" };
|
|
@@ -13844,6 +14143,10 @@ async function handleSerializedQuery(request, env, options = {}) {
|
|
|
13844
14143
|
if (definition === void 0 || definition.endpoint !== endpoint) {
|
|
13845
14144
|
return error6(400, "validation_error", "queryType is not mapped for this endpoint");
|
|
13846
14145
|
}
|
|
14146
|
+
const unsupported = unsupportedQueryParams(endpoint, parsed.value.queryParams);
|
|
14147
|
+
if (unsupported !== void 0) {
|
|
14148
|
+
return error6(unsupported.status, unsupported.code, unsupported.error);
|
|
14149
|
+
}
|
|
13847
14150
|
if (!definition.enabled || (options.projectors ?? DEPLOYED_PROJECTOR_REGISTRY).resolve(definition.tagProjector) === void 0) {
|
|
13848
14151
|
return error6(503, "projection_unavailable", "The mapped query projection is unavailable");
|
|
13849
14152
|
}
|
|
@@ -14230,10 +14533,10 @@ var SerializedReadWorker = class {
|
|
|
14230
14533
|
});
|
|
14231
14534
|
}
|
|
14232
14535
|
async ensureWindowDeterminate() {
|
|
14233
|
-
if (this.storeProvider === void 0 || this.storeProvider.isConfigured?.(this.env) === false) {
|
|
14234
|
-
return;
|
|
14235
|
-
}
|
|
14236
14536
|
try {
|
|
14537
|
+
if (this.storeProvider === void 0 || this.storeProvider.isConfigured?.(this.env) === false) {
|
|
14538
|
+
return;
|
|
14539
|
+
}
|
|
14237
14540
|
const store = this.storeProvider.create(this.env);
|
|
14238
14541
|
await store.initialize();
|
|
14239
14542
|
const nowMs = Date.now();
|
|
@@ -14274,13 +14577,662 @@ async function handleSerializedRead(request, env, registry = DEPLOYED_PROJECTOR_
|
|
|
14274
14577
|
}), registry, storeProvider).handle(request);
|
|
14275
14578
|
}
|
|
14276
14579
|
|
|
14580
|
+
// src/completeness/IncidentLifecycle.ts
|
|
14581
|
+
var MAX_IDENTITY_BYTES = 256;
|
|
14582
|
+
var MAX_LONG_TEXT_BYTES = 2048;
|
|
14583
|
+
var SHA256_DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
14584
|
+
var IncidentLifecycleError = class extends Error {
|
|
14585
|
+
code;
|
|
14586
|
+
status;
|
|
14587
|
+
constructor(code, status, message) {
|
|
14588
|
+
super(message);
|
|
14589
|
+
this.name = "IncidentLifecycleError";
|
|
14590
|
+
this.code = code;
|
|
14591
|
+
this.status = status;
|
|
14592
|
+
}
|
|
14593
|
+
};
|
|
14594
|
+
function isObject11(value) {
|
|
14595
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14596
|
+
}
|
|
14597
|
+
function hasOnlyKeys(value, keys) {
|
|
14598
|
+
const allowed = new Set(keys);
|
|
14599
|
+
return Object.keys(value).every((key) => allowed.has(key));
|
|
14600
|
+
}
|
|
14601
|
+
function bytes(value) {
|
|
14602
|
+
return new TextEncoder().encode(value).byteLength;
|
|
14603
|
+
}
|
|
14604
|
+
function boundedString(value, name, maximum, required = true) {
|
|
14605
|
+
if (typeof value !== "string") {
|
|
14606
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, `${name} must be a string`);
|
|
14607
|
+
}
|
|
14608
|
+
const normalized = value.trim();
|
|
14609
|
+
if (required && normalized.length === 0) {
|
|
14610
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, `${name} must be non-empty`);
|
|
14611
|
+
}
|
|
14612
|
+
if (bytes(normalized) > maximum) {
|
|
14613
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, `${name} is too long`);
|
|
14614
|
+
}
|
|
14615
|
+
return normalized.length === 0 ? null : normalized;
|
|
14616
|
+
}
|
|
14617
|
+
function normalizeIncidentIdentity(value) {
|
|
14618
|
+
const identity2 = boundedString(value, "incidentIdentity", MAX_IDENTITY_BYTES);
|
|
14619
|
+
if (identity2 === null) {
|
|
14620
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "incidentIdentity must be non-empty");
|
|
14621
|
+
}
|
|
14622
|
+
return identity2;
|
|
14623
|
+
}
|
|
14624
|
+
function safeInteger(value, name) {
|
|
14625
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value)) {
|
|
14626
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, `${name} must be a safe integer`);
|
|
14627
|
+
}
|
|
14628
|
+
return value;
|
|
14629
|
+
}
|
|
14630
|
+
function normalizeCorrection(value) {
|
|
14631
|
+
if (!isObject11(value) || !hasOnlyKeys(value, ["kind", "reference", "digest"])) {
|
|
14632
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "correction has unknown or missing keys");
|
|
14633
|
+
}
|
|
14634
|
+
if (value.kind !== "event" && value.kind !== "receipt") {
|
|
14635
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "correction.kind must be event or receipt");
|
|
14636
|
+
}
|
|
14637
|
+
const reference = boundedString(value.reference, "correction.reference", MAX_LONG_TEXT_BYTES);
|
|
14638
|
+
const digest = boundedString(value.digest, "correction.digest", MAX_IDENTITY_BYTES);
|
|
14639
|
+
if (reference === null || digest === null || !SHA256_DIGEST.test(digest)) {
|
|
14640
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "correction.digest must be lowercase sha256");
|
|
14641
|
+
}
|
|
14642
|
+
return { kind: value.kind, reference, digest };
|
|
14643
|
+
}
|
|
14644
|
+
function normalizeRequest(value) {
|
|
14645
|
+
if (!isObject11(value) || !hasOnlyKeys(value, ["action", "incidentIdentity", "transitionKey", "expectedVersion", "reason", "ownerId", "deadlineAt", "correction", "resolution"])) {
|
|
14646
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "transition has unknown keys");
|
|
14647
|
+
}
|
|
14648
|
+
const action = value.action;
|
|
14649
|
+
if (typeof action !== "string" || !["ACKNOWLEDGE", "UPDATE_ASSIGNMENT", "RECORD_CORRECTION", "CLOSE", "REOPEN"].includes(action)) {
|
|
14650
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "transition action is invalid");
|
|
14651
|
+
}
|
|
14652
|
+
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"];
|
|
14653
|
+
if (!hasOnlyKeys(value, actionKeys)) {
|
|
14654
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "transition has keys not valid for its action");
|
|
14655
|
+
}
|
|
14656
|
+
const base = {
|
|
14657
|
+
incidentIdentity: normalizeIncidentIdentity(value.incidentIdentity),
|
|
14658
|
+
transitionKey: boundedString(value.transitionKey, "transitionKey", MAX_IDENTITY_BYTES),
|
|
14659
|
+
expectedVersion: safeInteger(value.expectedVersion, "expectedVersion"),
|
|
14660
|
+
reason: boundedString(value.reason, "reason", MAX_LONG_TEXT_BYTES)
|
|
14661
|
+
};
|
|
14662
|
+
if (base.expectedVersion < 0) {
|
|
14663
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "expectedVersion must not be negative");
|
|
14664
|
+
}
|
|
14665
|
+
if (action === "ACKNOWLEDGE" || action === "UPDATE_ASSIGNMENT" || action === "REOPEN") {
|
|
14666
|
+
const ownerId = boundedString(value.ownerId, "ownerId", MAX_IDENTITY_BYTES);
|
|
14667
|
+
const deadlineAt = safeInteger(value.deadlineAt, "deadlineAt");
|
|
14668
|
+
if (ownerId === null) throw new IncidentLifecycleError("incident_invalid_request", 400, "ownerId must be non-empty");
|
|
14669
|
+
return { ...base, action, ownerId, deadlineAt };
|
|
14670
|
+
}
|
|
14671
|
+
if (action === "RECORD_CORRECTION") {
|
|
14672
|
+
return { ...base, action, correction: normalizeCorrection(value.correction) };
|
|
14673
|
+
}
|
|
14674
|
+
if (!isObject11(value.resolution) || !hasOnlyKeys(value.resolution, ["kind", "explanation"])) {
|
|
14675
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "resolution has unknown or missing keys");
|
|
14676
|
+
}
|
|
14677
|
+
if (value.resolution.kind === "CORRECTED") {
|
|
14678
|
+
if (value.resolution.explanation !== void 0) {
|
|
14679
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "CORRECTED resolution has unknown keys");
|
|
14680
|
+
}
|
|
14681
|
+
return { ...base, action, resolution: { kind: "CORRECTED" } };
|
|
14682
|
+
}
|
|
14683
|
+
if (value.resolution.kind === "ACCEPTED_AS_IS") {
|
|
14684
|
+
const explanation = boundedString(value.resolution.explanation, "resolution.explanation", MAX_LONG_TEXT_BYTES);
|
|
14685
|
+
if (explanation === null) throw new IncidentLifecycleError("incident_invalid_request", 400, "resolution.explanation is required");
|
|
14686
|
+
return { ...base, action, resolution: { kind: "ACCEPTED_AS_IS", explanation } };
|
|
14687
|
+
}
|
|
14688
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "resolution.kind is invalid");
|
|
14689
|
+
}
|
|
14690
|
+
function canonicalize(value) {
|
|
14691
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
14692
|
+
if (!isObject11(value)) return value;
|
|
14693
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]));
|
|
14694
|
+
}
|
|
14695
|
+
async function digestFor(request, actorId) {
|
|
14696
|
+
const envelope = canonicalize({ action: request, actor: actorId });
|
|
14697
|
+
const encoded = new TextEncoder().encode(JSON.stringify(envelope));
|
|
14698
|
+
const digest = await crypto.subtle.digest("SHA-256", encoded);
|
|
14699
|
+
return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
14700
|
+
}
|
|
14701
|
+
function projectionFromRow(row, finding2) {
|
|
14702
|
+
if (row === null) {
|
|
14703
|
+
return {
|
|
14704
|
+
serviceId: finding2.service_id,
|
|
14705
|
+
incidentIdentity: finding2.incident_identity,
|
|
14706
|
+
lifecycleState: "OPEN",
|
|
14707
|
+
ownerId: null,
|
|
14708
|
+
deadlineAt: null,
|
|
14709
|
+
correction: null,
|
|
14710
|
+
closeResolution: null,
|
|
14711
|
+
closeReason: null,
|
|
14712
|
+
version: 0,
|
|
14713
|
+
lastTransitionKey: "",
|
|
14714
|
+
updatedAt: finding2.last_observed_at
|
|
14715
|
+
};
|
|
14716
|
+
}
|
|
14717
|
+
return {
|
|
14718
|
+
serviceId: row.service_id,
|
|
14719
|
+
incidentIdentity: row.incident_identity,
|
|
14720
|
+
lifecycleState: row.lifecycle_state,
|
|
14721
|
+
ownerId: row.owner_id,
|
|
14722
|
+
deadlineAt: row.deadline_at,
|
|
14723
|
+
correction: row.correction_kind === null ? null : {
|
|
14724
|
+
kind: row.correction_kind,
|
|
14725
|
+
reference: row.correction_reference,
|
|
14726
|
+
digest: row.correction_digest
|
|
14727
|
+
},
|
|
14728
|
+
closeResolution: row.close_resolution,
|
|
14729
|
+
closeReason: row.close_reason,
|
|
14730
|
+
version: row.version,
|
|
14731
|
+
lastTransitionKey: row.last_transition_key,
|
|
14732
|
+
updatedAt: row.updated_at
|
|
14733
|
+
};
|
|
14734
|
+
}
|
|
14735
|
+
function findingFromRow(row) {
|
|
14736
|
+
return {
|
|
14737
|
+
serviceId: row.service_id,
|
|
14738
|
+
incidentIdentity: row.incident_identity,
|
|
14739
|
+
incidentType: row.incident_type,
|
|
14740
|
+
partitionTag: row.partition_tag,
|
|
14741
|
+
obligationSequence: row.obligation_sequence,
|
|
14742
|
+
eventId: row.event_id,
|
|
14743
|
+
eventDigest: row.event_digest,
|
|
14744
|
+
state: row.finding_state,
|
|
14745
|
+
firstObservedAt: row.first_observed_at,
|
|
14746
|
+
lastObservedAt: row.last_observed_at
|
|
14747
|
+
};
|
|
14748
|
+
}
|
|
14749
|
+
function transitionFromRow(row) {
|
|
14750
|
+
const before = projectionFromAudit(row, "before");
|
|
14751
|
+
const after = projectionFromAudit(row, "after");
|
|
14752
|
+
return {
|
|
14753
|
+
transitionId: row.transition_id,
|
|
14754
|
+
serviceId: row.service_id,
|
|
14755
|
+
incidentIdentity: row.incident_identity,
|
|
14756
|
+
transitionKey: row.transition_key,
|
|
14757
|
+
requestDigest: row.request_digest,
|
|
14758
|
+
action: row.action,
|
|
14759
|
+
fromState: row.from_state,
|
|
14760
|
+
toState: row.to_state,
|
|
14761
|
+
fromVersion: row.from_version,
|
|
14762
|
+
toVersion: row.to_version,
|
|
14763
|
+
actorId: row.actor_id,
|
|
14764
|
+
before,
|
|
14765
|
+
after,
|
|
14766
|
+
reason: row.reason,
|
|
14767
|
+
occurredAt: row.occurred_at
|
|
14768
|
+
};
|
|
14769
|
+
}
|
|
14770
|
+
function projectionFromAudit(row, prefix) {
|
|
14771
|
+
const state = prefix === "before" ? row.from_state : row.to_state;
|
|
14772
|
+
const version = prefix === "before" ? row.from_version : row.to_version;
|
|
14773
|
+
const ownerId = row[`${prefix}_owner_id`];
|
|
14774
|
+
const correctionKind = row[`${prefix}_correction_kind`];
|
|
14775
|
+
return {
|
|
14776
|
+
serviceId: row.service_id,
|
|
14777
|
+
incidentIdentity: row.incident_identity,
|
|
14778
|
+
lifecycleState: state,
|
|
14779
|
+
ownerId,
|
|
14780
|
+
deadlineAt: row[`${prefix}_deadline_at`],
|
|
14781
|
+
correction: correctionKind === null ? null : {
|
|
14782
|
+
kind: correctionKind,
|
|
14783
|
+
reference: row[`${prefix}_correction_reference`],
|
|
14784
|
+
digest: row[`${prefix}_correction_digest`]
|
|
14785
|
+
},
|
|
14786
|
+
closeResolution: row[`${prefix}_close_resolution`],
|
|
14787
|
+
closeReason: row[`${prefix}_close_reason`],
|
|
14788
|
+
version,
|
|
14789
|
+
lastTransitionKey: prefix === "after" ? row.transition_key : "",
|
|
14790
|
+
updatedAt: row.occurred_at
|
|
14791
|
+
};
|
|
14792
|
+
}
|
|
14793
|
+
function active(state) {
|
|
14794
|
+
return state !== "CLOSED";
|
|
14795
|
+
}
|
|
14796
|
+
function compareItems(left, right) {
|
|
14797
|
+
if (left.overdue !== right.overdue) return left.overdue ? -1 : 1;
|
|
14798
|
+
if (left.unowned !== right.unowned) return left.unowned ? -1 : 1;
|
|
14799
|
+
if (left.finding.lastObservedAt !== right.finding.lastObservedAt) return right.finding.lastObservedAt - left.finding.lastObservedAt;
|
|
14800
|
+
return left.finding.incidentIdentity < right.finding.incidentIdentity ? -1 : left.finding.incidentIdentity > right.finding.incidentIdentity ? 1 : 0;
|
|
14801
|
+
}
|
|
14802
|
+
var IncidentLifecycle = class {
|
|
14803
|
+
constructor(database, now2 = () => Date.now()) {
|
|
14804
|
+
this.database = database;
|
|
14805
|
+
this.now = now2;
|
|
14806
|
+
}
|
|
14807
|
+
database;
|
|
14808
|
+
now;
|
|
14809
|
+
static parseTransition(value) {
|
|
14810
|
+
return normalizeRequest(value);
|
|
14811
|
+
}
|
|
14812
|
+
async finding(serviceId, incidentIdentity) {
|
|
14813
|
+
const row = await this.database.prepare(
|
|
14814
|
+
`SELECT service_id, incident_identity, incident_type, partition_tag,
|
|
14815
|
+
obligation_sequence, event_id, event_digest, state AS finding_state,
|
|
14816
|
+
first_observed_at, last_observed_at
|
|
14817
|
+
FROM serialized_dcb_completeness_findings
|
|
14818
|
+
WHERE service_id = ? AND incident_identity = ?`
|
|
14819
|
+
).bind(serviceId, incidentIdentity).first();
|
|
14820
|
+
if (row === null || row === void 0) {
|
|
14821
|
+
throw new IncidentLifecycleError("incident_not_found", 404, "The completeness incident was not found");
|
|
14822
|
+
}
|
|
14823
|
+
return row;
|
|
14824
|
+
}
|
|
14825
|
+
async lifecycle(serviceId, incidentIdentity) {
|
|
14826
|
+
const row = await this.database.prepare(
|
|
14827
|
+
`SELECT service_id, incident_identity, lifecycle_state, owner_id, deadline_at,
|
|
14828
|
+
correction_kind, correction_reference, correction_digest,
|
|
14829
|
+
close_resolution, close_reason, version, last_transition_key, updated_at
|
|
14830
|
+
FROM serialized_dcb_incident_lifecycles
|
|
14831
|
+
WHERE service_id = ? AND incident_identity = ?`
|
|
14832
|
+
).bind(serviceId, incidentIdentity).first();
|
|
14833
|
+
return row === void 0 ? null : row;
|
|
14834
|
+
}
|
|
14835
|
+
async transitions(serviceId, incidentIdentity) {
|
|
14836
|
+
const result2 = await this.database.prepare(
|
|
14837
|
+
`SELECT * FROM serialized_dcb_incident_transitions
|
|
14838
|
+
WHERE service_id = ? AND incident_identity = ?
|
|
14839
|
+
ORDER BY transition_id ASC`
|
|
14840
|
+
).bind(serviceId, incidentIdentity).all();
|
|
14841
|
+
return result2.results.map(transitionFromRow);
|
|
14842
|
+
}
|
|
14843
|
+
async closeTime(serviceId, incidentIdentity) {
|
|
14844
|
+
const row = await this.database.prepare(
|
|
14845
|
+
`SELECT occurred_at FROM serialized_dcb_incident_transitions
|
|
14846
|
+
WHERE service_id = ? AND incident_identity = ? AND to_state = 'CLOSED'
|
|
14847
|
+
ORDER BY transition_id DESC LIMIT 1`
|
|
14848
|
+
).bind(serviceId, incidentIdentity).first();
|
|
14849
|
+
return row?.occurred_at ?? null;
|
|
14850
|
+
}
|
|
14851
|
+
async item(finding2, row, at) {
|
|
14852
|
+
const lifecycle = projectionFromRow(row, finding2);
|
|
14853
|
+
const closeTime = lifecycle.lifecycleState === "OPEN" && row === null ? null : await this.closeTime(finding2.service_id, finding2.incident_identity);
|
|
14854
|
+
const observedAfterClose = closeTime !== null && finding2.last_observed_at > closeTime;
|
|
14855
|
+
const overdue = active(lifecycle.lifecycleState) && lifecycle.deadlineAt !== null && lifecycle.deadlineAt < at;
|
|
14856
|
+
return {
|
|
14857
|
+
finding: findingFromRow(finding2),
|
|
14858
|
+
lifecycle,
|
|
14859
|
+
overdue,
|
|
14860
|
+
unowned: lifecycle.ownerId === null,
|
|
14861
|
+
observedAfterClose
|
|
14862
|
+
};
|
|
14863
|
+
}
|
|
14864
|
+
async list(serviceId, filters = {}, at = this.now()) {
|
|
14865
|
+
const rows = await this.database.prepare(
|
|
14866
|
+
`SELECT service_id, incident_identity, incident_type, partition_tag,
|
|
14867
|
+
obligation_sequence, event_id, event_digest, state AS finding_state,
|
|
14868
|
+
first_observed_at, last_observed_at
|
|
14869
|
+
FROM serialized_dcb_completeness_findings
|
|
14870
|
+
WHERE service_id = ?`
|
|
14871
|
+
).bind(serviceId).all();
|
|
14872
|
+
const items = [];
|
|
14873
|
+
for (const finding2 of rows.results) {
|
|
14874
|
+
const item = await this.item(finding2, await this.lifecycle(serviceId, finding2.incident_identity), at);
|
|
14875
|
+
if (filters.state !== void 0 && item.lifecycle.lifecycleState !== filters.state) continue;
|
|
14876
|
+
if (filters.owner !== void 0 && item.lifecycle.ownerId !== filters.owner) continue;
|
|
14877
|
+
if (filters.unowned === true && !item.unowned) continue;
|
|
14878
|
+
if (filters.overdue === true && !item.overdue) continue;
|
|
14879
|
+
if (filters.observedAfterClose === true && !item.observedAfterClose) continue;
|
|
14880
|
+
items.push(item);
|
|
14881
|
+
}
|
|
14882
|
+
items.sort(compareItems);
|
|
14883
|
+
const byState = {
|
|
14884
|
+
OPEN: 0,
|
|
14885
|
+
ACKNOWLEDGED: 0,
|
|
14886
|
+
CORRECTION_RECORDED: 0,
|
|
14887
|
+
CLOSED: 0,
|
|
14888
|
+
REOPENED: 0
|
|
14889
|
+
};
|
|
14890
|
+
for (const item of items) byState[item.lifecycle.lifecycleState] += 1;
|
|
14891
|
+
return {
|
|
14892
|
+
items,
|
|
14893
|
+
summary: {
|
|
14894
|
+
total: items.length,
|
|
14895
|
+
byState,
|
|
14896
|
+
unowned: items.filter((item) => item.unowned).length,
|
|
14897
|
+
overdue: items.filter((item) => item.overdue).length,
|
|
14898
|
+
observedAfterClose: items.filter((item) => item.observedAfterClose).length
|
|
14899
|
+
}
|
|
14900
|
+
};
|
|
14901
|
+
}
|
|
14902
|
+
async detail(serviceId, incidentIdentity, at = this.now()) {
|
|
14903
|
+
const finding2 = await this.finding(serviceId, incidentIdentity);
|
|
14904
|
+
const lifecycle = await this.lifecycle(serviceId, incidentIdentity);
|
|
14905
|
+
const item = await this.item(finding2, lifecycle, at);
|
|
14906
|
+
return {
|
|
14907
|
+
finding: item.finding,
|
|
14908
|
+
lifecycle: item.lifecycle,
|
|
14909
|
+
flags: { overdue: item.overdue, unowned: item.unowned, observedAfterClose: item.observedAfterClose },
|
|
14910
|
+
transitions: await this.transitions(serviceId, incidentIdentity)
|
|
14911
|
+
};
|
|
14912
|
+
}
|
|
14913
|
+
async transition(serviceId, value, actor) {
|
|
14914
|
+
const request = normalizeRequest(value);
|
|
14915
|
+
let actorId;
|
|
14916
|
+
try {
|
|
14917
|
+
actorId = boundedString(actor, "x-sdt-maintainer", MAX_IDENTITY_BYTES);
|
|
14918
|
+
} catch (caught) {
|
|
14919
|
+
if (caught instanceof IncidentLifecycleError && caught.code === "incident_invalid_request") {
|
|
14920
|
+
throw new IncidentLifecycleError("incident_invalid_actor", 400, caught.message);
|
|
14921
|
+
}
|
|
14922
|
+
throw caught;
|
|
14923
|
+
}
|
|
14924
|
+
if (actorId === null) throw new IncidentLifecycleError("incident_invalid_actor", 400, "x-sdt-maintainer must be non-empty");
|
|
14925
|
+
const finding2 = await this.finding(serviceId, request.incidentIdentity);
|
|
14926
|
+
const digest = await digestFor(request, actorId);
|
|
14927
|
+
const existing = await this.database.prepare(
|
|
14928
|
+
`SELECT * FROM serialized_dcb_incident_transitions
|
|
14929
|
+
WHERE service_id = ? AND incident_identity = ? AND transition_key = ?`
|
|
14930
|
+
).bind(serviceId, request.incidentIdentity, request.transitionKey).first();
|
|
14931
|
+
if (existing !== null && existing !== void 0) {
|
|
14932
|
+
if (existing.request_digest !== digest) {
|
|
14933
|
+
throw new IncidentLifecycleError("incident_idempotency_conflict", 409, "The transition key was already used with different content");
|
|
14934
|
+
}
|
|
14935
|
+
return { projection: transitionFromRow(existing).after, transition: transitionFromRow(existing), idempotent: true };
|
|
14936
|
+
}
|
|
14937
|
+
const priorRow = await this.lifecycle(serviceId, request.incidentIdentity);
|
|
14938
|
+
const before = projectionFromRow(priorRow, finding2);
|
|
14939
|
+
if (before.version !== request.expectedVersion) {
|
|
14940
|
+
throw new IncidentLifecycleError("incident_version_conflict", 409, "The incident version is stale");
|
|
14941
|
+
}
|
|
14942
|
+
const at = this.now();
|
|
14943
|
+
const after = this.nextProjection(before, request, at);
|
|
14944
|
+
const newVersion = before.version + 1;
|
|
14945
|
+
const occurredAt = at;
|
|
14946
|
+
const seed = this.database.prepare(
|
|
14947
|
+
`INSERT INTO serialized_dcb_incident_lifecycles
|
|
14948
|
+
(service_id, incident_identity, lifecycle_state, version, last_transition_key, updated_at)
|
|
14949
|
+
VALUES (?, ?, 'OPEN', 0, '', ?)
|
|
14950
|
+
ON CONFLICT (service_id, incident_identity) DO NOTHING`
|
|
14951
|
+
).bind(serviceId, request.incidentIdentity, occurredAt);
|
|
14952
|
+
const update = this.database.prepare(
|
|
14953
|
+
`UPDATE serialized_dcb_incident_lifecycles
|
|
14954
|
+
SET lifecycle_state = ?, owner_id = ?, deadline_at = ?,
|
|
14955
|
+
correction_kind = ?, correction_reference = ?, correction_digest = ?,
|
|
14956
|
+
close_resolution = ?, close_reason = ?, version = ?,
|
|
14957
|
+
last_transition_key = ?, updated_at = ?
|
|
14958
|
+
WHERE service_id = ? AND incident_identity = ? AND version = ?
|
|
14959
|
+
AND last_transition_key <> ?`
|
|
14960
|
+
).bind(
|
|
14961
|
+
after.lifecycleState,
|
|
14962
|
+
after.ownerId,
|
|
14963
|
+
after.deadlineAt,
|
|
14964
|
+
after.correction?.kind ?? null,
|
|
14965
|
+
after.correction?.reference ?? null,
|
|
14966
|
+
after.correction?.digest ?? null,
|
|
14967
|
+
after.closeResolution,
|
|
14968
|
+
after.closeReason,
|
|
14969
|
+
newVersion,
|
|
14970
|
+
request.transitionKey,
|
|
14971
|
+
occurredAt,
|
|
14972
|
+
serviceId,
|
|
14973
|
+
request.incidentIdentity,
|
|
14974
|
+
before.version,
|
|
14975
|
+
request.transitionKey
|
|
14976
|
+
);
|
|
14977
|
+
const audit = this.database.prepare(
|
|
14978
|
+
`INSERT INTO serialized_dcb_incident_transitions
|
|
14979
|
+
(service_id, incident_identity, transition_key, request_digest, action,
|
|
14980
|
+
from_state, to_state, from_version, to_version, actor_id,
|
|
14981
|
+
before_owner_id, before_deadline_at, before_correction_kind,
|
|
14982
|
+
before_correction_reference, before_correction_digest, before_close_resolution,
|
|
14983
|
+
before_close_reason, after_owner_id, after_deadline_at, after_correction_kind,
|
|
14984
|
+
after_correction_reference, after_correction_digest, after_close_resolution,
|
|
14985
|
+
after_close_reason, reason, occurred_at)
|
|
14986
|
+
SELECT ?, ?, ?, ?, ?, ?, lifecycle_state, ?, version, ?,
|
|
14987
|
+
?, ?, ?, ?, ?, ?, ?, owner_id, deadline_at, correction_kind,
|
|
14988
|
+
correction_reference, correction_digest, close_resolution, close_reason, ?, ?
|
|
14989
|
+
FROM serialized_dcb_incident_lifecycles
|
|
14990
|
+
WHERE service_id = ? AND incident_identity = ? AND version = ? AND last_transition_key = ?`
|
|
14991
|
+
).bind(
|
|
14992
|
+
serviceId,
|
|
14993
|
+
request.incidentIdentity,
|
|
14994
|
+
request.transitionKey,
|
|
14995
|
+
digest,
|
|
14996
|
+
request.action,
|
|
14997
|
+
before.lifecycleState,
|
|
14998
|
+
before.version,
|
|
14999
|
+
actorId,
|
|
15000
|
+
before.ownerId,
|
|
15001
|
+
before.deadlineAt,
|
|
15002
|
+
before.correction?.kind ?? null,
|
|
15003
|
+
before.correction?.reference ?? null,
|
|
15004
|
+
before.correction?.digest ?? null,
|
|
15005
|
+
before.closeResolution,
|
|
15006
|
+
before.closeReason,
|
|
15007
|
+
request.reason,
|
|
15008
|
+
occurredAt,
|
|
15009
|
+
serviceId,
|
|
15010
|
+
request.incidentIdentity,
|
|
15011
|
+
newVersion,
|
|
15012
|
+
request.transitionKey
|
|
15013
|
+
);
|
|
15014
|
+
await this.database.batch([seed, update, audit]);
|
|
15015
|
+
const inserted = await this.database.prepare(
|
|
15016
|
+
`SELECT * FROM serialized_dcb_incident_transitions
|
|
15017
|
+
WHERE service_id = ? AND incident_identity = ? AND transition_key = ?`
|
|
15018
|
+
).bind(serviceId, request.incidentIdentity, request.transitionKey).first();
|
|
15019
|
+
if (inserted === null || inserted === void 0) {
|
|
15020
|
+
const competing = await this.database.prepare(
|
|
15021
|
+
`SELECT * FROM serialized_dcb_incident_transitions
|
|
15022
|
+
WHERE service_id = ? AND incident_identity = ? AND transition_key = ?`
|
|
15023
|
+
).bind(serviceId, request.incidentIdentity, request.transitionKey).first();
|
|
15024
|
+
if (competing !== null && competing !== void 0 && competing.request_digest !== digest) {
|
|
15025
|
+
throw new IncidentLifecycleError("incident_idempotency_conflict", 409, "The transition key was already used with different content");
|
|
15026
|
+
}
|
|
15027
|
+
if (competing !== null && competing !== void 0) {
|
|
15028
|
+
const transition2 = transitionFromRow(competing);
|
|
15029
|
+
return { projection: transition2.after, transition: transition2, idempotent: true };
|
|
15030
|
+
}
|
|
15031
|
+
throw new IncidentLifecycleError("incident_version_conflict", 409, "The incident version is stale");
|
|
15032
|
+
}
|
|
15033
|
+
const transition = transitionFromRow(inserted);
|
|
15034
|
+
return { projection: transition.after, transition, idempotent: false };
|
|
15035
|
+
}
|
|
15036
|
+
nextProjection(before, request, at) {
|
|
15037
|
+
const laterDeadline = (deadlineAt2) => {
|
|
15038
|
+
if (deadlineAt2 <= at) throw new IncidentLifecycleError("incident_invalid_deadline", 400, "deadlineAt must be in the future");
|
|
15039
|
+
return deadlineAt2;
|
|
15040
|
+
};
|
|
15041
|
+
let lifecycleState;
|
|
15042
|
+
let ownerId = before.ownerId;
|
|
15043
|
+
let deadlineAt = before.deadlineAt;
|
|
15044
|
+
let correction = before.correction;
|
|
15045
|
+
let closeResolution = before.closeResolution;
|
|
15046
|
+
let closeReason = before.closeReason;
|
|
15047
|
+
if (request.action === "ACKNOWLEDGE") {
|
|
15048
|
+
if (before.lifecycleState !== "OPEN" && before.lifecycleState !== "REOPENED") {
|
|
15049
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "The incident cannot be acknowledged from its current state");
|
|
15050
|
+
}
|
|
15051
|
+
lifecycleState = "ACKNOWLEDGED";
|
|
15052
|
+
ownerId = request.ownerId;
|
|
15053
|
+
deadlineAt = laterDeadline(request.deadlineAt);
|
|
15054
|
+
correction = null;
|
|
15055
|
+
closeResolution = null;
|
|
15056
|
+
closeReason = null;
|
|
15057
|
+
} else if (request.action === "UPDATE_ASSIGNMENT") {
|
|
15058
|
+
if (before.lifecycleState !== "ACKNOWLEDGED" && before.lifecycleState !== "CORRECTION_RECORDED") {
|
|
15059
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "The incident cannot be reassigned from its current state");
|
|
15060
|
+
}
|
|
15061
|
+
lifecycleState = before.lifecycleState;
|
|
15062
|
+
ownerId = request.ownerId;
|
|
15063
|
+
deadlineAt = laterDeadline(request.deadlineAt);
|
|
15064
|
+
} else if (request.action === "RECORD_CORRECTION") {
|
|
15065
|
+
if (before.lifecycleState !== "ACKNOWLEDGED") {
|
|
15066
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "The incident must be acknowledged before correction evidence");
|
|
15067
|
+
}
|
|
15068
|
+
lifecycleState = "CORRECTION_RECORDED";
|
|
15069
|
+
correction = request.correction;
|
|
15070
|
+
} else if (request.action === "CLOSE") {
|
|
15071
|
+
if (before.ownerId === null || before.deadlineAt === null) {
|
|
15072
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "The incident must have an owner and deadline before closure");
|
|
15073
|
+
}
|
|
15074
|
+
if (request.resolution.kind === "CORRECTED") {
|
|
15075
|
+
if (before.lifecycleState !== "CORRECTION_RECORDED") {
|
|
15076
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "A corrected closure requires recorded correction evidence");
|
|
15077
|
+
}
|
|
15078
|
+
lifecycleState = "CLOSED";
|
|
15079
|
+
closeResolution = "CORRECTED";
|
|
15080
|
+
closeReason = request.reason;
|
|
15081
|
+
} else {
|
|
15082
|
+
if (before.lifecycleState !== "ACKNOWLEDGED") {
|
|
15083
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "Accepted-as-is closure requires an acknowledged incident");
|
|
15084
|
+
}
|
|
15085
|
+
lifecycleState = "CLOSED";
|
|
15086
|
+
closeResolution = "ACCEPTED_AS_IS";
|
|
15087
|
+
closeReason = request.resolution.explanation;
|
|
15088
|
+
correction = null;
|
|
15089
|
+
}
|
|
15090
|
+
} else {
|
|
15091
|
+
if (before.lifecycleState !== "CLOSED") {
|
|
15092
|
+
throw new IncidentLifecycleError("incident_invalid_transition", 409, "Only a closed incident can be reopened");
|
|
15093
|
+
}
|
|
15094
|
+
lifecycleState = "REOPENED";
|
|
15095
|
+
ownerId = request.ownerId;
|
|
15096
|
+
deadlineAt = laterDeadline(request.deadlineAt);
|
|
15097
|
+
correction = null;
|
|
15098
|
+
closeResolution = null;
|
|
15099
|
+
closeReason = null;
|
|
15100
|
+
}
|
|
15101
|
+
return {
|
|
15102
|
+
...before,
|
|
15103
|
+
lifecycleState,
|
|
15104
|
+
ownerId,
|
|
15105
|
+
deadlineAt,
|
|
15106
|
+
correction,
|
|
15107
|
+
closeResolution,
|
|
15108
|
+
closeReason,
|
|
15109
|
+
version: before.version + 1,
|
|
15110
|
+
lastTransitionKey: request.transitionKey,
|
|
15111
|
+
updatedAt: at
|
|
15112
|
+
};
|
|
15113
|
+
}
|
|
15114
|
+
};
|
|
15115
|
+
|
|
15116
|
+
// src/http/IncidentMaintenance.ts
|
|
15117
|
+
function json10(body, status = 200) {
|
|
15118
|
+
return new Response(JSON.stringify(body), {
|
|
15119
|
+
status,
|
|
15120
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
15121
|
+
});
|
|
15122
|
+
}
|
|
15123
|
+
function error8(status, code, message) {
|
|
15124
|
+
return json10({ error: message, code }, status);
|
|
15125
|
+
}
|
|
15126
|
+
function boolFilter(value, name) {
|
|
15127
|
+
if (value === null) return void 0;
|
|
15128
|
+
if (value === "true") return true;
|
|
15129
|
+
if (value === "false") return false;
|
|
15130
|
+
throw new IncidentLifecycleError("incident_invalid_filter", 400, `${name} must be true or false`);
|
|
15131
|
+
}
|
|
15132
|
+
function parseFilters(url) {
|
|
15133
|
+
const allowed = /* @__PURE__ */ new Set(["state", "owner", "unowned", "overdue", "observedAfterClose"]);
|
|
15134
|
+
let unknown = false;
|
|
15135
|
+
url.searchParams.forEach((_value, key) => {
|
|
15136
|
+
if (!allowed.has(key)) unknown = true;
|
|
15137
|
+
});
|
|
15138
|
+
if (unknown) throw new IncidentLifecycleError("incident_invalid_filter", 400, "Unknown incident list filter");
|
|
15139
|
+
const state = url.searchParams.get("state");
|
|
15140
|
+
if (state !== null && !["OPEN", "ACKNOWLEDGED", "CORRECTION_RECORDED", "CLOSED", "REOPENED"].includes(state)) {
|
|
15141
|
+
throw new IncidentLifecycleError("incident_invalid_filter", 400, "state is invalid");
|
|
15142
|
+
}
|
|
15143
|
+
const owner = url.searchParams.get("owner");
|
|
15144
|
+
if (owner !== null) {
|
|
15145
|
+
const normalizedOwner = owner.trim();
|
|
15146
|
+
if (normalizedOwner.length === 0) {
|
|
15147
|
+
throw new IncidentLifecycleError("incident_invalid_filter", 400, "owner must be non-empty");
|
|
15148
|
+
}
|
|
15149
|
+
if (new TextEncoder().encode(normalizedOwner).byteLength > 256) {
|
|
15150
|
+
throw new IncidentLifecycleError("incident_invalid_filter", 400, "owner is too long");
|
|
15151
|
+
}
|
|
15152
|
+
}
|
|
15153
|
+
const unowned = boolFilter(url.searchParams.get("unowned"), "unowned");
|
|
15154
|
+
const overdue = boolFilter(url.searchParams.get("overdue"), "overdue");
|
|
15155
|
+
const observedAfterClose = boolFilter(url.searchParams.get("observedAfterClose"), "observedAfterClose");
|
|
15156
|
+
return {
|
|
15157
|
+
...state === null ? {} : { state },
|
|
15158
|
+
...owner === null ? {} : { owner: owner.trim() },
|
|
15159
|
+
...unowned === void 0 ? {} : { unowned },
|
|
15160
|
+
...overdue === void 0 ? {} : { overdue },
|
|
15161
|
+
...observedAfterClose === void 0 ? {} : { observedAfterClose }
|
|
15162
|
+
};
|
|
15163
|
+
}
|
|
15164
|
+
function incidentIdentityFromPath(pathname) {
|
|
15165
|
+
const encoded = pathname.slice("/maintenance/incidents/".length);
|
|
15166
|
+
if (encoded.length === 0 || encoded.includes("/")) {
|
|
15167
|
+
throw new IncidentLifecycleError("incident_route_not_found", 404, "Incident route not found");
|
|
15168
|
+
}
|
|
15169
|
+
try {
|
|
15170
|
+
return normalizeIncidentIdentity(decodeURIComponent(encoded));
|
|
15171
|
+
} catch {
|
|
15172
|
+
throw new IncidentLifecycleError("incident_invalid_request", 400, "Incident identity must be URI encoded");
|
|
15173
|
+
}
|
|
15174
|
+
}
|
|
15175
|
+
function isIncidentMaintenancePath(pathname) {
|
|
15176
|
+
return pathname === "/maintenance/incidents" || pathname.startsWith("/maintenance/incidents/");
|
|
15177
|
+
}
|
|
15178
|
+
async function handleIncidentMaintenance(request, env, serviceIdentityProvider) {
|
|
15179
|
+
const decision = await checkConfiguredBearer(request, env.INCIDENT_MAINTAINER_TOKEN, {
|
|
15180
|
+
tokenNormalization: "trim"
|
|
15181
|
+
});
|
|
15182
|
+
if (decision === "unconfigured") {
|
|
15183
|
+
return error8(503, "incident_maintenance_unavailable", "Incident maintenance is not configured");
|
|
15184
|
+
}
|
|
15185
|
+
if (decision === "missing" || decision === "invalid") {
|
|
15186
|
+
return error8(401, "incident_auth_required", "A valid incident maintenance bearer token is required");
|
|
15187
|
+
}
|
|
15188
|
+
let serviceId;
|
|
15189
|
+
try {
|
|
15190
|
+
serviceId = requestServiceIdentity(request, serviceIdentityProvider, {
|
|
15191
|
+
allowG11Verification: env.G11_VERIFICATION_ENABLED === "true"
|
|
15192
|
+
});
|
|
15193
|
+
} catch {
|
|
15194
|
+
return error8(503, "scope.identity_missing", "A deployment service identity is required for this scope");
|
|
15195
|
+
}
|
|
15196
|
+
if (env.D1 === void 0) {
|
|
15197
|
+
return error8(503, "incident_maintenance_unavailable", "Incident maintenance storage is unavailable");
|
|
15198
|
+
}
|
|
15199
|
+
const lifecycle = new IncidentLifecycle(env.D1);
|
|
15200
|
+
const url = new URL(request.url);
|
|
15201
|
+
try {
|
|
15202
|
+
if (request.method === "GET" && url.pathname === "/maintenance/incidents") {
|
|
15203
|
+
return json10(await lifecycle.list(serviceId, parseFilters(url)));
|
|
15204
|
+
}
|
|
15205
|
+
if (request.method === "GET" && url.pathname.startsWith("/maintenance/incidents/")) {
|
|
15206
|
+
return json10(await lifecycle.detail(serviceId, incidentIdentityFromPath(url.pathname)));
|
|
15207
|
+
}
|
|
15208
|
+
if (request.method === "POST" && url.pathname === "/maintenance/incidents/transitions") {
|
|
15209
|
+
let body;
|
|
15210
|
+
try {
|
|
15211
|
+
body = await request.json();
|
|
15212
|
+
} catch {
|
|
15213
|
+
return error8(400, "incident_invalid_request", "Transition request must be JSON");
|
|
15214
|
+
}
|
|
15215
|
+
const requestValue = IncidentLifecycle.parseTransition(body);
|
|
15216
|
+
const actor = request.headers.get("x-sdt-maintainer");
|
|
15217
|
+
if (actor === null) {
|
|
15218
|
+
return error8(400, "incident_invalid_actor", "x-sdt-maintainer is required");
|
|
15219
|
+
}
|
|
15220
|
+
return json10(await lifecycle.transition(serviceId, requestValue, actor));
|
|
15221
|
+
}
|
|
15222
|
+
return error8(404, "incident_route_not_found", "Incident maintenance route not found");
|
|
15223
|
+
} catch (caught) {
|
|
15224
|
+
if (caught instanceof IncidentLifecycleError) return error8(caught.status, caught.code, caught.message);
|
|
15225
|
+
return error8(500, "incident_maintenance_failed", "Incident maintenance could not complete the request");
|
|
15226
|
+
}
|
|
15227
|
+
}
|
|
15228
|
+
|
|
14277
15229
|
// src/downstream/Doorbell.ts
|
|
14278
15230
|
var MAX_SERVICE_BINDING_INVOCATIONS_PER_REQUEST = 32;
|
|
14279
|
-
function classifyDirectDoorbellFailure(
|
|
14280
|
-
const structured = typeof
|
|
15231
|
+
function classifyDirectDoorbellFailure(error11) {
|
|
15232
|
+
const structured = typeof error11 === "object" && error11 !== null ? error11 : {};
|
|
14281
15233
|
const status = typeof structured.status === "number" ? structured.status : typeof structured.statusCode === "number" ? structured.statusCode : void 0;
|
|
14282
15234
|
if (status !== void 0 && (status < 200 || status >= 300)) return "non-2xx";
|
|
14283
|
-
const value = String(
|
|
15235
|
+
const value = String(error11).toLowerCase();
|
|
14284
15236
|
const code = `${String(structured.code ?? "")} ${String(structured.name ?? "")}`.toLowerCase();
|
|
14285
15237
|
if (value.includes("timeout") || value.includes("deadline") || code.includes("timeout")) return "timeout";
|
|
14286
15238
|
if (value.includes("cancel") || value.includes("abort") || code.includes("abort")) return "cancel";
|
|
@@ -14523,6 +15475,7 @@ var TAG_SQL_SCHEMA_DDL = `
|
|
|
14523
15475
|
committed_event_count INTEGER NOT NULL,
|
|
14524
15476
|
head_suid TEXT NOT NULL,
|
|
14525
15477
|
reservation_confirmed INTEGER NOT NULL CHECK (reservation_confirmed IN (0, 1)),
|
|
15478
|
+
written_version INTEGER NOT NULL,
|
|
14526
15479
|
PRIMARY KEY (attempt_id, epoch)
|
|
14527
15480
|
);
|
|
14528
15481
|
|
|
@@ -14573,6 +15526,12 @@ var TAG_READ_AFTER_THROUGH_SQL = `
|
|
|
14573
15526
|
`;
|
|
14574
15527
|
function initializeTagSqlSchema(sql) {
|
|
14575
15528
|
sql.exec(TAG_SQL_SCHEMA_DDL);
|
|
15529
|
+
ensureTagCommitReceiptWrittenVersion(sql);
|
|
15530
|
+
}
|
|
15531
|
+
function ensureTagCommitReceiptWrittenVersion(sql) {
|
|
15532
|
+
const columns = sql.exec("SELECT name FROM pragma_table_info('tag_commit_receipt')").toArray();
|
|
15533
|
+
if (columns.some((column) => column.name === "written_version")) return;
|
|
15534
|
+
sql.exec("ALTER TABLE tag_commit_receipt ADD COLUMN written_version INTEGER");
|
|
14576
15535
|
}
|
|
14577
15536
|
function hasTagSqlStorage(storage) {
|
|
14578
15537
|
return typeof storage.sql?.exec === "function";
|
|
@@ -14699,14 +15658,14 @@ var TagIdentityConflict = class extends Error {
|
|
|
14699
15658
|
this.name = "TagIdentityConflict";
|
|
14700
15659
|
}
|
|
14701
15660
|
};
|
|
14702
|
-
function
|
|
15661
|
+
function json11(body, status = 200) {
|
|
14703
15662
|
return new Response(JSON.stringify(body), {
|
|
14704
15663
|
status,
|
|
14705
15664
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
14706
15665
|
});
|
|
14707
15666
|
}
|
|
14708
|
-
function
|
|
14709
|
-
return
|
|
15667
|
+
function error9(status, code, message, retryable = false) {
|
|
15668
|
+
return json11(retryable ? { error: message, code, retryable: true } : { error: message, code }, status);
|
|
14710
15669
|
}
|
|
14711
15670
|
function withGlobalAdmission2(response3, status) {
|
|
14712
15671
|
response3.headers.set(G65_GLOBAL_ADMISSION_HEADER, status);
|
|
@@ -14725,7 +15684,7 @@ function repairRejected(reason, status = 409) {
|
|
|
14725
15684
|
body: { code: "repair_operation_rejected", error: reason, reason }
|
|
14726
15685
|
};
|
|
14727
15686
|
}
|
|
14728
|
-
function
|
|
15687
|
+
function isObject12(value) {
|
|
14729
15688
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14730
15689
|
}
|
|
14731
15690
|
function isNonEmptyString11(value) {
|
|
@@ -14770,8 +15729,8 @@ function sqlJson(value, column) {
|
|
|
14770
15729
|
throw new Error(`Tag SQL column ${column} is not valid JSON`);
|
|
14771
15730
|
}
|
|
14772
15731
|
}
|
|
14773
|
-
function copyArrayBuffer(
|
|
14774
|
-
return
|
|
15732
|
+
function copyArrayBuffer(bytes2) {
|
|
15733
|
+
return bytes2.buffer.slice(bytes2.byteOffset, bytes2.byteOffset + bytes2.byteLength);
|
|
14775
15734
|
}
|
|
14776
15735
|
function arrayBufferBase642(value) {
|
|
14777
15736
|
let binary = "";
|
|
@@ -14782,7 +15741,7 @@ function nowIso() {
|
|
|
14782
15741
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
14783
15742
|
}
|
|
14784
15743
|
function epochFrom(value) {
|
|
14785
|
-
if (!
|
|
15744
|
+
if (!isObject12(value) || !isNonEmptyString11(value.attemptId) || !isEpoch(value.epoch)) {
|
|
14786
15745
|
return { error: "attemptId and a non-negative safe-integer epoch are required" };
|
|
14787
15746
|
}
|
|
14788
15747
|
return { value: { attemptId: value.attemptId, epoch: value.epoch } };
|
|
@@ -14802,7 +15761,7 @@ function consistencyTagsFrom(value) {
|
|
|
14802
15761
|
}
|
|
14803
15762
|
const entries = [];
|
|
14804
15763
|
for (const rawEntry of value) {
|
|
14805
|
-
if (!
|
|
15764
|
+
if (!isObject12(rawEntry) || !isNonEmptyString11(rawEntry.tag)) {
|
|
14806
15765
|
return { error: "each consistency tag needs a non-empty tag" };
|
|
14807
15766
|
}
|
|
14808
15767
|
if (!("lastSortableUniqueId" in rawEntry)) {
|
|
@@ -14830,7 +15789,7 @@ function consistencyTagsFrom(value) {
|
|
|
14830
15789
|
}
|
|
14831
15790
|
function acquireFrom(value, tag) {
|
|
14832
15791
|
const epoch2 = epochFrom(value);
|
|
14833
|
-
if (epoch2.value === void 0 || !
|
|
15792
|
+
if (epoch2.value === void 0 || !isObject12(value)) {
|
|
14834
15793
|
return { error: epoch2.error ?? "Invalid acquire request" };
|
|
14835
15794
|
}
|
|
14836
15795
|
const eventTags = stringArrayFrom(value.eventTags, "eventTags");
|
|
@@ -14853,7 +15812,7 @@ function acquireFrom(value, tag) {
|
|
|
14853
15812
|
}
|
|
14854
15813
|
function reservationFrom(value) {
|
|
14855
15814
|
const epoch2 = epochFrom(value);
|
|
14856
|
-
if (epoch2.value === void 0 || !
|
|
15815
|
+
if (epoch2.value === void 0 || !isObject12(value)) {
|
|
14857
15816
|
return { error: epoch2.error ?? "Invalid reservation request" };
|
|
14858
15817
|
}
|
|
14859
15818
|
if (value.reservationToken !== void 0 && !isNonEmptyString11(value.reservationToken)) {
|
|
@@ -14872,7 +15831,7 @@ function reservationFrom(value) {
|
|
|
14872
15831
|
}
|
|
14873
15832
|
function appendFrom(value, tag) {
|
|
14874
15833
|
const reservation = reservationFrom(value);
|
|
14875
|
-
if (reservation.value === void 0 || !
|
|
15834
|
+
if (reservation.value === void 0 || !isObject12(value) || !Array.isArray(value.candidates)) {
|
|
14876
15835
|
return { error: reservation.error ?? "candidates must be an array" };
|
|
14877
15836
|
}
|
|
14878
15837
|
if (value.candidates.length === 0) {
|
|
@@ -14880,7 +15839,7 @@ function appendFrom(value, tag) {
|
|
|
14880
15839
|
}
|
|
14881
15840
|
const candidates = [];
|
|
14882
15841
|
for (const rawCandidate of value.candidates) {
|
|
14883
|
-
if (!
|
|
15842
|
+
if (!isObject12(rawCandidate) || !isNonEmptyString11(rawCandidate.eventId) || !isNonEmptyString11(rawCandidate.suid) || typeof rawCandidate.payload !== "string") {
|
|
14884
15843
|
return { error: "each candidate needs eventId, suid, and payload" };
|
|
14885
15844
|
}
|
|
14886
15845
|
const eventTags = stringArrayFrom(rawCandidate.eventTags, "candidate eventTags");
|
|
@@ -14894,8 +15853,8 @@ function appendFrom(value, tag) {
|
|
|
14894
15853
|
assertSortableUniqueId(rawCandidate.suid);
|
|
14895
15854
|
if (!isUuidV7(rawCandidate.eventId)) return { error: "candidate eventId must be a UUID v7" };
|
|
14896
15855
|
JSON.parse(rawCandidate.payload);
|
|
14897
|
-
} catch (
|
|
14898
|
-
return { error:
|
|
15856
|
+
} catch (error11) {
|
|
15857
|
+
return { error: error11 instanceof Error ? error11.message : "candidate identity or payload is invalid" };
|
|
14899
15858
|
}
|
|
14900
15859
|
if (rawCandidate.provenance !== "g32") return { error: "candidate provenance must be g32" };
|
|
14901
15860
|
if (!isNonEmptyString11(rawCandidate.allocatorLineageId)) return { error: "candidate allocatorLineageId is required" };
|
|
@@ -14929,10 +15888,10 @@ function appendFrom(value, tag) {
|
|
|
14929
15888
|
return { value: { ...reservation.value, candidates: ordered, faultInjection } };
|
|
14930
15889
|
}
|
|
14931
15890
|
function bootstrapAppendFrom(value, tag) {
|
|
14932
|
-
if (!
|
|
15891
|
+
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
15892
|
const candidates = [];
|
|
14934
15893
|
for (const candidate of value.candidates) {
|
|
14935
|
-
if (!
|
|
15894
|
+
if (!isObject12(candidate) || !isNonEmptyString11(candidate.eventId) || !isNonEmptyString11(candidate.suid) || typeof candidate.payload !== "string") return { error: "bootstrap candidate is invalid" };
|
|
14936
15895
|
const tags = stringArrayFrom(candidate.eventTags, "bootstrap candidate eventTags");
|
|
14937
15896
|
if (tags.value === void 0 || !tags.value.includes(tag) || !isNonEmptyString11(candidate.allocatorLineageId)) return { error: "bootstrap candidate must include this tag and lineage" };
|
|
14938
15897
|
let eventType;
|
|
@@ -14942,8 +15901,8 @@ function bootstrapAppendFrom(value, tag) {
|
|
|
14942
15901
|
assertSortableUniqueId(candidate.suid);
|
|
14943
15902
|
if (!isRfc4122Uuid(candidate.eventId)) return { error: "bootstrap candidate eventId must be an RFC 4122 UUID" };
|
|
14944
15903
|
JSON.parse(candidate.payload);
|
|
14945
|
-
} catch (
|
|
14946
|
-
return { error:
|
|
15904
|
+
} catch (error11) {
|
|
15905
|
+
return { error: error11 instanceof Error ? error11.message : "bootstrap candidate identity or payload is invalid" };
|
|
14947
15906
|
}
|
|
14948
15907
|
if (candidate.provenance !== "g32") return { error: "bootstrap candidate provenance must be g32" };
|
|
14949
15908
|
if (!isNonEmptyString11(candidate.timestamp) || !CANONICAL_UTC_TIMESTAMP_PATTERN.test(candidate.timestamp)) return { error: "bootstrap candidate timestamp must be canonical UTC" };
|
|
@@ -14955,13 +15914,13 @@ function bootstrapAppendFrom(value, tag) {
|
|
|
14955
15914
|
}
|
|
14956
15915
|
function fenceFrom(value) {
|
|
14957
15916
|
const epoch2 = epochFrom(value);
|
|
14958
|
-
if (epoch2.value === void 0 || !
|
|
15917
|
+
if (epoch2.value === void 0 || !isObject12(value) || !isNonEmptyString11(value.reason)) {
|
|
14959
15918
|
return { error: epoch2.error ?? "reason is required" };
|
|
14960
15919
|
}
|
|
14961
15920
|
return { value: { ...epoch2.value, reason: value.reason } };
|
|
14962
15921
|
}
|
|
14963
15922
|
function repairScopeItemFrom(value, tag) {
|
|
14964
|
-
if (!
|
|
15923
|
+
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
15924
|
return { error: "each repair scope item needs attemptId, eventId, suid, and payload" };
|
|
14966
15925
|
}
|
|
14967
15926
|
const eventTags = stringArrayFrom(value.eventTags, "repair eventTags");
|
|
@@ -15009,13 +15968,13 @@ function repairScopeFrom(value, tag) {
|
|
|
15009
15968
|
return { value: sortRepairScope(scope) };
|
|
15010
15969
|
}
|
|
15011
15970
|
function repairLeaseFrom(value) {
|
|
15012
|
-
if (!
|
|
15971
|
+
if (!isObject12(value) || !isNonEmptyString11(value.owner) || !isEpoch(value.epoch)) {
|
|
15013
15972
|
return { error: "repair owner and non-negative safe-integer epoch are required" };
|
|
15014
15973
|
}
|
|
15015
15974
|
return { value: { owner: value.owner, epoch: value.epoch } };
|
|
15016
15975
|
}
|
|
15017
15976
|
function repairAcquireFrom(value, tag) {
|
|
15018
|
-
if (!
|
|
15977
|
+
if (!isObject12(value) || !isNonEmptyString11(value.owner)) {
|
|
15019
15978
|
return { error: "repair owner is required" };
|
|
15020
15979
|
}
|
|
15021
15980
|
const leaseMs = value.leaseMs ?? DEFAULT_REPAIR_LEASE_MS;
|
|
@@ -15027,7 +15986,7 @@ function repairAcquireFrom(value, tag) {
|
|
|
15027
15986
|
}
|
|
15028
15987
|
function repairScopeUnionFrom(value, tag) {
|
|
15029
15988
|
const lease = repairLeaseFrom(value);
|
|
15030
|
-
if (lease.value === void 0 || !
|
|
15989
|
+
if (lease.value === void 0 || !isObject12(value)) {
|
|
15031
15990
|
return { error: lease.error };
|
|
15032
15991
|
}
|
|
15033
15992
|
const scope = repairScopeFrom(value.scope, tag);
|
|
@@ -15035,7 +15994,7 @@ function repairScopeUnionFrom(value, tag) {
|
|
|
15035
15994
|
}
|
|
15036
15995
|
function repairApplyFrom(value, tag) {
|
|
15037
15996
|
const lease = repairLeaseFrom(value);
|
|
15038
|
-
if (lease.value === void 0 || !
|
|
15997
|
+
if (lease.value === void 0 || !isObject12(value)) {
|
|
15039
15998
|
return { error: lease.error };
|
|
15040
15999
|
}
|
|
15041
16000
|
const item = repairScopeItemFrom(value.item, tag);
|
|
@@ -15043,20 +16002,20 @@ function repairApplyFrom(value, tag) {
|
|
|
15043
16002
|
}
|
|
15044
16003
|
function repairAuditFrom(value, tag) {
|
|
15045
16004
|
const apply = repairApplyFrom(value, tag);
|
|
15046
|
-
if (apply.value === void 0 || !
|
|
16005
|
+
if (apply.value === void 0 || !isObject12(value) || !isNonEmptyString11(value.actor)) {
|
|
15047
16006
|
return { error: apply.error ?? "audit actor is required" };
|
|
15048
16007
|
}
|
|
15049
16008
|
return { value: { ...apply.value, actor: value.actor } };
|
|
15050
16009
|
}
|
|
15051
16010
|
function repairClearFrom(value) {
|
|
15052
16011
|
const lease = repairLeaseFrom(value);
|
|
15053
|
-
if (lease.value === void 0 || !
|
|
16012
|
+
if (lease.value === void 0 || !isObject12(value) || !isNonEmptyString11(value.attemptId) || !isNonNegativeInteger4(value.scopeVersion)) {
|
|
15054
16013
|
return { error: lease.error ?? "attemptId and scopeVersion are required" };
|
|
15055
16014
|
}
|
|
15056
16015
|
return { value: { ...lease.value, attemptId: value.attemptId, scopeVersion: value.scopeVersion } };
|
|
15057
16016
|
}
|
|
15058
16017
|
function outboxPendingFrom(value) {
|
|
15059
|
-
if (!
|
|
16018
|
+
if (!isObject12(value)) {
|
|
15060
16019
|
return { error: "outbox pending request must be an object" };
|
|
15061
16020
|
}
|
|
15062
16021
|
const nowMs = value.nowMs ?? Date.now();
|
|
@@ -15074,22 +16033,22 @@ function outboxPendingFrom(value) {
|
|
|
15074
16033
|
return { value: { nowMs, limit, force } };
|
|
15075
16034
|
}
|
|
15076
16035
|
function outboxMarkFrom(value) {
|
|
15077
|
-
if (!
|
|
16036
|
+
if (!isObject12(value) || !Array.isArray(value.deliveries) || value.deliveries.length === 0) {
|
|
15078
16037
|
return { error: "deliveries must be a non-empty array" };
|
|
15079
16038
|
}
|
|
15080
16039
|
const deliveries = [];
|
|
15081
16040
|
for (const raw of value.deliveries) {
|
|
15082
|
-
if (!
|
|
16041
|
+
if (!isObject12(raw) || !isNonEmptyString11(raw.attemptId) || !isNonEmptyString11(raw.eventId) || !isNonEmptyString11(raw.suid) || typeof raw.payload !== "string" || !isSafeInteger(raw.enqueuedAt)) {
|
|
15083
16042
|
return { error: "each delivery needs attemptId, eventId, suid, payload, and enqueuedAt" };
|
|
15084
16043
|
}
|
|
15085
16044
|
if (!isNonEmptyString11(raw.eventType) || raw.provenance !== "g32" || !isNonEmptyString11(raw.timestamp) || !isNonEmptyString11(raw.allocatorLineageId)) return { error: "delivery must carry the complete G32 identity" };
|
|
15086
|
-
if (!
|
|
16045
|
+
if (!isObject12(raw.completeness)) {
|
|
15087
16046
|
return { error: "delivery must carry a readable global receipt/membership join proof" };
|
|
15088
16047
|
}
|
|
15089
16048
|
const completeness = raw.completeness;
|
|
15090
16049
|
const obligationSequence = completeness.obligationSequence;
|
|
15091
16050
|
const memberships = completeness.localCommittedMembership;
|
|
15092
|
-
if (!isNonEmptyString11(completeness.eventDigest) || !isSafeInteger(obligationSequence) || obligationSequence < 1 || !Array.isArray(memberships) || memberships.length !== 1 || !
|
|
16051
|
+
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
16052
|
return { error: "delivery must carry a readable global receipt/membership join proof" };
|
|
15094
16053
|
}
|
|
15095
16054
|
const membership = memberships[0];
|
|
@@ -15128,7 +16087,7 @@ function outboxMarkFrom(value) {
|
|
|
15128
16087
|
return { value: { deliveries, nowMs } };
|
|
15129
16088
|
}
|
|
15130
16089
|
function g44SourceScanFrom(value) {
|
|
15131
|
-
if (!
|
|
16090
|
+
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
16091
|
return { error: "source scan needs serviceId, tag, and a bounded compound cursor" };
|
|
15133
16092
|
}
|
|
15134
16093
|
return {
|
|
@@ -15142,7 +16101,7 @@ function g44SourceScanFrom(value) {
|
|
|
15142
16101
|
};
|
|
15143
16102
|
}
|
|
15144
16103
|
function g46TagStateSourceFrom(value) {
|
|
15145
|
-
if (!
|
|
16104
|
+
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
16105
|
return { error: "G46 source read needs tag, cursor, optional through, and a bounded limit" };
|
|
15147
16106
|
}
|
|
15148
16107
|
try {
|
|
@@ -15316,8 +16275,8 @@ function logicalNow(record) {
|
|
|
15316
16275
|
return record.clockNowMs ?? Date.now() + record.clockOffsetMs;
|
|
15317
16276
|
}
|
|
15318
16277
|
function expireReservation(record) {
|
|
15319
|
-
const
|
|
15320
|
-
if (
|
|
16278
|
+
const active2 = record.activeReservation;
|
|
16279
|
+
if (active2 === null || active2.expiresAt > logicalNow(record)) {
|
|
15321
16280
|
return { record, expired: false };
|
|
15322
16281
|
}
|
|
15323
16282
|
return {
|
|
@@ -15435,7 +16394,7 @@ var TagDurableObject = class {
|
|
|
15435
16394
|
const url = new URL(request.url);
|
|
15436
16395
|
const tag = url.searchParams.get("__tag");
|
|
15437
16396
|
if (!isNonEmptyString11(tag)) {
|
|
15438
|
-
return
|
|
16397
|
+
return error9(400, "tag_identity_required", "Tag identity is required");
|
|
15439
16398
|
}
|
|
15440
16399
|
const serviceId = url.searchParams.get("__serviceId");
|
|
15441
16400
|
if (request.method === "GET" && url.pathname === "/head-facts") {
|
|
@@ -15443,10 +16402,10 @@ var TagDurableObject = class {
|
|
|
15443
16402
|
observation.markFirstStorageRead();
|
|
15444
16403
|
try {
|
|
15445
16404
|
const facts = this.readHeadFacts(tag);
|
|
15446
|
-
return facts === void 0 ?
|
|
16405
|
+
return facts === void 0 ? error9(404, "tag_not_found", "Tag has no durable state yet") : json11(facts);
|
|
15447
16406
|
} catch (caught) {
|
|
15448
16407
|
if (caught instanceof TagIdentityConflict) {
|
|
15449
|
-
return
|
|
16408
|
+
return error9(409, "tag_identity_conflict", "Tag Durable Object identity changed");
|
|
15450
16409
|
}
|
|
15451
16410
|
throw caught;
|
|
15452
16411
|
}
|
|
@@ -15457,9 +16416,9 @@ var TagDurableObject = class {
|
|
|
15457
16416
|
observation.markFirstStorageRead();
|
|
15458
16417
|
const record = this.readStoredRecord(tag);
|
|
15459
16418
|
if (record === void 0) {
|
|
15460
|
-
return
|
|
16419
|
+
return error9(404, "tag_not_found", "Tag has no durable state yet");
|
|
15461
16420
|
}
|
|
15462
|
-
return record.tag === tag ?
|
|
16421
|
+
return record.tag === tag ? json11(requireG32TagRecord(record)) : error9(409, "tag_identity_conflict", "Tag Durable Object identity changed");
|
|
15463
16422
|
});
|
|
15464
16423
|
}
|
|
15465
16424
|
if (request.method === "GET" && url.pathname === "/repair/facts") {
|
|
@@ -15467,7 +16426,7 @@ var TagDurableObject = class {
|
|
|
15467
16426
|
}
|
|
15468
16427
|
if (request.method === "POST" && url.pathname === "/debug/alarm") {
|
|
15469
16428
|
const record = await this.runAlarm();
|
|
15470
|
-
return record === void 0 ?
|
|
16429
|
+
return record === void 0 ? error9(404, "tag_not_found", "Tag has no durable state yet") : json11(record);
|
|
15471
16430
|
}
|
|
15472
16431
|
if (request.method === "POST" && url.pathname === "/acquire") {
|
|
15473
16432
|
return this.traceCommitActor(request, tag, serviceId, activation2, observation, (body2) => this.acquire(tag, body2, observation));
|
|
@@ -15490,37 +16449,37 @@ var TagDurableObject = class {
|
|
|
15490
16449
|
}
|
|
15491
16450
|
const body = await this.jsonBody(request);
|
|
15492
16451
|
if (body === void 0) {
|
|
15493
|
-
return
|
|
16452
|
+
return error9(400, "malformed_tag_request", "Request body must be JSON");
|
|
15494
16453
|
}
|
|
15495
16454
|
if (request.method === "POST" && url.pathname === "/__internal/g46/tag-state-incremental") {
|
|
15496
16455
|
if (request.headers.get("x-sdt-g46-source-read") !== "1") {
|
|
15497
|
-
return
|
|
16456
|
+
return error9(403, "g46_source_read_forbidden", "G46 source reads require the TagState adapter");
|
|
15498
16457
|
}
|
|
15499
16458
|
const parsed = g46TagStateSourceFrom(body);
|
|
15500
16459
|
if (parsed.value === void 0 || parsed.value.tag !== tag) {
|
|
15501
|
-
return
|
|
16460
|
+
return error9(400, "g46_source_read_invalid", parsed.error ?? "G46 source read is invalid");
|
|
15502
16461
|
}
|
|
15503
16462
|
try {
|
|
15504
|
-
return
|
|
16463
|
+
return json11(await this.g43TagStateIncrementalCatchUp(parsed.value));
|
|
15505
16464
|
} catch (caught) {
|
|
15506
16465
|
if (caught instanceof TagIdentityConflict) {
|
|
15507
|
-
return
|
|
16466
|
+
return error9(409, "tag_identity_conflict", "Tag Durable Object identity changed");
|
|
15508
16467
|
}
|
|
15509
|
-
return
|
|
16468
|
+
return error9(503, "g46_source_read_unavailable", caught instanceof Error ? caught.message : "G46 source read is unavailable");
|
|
15510
16469
|
}
|
|
15511
16470
|
}
|
|
15512
16471
|
if (request.method === "POST" && url.pathname === "/__internal/g44/source-obligations") {
|
|
15513
16472
|
if (request.headers.get("x-sdt-g44-source-scan") !== "1") {
|
|
15514
|
-
return
|
|
16473
|
+
return error9(403, "g44_source_scan_forbidden", "G44 source scan requires the internal scanner seam");
|
|
15515
16474
|
}
|
|
15516
16475
|
const parsed = g44SourceScanFrom(body);
|
|
15517
16476
|
if (parsed.value === void 0 || parsed.value.tag !== tag || parsed.value.serviceId !== serviceId) {
|
|
15518
|
-
return
|
|
16477
|
+
return error9(400, "g44_source_scan_invalid", parsed.error ?? "G44 source scan identity is invalid");
|
|
15519
16478
|
}
|
|
15520
16479
|
try {
|
|
15521
|
-
return
|
|
16480
|
+
return json11(await this.g44ReadSourceObligations(parsed.value));
|
|
15522
16481
|
} catch (failure3) {
|
|
15523
|
-
return
|
|
16482
|
+
return error9(503, "g44_source_scan_unavailable", failure3 instanceof Error ? failure3.message : "G44 source scan is unavailable");
|
|
15524
16483
|
}
|
|
15525
16484
|
}
|
|
15526
16485
|
if (request.method === "POST" && url.pathname === "/bootstrap/admit") {
|
|
@@ -15567,7 +16526,7 @@ var TagDurableObject = class {
|
|
|
15567
16526
|
}
|
|
15568
16527
|
if (request.method === "POST" && url.pathname === "/__internal/g77/inspect-target") {
|
|
15569
16528
|
if (!isNonEmptyString11(serviceId)) {
|
|
15570
|
-
return
|
|
16529
|
+
return error9(400, "service_identity_required", "Service identity is required");
|
|
15571
16530
|
}
|
|
15572
16531
|
return this.inspectG77Target(serviceId, tag, body);
|
|
15573
16532
|
}
|
|
@@ -15575,28 +16534,28 @@ var TagDurableObject = class {
|
|
|
15575
16534
|
return this.fenceAbsentG77Target(tag, body);
|
|
15576
16535
|
}
|
|
15577
16536
|
if (request.method === "POST" && url.pathname === "/__internal/g77/capabilities") {
|
|
15578
|
-
return
|
|
16537
|
+
return json11({ inspectTarget: true, fenceAbsentTarget: true });
|
|
15579
16538
|
}
|
|
15580
|
-
return
|
|
16539
|
+
return error9(404, "tag_route_not_found", "Tag route was not found");
|
|
15581
16540
|
}
|
|
15582
16541
|
/** Identity-bound installed/absent inspection for G77 issuance reconciliation. */
|
|
15583
16542
|
inspectG77Target(serviceId, tag, body) {
|
|
15584
|
-
if (!
|
|
15585
|
-
return
|
|
16543
|
+
if (!isObject12(body) || !isNonEmptyString11(body.eventId) || !isNonEmptyString11(body.suid)) {
|
|
16544
|
+
return error9(400, "invalid_inspect_target", "eventId and suid are required");
|
|
15586
16545
|
}
|
|
15587
16546
|
const pinnedWriterEpoch = isNonNegativeInteger4(body.pinnedWriterEpoch) ? body.pinnedWriterEpoch : 0;
|
|
15588
16547
|
const attemptId = isNonEmptyString11(body.attemptId) ? body.attemptId : void 0;
|
|
15589
16548
|
const sql = this.sqlStorage();
|
|
15590
|
-
if (sql === void 0) return
|
|
16549
|
+
if (sql === void 0) return error9(503, "tag_sql_unavailable", "Tag SQL storage is unavailable");
|
|
15591
16550
|
const identity2 = sql.exec("SELECT tag FROM tag_identity WHERE singleton = 1").toArray()[0];
|
|
15592
16551
|
if (identity2 === void 0) {
|
|
15593
16552
|
if (attemptId !== void 0) {
|
|
15594
|
-
return
|
|
16553
|
+
return json11({ inspectionStatus: "absent-never-contacted" });
|
|
15595
16554
|
}
|
|
15596
|
-
return
|
|
16555
|
+
return error9(409, "target_not_terminal", "Target is not terminal under pinned writer authority");
|
|
15597
16556
|
}
|
|
15598
16557
|
if (sqlString(identity2.tag, "tag_identity.tag") !== tag) {
|
|
15599
|
-
return
|
|
16558
|
+
return error9(409, "tag_identity_mismatch", "Tag identity does not match the inspected tag");
|
|
15600
16559
|
}
|
|
15601
16560
|
const event = sql.exec(`
|
|
15602
16561
|
SELECT attempt_id, event_id, suid FROM tag_event
|
|
@@ -15609,41 +16568,41 @@ var TagDurableObject = class {
|
|
|
15609
16568
|
`, serviceId, body.eventId).toArray()[0];
|
|
15610
16569
|
if (obligation !== void 0) {
|
|
15611
16570
|
const obligationDigest = sqlString(obligation.event_digest, "tag_outbox_obligation.event_digest");
|
|
15612
|
-
return
|
|
16571
|
+
return json11({
|
|
15613
16572
|
terminalStatus: "installed-and-covered",
|
|
15614
16573
|
eventId: body.eventId,
|
|
15615
16574
|
obligationDigest
|
|
15616
16575
|
});
|
|
15617
16576
|
}
|
|
15618
|
-
return
|
|
16577
|
+
return error9(409, "target_not_terminal", "Target append is in flight under pinned writer authority");
|
|
15619
16578
|
}
|
|
15620
16579
|
if (attemptId !== void 0) {
|
|
15621
16580
|
const tombstone = sql.exec("SELECT epoch FROM tag_tombstone WHERE attempt_id = ?", attemptId).toArray()[0];
|
|
15622
16581
|
const tombstoneEpoch = tombstone === void 0 ? void 0 : sqlNumber(tombstone.epoch, "tag_tombstone.epoch");
|
|
15623
16582
|
if (tombstoneEpoch !== void 0 && pinnedWriterEpoch <= tombstoneEpoch) {
|
|
15624
|
-
return
|
|
16583
|
+
return json11({ terminalStatus: "absent-and-irrevocably-fenced", tombstoneEpoch });
|
|
15625
16584
|
}
|
|
15626
16585
|
const reservation = sql.exec("SELECT attempt_id, expires_at FROM tag_reservation WHERE singleton = 1").toArray()[0];
|
|
15627
16586
|
if (reservation !== void 0 && sqlString(reservation.attempt_id, "tag_reservation.attempt_id") === attemptId) {
|
|
15628
|
-
return
|
|
16587
|
+
return error9(409, "target_not_terminal", "Active reservation holds writer authority");
|
|
15629
16588
|
}
|
|
15630
16589
|
const epoch2 = sql.exec("SELECT attempt_id FROM tag_epoch WHERE attempt_id = ?", attemptId).toArray()[0];
|
|
15631
16590
|
if (epoch2 !== void 0) {
|
|
15632
|
-
return
|
|
16591
|
+
return json11({ inspectionStatus: "absent-but-unfenced" });
|
|
15633
16592
|
}
|
|
15634
|
-
return
|
|
16593
|
+
return json11({ inspectionStatus: "absent-never-contacted" });
|
|
15635
16594
|
}
|
|
15636
|
-
return
|
|
16595
|
+
return error9(409, "target_not_terminal", "Target is not terminal under pinned writer authority");
|
|
15637
16596
|
}
|
|
15638
16597
|
/** Reconciler-only fence for never-contacted targets; creates durable tombstone state. */
|
|
15639
16598
|
async fenceAbsentG77Target(tag, body) {
|
|
15640
16599
|
const parsed = reservationFrom(body);
|
|
15641
16600
|
if (parsed.value === void 0) {
|
|
15642
|
-
return
|
|
16601
|
+
return error9(400, "invalid_fence_target", parsed.error ?? "attemptId and epoch are required");
|
|
15643
16602
|
}
|
|
15644
16603
|
this.ensureSqlTag(tag);
|
|
15645
16604
|
const result2 = await this.cancelSql(tag, { ...parsed.value, forceTombstone: true });
|
|
15646
|
-
return result2.status >= 200 && result2.status < 300 ?
|
|
16605
|
+
return result2.status >= 200 && result2.status < 300 ? json11(result2.body) : error9(result2.status, "fence_target_rejected", "Absent target fence was rejected");
|
|
15647
16606
|
}
|
|
15648
16607
|
async alarm() {
|
|
15649
16608
|
this.activation.beginHandler();
|
|
@@ -15990,21 +16949,21 @@ var TagDurableObject = class {
|
|
|
15990
16949
|
const result2 = await authority;
|
|
15991
16950
|
g44GlobalArrayAuthorityResultByD1.set(database, result2);
|
|
15992
16951
|
return result2;
|
|
15993
|
-
} catch (
|
|
16952
|
+
} catch (error11) {
|
|
15994
16953
|
if (g44GlobalArrayAuthorityByD1.get(database) === authority) {
|
|
15995
16954
|
g44GlobalArrayAuthorityByD1.delete(database);
|
|
15996
16955
|
}
|
|
15997
|
-
throw
|
|
16956
|
+
throw error11;
|
|
15998
16957
|
}
|
|
15999
16958
|
}
|
|
16000
16959
|
async readG44GlobalArrayAuthority(database) {
|
|
16001
16960
|
try {
|
|
16002
16961
|
await database.prepare('SELECT "EventDigest" FROM dcb_events WHERE 1 = 0').all();
|
|
16003
16962
|
return true;
|
|
16004
|
-
} catch (
|
|
16005
|
-
const message =
|
|
16963
|
+
} catch (error11) {
|
|
16964
|
+
const message = error11 instanceof Error ? error11.message : String(error11);
|
|
16006
16965
|
if (/no such (?:table|column):\s*(?:dcb_events|EventDigest)/i.test(message)) return false;
|
|
16007
|
-
throw
|
|
16966
|
+
throw error11;
|
|
16008
16967
|
}
|
|
16009
16968
|
}
|
|
16010
16969
|
async traceCommitActor(request, tag, serviceId, activation2, observation, callback) {
|
|
@@ -16014,10 +16973,10 @@ var TagDurableObject = class {
|
|
|
16014
16973
|
{ actorClass: "TAG", actorKey: `tag:${serviceId}:${tag}`, activation: activation2, observation },
|
|
16015
16974
|
async () => {
|
|
16016
16975
|
body = await this.jsonBody(request);
|
|
16017
|
-
const attemptId =
|
|
16976
|
+
const attemptId = isObject12(body) && isNonEmptyString11(body.attemptId) ? body.attemptId : void 0;
|
|
16018
16977
|
return attemptId === void 0 || !isNonEmptyString11(serviceId) ? void 0 : { attemptId, serviceId };
|
|
16019
16978
|
},
|
|
16020
|
-
async () => body === void 0 ?
|
|
16979
|
+
async () => body === void 0 ? error9(400, "malformed_tag_request", "Request body must be JSON") : callback(body)
|
|
16021
16980
|
);
|
|
16022
16981
|
}
|
|
16023
16982
|
/**
|
|
@@ -16231,6 +17190,7 @@ var TagDurableObject = class {
|
|
|
16231
17190
|
ensureSqlTag(tag) {
|
|
16232
17191
|
const sql = this.sqlStorage();
|
|
16233
17192
|
if (sql === void 0) throw new Error("Tag SQL storage is unavailable");
|
|
17193
|
+
ensureTagCommitReceiptWrittenVersion(sql);
|
|
16234
17194
|
const existing = sql.exec("SELECT tag FROM tag_identity WHERE singleton = 1").toArray()[0];
|
|
16235
17195
|
if (existing !== void 0) {
|
|
16236
17196
|
if (sqlString(existing.tag, "tag_identity.tag") !== tag) throw new Error("Tag Durable Object identity changed");
|
|
@@ -16343,17 +17303,18 @@ var TagDurableObject = class {
|
|
|
16343
17303
|
sql.exec("UPDATE tag_control SET head_suid = ?, version = ?, updated_at = ? WHERE singleton = 1", head, version, committedAt);
|
|
16344
17304
|
sql.exec("UPDATE tag_head SET service_id = ?, head_suid = ? WHERE singleton = 1", serviceId, head);
|
|
16345
17305
|
}
|
|
16346
|
-
writeCommittedSqlReceipt(sql, input, committedAt, eventCount, head, confirmsReservation) {
|
|
17306
|
+
writeCommittedSqlReceipt(sql, input, committedAt, eventCount, head, confirmsReservation, writtenVersion) {
|
|
16347
17307
|
sql.exec(`
|
|
16348
17308
|
INSERT INTO tag_commit_receipt
|
|
16349
|
-
(attempt_id, epoch, committed_at, committed_event_count, head_suid, reservation_confirmed)
|
|
16350
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
17309
|
+
(attempt_id, epoch, committed_at, committed_event_count, head_suid, reservation_confirmed, written_version)
|
|
17310
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
16351
17311
|
ON CONFLICT(attempt_id, epoch) DO UPDATE SET
|
|
16352
17312
|
committed_at = excluded.committed_at,
|
|
16353
17313
|
committed_event_count = excluded.committed_event_count,
|
|
16354
17314
|
head_suid = excluded.head_suid,
|
|
16355
|
-
reservation_confirmed = excluded.reservation_confirmed
|
|
16356
|
-
|
|
17315
|
+
reservation_confirmed = excluded.reservation_confirmed,
|
|
17316
|
+
written_version = excluded.written_version
|
|
17317
|
+
`, input.attemptId, input.epoch, committedAt, eventCount, head, confirmsReservation ? 1 : 0, writtenVersion);
|
|
16357
17318
|
}
|
|
16358
17319
|
/**
|
|
16359
17320
|
* The normal append path deliberately probes only the indexed rows it needs
|
|
@@ -16395,11 +17356,29 @@ var TagDurableObject = class {
|
|
|
16395
17356
|
return { event, obligation, exact };
|
|
16396
17357
|
});
|
|
16397
17358
|
if (existingCandidates.every(({ exact }) => exact)) {
|
|
16398
|
-
|
|
17359
|
+
const receipts = sql.exec(`
|
|
17360
|
+
SELECT epoch, committed_at, written_version, head_suid FROM tag_commit_receipt
|
|
17361
|
+
WHERE attempt_id = ?
|
|
17362
|
+
`, input.attemptId).toArray();
|
|
17363
|
+
const sameEpoch = receipts.find((row) => sqlNumber(row.epoch, "tag_commit_receipt.epoch") === input.epoch);
|
|
17364
|
+
const replayHead = input.candidates.at(-1)?.suid;
|
|
17365
|
+
const byHead = replayHead === void 0 ? [] : receipts.filter((row) => sqlString(row.head_suid, "tag_commit_receipt.head_suid") === replayHead);
|
|
17366
|
+
const receipt = sameEpoch ?? (byHead.length === 1 ? byHead[0] : void 0) ?? (receipts.length === 1 ? receipts[0] : void 0);
|
|
17367
|
+
if (receipt === void 0) {
|
|
17368
|
+
await this.rearmScheduler(txn);
|
|
17369
|
+
return { ...rejected("duplicate_receipt_missing"), body: { ...rejected("duplicate_receipt_missing").body, version } };
|
|
17370
|
+
}
|
|
17371
|
+
const storedAt = sqlNullableString(receipt.committed_at, "tag_commit_receipt.committed_at");
|
|
17372
|
+
const storedVersion = sqlNullableNumber(receipt.written_version, "tag_commit_receipt.written_version");
|
|
17373
|
+
if (storedAt === null || storedVersion === null) {
|
|
17374
|
+
await this.rearmScheduler(txn);
|
|
17375
|
+
return { ...rejected("duplicate_receipt_unknown"), body: { ...rejected("duplicate_receipt_unknown").body, version } };
|
|
17376
|
+
}
|
|
17377
|
+
return { status: 200, body: { status: "duplicate", version: storedVersion, updatedAt: storedAt } };
|
|
16399
17378
|
}
|
|
16400
|
-
const
|
|
17379
|
+
const active2 = sql.exec("SELECT * FROM tag_reservation WHERE singleton = 1").toArray()[0];
|
|
16401
17380
|
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 =
|
|
17381
|
+
let reservation = active2;
|
|
16403
17382
|
if (reservation !== void 0 && sqlNumber(reservation.expires_at, "tag_reservation.expires_at") <= now2) {
|
|
16404
17383
|
sql.exec("DELETE FROM tag_reservation WHERE singleton = 1");
|
|
16405
17384
|
version += 1;
|
|
@@ -16492,7 +17471,7 @@ var TagDurableObject = class {
|
|
|
16492
17471
|
if (input.faultInjection === "after-append-before-confirm") {
|
|
16493
17472
|
throw new AppendTransactionFault("Simulated interruption before atomic commit receipt");
|
|
16494
17473
|
}
|
|
16495
|
-
this.writeCommittedSqlReceipt(sql, input, committedAt, events.length, head, confirmsReservation);
|
|
17474
|
+
this.writeCommittedSqlReceipt(sql, input, committedAt, events.length, head, confirmsReservation, version + 1);
|
|
16496
17475
|
if (confirmsReservation) sql.exec("DELETE FROM tag_reservation WHERE singleton = 1");
|
|
16497
17476
|
await this.rearmScheduler(txn);
|
|
16498
17477
|
return {
|
|
@@ -16500,7 +17479,8 @@ var TagDurableObject = class {
|
|
|
16500
17479
|
body: {
|
|
16501
17480
|
status: "appended",
|
|
16502
17481
|
fenceGate: { checked: true, activeFenceCount: 0 },
|
|
16503
|
-
version: version + 1
|
|
17482
|
+
version: version + 1,
|
|
17483
|
+
updatedAt: committedAt
|
|
16504
17484
|
},
|
|
16505
17485
|
hopFacts
|
|
16506
17486
|
};
|
|
@@ -16901,18 +17881,20 @@ var TagDurableObject = class {
|
|
|
16901
17881
|
);
|
|
16902
17882
|
this.upsertSourcePartitionRegistration(sql, eventServiceId, record.tag, outbox.eventId);
|
|
16903
17883
|
}
|
|
17884
|
+
ensureTagCommitReceiptWrittenVersion(sql);
|
|
16904
17885
|
for (const confirmation of record.confirmations) {
|
|
16905
17886
|
sql.exec(
|
|
16906
17887
|
`
|
|
16907
17888
|
INSERT OR IGNORE INTO tag_commit_receipt
|
|
16908
|
-
(attempt_id, epoch, committed_at, committed_event_count, head_suid, reservation_confirmed)
|
|
16909
|
-
VALUES (?, ?, ?, ?, ?, 1)
|
|
17889
|
+
(attempt_id, epoch, committed_at, committed_event_count, head_suid, reservation_confirmed, written_version)
|
|
17890
|
+
VALUES (?, ?, ?, ?, ?, 1, ?)
|
|
16910
17891
|
`,
|
|
16911
17892
|
confirmation.attemptId,
|
|
16912
17893
|
confirmation.epoch,
|
|
16913
17894
|
record.updatedAt,
|
|
16914
17895
|
record.events.filter((event) => event.attemptId === confirmation.attemptId).length,
|
|
16915
|
-
record.head
|
|
17896
|
+
record.head,
|
|
17897
|
+
record.version
|
|
16916
17898
|
);
|
|
16917
17899
|
}
|
|
16918
17900
|
}
|
|
@@ -16961,13 +17943,13 @@ var TagDurableObject = class {
|
|
|
16961
17943
|
async acquire(tag, body, observation) {
|
|
16962
17944
|
const parsed = acquireFrom(body, tag);
|
|
16963
17945
|
if (parsed.value === void 0) {
|
|
16964
|
-
return
|
|
17946
|
+
return error9(400, "invalid_tag_acquire", parsed.error ?? "Invalid acquire request");
|
|
16965
17947
|
}
|
|
16966
17948
|
const input = parsed.value;
|
|
16967
17949
|
observation?.markFirstStorageRead();
|
|
16968
17950
|
if (this.sqlStorage() !== void 0) {
|
|
16969
17951
|
const result3 = await this.acquireSql(tag, input);
|
|
16970
|
-
return
|
|
17952
|
+
return json11(result3.body, result3.status);
|
|
16971
17953
|
}
|
|
16972
17954
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
16973
17955
|
const loaded = await this.recordFor(txn, tag);
|
|
@@ -17002,9 +17984,9 @@ var TagDurableObject = class {
|
|
|
17002
17984
|
body: { ...rejected("consistency_head_mismatch").body, version: record.version }
|
|
17003
17985
|
};
|
|
17004
17986
|
}
|
|
17005
|
-
const
|
|
17006
|
-
if (
|
|
17007
|
-
if (
|
|
17987
|
+
const active2 = record.activeReservation;
|
|
17988
|
+
if (active2 !== null) {
|
|
17989
|
+
if (active2.attemptId === input.attemptId && active2.epoch === input.epoch) {
|
|
17008
17990
|
return {
|
|
17009
17991
|
status: 200,
|
|
17010
17992
|
body: {
|
|
@@ -17049,18 +18031,18 @@ var TagDurableObject = class {
|
|
|
17049
18031
|
body: { status: "reserved", reservation, fenceGate, version: updated.version }
|
|
17050
18032
|
};
|
|
17051
18033
|
});
|
|
17052
|
-
return
|
|
18034
|
+
return json11(result2.body, result2.status);
|
|
17053
18035
|
}
|
|
17054
18036
|
async cancel(tag, body, observation) {
|
|
17055
18037
|
const parsed = reservationFrom(body);
|
|
17056
18038
|
if (parsed.value === void 0) {
|
|
17057
|
-
return
|
|
18039
|
+
return error9(400, "invalid_tag_cancel", parsed.error ?? "Invalid cancel request");
|
|
17058
18040
|
}
|
|
17059
18041
|
const input = parsed.value;
|
|
17060
18042
|
observation?.markFirstStorageRead();
|
|
17061
18043
|
if (this.sqlStorage() !== void 0) {
|
|
17062
18044
|
const result3 = await this.cancelSql(tag, input);
|
|
17063
|
-
return
|
|
18045
|
+
return json11(result3.body, result3.status);
|
|
17064
18046
|
}
|
|
17065
18047
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
17066
18048
|
const loaded = await this.recordFor(txn, tag);
|
|
@@ -17095,8 +18077,8 @@ var TagDurableObject = class {
|
|
|
17095
18077
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
17096
18078
|
return { ...rejected(epochError), body: { ...rejected(epochError).body, version: record.version } };
|
|
17097
18079
|
}
|
|
17098
|
-
const
|
|
17099
|
-
if (
|
|
18080
|
+
const active2 = record.activeReservation;
|
|
18081
|
+
if (active2 === null || active2.attemptId !== input.attemptId || active2.epoch !== input.epoch || active2.token !== input.reservationToken) {
|
|
17100
18082
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
17101
18083
|
return {
|
|
17102
18084
|
...rejected("reservation_token_required"),
|
|
@@ -17111,12 +18093,12 @@ var TagDurableObject = class {
|
|
|
17111
18093
|
});
|
|
17112
18094
|
return { status: 200, body: { status: "cancelled", idempotent: false, version: updated.version } };
|
|
17113
18095
|
});
|
|
17114
|
-
return
|
|
18096
|
+
return json11(result2.body, result2.status);
|
|
17115
18097
|
}
|
|
17116
18098
|
async seal(tag, body, observation) {
|
|
17117
18099
|
const parsed = epochFrom(body);
|
|
17118
18100
|
if (parsed.value === void 0) {
|
|
17119
|
-
return
|
|
18101
|
+
return error9(400, "invalid_tag_seal", parsed.error ?? "Invalid seal request");
|
|
17120
18102
|
}
|
|
17121
18103
|
const input = parsed.value;
|
|
17122
18104
|
observation?.markFirstStorageRead();
|
|
@@ -17151,7 +18133,7 @@ var TagDurableObject = class {
|
|
|
17151
18133
|
});
|
|
17152
18134
|
return { status: 200, body: { status: "sealed", highestEpoch: input.epoch, version: updated.version } };
|
|
17153
18135
|
});
|
|
17154
|
-
return
|
|
18136
|
+
return json11(result2.body, result2.status);
|
|
17155
18137
|
}
|
|
17156
18138
|
/**
|
|
17157
18139
|
* Coordinator-only import admission. This is deliberately not a flag on
|
|
@@ -17160,8 +18142,8 @@ var TagDurableObject = class {
|
|
|
17160
18142
|
*/
|
|
17161
18143
|
async bootstrapAppend(tag, body, serviceId) {
|
|
17162
18144
|
const parsed = bootstrapAppendFrom(body, tag);
|
|
17163
|
-
if (parsed.value === void 0) return
|
|
17164
|
-
if (serviceId !== parsed.value.targetServiceId) return
|
|
18145
|
+
if (parsed.value === void 0) return error9(400, "invalid_bootstrap_admission", parsed.error ?? "Invalid bootstrap admission");
|
|
18146
|
+
if (serviceId !== parsed.value.targetServiceId) return error9(409, "bootstrap_service_scope_mismatch", "Bootstrap target service does not match tag scope");
|
|
17165
18147
|
const input = parsed.value;
|
|
17166
18148
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
17167
18149
|
const loaded = await this.recordFor(txn, tag);
|
|
@@ -17182,10 +18164,10 @@ var TagDurableObject = class {
|
|
|
17182
18164
|
);
|
|
17183
18165
|
return { status: 201, body: { status: "bootstrap_admitted", version: updated.version } };
|
|
17184
18166
|
});
|
|
17185
|
-
return
|
|
18167
|
+
return json11(result2.body, result2.status);
|
|
17186
18168
|
}
|
|
17187
18169
|
async closeBootstrap(tag, body) {
|
|
17188
|
-
if (!
|
|
18170
|
+
if (!isObject12(body) || !isNonEmptyString11(body.importId) || !isEpoch(body.leaseEpoch)) return error9(400, "invalid_bootstrap_close", "bootstrap importId and leaseEpoch are required");
|
|
17189
18171
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
17190
18172
|
const loaded = await this.recordFor(txn, tag);
|
|
17191
18173
|
const record = loaded.record;
|
|
@@ -17195,7 +18177,7 @@ var TagDurableObject = class {
|
|
|
17195
18177
|
const updated = await this.commit(txn, record, { bootstrapAdmission: { ...admission, closed: true } });
|
|
17196
18178
|
return { status: 200, body: { status: "closed", version: updated.version } };
|
|
17197
18179
|
});
|
|
17198
|
-
return
|
|
18180
|
+
return json11(result2.body, result2.status);
|
|
17199
18181
|
}
|
|
17200
18182
|
directDoorbellPreflight(domainDeliveryClass) {
|
|
17201
18183
|
const domainClass = domainDeliveryClass === void 0 ? void 0 : readDomainDeliveryClass({ DOMAIN_DELIVERY_CLASS: domainDeliveryClass });
|
|
@@ -17214,7 +18196,7 @@ var TagDurableObject = class {
|
|
|
17214
18196
|
async append(tag, body, serviceId, domainDeliveryClass, observation) {
|
|
17215
18197
|
const parsed = appendFrom(body, tag);
|
|
17216
18198
|
if (parsed.value === void 0) {
|
|
17217
|
-
return
|
|
18199
|
+
return error9(400, "invalid_tag_append", parsed.error ?? "Invalid append request");
|
|
17218
18200
|
}
|
|
17219
18201
|
const input = parsed.value;
|
|
17220
18202
|
try {
|
|
@@ -17252,7 +18234,7 @@ var TagDurableObject = class {
|
|
|
17252
18234
|
if ((result3.status === 201 || result3.status === 200) && serviceId !== null && serviceId.length > 0 && sourcePartitionRegistration !== "unconfigured") {
|
|
17253
18235
|
this.scheduleSourcePartitionWatermark(tag, serviceId);
|
|
17254
18236
|
}
|
|
17255
|
-
const response4 =
|
|
18237
|
+
const response4 = json11(result3.body, result3.status);
|
|
17256
18238
|
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
18239
|
const directRows = doorbellPreflight.status === "ready" && this.env.DOWNSTREAM_DOORBELL !== void 0 ? await this.directDeliveryBeforeResponse(tag, serviceId) : void 0;
|
|
17258
18240
|
if (sourcePartitionRegistration !== "unconfigured" || sourcePartitionWasRegistered) {
|
|
@@ -17269,7 +18251,19 @@ var TagDurableObject = class {
|
|
|
17269
18251
|
const loaded = await this.recordFor(txn, tag);
|
|
17270
18252
|
let record = loaded.record;
|
|
17271
18253
|
if (batchIsExactDuplicate(record, input)) {
|
|
17272
|
-
|
|
18254
|
+
const receipts = record.writeReceipts ?? {};
|
|
18255
|
+
const sameEpoch = receipts[`${input.attemptId}:${input.epoch}`];
|
|
18256
|
+
const forAttempt = Object.entries(receipts).filter(([key]) => key.startsWith(`${input.attemptId}:`));
|
|
18257
|
+
const replayHead = input.candidates.at(-1)?.suid;
|
|
18258
|
+
const byHead = replayHead === void 0 ? [] : forAttempt.filter(([, value]) => value.headSuid === replayHead);
|
|
18259
|
+
const receipt = sameEpoch ?? (byHead.length === 1 ? byHead[0]?.[1] : void 0) ?? (forAttempt.length === 1 ? forAttempt[0]?.[1] : void 0);
|
|
18260
|
+
if (receipt === void 0) {
|
|
18261
|
+
return {
|
|
18262
|
+
...rejected("duplicate_receipt_unknown"),
|
|
18263
|
+
body: { ...rejected("duplicate_receipt_unknown").body, version: record.version }
|
|
18264
|
+
};
|
|
18265
|
+
}
|
|
18266
|
+
return { status: 200, body: { status: "duplicate", version: receipt.version, updatedAt: receipt.updatedAt } };
|
|
17273
18267
|
}
|
|
17274
18268
|
const expiry = expireReservation(record);
|
|
17275
18269
|
record = expiry.record;
|
|
@@ -17282,9 +18276,9 @@ var TagDurableObject = class {
|
|
|
17282
18276
|
await this.commitExpiryIfNeeded(txn, expiry);
|
|
17283
18277
|
return fenceGateRejected();
|
|
17284
18278
|
}
|
|
17285
|
-
const
|
|
17286
|
-
const confirmsReservation =
|
|
17287
|
-
if (
|
|
18279
|
+
const active2 = record.activeReservation;
|
|
18280
|
+
const confirmsReservation = active2 !== null && active2.attemptId === input.attemptId && active2.epoch === input.epoch && active2.token === input.reservationToken;
|
|
18281
|
+
if (active2 !== null && (active2.attemptId !== input.attemptId || active2.epoch !== input.epoch || active2.token !== input.reservationToken)) {
|
|
17288
18282
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
17289
18283
|
return {
|
|
17290
18284
|
...rejected("reservation_token_required"),
|
|
@@ -17346,13 +18340,20 @@ var TagDurableObject = class {
|
|
|
17346
18340
|
alarmDueAt: null,
|
|
17347
18341
|
confirmations: confirmsReservation ? withMaxEpoch(record.confirmations, input.attemptId, input.epoch) : record.confirmations
|
|
17348
18342
|
});
|
|
17349
|
-
|
|
18343
|
+
const recorded = {
|
|
18344
|
+
...updated,
|
|
18345
|
+
writeReceipts: {
|
|
18346
|
+
...record.writeReceipts ?? {},
|
|
18347
|
+
[`${input.attemptId}:${input.epoch}`]: { version: updated.version, updatedAt: updated.updatedAt, headSuid: updated.head }
|
|
18348
|
+
}
|
|
18349
|
+
};
|
|
18350
|
+
await this.write(txn, recorded, serviceId ?? "");
|
|
17350
18351
|
return {
|
|
17351
18352
|
status: 201,
|
|
17352
|
-
body: { status: "appended", fenceGate, version:
|
|
18353
|
+
body: { status: "appended", fenceGate, version: recorded.version, updatedAt: recorded.updatedAt }
|
|
17353
18354
|
};
|
|
17354
18355
|
});
|
|
17355
|
-
const response3 =
|
|
18356
|
+
const response3 = json11(result2.body, result2.status);
|
|
17356
18357
|
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
18358
|
const directRows = doorbellPreflight.status === "ready" && this.env.DOWNSTREAM_DOORBELL !== void 0 ? await this.directDeliveryBeforeResponse(tag, serviceId) : void 0;
|
|
17358
18359
|
withGlobalAdmission2(
|
|
@@ -17364,16 +18365,16 @@ var TagDurableObject = class {
|
|
|
17364
18365
|
return response3;
|
|
17365
18366
|
} catch (failure3) {
|
|
17366
18367
|
if (failure3 instanceof PartitionRegistrationUnavailableError) {
|
|
17367
|
-
return
|
|
18368
|
+
return error9(503, "partition_registration_unavailable", "Source partition registration is unavailable; retry the commit.", true);
|
|
17368
18369
|
}
|
|
17369
18370
|
if (failure3 instanceof AppendTransactionFault) {
|
|
17370
|
-
return
|
|
18371
|
+
return error9(
|
|
17371
18372
|
503,
|
|
17372
18373
|
"simulated_append_crash",
|
|
17373
18374
|
"Simulated interruption before atomic append and confirmation commit"
|
|
17374
18375
|
);
|
|
17375
18376
|
}
|
|
17376
|
-
return
|
|
18377
|
+
return error9(500, "tag_append_failure", "Tag append could not be persisted");
|
|
17377
18378
|
}
|
|
17378
18379
|
}
|
|
17379
18380
|
/**
|
|
@@ -17489,7 +18490,7 @@ var TagDurableObject = class {
|
|
|
17489
18490
|
let timer;
|
|
17490
18491
|
const operationResult = Promise.resolve().then(operation2).then(
|
|
17491
18492
|
(value) => ({ status: "completed", value }),
|
|
17492
|
-
(
|
|
18493
|
+
(error11) => ({ status: "failed", error: error11 })
|
|
17493
18494
|
);
|
|
17494
18495
|
const timeout = new Promise((resolve) => {
|
|
17495
18496
|
timer = setTimeout(() => resolve({ status: "timeout" }), budgetMs);
|
|
@@ -17540,14 +18541,14 @@ var TagDurableObject = class {
|
|
|
17540
18541
|
envelopeBytes,
|
|
17541
18542
|
status: "success"
|
|
17542
18543
|
});
|
|
17543
|
-
} catch (
|
|
18544
|
+
} catch (error11) {
|
|
17544
18545
|
console.warn("direct_doorbell_status", {
|
|
17545
18546
|
status: "queued-degraded",
|
|
17546
18547
|
reason: "direct_doorbell_delivery_failed",
|
|
17547
18548
|
correlationId,
|
|
17548
18549
|
envelopeBytes,
|
|
17549
|
-
failureKind: classifyDirectDoorbellFailure(
|
|
17550
|
-
error: String(
|
|
18550
|
+
failureKind: classifyDirectDoorbellFailure(error11),
|
|
18551
|
+
error: String(error11)
|
|
17551
18552
|
});
|
|
17552
18553
|
}
|
|
17553
18554
|
}
|
|
@@ -17608,7 +18609,7 @@ var TagDurableObject = class {
|
|
|
17608
18609
|
async confirm(tag, body) {
|
|
17609
18610
|
const parsed = reservationFrom(body);
|
|
17610
18611
|
if (parsed.value === void 0) {
|
|
17611
|
-
return
|
|
18612
|
+
return error9(400, "invalid_tag_confirm", parsed.error ?? "Invalid confirm request");
|
|
17612
18613
|
}
|
|
17613
18614
|
const input = parsed.value;
|
|
17614
18615
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17624,8 +18625,8 @@ var TagDurableObject = class {
|
|
|
17624
18625
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
17625
18626
|
return { ...rejected(epochError), body: { ...rejected(epochError).body, version: record.version } };
|
|
17626
18627
|
}
|
|
17627
|
-
const
|
|
17628
|
-
if (
|
|
18628
|
+
const active2 = record.activeReservation;
|
|
18629
|
+
if (active2 !== null && active2.attemptId === input.attemptId && active2.epoch === input.epoch && active2.token === input.reservationToken) {
|
|
17629
18630
|
record = await this.commitExpiryIfNeeded(txn, expiry);
|
|
17630
18631
|
return {
|
|
17631
18632
|
...rejected("append_required_for_confirmation"),
|
|
@@ -17638,12 +18639,12 @@ var TagDurableObject = class {
|
|
|
17638
18639
|
body: { ...rejected("confirmation_not_found").body, version: record.version }
|
|
17639
18640
|
};
|
|
17640
18641
|
});
|
|
17641
|
-
return
|
|
18642
|
+
return json11(result2.body, result2.status);
|
|
17642
18643
|
}
|
|
17643
18644
|
async installFence(tag, body) {
|
|
17644
18645
|
const parsed = fenceFrom(body);
|
|
17645
18646
|
if (parsed.value === void 0) {
|
|
17646
|
-
return
|
|
18647
|
+
return error9(400, "invalid_tag_fence", parsed.error ?? "Invalid fence request");
|
|
17647
18648
|
}
|
|
17648
18649
|
const input = parsed.value;
|
|
17649
18650
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17680,12 +18681,12 @@ var TagDurableObject = class {
|
|
|
17680
18681
|
});
|
|
17681
18682
|
return { status: 201, body: { status: "fence-installed", idempotent: false, version: updated.version } };
|
|
17682
18683
|
});
|
|
17683
|
-
return
|
|
18684
|
+
return json11(result2.body, result2.status);
|
|
17684
18685
|
}
|
|
17685
18686
|
async clearFence(tag, body) {
|
|
17686
18687
|
const parsed = fenceFrom(body);
|
|
17687
18688
|
if (parsed.value === void 0) {
|
|
17688
|
-
return
|
|
18689
|
+
return error9(400, "invalid_tag_fence", parsed.error ?? "Invalid fence request");
|
|
17689
18690
|
}
|
|
17690
18691
|
const input = parsed.value;
|
|
17691
18692
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17711,7 +18712,7 @@ var TagDurableObject = class {
|
|
|
17711
18712
|
});
|
|
17712
18713
|
return { status: 200, body: { status: "fence-cleared", idempotent: false, version: updated.version } };
|
|
17713
18714
|
});
|
|
17714
|
-
return
|
|
18715
|
+
return json11(result2.body, result2.status);
|
|
17715
18716
|
}
|
|
17716
18717
|
/**
|
|
17717
18718
|
* Returns Tag-side repair facts only. Journal observations are deliberately
|
|
@@ -17747,7 +18748,7 @@ var TagDurableObject = class {
|
|
|
17747
18748
|
}
|
|
17748
18749
|
};
|
|
17749
18750
|
});
|
|
17750
|
-
return
|
|
18751
|
+
return json11(result2.body, result2.status);
|
|
17751
18752
|
}
|
|
17752
18753
|
/**
|
|
17753
18754
|
* Internal outbox handoff: enqueuedAt is persisted before any Queue send so
|
|
@@ -17757,10 +18758,10 @@ var TagDurableObject = class {
|
|
|
17757
18758
|
async pendingOutbox(tag, serviceId, body) {
|
|
17758
18759
|
const parsed = outboxPendingFrom(body);
|
|
17759
18760
|
if (parsed.value === void 0) {
|
|
17760
|
-
return
|
|
18761
|
+
return error9(400, "invalid_outbox_pending", parsed.error ?? "Invalid outbox pending request");
|
|
17761
18762
|
}
|
|
17762
18763
|
if (!isNonEmptyString11(serviceId)) {
|
|
17763
|
-
return
|
|
18764
|
+
return error9(400, "outbox_service_identity_required", "Outbox delivery requires a service identity");
|
|
17764
18765
|
}
|
|
17765
18766
|
const input = parsed.value;
|
|
17766
18767
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -17836,7 +18837,7 @@ var TagDurableObject = class {
|
|
|
17836
18837
|
}
|
|
17837
18838
|
return { status: 200, body: { rows } };
|
|
17838
18839
|
});
|
|
17839
|
-
return
|
|
18840
|
+
return json11(result2.body, result2.status);
|
|
17840
18841
|
}
|
|
17841
18842
|
/**
|
|
17842
18843
|
* Source-side obligation scanner. It deliberately does not call the Queue,
|
|
@@ -17906,7 +18907,7 @@ var TagDurableObject = class {
|
|
|
17906
18907
|
}
|
|
17907
18908
|
};
|
|
17908
18909
|
});
|
|
17909
|
-
return
|
|
18910
|
+
return json11(result2.body, result2.status);
|
|
17910
18911
|
}
|
|
17911
18912
|
/** Record a retry or terminal poison disposition without changing the source event. */
|
|
17912
18913
|
async recordOutboxFailure(tag, delivery, failure3) {
|
|
@@ -17960,16 +18961,16 @@ var TagDurableObject = class {
|
|
|
17960
18961
|
async markOutboxDelivered(tag, body) {
|
|
17961
18962
|
const parsed = outboxMarkFrom(body);
|
|
17962
18963
|
if (parsed.value === void 0) {
|
|
17963
|
-
return
|
|
18964
|
+
return error9(400, "invalid_outbox_delivery_mark", parsed.error ?? "Invalid outbox delivery mark");
|
|
17964
18965
|
}
|
|
17965
18966
|
const input = parsed.value;
|
|
17966
18967
|
try {
|
|
17967
18968
|
const joined = await Promise.all(input.deliveries.map((delivery) => this.globalReceiptMatches(delivery)));
|
|
17968
18969
|
if (joined.some((value) => !value)) {
|
|
17969
|
-
return
|
|
18970
|
+
return error9(409, "outbox_global_receipt_unverified", "Global receipt and membership must be read back before source acknowledgement");
|
|
17970
18971
|
}
|
|
17971
18972
|
} catch {
|
|
17972
|
-
return
|
|
18973
|
+
return error9(503, "outbox_global_receipt_unavailable", "Global receipt verification is unavailable");
|
|
17973
18974
|
}
|
|
17974
18975
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
17975
18976
|
if (this.sqlStorage() !== void 0) {
|
|
@@ -18008,7 +19009,7 @@ var TagDurableObject = class {
|
|
|
18008
19009
|
}
|
|
18009
19010
|
return { status: 200, body: { marked, idempotent: marked === 0 } };
|
|
18010
19011
|
});
|
|
18011
|
-
return
|
|
19012
|
+
return json11(result2.body, result2.status);
|
|
18012
19013
|
}
|
|
18013
19014
|
/**
|
|
18014
19015
|
* The delivery handoff reads its universe from `tag_outbox_obligation`.
|
|
@@ -18147,7 +19148,7 @@ var TagDurableObject = class {
|
|
|
18147
19148
|
async acquireRepairLease(tag, body) {
|
|
18148
19149
|
const parsed = repairAcquireFrom(body, tag);
|
|
18149
19150
|
if (parsed.value === void 0) {
|
|
18150
|
-
return
|
|
19151
|
+
return error9(400, "invalid_repair_acquire", parsed.error ?? "Invalid repair acquire request");
|
|
18151
19152
|
}
|
|
18152
19153
|
const input = parsed.value;
|
|
18153
19154
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18193,12 +19194,12 @@ var TagDurableObject = class {
|
|
|
18193
19194
|
}
|
|
18194
19195
|
};
|
|
18195
19196
|
});
|
|
18196
|
-
return
|
|
19197
|
+
return json11(result2.body, result2.status);
|
|
18197
19198
|
}
|
|
18198
19199
|
async renewRepairLease(tag, body) {
|
|
18199
19200
|
const parsed = repairLeaseFrom(body);
|
|
18200
19201
|
if (parsed.value === void 0) {
|
|
18201
|
-
return
|
|
19202
|
+
return error9(400, "invalid_repair_renew", parsed.error ?? "Invalid repair renew request");
|
|
18202
19203
|
}
|
|
18203
19204
|
const input = parsed.value;
|
|
18204
19205
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18222,12 +19223,12 @@ var TagDurableObject = class {
|
|
|
18222
19223
|
}
|
|
18223
19224
|
};
|
|
18224
19225
|
});
|
|
18225
|
-
return
|
|
19226
|
+
return json11(result2.body, result2.status);
|
|
18226
19227
|
}
|
|
18227
19228
|
async unionRepairScope(tag, body) {
|
|
18228
19229
|
const parsed = repairScopeUnionFrom(body, tag);
|
|
18229
19230
|
if (parsed.value === void 0) {
|
|
18230
|
-
return
|
|
19231
|
+
return error9(400, "invalid_repair_scope_union", parsed.error ?? "Invalid repair scope union request");
|
|
18231
19232
|
}
|
|
18232
19233
|
const input = parsed.value;
|
|
18233
19234
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18263,12 +19264,12 @@ var TagDurableObject = class {
|
|
|
18263
19264
|
body: { status: "repair-scope-unioned", scopeVersion: updated.repairScopeVersion, scope: updated.repairScope }
|
|
18264
19265
|
};
|
|
18265
19266
|
});
|
|
18266
|
-
return
|
|
19267
|
+
return json11(result2.body, result2.status);
|
|
18267
19268
|
}
|
|
18268
19269
|
async applyRepair(tag, body) {
|
|
18269
19270
|
const parsed = repairApplyFrom(body, tag);
|
|
18270
19271
|
if (parsed.value === void 0) {
|
|
18271
|
-
return
|
|
19272
|
+
return error9(400, "invalid_repair_apply", parsed.error ?? "Invalid repair apply request");
|
|
18272
19273
|
}
|
|
18273
19274
|
const input = parsed.value;
|
|
18274
19275
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18349,12 +19350,12 @@ var TagDurableObject = class {
|
|
|
18349
19350
|
body: { status: branch, idempotent: false, head: (updatedRecord ?? record).head, version: (updatedRecord ?? record).version }
|
|
18350
19351
|
};
|
|
18351
19352
|
});
|
|
18352
|
-
return
|
|
19353
|
+
return json11(result2.body, result2.status);
|
|
18353
19354
|
}
|
|
18354
19355
|
async auditRepair(tag, body) {
|
|
18355
19356
|
const parsed = repairAuditFrom(body, tag);
|
|
18356
19357
|
if (parsed.value === void 0) {
|
|
18357
|
-
return
|
|
19358
|
+
return error9(400, "invalid_repair_audit", parsed.error ?? "Invalid repair audit request");
|
|
18358
19359
|
}
|
|
18359
19360
|
const input = parsed.value;
|
|
18360
19361
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18391,12 +19392,12 @@ var TagDurableObject = class {
|
|
|
18391
19392
|
await txn.put(REPAIR_FACTS_KEY, { ...facts, audits: [...facts.audits, audit] });
|
|
18392
19393
|
return { status: 200, body: { status: "repair-audited", idempotent: false, branch: audit.branch } };
|
|
18393
19394
|
});
|
|
18394
|
-
return
|
|
19395
|
+
return json11(result2.body, result2.status);
|
|
18395
19396
|
}
|
|
18396
19397
|
async clearRepairFence(tag, body) {
|
|
18397
19398
|
const parsed = repairClearFrom(body);
|
|
18398
19399
|
if (parsed.value === void 0) {
|
|
18399
|
-
return
|
|
19400
|
+
return error9(400, "invalid_repair_clear", parsed.error ?? "Invalid repair clear request");
|
|
18400
19401
|
}
|
|
18401
19402
|
const input = parsed.value;
|
|
18402
19403
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18427,16 +19428,16 @@ var TagDurableObject = class {
|
|
|
18427
19428
|
});
|
|
18428
19429
|
return { status: 200, body: { status: "repair-fence-cleared", idempotent: false, version: updated.version } };
|
|
18429
19430
|
});
|
|
18430
|
-
return
|
|
19431
|
+
return json11(result2.body, result2.status);
|
|
18431
19432
|
}
|
|
18432
19433
|
async setClockOffset(tag, body) {
|
|
18433
|
-
if (!
|
|
18434
|
-
return
|
|
19434
|
+
if (!isObject12(body)) {
|
|
19435
|
+
return error9(400, "invalid_tag_clock", "offsetMs or nowMs must be a safe integer");
|
|
18435
19436
|
}
|
|
18436
19437
|
const nowMs = body.nowMs;
|
|
18437
19438
|
const offsetMs = body.offsetMs;
|
|
18438
19439
|
if (!isSafeInteger(nowMs) && !isSafeInteger(offsetMs)) {
|
|
18439
|
-
return
|
|
19440
|
+
return error9(400, "invalid_tag_clock", "offsetMs or nowMs must be a safe integer");
|
|
18440
19441
|
}
|
|
18441
19442
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
18442
19443
|
const loaded = await this.recordFor(txn, tag);
|
|
@@ -18454,7 +19455,7 @@ var TagDurableObject = class {
|
|
|
18454
19455
|
}
|
|
18455
19456
|
};
|
|
18456
19457
|
});
|
|
18457
|
-
return
|
|
19458
|
+
return json11(result2.body, result2.status);
|
|
18458
19459
|
}
|
|
18459
19460
|
async runAlarm() {
|
|
18460
19461
|
const due = await this.ctx.storage.transaction(async (txn) => {
|
|
@@ -18573,16 +19574,16 @@ var TagStateCheckpointFault = class extends Error {
|
|
|
18573
19574
|
this.name = "TagStateCheckpointFault";
|
|
18574
19575
|
}
|
|
18575
19576
|
};
|
|
18576
|
-
function
|
|
19577
|
+
function json12(body, status = 200) {
|
|
18577
19578
|
return new Response(JSON.stringify(body), {
|
|
18578
19579
|
status,
|
|
18579
19580
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
18580
19581
|
});
|
|
18581
19582
|
}
|
|
18582
|
-
function
|
|
18583
|
-
return
|
|
19583
|
+
function error10(status, code, message) {
|
|
19584
|
+
return json12({ code, error: message }, status);
|
|
18584
19585
|
}
|
|
18585
|
-
function
|
|
19586
|
+
function isObject13(value) {
|
|
18586
19587
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18587
19588
|
}
|
|
18588
19589
|
function isNonEmptyString12(value) {
|
|
@@ -18597,7 +19598,7 @@ function sqlNullableString2(value, column) {
|
|
|
18597
19598
|
return sqlString2(value, column);
|
|
18598
19599
|
}
|
|
18599
19600
|
function parseIdentity(value) {
|
|
18600
|
-
if (!
|
|
19601
|
+
if (!isObject13(value) || !isNonEmptyString12(value.serviceId) || !isNonEmptyString12(value.tag) || !isNonEmptyString12(value.projectorId)) {
|
|
18601
19602
|
return void 0;
|
|
18602
19603
|
}
|
|
18603
19604
|
return { serviceId: value.serviceId, tag: value.tag, projectorId: value.projectorId };
|
|
@@ -18659,17 +19660,17 @@ var TagStateDurableObject = class {
|
|
|
18659
19660
|
async fetch(request) {
|
|
18660
19661
|
const url = new URL(request.url);
|
|
18661
19662
|
if (request.method !== "POST" || url.pathname !== "/read") {
|
|
18662
|
-
return
|
|
19663
|
+
return error10(404, "tag_state_route_not_found", "TagState route was not found");
|
|
18663
19664
|
}
|
|
18664
19665
|
let body;
|
|
18665
19666
|
try {
|
|
18666
19667
|
body = await request.json();
|
|
18667
19668
|
} catch {
|
|
18668
|
-
return
|
|
19669
|
+
return error10(400, "tag_state_validation_error", "TagState request must be JSON");
|
|
18669
19670
|
}
|
|
18670
19671
|
const identity2 = parseIdentity(body);
|
|
18671
19672
|
if (identity2 === void 0) {
|
|
18672
|
-
return
|
|
19673
|
+
return error10(400, "tag_state_validation_error", "TagState identity requires serviceId, tag, and projectorId");
|
|
18673
19674
|
}
|
|
18674
19675
|
return await this.read(identity2);
|
|
18675
19676
|
}
|
|
@@ -18691,16 +19692,16 @@ var TagStateDurableObject = class {
|
|
|
18691
19692
|
return await this.continueRebuild(identity2, projector, cache);
|
|
18692
19693
|
} catch (caught) {
|
|
18693
19694
|
if (caught instanceof TagStateIdentityConflict) {
|
|
18694
|
-
return
|
|
19695
|
+
return error10(409, "tag_state_identity_conflict", "TagState Durable Object identity changed");
|
|
18695
19696
|
}
|
|
18696
19697
|
if (caught instanceof TagStateUnknownProjector) {
|
|
18697
|
-
return
|
|
19698
|
+
return error10(404, "tag_state_unknown_projector", "TagState projector is not registered");
|
|
18698
19699
|
}
|
|
18699
19700
|
if (caught instanceof TagStateRegistryFailure) {
|
|
18700
|
-
return
|
|
19701
|
+
return error10(503, "tag_state_projector_registry_failure", "TagState projector registry is unavailable");
|
|
18701
19702
|
}
|
|
18702
19703
|
if (caught instanceof TagStateSourceFailure) {
|
|
18703
|
-
return
|
|
19704
|
+
return error10(caught.status, "tag_state_source_frontier_failure", "TagState source frontier could not be read");
|
|
18704
19705
|
}
|
|
18705
19706
|
if (caught instanceof TagStateCacheCorruption) {
|
|
18706
19707
|
try {
|
|
@@ -18708,21 +19709,21 @@ var TagStateDurableObject = class {
|
|
|
18708
19709
|
this.beginRebuild(projector, "cache-corrupt");
|
|
18709
19710
|
} catch (rebuildFailure) {
|
|
18710
19711
|
if (rebuildFailure instanceof TagStateRegistryFailure) {
|
|
18711
|
-
return
|
|
19712
|
+
return error10(503, "tag_state_projector_registry_failure", "TagState projector registry is unavailable");
|
|
18712
19713
|
}
|
|
18713
19714
|
}
|
|
18714
|
-
return
|
|
19715
|
+
return error10(409, "tag_state_cache_corrupt", "TagState cache is corrupt and is rebuilding");
|
|
18715
19716
|
}
|
|
18716
19717
|
if (caught instanceof TagStateCheckpointFault) {
|
|
18717
|
-
return
|
|
19718
|
+
return error10(503, "tag_state_rebuild_interrupted", "TagState replay checkpoint was interrupted");
|
|
18718
19719
|
}
|
|
18719
|
-
return
|
|
19720
|
+
return error10(503, "tag_state_unavailable", "TagState cache is unavailable");
|
|
18720
19721
|
}
|
|
18721
19722
|
}
|
|
18722
19723
|
async beginDeltaOrRespondReady(identity2, projector, cache) {
|
|
18723
19724
|
const page = await this.readSource(identity2, cache.lastSuid, void 0);
|
|
18724
19725
|
if (page.events.length === 0 && page.completeThrough !== null) {
|
|
18725
|
-
return
|
|
19726
|
+
return json12(this.success(projector, cache.stateJson, cache.lastSuid));
|
|
18726
19727
|
}
|
|
18727
19728
|
const rebuild = this.beginRebuild(projector, "normal-delta", {
|
|
18728
19729
|
cursor: cache.lastSuid,
|
|
@@ -18771,9 +19772,9 @@ var TagStateDurableObject = class {
|
|
|
18771
19772
|
});
|
|
18772
19773
|
if (this.g46CheckpointFault === "after-checkpoint") throw new TagStateCheckpointFault();
|
|
18773
19774
|
if (!complete) {
|
|
18774
|
-
return
|
|
19775
|
+
return error10(503, "tag_state_rebuild_in_progress", "TagState replay is rebuilding to its frozen frontier");
|
|
18775
19776
|
}
|
|
18776
|
-
return
|
|
19777
|
+
return json12(this.success(projector, accumulatorJson, page.completeThrough));
|
|
18777
19778
|
}
|
|
18778
19779
|
success(projector, stateJson, lastSortedUniqueId) {
|
|
18779
19780
|
try {
|
|
@@ -18830,7 +19831,7 @@ var TagStateDurableObject = class {
|
|
|
18830
19831
|
}
|
|
18831
19832
|
}
|
|
18832
19833
|
assertSourcePage(cursor, expectedThrough, page) {
|
|
18833
|
-
if (!
|
|
19834
|
+
if (!isObject13(page) || !Array.isArray(page.events) || typeof page.lastSortableUniqueId !== "string" || typeof page.through !== "string" || page.completeThrough !== null && typeof page.completeThrough !== "string") {
|
|
18834
19835
|
throw new TagStateSourceFailure(503, new Error("TagState source response was malformed"));
|
|
18835
19836
|
}
|
|
18836
19837
|
if (expectedThrough !== void 0 && expectedThrough !== null && page.through !== expectedThrough) {
|
|
@@ -19029,12 +20030,12 @@ function encodedCommitSnapshotLogBytes(record) {
|
|
|
19029
20030
|
function assertCommitSnapshotLogSize(record) {
|
|
19030
20031
|
const encodedBytes2 = encodedCommitSnapshotLogBytes(record);
|
|
19031
20032
|
if (encodedBytes2 > COMMIT_SNAPSHOT_LOG_BYTE_BUDGET) {
|
|
19032
|
-
const
|
|
20033
|
+
const error11 = new Error(
|
|
19033
20034
|
`Commit snapshot log is ${encodedBytes2} bytes; budget is ${COMMIT_SNAPSHOT_LOG_BYTE_BUDGET} bytes`
|
|
19034
20035
|
);
|
|
19035
|
-
|
|
19036
|
-
|
|
19037
|
-
throw
|
|
20036
|
+
error11.code = "commit-snapshot-log-size";
|
|
20037
|
+
error11.encodedBytes = encodedBytes2;
|
|
20038
|
+
throw error11;
|
|
19038
20039
|
}
|
|
19039
20040
|
return encodedBytes2;
|
|
19040
20041
|
}
|
|
@@ -19053,8 +20054,8 @@ function createCommitTraceConsoleSink(options = {}) {
|
|
|
19053
20054
|
if (record === void 0) return;
|
|
19054
20055
|
try {
|
|
19055
20056
|
assertCommitSnapshotLogSize(record);
|
|
19056
|
-
} catch (
|
|
19057
|
-
const encodedBytes2 = typeof
|
|
20057
|
+
} catch (error11) {
|
|
20058
|
+
const encodedBytes2 = typeof error11 === "object" && error11 !== null && "encodedBytes" in error11 && typeof error11.encodedBytes === "number" ? error11.encodedBytes : encodedCommitSnapshotLogBytes(record);
|
|
19058
20059
|
reportRejectedSize(Object.freeze({
|
|
19059
20060
|
schema: COMMIT_SNAPSHOT_LOG_SCHEMA,
|
|
19060
20061
|
event: "commit.snapshot-size-rejected",
|
|
@@ -19063,43 +20064,13 @@ function createCommitTraceConsoleSink(options = {}) {
|
|
|
19063
20064
|
encodedBytes: encodedBytes2,
|
|
19064
20065
|
byteBudget: COMMIT_SNAPSHOT_LOG_BYTE_BUDGET
|
|
19065
20066
|
}));
|
|
19066
|
-
throw
|
|
20067
|
+
throw error11;
|
|
19067
20068
|
}
|
|
19068
20069
|
log(record);
|
|
19069
20070
|
}
|
|
19070
20071
|
});
|
|
19071
20072
|
}
|
|
19072
20073
|
|
|
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
20074
|
// src/diagnostics/G65DirectRing.ts
|
|
19104
20075
|
var G65_DIRECT_RING_BUDGET_MS = 100;
|
|
19105
20076
|
var G65_DIRECT_RING_CLOCK_ORIGIN = "Date.now epoch ms";
|
|
@@ -19165,7 +20136,7 @@ async function markG65DirectApplyStarted(database, message, observedAt) {
|
|
|
19165
20136
|
WHERE service_id = ? AND event_id = ? AND attempt_id = ?
|
|
19166
20137
|
`).bind(observedAt, message.serviceId, message.eventId, message.attemptId).run();
|
|
19167
20138
|
}
|
|
19168
|
-
async function markG65DirectApplyFinished(database, message, observedAt, outcome,
|
|
20139
|
+
async function markG65DirectApplyFinished(database, message, observedAt, outcome, error11) {
|
|
19169
20140
|
validateTimestamp(observedAt, "apply finish");
|
|
19170
20141
|
if (outcome.length === 0) throw new Error("G65 direct apply outcome must be non-empty");
|
|
19171
20142
|
await database.prepare(`
|
|
@@ -19177,13 +20148,109 @@ async function markG65DirectApplyFinished(database, message, observedAt, outcome
|
|
|
19177
20148
|
`).bind(
|
|
19178
20149
|
observedAt,
|
|
19179
20150
|
outcome,
|
|
19180
|
-
|
|
20151
|
+
error11 ?? null,
|
|
19181
20152
|
message.serviceId,
|
|
19182
20153
|
message.eventId,
|
|
19183
20154
|
message.attemptId
|
|
19184
20155
|
).run();
|
|
19185
20156
|
}
|
|
19186
20157
|
|
|
20158
|
+
// src/shard/ShardRotation.ts
|
|
20159
|
+
var ShardRotationError = class extends Error {
|
|
20160
|
+
diagnostic;
|
|
20161
|
+
constructor(diagnostic) {
|
|
20162
|
+
super(diagnostic.code);
|
|
20163
|
+
this.name = "ShardRotationError";
|
|
20164
|
+
this.diagnostic = diagnostic;
|
|
20165
|
+
}
|
|
20166
|
+
};
|
|
20167
|
+
function fail(code, path, reason) {
|
|
20168
|
+
throw new ShardRotationError({ code, path, reason });
|
|
20169
|
+
}
|
|
20170
|
+
function createRotationState(active2) {
|
|
20171
|
+
if (typeof active2 !== "string" || active2.length === 0) fail("ACTIVE_COUNT", "active", "active-missing");
|
|
20172
|
+
return { sealed: [], active: active2, ledger: [] };
|
|
20173
|
+
}
|
|
20174
|
+
function assertStrictlyBefore(lastD1_1, firstD1_2) {
|
|
20175
|
+
assertSortableUniqueId(lastD1_1);
|
|
20176
|
+
assertSortableUniqueId(firstD1_2);
|
|
20177
|
+
if (compareSortableUniqueId(lastD1_1, firstD1_2) >= 0) {
|
|
20178
|
+
fail("ORDER_INVARIANT", "last(D1-1)<first(D1-2)", "order-invariant");
|
|
20179
|
+
}
|
|
20180
|
+
}
|
|
20181
|
+
function maxSortableUniqueId(values) {
|
|
20182
|
+
if (values.length === 0) fail("SEAL_MAX_MISSING", "d1-1", "seal-max-missing");
|
|
20183
|
+
let max = assertSortableUniqueId(values[0]).value;
|
|
20184
|
+
for (let i = 1; i < values.length; i += 1) {
|
|
20185
|
+
const next = assertSortableUniqueId(values[i]).value;
|
|
20186
|
+
if (next > max) max = next;
|
|
20187
|
+
}
|
|
20188
|
+
return max;
|
|
20189
|
+
}
|
|
20190
|
+
function rotateAppend(state, sealedId, shardSuids, nextActiveId) {
|
|
20191
|
+
if (sealedId !== state.active) fail("UNKNOWN_SHARD", sealedId, "unknown-shard");
|
|
20192
|
+
if (typeof nextActiveId !== "string" || nextActiveId.length === 0 || nextActiveId === sealedId) {
|
|
20193
|
+
fail("ACTIVE_COUNT", "active", "active-count");
|
|
20194
|
+
}
|
|
20195
|
+
if (state.sealed.some((entry) => entry.id === nextActiveId)) {
|
|
20196
|
+
fail("ACTIVE_COUNT", nextActiveId, "active-already-sealed");
|
|
20197
|
+
}
|
|
20198
|
+
if (state.sealed.some((entry) => entry.id === sealedId)) fail("IDENTITY_SWAP", sealedId, "identity-swap");
|
|
20199
|
+
const lastSuid = maxSortableUniqueId(shardSuids);
|
|
20200
|
+
return {
|
|
20201
|
+
sealed: [...state.sealed, { id: sealedId, lastSuid }],
|
|
20202
|
+
active: nextActiveId,
|
|
20203
|
+
ledger: state.ledger
|
|
20204
|
+
};
|
|
20205
|
+
}
|
|
20206
|
+
function replaceSealedIdentity(state, index, nextId) {
|
|
20207
|
+
if (index < 0 || index >= state.sealed.length) fail("UNKNOWN_SHARD", "sealed", "unknown-shard");
|
|
20208
|
+
fail("IDENTITY_SWAP", state.sealed[index].id, `identity-swap:${nextId}`);
|
|
20209
|
+
}
|
|
20210
|
+
function refuseSealedWrite(state, shardId) {
|
|
20211
|
+
if (state.sealed.some((entry) => entry.id === shardId)) fail("SEALED_WRITE", shardId, "sealed-write");
|
|
20212
|
+
}
|
|
20213
|
+
function admitActiveWrite(state, shardId, firstOrNextSuid) {
|
|
20214
|
+
assertSortableUniqueId(firstOrNextSuid);
|
|
20215
|
+
refuseSealedWrite(state, shardId);
|
|
20216
|
+
if (shardId !== state.active) fail("UNKNOWN_SHARD", shardId, "unknown-shard");
|
|
20217
|
+
if (state.sealed.length === 0) return;
|
|
20218
|
+
const previous = state.sealed[state.sealed.length - 1];
|
|
20219
|
+
assertStrictlyBefore(previous.lastSuid, firstOrNextSuid);
|
|
20220
|
+
}
|
|
20221
|
+
function recordLateArrival(state, sealedShardId, suid) {
|
|
20222
|
+
assertSortableUniqueId(suid);
|
|
20223
|
+
const sealed = state.sealed.find((entry) => entry.id === sealedShardId);
|
|
20224
|
+
if (sealed === void 0) fail("UNKNOWN_SHARD", sealedShardId, "unknown-shard");
|
|
20225
|
+
if (compareSortableUniqueId(suid, sealed.lastSuid) > 0) {
|
|
20226
|
+
fail("ORDER_INVARIANT", sealedShardId, "not-late-arrival");
|
|
20227
|
+
}
|
|
20228
|
+
return {
|
|
20229
|
+
...state,
|
|
20230
|
+
ledger: [...state.ledger, { shardId: sealed.id, suid, reason: "sealed-range" }]
|
|
20231
|
+
};
|
|
20232
|
+
}
|
|
20233
|
+
var SILENT_MERGE_TOKENS = [
|
|
20234
|
+
["merge", "Ledger", "Into", "Sealed"],
|
|
20235
|
+
["merge", "OnRead", "Sealed"],
|
|
20236
|
+
["silent", "Merge", "Ledger"],
|
|
20237
|
+
["ledger", ".concat(", "sealed"],
|
|
20238
|
+
["sealed", ".concat(", "ledger"]
|
|
20239
|
+
];
|
|
20240
|
+
function assertNoSilentLedgerMerge(source) {
|
|
20241
|
+
const lowered = source.toLowerCase();
|
|
20242
|
+
for (const token of SILENT_MERGE_TOKENS) {
|
|
20243
|
+
const needle = token.join("").toLowerCase();
|
|
20244
|
+
if (lowered.includes(needle)) fail("SILENT_MERGE", "ledger", "silent-merge");
|
|
20245
|
+
}
|
|
20246
|
+
}
|
|
20247
|
+
function refuseSilentLedgerMerge() {
|
|
20248
|
+
fail("SILENT_MERGE", "ledger", "silent-merge");
|
|
20249
|
+
}
|
|
20250
|
+
function refuseSafeWindowSeal() {
|
|
20251
|
+
fail("SAFEWINDOW_SEAL", "safe-window", "safewindow-seal");
|
|
20252
|
+
}
|
|
20253
|
+
|
|
19187
20254
|
// src/cloudflare.ts
|
|
19188
20255
|
function cloudflareCommitTraceProvider(request, env) {
|
|
19189
20256
|
const versionId = env.WORKER_VERSION?.id;
|
|
@@ -19224,6 +20291,22 @@ function createCloudflareOnlyRuntimeWorker(options = {}) {
|
|
|
19224
20291
|
const storeProvider = createD1StoreProvider();
|
|
19225
20292
|
return {
|
|
19226
20293
|
async fetch(request, env, ctx) {
|
|
20294
|
+
const url = new URL(request.url);
|
|
20295
|
+
if (isIncidentMaintenancePath(url.pathname)) {
|
|
20296
|
+
return handleIncidentMaintenance(request, env, options.serviceIdentityProvider ?? envServiceIdentity(env));
|
|
20297
|
+
}
|
|
20298
|
+
const operatorRepair = url.pathname === "/operator/repair";
|
|
20299
|
+
const operatorBootstrap = url.pathname.startsWith("/operator/bootstrap/");
|
|
20300
|
+
if (operatorRepair) {
|
|
20301
|
+
const methodDenied = nonPostOperatorRepairResponse(request);
|
|
20302
|
+
if (methodDenied !== void 0) return methodDenied;
|
|
20303
|
+
const denied = await authenticateOperatorRepair(request, env.REPAIR_OPERATOR_TOKEN);
|
|
20304
|
+
if (denied !== void 0) return denied;
|
|
20305
|
+
}
|
|
20306
|
+
if (operatorBootstrap) {
|
|
20307
|
+
const denied = await authenticateOperatorBootstrap(request, env.REPAIR_OPERATOR_TOKEN);
|
|
20308
|
+
if (denied !== void 0) return denied;
|
|
20309
|
+
}
|
|
19227
20310
|
const serviceIdentity = options.serviceIdentityProvider ?? envServiceIdentity(env);
|
|
19228
20311
|
const requestIdentityOptions = { allowG11Verification: env.G11_VERIFICATION_ENABLED === "true" };
|
|
19229
20312
|
let requestServiceId;
|
|
@@ -19233,7 +20316,6 @@ function createCloudflareOnlyRuntimeWorker(options = {}) {
|
|
|
19233
20316
|
return scopeIdentityMissingResponse();
|
|
19234
20317
|
}
|
|
19235
20318
|
const durableHopObserver = createG60DurableHopObserver(env.D1, (promise) => ctx.waitUntil(promise));
|
|
19236
|
-
const url = new URL(request.url);
|
|
19237
20319
|
if (url.pathname === "/api/sekiban/serialized/commit") {
|
|
19238
20320
|
return handleSerializedCommit(request, env, {
|
|
19239
20321
|
domainDeliveryClass: options.config?.deliveryClass,
|
|
@@ -19277,19 +20359,26 @@ function createCloudflareOnlyRuntimeWorker(options = {}) {
|
|
|
19277
20359
|
serviceIdentityProvider: serviceIdentity
|
|
19278
20360
|
});
|
|
19279
20361
|
}
|
|
19280
|
-
if (
|
|
19281
|
-
return
|
|
20362
|
+
if (operatorRepair) {
|
|
20363
|
+
return handleAuthorizedOperatorRepair(request, env, cloudflareTracing(ctx), serviceIdentity);
|
|
19282
20364
|
}
|
|
19283
|
-
if (
|
|
19284
|
-
return
|
|
20365
|
+
if (operatorBootstrap) {
|
|
20366
|
+
return handleAuthorizedOperatorBootstrap(request, env, storeProvider, {
|
|
19285
20367
|
afterVerifyBeforeReady: options.afterBootstrapVerify === void 0 ? void 0 : ({ serviceId }) => options.afterBootstrapVerify({ serviceId, env })
|
|
19286
20368
|
});
|
|
19287
20369
|
}
|
|
19288
20370
|
if (url.pathname === "/internal/downstream/drain" && request.method === "POST") {
|
|
19289
20371
|
if (durableHopObserver === void 0) {
|
|
19290
|
-
return handleOutboxDrainRequest(request, env, {
|
|
20372
|
+
return handleOutboxDrainRequest(request, env, {
|
|
20373
|
+
acknowledgement: "global-receipt",
|
|
20374
|
+
serviceIdentityProvider: serviceIdentity
|
|
20375
|
+
});
|
|
19291
20376
|
}
|
|
19292
|
-
return handleOutboxDrainRequest(request, env, {
|
|
20377
|
+
return handleOutboxDrainRequest(request, env, {
|
|
20378
|
+
acknowledgement: "global-receipt",
|
|
20379
|
+
durableHopObserver,
|
|
20380
|
+
serviceIdentityProvider: serviceIdentity
|
|
20381
|
+
});
|
|
19293
20382
|
}
|
|
19294
20383
|
if (url.pathname === "/internal/projection/lag") {
|
|
19295
20384
|
return handleProjectionLag(request, env, composition.projectors, storeProvider, serviceIdentity);
|
|
@@ -19440,14 +20529,20 @@ export {
|
|
|
19440
20529
|
MAX_SERVICE_BINDING_INVOCATIONS_PER_REQUEST,
|
|
19441
20530
|
ScopeNameError,
|
|
19442
20531
|
ServiceIdentityMissingError,
|
|
20532
|
+
ShardRotationError,
|
|
19443
20533
|
TEST_SERVICE_ID_HEADER,
|
|
19444
20534
|
TagDurableObject2 as TagDurableObject,
|
|
19445
20535
|
TagStateDurableObject2 as TagStateDurableObject,
|
|
20536
|
+
admitActiveWrite,
|
|
20537
|
+
assertNoSilentLedgerMerge,
|
|
20538
|
+
assertStrictlyBefore,
|
|
19446
20539
|
buildScopeName,
|
|
20540
|
+
checkConfiguredBearer,
|
|
19447
20541
|
classifyDirectDoorbellFailure,
|
|
19448
20542
|
cleanupG42JournalProbeTrial,
|
|
19449
20543
|
createCloudflareOnlyRuntimeWorker,
|
|
19450
20544
|
createG60DurableHopObserver,
|
|
20545
|
+
createRotationState,
|
|
19451
20546
|
cloudflare_default as default,
|
|
19452
20547
|
deliveryCorrelationId,
|
|
19453
20548
|
downstreamEnvelopeBytes,
|
|
@@ -19460,6 +20555,7 @@ export {
|
|
|
19460
20555
|
isScopeServiceId,
|
|
19461
20556
|
markG65DirectApplyFinished,
|
|
19462
20557
|
markG65DirectApplyStarted,
|
|
20558
|
+
maxSortableUniqueId,
|
|
19463
20559
|
measureG42JournalProbeTrial,
|
|
19464
20560
|
observeFaultBarrier,
|
|
19465
20561
|
observeG60UnsafeWriter,
|
|
@@ -19477,8 +20573,14 @@ export {
|
|
|
19477
20573
|
recordDurableUnsafeWriterBoundary,
|
|
19478
20574
|
recordFirstUnsafeVisibleRead,
|
|
19479
20575
|
recordG65DirectRing,
|
|
20576
|
+
recordLateArrival,
|
|
20577
|
+
refuseSafeWindowSeal,
|
|
20578
|
+
refuseSealedWrite,
|
|
20579
|
+
refuseSilentLedgerMerge,
|
|
20580
|
+
replaceSealedIdentity,
|
|
19480
20581
|
requestServiceIdentity,
|
|
19481
20582
|
requireServiceIdentity,
|
|
20583
|
+
rotateAppend,
|
|
19482
20584
|
runG42JournalProbeTrial,
|
|
19483
20585
|
scheduledLiveProjectionMaximumSuid,
|
|
19484
20586
|
scopeIdFor,
|