Drive

Drive is a light, object-store-backed file store. Users drag files — a large Parquet export, a CSV, an Excel sheet — into the Drive page; the bytes are staged in the deployment's existing object-store bucket, and any Parquet/CSV/JSON file can be loaded into a lakehouse table with one click.

It is the UI counterpart to the headless definite run load CLI flow.

Storage layout

Drive needs no new bucket and no new credentials. The deployment already has one object-store bucket with the lakehouse's data files; Drive adds one sibling prefix:

PrefixContents
lake/DuckLake's own Parquet data files
lake/_staging/Files staged by definite run load by default
drive/Drive uploadsdrive/{file_id}/{filename}

All three prefixes are covered by the same object-store credentials (GCS_HMAC_KEY_ID/GCS_HMAC_SECRET, S3_ACCESS_KEY_ID/S3_SECRET_ACCESS_KEY or S3 IRSA through the AWS credential chain, or AZURE_STORAGE_ACCOUNT/AZURE_STORAGE_KEY) that the API and job-runner pods already use. If jobRunner.staging.uri is explicitly moved outside the lakehouse prefix, grant that custom staging prefix to the same object-store identity as well.

On Azure the store's "bucket" is the account host, so these are prefixes inside the configured container (<container>/lake/, <container>/drive/, and so on).

Bucket CORS — required

Drive uploads go directly from the browser to object storage via presigned PUT URLs, so multi-GB files never pass through the API pod. A browser cross-origin PUT requires the bucket to have a CORS policy that allows GET and PUT from the deployment's web origin.

GCS example (cors.json, applied with gcloud storage buckets update gs://YOUR_BUCKET --cors-file=cors.json):

[
  {
    "origin": ["https://your-deployment.example.com"],
    "method": ["GET", "PUT"],
    "responseHeader": ["Content-Type"],
    "maxAgeSeconds": 3600
  }
]

The equivalent S3 CORS rule allows the same AllowedOrigins / AllowedMethods (GET, PUT) and AllowedHeaders (Content-Type).

Azure Blob

Azure uploads use an account-key SAS PUT to the blob endpoint, and that PUT carries an x-ms-blob-type header, so the storage account's CORS rules must allow that header in addition to GET/PUT. Set CORS on the storage account (Portal: Settings > Resource sharing (CORS) > Blob service, or the CLI):

az storage cors add \
  --services b \
  --methods GET PUT \
  --origins https://your-deployment.example.com \
  --allowed-headers 'x-ms-blob-type,content-type,content-length' \
  --exposed-headers '*' \
  --max-age 3600 \
  --account-name YOUR_ACCOUNT --account-key "$AZURE_STORAGE_KEY"

Private-endpoint / VNet-restricted accounts. The browser (not the API pod) connects straight to https://<account>.blob.core.windows.net, so that endpoint has to be reachable and DNS-resolvable from the end user's browser. A storage account locked behind a private endpoint only answers inside the VNet, so end users must reach it over that same private network (corporate network, VPN, or ExpressRoute) with the privatelink.blob.core.windows.net private DNS zone in effect. If browsers cannot resolve or reach the blob endpoint, Drive uploads fail even with correct CORS. This is the same browser-to-storage requirement S3 and GCS have; Azure private endpoints just make it explicit.

Without a CORS rule, browser uploads fail with a CORS error; the headless definite run load flow is unaffected (the CLI is not a browser).

Lifecycle

  • Uploads are persistent by default — they stay until explicitly deleted.
  • An upload can be marked temporary with a TTL of 1–30 days. Expired files are swept (object + metadata row) the next time the file list is fetched.
  • Deleting a file removes both the object and the drive_files row.

API

All endpoints are under /api/v1/drive. Reads require any authenticated user; uploads, loads, and deletes require the editor role. Drive files are workspace-shared — every user sees every file.

Method + pathPurpose
POST /presignMint a presigned PUT URL; creates a pending row.
POST /files/{id}/completeConfirm the upload finished; flips the row to ready.
GET /filesList all ready files (also sweeps expired ones).
GET /files/{id}/download-urlShort-lived presigned GET URL.
POST /files/{id}/loadLoad the file into a lakehouse table.
DELETE /files/{id}Delete the object and its row.

Row actions

Each file row has a three-dot (⋮) menu:

  • Ask Fi — opens a fresh Fi conversation pre-seeded with the file's path, so you can ask the agent to analyze it.
  • Load into lakehouse — wrap the file in a table (Parquet/CSV/JSON only).
  • Copy path — copy the file's gs://…/s3://… object path to the clipboard. Paste it to Fi, or into a read_csv() / read_parquet() query.
  • Download — open a short-lived signed URL.
  • Delete — remove the object and its row.

Working with Fi

The Fi agent can read Drive files directly. It has no object-store credentials of its own and does not need them: its definite_sql_query tool runs through the lakehouse, whose DuckDB session holds the bucket-wide credentials. The access path Fi reaches for depends on the file type:

File typeHow Fi reads it
Tabular (Parquet/CSV/JSON)SELECT * FROM read_parquet('gs://…/drive/…/sales.parquet') (or read_csv_auto / read_json_auto)
Text (.md, .txt, .log, .sql, .yaml, source code…)SELECT content FROM read_text('gs://…/drive/…/notes.md')
Binary / arbitrary (.xlsx, .pdf, images, .zip…)definite run drive get <path-or-id> <local-path> into the sandbox, then process with bash + Python

Fi's system prompt has a Drive section that points it at each path. Nothing needs to be granted to the Fi sandbox itself.

CLI: definite run drive get

Download a Drive file by its gs:///s3:// path or by its bare file id:

# Path form — destination defaults to the filename in the path.
definite run drive get gs://acme-lake/drive/946aacbb-…/notes.md

# Bare id form — destination is required.
definite run drive get 946aacbb-ea0a-4776-b854-a5e654d2b6d8 ./notes.md

# Stream to stdout (no envelope on stdout — info goes to stderr):
definite run drive get gs://…/drive/…/data.csv -

Use --force to overwrite an existing destination file. This is how Fi pulls binary files (xlsx, pdf, images) into its sandbox so they can be processed locally; humans use it the same way.

Limitations (v1)

  • Flat filenames only — no nested folders.
  • Single presigned PUT, so uploads cap at 5 GB on S3 and roughly 5,000 MiB on Azure Blob (GCS is higher); multipart / block upload is a follow-up. Same limitation as definite run load.
  • Only Parquet, CSV, and JSON can be loaded into the lakehouse; other file types are stored but not loadable.
  • Azure Blob uploads need the storage account's CORS rules to allow the x-ms-blob-type header and the blob endpoint to be reachable from end-user browsers (see Bucket CORS).