Defines opaque, tenant-scoped object keys and a database ownership record so filenames, users, and mutable authorization never become storage identity or leak through URLs and logs.
Package status: reference context ready for human review. The contract and test scenarios are complete, but no claim is made that an adopting implementation has passed them.
Decision
Generate server-side opaque keys under an environment and tenant namespace; keep original names and authorization in the application database. Treat the bucket as private and resolve every upload or download through the ownership record.
Scope
- Key grammar, ownership mapping, version identity, multipart uploads, temporary objects, logs, lifecycle prefixes, and migration.
- Private application objects in S3-compatible storage.
- Collision, enumeration, and cross-tenant controls.
Outside this block
- Content-addressed de-duplication across projects.
- Making object keys an authorization mechanism or exposing raw bucket listing.
Contract
- A key contains only controlled slugs and random or server-issued identifiers; it excludes original filename, email, account name, and untrusted path text.
- Tenant ID and environment are distinct key segments validated against the ownership record.
- One logical file version maps to one immutable object key; replacements create a new version key.
- The database record owns state, media type, size, digest, uploader, tenant, version, and lifecycle; object metadata is not the sole source of truth.
- Client-provided names are presentation metadata and are sanitized separately for download headers.
- Temporary and quarantine prefixes have explicit lifecycle rules and cannot be served as active files.
Implementation guidance
- Use a grammar such as
env/tenant-id/files/file-id/version-id/blob, with every ID generated and validated server-side. - Issue scoped upload intents tied to the expected key, byte limit, content-type policy, and expiry.
- Log object IDs or hashes rather than original filenames where operationally sufficient.
- Migrate by writing a mapping and copying to new immutable keys before switching reads; verify digest before old-object cleanup.
Failure handling and safeguards
- Reject keys containing traversal segments, control characters, ambiguous normalization, or a tenant inconsistent with the ownership record.
- If an upload completes without a committed database record, leave it quarantined for orphan reconciliation.
- If the database and object digest differ, block serving and investigate rather than updating ownership silently.
Verification and operations
- Measure key-validation rejection, cross-tenant denial, missing-object and unowned-object reconciliation, digest mismatch, and temporary-object age.
- Test that logs, signed URLs, and error responses do not expose original private names.
- Audit bucket policies to ensure direct public reads and listings remain disabled.
The executable-looking examples in this package are fixtures and acceptance contracts. Run
python tools/validate.py from the collection root to check package structure and metadata; then
implement and execute the scenarios in the target repository.
Adoption assumptions
- Names and numeric values in
example.yamlare an adoption profile, not universal defaults. - The adopting team must map actors, data classes, error vocabulary, and ownership to its system.