Simulators
S3 changelog
What changed in each S3 simulator release, newest first.
v0.0.7 — 2026-11-06
- The docs have an Errors and limits section. It shows the
<Error>document and its fields, the responses that depart from it, and where the simulator differs from S3: no throttling, noContent-MD5check and no cap on a version listing. It lists each listing’s page size and next-page marker, and says what a re-sent PUT, DELETE or bucket create does.
v0.0.6 — 2026-11-06
- The docs say what the one account holds. The limitations describe the account’s owner and ACLs without repeating how to run several simulators.
v0.0.5 — 2026-11-05
-
Setting the clock never stamps records before the timeline events it fired. When
POST /_rystic/clock {"time": ...}fires timeline events due at its target, the next request’s records stamp after theirs instead of reusing their instant; a set past every event still stamps one millisecond past its target, as before. -
A paced clock keeps wall time, and
rystic run --speed 0stops it. UnderRYSTIC_PACE, each tick moves the clock by the wall time that passed since the last, so a tick missed while the twin was busy no longer makes timeline events fire late.rystic runhands the twin only the pace it resolved, so--speed 0, a scenario without events,--tapeand--livekeep the clock manual even when your shell exportsRYSTIC_PACE. -
Swagger UI to read and call the twin from the browser.
/_rystic/swagger, linked from the state UI header, is Swagger UI over the published API trimmed to the operations this twin serves; Try it out sends each request to the twin. -
An unsigned ListBuckets answers 307, as S3 does.
GET /with noAuthorizationand no presigned query answers307 Temporary Redirectwith no body, where it answered403 AccessDenied. A request with no credential on a bucket that does not exist answers404 NoSuchBucket, as before, and one on a bucket that exists is still served where S3 refuses it, so sign every request. -
A state dump seeds back as it was, and the next record gets a fresh id.
GET /_rystic/inspectposted toPOST /_rystic/seedafter a reset restores every record where it was, per-caller records included, which used to land under a doubled key. The next record created after any seed passes every uuid and event id the simulator minted that the seed holds.GET /_rystic/inspect?view=sequenceslists every id counter, and a world document’s newsequenceskey restores them, so ids the simulator renders from a counter continue where the dump left off. -
A slow client no longer holds up the others. The simulator reads a request’s whole body before it starts serving it and sends the answer after, so a client that uploads slowly, or reads a large answer slowly, no longer delays other clients’ requests. Answers are unchanged; one larger than 4 MiB still makes other requests wait while it is sent.
-
The call log shows what each call asked for and whether it succeeded. Each
GET /_rystic/callsentry now carriesargs, the call’s query and body arguments with credentials redacted (long values cut, file contents left out), andok, false for an answer that is not 2xx. A path the simulator does not serve is markedunknown_route: true.?tail=Nreturns only the last N calls, anddroppedcounts the calls the log no longer keeps. -
The docs are one page, with a page per feature. The archive ships
README.mdin place ofHOWTO.mdandSCENARIOS.md, with the same sections in the same order as every Rystic simulator’s page, andCHANGELOG.mdbeside it. Multipart uploads and checksums, versioned buckets, presigned URLs, virtual-hosted addressing and Swagger UI each have their own page underdocs/features/, and every example on these pages runs as written against a fresh simulator. -
The Scenarios table is easier to scan. Each row says in a line what the scenario is for, and a list below the table says what each scenario holds.
-
Pro starts with a 14-day trial. Free has ended, so every simulator is on Pro and Enterprise, and the docs name only those plans.
-
Limitations is shorter to read. It opens with the known issues and one sentence counting the rest. The detailed lists follow, and the website folds them behind “Show all limitations”.
-
The page has no Plans section. The Quickstart names the plans that run the simulator, and the pricing page lists what each plan includes.
-
The docs read more plainly, mark where the simulator differs from S3 and keep to the simulator’s own facts. Sentences are shorter, and a line marked “Differs from S3” sits beside the behaviors that depart from S3, such as the unchecked signature, the 64 MiB object limit and reading an older version. What a misconfigured client sees, conditional requests and the static site’s timeline are tables, and Seeding state names the keys of every register. The auth, seeding and reset sections link the shared pages for the generic mechanics.
v0.0.4 — 2026-10-12
-
A world can carry its own timeline. A
--seedfile, or a complete world document posted toPOST /_rystic/seed, may addcredentials, binding presented credentials to actors, andevents: calls the twin makes on its own API at set sim times as one of those actors, through the same handlers and egress as your requests. A top-levelcredentialsoreventskey the twin used to ignore now takes effect.RYSTIC_PACE=<speed>plays the clock, and the timeline, in real time, and an event’satmay be+<duration>./_rystic/inject,--injectandRYSTIC_INJECTnow needstate_setinstead ofscenarios. -
A timeline call can hand what it minted to later events. A
callevent may addbind,{"<name>": "<JSON Pointer>"}, saving fields of its answer, such as an ID the twin minted; a later event uses one as${name}in a call’s path, headers or body or in a seed event’s record keys and values (a register name cannot be bound). A schedule naming a${name}no earlier call binds is refused at load, and an event whose binding call failed records an error instead of firing. -
s3_static_siteplays a timeline. Over the two hours after the pinned clock, the site’s deploy bot, bound to its ownAWS4-HMAC-SHA256authorization header, snapshotsindex.htmlintoreleases/2026-02-01/, puts a newassets/app.js, deletes thelegacy.htmlredirect stub, rolls an access log and uploads the release bundle as a one-part multipart upload, all through the S3 API; advance the clock to watch the release land. -
A clock jump fires each timeline event at its own time. When one
POST /_rystic/clockor paced tick passes several events, each fires with the clock at itsat, so the records it creates carry that time, and the clock then lands where the move asked. An event already due when the clock moves fires at the current time. -
A clock jump stamps each timeline event at its exact instant. When a clock move passes timeline events, the records each one makes carry exactly its
at, to the millisecond, whatever traffic came before it, so one jump leaves the same records as stepping the clock to each event in turn. -
A timeline call can carry raw bytes, XML or a JSON array. A
callevent may sendbody_textorbody_base64instead ofbody; the twin sends it as written, under the route’s own content type (XML, bytes, JSON or a form), so a scheduled call can upload an object’s bytes or post an XML document, and${name}fills inbody_text. Abodymay be a JSON array where the route reads one. A call that answers XML binds from its elements by JSON Pointer, such as/InitiateMultipartUploadResult/UploadId.
v0.0.3 — 2026-10-11
-
DeleteObjects in quiet mode answers only the keys that failed. A
<Quiet>true</Quiet>delete now answers S3’s quietDeleteResult: noDeletedentries, only anErrorper key that failed, so a fully successful quiet delete answers an empty result. -
GetObjectAttributes answers every attribute you ask for.
x-amz-object-attributesnamingChecksum,ObjectPartsorStorageClassnow returns them besideETagandObjectSize: the stored checksum and its type, a completed multipart object’s parts, andSTANDARD. TheETagelement is unquoted, as S3 answers it. -
A multipart upload keeps the checksum algorithm it was created with.
CreateMultipartUploadwithx-amz-checksum-algorithm(CRC32, CRC32C, SHA1, SHA256) now completes with S3’s composite checksum for it (<base64>-<part count>, typeCOMPOSITE) instead of the default CRC64NVME, andCompleteMultipartUploadanswersx-amz-server-side-encryption. The completed object reads back throughHeadObjectandGetObjectwith that checksum andx-amz-server-side-encryption, as a single-PUT object does, andGetObjectAttributeslists its parts. -
ListObjectVersions lists the objects you write. In a bucket without versioning every object is listed as a
VersionwithVersionIdnullandIsLatesttrue, so emptying a bucket through the version listing (boto3’sbucket.object_versions.delete(), Terraform’sforce_destroy) now removes your objects. -
DeleteObject in a versioning-enabled bucket hides the object. A delete marker becomes the key’s current version and the object stays listed in
ListObjectVersionsas a noncurrent version, so a laterGetObjectorHeadObjectanswers 404NoSuchKeyas S3 does, where the twin used to keep serving the object. -
ACL grantee group URIs are declared identifiers.
http://acs.amazonaws.com/groups/global/AllUsersand its siblings in a bucket’s or an object’s ACL are handed out byte for byte and never rebased onto the twin, by declaration (pipeline_policy.jsonhost_roles), so a seeded ACL naming them passesRYSTIC_SEED_GATE=rejectand a client comparing a grantee URI against the AWS constant matches. -
The scripts in this archive are MIT-licensed.
check.shand the archive’s other scripts are published under the MIT License at https://github.com/Rystic-Inc/rystic-scripts , so you can copy, change and share them.LICENSE.txtno longer covers them; it still governs the twin itself.
v0.0.2 — 2026-10-07
-
Streaming uploads in the
aws-chunkedcoding. A body sentaws-chunked(Content-Encoding: aws-chunkedorx-amz-content-sha256: STREAMING-…), as SDKs stream uploads with a trailing checksum, is decoded: the object stores the payload alone, a trailingx-amz-checksum-*is verified, stored and echoed like the header form, and framing that does not parse answers400 IncompleteBodywithout storing anything. -
Presigned URLs. A SigV4 or SigV2 presigned URL, as
aws s3 presignand boto3’sgenerate_presigned_urlmake, stands in for theAuthorizationheader. The twin checks its shape, and its expiry against the real clock, never its signature: a misshapen SigV4 URL answers400 AuthorizationQueryParametersError, a misshapen SigV2 one403 AccessDenied, an expired one403 AccessDenied, and one sent beside a second credential400 InvalidArgument, as S3 answers them. -
Breaking: a request’s SigV4 signature must be well formed and fresh. The twin still holds no key and never verifies a signature, but it now checks the signing time against your machine’s real clock and then the
Authorizationheader’s shape, in S3’s order: a signed request with no parseableX-Amz-DateorDateanswers403 AccessDenied(“AWS authentication requires a valid Date or x-amz-date header”), one signed more than 15 minutes from now, either way, answers403 RequestTimeTooSkewedwhatever its shape, one with a fresh date but without exactly one well-formedCredential,SignedHeadersand 64-hexSignatureanswers400 AuthorizationHeaderMalformed, and one with no credential answers403 AccessDenied. A staticAuthorizationheader, such as the placeholder v0.0.1’sHOWTO.mdandcheck.shsent, is refused: sign each request instead, as the AWS SDKs and CLI already do, or withcurl --aws-sigv4 'aws:amz:us-east-1:s3' --user <access key>:<secret>(curl 7.75 or later; any key pair works)./_rystic/clocknever makes a fresh signature stale. -
Virtual-hosted addressing. A request to
<bucket>.s3.us-east-1.amazonaws.comor<bucket>.s3.amazonaws.comis served as the path-style request with that bucket first in the path, so a client keeping S3’s own hostnames and default addressing reaches the twin throughrystic run s3-twin --interceptandrystic exec, when it honors the gateway’s proxy and CA settings. Path-style on an endpoint override works as before.
v0.0.1 — 2026-10-07
-
A local Amazon S3 for your storage code and the agents that drive it. The first release of the S3 twin: 32 operations of S3’s REST API, path-style on
/,/{Bucket}and/{Bucket}/{Key+}— buckets, objects under any key, listings, multipart uploads, copies, tagging, batch deletes, conditional and Range requests and request checksums — served from a model validated against S3 itself. Point boto3 or the AWS CLI at it with an endpoint override, path-style addressing and any access key pair (HOWTO.mdshows both), and your code runs against it unchanged; nothing it stores reaches AWS. -
Objects under any key, read back as S3 answers them. PutObject, GetObject, HeadObject and DeleteObject on
/{Bucket}/{Key+}, where a key such aslogs/2026/09/report.csvis one object and%2Fin a path is the/it stands for; ListObjectsV2, ListObjects and ListObjectVersions list byprefixanddelimiter; GetObjectTagging, PutObjectTagging, DeleteObjectTagging, GetObjectAttributes and GetObjectAcl read and write an object’s settings. Bodies run to 64 MiB; a larger one answers400 EntityTooLarge. Every routed answer is S3’s own XML or headers and carriesx-amz-request-idandx-amz-id-2, every error a routed S3 request gets is S3’s<Error>document (only a path no route matches answers the twin’s own JSON 404), and an operation on a modeled path that the twin does not model (ACL writes, legal hold, retention, restore, select, and every bucket setting but versioning and tags) answers501 NotImplementedand changes nothing. -
Multipart uploads, copies and batch deletes. CreateMultipartUpload, UploadPart, ListParts, CompleteMultipartUpload, AbortMultipartUpload and ListMultipartUploads let an SDK’s upload manager — boto3’s
upload_file,aws s3 cp— start an upload, send its parts and complete it with the multipart ETag S3 gives it, up to twelve parts at S3’s 5 MiB minimum; a part whose ETag matches none uploaded answersInvalidPart, an unknown upload IDNoSuchUpload, and the completed object’sLocationleads back to the twin. CopyObject and UploadPartCopy copy within a bucket or from another byx-amz-copy-source, and DeleteObjects deletes every key its<Delete>document names. -
Request checksums, conditional writes and Range reads. A PutObject or UploadPart carrying
x-amz-checksum-crc32,-crc32c,-crc64nvme,-sha1or-sha256, as the AWS SDKs send by default, is checked against the body — a mismatch answers400 BadDigestand stores nothing — and the stored checksum is echoed underx-amz-checksum-mode: ENABLED, so SDK integrity validation passes.If-None-Match: *or a staleIf-Matchon PutObject answers412 PreconditionFailedand leaves the object alone;If-None-MatchandIf-Modified-Sinceon GetObject and HeadObject answer304;If-Matchnaming another ETag answers412; and aRangeanswers206 Partial Contentwith itsContent-Range. -
Buckets: create, list, read, configure and delete. CreateBucket makes a bucket by the name you choose, ListBuckets lists the account’s buckets with each one’s
CreationDate(andBucketRegionwhen asked), HeadBucket, GetBucketLocation, GetBucketAcl, GetBucketVersioning and GetBucketTagging read one, PutBucketVersioning turns versioning on or suspends it, PutBucketTagging and DeleteBucketTagging write its tags, and DeleteBucket removes an empty one — a bucket still holding objects answersBucketNotEmpty.x-amz-expected-bucket-ownerpasses for the owning account’s 12-digit ID (123456789012unless a seed says otherwise) and answers403 AccessDeniedfor any other. -
Five shipped worlds, and the one your test needs.
--scenario s3_static_sitebootsacme-demo-site, an unversioned website bucket with seven keys under real prefixes beside its access-log bucket, and is the world Free boots andPOST /_rystic/resetreturns to.s3_versioned_archiveholds six version rows with a delete marker among them,s3_encrypted_vaultfour objects pinning different encryption, checksum and storage-class combinations,s3_bucket_farmsix buckets over five regions and every versioning state, ands3_fresh_bucketone empty tagged bucket;SCENARIOS.mdsays what each is for. On Pro and Enterprise,POST /_rystic/seedlands buckets, objects, versions and multipart uploads directly, andPOST /_rystic/resetrewinds the ID sequence and the clock, so two identical runs give byte-identical objects and listings down toETag,Last-ModifiedandUploadId. -
Measured against S3 itself. The 2026-10-06 full-suite round matched 117 of 117 probes against
s3.us-east-1.amazonaws.com, with 0 inconclusive.HOWTO.mdstates what the suite leaves out and the differences this release ships with: a versioning-enabled bucket keeps no versions of what you write, ListObjectVersions lists nonullversion for an object you write, DeleteObjects in quiet mode lists every deleted key, GetObjectAttributes answersETagandObjectSizeonly, and a multipart upload completes with CRC64NVME whatever algorithm it was created with and reads back without its checksum or encryption headers.