# Phase 4 — Tenant storage

## Delivered

- Measured storage under `storage/tenants/<storage_key>`.
- Tenant and app path confinement.
- Atomic file replacement using temporary files and `os.replace`.
- Per-tenant filesystem locking during capacity checks and writes.
- Global or app-specific `storage_mb` limits.
- HTTP `413 storage_quota_exceeded` from the invoice gateway flow.
- Storage usage, capacity, remaining space, percentage, and status in the
  signed usage and entitlement responses.
- English WordPress usage display in MB.

## Security properties

- The storage key comes from the authenticated tenant record, never from an
  untrusted request header.
- Resolved paths must remain below the tenant/app root.
- A rejected write does not leave the target file behind.
- Replacing an existing file accounts for the size delta.

## Horizontal scaling requirement

Filesystem measurement is authoritative only when every web node mounts the
same tenant volume. Before adding the virtual server, use one of:

1. shared NFS/Ceph storage mounted at the same tenant-storage root; or
2. object storage with tenant-prefixed objects and a centralized reservation
   counter.

Independent local disks must not be presented as one storage allowance. The
current file lock coordinates processes that share the same mounted volume.

## Adoption rule

New platform upload flows must use `platform.storage.service.write_bytes`.
Legacy app-specific upload routes remain outside this phase until each app is
migrated; they must not be exposed as platform-managed storage beforehand.
