docs GitHub

ContributingDraft

Drive onboarding

Options for the new step (between connecting accounts and the phone, per the lead). Each marked built, S, M or L. Follows interaction.md section 3: reversible and cheap choices (turning a folder on, the files.receive switch) are optimistic with Undo; sync.delete stays confirm-with-preview, never Touch ID (it's not a secret action, ADR 0024).

  1. What to sync. Not built. Today VyreDrive shares whole folders the person names by hand (files.drive.share); there's no picker showing Desktop/Documents/each project's files folder with sizes, no default exclusions the person can edit (drive.js's scan skips node_modules, .git, dist etc., but that list isn't user-facing or editable). M: a scan+picker UI plus an editable exclusion list (size and an over-N-MB filter).

  2. How.

    • Files on demand: this is what VyreDrive already does: a mounted share, fetched on open, nothing downloaded up front. Built. Recommend as the default; it's honest because it's the only mode that costs nothing extra.
    • Keep a copy on this device: not built. Needs real local caching/pinning, not just a mount. L.
    • Server only: today this is just "don't mount this share here", an access choice per device, not a sync mode. Built as a checkbox, not as a labeled "mode."
  3. Where it shows up. Finder sidebar on the Mac: built (files.drive.mount). Windows network drive: not built: no Windows client exists at all yet (session import's device role is the first Windows-facing work, and it doesn't touch Drive). Capsule drop target: not built: files.deliver (box→Mac) exists as a tool, no Capsule UI calls it. Phone: not built.

  4. Per-folder access (Capsule, chat, agents, per project). Not built. Vault's grant pattern (per-secret, per-caller) exists and is the right shape to extend, but Drive shares have no grant table today: a share is visible to whoever can reach the mount, full stop. Agents get none by default is also not enforced (no agent has a Drive-aware tool at all right now, so this is moot until one exists: cheap to get right, list it as a gate, not a feature). M.

  5. Receiving files from the server (files.receive). Built this session (0.1.1, e2e review of aa9cb40c/9338a6a5): off by default, per device, in config today: not yet a declared settings-hub entry (tried; a device-level setting needs a different store than config.json, reverted rather than ship unreviewed: see team/archive/work-journals/federation.md). Offering it as a clear toggle in this step is S: wire the existing tool to a UI checkbox.

  6. Conflicts. Partially built: Taildrop already renames on a name clash (--conflict=rename, core/files/drop.js) rather than overwriting: so the underlying transport already never silently overwrites. What's missing is telling the person: no visible note, no "keep both" framing anywhere. S: surface the rename as a notice; M if "keep both, ask which to trust" needs its own UI beyond a note.

  7. The moment that makes it stick. Not built: needs the phone app and the Capsule drop target (3, above) both live before "drag a file in, see it on your phone in seconds" is real. Ordering: this is the payoff, not a separate build; it falls out once 3's gaps close. Track as part of 3's size, not its own line.

  8. Size and quota shown up front. Sizes: not built into the picker (1) yet, but the numbers exist (drive.js already scans and could total a folder's size before sharing). Per-device quota: the sync.upload path has one (sync_peers.quota_bytes, no UI); Drive shares have none. Both need the user's call on the actual number: flagged, not decided here, per the lead's note.

What's real right now, plainly

Drive today is a person naming a folder to share, mounted read-only or read-write on the Mac, scanned once for secrets before it's allowed. Nothing is a "mode" yet in the sense this step implies (on-demand vs. offline vs. server-only): on-demand is just what mounting already is. Framing it as a choice is fine (it reads well and sets the right default), as long as the other two options are honestly labeled "coming" rather than implied to work today.

Step 3 design: per-folder access, a concrete shape (28 Sep 2026, federation)

Item 4 above named the direction. This is the concrete shape, for the lead and the reviewer to read before any code lands.

The grant

A new table, sync_grants, in core/sync's own migrations (not vault_grants itself: that table is credential items and its own invariants, kinds, and audit path belong to the vault team; this mirrors its exact shape instead of borrowing its rows):

CREATE TABLE sync_grants (
  id TEXT PRIMARY KEY, project TEXT NOT NULL, agent TEXT NOT NULL DEFAULT '',
  status TEXT NOT NULL, by TEXT NOT NULL, at INTEGER NOT NULL,
  UNIQUE (project, agent)
)

Same fields as vault_grants (item, module, watcher, status, by, at), renamed for this domain: project (a project-id.js slug, isProjectId checked before it is ever stored) stands in for item, agent (an agent's name, empty string for "every agent") stands in for module/watcher. status is "granted" or "revoked" rather than deleting a row, so a later sync_grants.list shows history the same way vault.list does for its own grants.

Why a project-id.js slug and not the sync.scan folder name directly: a project's synced folder (the device's projects/<name> under its Claude Code folder) and a box-side project (projects.*, teammates' agent naming, memory's rooms) are not guaranteed to be the same string today, and this is the join point. Deciding that join is its own small piece of work (probably: the person's own project picker at consent time offers box-side project ids to tag a device's folder with, not the device's own raw folder name) and is out of this design; sync_grants stores whichever slug that join lands on, and refuses anything that fails isProjectId.

The default, and who may change it

No agent has a grant by default (deny by default, same as vault). sync.grants.set { project, agent, status } is person-only (core/presence PERSON_ONLY, same reasoning as files.drive.access: a switch on an existing capability, not a secret reveal, so PERSON_ONLY rather than HUMAN_ONLY). An empty agent string is "every agent" (matching a bare module grant in vault_grants); a named agent narrows it to that one, and the more specific row wins when both exist, same precedence vault's own release path already resolves.

What an agent sees when a folder is not granted

Two surfaces, both refuse the same way vault's vault.release does today (line ~1074, core/vault/vault.js): a clear, actionable error naming the exact fix, never a silent empty result and never a vague "denied":

  • A specific file or session. Reading a file, or a session's content, inside an ungranted project throws "<project>" is not granted to <agent>; call sync.grants.set to fix it (the reviewer may want the exact wording tightened to the harness's own conventions before this ships; the point is the shape, not the string).
  • A list or search. Recall search, sync.scan-style listings, anything that returns rows across several projects: an ungranted project's rows are left out of the result entirely, the same way vault.list never shows a value it did not grant. Nothing marks the gap (no "1 hidden project" placeholder): an agent that has never been told a project exists learns nothing new by its absence, which is the point of a project boundary in the first place.

Open questions for the lead and the reviewer

  1. Is sync_grants the right owner (core/sync), or should this live wherever projects.* lives today, since it is a project-level concept and sync is only the first caller of it? Building it in core/sync first and moving it if a second caller needs it seems cheaper than guessing now, but flagging it since it is the kind of thing that is annoying to move later.
  2. The project-id join (device folder name to box project id) needs a decision before step 3's code, not just its design: does the person pick a box project for each device folder at sync.consent time (extending the picker this session's sync.scan already built), or is it inferred some other way?
  3. sync_grants needs its own presence rule confirmed (PERSON_ONLY vs. HUMAN_ONLY) before it ships, the same review files.receive just went through, flagging now so it is not a second HOLD.