feat(drive): personal drive are photo + music indexed by default

This commit is contained in:
Edouard Vanbelle
2026-07-01 21:28:00 +02:00
parent 473c126291
commit 20e5ef0ef2
+61 -26
View File
@@ -1324,13 +1324,12 @@ mutation site updates `updated_by`.
the filesystem rather than browsing a single folder: Photos, Music the filesystem rather than browsing a single folder: Photos, Music
library, Favorites, Recent items, Search, Trash. With drives library, Favorites, Recent items, Search, Trash. With drives
landing, each of these needs an explicit scope decision. The landing, each of these needs an explicit scope decision. The
table below locks the choices; the rationale is **noise risk by table below locks the choices.
file type**, not a uniform rule.
| Section | Scope | Capability flag (per-drive policy) | Why | | Section | Scope | Capability flag (per-drive policy) | Why |
|---|---|---|---| |---|---|---|---|
| **Photos** (`/api/photos`) | Default Personal Drive only | `policies.include_in_photo_index = true` to opt a non-default drive in | Shared drives often carry images that aren't "photos" (screenshots, scans, charts-as-PNGs). Defaulting cross-drive pollutes the personal timeline. Opt-in for shared drives where the owner explicitly wants them indexed (e.g. "Family Photos" shared drive). | | **Photos** (`/api/photos`) | Default Personal Drive only | `policies.include_in_photo_index = true` to opt a non-default drive in | Non-default drives often carry images that aren't "photos" (screenshots, scans, charts-as-PNGs). Defaulting cross-drive pollutes the personal timeline. Opt-in for non-default drives where the owner explicitly wants them indexed (e.g. "Family Photos" shared drive). |
| **Music** — library view (future) + playlists | Cross-drive (all accessible drives) | `policies.forbid_music_index = true` to opt a drive out | Audio files in shared drives are almost always intentional content (band collaboration, family music, podcast archive). Defaulting cross-drive matches user intent. Owner opts a drive out for the rare case it shouldn't be indexed. The Music section today is *only* playlists; a `/api/music/tracks` library view added later inherits this scope. | | **Music** — library view (future) + playlists | Default Personal Drive only | `policies.include_in_music_index = true` to opt a non-default drive in | Symmetric with Photos: audio files in a work drive or a random shared folder shouldn't silently bleed into the personal music library. Owner opts a non-default drive in (e.g. "Family Music", "Band Collaboration") when the drive genuinely is a music library. The Music section today is *only* playlists; a `/api/music/tracks` library view added later inherits this scope. |
| **Music playlists** (`audio.playlists`) | User-scoped, cross-drive curation | n/a | Playlists are a curation tool. `owner_id` stays on `auth.users(id)`; tracks reference files via `playlist_items.file_id` and may live in any drive the user has access to. At list time, `list_playlist_tracks` filters out tracks in drives the caller can no longer reach (see §11's defense-in-depth pattern). | | **Music playlists** (`audio.playlists`) | User-scoped, cross-drive curation | n/a | Playlists are a curation tool. `owner_id` stays on `auth.users(id)`; tracks reference files via `playlist_items.file_id` and may live in any drive the user has access to. At list time, `list_playlist_tracks` filters out tracks in drives the caller can no longer reach (see §11's defense-in-depth pattern). |
| **Favorites** (`/api/favorites/resources`) | Cross-drive (all accessible drives) | n/a | Personal organisation tool. Star a PDF from the work drive AND a photo from Personal — the whole point is cross-drive curation. ReBAC visibility check at list time drops rows the user can no longer reach. | | **Favorites** (`/api/favorites/resources`) | Cross-drive (all accessible drives) | n/a | Personal organisation tool. Star a PDF from the work drive AND a photo from Personal — the whole point is cross-drive curation. ReBAC visibility check at list time drops rows the user can no longer reach. |
| **Recent items** (`/api/recent/*`) | Cross-drive (all accessible drives) | n/a | Personal history. Same shape as Favorites — you touched files across drives; the timeline reflects that. ReBAC visibility check at list time. | | **Recent items** (`/api/recent/*`) | Cross-drive (all accessible drives) | n/a | Personal history. Same shape as Favorites — you touched files across drives; the timeline reflects that. ReBAC visibility check at list time. |
@@ -1340,33 +1339,69 @@ file type**, not a uniform rule.
#### Capability flag mechanism #### Capability flag mechanism
Both `policies.include_in_photo_index` and Both `policies.include_in_photo_index` and
`policies.forbid_music_index` live under the same JSONB `policies.include_in_music_index` live under the same JSONB
`policies` column on `storage.drives` (see §8) — no new schema. `policies` column on `storage.drives` (see §8) — no new schema.
The default values reflect the table above: omitted = "off" for Both flags follow the same shape: **omitted = off**. The query
photos (so non-default drives don't show photos unless the owner predicate then reduces to a single positive rule for every
opts in), omitted = "off" for music (so all accessible drives drive:
*are* indexed unless the owner opts out).
The owner-only UI in the drive settings panel toggles these ```sql
flags. The query layer reads them at request time; flipping WHERE fi.drive_id IN (
either flag is instant — no reindex required because the filter SELECT d.id FROM storage.drives d
applies in the query Must-clause, the index itself is unchanged. JOIN storage.role_grants rg
ON rg.resource_type='drive' AND rg.resource_id=d.id
WHERE rg.subject_id IN (caller's effective subjects)
AND (d.policies->>'include_in_photo_index')::boolean = true
)
```
#### The Photos/Music asymmetry — defensible, not a smell No `default_for_user` OR-branch, no per-kind carve-out.
Photos defaulting to "default-drive only" while Music defaults to **Default personal drive gets both flags set to `true` on
"cross-drive" is the one case where two similar surfaces have creation.** The `PersonalDriveLifecycleHook` (§3) that creates
different defaults. The justification is the noise-risk argument the default personal drive on user provisioning populates
above: image content in shared drives is heterogeneous (often `policies` with `{"include_in_photo_index": true,
not "photos" in the gallery sense), audio content in shared "include_in_music_index": true}`. Existing default personal
drives is usually intentional. The capability flags let owners drives get the same two flags via a one-shot backfill migration
fix either case, but the defaults match what the typical user alongside the flag introduction. Net effect: every user's
will want without configuration. default personal drive is in scope from moment one, no user
configuration required for the common case, but the SQL is
kind-agnostic.
If a uniform rule is ever preferred, the cheapest move is to **Non-default drives** (secondary personals, shared drives) are
flip Photos to cross-drive with `forbid_photo_index` as the created with the flags omitted, so they stay out of scope until
opt-out (mirroring Music). That can land later without a schema the owner explicitly opts in via the admin "Manage policies"
change — just a behaviour change. modal.
Flipping either flag on any drive is instant — the query reads
`policies` at request time; the index itself is unchanged.
Toggle-off on a default personal drive is *possible* (admins
own the drive-policy mutation surface — see §8) but shows a
confirm dialog in the UI ("this will empty the user's Photos
timeline" / "…their Music library"), since it's an unusual
action.
#### Why symmetric (both opt-in) instead of asymmetric
An earlier version of this section had Music default to
cross-drive (`forbid_music_index` as an opt-out), on the
argument that audio in shared drives is "almost always
intentional content." That asymmetry created two problems:
1. **Mixed-form flag naming** — one `include_in_*` and one
`forbid_*` with opposite meanings, hard to reason about in the
admin UI and the query layer.
2. **The "shared audio is always intentional" claim doesn't
hold under scrutiny** — a work drive with a few voicemail
MP3s or a project drive with a stray podcast recording
shouldn't bleed into the personal music library any more than
a work drive with screenshots should bleed into Photos.
Symmetric opt-in (`include_in_*_index` for both) fixes both.
The "Family Music" case still works — the owner flips the flag
once on drive creation, same one-time gesture as "Family Photos"
under the pre-existing photo policy. The default-personal case
(90%+ of users) needs no configuration for either surface.
#### Face indexing — per-drive clustering, scope follows Photos #### Face indexing — per-drive clustering, scope follows Photos