Object storage, provider adapters, and signed URLs

Canonical proposed publication URL: https://3sln.me/artefacts/contracts/object-storage/v1. It is a public versioned artefact, distinct from private account resources. The version is immutable after formal release; a gated public deployment exposes this explicitly labeled draft for review without declaring stable conformance. The draft JSON Schema 2020-12 in docs/artefacts/object-storage-v1.json defines method request variants, argument bounds, metadata and result schemas. Runtime checks additionally enforce byte limits, UTF-8 key lengths, range ordering and authority. Publication and independent interoperability review remain pending. Reviewer: ___ Date: ___ Outcome: ___.

Unlike the first draft's mandatory write contract, this contract supports capability negotiation. Required capabilities truthfully reports read, write, byte range, read/write presigning, atomic conditional writes, byte limits, and operational limits. Apps request method scopes separately. A provider may expose reads without writes, and the UI must say so. Changing a capability never expands an existing method grant.

Objects use stable logical keys, byte payloads, opaque versions, content type, size and modification metadata. Reads support inclusive {start,end} ranges only when advertised. Writes require {absent:true} or exact version conditions. Deletes require exact versions. No unconditional overwrite is implied. Driver errors map to safe protocol codes; upstream tokens and errors never reach clients.

presign requests a read or write bearer URL, HTTP method and required headers, with lifetime at most five minutes. The corresponding explicit presign grant is mandatory. URLs may expose data until expiry even after app/token revocation; approval must disclose this and offer proxy-only access instead. Upload signing needs content length/type bounds, namespace scope and required conditional semantics. Until these constraints are enforced, the implementation rejects presigned writes and advertises them unavailable.

Provider boundaries

Gateway network security does not rely on string URL validation alone. Operator allowlists must include only vetted public services; custom-host approval requires DNS/private-network checks and a safe egress policy. Credentials are sent only after endpoint validation, with redirect following disabled. Disabled/unconfigured provider cards must explain the required credential or operator setup.

Release gates

Machine schemas, normalized object metadata and mocked provider conformance tests are present. Provider-native listing helpers are retained only as internal utilities and are not part of the object-storage contract or public RPC gateway. Listing requests fail before storage or provider requests; old capability records are projected without their legacy list field.

Provider response errors map to safe codes; token-bearing response bodies are not exposed. Range reads require an actual bounded partial response. S3 SigV4 GET presigning is restricted to the namespace and five minutes; uploads remain unavailable. R2 supports native atomic conditional PUT only; S3 writes require operator-reviewed conditional capability, and must never be enabled directly from an untrusted configuration flag.

Remaining gates include live credentials and provider conformance, OAuth refresh/revocation, DNS and connection-time egress enforcement, cross-owner gateway authorization, quota/rate enforcement, audited credential encryption, Unicode/name compatibility, and durable integration tests. Mocked adapters demonstrate request construction and defensive handling, not working production accounts. Connector verification reads an explicitly supplied known object key; it never falls back to enumeration or treats a missing test object as proof of read access. Empty connectors remain unverified until a readable bootstrap/probe object is supplied. The Worker currently enables S3/WebDAV configuration only; Google setup is enabled only when its client credentials and encryption key are configured; configured status does not establish live conformance. Dropbox/OneDrive OAuth cards remain unavailable. No remote provider account was connected or altered during implementation.

Application-managed directories

The contract deliberately has no list method or listing capability. Apps maintain directory/index objects containing the exact keys, versions and metadata of their own objects. This keeps enumeration cost and indexing policy with the application, across providers that differ in native listing support and cost. It does not guarantee zero-cost operations: index reads/writes and provider-side key resolution can still incur requests. Inbox/message collection enumeration belongs to its separate collection contract and is unaffected.

An app chooses a collision-resistant namespace and a deterministic root index key, for example apps/<app-id>/directory.json, and records that root pointer in its existing granted preferences/resource state so another device can find it without enumeration. Applications must never overwrite another app's root. Resource IDs and root keys must be persisted/exported together for recovery. A provider with opaque IDs needs an explicitly provisioned root ID or a provider-owned logical-key mapping; names are not unique identifiers.

For writable synchronization, require reliable atomic conditional writes. Create objects with absent:true, update a directory against its exact previous version, and retry conflicts by rereading/merging rather than overwriting. Store new content first, then atomically update the directory pointer; there is no multi-object transaction. Use immutable content keys and a durable app-owned pending-operation manifest so interrupted writes can be reconciled. A read-only provider can host an externally supplied index but cannot offer writable app sync; a provider without reliable conditional writes must not claim safe concurrent writable sync.

Removing an entry and deleting its object are distinct effects with separate delete permission. Use a tombstone/pending-deletion ledger and retention window; failed index updates can leave orphans, and no listing fallback exists to rediscover them. Apps must retain known created keys for reconciliation and disclose these limits. Directory references do not grant access: every referenced object still requires current resource, namespace and method authorization. A lost sole root/index cannot be reconstructed by this contract; backup/export the directory state. Large directories should use bounded sharded index objects and a small CAS-updated root rather than one unbounded file.

The managed Google Drive adapter supplies persistent logical-key mappings, so apps can use deterministic directory keys without Drive listing. This map is authoritative host state and must accompany account/provider backups; another independent host cannot reconstruct it from Drive filenames. Same-Google-account reconnect preserves resource ID, root folder and mappings. External edits, moves, missing files or changed folder ownership fail safely. The independent legacy read adapter still accepts raw file IDs and is not used for new portal connections.