feat(drive): personal drive are photo + music indexed by default
This commit is contained in:
+61
-26
@@ -1324,13 +1324,12 @@ mutation site updates `updated_by`.
|
||||
the filesystem rather than browsing a single folder: Photos, Music
|
||||
library, Favorites, Recent items, Search, Trash. With drives
|
||||
landing, each of these needs an explicit scope decision. The
|
||||
table below locks the choices; the rationale is **noise risk by
|
||||
file type**, not a uniform rule.
|
||||
table below locks the choices.
|
||||
|
||||
| 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). |
|
||||
| **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. |
|
||||
| **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 | 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). |
|
||||
| **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. |
|
||||
@@ -1340,33 +1339,69 @@ file type**, not a uniform rule.
|
||||
#### Capability flag mechanism
|
||||
|
||||
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.
|
||||
The default values reflect the table above: omitted = "off" for
|
||||
photos (so non-default drives don't show photos unless the owner
|
||||
opts in), omitted = "off" for music (so all accessible drives
|
||||
*are* indexed unless the owner opts out).
|
||||
Both flags follow the same shape: **omitted = off**. The query
|
||||
predicate then reduces to a single positive rule for every
|
||||
drive:
|
||||
|
||||
The owner-only UI in the drive settings panel toggles these
|
||||
flags. The query layer reads them at request time; flipping
|
||||
either flag is instant — no reindex required because the filter
|
||||
applies in the query Must-clause, the index itself is unchanged.
|
||||
```sql
|
||||
WHERE fi.drive_id IN (
|
||||
SELECT d.id FROM storage.drives d
|
||||
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
|
||||
"cross-drive" is the one case where two similar surfaces have
|
||||
different defaults. The justification is the noise-risk argument
|
||||
above: image content in shared drives is heterogeneous (often
|
||||
not "photos" in the gallery sense), audio content in shared
|
||||
drives is usually intentional. The capability flags let owners
|
||||
fix either case, but the defaults match what the typical user
|
||||
will want without configuration.
|
||||
**Default personal drive gets both flags set to `true` on
|
||||
creation.** The `PersonalDriveLifecycleHook` (§3) that creates
|
||||
the default personal drive on user provisioning populates
|
||||
`policies` with `{"include_in_photo_index": true,
|
||||
"include_in_music_index": true}`. Existing default personal
|
||||
drives get the same two flags via a one-shot backfill migration
|
||||
alongside the flag introduction. Net effect: every user's
|
||||
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
|
||||
flip Photos to cross-drive with `forbid_photo_index` as the
|
||||
opt-out (mirroring Music). That can land later without a schema
|
||||
change — just a behaviour change.
|
||||
**Non-default drives** (secondary personals, shared drives) are
|
||||
created with the flags omitted, so they stay out of scope until
|
||||
the owner explicitly opts in via the admin "Manage policies"
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user