Skip to content

Write provisioning intent before a driver creates a device #74

Description

@thymikee

Part of #70. See ADR 0021.

Purpose

Store provisioning intent before a driver creates or clones a device.

Today, DeviceProvisioner calls driver.provision() before it writes the device record. A crash can leave a device that Simlock cannot identify.

The in-process capacity reservation does not close this crash window.

Proposed record

Add the intent to Simlock's authoritative registry. Do not create a second registry.

type ProvisioningOrigin =
  | {
      kind: "lease-request";
      requesterId: string;
      idempotencyKey: string;
      generation: number;
    }
  | {
      kind: "warm-convergence";
      operationId: string;
      generation: number;
      maintenanceEpoch?: string;
    };

interface ProvisioningIntent {
  id: string;
  version: number;
  origin: ProvisioningOrigin;
  requestedSpec: ManagedShapeRequest;
  resolvedSpec: ResolvedDeviceSpec;
  state:
    | "reserved"
    | "driver-created"
    | "committed"
    | "cleanup-pending"
    | "failed"
    | "quarantined";
  driverDeviceId?: string;
  driverData?: unknown;
  createdAt: number;
  updatedAt: number;
}

An internal owner can expose these transitions:

interface ProvisioningJournal {
  createIntent(input: {
    origin: ProvisioningOrigin;
    requestedSpec: ManagedShapeRequest;
    resolvedSpec: ResolvedDeviceSpec;
  }): Promise<ProvisioningIntent>;
  recordDriverDevice(input: {
    intentId: string;
    expectedVersion: number;
    driverDeviceId: string;
    driverData: unknown;
  }): Promise<ProvisioningIntent>;
  commitDevice(input: {
    intentId: string;
    expectedVersion: number;
  }): Promise<DeviceRecord>;
  failIntent(input: {
    intentId: string;
    expectedVersion: number;
    error: SimlockError;
  }): Promise<void>;
}

The exact record shape can change. The durable origin, pinned resolved shape, fenced transitions, and capacity rules cannot change.

Required sequence

  1. Resolve the shape to one concrete installed device type and runtime.
  2. Check disk space.
  3. Reserve every applicable capacity dimension and store the intent with the resolved shape.
  4. Call the driver with the stable intent ID, pinned resolved shape, and intent-bound ownership provenance.
  5. Store the driver device identity.
  6. Commit the device record and transfer the capacity charge atomically.

Each origin is durable and idempotent. Repeating the same lease generation or warm convergence generation must reuse its intent.

Retries and restart recovery must use the stored resolvedSpec. They must not resolve the request again after the installed catalog changes.

Reserved, driver-created, cleanup-pending, and quarantined intents use every managed and running capacity dimension that their device consumes. Simlock must load these charges before it accepts new lease requests.

Commit must transfer the charge from the intent to the device record without a free-capacity gap or a double charge.

failed is freeable only when Simlock proves that no driver mutation occurred or that cleanup completed. Any possible driver side effect remains capacity-bearing in cleanup-pending or quarantined until ownership-safe cleanup completes.

Disk error

Reuse the Simlock error for insufficient disk space.

interface InsufficientDiskSpaceError {
  code: "INSUFFICIENT_DISK_SPACE";
  requiredBytes?: number;
  availableBytes?: number;
}

Do not call the driver after this error.

Restart recovery

  • Correlate a created device with intent-bound provenance that contains the stable intent ID.
  • Use the device-set or home ownership marker only to authorize access. It is not intent correlation.
  • Continue or roll back an unambiguous intent with its pinned resolved shape.
  • Quarantine an ambiguous record and keep its capacity charged.
  • Fence each durable transition with the expected intent version.
  • Refuse startup when the complete registry document is unreadable.
  • Do not delete a device without both durable intent and valid ownership evidence.

Implement the same record and transitions for iOS and Android.

Completion conditions

  • A crash before the driver call does not create a device.
  • A crash after device creation does not create a duplicate after restart.
  • A crash before registry commit does not leave an unknown device.
  • Startup recovery and a live retry cannot advance the same intent.
  • A stale transition fails without mutation.
  • Lease and warm origins each recover the same intent after restart.
  • A catalog change after intent creation cannot change the device type or runtime used by recovery.
  • No crash transition admits work above an applicable capacity limit.
  • Tests inject a crash at each transition for both platforms.
  • Tests cover corrupt records, a corrupt registry document, low disk, full capacity, uncertain driver failure, and cleanup quarantine.

The lease-request origin depends on #72. The iOS implementation depends on #73.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions