Add files table in its own migration
An additive migration keeps already-migrated databases intact instead of
rewriting the applied documents migration in place. The design note
records how documents and assets are meant to layer on top of files.

Assisted-by: Claude Opus 4.8 via Claude Code
change vrwlstqokzzvuyuwqmtlxvtquulyyxul
commit 3d7f247d2bad431347c560d63ee09f79d820c922
author Alpha Chen <alpha@kejadlen.dev>
date
parent dc0c4b2a
diff --git a/db/migrate/001_create_documents.rb b/db/migrate/001_create_documents.rb
new file mode 100644
index 0000000..29268a3
--- /dev/null
+++ b/db/migrate/001_create_documents.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+Sequel.migration do
+  change do
+    create_table(:documents) do
+      primary_key :id
+      String :path, null: false
+      String :kind, null: false
+      DateTime :received_at, null: false
+      DateTime :created_at, null: false
+    end
+  end
+end
diff --git a/db/migrate/001_create_files.rb b/db/migrate/002_create_files.rb
similarity index 100%
rename from db/migrate/001_create_files.rb
rename to db/migrate/002_create_files.rb
diff --git a/docs/plans/2026-06-11-data-model.md b/docs/plans/2026-06-11-data-model.md
new file mode 100644
index 0000000..337d0e2
--- /dev/null
+++ b/docs/plans/2026-06-11-data-model.md
@@ -0,0 +1,56 @@
+# Data model
+
+This note records the intended shape of Domus storage as of the capture
+flow landing. It supersedes the unified-`documents` thinking in
+`2026-06-02-documents-schema.md`, which was written for the
+since-removed email ingestion path.
+
+## The layering
+
+Storage is three layers, each building on the one below.
+
+### Files
+
+A file is one stored blob — the raw bytes plus enough metadata to find
+them again. Every uploaded or ingested byte stream becomes exactly one
+`files` row, regardless of what it represents.
+
+| Column | Notes |
+|--------|-------|
+| `id` | primary key |
+| `extension` | original file extension, e.g. `.png` (lowercased) |
+| `created_at` | when the row was written |
+
+The blob lives on disk at `storage_path/files/<id><extension>`; the path
+is derived from the row rather than stored, so there is one source of
+truth for where a file lives (`App#file_path`).
+
+### Documents
+
+A document is a logical document — today that means a PDF — that points
+at a file. The document layer carries document-level meaning (title,
+received date, classification) while delegating the bytes to a `files`
+row. One file backs one document.
+
+The current `documents` table predates this model: it still has the
+`path`, `kind`, and `received_at` columns from the unified design and
+does not yet reference `files`. Reshaping it to point at a file is
+follow-up work, not part of the capture flow.
+
+### Assets
+
+An asset is a thing the user is tracking — a piece of equipment, a
+property, an account — that has attachments. Each attachment can be any
+file, so the asset-to-file relationship is many-to-many through a join,
+with no constraint that attachments be PDFs or images.
+
+Assets are not built yet; they are recorded here so the file and
+document layers stay general enough to support them.
+
+## Why layer it this way
+
+Keeping `files` as a thin, universal base means every higher concept
+reuses one storage and addressing scheme. Documents and assets differ in
+what they *mean* and how they relate to other records, not in how their
+bytes are stored. That separation lets the capture flow ship against
+`files` alone while documents and assets are designed independently.
diff --git a/lib/models.rb b/lib/models.rb
index 00acb70..dff4000 100644
--- a/lib/models.rb
+++ b/lib/models.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
 module Domus
-  class FileRecord < Sequel::Model(:files)
+  class Document < Sequel::Model
   end
 end