@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.
Files changed (44) hide show
  1. package/README.md +32 -1
  2. package/dist/allocator/AllocatorDurableObject.js +25 -2
  3. package/dist/bootstrap/BootstrapCoordinatorDurableObject.d.ts +2 -0
  4. package/dist/bootstrap/BootstrapCoordinatorDurableObject.js +64 -1
  5. package/dist/bootstrap/OperatorBootstrap.d.ts +5 -1
  6. package/dist/bootstrap/OperatorBootstrap.js +17 -10
  7. package/dist/bootstrap/types.d.ts +6 -0
  8. package/dist/cli/OperatorRepairCli.d.ts +6 -1
  9. package/dist/cli/OperatorRepairCli.js +26 -6
  10. package/dist/cloudflare.d.ts +5 -0
  11. package/dist/cloudflare.js +1567 -465
  12. package/dist/commit/CommitWorker.d.ts +19 -0
  13. package/dist/commit/CommitWorker.js +176 -27
  14. package/dist/completeness/IncidentLifecycle.d.ts +75 -0
  15. package/dist/completeness/IncidentLifecycle.js +503 -0
  16. package/dist/completeness/types.d.ts +67 -0
  17. package/dist/cosmos.d.ts +34 -4
  18. package/dist/cosmos.js +214 -49
  19. package/dist/downstream/OutboxDrain.d.ts +4 -1
  20. package/dist/downstream/OutboxDrain.js +24 -2
  21. package/dist/generated/cosmos-layout.d.ts +87 -0
  22. package/dist/generated/cosmos-layout.js +99 -0
  23. package/dist/http/ConfiguredBearer.d.ts +12 -0
  24. package/dist/http/ConfiguredBearer.js +33 -0
  25. package/dist/http/IncidentMaintenance.d.ts +8 -0
  26. package/dist/http/IncidentMaintenance.js +124 -0
  27. package/dist/http/SerializedQueryWorker.js +37 -0
  28. package/dist/index.d.ts +13 -0
  29. package/dist/index.js +1627 -465
  30. package/dist/read/SerializedReadWorker.d.ts +3 -0
  31. package/dist/read/SerializedReadWorker.js +3 -3
  32. package/dist/shard/ShardRotation.d.ts +48 -0
  33. package/dist/shard/ShardRotation.js +120 -0
  34. package/dist/store/CosmosEventStore.js +56 -46
  35. package/dist/store/PostgresEventStore.d.ts +20 -0
  36. package/dist/store/PostgresEventStore.js +53 -0
  37. package/dist/store/provider.d.ts +4 -0
  38. package/dist/tag/TagDurableObject.js +66 -14
  39. package/dist/tag/TagSqlSchema.d.ts +6 -1
  40. package/dist/tag/TagSqlSchema.js +15 -0
  41. package/dist/tag/types.d.ts +9 -0
  42. package/dist/trace/CommitTrace.d.ts +3 -3
  43. package/dist/trace/CommitTraceVerifier.js +1 -1
  44. package/package.json +6 -15
package/README.md CHANGED
@@ -5,9 +5,40 @@ Cloudflare Durable Object and HTTP runtime for Serialized DCB V1.
5
5
  This package is the matched-set runtime companion to `@sekiban/dcb-core`,
6
6
  `@sekiban/dcb-domain`, and `@sekiban/dcb-client`. Consumers such as the
7
7
  meeting-room Cloudflare sample import public entry points (`@sekiban/dcb-runtime`,
8
- `@sekiban/dcb-runtime/cloudflare`, `/d1`, `/d1-mv`, `/mv`, `/cosmos`) and re-export
8
+ `@sekiban/dcb-runtime/cloudflare`, `/d1`, `/d1-mv`, `/mv`, and the experimental `/cosmos`) and re-export
9
9
  Durable Object classes from their Worker module graph as Cloudflare requires.
10
10
 
11
+ The experimental Cosmos provider is never selected by environment values. A
12
+ consumer must import `createCosmosStoreProvider` from `/cosmos` and pass it as
13
+ `createRuntimeWorker({ storeProvider })`.
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
+
11
42
  ## License
12
43
 
13
44
  Elastic License 2.0. See [LICENSE](./LICENSE).
@@ -10,6 +10,8 @@ const ATTEMPT_KEY_PREFIX = "attempt:";
10
10
  const ROLLBACK_WARNING_WINDOW_MS = 1000n;
11
11
  class AllocationTransactionFault extends Error {
12
12
  }
13
+ class ImportSeedRequiredError extends Error {
14
+ }
13
15
  function json(body, status = 200) {
14
16
  return new Response(JSON.stringify(body), {
15
17
  status,
@@ -55,7 +57,19 @@ function seedFrom(value) {
55
57
  catch {
56
58
  return { error: "highWatermark must be an allocator SUID" };
57
59
  }
58
- return { value: { importId: value.importId, leaseEpoch: value.leaseEpoch, highWatermark: value.highWatermark } };
60
+ let storeMaximum;
61
+ if (value.storeMaximum !== undefined) {
62
+ if (!isNonEmptyString(value.storeMaximum))
63
+ return { error: "storeMaximum must be an allocator SUID" };
64
+ try {
65
+ decodeSuid(value.storeMaximum);
66
+ }
67
+ catch {
68
+ return { error: "storeMaximum must be an allocator SUID" };
69
+ }
70
+ storeMaximum = value.storeMaximum;
71
+ }
72
+ return { value: { importId: value.importId, leaseEpoch: value.leaseEpoch, highWatermark: value.highWatermark, ...(storeMaximum !== undefined ? { storeMaximum } : {}) } };
59
73
  }
60
74
  function newAllocatorLineageId() {
61
75
  // A fresh token is generated when the allocator has no durable state, so a
@@ -118,6 +132,7 @@ function allocateFrom(value) {
118
132
  ...(serviceId !== undefined ? { serviceId } : {}),
119
133
  ...(bootstrapCommandId !== undefined ? { bootstrapCommandId } : {}),
120
134
  ...(bootstrapEpoch !== undefined ? { bootstrapEpoch } : {}),
135
+ ...(value.requiresImportSeed === true ? { requiresImportSeed: true } : {}),
121
136
  },
122
137
  };
123
138
  }
@@ -519,6 +534,8 @@ export class AllocatorDurableObject {
519
534
  const state = persistedState === undefined
520
535
  ? currentState(lineage)
521
536
  : { ...persistedState, allocatorLineageId: lineage, schemaVersion: 5, bootstrapSeed: persistedState.bootstrapSeed ?? null, lastRollbackWarningFingerprint: persistedState.lastRollbackWarningFingerprint ?? null };
537
+ if (input.requiresImportSeed === true && state.bootstrapSeed === null)
538
+ throw new ImportSeedRequiredError();
522
539
  // This is intentionally before the first transaction write. A clock
523
540
  // failure therefore cannot leave a vector, watermark, or warning fact.
524
541
  let clockTick;
@@ -609,6 +626,9 @@ export class AllocatorDurableObject {
609
626
  return json(result.vector, result.created ? 201 : 200);
610
627
  }
611
628
  catch (failure) {
629
+ if (failure instanceof ImportSeedRequiredError) {
630
+ return error(409, "allocator_seed_required", "commit before import seed");
631
+ }
612
632
  if (failure instanceof AllocationTransactionFault) {
613
633
  return error(503, "simulated_allocation_crash", "Simulated interruption before durable allocation transaction commit");
614
634
  }
@@ -649,7 +669,10 @@ export class AllocatorDurableObject {
649
669
  }
650
670
  if (state.allocatedWatermark !== null)
651
671
  return { status: 409, body: { code: "allocator_seed_rejected", error: "allocator already allocating" } };
652
- const updated = { ...state, allocatedWatermark: input.highWatermark, bootstrapSeed: input };
672
+ if (input.storeMaximum !== undefined && input.highWatermark < input.storeMaximum) {
673
+ return { status: 409, body: { code: "allocator_seed_below_store_max", error: "seed below store maximum" } };
674
+ }
675
+ const updated = { ...state, allocatedWatermark: input.highWatermark, bootstrapSeed: { importId: input.importId, leaseEpoch: input.leaseEpoch, highWatermark: input.highWatermark } };
653
676
  await txn.put(STATE_KEY, updated);
654
677
  return { status: 201, body: updated };
655
678
  });
@@ -23,6 +23,8 @@ export declare class BootstrapCoordinatorDurableObject implements DurableObject
23
23
  /** Operator abort retains the durable failed plan for audit/retry; it never clears target state. */
24
24
  private abort;
25
25
  private route;
26
+ private permit;
27
+ private permitRelease;
26
28
  private command;
27
29
  }
28
30
  export {};
@@ -13,13 +13,22 @@ const epoch = (value) => typeof value === "number" && Number.isSafeInteger(value
13
13
  const response = (body, status = 200) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json; charset=utf-8" } });
14
14
  const reject = (code, message, status = 409) => response({ code, error: message }, status);
15
15
  function empty(serviceId) {
16
- 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 };
16
+ 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 };
17
17
  }
18
18
  function expired(control) { return control.leaseUntil !== null && control.leaseUntil <= Date.now(); }
19
19
  function commands(control) { return { ...(control.normalCommands ?? {}) }; }
20
20
  function released(control) { return { ...(control.releasedCommands ?? {}) }; }
21
+ function permits(control) { return { ...(control.writePermits ?? {}) }; }
21
22
  function fault(body, point) { return object(body) && body.faultAt === point; }
22
23
  function encodedBytes(value) { return new TextEncoder().encode(JSON.stringify(value)).byteLength; }
24
+ async function readBody(request) {
25
+ try {
26
+ return await request.json();
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
23
32
  /** Per-service authority and linearization point for bootstrap and normal writes. */
24
33
  export class BootstrapCoordinatorDurableObject {
25
34
  ctx;
@@ -48,6 +57,10 @@ export class BootstrapCoordinatorDurableObject {
48
57
  return this.traceCommand(request, serviceId, activation, observation, "finalize");
49
58
  if (request.method === "POST" && url.pathname === "/command/release")
50
59
  return this.traceCommand(request, serviceId, activation, observation, "release");
60
+ if (request.method === "POST" && url.pathname === "/command/permit")
61
+ return this.permit(serviceId, await readBody(request));
62
+ if (request.method === "POST" && url.pathname === "/command/permit-release")
63
+ return this.permitRelease(serviceId, await readBody(request));
51
64
  let body;
52
65
  try {
53
66
  body = await request.json();
@@ -119,6 +132,8 @@ export class BootstrapCoordinatorDurableObject {
119
132
  return { error: "bootstrap_ready_permanent" };
120
133
  if (control.status !== "EMPTY")
121
134
  return control.importId === importId && control.digest === dump.manifest.contentDigest ? { control } : { error: "bootstrap_plan_conflict" };
135
+ if (Object.keys(permits(control)).length !== 0)
136
+ return { error: "bootstrap_write_permit_active" };
122
137
  if (control.normalInFlight !== 0 || Object.keys(commands(control)).length !== 0)
123
138
  return { error: "bootstrap_normal_command_in_flight" };
124
139
  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() + 30_000, failure: null, progress: {}, verifiedImportId: null, verifiedLeaseEpoch: null, storeCompletion: null };
@@ -315,6 +330,54 @@ export class BootstrapCoordinatorDurableObject {
315
330
  const control = await this.control(serviceId);
316
331
  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`);
317
332
  }
333
+ async permit(serviceId, body) {
334
+ if (!object(body) || !string(body.commandId) || !string(body.digest))
335
+ return reject("bootstrap_permit_invalid", "commandId and digest are required", 400);
336
+ const commandId = body.commandId;
337
+ const digest = body.digest;
338
+ const result = await this.ctx.storage.transaction(async (txn) => {
339
+ const control = (await txn.get(CONTROL)) ?? empty(serviceId);
340
+ const active = permits(control);
341
+ if (active[commandId] !== undefined) {
342
+ return active[commandId] === digest
343
+ ? { control }
344
+ : { digestMismatch: true };
345
+ }
346
+ if (control.status !== "EMPTY" && control.status !== "READY")
347
+ return { rejected: true };
348
+ const updated = { ...control, writePermits: { ...active, [commandId]: digest } };
349
+ await txn.put(CONTROL, updated);
350
+ return { control: updated };
351
+ });
352
+ if ("digestMismatch" in result)
353
+ return reject("bootstrap_permit_digest_mismatch", "bootstrap permit digest does not match the held permit");
354
+ if ("rejected" in result)
355
+ return reject("bootstrap_command_rejected", "bootstrap epoch rejects normal durable mutation");
356
+ return response({ leaseEpoch: result.control.leaseEpoch, admitted: true });
357
+ }
358
+ async permitRelease(serviceId, body) {
359
+ if (!object(body) || !string(body.commandId))
360
+ return reject("bootstrap_permit_invalid", "commandId is required", 400);
361
+ const commandId = body.commandId;
362
+ const digest = string(body.digest) ? body.digest : undefined;
363
+ const result = await this.ctx.storage.transaction(async (txn) => {
364
+ const control = (await txn.get(CONTROL)) ?? empty(serviceId);
365
+ const active = permits(control);
366
+ if (active[commandId] !== undefined && digest !== undefined && active[commandId] !== digest) {
367
+ return { digestMismatch: true };
368
+ }
369
+ if (active[commandId] === undefined)
370
+ return { control };
371
+ const next = { ...active };
372
+ delete next[commandId];
373
+ const updated = { ...control, writePermits: next };
374
+ await txn.put(CONTROL, updated);
375
+ return { control: updated };
376
+ });
377
+ if ("digestMismatch" in result)
378
+ return reject("bootstrap_permit_digest_mismatch", "bootstrap permit digest does not match the held permit");
379
+ return response({ leaseEpoch: result.control.leaseEpoch, admitted: true });
380
+ }
318
381
  async command(serviceId, body, action, observation) {
319
382
  if (!object(body) || !string(body.commandId))
320
383
  return reject("bootstrap_command_invalid", "commandId is required", 400);
@@ -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
- /** Bearer-only operator lane. Incoming headers are never forwarded to a DO. */
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
- function bearer(request, token) {
7
- const supplied = request.headers.get("authorization");
8
- // Keep absence indistinguishable from an undiscovered management surface.
9
- if (supplied === null)
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
- if (supplied !== `Bearer ${token}`)
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
- /** Bearer-only operator lane. Incoming headers are never forwarded to a DO. */
16
- export async function handleOperatorBootstrap(request, env, storeProvider, hooks = {}) {
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
+ }
@@ -55,6 +55,12 @@ export interface BootstrapControlRecord {
55
55
  readonly normalCommands?: Readonly<Record<string, number>>;
56
56
  /** Recently released entry admissions, retained only for their final epoch check. */
57
57
  readonly releasedCommands?: Readonly<Record<string, number>>;
58
+ /**
59
+ * SDT-G36 durable write permits. Held from before allocation until every
60
+ * target tag of that attempt is durably resolved. Absent on old records
61
+ * (treated as `{}`). Only a bootstrap barrier, never commit authority.
62
+ */
63
+ readonly writePermits?: Readonly<Record<string, string>>;
58
64
  /** READY is a post-verification state, never merely an import completion. */
59
65
  readonly verifiedImportId?: string | null;
60
66
  readonly verifiedLeaseEpoch?: number | null;
@@ -7,5 +7,10 @@ export interface OperatorRepairEnv {
7
7
  REPAIR_EXCLUSION_LOOKUP?: Fetcher;
8
8
  SDT_SERVICE_ID?: string;
9
9
  }
10
- /** Authenticated, bounded control-plane command; it is not a V1 public wire endpoint. */
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
- /** Authenticated, bounded control-plane command; it is not a V1 public wire endpoint. */
78
- export async function handleOperatorRepair(request, env, nativeTracing = noOpNativeTracing, serviceIdentityProvider) {
79
- if (request.method !== "POST") {
80
- return error(404, "operator_repair_route_not_found", "Operator repair requires POST");
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
- const token = env.REPAIR_OPERATOR_TOKEN;
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
+ }
@@ -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;
@@ -42,6 +44,7 @@ export interface CloudflareOnlyEnv {
42
44
  D1_MV: D1Database;
43
45
  AUTO_DRAIN_OUTBOX?: string;
44
46
  REPAIR_OPERATOR_TOKEN: string;
47
+ INCIDENT_MAINTAINER_TOKEN?: string;
45
48
  REPAIR_EXCLUSION_LOOKUP?: Fetcher;
46
49
  G11_VERIFICATION_ENABLED?: string;
47
50
  SDT_SERVICE_ID?: string;
@@ -152,6 +155,8 @@ export type { ClosedPrefixCertificate } from "./allocator/types";
152
155
  export type { SafeViewCoverageContext } from "./projection/ProjectionRuntime";
153
156
  export { downstreamEnvelopeBytes, classifyDirectDoorbellFailure, preflightDirectDoorbell, readDomainDeliveryClass, readDirectDoorbellConfig, selectDirectDoorbellViews, MAX_SERVICE_BINDING_INVOCATIONS_PER_REQUEST, } from "./downstream/Doorbell";
154
157
  export { deliveryCorrelationId } from "./downstream/DeliveryCore";
158
+ export { admitActiveWrite, assertNoSilentLedgerMerge, assertStrictlyBefore, createRotationState, maxSortableUniqueId, recordLateArrival, refuseSafeWindowSeal, refuseSealedWrite, refuseSilentLedgerMerge, replaceSealedIdentity, rotateAppend, ShardRotationError, } from "./shard/ShardRotation";
159
+ export type { LateArrival, SealedShard, ShardRotationDiagnostic, ShardRotationState, } from "./shard/ShardRotation";
155
160
  export { GlobalCompletenessReconciler } from "./completeness/GlobalCompletenessReconciler";
156
161
  export { G44_HEALTH_STALE_AFTER_MS, G44_SCANNER_VERSION, GLOBAL_COMPLETENESS_INTERIM_DISPOSITION, } from "./completeness/types";
157
162
  export type { GlobalCompletenessHealth, GlobalCompletenessCoverage, GlobalCompletenessHealthRecord, GlobalCompletenessScanResult, SourceObligationFact, SourceObligationPage, SourcePartitionSnapshot, } from "./completeness/types";