Overcast is alpha — behaviour and APIs may change between releases. Pin your version and read the changelog before upgrading.

overcast

S3 limitations

How S3 versioning, lifecycle rules, website configuration and encryption diverge from AWS in Overcast, in full.

The long-form behaviour behind S3: what each configurable subsystem really does, and where it stops.

Versioning: version ids, delete markers and suspended buckets

A bucket has three versioning states.

StateBehaviour
UnversionedThe initial state. Each key holds one object, a write replaces it, a delete removes it. ListObjectVersions reports each object as its key’s null version, as AWS reports it
EnabledEvery write mints a version id, returned in x-amz-version-id, and the previous version becomes noncurrent. A delete with no versionId removes nothing — it adds a delete marker that becomes the current version. A delete naming a versionId removes that version permanently, and the newest survivor takes its place if it was current
SuspendedNot “off”. Recorded versions are kept and stay addressable; new writes become the key’s single null version, replacing any previous null version; a delete still leaves a delete marker, with version id null

There is no transition back to unversioned, on AWS or here.

Objects stored before a bucket was versioned are the null version of their key. They stay readable, appear in ListObjectVersions as VersionId=null, and can be addressed with ?versionId=null. Nothing has to be migrated by hand.

Reads distinguish AWS’s two “not there” answers:

RequestAnswer
GET/HEAD of a key whose current version is a delete marker404 NoSuchKey with x-amz-delete-marker: true
GET/HEAD naming a delete marker’s own versionId405 MethodNotAllowed with x-amz-delete-marker: true and Allow: DELETE
GET/HEAD naming a versionId that does not exist404 NoSuchVersion

ListObjects and ListObjectsV2 never show delete markers or noncurrent versions — only ListObjectVersions does. It returns keys ascending and, within a key, most recently stored first, with Version and DeleteMarker elements interleaved, and supports prefix, delimiter, max-keys and resumable key-marker/version-id-marker pagination. A version-id-marker without a key-marker, or one naming no version of the marker key, is refused with 400 InvalidArgument as AWS refuses it.

Version ids are opaque, URL-safe, fixed-width tokens. Nothing about their internal structure is part of the contract.

Lifecycle: version-aware actions

On a versioned bucket the same rules mean different things.

RuleBehaviour
ExpirationOn the current version of a versioned object it adds a delete marker rather than deleting anything; on an unversioned bucket it deletes outright
NoncurrentVersionExpirationPermanently removes versions noncurrent for NoncurrentDays. The clock starts when the version that replaced it was written. NewerNoncurrentVersions retains that many newer noncurrent versions first
NoncurrentVersionTransitionMarks noncurrent versions with a storage class on the same eligibility rules, and still respects the minimum transition size below. Delete markers are never transitioned — there are no bytes to move
ExpiredObjectDeleteMarkerRemoves a delete marker that has become its key’s only version. AWS refuses it alongside Days or Date in the same Expiration, and so does Overcast (400 InvalidRequest)

Expiration wins over transition for a given version, as on AWS. The hourly sweeper runs on the injected clock, so a test advances a mock clock rather than waiting.

Lifecycle: default minimum transition size

PutBucketLifecycleConfiguration accepts the x-amz-transition-default-minimum-object-size header and echoes it on its own response and on GetBucketLifecycleConfiguration. Omitting it stores nothing and reads back as AWS’s default, all_storage_classes_128K.

The setting is applied, not merely round-tripped:

ValueEffect
all_storage_classes_128K (default)An object under 128 KB is not transitioned to any storage class
varies_by_storage_classAn object under 128 KB still transitions to GLACIER or DEEP_ARCHIVE; every other class keeps the 128 KB floor

A rule whose own Filter sets ObjectSizeGreaterThan or ObjectSizeLessThan opts out of the default, as on AWS. A value outside the two documented ones is refused with 400 InvalidArgument rather than stored. Every PutBucketLifecycleConfiguration replaces the configuration wholesale.

CloudFormation’s AWS::S3::Bucket.LifecycleConfiguration.TransitionDefaultMinimumObjectSize dispatches through this header; S3 still owns the validation.

Website: stored faithfully, served by nothing

PutBucketWebsite accepts the whole WebsiteConfigurationRedirectAllRequestsTo, IndexDocument, ErrorDocument and RoutingRules with their Condition and Redirect members — and GetBucketWebsite returns it unchanged. AWS’s constraints are enforced:

  • RedirectAllRequestsTo cannot be combined with any other element, and a configuration must carry either it or an IndexDocument.
  • Protocol must be http or https; an IndexDocument Suffix must be non-empty and slash-free.
  • A Redirect must name at least one destination field, and ReplaceKeyWith and ReplaceKeyPrefixWith are mutually exclusive.
  • A Condition must carry at least one predicate.

Anything refused returns 400 InvalidArgument and leaves the previous configuration in place. Each Put replaces the whole document; DeleteBucketWebsite removes it.

Two boundaries:

  • HttpRedirectCode values are not validated. Real S3 rejects a code that is not a valid HTTP redirect status; Overcast stores whatever it is sent.
  • No website endpoint is served. A stack that deploys one deploys it, but no request is redirected or answered with an index document.

Encryption

PutBucketEncryption stores AES256 and KMS rules and GetBucketEncryption returns them, defaulting to SSE-S3 when none is set. Nothing is encrypted: objects are stored as sent. Object-level SSE request headers (x-amz-server-side-encryption and its SSE-C siblings) are ignored — they are neither stored nor echoed on a later GetObject or HeadObject.