Skip to content

Campus Drive and logical storage

Campus logical file storage is the byte substrate for user-visible documents, app-created shared resources, and synchronized system resources. It is available after authentication and does not require a Node, a running workload, or hosted-compute payment. The platform-owned Content graph is the reusable end-to-end encrypted data foundation of Campus Drive: it adds encrypted objects, versions, collections, membership, invitations, and synchronization above the opaque Files primitive. The Campus Drive app is the folder-oriented human client over that foundation, while Campus Photos is the media-library/gallery client over the same Drive objects and collections. Drive is a preinstalled, system-supplied app: catalog reseeding adds it to existing accounts that do not yet have an installation, and new accounts receive it with the rest of the default toolset.

The layers intentionally remain separate:

  • logical storage owns stable IDs, account/app ownership, ACLs, content hashes, and byte placement;
  • the Drive E2E foundation, implemented by Content, owns encryption formats, opaque objects and collections, reachability, roles, invitations, and ordered changes;
  • the Drive app presents those objects as files, folders, sharing, imports, and exports;
  • Photos presents Drive-backed media objects as a chronological library, albums, derivatives, and nondestructive edits; it does not call raw logical Files or define another crypto/share format;
  • private application /data remains app-internal state and never appears in Drive merely because it is durable;
  • reserved .campus/ resources use logical storage but are not ordinary Drive documents.
text
Campus Files primitive
  -> Campus Drive E2E foundation (Content graph + crypto + sharing + sync)
       -> Campus Drive app (files and folders)
       -> Campus Photos app (library, gallery, albums, and edits)

One API, two byte providers

CampusOS owns logical file metadata, ownership, ACLs, content hashes, and short-lived transfer authorization. It does not proxy blob bytes. After authorization, the browser or app backend uploads/downloads the immutable content-addressed blob directly:

Every new logical file also declares the immutable confidentiality contract documented in Logical Files protection contracts. Drive object versions require client-sealed with Session authority; Files stores that declaration and ciphertext bytes but no key, keyring handle, filename plaintext, or Content envelope.

text
caller -> CampusOS logical-storage API -> signed transfer grant
caller -> local blob store or Spaces -> blob bytes
caller -> CampusOS complete -> ready logical file

The backing provider changes by deployment, not by app:

ModeUseByte store
localDefault development modecampus-blob-store, filesystem-backed Rust service
spacesHosted/production modeS3-compatible DigitalOcean Spaces
noopExplicit metadata-only testsNo bytes; never the normal development default

Both real providers return the same Campus transfer shape: mode, method, url, headers, and expiresAt. CampusSDK accepts local and spaces without changing the logical file methods. The local service intentionally implements this narrow Campus capability contract instead of pretending to be a complete S3 server.

Local grants are HMAC-SHA256 bearer URLs scoped to exactly one GET or PUT, one blobs/sha256/<prefix>/<digest> object, and one expiry. Uploads are streamed to a temporary file, bounded by the configured maximum, verified against the object-key SHA-256, and atomically committed. The signing key and blob bytes live under data/blob-store/ and must not be committed.

bb dev owns the local service at http://localhost:4191; GET /health reports readiness. It can also be run as bb blob-store or cargo run -p campus-blob-store --bin campus-blob-store. Relevant settings are:

text
CAMPUS_BLOB_STORAGE_MODE=local
CAMPUS_LOCAL_BLOB_LISTEN=0.0.0.0:4191
CAMPUS_LOCAL_BLOB_BROWSER_URL=http://localhost:4191
CAMPUS_LOCAL_BLOB_NODE_URL=http://host.docker.internal:4191
CAMPUS_LOCAL_BLOB_DATA_DIR=data/blob-store/objects
CAMPUS_LOCAL_BLOB_SIGNING_KEY_PATH=data/blob-store/signing.key
CAMPUS_LOCAL_BLOB_MAX_BYTES=5368709120

The separate browser and Node URLs refer to the same service because a Docker workload cannot use the host browser's localhost. Production selects spaces and supplies the Spaces bucket, region, and credentials; the caller still sees a direct transfer grant.

Campus Drive encryption

Drive creates one random 256-bit AES-GCM key for every file. Browser uploads retain the v1 single-message format for compatibility. Delegated imports use v2: independently authenticated chunks with an 8-byte random nonce prefix plus the chunk index, and additional data binding the asset ID, index, and exact plaintext length. CampusOS therefore receives a random .encrypted/<asset-id>.campus logical name, the ciphertext size/hash, and the fact that the object is a Drive ciphertext; the blob provider receives only ciphertext bytes.

Drive's private metadata contains the original filename, media type, plaintext size, logical file ID, IV, and per-file key. New records are encrypted into Content object/version envelopes and placed in folder collections. The legacy Drive workload index remains encrypted as a compatibility and rollback record while migration verifies the corresponding Content objects; neither a workload-volume nor a blob-store disclosure reveals file contents or filenames. Optimistic Content revisions reject concurrent stale writes rather than silently overwriting another device.

Drive follows the Campus light/dark theme with a monochrome interface. Images, PDFs, browser-native video and audio, and bounded text previews open on demand: the browser obtains the authorized Content transfer, verifies and decrypts the ciphertext locally, renders a temporary object URL (or text surface), and revokes that URL when the viewer closes or navigates away. CampusOS and the blob provider never receive preview plaintext. Playback currently begins after local decryption; v2's chunk boundaries make range decryption possible without changing stored ciphertext, but a range-serving preview transport is not yet implemented.

Delegated imports

files.user.import is the create-only authority for a Campus App to put new encrypted content in Drive without gaining files.user.manage. A frontend calls CampusContent.prepareDriveImport with an editable source collection and its in-memory collection key. The SDK reserves the final logical file ID, generates one file key, and encrypts both the file record and placement envelope under the collection key. Only the resulting opaque envelopes and per-file encryption descriptor go to the same app's workload; the collection key does not.

The workload encrypts chunks with encryptCampusDriveChunk, calculates the ciphertext digest, and calls CampusServicesClient.commitDriveImport. Campus grants a temporary ACL only on that newly created user-owned file. Commit uploads and completes it, links the browser-prepared opaque Content envelopes, and removes the ACL. The exact preselected file ID makes a delegation one-use: replay collides instead of replacing content. Importers cannot list or manage unrelated user files. A long producer acquires a renewable work lease, so fetching, encryption, and upload can continue after its frontend closes. Torrents uses this contract for Save to Drive; Agent apps can use the same SDK surface for generated or downloaded artifacts.

Legacy files retain the origin-scoped IndexedDB vault key and exportable recovery key. Content folder keys are instead sealed to the account recipient whose private half is inside the existing encrypted user-secret bundle. The normal passkey login assertion also evaluates the PRF needed to unlock that bundle. The trusted shell keeps the full secrets only in memory, caches only the account recipient in account-bound tab sessionStorage, and returns only an app-authorized collection key. Drive never receives the account recipient private key. Older bindings require one final passkey unlock to rewrap them for this login salt.

This is the current v1 behavior, not the intended third-party key contract. The pending Session keyring, request-secret vault, and explicit intent keys proposal splits Wallet authority out of the login-unlocked bundle, replaces raw collection-key delivery with opaque Content handles, removes the legacy Drive vault after verified import or account reset, and requires a fresh-device test before v2 data is accepted. Because the legacy vault and several other app keys can exist only in one browser or workload, v2 migration is all-or-nothing per account; the recommended development cutover recreates accounts whose complete authority cannot be verified.

The v2 producer keeps encryption memory bounded to one chunk, but logical storage still uses one content-addressed PUT. The producer may stage ciphertext on its private volume before that upload. Multipart Spaces transfer and browser range decryption remain necessary before Drive can claim resumable full-5-GiB imports and instant seeking in very large media.

Delegated file sinks

A file sink is the client-held form of one delegated import. The destination frontend calls CampusEncryptedContent.prepareFileSink with an editable collection, its in-memory collection key, and exact file metadata. The SDK returns a short-lived campus.file-sink capability containing only one file's encryption descriptor and one opaque bearer. The collection key never enters the sink or CampusOS.

A free or paid handler produces the exact plaintext bytes (or a one-time retrieval grant). The destination agent/client then calls fillCampusFileSink or CampusServicesClient.fillFileSink with those bytes. The SDK encrypts the file in authenticated chunks, binds the sink to the ciphertext size and SHA-256, uploads through a direct transfer grant, and completes one buyer-owned Content object. Exact retries return the same receipt; another digest, an expired capability, a revoked destination grant, or a reused logical file ID fails closed. The sink cannot list, read, replace, or delete any other destination file.

The standalone helper redeems the opaque sink bearer and is available to trusted installed-app browser origins. CampusServicesClient.fillFileSink instead redeems through the owning installation's Gateway, so a local workload neither forwards that bearer nor depends on reaching a browser-oriented localhost Campus URL.

File sinks contain no payment meaning. The destination agent may fill one from a free tool result, after an admitted tool settles, or after local generation. Acceptance, payment, licensing, and delivery policy remain application-owned conditions around the same storage primitive.

The complete sink descriptor is intentionally not an MCP argument: it contains the one-file encryption key, and brokered tool arguments pass through Campus control planes. Cross-account tools return bytes or a scoped download grant; the destination agent keeps the sink local and performs the final encrypted write. A future workload-recipient envelope can enable direct remote filling without weakening this boundary.

Encrypted sharing

Authenticated sharing is collection based. Drive resolves an exact Campus address, encrypts the folder key to that account's active recipient key, and creates an address-bound invitation. Accepted members receive owner, manager, contributor, or viewer authorization. A parent-folder membership authorizes its cycle-free descendant graph; encrypted parent entries wrap child keys. Removing a member immediately denies new object transfers and advances the parent key epoch for future writes.

The same /v1/content and /v1/services/content surfaces are the Drive E2E boundary for the Drive app, planned Photos libraries/shared albums, and scoped Agent workspaces. Package grants are content.collections.read, content.collections.write, and content.collections.manage; app permission is always intersected with the account's collection access. Photos uses these collection grants and never receives Drive's trusted files.user.manage override.

Public bearer links remain a separate compatibility mode. Creating one generates a random share key. Drive encrypts only the selected file's private metadata and per-file key into a small share envelope stored by the Drive workload. The recipient URL is:

text
https://<drive-public-address>/s/<random-share-id>#key=<random-share-key>

The URL fragment is not sent in HTTP requests. The public Drive route can authorize the random share ID and proxy the encrypted logical blob, but it never receives the share key, filename, or file key. Recipient JavaScript decrypts the share envelope and file locally. Deleting the server record revokes the route even if a recipient retained the fragment. Expiry is enforced before the ciphertext transfer grant is issued.

Drive can hand the complete link to the operating-system share sheet or send it to an explicitly entered Campus/Matrix address through Campus Messaging. Drive has no contacts.read grant and therefore cannot enumerate the account's durable friend graph.

Launcher wallpaper

Home keeps its selected appearance in two reserved, user-owned logical files:

text
.campus/launcher/appearance.json
.campus/launcher/wallpaper.jpg

The trusted shell performs these account-cookie operations on Home's behalf, then transfers the bytes directly using the same grant contract. The JSON record stores a theme-neutral wallpaper family; each device resolves its matching light or dark variant. Devices normally inherit this account default, but a user can create an account-scoped override in the current browser profile for its theme and selected family. Passkeys do not identify devices. Home also keeps a browser-local copy for first paint; after launch it reconciles that cache with the logical file and refreshes it when account storage differs. Custom image bytes remain user-owned account data even when only the local device selects them.

Reserved .campus/ resources remain user-owned and inspectable through the authorized API, but Drive hides them from its ordinary document list.

Launcher resources use logical storage and remain usable before Node/payment provisioning.

Logical ownership

  • User sessions create user-owned files. The trusted Drive app may manage them with files.user.manage.
  • Apps with files.user.import may create only new client-sealed user files and receive a temporary ACL on each exact import; they cannot enumerate existing Drive files.
  • Ordinary apps create app-owned files, private to that installation by default.
  • Per-file ACLs may grant explicit read, write, or manage access to another principal in the same account.
  • App backends use the Node transfer URL; browser SDK callers use the browser transfer URL.

The logical APIs are /v1/storage/files for the trusted account surface and /v1/services/storage/files for scoped app callers.

Software belongs to people. Campus gives it a durable place to run.