baa-conductor

git clone 

baa-conductor / packages / db / src
im_wower  ·  2026-03-21

index.ts

   1export const D1_TABLES = [
   2  "leader_lease",
   3  "controllers",
   4  "workers",
   5  "tasks",
   6  "task_steps",
   7  "task_runs",
   8  "task_checkpoints",
   9  "task_logs",
  10  "system_state",
  11  "task_artifacts"
  12] as const;
  13
  14export type D1TableName = (typeof D1_TABLES)[number];
  15
  16export const GLOBAL_LEASE_NAME = "global";
  17export const AUTOMATION_STATE_KEY = "automation";
  18export const DEFAULT_AUTOMATION_MODE = "running";
  19export const DEFAULT_LEASE_TTL_SEC = 30;
  20export const DEFAULT_LEASE_RENEW_INTERVAL_SEC = 5;
  21export const DEFAULT_LEASE_RENEW_FAILURE_THRESHOLD = 2;
  22
  23export const AUTOMATION_MODE_VALUES = ["running", "draining", "paused"] as const;
  24export const TASK_STATUS_VALUES = [
  25  "queued",
  26  "planning",
  27  "running",
  28  "paused",
  29  "done",
  30  "failed",
  31  "canceled"
  32] as const;
  33export const STEP_STATUS_VALUES = ["pending", "running", "done", "failed", "timeout"] as const;
  34export const STEP_KIND_VALUES = ["planner", "codex", "shell", "git", "review", "finalize"] as const;
  35
  36export type AutomationMode = (typeof AUTOMATION_MODE_VALUES)[number];
  37export type TaskStatus = (typeof TASK_STATUS_VALUES)[number];
  38export type StepStatus = (typeof STEP_STATUS_VALUES)[number];
  39export type StepKind = (typeof STEP_KIND_VALUES)[number];
  40
  41export type JsonPrimitive = boolean | number | null | string;
  42export type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
  43
  44export interface JsonObject {
  45  [key: string]: JsonValue;
  46}
  47
  48export type D1Bindable = number | null | string;
  49
  50export interface DatabaseRow {
  51  [column: string]: unknown;
  52}
  53
  54export interface D1ResultMeta {
  55  changed_db?: boolean;
  56  changes?: number;
  57  duration?: number;
  58  last_row_id?: number;
  59  rows_read?: number;
  60  rows_written?: number;
  61  served_by?: string;
  62  size_after?: number;
  63}
  64
  65export interface D1QueryResult<T = never> {
  66  meta: D1ResultMeta;
  67  results?: T[];
  68  success: boolean;
  69}
  70
  71export interface D1ExecResult {
  72  count?: number;
  73  duration?: number;
  74}
  75
  76export interface D1PreparedStatementLike {
  77  all<T = DatabaseRow>(): Promise<D1QueryResult<T>>;
  78  bind(...values: D1Bindable[]): D1PreparedStatementLike;
  79  first<T = DatabaseRow>(columnName?: string): Promise<T | null>;
  80  raw<T = unknown[]>(options?: { columnNames?: boolean }): Promise<T[]>;
  81  run(): Promise<D1QueryResult<never>>;
  82}
  83
  84export interface D1DatabaseLike {
  85  batch<T = never>(statements: D1PreparedStatementLike[]): Promise<Array<D1QueryResult<T>>>;
  86  exec(query: string): Promise<D1ExecResult>;
  87  prepare(query: string): D1PreparedStatementLike;
  88}
  89
  90export interface LeaderLeaseRecord {
  91  leaseName: string;
  92  holderId: string;
  93  holderHost: string;
  94  term: number;
  95  leaseExpiresAt: number;
  96  renewedAt: number;
  97  preferredHolderId: string | null;
  98  metadataJson: string | null;
  99}
 100
 101export interface ControllerRecord {
 102  controllerId: string;
 103  host: string;
 104  role: string;
 105  priority: number;
 106  status: string;
 107  version: string | null;
 108  lastHeartbeatAt: number;
 109  lastStartedAt: number | null;
 110  metadataJson: string | null;
 111}
 112
 113export type LeaderLeaseOperation = "acquire" | "renew";
 114
 115export interface ControllerHeartbeatInput {
 116  controllerId: string;
 117  host: string;
 118  role: string;
 119  priority: number;
 120  status: string;
 121  version?: string | null;
 122  heartbeatAt?: number;
 123  startedAt?: number | null;
 124  metadataJson?: string | null;
 125}
 126
 127export interface LeaderLeaseAcquireInput {
 128  controllerId: string;
 129  host: string;
 130  ttlSec: number;
 131  preferred?: boolean;
 132  metadataJson?: string | null;
 133  now?: number;
 134}
 135
 136export interface LeaderLeaseAcquireResult {
 137  holderId: string;
 138  holderHost: string;
 139  term: number;
 140  leaseExpiresAt: number;
 141  renewedAt: number;
 142  isLeader: boolean;
 143  operation: LeaderLeaseOperation;
 144  lease: LeaderLeaseRecord;
 145}
 146
 147export interface WorkerRecord {
 148  workerId: string;
 149  controllerId: string;
 150  host: string;
 151  workerType: string;
 152  status: string;
 153  maxParallelism: number;
 154  currentLoad: number;
 155  lastHeartbeatAt: number;
 156  capabilitiesJson: string | null;
 157  metadataJson: string | null;
 158}
 159
 160export interface TaskRecord {
 161  taskId: string;
 162  repo: string;
 163  taskType: string;
 164  title: string;
 165  goal: string;
 166  source: string;
 167  priority: number;
 168  status: TaskStatus;
 169  planningStrategy: string | null;
 170  plannerProvider: string | null;
 171  branchName: string | null;
 172  baseRef: string | null;
 173  targetHost: string | null;
 174  assignedControllerId: string | null;
 175  currentStepIndex: number;
 176  constraintsJson: string | null;
 177  acceptanceJson: string | null;
 178  metadataJson: string | null;
 179  resultSummary: string | null;
 180  resultJson: string | null;
 181  errorText: string | null;
 182  createdAt: number;
 183  updatedAt: number;
 184  startedAt: number | null;
 185  finishedAt: number | null;
 186}
 187
 188export interface TaskStepRecord {
 189  stepId: string;
 190  taskId: string;
 191  stepIndex: number;
 192  stepName: string;
 193  stepKind: StepKind;
 194  status: StepStatus;
 195  assignedWorkerId: string | null;
 196  assignedControllerId: string | null;
 197  timeoutSec: number;
 198  retryLimit: number;
 199  retryCount: number;
 200  leaseExpiresAt: number | null;
 201  inputJson: string | null;
 202  outputJson: string | null;
 203  summary: string | null;
 204  errorText: string | null;
 205  createdAt: number;
 206  updatedAt: number;
 207  startedAt: number | null;
 208  finishedAt: number | null;
 209}
 210
 211export interface TaskRunRecord {
 212  runId: string;
 213  taskId: string;
 214  stepId: string;
 215  workerId: string;
 216  controllerId: string;
 217  host: string;
 218  pid: number | null;
 219  status: string;
 220  leaseExpiresAt: number | null;
 221  heartbeatAt: number | null;
 222  logDir: string;
 223  stdoutPath: string | null;
 224  stderrPath: string | null;
 225  workerLogPath: string | null;
 226  checkpointSeq: number;
 227  exitCode: number | null;
 228  resultJson: string | null;
 229  errorText: string | null;
 230  createdAt: number;
 231  startedAt: number | null;
 232  finishedAt: number | null;
 233}
 234
 235export interface TaskCheckpointRecord {
 236  checkpointId: string;
 237  taskId: string;
 238  stepId: string;
 239  runId: string;
 240  seq: number;
 241  checkpointType: string;
 242  summary: string | null;
 243  contentText: string | null;
 244  contentJson: string | null;
 245  createdAt: number;
 246}
 247
 248export interface TaskLogRecord {
 249  logId: number;
 250  taskId: string;
 251  stepId: string | null;
 252  runId: string;
 253  seq: number;
 254  stream: string;
 255  level: string | null;
 256  message: string;
 257  createdAt: number;
 258}
 259
 260export interface NewTaskLogRecord {
 261  taskId: string;
 262  stepId: string | null;
 263  runId: string;
 264  seq: number;
 265  stream: string;
 266  level: string | null;
 267  message: string;
 268  createdAt: number;
 269}
 270
 271export interface SystemStateRecord {
 272  stateKey: string;
 273  valueJson: string;
 274  updatedAt: number;
 275}
 276
 277export interface AutomationStateRecord extends SystemStateRecord {
 278  stateKey: typeof AUTOMATION_STATE_KEY;
 279  mode: AutomationMode;
 280}
 281
 282export interface TaskArtifactRecord {
 283  artifactId: string;
 284  taskId: string;
 285  stepId: string | null;
 286  runId: string | null;
 287  artifactType: string;
 288  path: string | null;
 289  uri: string | null;
 290  sizeBytes: number | null;
 291  sha256: string | null;
 292  metadataJson: string | null;
 293  createdAt: number;
 294}
 295
 296export interface ControlPlaneRepository {
 297  appendTaskLog(record: NewTaskLogRecord): Promise<number | null>;
 298  heartbeatController(input: ControllerHeartbeatInput): Promise<ControllerRecord>;
 299  ensureAutomationState(mode?: AutomationMode): Promise<void>;
 300  acquireLeaderLease(input: LeaderLeaseAcquireInput): Promise<LeaderLeaseAcquireResult>;
 301  getAutomationState(): Promise<AutomationStateRecord | null>;
 302  getCurrentLease(): Promise<LeaderLeaseRecord | null>;
 303  getSystemState(stateKey: string): Promise<SystemStateRecord | null>;
 304  getTask(taskId: string): Promise<TaskRecord | null>;
 305  insertTask(record: TaskRecord): Promise<void>;
 306  insertTaskArtifact(record: TaskArtifactRecord): Promise<void>;
 307  insertTaskCheckpoint(record: TaskCheckpointRecord): Promise<void>;
 308  insertTaskRun(record: TaskRunRecord): Promise<void>;
 309  insertTaskStep(record: TaskStepRecord): Promise<void>;
 310  insertTaskSteps(records: TaskStepRecord[]): Promise<void>;
 311  listTaskSteps(taskId: string): Promise<TaskStepRecord[]>;
 312  putLeaderLease(record: LeaderLeaseRecord): Promise<void>;
 313  putSystemState(record: SystemStateRecord): Promise<void>;
 314  setAutomationMode(mode: AutomationMode, updatedAt?: number): Promise<void>;
 315  upsertController(record: ControllerRecord): Promise<void>;
 316  upsertWorker(record: WorkerRecord): Promise<void>;
 317}
 318
 319const AUTOMATION_MODE_SET = new Set<string>(AUTOMATION_MODE_VALUES);
 320const TASK_STATUS_SET = new Set<string>(TASK_STATUS_VALUES);
 321const STEP_STATUS_SET = new Set<string>(STEP_STATUS_VALUES);
 322const STEP_KIND_SET = new Set<string>(STEP_KIND_VALUES);
 323
 324export function nowUnixSeconds(date: Date = new Date()): number {
 325  return Math.floor(date.getTime() / 1000);
 326}
 327
 328export function stringifyJson(value: JsonValue | null | undefined): string | null {
 329  if (value == null) {
 330    return null;
 331  }
 332
 333  return JSON.stringify(value);
 334}
 335
 336export function parseJsonText<T>(jsonText: string | null | undefined): T | null {
 337  if (jsonText == null || jsonText === "") {
 338    return null;
 339  }
 340
 341  return JSON.parse(jsonText) as T;
 342}
 343
 344export function isAutomationMode(value: unknown): value is AutomationMode {
 345  return typeof value === "string" && AUTOMATION_MODE_SET.has(value);
 346}
 347
 348export function isTaskStatus(value: unknown): value is TaskStatus {
 349  return typeof value === "string" && TASK_STATUS_SET.has(value);
 350}
 351
 352export function isStepStatus(value: unknown): value is StepStatus {
 353  return typeof value === "string" && STEP_STATUS_SET.has(value);
 354}
 355
 356export function isStepKind(value: unknown): value is StepKind {
 357  return typeof value === "string" && STEP_KIND_SET.has(value);
 358}
 359
 360export function buildAutomationStateValue(mode: AutomationMode): string {
 361  return JSON.stringify({ mode });
 362}
 363
 364export function isLeaderLeaseExpired(
 365  lease: Pick<LeaderLeaseRecord, "leaseExpiresAt">,
 366  now: number = nowUnixSeconds()
 367): boolean {
 368  return lease.leaseExpiresAt <= now;
 369}
 370
 371export function canAcquireLeaderLease(
 372  lease: LeaderLeaseRecord | null,
 373  controllerId: string,
 374  now: number = nowUnixSeconds()
 375): boolean {
 376  return lease == null || lease.holderId === controllerId || isLeaderLeaseExpired(lease, now);
 377}
 378
 379export function getLeaderLeaseOperation(
 380  lease: LeaderLeaseRecord | null,
 381  controllerId: string,
 382  now: number = nowUnixSeconds()
 383): LeaderLeaseOperation {
 384  return lease != null && lease.holderId === controllerId && !isLeaderLeaseExpired(lease, now)
 385    ? "renew"
 386    : "acquire";
 387}
 388
 389export function buildControllerHeartbeatRecord(input: ControllerHeartbeatInput): ControllerRecord {
 390  const heartbeatAt = input.heartbeatAt ?? nowUnixSeconds();
 391
 392  return {
 393    controllerId: input.controllerId,
 394    host: input.host,
 395    role: input.role,
 396    priority: input.priority,
 397    status: input.status,
 398    version: input.version ?? null,
 399    lastHeartbeatAt: heartbeatAt,
 400    lastStartedAt: input.startedAt ?? heartbeatAt,
 401    metadataJson: input.metadataJson ?? null
 402  };
 403}
 404
 405export function buildLeaderLeaseRecord(
 406  currentLease: LeaderLeaseRecord | null,
 407  input: LeaderLeaseAcquireInput
 408): LeaderLeaseRecord {
 409  const now = input.now ?? nowUnixSeconds();
 410  const operation = getLeaderLeaseOperation(currentLease, input.controllerId, now);
 411
 412  return {
 413    leaseName: GLOBAL_LEASE_NAME,
 414    holderId: input.controllerId,
 415    holderHost: input.host,
 416    term: operation === "renew" ? currentLease!.term : (currentLease?.term ?? 0) + 1,
 417    leaseExpiresAt: now + input.ttlSec,
 418    renewedAt: now,
 419    preferredHolderId: input.preferred ? input.controllerId : currentLease?.preferredHolderId ?? null,
 420    metadataJson: input.metadataJson ?? currentLease?.metadataJson ?? null
 421  };
 422}
 423
 424export function buildLeaderLeaseAcquireResult(
 425  previousLease: LeaderLeaseRecord | null,
 426  currentLease: LeaderLeaseRecord,
 427  input: LeaderLeaseAcquireInput
 428): LeaderLeaseAcquireResult {
 429  const now = input.now ?? currentLease.renewedAt;
 430
 431  return {
 432    holderId: currentLease.holderId,
 433    holderHost: currentLease.holderHost,
 434    term: currentLease.term,
 435    leaseExpiresAt: currentLease.leaseExpiresAt,
 436    renewedAt: currentLease.renewedAt,
 437    isLeader: currentLease.holderId === input.controllerId,
 438    operation:
 439      currentLease.holderId === input.controllerId
 440        ? getLeaderLeaseOperation(previousLease, input.controllerId, now)
 441        : "acquire",
 442    lease: currentLease
 443  };
 444}
 445
 446function toD1Bindable(value: D1Bindable | undefined): D1Bindable {
 447  return value ?? null;
 448}
 449
 450function readColumn(row: DatabaseRow, column: string): unknown {
 451  if (!(column in row)) {
 452    throw new TypeError(`Expected D1 row to include column "${column}".`);
 453  }
 454
 455  return row[column];
 456}
 457
 458function readRequiredString(row: DatabaseRow, column: string): string {
 459  const value = readColumn(row, column);
 460
 461  if (typeof value !== "string") {
 462    throw new TypeError(`Expected column "${column}" to be a string.`);
 463  }
 464
 465  return value;
 466}
 467
 468function readOptionalString(row: DatabaseRow, column: string): string | null {
 469  const value = readColumn(row, column);
 470
 471  if (value == null) {
 472    return null;
 473  }
 474
 475  if (typeof value !== "string") {
 476    throw new TypeError(`Expected column "${column}" to be a string or null.`);
 477  }
 478
 479  return value;
 480}
 481
 482function readRequiredNumber(row: DatabaseRow, column: string): number {
 483  const value = readColumn(row, column);
 484
 485  if (typeof value !== "number") {
 486    throw new TypeError(`Expected column "${column}" to be a number.`);
 487  }
 488
 489  return value;
 490}
 491
 492function readOptionalNumber(row: DatabaseRow, column: string): number | null {
 493  const value = readColumn(row, column);
 494
 495  if (value == null) {
 496    return null;
 497  }
 498
 499  if (typeof value !== "number") {
 500    throw new TypeError(`Expected column "${column}" to be a number or null.`);
 501  }
 502
 503  return value;
 504}
 505
 506function readTaskStatus(row: DatabaseRow, column: string): TaskStatus {
 507  const value = readRequiredString(row, column);
 508
 509  if (!isTaskStatus(value)) {
 510    throw new TypeError(`Unexpected task status "${value}".`);
 511  }
 512
 513  return value;
 514}
 515
 516function readStepStatus(row: DatabaseRow, column: string): StepStatus {
 517  const value = readRequiredString(row, column);
 518
 519  if (!isStepStatus(value)) {
 520    throw new TypeError(`Unexpected step status "${value}".`);
 521  }
 522
 523  return value;
 524}
 525
 526function readStepKind(row: DatabaseRow, column: string): StepKind {
 527  const value = readRequiredString(row, column);
 528
 529  if (!isStepKind(value)) {
 530    throw new TypeError(`Unexpected step kind "${value}".`);
 531  }
 532
 533  return value;
 534}
 535
 536export function mapLeaderLeaseRow(row: DatabaseRow): LeaderLeaseRecord {
 537  return {
 538    leaseName: readRequiredString(row, "lease_name"),
 539    holderId: readRequiredString(row, "holder_id"),
 540    holderHost: readRequiredString(row, "holder_host"),
 541    term: readRequiredNumber(row, "term"),
 542    leaseExpiresAt: readRequiredNumber(row, "lease_expires_at"),
 543    renewedAt: readRequiredNumber(row, "renewed_at"),
 544    preferredHolderId: readOptionalString(row, "preferred_holder_id"),
 545    metadataJson: readOptionalString(row, "metadata_json")
 546  };
 547}
 548
 549export function mapControllerRow(row: DatabaseRow): ControllerRecord {
 550  return {
 551    controllerId: readRequiredString(row, "controller_id"),
 552    host: readRequiredString(row, "host"),
 553    role: readRequiredString(row, "role"),
 554    priority: readRequiredNumber(row, "priority"),
 555    status: readRequiredString(row, "status"),
 556    version: readOptionalString(row, "version"),
 557    lastHeartbeatAt: readRequiredNumber(row, "last_heartbeat_at"),
 558    lastStartedAt: readOptionalNumber(row, "last_started_at"),
 559    metadataJson: readOptionalString(row, "metadata_json")
 560  };
 561}
 562
 563export function mapWorkerRow(row: DatabaseRow): WorkerRecord {
 564  return {
 565    workerId: readRequiredString(row, "worker_id"),
 566    controllerId: readRequiredString(row, "controller_id"),
 567    host: readRequiredString(row, "host"),
 568    workerType: readRequiredString(row, "worker_type"),
 569    status: readRequiredString(row, "status"),
 570    maxParallelism: readRequiredNumber(row, "max_parallelism"),
 571    currentLoad: readRequiredNumber(row, "current_load"),
 572    lastHeartbeatAt: readRequiredNumber(row, "last_heartbeat_at"),
 573    capabilitiesJson: readOptionalString(row, "capabilities_json"),
 574    metadataJson: readOptionalString(row, "metadata_json")
 575  };
 576}
 577
 578export function mapTaskRow(row: DatabaseRow): TaskRecord {
 579  return {
 580    taskId: readRequiredString(row, "task_id"),
 581    repo: readRequiredString(row, "repo"),
 582    taskType: readRequiredString(row, "task_type"),
 583    title: readRequiredString(row, "title"),
 584    goal: readRequiredString(row, "goal"),
 585    source: readRequiredString(row, "source"),
 586    priority: readRequiredNumber(row, "priority"),
 587    status: readTaskStatus(row, "status"),
 588    planningStrategy: readOptionalString(row, "planning_strategy"),
 589    plannerProvider: readOptionalString(row, "planner_provider"),
 590    branchName: readOptionalString(row, "branch_name"),
 591    baseRef: readOptionalString(row, "base_ref"),
 592    targetHost: readOptionalString(row, "target_host"),
 593    assignedControllerId: readOptionalString(row, "assigned_controller_id"),
 594    currentStepIndex: readRequiredNumber(row, "current_step_index"),
 595    constraintsJson: readOptionalString(row, "constraints_json"),
 596    acceptanceJson: readOptionalString(row, "acceptance_json"),
 597    metadataJson: readOptionalString(row, "metadata_json"),
 598    resultSummary: readOptionalString(row, "result_summary"),
 599    resultJson: readOptionalString(row, "result_json"),
 600    errorText: readOptionalString(row, "error_text"),
 601    createdAt: readRequiredNumber(row, "created_at"),
 602    updatedAt: readRequiredNumber(row, "updated_at"),
 603    startedAt: readOptionalNumber(row, "started_at"),
 604    finishedAt: readOptionalNumber(row, "finished_at")
 605  };
 606}
 607
 608export function mapTaskStepRow(row: DatabaseRow): TaskStepRecord {
 609  return {
 610    stepId: readRequiredString(row, "step_id"),
 611    taskId: readRequiredString(row, "task_id"),
 612    stepIndex: readRequiredNumber(row, "step_index"),
 613    stepName: readRequiredString(row, "step_name"),
 614    stepKind: readStepKind(row, "step_kind"),
 615    status: readStepStatus(row, "status"),
 616    assignedWorkerId: readOptionalString(row, "assigned_worker_id"),
 617    assignedControllerId: readOptionalString(row, "assigned_controller_id"),
 618    timeoutSec: readRequiredNumber(row, "timeout_sec"),
 619    retryLimit: readRequiredNumber(row, "retry_limit"),
 620    retryCount: readRequiredNumber(row, "retry_count"),
 621    leaseExpiresAt: readOptionalNumber(row, "lease_expires_at"),
 622    inputJson: readOptionalString(row, "input_json"),
 623    outputJson: readOptionalString(row, "output_json"),
 624    summary: readOptionalString(row, "summary"),
 625    errorText: readOptionalString(row, "error_text"),
 626    createdAt: readRequiredNumber(row, "created_at"),
 627    updatedAt: readRequiredNumber(row, "updated_at"),
 628    startedAt: readOptionalNumber(row, "started_at"),
 629    finishedAt: readOptionalNumber(row, "finished_at")
 630  };
 631}
 632
 633export function mapTaskRunRow(row: DatabaseRow): TaskRunRecord {
 634  return {
 635    runId: readRequiredString(row, "run_id"),
 636    taskId: readRequiredString(row, "task_id"),
 637    stepId: readRequiredString(row, "step_id"),
 638    workerId: readRequiredString(row, "worker_id"),
 639    controllerId: readRequiredString(row, "controller_id"),
 640    host: readRequiredString(row, "host"),
 641    pid: readOptionalNumber(row, "pid"),
 642    status: readRequiredString(row, "status"),
 643    leaseExpiresAt: readOptionalNumber(row, "lease_expires_at"),
 644    heartbeatAt: readOptionalNumber(row, "heartbeat_at"),
 645    logDir: readRequiredString(row, "log_dir"),
 646    stdoutPath: readOptionalString(row, "stdout_path"),
 647    stderrPath: readOptionalString(row, "stderr_path"),
 648    workerLogPath: readOptionalString(row, "worker_log_path"),
 649    checkpointSeq: readRequiredNumber(row, "checkpoint_seq"),
 650    exitCode: readOptionalNumber(row, "exit_code"),
 651    resultJson: readOptionalString(row, "result_json"),
 652    errorText: readOptionalString(row, "error_text"),
 653    createdAt: readRequiredNumber(row, "created_at"),
 654    startedAt: readOptionalNumber(row, "started_at"),
 655    finishedAt: readOptionalNumber(row, "finished_at")
 656  };
 657}
 658
 659export function mapTaskCheckpointRow(row: DatabaseRow): TaskCheckpointRecord {
 660  return {
 661    checkpointId: readRequiredString(row, "checkpoint_id"),
 662    taskId: readRequiredString(row, "task_id"),
 663    stepId: readRequiredString(row, "step_id"),
 664    runId: readRequiredString(row, "run_id"),
 665    seq: readRequiredNumber(row, "seq"),
 666    checkpointType: readRequiredString(row, "checkpoint_type"),
 667    summary: readOptionalString(row, "summary"),
 668    contentText: readOptionalString(row, "content_text"),
 669    contentJson: readOptionalString(row, "content_json"),
 670    createdAt: readRequiredNumber(row, "created_at")
 671  };
 672}
 673
 674export function mapTaskLogRow(row: DatabaseRow): TaskLogRecord {
 675  return {
 676    logId: readRequiredNumber(row, "log_id"),
 677    taskId: readRequiredString(row, "task_id"),
 678    stepId: readOptionalString(row, "step_id"),
 679    runId: readRequiredString(row, "run_id"),
 680    seq: readRequiredNumber(row, "seq"),
 681    stream: readRequiredString(row, "stream"),
 682    level: readOptionalString(row, "level"),
 683    message: readRequiredString(row, "message"),
 684    createdAt: readRequiredNumber(row, "created_at")
 685  };
 686}
 687
 688export function mapSystemStateRow(row: DatabaseRow): SystemStateRecord {
 689  return {
 690    stateKey: readRequiredString(row, "state_key"),
 691    valueJson: readRequiredString(row, "value_json"),
 692    updatedAt: readRequiredNumber(row, "updated_at")
 693  };
 694}
 695
 696export function mapAutomationStateRow(row: DatabaseRow): AutomationStateRecord {
 697  const record = mapSystemStateRow(row);
 698
 699  if (record.stateKey !== AUTOMATION_STATE_KEY) {
 700    throw new TypeError(`Expected state_key "${AUTOMATION_STATE_KEY}", received "${record.stateKey}".`);
 701  }
 702
 703  const parsed = parseJsonText<{ mode?: unknown }>(record.valueJson);
 704  const mode = parsed?.mode;
 705
 706  if (!isAutomationMode(mode)) {
 707    throw new TypeError(`Automation state is missing a valid mode in "${record.valueJson}".`);
 708  }
 709
 710  return {
 711    ...record,
 712    stateKey: AUTOMATION_STATE_KEY,
 713    mode
 714  };
 715}
 716
 717export function mapTaskArtifactRow(row: DatabaseRow): TaskArtifactRecord {
 718  return {
 719    artifactId: readRequiredString(row, "artifact_id"),
 720    taskId: readRequiredString(row, "task_id"),
 721    stepId: readOptionalString(row, "step_id"),
 722    runId: readOptionalString(row, "run_id"),
 723    artifactType: readRequiredString(row, "artifact_type"),
 724    path: readOptionalString(row, "path"),
 725    uri: readOptionalString(row, "uri"),
 726    sizeBytes: readOptionalNumber(row, "size_bytes"),
 727    sha256: readOptionalString(row, "sha256"),
 728    metadataJson: readOptionalString(row, "metadata_json"),
 729    createdAt: readRequiredNumber(row, "created_at")
 730  };
 731}
 732
 733export const SELECT_CURRENT_LEASE_SQL = `
 734  SELECT
 735    lease_name,
 736    holder_id,
 737    holder_host,
 738    term,
 739    lease_expires_at,
 740    renewed_at,
 741    preferred_holder_id,
 742    metadata_json
 743  FROM leader_lease
 744  WHERE lease_name = ?
 745`;
 746
 747export const UPSERT_LEADER_LEASE_SQL = `
 748  INSERT INTO leader_lease (
 749    lease_name,
 750    holder_id,
 751    holder_host,
 752    term,
 753    lease_expires_at,
 754    renewed_at,
 755    preferred_holder_id,
 756    metadata_json
 757  )
 758  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
 759  ON CONFLICT(lease_name) DO UPDATE SET
 760    holder_id = excluded.holder_id,
 761    holder_host = excluded.holder_host,
 762    term = excluded.term,
 763    lease_expires_at = excluded.lease_expires_at,
 764    renewed_at = excluded.renewed_at,
 765    preferred_holder_id = excluded.preferred_holder_id,
 766    metadata_json = excluded.metadata_json
 767`;
 768
 769export const ACQUIRE_LEADER_LEASE_SQL = `
 770  INSERT INTO leader_lease (
 771    lease_name,
 772    holder_id,
 773    holder_host,
 774    term,
 775    lease_expires_at,
 776    renewed_at,
 777    preferred_holder_id,
 778    metadata_json
 779  )
 780  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
 781  ON CONFLICT(lease_name) DO UPDATE SET
 782    holder_id = CASE
 783      WHEN leader_lease.holder_id = excluded.holder_id OR leader_lease.lease_expires_at <= ? THEN excluded.holder_id
 784      ELSE leader_lease.holder_id
 785    END,
 786    holder_host = CASE
 787      WHEN leader_lease.holder_id = excluded.holder_id OR leader_lease.lease_expires_at <= ? THEN excluded.holder_host
 788      ELSE leader_lease.holder_host
 789    END,
 790    term = CASE
 791      WHEN leader_lease.holder_id = excluded.holder_id THEN leader_lease.term
 792      WHEN leader_lease.lease_expires_at <= ? THEN leader_lease.term + 1
 793      ELSE leader_lease.term
 794    END,
 795    lease_expires_at = CASE
 796      WHEN leader_lease.holder_id = excluded.holder_id OR leader_lease.lease_expires_at <= ? THEN excluded.lease_expires_at
 797      ELSE leader_lease.lease_expires_at
 798    END,
 799    renewed_at = CASE
 800      WHEN leader_lease.holder_id = excluded.holder_id OR leader_lease.lease_expires_at <= ? THEN excluded.renewed_at
 801      ELSE leader_lease.renewed_at
 802    END,
 803    preferred_holder_id = CASE
 804      WHEN leader_lease.holder_id = excluded.holder_id OR leader_lease.lease_expires_at <= ? THEN excluded.preferred_holder_id
 805      ELSE leader_lease.preferred_holder_id
 806    END,
 807    metadata_json = CASE
 808      WHEN leader_lease.holder_id = excluded.holder_id OR leader_lease.lease_expires_at <= ? THEN excluded.metadata_json
 809      ELSE leader_lease.metadata_json
 810    END
 811`;
 812
 813export const UPSERT_CONTROLLER_SQL = `
 814  INSERT INTO controllers (
 815    controller_id,
 816    host,
 817    role,
 818    priority,
 819    status,
 820    version,
 821    last_heartbeat_at,
 822    last_started_at,
 823    metadata_json
 824  )
 825  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
 826  ON CONFLICT(controller_id) DO UPDATE SET
 827    host = excluded.host,
 828    role = excluded.role,
 829    priority = excluded.priority,
 830    status = excluded.status,
 831    version = excluded.version,
 832    last_heartbeat_at = excluded.last_heartbeat_at,
 833    last_started_at = excluded.last_started_at,
 834    metadata_json = excluded.metadata_json
 835`;
 836
 837export const UPSERT_WORKER_SQL = `
 838  INSERT INTO workers (
 839    worker_id,
 840    controller_id,
 841    host,
 842    worker_type,
 843    status,
 844    max_parallelism,
 845    current_load,
 846    last_heartbeat_at,
 847    capabilities_json,
 848    metadata_json
 849  )
 850  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
 851  ON CONFLICT(worker_id) DO UPDATE SET
 852    controller_id = excluded.controller_id,
 853    host = excluded.host,
 854    worker_type = excluded.worker_type,
 855    status = excluded.status,
 856    max_parallelism = excluded.max_parallelism,
 857    current_load = excluded.current_load,
 858    last_heartbeat_at = excluded.last_heartbeat_at,
 859    capabilities_json = excluded.capabilities_json,
 860    metadata_json = excluded.metadata_json
 861`;
 862
 863export const UPSERT_SYSTEM_STATE_SQL = `
 864  INSERT INTO system_state (
 865    state_key,
 866    value_json,
 867    updated_at
 868  )
 869  VALUES (?, ?, ?)
 870  ON CONFLICT(state_key) DO UPDATE SET
 871    value_json = excluded.value_json,
 872    updated_at = excluded.updated_at
 873`;
 874
 875export const ENSURE_AUTOMATION_STATE_SQL = `
 876  INSERT INTO system_state (
 877    state_key,
 878    value_json,
 879    updated_at
 880  )
 881  VALUES (?, ?, ?)
 882  ON CONFLICT(state_key) DO NOTHING
 883`;
 884
 885export const SELECT_SYSTEM_STATE_SQL = `
 886  SELECT
 887    state_key,
 888    value_json,
 889    updated_at
 890  FROM system_state
 891  WHERE state_key = ?
 892`;
 893
 894export const INSERT_TASK_SQL = `
 895  INSERT INTO tasks (
 896    task_id,
 897    repo,
 898    task_type,
 899    title,
 900    goal,
 901    source,
 902    priority,
 903    status,
 904    planning_strategy,
 905    planner_provider,
 906    branch_name,
 907    base_ref,
 908    target_host,
 909    assigned_controller_id,
 910    current_step_index,
 911    constraints_json,
 912    acceptance_json,
 913    metadata_json,
 914    result_summary,
 915    result_json,
 916    error_text,
 917    created_at,
 918    updated_at,
 919    started_at,
 920    finished_at
 921  )
 922  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
 923`;
 924
 925export const SELECT_TASK_SQL = `
 926  SELECT
 927    task_id,
 928    repo,
 929    task_type,
 930    title,
 931    goal,
 932    source,
 933    priority,
 934    status,
 935    planning_strategy,
 936    planner_provider,
 937    branch_name,
 938    base_ref,
 939    target_host,
 940    assigned_controller_id,
 941    current_step_index,
 942    constraints_json,
 943    acceptance_json,
 944    metadata_json,
 945    result_summary,
 946    result_json,
 947    error_text,
 948    created_at,
 949    updated_at,
 950    started_at,
 951    finished_at
 952  FROM tasks
 953  WHERE task_id = ?
 954`;
 955
 956export const INSERT_TASK_STEP_SQL = `
 957  INSERT INTO task_steps (
 958    step_id,
 959    task_id,
 960    step_index,
 961    step_name,
 962    step_kind,
 963    status,
 964    assigned_worker_id,
 965    assigned_controller_id,
 966    timeout_sec,
 967    retry_limit,
 968    retry_count,
 969    lease_expires_at,
 970    input_json,
 971    output_json,
 972    summary,
 973    error_text,
 974    created_at,
 975    updated_at,
 976    started_at,
 977    finished_at
 978  )
 979  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
 980`;
 981
 982export const SELECT_TASK_STEPS_SQL = `
 983  SELECT
 984    step_id,
 985    task_id,
 986    step_index,
 987    step_name,
 988    step_kind,
 989    status,
 990    assigned_worker_id,
 991    assigned_controller_id,
 992    timeout_sec,
 993    retry_limit,
 994    retry_count,
 995    lease_expires_at,
 996    input_json,
 997    output_json,
 998    summary,
 999    error_text,
1000    created_at,
1001    updated_at,
1002    started_at,
1003    finished_at
1004  FROM task_steps
1005  WHERE task_id = ?
1006  ORDER BY step_index ASC
1007`;
1008
1009export const INSERT_TASK_RUN_SQL = `
1010  INSERT INTO task_runs (
1011    run_id,
1012    task_id,
1013    step_id,
1014    worker_id,
1015    controller_id,
1016    host,
1017    pid,
1018    status,
1019    lease_expires_at,
1020    heartbeat_at,
1021    log_dir,
1022    stdout_path,
1023    stderr_path,
1024    worker_log_path,
1025    checkpoint_seq,
1026    exit_code,
1027    result_json,
1028    error_text,
1029    created_at,
1030    started_at,
1031    finished_at
1032  )
1033  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1034`;
1035
1036export const INSERT_TASK_CHECKPOINT_SQL = `
1037  INSERT INTO task_checkpoints (
1038    checkpoint_id,
1039    task_id,
1040    step_id,
1041    run_id,
1042    seq,
1043    checkpoint_type,
1044    summary,
1045    content_text,
1046    content_json,
1047    created_at
1048  )
1049  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1050`;
1051
1052export const INSERT_TASK_LOG_SQL = `
1053  INSERT INTO task_logs (
1054    task_id,
1055    step_id,
1056    run_id,
1057    seq,
1058    stream,
1059    level,
1060    message,
1061    created_at
1062  )
1063  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1064`;
1065
1066export const INSERT_TASK_ARTIFACT_SQL = `
1067  INSERT INTO task_artifacts (
1068    artifact_id,
1069    task_id,
1070    step_id,
1071    run_id,
1072    artifact_type,
1073    path,
1074    uri,
1075    size_bytes,
1076    sha256,
1077    metadata_json,
1078    created_at
1079  )
1080  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1081`;
1082
1083function leaderLeaseParams(record: LeaderLeaseRecord): D1Bindable[] {
1084  return [
1085    record.leaseName,
1086    record.holderId,
1087    record.holderHost,
1088    record.term,
1089    record.leaseExpiresAt,
1090    record.renewedAt,
1091    record.preferredHolderId,
1092    record.metadataJson
1093  ];
1094}
1095
1096function acquireLeaderLeaseParams(record: LeaderLeaseRecord, now: number): D1Bindable[] {
1097  return [...leaderLeaseParams(record), now, now, now, now, now, now, now];
1098}
1099
1100function controllerParams(record: ControllerRecord): D1Bindable[] {
1101  return [
1102    record.controllerId,
1103    record.host,
1104    record.role,
1105    record.priority,
1106    record.status,
1107    record.version,
1108    record.lastHeartbeatAt,
1109    record.lastStartedAt,
1110    record.metadataJson
1111  ];
1112}
1113
1114function workerParams(record: WorkerRecord): D1Bindable[] {
1115  return [
1116    record.workerId,
1117    record.controllerId,
1118    record.host,
1119    record.workerType,
1120    record.status,
1121    record.maxParallelism,
1122    record.currentLoad,
1123    record.lastHeartbeatAt,
1124    record.capabilitiesJson,
1125    record.metadataJson
1126  ];
1127}
1128
1129function systemStateParams(record: SystemStateRecord): D1Bindable[] {
1130  return [record.stateKey, record.valueJson, record.updatedAt];
1131}
1132
1133function taskParams(record: TaskRecord): D1Bindable[] {
1134  return [
1135    record.taskId,
1136    record.repo,
1137    record.taskType,
1138    record.title,
1139    record.goal,
1140    record.source,
1141    record.priority,
1142    record.status,
1143    record.planningStrategy,
1144    record.plannerProvider,
1145    record.branchName,
1146    record.baseRef,
1147    record.targetHost,
1148    record.assignedControllerId,
1149    record.currentStepIndex,
1150    record.constraintsJson,
1151    record.acceptanceJson,
1152    record.metadataJson,
1153    record.resultSummary,
1154    record.resultJson,
1155    record.errorText,
1156    record.createdAt,
1157    record.updatedAt,
1158    record.startedAt,
1159    record.finishedAt
1160  ];
1161}
1162
1163function taskStepParams(record: TaskStepRecord): D1Bindable[] {
1164  return [
1165    record.stepId,
1166    record.taskId,
1167    record.stepIndex,
1168    record.stepName,
1169    record.stepKind,
1170    record.status,
1171    record.assignedWorkerId,
1172    record.assignedControllerId,
1173    record.timeoutSec,
1174    record.retryLimit,
1175    record.retryCount,
1176    record.leaseExpiresAt,
1177    record.inputJson,
1178    record.outputJson,
1179    record.summary,
1180    record.errorText,
1181    record.createdAt,
1182    record.updatedAt,
1183    record.startedAt,
1184    record.finishedAt
1185  ];
1186}
1187
1188function taskRunParams(record: TaskRunRecord): D1Bindable[] {
1189  return [
1190    record.runId,
1191    record.taskId,
1192    record.stepId,
1193    record.workerId,
1194    record.controllerId,
1195    record.host,
1196    record.pid,
1197    record.status,
1198    record.leaseExpiresAt,
1199    record.heartbeatAt,
1200    record.logDir,
1201    record.stdoutPath,
1202    record.stderrPath,
1203    record.workerLogPath,
1204    record.checkpointSeq,
1205    record.exitCode,
1206    record.resultJson,
1207    record.errorText,
1208    record.createdAt,
1209    record.startedAt,
1210    record.finishedAt
1211  ];
1212}
1213
1214function taskCheckpointParams(record: TaskCheckpointRecord): D1Bindable[] {
1215  return [
1216    record.checkpointId,
1217    record.taskId,
1218    record.stepId,
1219    record.runId,
1220    record.seq,
1221    record.checkpointType,
1222    record.summary,
1223    record.contentText,
1224    record.contentJson,
1225    record.createdAt
1226  ];
1227}
1228
1229function taskLogParams(record: NewTaskLogRecord): D1Bindable[] {
1230  return [
1231    record.taskId,
1232    record.stepId,
1233    record.runId,
1234    record.seq,
1235    record.stream,
1236    record.level,
1237    record.message,
1238    record.createdAt
1239  ];
1240}
1241
1242function taskArtifactParams(record: TaskArtifactRecord): D1Bindable[] {
1243  return [
1244    record.artifactId,
1245    record.taskId,
1246    record.stepId,
1247    record.runId,
1248    record.artifactType,
1249    record.path,
1250    record.uri,
1251    record.sizeBytes,
1252    record.sha256,
1253    record.metadataJson,
1254    record.createdAt
1255  ];
1256}
1257
1258export class D1ControlPlaneRepository implements ControlPlaneRepository {
1259  private readonly db: D1DatabaseLike;
1260
1261  constructor(db: D1DatabaseLike) {
1262    this.db = db;
1263  }
1264
1265  async ensureAutomationState(mode: AutomationMode = DEFAULT_AUTOMATION_MODE): Promise<void> {
1266    await this.run(ENSURE_AUTOMATION_STATE_SQL, [
1267      AUTOMATION_STATE_KEY,
1268      buildAutomationStateValue(mode),
1269      nowUnixSeconds()
1270    ]);
1271  }
1272
1273  async heartbeatController(input: ControllerHeartbeatInput): Promise<ControllerRecord> {
1274    const record = buildControllerHeartbeatRecord(input);
1275    await this.upsertController(record);
1276    return record;
1277  }
1278
1279  async acquireLeaderLease(input: LeaderLeaseAcquireInput): Promise<LeaderLeaseAcquireResult> {
1280    const now = input.now ?? nowUnixSeconds();
1281    const normalizedInput = { ...input, now };
1282    const currentLease = await this.getCurrentLease();
1283    const desiredLease = buildLeaderLeaseRecord(currentLease, normalizedInput);
1284
1285    await this.run(ACQUIRE_LEADER_LEASE_SQL, acquireLeaderLeaseParams(desiredLease, now));
1286
1287    const updatedLease = await this.getCurrentLease();
1288
1289    if (updatedLease == null) {
1290      throw new Error("leader_lease row was not available after acquire attempt.");
1291    }
1292
1293    return buildLeaderLeaseAcquireResult(currentLease, updatedLease, normalizedInput);
1294  }
1295
1296  async getAutomationState(): Promise<AutomationStateRecord | null> {
1297    const row = await this.fetchFirst(SELECT_SYSTEM_STATE_SQL, [AUTOMATION_STATE_KEY]);
1298    return row == null ? null : mapAutomationStateRow(row);
1299  }
1300
1301  async getCurrentLease(): Promise<LeaderLeaseRecord | null> {
1302    const row = await this.fetchFirst(SELECT_CURRENT_LEASE_SQL, [GLOBAL_LEASE_NAME]);
1303    return row == null ? null : mapLeaderLeaseRow(row);
1304  }
1305
1306  async getSystemState(stateKey: string): Promise<SystemStateRecord | null> {
1307    const row = await this.fetchFirst(SELECT_SYSTEM_STATE_SQL, [stateKey]);
1308    return row == null ? null : mapSystemStateRow(row);
1309  }
1310
1311  async getTask(taskId: string): Promise<TaskRecord | null> {
1312    const row = await this.fetchFirst(SELECT_TASK_SQL, [taskId]);
1313    return row == null ? null : mapTaskRow(row);
1314  }
1315
1316  async insertTask(record: TaskRecord): Promise<void> {
1317    await this.run(INSERT_TASK_SQL, taskParams(record));
1318  }
1319
1320  async insertTaskArtifact(record: TaskArtifactRecord): Promise<void> {
1321    await this.run(INSERT_TASK_ARTIFACT_SQL, taskArtifactParams(record));
1322  }
1323
1324  async insertTaskCheckpoint(record: TaskCheckpointRecord): Promise<void> {
1325    await this.run(INSERT_TASK_CHECKPOINT_SQL, taskCheckpointParams(record));
1326  }
1327
1328  async insertTaskRun(record: TaskRunRecord): Promise<void> {
1329    await this.run(INSERT_TASK_RUN_SQL, taskRunParams(record));
1330  }
1331
1332  async insertTaskStep(record: TaskStepRecord): Promise<void> {
1333    await this.run(INSERT_TASK_STEP_SQL, taskStepParams(record));
1334  }
1335
1336  async insertTaskSteps(records: TaskStepRecord[]): Promise<void> {
1337    if (records.length === 0) {
1338      return;
1339    }
1340
1341    await this.db.batch(records.map((record) => this.bind(INSERT_TASK_STEP_SQL, taskStepParams(record))));
1342  }
1343
1344  async listTaskSteps(taskId: string): Promise<TaskStepRecord[]> {
1345    const rows = await this.fetchAll(SELECT_TASK_STEPS_SQL, [taskId]);
1346    return rows.map(mapTaskStepRow);
1347  }
1348
1349  async putLeaderLease(record: LeaderLeaseRecord): Promise<void> {
1350    if (record.leaseName !== GLOBAL_LEASE_NAME) {
1351      throw new RangeError(`leader_lease only supports lease_name="${GLOBAL_LEASE_NAME}".`);
1352    }
1353
1354    await this.run(UPSERT_LEADER_LEASE_SQL, leaderLeaseParams(record));
1355  }
1356
1357  async putSystemState(record: SystemStateRecord): Promise<void> {
1358    await this.run(UPSERT_SYSTEM_STATE_SQL, systemStateParams(record));
1359  }
1360
1361  async setAutomationMode(
1362    mode: AutomationMode,
1363    updatedAt: number = nowUnixSeconds()
1364  ): Promise<void> {
1365    await this.run(UPSERT_SYSTEM_STATE_SQL, [
1366      AUTOMATION_STATE_KEY,
1367      buildAutomationStateValue(mode),
1368      updatedAt
1369    ]);
1370  }
1371
1372  async upsertController(record: ControllerRecord): Promise<void> {
1373    await this.run(UPSERT_CONTROLLER_SQL, controllerParams(record));
1374  }
1375
1376  async upsertWorker(record: WorkerRecord): Promise<void> {
1377    await this.run(UPSERT_WORKER_SQL, workerParams(record));
1378  }
1379
1380  async appendTaskLog(record: NewTaskLogRecord): Promise<number | null> {
1381    const result = await this.run(INSERT_TASK_LOG_SQL, taskLogParams(record));
1382    return result.meta.last_row_id ?? null;
1383  }
1384
1385  private bind(query: string, params: readonly (D1Bindable | undefined)[]): D1PreparedStatementLike {
1386    return this.db.prepare(query).bind(...params.map(toD1Bindable));
1387  }
1388
1389  private async fetchAll(
1390    query: string,
1391    params: readonly (D1Bindable | undefined)[] = []
1392  ): Promise<DatabaseRow[]> {
1393    const result = await this.bind(query, params).all<DatabaseRow>();
1394    return result.results ?? [];
1395  }
1396
1397  private async fetchFirst(
1398    query: string,
1399    params: readonly (D1Bindable | undefined)[] = []
1400  ): Promise<DatabaseRow | null> {
1401    return this.bind(query, params).first<DatabaseRow>();
1402  }
1403
1404  private async run(
1405    query: string,
1406    params: readonly (D1Bindable | undefined)[] = []
1407  ): Promise<D1QueryResult<never>> {
1408    return this.bind(query, params).run();
1409  }
1410}
1411
1412export function createD1ControlPlaneRepository(db: D1DatabaseLike): ControlPlaneRepository {
1413  return new D1ControlPlaneRepository(db);
1414}