@sekiban/dcb-runtime 0.2.1 → 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 +27 -0
- package/dist/bootstrap/OperatorBootstrap.d.ts +5 -1
- package/dist/bootstrap/OperatorBootstrap.js +17 -10
- package/dist/cli/OperatorRepairCli.d.ts +6 -1
- package/dist/cli/OperatorRepairCli.js +26 -6
- package/dist/cloudflare.d.ts +2 -0
- package/dist/cloudflare.js +122 -62
- package/dist/http/ConfiguredBearer.d.ts +12 -0
- package/dist/http/ConfiguredBearer.js +33 -0
- package/dist/http/IncidentMaintenance.js +6 -27
- package/dist/http/SerializedQueryWorker.js +37 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +127 -62
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,6 +12,33 @@ The experimental Cosmos provider is never selected by environment values. A
|
|
|
12
12
|
consumer must import `createCosmosStoreProvider` from `/cosmos` and pass it as
|
|
13
13
|
`createRuntimeWorker({ storeProvider })`.
|
|
14
14
|
|
|
15
|
+
## Query parameters
|
|
16
|
+
|
|
17
|
+
A scalar query accepts only a parsed empty object. A list query accepts only
|
|
18
|
+
`PageNumber`, `PageSize`, `NewestFirst`, and `consistency`. Any other parsed
|
|
19
|
+
scalar value, and any unknown list key, is HTTP 400 `query_params_unsupported`
|
|
20
|
+
with the message "The selected query does not support these parameters." The
|
|
21
|
+
check runs after JSON parsing and endpoint matching and before projector
|
|
22
|
+
lookup or a store read, on every backing. The V1 response envelope is
|
|
23
|
+
unchanged. Typed query handlers are planned for `0.3.0`; their API is not
|
|
24
|
+
defined here.
|
|
25
|
+
|
|
26
|
+
## Configured bearer checks
|
|
27
|
+
|
|
28
|
+
`checkConfiguredBearer(request, binding, options?)` is exported from
|
|
29
|
+
`@sekiban/dcb-runtime` and `@sekiban/dcb-runtime/cloudflare`. `binding` is
|
|
30
|
+
`unknown`. The result is `"unconfigured" | "missing" | "invalid" | "authorized"`.
|
|
31
|
+
Callers choose the HTTP status. A binding is configured only when it is a
|
|
32
|
+
string containing a non-whitespace character. The default normalization keeps
|
|
33
|
+
the original bytes. `{ tokenNormalization: "trim" }` is the only other mode
|
|
34
|
+
and is reserved for incident maintenance, which already trimmed its token.
|
|
35
|
+
|
|
36
|
+
The helper hashes the complete expected `Bearer ` credential and the complete
|
|
37
|
+
supplied `Authorization` header with global WebCrypto SHA-256, then compares
|
|
38
|
+
every digest byte. It does not use Node crypto. Scheme spelling and case are
|
|
39
|
+
exact. Treat `unconfigured` as disabled on every exposed shared-secret route.
|
|
40
|
+
Do not compare a blank binding as if it were a credential.
|
|
41
|
+
|
|
15
42
|
## License
|
|
16
43
|
|
|
17
44
|
Elastic License 2.0. See [LICENSE](./LICENSE).
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { BootstrapManifest } from "./types";
|
|
2
2
|
import type { StoreProvider, StoreProviderEnvironment } from "../store/provider";
|
|
3
|
+
/** Missing and unconfigured stay indistinguishable from an unknown path. */
|
|
4
|
+
export declare function authenticateOperatorBootstrap(request: Request, binding: unknown): Promise<Response | undefined>;
|
|
3
5
|
export interface OperatorBootstrapEnv extends StoreProviderEnvironment {
|
|
4
6
|
readonly BOOTSTRAP: DurableObjectNamespace;
|
|
5
7
|
readonly ALLOCATOR: DurableObjectNamespace;
|
|
@@ -16,5 +18,7 @@ export interface OperatorBootstrapHooks {
|
|
|
16
18
|
readonly manifest: BootstrapManifest;
|
|
17
19
|
}) => Promise<void>;
|
|
18
20
|
}
|
|
19
|
-
/**
|
|
21
|
+
/** Authorized continuation. Authentication runs before this function. */
|
|
22
|
+
export declare function handleAuthorizedOperatorBootstrap(request: Request, env: OperatorBootstrapEnv, storeProvider: StoreProvider, hooks?: OperatorBootstrapHooks): Promise<Response>;
|
|
23
|
+
/** Bearer-only bootstrap lane. Incoming headers are never forwarded to a DO. */
|
|
20
24
|
export declare function handleOperatorBootstrap(request: Request, env: OperatorBootstrapEnv, storeProvider: StoreProvider, hooks?: OperatorBootstrapHooks): Promise<Response>;
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { parseBootstrapDump } from "./manifest";
|
|
2
2
|
import { createBootstrapStoreAdapter } from "./BootstrapStoreAdapter";
|
|
3
|
+
import { checkConfiguredBearer } from "../http/ConfiguredBearer";
|
|
3
4
|
import { scopeIdFor } from "../scope/ScopeName";
|
|
4
5
|
function json(body, status = 200) { return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json; charset=utf-8" } }); }
|
|
5
6
|
function object(value) { return typeof value === "object" && value !== null && !Array.isArray(value); }
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (
|
|
7
|
+
/** Missing and unconfigured stay indistinguishable from an unknown path. */
|
|
8
|
+
export async function authenticateOperatorBootstrap(request, binding) {
|
|
9
|
+
const decision = await checkConfiguredBearer(request, binding);
|
|
10
|
+
if (decision === "unconfigured" || decision === "missing") {
|
|
10
11
|
return json({ code: "not_found", error: "not found" }, 404);
|
|
11
|
-
|
|
12
|
+
}
|
|
13
|
+
if (decision === "invalid") {
|
|
12
14
|
return json({ code: "operator_auth_required", error: "valid bearer required" }, 403);
|
|
15
|
+
}
|
|
13
16
|
return undefined;
|
|
14
17
|
}
|
|
15
|
-
/**
|
|
16
|
-
export async function
|
|
17
|
-
const denied = bearer(request, env.REPAIR_OPERATOR_TOKEN);
|
|
18
|
-
if (denied !== undefined)
|
|
19
|
-
return denied;
|
|
18
|
+
/** Authorized continuation. Authentication runs before this function. */
|
|
19
|
+
export async function handleAuthorizedOperatorBootstrap(request, env, storeProvider, hooks = {}) {
|
|
20
20
|
const url = new URL(request.url);
|
|
21
21
|
const match = url.pathname.match(/^\/operator\/bootstrap\/([^/]+)\/(plan|import|status|abort|export)$/);
|
|
22
22
|
if (match === null)
|
|
@@ -102,3 +102,10 @@ export async function handleOperatorBootstrap(request, env, storeProvider, hooks
|
|
|
102
102
|
}
|
|
103
103
|
return invoke("/ready", body);
|
|
104
104
|
}
|
|
105
|
+
/** Bearer-only bootstrap lane. Incoming headers are never forwarded to a DO. */
|
|
106
|
+
export async function handleOperatorBootstrap(request, env, storeProvider, hooks = {}) {
|
|
107
|
+
const denied = await authenticateOperatorBootstrap(request, env.REPAIR_OPERATOR_TOKEN);
|
|
108
|
+
if (denied !== undefined)
|
|
109
|
+
return denied;
|
|
110
|
+
return handleAuthorizedOperatorBootstrap(request, env, storeProvider, hooks);
|
|
111
|
+
}
|
|
@@ -7,5 +7,10 @@ export interface OperatorRepairEnv {
|
|
|
7
7
|
REPAIR_EXCLUSION_LOOKUP?: Fetcher;
|
|
8
8
|
SDT_SERVICE_ID?: string;
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
10
|
+
/** Non-POST stays a route miss for every binding state, before authentication. */
|
|
11
|
+
export declare function nonPostOperatorRepairResponse(request: Request): Response | undefined;
|
|
12
|
+
export declare function authenticateOperatorRepair(request: Request, binding: unknown): Promise<Response | undefined>;
|
|
13
|
+
/** Authorized continuation. Authentication and the POST check run before this. */
|
|
14
|
+
export declare function handleAuthorizedOperatorRepair(request: Request, env: OperatorRepairEnv, nativeTracing?: NativeTracing, serviceIdentityProvider?: ServiceIdentityProvider): Promise<Response>;
|
|
15
|
+
/** Authenticated, bounded repair command; it is not a V1 public wire endpoint. */
|
|
11
16
|
export declare function handleOperatorRepair(request: Request, env: OperatorRepairEnv, nativeTracing?: NativeTracing, serviceIdentityProvider?: ServiceIdentityProvider): Promise<Response>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BindingExclusionLookupClient } from "../downstream/ExclusionLookup";
|
|
2
|
+
import { checkConfiguredBearer } from "../http/ConfiguredBearer";
|
|
2
3
|
import { RepairWorker, RepairWorkerFailure } from "../repair/RepairWorker";
|
|
3
4
|
import { noOpNativeTracing } from "../trace/CommitTrace";
|
|
4
5
|
import { envServiceIdentity, requireServiceIdentity } from "../service/ServiceIdentityProvider";
|
|
@@ -74,15 +75,24 @@ function testFaultFromRequest(request) {
|
|
|
74
75
|
? value
|
|
75
76
|
: undefined;
|
|
76
77
|
}
|
|
77
|
-
/**
|
|
78
|
-
export
|
|
79
|
-
if (request.method
|
|
80
|
-
return
|
|
78
|
+
/** Non-POST stays a route miss for every binding state, before authentication. */
|
|
79
|
+
export function nonPostOperatorRepairResponse(request) {
|
|
80
|
+
if (request.method === "POST")
|
|
81
|
+
return undefined;
|
|
82
|
+
return error(404, "operator_repair_route_not_found", "Operator repair requires POST");
|
|
83
|
+
}
|
|
84
|
+
export async function authenticateOperatorRepair(request, binding) {
|
|
85
|
+
const decision = await checkConfiguredBearer(request, binding);
|
|
86
|
+
if (decision === "unconfigured") {
|
|
87
|
+
return error(404, "operator_repair_route_not_found", "Operator repair route not found");
|
|
81
88
|
}
|
|
82
|
-
|
|
83
|
-
if (!isNonEmptyString(token) || request.headers.get("authorization") !== `Bearer ${token}`) {
|
|
89
|
+
if (decision === "missing" || decision === "invalid") {
|
|
84
90
|
return error(401, "operator_auth_required", "A valid operator repair bearer token is required");
|
|
85
91
|
}
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
/** Authorized continuation. Authentication and the POST check run before this. */
|
|
95
|
+
export async function handleAuthorizedOperatorRepair(request, env, nativeTracing = noOpNativeTracing, serviceIdentityProvider) {
|
|
86
96
|
let body;
|
|
87
97
|
try {
|
|
88
98
|
body = await request.json();
|
|
@@ -106,3 +116,13 @@ export async function handleOperatorRepair(request, env, nativeTracing = noOpNat
|
|
|
106
116
|
return error(500, "operator_repair_failed", message);
|
|
107
117
|
}
|
|
108
118
|
}
|
|
119
|
+
/** Authenticated, bounded repair command; it is not a V1 public wire endpoint. */
|
|
120
|
+
export async function handleOperatorRepair(request, env, nativeTracing = noOpNativeTracing, serviceIdentityProvider) {
|
|
121
|
+
const methodDenied = nonPostOperatorRepairResponse(request);
|
|
122
|
+
if (methodDenied !== undefined)
|
|
123
|
+
return methodDenied;
|
|
124
|
+
const denied = await authenticateOperatorRepair(request, env.REPAIR_OPERATOR_TOKEN);
|
|
125
|
+
if (denied !== undefined)
|
|
126
|
+
return denied;
|
|
127
|
+
return handleAuthorizedOperatorRepair(request, env, nativeTracing, serviceIdentityProvider);
|
|
128
|
+
}
|
package/dist/cloudflare.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ export { buildScopeName, DURABLE_OBJECT_SCOPE_CLASSES, isScopeServiceId, parseSc
|
|
|
30
30
|
export type { DurableObjectScope, DurableObjectScopeClass, ScopeNameNamespace } from "./scope/ScopeName";
|
|
31
31
|
export { envServiceIdentity, G11_SERVICE_ID_HEADER, injectableServiceIdentity, requestServiceIdentity, requireServiceIdentity, ServiceIdentityMissingError, TEST_SERVICE_ID_HEADER, } from "./service/ServiceIdentityProvider";
|
|
32
32
|
export type { ServiceIdentityBehavior, ServiceIdentityEnvironment, ServiceIdentityProvider, ServiceIdentityRequestOptions, ServiceIdentityResolution, } from "./service/ServiceIdentityProvider";
|
|
33
|
+
export { checkConfiguredBearer } from "./http/ConfiguredBearer";
|
|
34
|
+
export type { CheckConfiguredBearerOptions, ConfiguredBearerResult, ConfiguredBearerTokenNormalization, } from "./http/ConfiguredBearer";
|
|
33
35
|
export interface CloudflareOnlyEnv {
|
|
34
36
|
ALLOCATOR: DurableObjectNamespace;
|
|
35
37
|
BOOTSTRAP: DurableObjectNamespace;
|
package/dist/cloudflare.js
CHANGED
|
@@ -5458,15 +5458,15 @@ var BootstrapCoordinatorDurableObject = class {
|
|
|
5458
5458
|
async permit(serviceId, body) {
|
|
5459
5459
|
if (!object(body) || !string(body.commandId) || !string(body.digest)) return reject("bootstrap_permit_invalid", "commandId and digest are required", 400);
|
|
5460
5460
|
const commandId = body.commandId;
|
|
5461
|
-
const
|
|
5461
|
+
const digest = body.digest;
|
|
5462
5462
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
5463
5463
|
const control = await txn.get(CONTROL) ?? empty(serviceId);
|
|
5464
5464
|
const active2 = permits(control);
|
|
5465
5465
|
if (active2[commandId] !== void 0) {
|
|
5466
|
-
return active2[commandId] ===
|
|
5466
|
+
return active2[commandId] === digest ? { control } : { digestMismatch: true };
|
|
5467
5467
|
}
|
|
5468
5468
|
if (control.status !== "EMPTY" && control.status !== "READY") return { rejected: true };
|
|
5469
|
-
const updated = { ...control, writePermits: { ...active2, [commandId]:
|
|
5469
|
+
const updated = { ...control, writePermits: { ...active2, [commandId]: digest } };
|
|
5470
5470
|
await txn.put(CONTROL, updated);
|
|
5471
5471
|
return { control: updated };
|
|
5472
5472
|
});
|
|
@@ -5477,11 +5477,11 @@ var BootstrapCoordinatorDurableObject = class {
|
|
|
5477
5477
|
async permitRelease(serviceId, body) {
|
|
5478
5478
|
if (!object(body) || !string(body.commandId)) return reject("bootstrap_permit_invalid", "commandId is required", 400);
|
|
5479
5479
|
const commandId = body.commandId;
|
|
5480
|
-
const
|
|
5480
|
+
const digest = string(body.digest) ? body.digest : void 0;
|
|
5481
5481
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
5482
5482
|
const control = await txn.get(CONTROL) ?? empty(serviceId);
|
|
5483
5483
|
const active2 = permits(control);
|
|
5484
|
-
if (active2[commandId] !== void 0 &&
|
|
5484
|
+
if (active2[commandId] !== void 0 && digest !== void 0 && active2[commandId] !== digest) {
|
|
5485
5485
|
return { digestMismatch: true };
|
|
5486
5486
|
}
|
|
5487
5487
|
if (active2[commandId] === void 0) return { control };
|
|
@@ -5667,8 +5667,8 @@ function eventDigestBytes(input) {
|
|
|
5667
5667
|
async function eventDigestHex(input) {
|
|
5668
5668
|
const bytes2 = eventDigestBytes(input);
|
|
5669
5669
|
const source = bytes2.buffer.slice(bytes2.byteOffset, bytes2.byteOffset + bytes2.byteLength);
|
|
5670
|
-
const
|
|
5671
|
-
return [...new Uint8Array(
|
|
5670
|
+
const digest = await crypto.subtle.digest("SHA-256", source);
|
|
5671
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
5672
5672
|
}
|
|
5673
5673
|
|
|
5674
5674
|
// src/bootstrap/BootstrapStoreAdapter.ts
|
|
@@ -5814,6 +5814,34 @@ function createBootstrapStoreAdapter(provider, store, identityGuard) {
|
|
|
5814
5814
|
return new BootstrapStoreAdapter(provider, store, identityGuard);
|
|
5815
5815
|
}
|
|
5816
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
|
+
|
|
5817
5845
|
// src/bootstrap/OperatorBootstrap.ts
|
|
5818
5846
|
function json2(body, status = 200) {
|
|
5819
5847
|
return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json; charset=utf-8" } });
|
|
@@ -5821,15 +5849,17 @@ function json2(body, status = 200) {
|
|
|
5821
5849
|
function object2(value) {
|
|
5822
5850
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5823
5851
|
}
|
|
5824
|
-
function
|
|
5825
|
-
const
|
|
5826
|
-
if (
|
|
5827
|
-
|
|
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
|
+
}
|
|
5828
5860
|
return void 0;
|
|
5829
5861
|
}
|
|
5830
|
-
async function
|
|
5831
|
-
const denied = bearer(request, env.REPAIR_OPERATOR_TOKEN);
|
|
5832
|
-
if (denied !== void 0) return denied;
|
|
5862
|
+
async function handleAuthorizedOperatorBootstrap(request, env, storeProvider, hooks = {}) {
|
|
5833
5863
|
const url = new URL(request.url);
|
|
5834
5864
|
const match = url.pathname.match(/^\/operator\/bootstrap\/([^/]+)\/(plan|import|status|abort|export)$/);
|
|
5835
5865
|
if (match === null) return json2({ code: "not_found", error: "not found" }, 404);
|
|
@@ -6728,14 +6758,21 @@ function testFaultFromRequest(request) {
|
|
|
6728
6758
|
const value = request.headers.get("x-sdt-g6-test-fault");
|
|
6729
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;
|
|
6730
6760
|
}
|
|
6731
|
-
|
|
6732
|
-
if (request.method
|
|
6733
|
-
|
|
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");
|
|
6734
6769
|
}
|
|
6735
|
-
|
|
6736
|
-
if (!isNonEmptyString3(token) || request.headers.get("authorization") !== `Bearer ${token}`) {
|
|
6770
|
+
if (decision === "missing" || decision === "invalid") {
|
|
6737
6771
|
return error2(401, "operator_auth_required", "A valid operator repair bearer token is required");
|
|
6738
6772
|
}
|
|
6773
|
+
return void 0;
|
|
6774
|
+
}
|
|
6775
|
+
async function handleAuthorizedOperatorRepair(request, env, nativeTracing = noOpNativeTracing, serviceIdentityProvider) {
|
|
6739
6776
|
let body;
|
|
6740
6777
|
try {
|
|
6741
6778
|
body = await request.json();
|
|
@@ -7358,7 +7395,7 @@ var CommitWorker = class {
|
|
|
7358
7395
|
identity: "coordinator"
|
|
7359
7396
|
}));
|
|
7360
7397
|
}
|
|
7361
|
-
async bootstrapPermitAcquire(commandId,
|
|
7398
|
+
async bootstrapPermitAcquire(commandId, digest) {
|
|
7362
7399
|
const stub = this.bootstrapStub();
|
|
7363
7400
|
if (stub === void 0) return { ok: true };
|
|
7364
7401
|
try {
|
|
@@ -7367,7 +7404,7 @@ var CommitWorker = class {
|
|
|
7367
7404
|
const response3 = await stub.fetch(new Request(url, {
|
|
7368
7405
|
method: "POST",
|
|
7369
7406
|
headers: { "content-type": "application/json" },
|
|
7370
|
-
body: JSON.stringify({ commandId, digest
|
|
7407
|
+
body: JSON.stringify({ commandId, digest })
|
|
7371
7408
|
}));
|
|
7372
7409
|
if (response3.ok) return { ok: true };
|
|
7373
7410
|
const body = await response3.clone().json().catch(() => void 0);
|
|
@@ -7376,7 +7413,7 @@ var CommitWorker = class {
|
|
|
7376
7413
|
return { ok: false };
|
|
7377
7414
|
}
|
|
7378
7415
|
}
|
|
7379
|
-
async bootstrapPermitRelease(commandId,
|
|
7416
|
+
async bootstrapPermitRelease(commandId, digest) {
|
|
7380
7417
|
const stub = this.bootstrapStub();
|
|
7381
7418
|
if (stub === void 0) return;
|
|
7382
7419
|
try {
|
|
@@ -7385,7 +7422,7 @@ var CommitWorker = class {
|
|
|
7385
7422
|
await stub.fetch(new Request(url, {
|
|
7386
7423
|
method: "POST",
|
|
7387
7424
|
headers: { "content-type": "application/json" },
|
|
7388
|
-
body: JSON.stringify({ commandId, digest
|
|
7425
|
+
body: JSON.stringify({ commandId, digest })
|
|
7389
7426
|
}));
|
|
7390
7427
|
} catch {
|
|
7391
7428
|
}
|
|
@@ -13899,6 +13936,33 @@ function parseRequest(value) {
|
|
|
13899
13936
|
}
|
|
13900
13937
|
};
|
|
13901
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
|
+
}
|
|
13902
13966
|
function paginationFrom(value) {
|
|
13903
13967
|
if (!isObject9(value)) {
|
|
13904
13968
|
return { error: "list-query queryParamsJson must contain an object" };
|
|
@@ -14079,6 +14143,10 @@ async function handleSerializedQuery(request, env, options = {}) {
|
|
|
14079
14143
|
if (definition === void 0 || definition.endpoint !== endpoint) {
|
|
14080
14144
|
return error6(400, "validation_error", "queryType is not mapped for this endpoint");
|
|
14081
14145
|
}
|
|
14146
|
+
const unsupported = unsupportedQueryParams(endpoint, parsed.value.queryParams);
|
|
14147
|
+
if (unsupported !== void 0) {
|
|
14148
|
+
return error6(unsupported.status, unsupported.code, unsupported.error);
|
|
14149
|
+
}
|
|
14082
14150
|
if (!definition.enabled || (options.projectors ?? DEPLOYED_PROJECTOR_REGISTRY).resolve(definition.tagProjector) === void 0) {
|
|
14083
14151
|
return error6(503, "projection_unavailable", "The mapped query projection is unavailable");
|
|
14084
14152
|
}
|
|
@@ -14567,11 +14635,11 @@ function normalizeCorrection(value) {
|
|
|
14567
14635
|
throw new IncidentLifecycleError("incident_invalid_request", 400, "correction.kind must be event or receipt");
|
|
14568
14636
|
}
|
|
14569
14637
|
const reference = boundedString(value.reference, "correction.reference", MAX_LONG_TEXT_BYTES);
|
|
14570
|
-
const
|
|
14571
|
-
if (reference === null ||
|
|
14638
|
+
const digest = boundedString(value.digest, "correction.digest", MAX_IDENTITY_BYTES);
|
|
14639
|
+
if (reference === null || digest === null || !SHA256_DIGEST.test(digest)) {
|
|
14572
14640
|
throw new IncidentLifecycleError("incident_invalid_request", 400, "correction.digest must be lowercase sha256");
|
|
14573
14641
|
}
|
|
14574
|
-
return { kind: value.kind, reference, digest
|
|
14642
|
+
return { kind: value.kind, reference, digest };
|
|
14575
14643
|
}
|
|
14576
14644
|
function normalizeRequest(value) {
|
|
14577
14645
|
if (!isObject11(value) || !hasOnlyKeys(value, ["action", "incidentIdentity", "transitionKey", "expectedVersion", "reason", "ownerId", "deadlineAt", "correction", "resolution"])) {
|
|
@@ -14627,8 +14695,8 @@ function canonicalize(value) {
|
|
|
14627
14695
|
async function digestFor(request, actorId) {
|
|
14628
14696
|
const envelope = canonicalize({ action: request, actor: actorId });
|
|
14629
14697
|
const encoded = new TextEncoder().encode(JSON.stringify(envelope));
|
|
14630
|
-
const
|
|
14631
|
-
return `sha256:${Array.from(new Uint8Array(
|
|
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("")}`;
|
|
14632
14700
|
}
|
|
14633
14701
|
function projectionFromRow(row, finding2) {
|
|
14634
14702
|
if (row === null) {
|
|
@@ -14855,13 +14923,13 @@ var IncidentLifecycle = class {
|
|
|
14855
14923
|
}
|
|
14856
14924
|
if (actorId === null) throw new IncidentLifecycleError("incident_invalid_actor", 400, "x-sdt-maintainer must be non-empty");
|
|
14857
14925
|
const finding2 = await this.finding(serviceId, request.incidentIdentity);
|
|
14858
|
-
const
|
|
14926
|
+
const digest = await digestFor(request, actorId);
|
|
14859
14927
|
const existing = await this.database.prepare(
|
|
14860
14928
|
`SELECT * FROM serialized_dcb_incident_transitions
|
|
14861
14929
|
WHERE service_id = ? AND incident_identity = ? AND transition_key = ?`
|
|
14862
14930
|
).bind(serviceId, request.incidentIdentity, request.transitionKey).first();
|
|
14863
14931
|
if (existing !== null && existing !== void 0) {
|
|
14864
|
-
if (existing.request_digest !==
|
|
14932
|
+
if (existing.request_digest !== digest) {
|
|
14865
14933
|
throw new IncidentLifecycleError("incident_idempotency_conflict", 409, "The transition key was already used with different content");
|
|
14866
14934
|
}
|
|
14867
14935
|
return { projection: transitionFromRow(existing).after, transition: transitionFromRow(existing), idempotent: true };
|
|
@@ -14924,7 +14992,7 @@ var IncidentLifecycle = class {
|
|
|
14924
14992
|
serviceId,
|
|
14925
14993
|
request.incidentIdentity,
|
|
14926
14994
|
request.transitionKey,
|
|
14927
|
-
|
|
14995
|
+
digest,
|
|
14928
14996
|
request.action,
|
|
14929
14997
|
before.lifecycleState,
|
|
14930
14998
|
before.version,
|
|
@@ -14953,7 +15021,7 @@ var IncidentLifecycle = class {
|
|
|
14953
15021
|
`SELECT * FROM serialized_dcb_incident_transitions
|
|
14954
15022
|
WHERE service_id = ? AND incident_identity = ? AND transition_key = ?`
|
|
14955
15023
|
).bind(serviceId, request.incidentIdentity, request.transitionKey).first();
|
|
14956
|
-
if (competing !== null && competing !== void 0 && competing.request_digest !==
|
|
15024
|
+
if (competing !== null && competing !== void 0 && competing.request_digest !== digest) {
|
|
14957
15025
|
throw new IncidentLifecycleError("incident_idempotency_conflict", 409, "The transition key was already used with different content");
|
|
14958
15026
|
}
|
|
14959
15027
|
if (competing !== null && competing !== void 0) {
|
|
@@ -15055,29 +15123,6 @@ function json10(body, status = 200) {
|
|
|
15055
15123
|
function error8(status, code, message) {
|
|
15056
15124
|
return json10({ error: message, code }, status);
|
|
15057
15125
|
}
|
|
15058
|
-
function configuredToken(value) {
|
|
15059
|
-
if (typeof value !== "string") return void 0;
|
|
15060
|
-
const token = value.trim();
|
|
15061
|
-
return token.length === 0 ? void 0 : token;
|
|
15062
|
-
}
|
|
15063
|
-
async function digest(value) {
|
|
15064
|
-
const bytes2 = new TextEncoder().encode(value);
|
|
15065
|
-
return new Uint8Array(await crypto.subtle.digest("SHA-256", bytes2));
|
|
15066
|
-
}
|
|
15067
|
-
function fixedLengthEqual(left, right) {
|
|
15068
|
-
let difference = left.length ^ right.length;
|
|
15069
|
-
const length = Math.max(left.length, right.length);
|
|
15070
|
-
for (let index = 0; index < length; index += 1) {
|
|
15071
|
-
difference |= (left[index] ?? 0) ^ (right[index] ?? 0);
|
|
15072
|
-
}
|
|
15073
|
-
return difference === 0;
|
|
15074
|
-
}
|
|
15075
|
-
async function authenticated(request, token) {
|
|
15076
|
-
const credential = request.headers.get("authorization") ?? "";
|
|
15077
|
-
const suppliedToken = credential.startsWith("Bearer ") ? credential.slice("Bearer ".length) : "";
|
|
15078
|
-
const [expectedDigest, suppliedDigest] = await Promise.all([digest(token), digest(suppliedToken)]);
|
|
15079
|
-
return fixedLengthEqual(expectedDigest, suppliedDigest);
|
|
15080
|
-
}
|
|
15081
15126
|
function boolFilter(value, name) {
|
|
15082
15127
|
if (value === null) return void 0;
|
|
15083
15128
|
if (value === "true") return true;
|
|
@@ -15131,11 +15176,13 @@ function isIncidentMaintenancePath(pathname) {
|
|
|
15131
15176
|
return pathname === "/maintenance/incidents" || pathname.startsWith("/maintenance/incidents/");
|
|
15132
15177
|
}
|
|
15133
15178
|
async function handleIncidentMaintenance(request, env, serviceIdentityProvider) {
|
|
15134
|
-
const
|
|
15135
|
-
|
|
15179
|
+
const decision = await checkConfiguredBearer(request, env.INCIDENT_MAINTAINER_TOKEN, {
|
|
15180
|
+
tokenNormalization: "trim"
|
|
15181
|
+
});
|
|
15182
|
+
if (decision === "unconfigured") {
|
|
15136
15183
|
return error8(503, "incident_maintenance_unavailable", "Incident maintenance is not configured");
|
|
15137
15184
|
}
|
|
15138
|
-
if (
|
|
15185
|
+
if (decision === "missing" || decision === "invalid") {
|
|
15139
15186
|
return error8(401, "incident_auth_required", "A valid incident maintenance bearer token is required");
|
|
15140
15187
|
}
|
|
15141
15188
|
let serviceId;
|
|
@@ -20248,6 +20295,18 @@ function createCloudflareOnlyRuntimeWorker(options = {}) {
|
|
|
20248
20295
|
if (isIncidentMaintenancePath(url.pathname)) {
|
|
20249
20296
|
return handleIncidentMaintenance(request, env, options.serviceIdentityProvider ?? envServiceIdentity(env));
|
|
20250
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
|
+
}
|
|
20251
20310
|
const serviceIdentity = options.serviceIdentityProvider ?? envServiceIdentity(env);
|
|
20252
20311
|
const requestIdentityOptions = { allowG11Verification: env.G11_VERIFICATION_ENABLED === "true" };
|
|
20253
20312
|
let requestServiceId;
|
|
@@ -20300,11 +20359,11 @@ function createCloudflareOnlyRuntimeWorker(options = {}) {
|
|
|
20300
20359
|
serviceIdentityProvider: serviceIdentity
|
|
20301
20360
|
});
|
|
20302
20361
|
}
|
|
20303
|
-
if (
|
|
20304
|
-
return
|
|
20362
|
+
if (operatorRepair) {
|
|
20363
|
+
return handleAuthorizedOperatorRepair(request, env, cloudflareTracing(ctx), serviceIdentity);
|
|
20305
20364
|
}
|
|
20306
|
-
if (
|
|
20307
|
-
return
|
|
20365
|
+
if (operatorBootstrap) {
|
|
20366
|
+
return handleAuthorizedOperatorBootstrap(request, env, storeProvider, {
|
|
20308
20367
|
afterVerifyBeforeReady: options.afterBootstrapVerify === void 0 ? void 0 : ({ serviceId }) => options.afterBootstrapVerify({ serviceId, env })
|
|
20309
20368
|
});
|
|
20310
20369
|
}
|
|
@@ -20478,6 +20537,7 @@ export {
|
|
|
20478
20537
|
assertNoSilentLedgerMerge,
|
|
20479
20538
|
assertStrictlyBefore,
|
|
20480
20539
|
buildScopeName,
|
|
20540
|
+
checkConfiguredBearer,
|
|
20481
20541
|
classifyDirectDoorbellFailure,
|
|
20482
20542
|
cleanupG42JournalProbeTrial,
|
|
20483
20543
|
createCloudflareOnlyRuntimeWorker,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type ConfiguredBearerResult = "unconfigured" | "missing" | "invalid" | "authorized";
|
|
2
|
+
export type ConfiguredBearerTokenNormalization = "exact" | "trim";
|
|
3
|
+
export interface CheckConfiguredBearerOptions {
|
|
4
|
+
readonly tokenNormalization?: ConfiguredBearerTokenNormalization;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Decide whether a configured shared secret admits this request.
|
|
8
|
+
* Callers map the four results onto their own HTTP responses.
|
|
9
|
+
* The binding is configured only when it is a string with a non-whitespace
|
|
10
|
+
* character. Exact mode keeps those bytes; trim mode is opt-in.
|
|
11
|
+
*/
|
|
12
|
+
export declare function checkConfiguredBearer(request: Request, binding: unknown, options?: CheckConfiguredBearerOptions): Promise<ConfiguredBearerResult>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
async function sha256(value) {
|
|
2
|
+
const encoded = new TextEncoder().encode(value);
|
|
3
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", encoded));
|
|
4
|
+
}
|
|
5
|
+
function digestsMatch(expected, supplied) {
|
|
6
|
+
let difference = expected.length ^ supplied.length;
|
|
7
|
+
const length = Math.max(expected.length, supplied.length);
|
|
8
|
+
for (let index = 0; index < length; index += 1) {
|
|
9
|
+
difference |= (expected[index] ?? 0) ^ (supplied[index] ?? 0);
|
|
10
|
+
}
|
|
11
|
+
return difference === 0;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Decide whether a configured shared secret admits this request.
|
|
15
|
+
* Callers map the four results onto their own HTTP responses.
|
|
16
|
+
* The binding is configured only when it is a string with a non-whitespace
|
|
17
|
+
* character. Exact mode keeps those bytes; trim mode is opt-in.
|
|
18
|
+
*/
|
|
19
|
+
export async function checkConfiguredBearer(request, binding, options) {
|
|
20
|
+
if (typeof binding !== "string" || binding.trim().length === 0) {
|
|
21
|
+
return "unconfigured";
|
|
22
|
+
}
|
|
23
|
+
const effectiveToken = options?.tokenNormalization === "trim" ? binding.trim() : binding;
|
|
24
|
+
const supplied = request.headers.get("authorization");
|
|
25
|
+
if (supplied === null)
|
|
26
|
+
return "missing";
|
|
27
|
+
const expected = `Bearer ${effectiveToken}`;
|
|
28
|
+
const [expectedDigest, suppliedDigest] = await Promise.all([
|
|
29
|
+
sha256(expected),
|
|
30
|
+
sha256(supplied),
|
|
31
|
+
]);
|
|
32
|
+
return digestsMatch(expectedDigest, suppliedDigest) ? "authorized" : "invalid";
|
|
33
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IncidentLifecycle, IncidentLifecycleError, normalizeIncidentIdentity, } from "../completeness/IncidentLifecycle";
|
|
2
2
|
import { requestServiceIdentity, } from "../service/ServiceIdentityProvider";
|
|
3
|
+
import { checkConfiguredBearer } from "./ConfiguredBearer";
|
|
3
4
|
function json(body, status = 200) {
|
|
4
5
|
return new Response(JSON.stringify(body), {
|
|
5
6
|
status,
|
|
@@ -9,30 +10,6 @@ function json(body, status = 200) {
|
|
|
9
10
|
function error(status, code, message) {
|
|
10
11
|
return json({ error: message, code }, status);
|
|
11
12
|
}
|
|
12
|
-
function configuredToken(value) {
|
|
13
|
-
if (typeof value !== "string")
|
|
14
|
-
return undefined;
|
|
15
|
-
const token = value.trim();
|
|
16
|
-
return token.length === 0 ? undefined : token;
|
|
17
|
-
}
|
|
18
|
-
async function digest(value) {
|
|
19
|
-
const bytes = new TextEncoder().encode(value);
|
|
20
|
-
return new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
|
|
21
|
-
}
|
|
22
|
-
function fixedLengthEqual(left, right) {
|
|
23
|
-
let difference = left.length ^ right.length;
|
|
24
|
-
const length = Math.max(left.length, right.length);
|
|
25
|
-
for (let index = 0; index < length; index += 1) {
|
|
26
|
-
difference |= (left[index] ?? 0) ^ (right[index] ?? 0);
|
|
27
|
-
}
|
|
28
|
-
return difference === 0;
|
|
29
|
-
}
|
|
30
|
-
async function authenticated(request, token) {
|
|
31
|
-
const credential = request.headers.get("authorization") ?? "";
|
|
32
|
-
const suppliedToken = credential.startsWith("Bearer ") ? credential.slice("Bearer ".length) : "";
|
|
33
|
-
const [expectedDigest, suppliedDigest] = await Promise.all([digest(token), digest(suppliedToken)]);
|
|
34
|
-
return fixedLengthEqual(expectedDigest, suppliedDigest);
|
|
35
|
-
}
|
|
36
13
|
function boolFilter(value, name) {
|
|
37
14
|
if (value === null)
|
|
38
15
|
return undefined;
|
|
@@ -92,11 +69,13 @@ export function isIncidentMaintenancePath(pathname) {
|
|
|
92
69
|
return pathname === "/maintenance/incidents" || pathname.startsWith("/maintenance/incidents/");
|
|
93
70
|
}
|
|
94
71
|
export async function handleIncidentMaintenance(request, env, serviceIdentityProvider) {
|
|
95
|
-
const
|
|
96
|
-
|
|
72
|
+
const decision = await checkConfiguredBearer(request, env.INCIDENT_MAINTAINER_TOKEN, {
|
|
73
|
+
tokenNormalization: "trim",
|
|
74
|
+
});
|
|
75
|
+
if (decision === "unconfigured") {
|
|
97
76
|
return error(503, "incident_maintenance_unavailable", "Incident maintenance is not configured");
|
|
98
77
|
}
|
|
99
|
-
if (
|
|
78
|
+
if (decision === "missing" || decision === "invalid") {
|
|
100
79
|
return error(401, "incident_auth_required", "A valid incident maintenance bearer token is required");
|
|
101
80
|
}
|
|
102
81
|
let serviceId;
|
|
@@ -57,6 +57,39 @@ function parseRequest(value) {
|
|
|
57
57
|
},
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
+
const QUERY_PARAMS_UNSUPPORTED_MESSAGE = "The selected query does not support these parameters.";
|
|
61
|
+
const LIST_QUERY_PARAM_KEYS = new Set(["PageNumber", "PageSize", "NewestFirst", "consistency"]);
|
|
62
|
+
/**
|
|
63
|
+
* Scalar queries have no parameter interpreter, so only an empty plain object
|
|
64
|
+
* is a supported no-parameter call. List queries keep the four existing
|
|
65
|
+
* controls and leave their value checks to paginationFrom. Non-object list
|
|
66
|
+
* input is also left to paginationFrom so that validation response stays put.
|
|
67
|
+
*/
|
|
68
|
+
function unsupportedQueryParams(endpoint, queryParams) {
|
|
69
|
+
if (endpoint === "query") {
|
|
70
|
+
if (isObject(queryParams) && Object.keys(queryParams).length === 0) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
status: 400,
|
|
75
|
+
code: "query_params_unsupported",
|
|
76
|
+
error: QUERY_PARAMS_UNSUPPORTED_MESSAGE,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (!isObject(queryParams)) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
for (const key of Object.keys(queryParams)) {
|
|
83
|
+
if (!LIST_QUERY_PARAM_KEYS.has(key)) {
|
|
84
|
+
return {
|
|
85
|
+
status: 400,
|
|
86
|
+
code: "query_params_unsupported",
|
|
87
|
+
error: QUERY_PARAMS_UNSUPPORTED_MESSAGE,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
60
93
|
function paginationFrom(value) {
|
|
61
94
|
if (!isObject(value)) {
|
|
62
95
|
return { error: "list-query queryParamsJson must contain an object" };
|
|
@@ -295,6 +328,10 @@ export async function handleSerializedQuery(request, env, options = {}) {
|
|
|
295
328
|
if (definition === undefined || definition.endpoint !== endpoint) {
|
|
296
329
|
return error(400, "validation_error", "queryType is not mapped for this endpoint");
|
|
297
330
|
}
|
|
331
|
+
const unsupported = unsupportedQueryParams(endpoint, parsed.value.queryParams);
|
|
332
|
+
if (unsupported !== undefined) {
|
|
333
|
+
return error(unsupported.status, unsupported.code, unsupported.error);
|
|
334
|
+
}
|
|
298
335
|
if (!definition.enabled || (options.projectors ?? DEPLOYED_PROJECTOR_REGISTRY).resolve(definition.tagProjector) === undefined) {
|
|
299
336
|
return error(503, "projection_unavailable", "The mapped query projection is unavailable");
|
|
300
337
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -55,6 +55,8 @@ export type { SerializedEventMetadata } from "./eventRecord";
|
|
|
55
55
|
export type { DeliveryCoreEnvironment, DeliveryCoreFailure, DeliveryCoreOptions, DeliveryCoreResult, DeliverySource, DeliveryViewApplyResult, DeliveryViewFailureClass, DeliveryViewHandler, DeliveryViewInput, DeliveryViewResult, } from "./downstream/DeliveryCore";
|
|
56
56
|
export type { DeliveryClass, DirectDoorbellDegradation, DirectDoorbellDeploymentConfig, DirectDoorbellPreflightResult, DirectDoorbellReceiverMode, DirectDoorbellFailureKind, DownstreamDoorbellBinding, } from "./downstream/Doorbell";
|
|
57
57
|
export type { JsonValue, MaterializedViewRowPatch } from "@sekiban/dcb-core";
|
|
58
|
+
export { checkConfiguredBearer } from "./http/ConfiguredBearer";
|
|
59
|
+
export type { CheckConfiguredBearerOptions, ConfiguredBearerResult, ConfiguredBearerTokenNormalization, } from "./http/ConfiguredBearer";
|
|
58
60
|
export { composeRuntime, createRuntimeCommitPort, registeredEventParsers, RuntimeCommandRegistry, } from "./composition";
|
|
59
61
|
export type { RuntimeCommitAllocationLike, RuntimeCommitCandidateLike, RuntimeCommitPort, RuntimeCommitPortOptions, RuntimeCommitPortResult, RuntimeCommandLike, RuntimeDomainLike, RuntimeQueryDefinition, RuntimeWorkerConfig, } from "./composition";
|
|
60
62
|
export { POSTGRES_STORE_PROVIDER, createPostgresBootstrapAdapter, createPostgresStoreProvider } from "./store/provider";
|
package/dist/index.js
CHANGED
|
@@ -5542,15 +5542,15 @@ var BootstrapCoordinatorDurableObject = class {
|
|
|
5542
5542
|
async permit(serviceId, body) {
|
|
5543
5543
|
if (!object(body) || !string(body.commandId) || !string(body.digest)) return reject("bootstrap_permit_invalid", "commandId and digest are required", 400);
|
|
5544
5544
|
const commandId = body.commandId;
|
|
5545
|
-
const
|
|
5545
|
+
const digest = body.digest;
|
|
5546
5546
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
5547
5547
|
const control = await txn.get(CONTROL) ?? empty(serviceId);
|
|
5548
5548
|
const active2 = permits(control);
|
|
5549
5549
|
if (active2[commandId] !== void 0) {
|
|
5550
|
-
return active2[commandId] ===
|
|
5550
|
+
return active2[commandId] === digest ? { control } : { digestMismatch: true };
|
|
5551
5551
|
}
|
|
5552
5552
|
if (control.status !== "EMPTY" && control.status !== "READY") return { rejected: true };
|
|
5553
|
-
const updated = { ...control, writePermits: { ...active2, [commandId]:
|
|
5553
|
+
const updated = { ...control, writePermits: { ...active2, [commandId]: digest } };
|
|
5554
5554
|
await txn.put(CONTROL, updated);
|
|
5555
5555
|
return { control: updated };
|
|
5556
5556
|
});
|
|
@@ -5561,11 +5561,11 @@ var BootstrapCoordinatorDurableObject = class {
|
|
|
5561
5561
|
async permitRelease(serviceId, body) {
|
|
5562
5562
|
if (!object(body) || !string(body.commandId)) return reject("bootstrap_permit_invalid", "commandId is required", 400);
|
|
5563
5563
|
const commandId = body.commandId;
|
|
5564
|
-
const
|
|
5564
|
+
const digest = string(body.digest) ? body.digest : void 0;
|
|
5565
5565
|
const result2 = await this.ctx.storage.transaction(async (txn) => {
|
|
5566
5566
|
const control = await txn.get(CONTROL) ?? empty(serviceId);
|
|
5567
5567
|
const active2 = permits(control);
|
|
5568
|
-
if (active2[commandId] !== void 0 &&
|
|
5568
|
+
if (active2[commandId] !== void 0 && digest !== void 0 && active2[commandId] !== digest) {
|
|
5569
5569
|
return { digestMismatch: true };
|
|
5570
5570
|
}
|
|
5571
5571
|
if (active2[commandId] === void 0) return { control };
|
|
@@ -5751,8 +5751,8 @@ function eventDigestBytes(input) {
|
|
|
5751
5751
|
async function eventDigestHex(input) {
|
|
5752
5752
|
const bytes2 = eventDigestBytes(input);
|
|
5753
5753
|
const source = bytes2.buffer.slice(bytes2.byteOffset, bytes2.byteOffset + bytes2.byteLength);
|
|
5754
|
-
const
|
|
5755
|
-
return [...new Uint8Array(
|
|
5754
|
+
const digest = await crypto.subtle.digest("SHA-256", source);
|
|
5755
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
5756
5756
|
}
|
|
5757
5757
|
|
|
5758
5758
|
// src/bootstrap/BootstrapStoreAdapter.ts
|
|
@@ -5898,6 +5898,34 @@ function createBootstrapStoreAdapter(provider, store, identityGuard) {
|
|
|
5898
5898
|
return new BootstrapStoreAdapter(provider, store, identityGuard);
|
|
5899
5899
|
}
|
|
5900
5900
|
|
|
5901
|
+
// src/http/ConfiguredBearer.ts
|
|
5902
|
+
async function sha256(value) {
|
|
5903
|
+
const encoded = new TextEncoder().encode(value);
|
|
5904
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", encoded));
|
|
5905
|
+
}
|
|
5906
|
+
function digestsMatch(expected, supplied) {
|
|
5907
|
+
let difference = expected.length ^ supplied.length;
|
|
5908
|
+
const length = Math.max(expected.length, supplied.length);
|
|
5909
|
+
for (let index = 0; index < length; index += 1) {
|
|
5910
|
+
difference |= (expected[index] ?? 0) ^ (supplied[index] ?? 0);
|
|
5911
|
+
}
|
|
5912
|
+
return difference === 0;
|
|
5913
|
+
}
|
|
5914
|
+
async function checkConfiguredBearer(request, binding, options) {
|
|
5915
|
+
if (typeof binding !== "string" || binding.trim().length === 0) {
|
|
5916
|
+
return "unconfigured";
|
|
5917
|
+
}
|
|
5918
|
+
const effectiveToken = options?.tokenNormalization === "trim" ? binding.trim() : binding;
|
|
5919
|
+
const supplied = request.headers.get("authorization");
|
|
5920
|
+
if (supplied === null) return "missing";
|
|
5921
|
+
const expected = `Bearer ${effectiveToken}`;
|
|
5922
|
+
const [expectedDigest, suppliedDigest] = await Promise.all([
|
|
5923
|
+
sha256(expected),
|
|
5924
|
+
sha256(supplied)
|
|
5925
|
+
]);
|
|
5926
|
+
return digestsMatch(expectedDigest, suppliedDigest) ? "authorized" : "invalid";
|
|
5927
|
+
}
|
|
5928
|
+
|
|
5901
5929
|
// src/bootstrap/OperatorBootstrap.ts
|
|
5902
5930
|
function json2(body, status = 200) {
|
|
5903
5931
|
return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json; charset=utf-8" } });
|
|
@@ -5905,15 +5933,17 @@ function json2(body, status = 200) {
|
|
|
5905
5933
|
function object2(value) {
|
|
5906
5934
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5907
5935
|
}
|
|
5908
|
-
function
|
|
5909
|
-
const
|
|
5910
|
-
if (
|
|
5911
|
-
|
|
5936
|
+
async function authenticateOperatorBootstrap(request, binding) {
|
|
5937
|
+
const decision = await checkConfiguredBearer(request, binding);
|
|
5938
|
+
if (decision === "unconfigured" || decision === "missing") {
|
|
5939
|
+
return json2({ code: "not_found", error: "not found" }, 404);
|
|
5940
|
+
}
|
|
5941
|
+
if (decision === "invalid") {
|
|
5942
|
+
return json2({ code: "operator_auth_required", error: "valid bearer required" }, 403);
|
|
5943
|
+
}
|
|
5912
5944
|
return void 0;
|
|
5913
5945
|
}
|
|
5914
|
-
async function
|
|
5915
|
-
const denied = bearer(request, env.REPAIR_OPERATOR_TOKEN);
|
|
5916
|
-
if (denied !== void 0) return denied;
|
|
5946
|
+
async function handleAuthorizedOperatorBootstrap(request, env, storeProvider, hooks = {}) {
|
|
5917
5947
|
const url = new URL(request.url);
|
|
5918
5948
|
const match = url.pathname.match(/^\/operator\/bootstrap\/([^/]+)\/(plan|import|status|abort|export)$/);
|
|
5919
5949
|
if (match === null) return json2({ code: "not_found", error: "not found" }, 404);
|
|
@@ -6812,14 +6842,21 @@ function testFaultFromRequest(request) {
|
|
|
6812
6842
|
const value = request.headers.get("x-sdt-g6-test-fault");
|
|
6813
6843
|
return value === "after-lease-before-prepare" || value === "after-prepare-before-apply" || value === "after-apply-before-observation" || value === "after-verify-before-audit" || value === "after-audit-before-clear" || value === "after-clear-before-final-observation" ? value : void 0;
|
|
6814
6844
|
}
|
|
6815
|
-
|
|
6816
|
-
if (request.method
|
|
6817
|
-
|
|
6845
|
+
function nonPostOperatorRepairResponse(request) {
|
|
6846
|
+
if (request.method === "POST") return void 0;
|
|
6847
|
+
return error2(404, "operator_repair_route_not_found", "Operator repair requires POST");
|
|
6848
|
+
}
|
|
6849
|
+
async function authenticateOperatorRepair(request, binding) {
|
|
6850
|
+
const decision = await checkConfiguredBearer(request, binding);
|
|
6851
|
+
if (decision === "unconfigured") {
|
|
6852
|
+
return error2(404, "operator_repair_route_not_found", "Operator repair route not found");
|
|
6818
6853
|
}
|
|
6819
|
-
|
|
6820
|
-
if (!isNonEmptyString3(token) || request.headers.get("authorization") !== `Bearer ${token}`) {
|
|
6854
|
+
if (decision === "missing" || decision === "invalid") {
|
|
6821
6855
|
return error2(401, "operator_auth_required", "A valid operator repair bearer token is required");
|
|
6822
6856
|
}
|
|
6857
|
+
return void 0;
|
|
6858
|
+
}
|
|
6859
|
+
async function handleAuthorizedOperatorRepair(request, env, nativeTracing = noOpNativeTracing, serviceIdentityProvider) {
|
|
6823
6860
|
let body;
|
|
6824
6861
|
try {
|
|
6825
6862
|
body = await request.json();
|
|
@@ -7442,7 +7479,7 @@ var CommitWorker = class {
|
|
|
7442
7479
|
identity: "coordinator"
|
|
7443
7480
|
}));
|
|
7444
7481
|
}
|
|
7445
|
-
async bootstrapPermitAcquire(commandId,
|
|
7482
|
+
async bootstrapPermitAcquire(commandId, digest) {
|
|
7446
7483
|
const stub = this.bootstrapStub();
|
|
7447
7484
|
if (stub === void 0) return { ok: true };
|
|
7448
7485
|
try {
|
|
@@ -7451,7 +7488,7 @@ var CommitWorker = class {
|
|
|
7451
7488
|
const response3 = await stub.fetch(new Request(url, {
|
|
7452
7489
|
method: "POST",
|
|
7453
7490
|
headers: { "content-type": "application/json" },
|
|
7454
|
-
body: JSON.stringify({ commandId, digest
|
|
7491
|
+
body: JSON.stringify({ commandId, digest })
|
|
7455
7492
|
}));
|
|
7456
7493
|
if (response3.ok) return { ok: true };
|
|
7457
7494
|
const body = await response3.clone().json().catch(() => void 0);
|
|
@@ -7460,7 +7497,7 @@ var CommitWorker = class {
|
|
|
7460
7497
|
return { ok: false };
|
|
7461
7498
|
}
|
|
7462
7499
|
}
|
|
7463
|
-
async bootstrapPermitRelease(commandId,
|
|
7500
|
+
async bootstrapPermitRelease(commandId, digest) {
|
|
7464
7501
|
const stub = this.bootstrapStub();
|
|
7465
7502
|
if (stub === void 0) return;
|
|
7466
7503
|
try {
|
|
@@ -7469,7 +7506,7 @@ var CommitWorker = class {
|
|
|
7469
7506
|
await stub.fetch(new Request(url, {
|
|
7470
7507
|
method: "POST",
|
|
7471
7508
|
headers: { "content-type": "application/json" },
|
|
7472
|
-
body: JSON.stringify({ commandId, digest
|
|
7509
|
+
body: JSON.stringify({ commandId, digest })
|
|
7473
7510
|
}));
|
|
7474
7511
|
} catch {
|
|
7475
7512
|
}
|
|
@@ -12191,6 +12228,33 @@ function parseRequest(value) {
|
|
|
12191
12228
|
}
|
|
12192
12229
|
};
|
|
12193
12230
|
}
|
|
12231
|
+
var QUERY_PARAMS_UNSUPPORTED_MESSAGE = "The selected query does not support these parameters.";
|
|
12232
|
+
var LIST_QUERY_PARAM_KEYS = /* @__PURE__ */ new Set(["PageNumber", "PageSize", "NewestFirst", "consistency"]);
|
|
12233
|
+
function unsupportedQueryParams(endpoint, queryParams) {
|
|
12234
|
+
if (endpoint === "query") {
|
|
12235
|
+
if (isObject9(queryParams) && Object.keys(queryParams).length === 0) {
|
|
12236
|
+
return void 0;
|
|
12237
|
+
}
|
|
12238
|
+
return {
|
|
12239
|
+
status: 400,
|
|
12240
|
+
code: "query_params_unsupported",
|
|
12241
|
+
error: QUERY_PARAMS_UNSUPPORTED_MESSAGE
|
|
12242
|
+
};
|
|
12243
|
+
}
|
|
12244
|
+
if (!isObject9(queryParams)) {
|
|
12245
|
+
return void 0;
|
|
12246
|
+
}
|
|
12247
|
+
for (const key of Object.keys(queryParams)) {
|
|
12248
|
+
if (!LIST_QUERY_PARAM_KEYS.has(key)) {
|
|
12249
|
+
return {
|
|
12250
|
+
status: 400,
|
|
12251
|
+
code: "query_params_unsupported",
|
|
12252
|
+
error: QUERY_PARAMS_UNSUPPORTED_MESSAGE
|
|
12253
|
+
};
|
|
12254
|
+
}
|
|
12255
|
+
}
|
|
12256
|
+
return void 0;
|
|
12257
|
+
}
|
|
12194
12258
|
function paginationFrom(value) {
|
|
12195
12259
|
if (!isObject9(value)) {
|
|
12196
12260
|
return { error: "list-query queryParamsJson must contain an object" };
|
|
@@ -12371,6 +12435,10 @@ async function handleSerializedQuery(request, env, options = {}) {
|
|
|
12371
12435
|
if (definition === void 0 || definition.endpoint !== endpoint) {
|
|
12372
12436
|
return error6(400, "validation_error", "queryType is not mapped for this endpoint");
|
|
12373
12437
|
}
|
|
12438
|
+
const unsupported = unsupportedQueryParams(endpoint, parsed.value.queryParams);
|
|
12439
|
+
if (unsupported !== void 0) {
|
|
12440
|
+
return error6(unsupported.status, unsupported.code, unsupported.error);
|
|
12441
|
+
}
|
|
12374
12442
|
if (!definition.enabled || (options.projectors ?? DEPLOYED_PROJECTOR_REGISTRY).resolve(definition.tagProjector) === void 0) {
|
|
12375
12443
|
return error6(503, "projection_unavailable", "The mapped query projection is unavailable");
|
|
12376
12444
|
}
|
|
@@ -12859,11 +12927,11 @@ function normalizeCorrection(value) {
|
|
|
12859
12927
|
throw new IncidentLifecycleError("incident_invalid_request", 400, "correction.kind must be event or receipt");
|
|
12860
12928
|
}
|
|
12861
12929
|
const reference = boundedString(value.reference, "correction.reference", MAX_LONG_TEXT_BYTES);
|
|
12862
|
-
const
|
|
12863
|
-
if (reference === null ||
|
|
12930
|
+
const digest = boundedString(value.digest, "correction.digest", MAX_IDENTITY_BYTES);
|
|
12931
|
+
if (reference === null || digest === null || !SHA256_DIGEST.test(digest)) {
|
|
12864
12932
|
throw new IncidentLifecycleError("incident_invalid_request", 400, "correction.digest must be lowercase sha256");
|
|
12865
12933
|
}
|
|
12866
|
-
return { kind: value.kind, reference, digest
|
|
12934
|
+
return { kind: value.kind, reference, digest };
|
|
12867
12935
|
}
|
|
12868
12936
|
function normalizeRequest(value) {
|
|
12869
12937
|
if (!isObject11(value) || !hasOnlyKeys(value, ["action", "incidentIdentity", "transitionKey", "expectedVersion", "reason", "ownerId", "deadlineAt", "correction", "resolution"])) {
|
|
@@ -12919,8 +12987,8 @@ function canonicalize(value) {
|
|
|
12919
12987
|
async function digestFor(request, actorId) {
|
|
12920
12988
|
const envelope = canonicalize({ action: request, actor: actorId });
|
|
12921
12989
|
const encoded = new TextEncoder().encode(JSON.stringify(envelope));
|
|
12922
|
-
const
|
|
12923
|
-
return `sha256:${Array.from(new Uint8Array(
|
|
12990
|
+
const digest = await crypto.subtle.digest("SHA-256", encoded);
|
|
12991
|
+
return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
12924
12992
|
}
|
|
12925
12993
|
function projectionFromRow(row, finding2) {
|
|
12926
12994
|
if (row === null) {
|
|
@@ -13147,13 +13215,13 @@ var IncidentLifecycle = class {
|
|
|
13147
13215
|
}
|
|
13148
13216
|
if (actorId === null) throw new IncidentLifecycleError("incident_invalid_actor", 400, "x-sdt-maintainer must be non-empty");
|
|
13149
13217
|
const finding2 = await this.finding(serviceId, request.incidentIdentity);
|
|
13150
|
-
const
|
|
13218
|
+
const digest = await digestFor(request, actorId);
|
|
13151
13219
|
const existing = await this.database.prepare(
|
|
13152
13220
|
`SELECT * FROM serialized_dcb_incident_transitions
|
|
13153
13221
|
WHERE service_id = ? AND incident_identity = ? AND transition_key = ?`
|
|
13154
13222
|
).bind(serviceId, request.incidentIdentity, request.transitionKey).first();
|
|
13155
13223
|
if (existing !== null && existing !== void 0) {
|
|
13156
|
-
if (existing.request_digest !==
|
|
13224
|
+
if (existing.request_digest !== digest) {
|
|
13157
13225
|
throw new IncidentLifecycleError("incident_idempotency_conflict", 409, "The transition key was already used with different content");
|
|
13158
13226
|
}
|
|
13159
13227
|
return { projection: transitionFromRow(existing).after, transition: transitionFromRow(existing), idempotent: true };
|
|
@@ -13216,7 +13284,7 @@ var IncidentLifecycle = class {
|
|
|
13216
13284
|
serviceId,
|
|
13217
13285
|
request.incidentIdentity,
|
|
13218
13286
|
request.transitionKey,
|
|
13219
|
-
|
|
13287
|
+
digest,
|
|
13220
13288
|
request.action,
|
|
13221
13289
|
before.lifecycleState,
|
|
13222
13290
|
before.version,
|
|
@@ -13245,7 +13313,7 @@ var IncidentLifecycle = class {
|
|
|
13245
13313
|
`SELECT * FROM serialized_dcb_incident_transitions
|
|
13246
13314
|
WHERE service_id = ? AND incident_identity = ? AND transition_key = ?`
|
|
13247
13315
|
).bind(serviceId, request.incidentIdentity, request.transitionKey).first();
|
|
13248
|
-
if (competing !== null && competing !== void 0 && competing.request_digest !==
|
|
13316
|
+
if (competing !== null && competing !== void 0 && competing.request_digest !== digest) {
|
|
13249
13317
|
throw new IncidentLifecycleError("incident_idempotency_conflict", 409, "The transition key was already used with different content");
|
|
13250
13318
|
}
|
|
13251
13319
|
if (competing !== null && competing !== void 0) {
|
|
@@ -13347,29 +13415,6 @@ function json10(body, status = 200) {
|
|
|
13347
13415
|
function error8(status, code, message) {
|
|
13348
13416
|
return json10({ error: message, code }, status);
|
|
13349
13417
|
}
|
|
13350
|
-
function configuredToken(value) {
|
|
13351
|
-
if (typeof value !== "string") return void 0;
|
|
13352
|
-
const token = value.trim();
|
|
13353
|
-
return token.length === 0 ? void 0 : token;
|
|
13354
|
-
}
|
|
13355
|
-
async function digest(value) {
|
|
13356
|
-
const bytes2 = new TextEncoder().encode(value);
|
|
13357
|
-
return new Uint8Array(await crypto.subtle.digest("SHA-256", bytes2));
|
|
13358
|
-
}
|
|
13359
|
-
function fixedLengthEqual(left, right) {
|
|
13360
|
-
let difference = left.length ^ right.length;
|
|
13361
|
-
const length = Math.max(left.length, right.length);
|
|
13362
|
-
for (let index = 0; index < length; index += 1) {
|
|
13363
|
-
difference |= (left[index] ?? 0) ^ (right[index] ?? 0);
|
|
13364
|
-
}
|
|
13365
|
-
return difference === 0;
|
|
13366
|
-
}
|
|
13367
|
-
async function authenticated(request, token) {
|
|
13368
|
-
const credential = request.headers.get("authorization") ?? "";
|
|
13369
|
-
const suppliedToken = credential.startsWith("Bearer ") ? credential.slice("Bearer ".length) : "";
|
|
13370
|
-
const [expectedDigest, suppliedDigest] = await Promise.all([digest(token), digest(suppliedToken)]);
|
|
13371
|
-
return fixedLengthEqual(expectedDigest, suppliedDigest);
|
|
13372
|
-
}
|
|
13373
13418
|
function boolFilter(value, name) {
|
|
13374
13419
|
if (value === null) return void 0;
|
|
13375
13420
|
if (value === "true") return true;
|
|
@@ -13423,11 +13468,13 @@ function isIncidentMaintenancePath(pathname) {
|
|
|
13423
13468
|
return pathname === "/maintenance/incidents" || pathname.startsWith("/maintenance/incidents/");
|
|
13424
13469
|
}
|
|
13425
13470
|
async function handleIncidentMaintenance(request, env, serviceIdentityProvider) {
|
|
13426
|
-
const
|
|
13427
|
-
|
|
13471
|
+
const decision = await checkConfiguredBearer(request, env.INCIDENT_MAINTAINER_TOKEN, {
|
|
13472
|
+
tokenNormalization: "trim"
|
|
13473
|
+
});
|
|
13474
|
+
if (decision === "unconfigured") {
|
|
13428
13475
|
return error8(503, "incident_maintenance_unavailable", "Incident maintenance is not configured");
|
|
13429
13476
|
}
|
|
13430
|
-
if (
|
|
13477
|
+
if (decision === "missing" || decision === "invalid") {
|
|
13431
13478
|
return error8(401, "incident_auth_required", "A valid incident maintenance bearer token is required");
|
|
13432
13479
|
}
|
|
13433
13480
|
let serviceId;
|
|
@@ -20735,6 +20782,18 @@ function createRuntimeWorker(options = {}) {
|
|
|
20735
20782
|
if (isIncidentMaintenancePath(url.pathname)) {
|
|
20736
20783
|
return handleIncidentMaintenance(request, env, options.serviceIdentityProvider ?? envServiceIdentity(env));
|
|
20737
20784
|
}
|
|
20785
|
+
const operatorRepair = url.pathname === "/operator/repair";
|
|
20786
|
+
const operatorBootstrap = url.pathname.startsWith("/operator/bootstrap/");
|
|
20787
|
+
if (operatorRepair) {
|
|
20788
|
+
const methodDenied = nonPostOperatorRepairResponse(request);
|
|
20789
|
+
if (methodDenied !== void 0) return methodDenied;
|
|
20790
|
+
const denied = await authenticateOperatorRepair(request, env.REPAIR_OPERATOR_TOKEN);
|
|
20791
|
+
if (denied !== void 0) return denied;
|
|
20792
|
+
}
|
|
20793
|
+
if (operatorBootstrap) {
|
|
20794
|
+
const denied = await authenticateOperatorBootstrap(request, env.REPAIR_OPERATOR_TOKEN);
|
|
20795
|
+
if (denied !== void 0) return denied;
|
|
20796
|
+
}
|
|
20738
20797
|
const serviceIdentity = options.serviceIdentityProvider ?? envServiceIdentity(env);
|
|
20739
20798
|
const requestIdentityOptions = { allowG11Verification: env.G11_VERIFICATION_ENABLED === "true" };
|
|
20740
20799
|
let requestServiceId;
|
|
@@ -20764,11 +20823,16 @@ function createRuntimeWorker(options = {}) {
|
|
|
20764
20823
|
serviceIdentityProvider: serviceIdentity
|
|
20765
20824
|
});
|
|
20766
20825
|
}
|
|
20767
|
-
if (
|
|
20768
|
-
return
|
|
20826
|
+
if (operatorRepair) {
|
|
20827
|
+
return handleAuthorizedOperatorRepair(
|
|
20828
|
+
request,
|
|
20829
|
+
env,
|
|
20830
|
+
nativeTracingFromContext(ctx),
|
|
20831
|
+
serviceIdentity
|
|
20832
|
+
);
|
|
20769
20833
|
}
|
|
20770
|
-
if (
|
|
20771
|
-
return
|
|
20834
|
+
if (operatorBootstrap) {
|
|
20835
|
+
return handleAuthorizedOperatorBootstrap(request, env, storeProvider);
|
|
20772
20836
|
}
|
|
20773
20837
|
if (url.pathname === "/internal/downstream/drain" && request.method === "POST") {
|
|
20774
20838
|
return handleOutboxDrainRequest(request, env, { serviceIdentityProvider: serviceIdentity });
|
|
@@ -20946,6 +21010,7 @@ export {
|
|
|
20946
21010
|
buildScopeName,
|
|
20947
21011
|
calculateUnattributedRatio,
|
|
20948
21012
|
canonicalEventType,
|
|
21013
|
+
checkConfiguredBearer,
|
|
20949
21014
|
chooseQueryBacking,
|
|
20950
21015
|
classifyDirectDoorbellFailure,
|
|
20951
21016
|
classifyReactivationCause,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sekiban/dcb-runtime",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Cloudflare Durable Object and HTTP runtime for Serialized DCB V1.",
|
|
6
6
|
"license": "Elastic-2.0",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"provenance": true
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@sekiban/dcb-core": "0.2.
|
|
55
|
+
"@sekiban/dcb-core": "0.2.2"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "tsc -p tsconfig.build.json && esbuild src/index.ts --bundle --format=esm --platform=neutral --external:postgres --external:cloudflare:workers --outfile=dist/index.js && esbuild src/cosmos.ts --bundle --format=esm --platform=neutral --external:postgres --external:cloudflare:workers --outfile=dist/cosmos.js && esbuild src/d1.ts --bundle --format=esm --platform=neutral --external:postgres --external:cloudflare:workers --outfile=dist/d1.js && esbuild src/d1-mv.ts --bundle --format=esm --platform=neutral --external:postgres --external:cloudflare:workers --outfile=dist/d1-mv.js && esbuild src/cloudflare.ts --bundle --format=esm --platform=neutral --external:cloudflare:workers --outfile=dist/cloudflare.js && esbuild src/mv/index.ts --bundle --format=esm --platform=neutral --external:postgres --external:cloudflare:workers --outfile=dist/mv/index.js",
|