Reference
JobsJobs
On this page
The proxy has one API surface for all the work that runs in the background: replication rules, lifecycle rules, and one-off maintenance jobs (bucket re-encryption, bucket migration, metadata backfill, and the delete jobs that the file browser starts). The admin UI shows this work on three pages. Storage → Replication and Storage → Lifecycle hold the rules, and Storage → Jobs lists every run (below).
The model#
Three subsystems share one surface. Every job appears as a row in GET /_/api/admin/jobs with the same normalized shape: kind, scope (bucket/prefix/target), status (idle / queued / running / cancelling / paused / deferred / succeeded / completed_with_errors / failed / cancelled), progress, and last run. A run is deferred when the proxy stopped it, not an operator: a maintenance job or a failed backend health check gates its bucket, or a replication pass used up its page budget. The run keeps its cursor, and the next run, about a minute later, continues from it. A one-off job that runs to its end with one or more failed objects is never succeeded: it is completed_with_errors when some objects went through, and failed when none did. Its row shows the failure count, which opens the job's Failures tab. Job ids are namespaced:
| Kind | Id | Defined by | Actions |
|---|---|---|---|
| Replication rule | replication:<name> | YAML (storage.replication.rules[]) | pause, resume, run-now, verify, kill, delete |
| Lifecycle rule | lifecycle:<name> | YAML (storage.lifecycle.rules[]) | pause, resume, run-now, preview, kill, delete |
| Maintenance one-off | maintenance:<n> | created via API/GUI, stored in the jobs DB | cancel (while active), delete (once finished) |
run-now starts one run on request. For replication, it runs even a disabled or paused rule once, and it does not change the flag. pause stops a replication run that is in progress at the end of the object that it copies, and this includes a run that run-now started. The run then has the status paused, which is different from cancelled (a kill): the run keeps its cursor, and the next run continues from that cursor. A rule that was already paused when run-now started its run does not stop that run; only a pause that you send during the run stops it. For lifecycle, it returns 409 on a disabled or paused rule. A lifecycle pause also stops a run in progress at the next object; the run keeps its cursor and ends paused, and a resume continues it at the next scheduler check. Both start the run in the background and answer 202 with status: "running", so poll GET /jobs/:id/runs for the result. kill interrupts a running replication run mid-object. A lifecycle kill stops the run at the next object and drops its cursor; the run ends cancelled. verify runs a parity audit; it returns 409 while a replication run is in flight for the same rule. verify?scope=unresolved reads again only the objects that the last audit could not read, and it adds the result to that audit. delete refuses (409) while the rule has a run or verify in progress.
The row of a replication rule carries the counters of its newest run in progress: processed (objects copied), skipped (objects already in sync), failed, bytes and deleted. The run writes these counters while it runs, so the row shows the progress of a running run and the totals of the last run when no run is in progress. bytes is the logical size of the copied objects, also when the copy moved fewer bytes through the proxy (a server-side copy on the same backend, or a delta that the destination stores as it is). failed_total is the number of failures that the newest run recorded, and last_error is the text of the newest of them. The event-driven path copies objects between runs, so the row also reports it in detail.events: copied, deleted and failed count what events did since the proxy started, and detail.last_event_applied_at is the time when an event last copied or deleted an object. These event counters are in memory on each node, so a restart sets them back to zero.
Rules are recurring and YAML-authored; maintenance jobs are one-offs born in the DB. An action outside a kind's capability matrix returns 400, and the error names the actions that the kind supports. GET /jobs/:id/runs and GET /jobs/:id/failures work for all kinds. A one-off synthesizes a single run, because the job is its own run.
API#
| Method | Path | Purpose |
|---|---|---|
GET | /_/api/admin/jobs | All jobs, normalized rows. One-off jobs: every active one and the 200 newest; older_oneoffs counts the older ones that the answer leaves out |
GET | /_/api/admin/jobs/runs | Every run on one list, newest first (below) |
GET | /_/api/admin/jobs/:id | One job row, also a one-off that the list no longer carries; 404 for an unknown id |
GET | /_/api/admin/jobs/:id/runs?limit=N | Recent runs for one job |
GET | /_/api/admin/jobs/:id/failures?limit=N&run_id=R | Recent per-object failures. With run_id, only the failures of that run of a rule. The response also has total: for a replication rule, every failure that the ring keeps; for a one-off job, every failed object. For a replication rule, each failure has trigger: run or event. The event-driven path keeps one failure per object: a new failed attempt replaces the older one. |
POST | /_/api/admin/jobs/:id/pause / resume / run-now / preview / cancel / verify / kill / delete | Per-kind actions; 400 outside the matrix |
GET | /_/api/admin/jobs/:id/preview | Progress of the running preview walk of a lifecycle rule |
GET | /_/api/admin/jobs/:id/verify | State of the parity audit of a replication rule |
POST | /_/api/admin/jobs/:id/verify/cancel | Stop a running parity audit |
POST | /_/api/admin/jobs/reencrypt | Create re-encrypt jobs: {"buckets": [...], "open_window": false, "purge_plaintext_versions": false} (max 100), one job per bucket. See the admin API reference for the two options |
POST | /_/api/admin/jobs/backfill-metadata | Create metadata-backfill jobs: {"buckets": [...], "refresh_last_modified": false} (max 100), one job per bucket |
POST | /_/api/admin/buckets/:bucket/migrate | Create a migrate job: {"target_backend", "delete_source", "target"} → 202 + maintenance:<n>. target is empty (default: the job fails in stage when the destination holds objects) or mirror (destination objects absent at the source are deleted before the flip, audited). 409 while config_sync_bucket is set: the routing flip is per-instance |
POST | /_/api/admin/jobs/filter-preview | What include/exclude globs select, on real keys (below) |
GET | /_/api/admin/jobs/bucket/:bucket | Busy state for one bucket: active is the active job row that holds the bucket (until it settles), writes_blocked says whether the write gate refuses writes now, and deletes lists the bucket's running delete jobs. Readable by non-admin browser sessions that may list the bucket (403 otherwise) |
All routes except the last are session-gated admin routes.
The runs list#
GET /_/api/admin/jobs/runs lists the runs of every replication and lifecycle rule and every one-off job, newest first by start time. A one-off job is one run: it has no rule. Each entry has the row shape of GET /jobs, and also these fields:
id: the run. For a rule run it is<job id>#<run id>, for examplereplication:nightly#42; for a one-off it is the job id,maintenance:<n>.job_idandrun_id: the job that ran, and the number of the run (rule runs only).origin: how the run started.scheduleandrun-nowfor rule runs,file-browserfor a bulk delete, andadminfor another one-off.started_by: who started a one-off (admin, or the IAM user name).
| Query | Effect |
|---|---|
status | all (default), active (queued, running or cancelling), or failed (failed, or ended with failed objects) |
kind | replication, lifecycle, or a one-off kind: delete, reencrypt, migrate, backfill-metadata |
rule | replication:<name> or lifecycle:<name>: the runs of one rule |
bucket | Runs that read or write the bucket: a rule whose source, destination or bucket it is, and the one-offs on it |
noop | true also lists the rule runs that ended without copying, deleting, moving or failing anything. By default they are hidden, because a rule that runs every hour makes such a run most hours |
limit, cursor | Page size (default 50, at most 200), and the next_cursor of the previous page |
Runs that start in the same second come in a fixed order: replication, then lifecycle, then one-offs.
The proxy keeps run history for a limited time. When a rule's run ends, the proxy deletes the rule's runs that changed nothing and started more than 30 days earlier, and it keeps at most the newest 1,000 runs of the rule. The failures of a deleted run go with it. A one-off job that ended more than 90 days earlier is deleted with its failures.
The admin pages#
The admin UI splits the work into definitions and runs. A rule is a definition: it lives in the YAML configuration until someone deletes it, and it runs many times. A run is one execution: of a rule, or of a one-off job, which is a run without a rule. So the rules have their own pages, and every run is on one list.
| Page | Route | Holds |
|---|---|---|
| Storage → Replication | /_/admin/storage/replication | The replication rules, the switch Run replication rules (storage.replication.enabled), and Check for due rules every (storage.replication.tick_interval) |
| Storage → Lifecycle | /_/admin/storage/lifecycle | The lifecycle rules, the switch Run lifecycle rules on schedule (storage.lifecycle.enabled), and Run each lifecycle rule every (storage.lifecycle.tick_interval) |
| Storage → Jobs | /_/admin/jobs | Every run, newest first: the runs of replication and lifecycle rules, and the one-off jobs. New job starts a re-encryption, a migration or a metadata backfill |
The Replication and Lifecycle pages#
- Rows. Each rule is a row with the columns Rule, Scope, Trigger, Status, Last run and Actions. Click a row to open the drawer of the rule. The drawer has the tabs Definition, Runs and Failures, and also Verify for a replication rule and Preview for a lifecycle rule.
- Status.
queuedreads "waiting to start" andcancellingreads "cancelling"; neither reads as running. A run in progress wins over a pause: a rule paused during a run reads "running · pauses after this run", because a pause stops the next run, not the run in progress. A run that a pause stopped reads "stopped · resumes where it left off", not "cancelled", because it kept its position. A run that the proxy stopped reads "deferred · continues shortly". - Progress. A rule with a run in progress shows a meter with its counts. Replication copies and deletes ("already in sync" for objects that need nothing), and lifecycle deletes, moves or copies. A lifecycle meter says why the run left the other objects alone: "not due" counts the objects that are not old enough yet, and "not matched" counts the objects that the rule's globs keep out. A run from a release before v2.0.18 does not have this split, so its meter says "skipped". A settled rule keeps the meter of its last run ("14 copied · 1 deleted"), with its failure count and last error. A replication rule also says when its last live copy (a copy made when an object changed, between runs) landed, and how many live copies, deletes and failures there were since the proxy started.
- Last run. The time at which the last run finished. During a run, the cell says how long the run has run. A scheduled rule also shows when it runs next. Relative times move between refreshes. All runs → opens the Jobs page with a filter for the rule.
- Actions. Pause or Resume, and Run now. A replication rule that is disabled or paused shows Run once instead of Run now: it runs the rule one time and does not enable or resume it. The "⋯" menu holds Clone, Remove rule… and, while a run is in progress, Kill run…. The page asks for confirmation before it removes a rule or kills a run. Remove rule… does not remove the rule at once. It stages the removal as an unapplied edit: the row shows a removing tag, and the rule keeps running. Review & apply removes the rule and its run history. Until you apply, the Keep this rule button in the row (or Discard) undoes the removal.
- Clone. Clone (in the "⋯" menu of the row and of the drawer) copies the rule, with its unsaved edits, into a new draft named
<name>-copy, and opens the draft. Change the buckets, the prefixes or the filters of the draft before you apply it. While two rules act on the same objects, the drawer warns, for example "Rule "releases-to-dr" has the same source, destination and filters". For a lifecycle rule, the warning compares the bucket, the prefix, the kind of action and the filters. - Unsaved edits. Each page has one editor and one bar with Review & apply. A rule with unapplied edits shows an edited tag. Run now and Preview wait only on that rule; the other rules keep them. A rule that you add with New replication rule, New lifecycle rule or Clone shows a draft tag until you apply it, and a draft with an empty name shows as "(unnamed)". The page keeps track of each rule that you edit by an identity of its own, not by its name, so the drawer stays on the rule while you type its name, also when the name passes through the name of another rule. The proxy knows a rule only by its name. For this reason, a rename shows the old rule with a removing tag and the new name as a draft: when you apply the rename, the proxy deletes the old rule with its run history and creates a rule with the new name. After the apply, the drawer closes, and the row of the rule flashes once, under its new name. The review dialog names the changed rule and field (
replication.rules["nightly-dr"].replicate_deletes). - Refresh. The page reads
GET /_/api/admin/jobsevery 2 seconds while a run is in progress, and every 10 seconds otherwise, so a run that another tab or the scheduler starts shows up within 10 seconds. After Review & apply, Resume or Run now, the page reads every 2 seconds until the run that follows is seen and finishes (at most 30 seconds when no run starts). The drawer's Runs and Failures tabs read again whenever a run starts or finishes, also a run that started and finished between two reads. - Runs and failures. Each run shows when it finished, how long it took, and the logical size of the data that it wrote. The Failures tab says "The 20 most recent of 1,000 failures" when the proxy recorded more than it lists, folds repeated error messages into a count, and wraps long errors. A replication failure says whether a run or a live copy hit it.
The Jobs page#
- The list. The page reads
GET /_/api/admin/jobs/runs. Each row is one run, newest first. Show older runs at the end of the list loads the next page. - Filters. All, Active and Failed select runs by status. Every kind and Every bucket narrow the list to one kind or one bucket. When you open the page from All runs → on a rule page, a chip names the rule; close the chip to see every rule again. The switch Show runs that changed nothing also lists the rule runs that copied, deleted, moved and failed nothing. It is off by default, because a rule that runs every hour makes such a run most hours. The filters are part of the URL, so a link opens the same view.
- Columns. Job shows the kind, and a title that tells one run from the next. A rule run is titled with the rule name and the number of the run ("nightly #12"). A bulk delete is titled with the first folder that it removes, and with how many more folders and objects it removes ("ror/e2e_reports/2026-09-25/ +2"). Its second line names those other folders. Any other one-off job is titled with its bucket. Started by says how the run started: Schedule, Run now, File browser · dana for a bulk delete, or Admin. Scope, Status and Started follow. Status shows the meter of the run, and a "3 failed" link that opens the failures of the run.
- Progress. A one-off in progress shows a meter with its counts. Backfill stamps ("already stamped"), re-encrypt re-encrypts ("already current"), a bulk delete deletes, and migrate names its phase (preparing, copying, verifying the copies, switching to the new backend, cleaning up). A finished one-off keeps its meter, so a job that stamped 4,003 objects and one that found nothing to do look different.
- Actions. The "⋯" menu of a row has Cancel… for a one-off in progress, Kill run… for a rule run in progress, and Remove… for a finished one-off. Each asks for confirmation first. A cancel asks first because a migration that is cancelled before the switch deletes the copies it made. Remove… deletes the job and its failures from the list, and it changes no object.
- Drawers. Click a rule run to open its run drawer. It shows the counts, the start, the end and the duration of the run, how it started, and the failures of that run. Open rule opens the rule on its page. Click a one-off to open its drawer. Its Definition tab shows its parameters (for a migration: the source and target backends, the destination mode, and what happens to the source copy), its counts, the data it wrote, and how long it ran. Cancel job… sits in that drawer while the job is active.
- Refresh. The list reads again every 2 seconds while a run in it is in progress, and every 10 seconds otherwise.
- Older links. A link from an older release to a rule on the Jobs page (
/_/admin/jobs?job=replication:releases-to-dr) opens the rule on its own page.
Busy banner#
The object browser polls GET /_/api/admin/jobs/bucket/:bucket every 10 seconds, and every 2 seconds while a job runs. Whether the banner says that uploads are unavailable, whether the Upload button shows, and whether the Buckets page shows a bucket as busy follow writes_blocked in that answer: the state of the write gate. A migration that is being cancelled, or that cleans up after the switch, no longer blocks writes while its job is still active, so the banner says that uploads and deletes work and the Upload button returns. Cancel on the Buckets page asks for confirmation, as on the Jobs page. After an expired session, the banner comes back without a reload once you sign in again.
Metadata backfill#
An object that reached the backend without the proxy (it was there before the proxy, or another tool wrote it) has none of the proxy's metadata: no content hash, no created-at. The proxy still serves it, but it cannot show or verify its checksum. The backfill-metadata job adds that metadata. It reads each such object once to compute its hashes and then rewrites only the metadata: an S3 backend does a server-side copy of the object onto itself, and a filesystem backend rewrites the extended attributes. The object bytes are not uploaded again, and objects that the proxy wrote are skipped.
A server-side copy does not carry the object's ACL or its Object Lock settings, so the proxy restates them on the copy. It reads the object's ACL first, and when the ACL grants more than the owner's full control, the copy sends the same grants. When the object has a retention period that has not ended, or a legal hold that is on, the copy sends the same retention mode, date and legal hold. When the backend does not answer the ACL request, the copy gets the bucket's default ACL, as before. When the backend answers that it does not implement object ACLs (status 501 or the error code NotImplemented), the proxy does not send the ACL request again for objects of that bucket for one hour.
The job reads each object with one HEAD request, and the copy uses the headers of that response instead of a second HEAD. The copy is conditional on the ETag of that response (x-amz-copy-source-if-match). When the object changed after the job read it, the copy fails, the object keeps its new content and metadata, and the job records the object as failed.
By default the job keeps the Last-Modified time that the proxy serves for each object, so sync tools and replication do not copy the objects again. Set refresh_last_modified: true to make the backfilled objects read as modified at the time of the job. A multipart object keeps the ETag that clients know. On an S3 backend this holds for HEAD and GET only. The server-side copy gives the stored object a new Last-Modified time and a new single-part ETag, and a LIST reports the stored values, because the proxy does not read its own metadata for every listed object. After a backfill on S3, a LIST therefore shows the time of the job, and a multipart object lists with a new ETag. A filesystem backend has no such difference. Start the job from Storage → Jobs (/_/admin/jobs) → New job → Backfill metadata…, or with the API row above.
The write gate#
While a migrate or backfill job is active, S3 writes (PUT, DELETE, POST, multipart) to that bucket return 503 SlowDown. A re-encrypt job does the same only on a backend without proven conditional writes. Every rewrite of a re-encrypt job is conditional on the object that the job read, so on a filesystem backend, or on an S3 backend whose conditional writes are proven, the job takes no gate and clients keep writing (details). AWS SDKs back off and retry automatically. Reads pass untouched. The gate starts when the proxy creates the job, so there is no window between creation and claim. The proxy drains in-flight writes before the copy starts, and it lifts the gate when the job finishes. For a migration, writes resume at the moment the bucket flips to the new backend, before any optional source cleanup. The embedded object browser shows a busy banner on gated buckets via GET /_/api/admin/jobs/bucket/:bucket. The 503 names the kind of the job that holds the bucket (a re-encryption, a migration to another backend, or a metadata backfill). The job row and the gate can differ for a short time: a cancelled migrate releases the source bucket at once, while it still removes its staged copies, and a migrate in its cleanup phase no longer gates the bucket. The writes_blocked field of the bucket status (at the top level and inside active) follows the gate.
Filter preview#
POST /_/api/admin/jobs/filter-preview shows what a rule's include and exclude globs do, before you save the rule. It runs the same matcher as replication and lifecycle rules. The request names a bucket, a prefix and the two lists:
{ "bucket": "releases", "prefix": "firmware/", "include_globs": [], "exclude_globs": ["*.tmp", "widget-3000/**"] }
The proxy lists every object under the prefix, up to 200,000 objects (limit sets a lower number), and classifies each one. Keys sort by name, so a sample of the first objects would miss globs for names that sort late, such as pre-release builds. A request with keys classifies those keys (at most 100) instead of a listing. The answer holds:
sampled, andtruncatedwhen the prefix holds more objects than the sample.- The counts
included,excluded,not_includedandinternal. Aninternalkey is a.deltaglider/key, on which no rule acts. globs: for each glob, itslist(includeorexclude), the number of sampled keys that itmatches, itserrorwhen it does not compile, and asuggestionwhen it matches nothing but the glob with the prefix in front of it matches.keys: each key with itsverdictand theglobthat decided it, at most 500 keys of each verdict. The counts cover every key.
The proxy keeps the listing for two minutes, and two previews of the same prefix share one listing, so a preview that runs on every edit lists the prefix once. A listing reads 1,000 keys per request, so a prefix of 100,000 objects costs 100 requests. The route needs an admin session.
Delete jobs#
The file browser deletes a selection with a server job of the kind delete. The browser starts the job with POST /_/api/admin/objects/delete-jobs (see the admin API), and the job then deletes on the proxy. Closing the browser tab therefore does not stop the delete, and a restart of the proxy resumes the job from its saved cursor.
A delete job differs from the other maintenance jobs in three ways:
- It does not hold its bucket. A re-encryption, a migration or a backfill holds its bucket, so the proxy refuses a second such job on the same bucket. A delete job holds nothing, so several delete jobs, and a delete job next to one of those jobs, can run on one bucket.
- It does not gate writes. Clients can still upload to the bucket while a delete job runs. When a job that gates writes (a re-encryption, for example) starts on the bucket, the delete job waits until that job ends, and then it continues from its cursor.
- It runs in a lane of its own. The other maintenance jobs run one at a time. Delete jobs run in a separate lane, four at a time, so deletes in buckets of different backends run in parallel, and a long re-encryption does not hold them up.
Each page of a delete job lists up to 1,000 keys of one selected folder, in the same way as an S3 LIST, and deletes them. The job acts as the user who started it, and the proxy checks that user again for every page. A user who is disabled or who loses the permission therefore stops the job, and a key that the user may not delete counts as a failure. The job saves its cursor after every page, so a restart resumes it at the next page. An admin can cancel any delete job, and a user can cancel the delete jobs that the user started. The keys that the job deleted before the cancel stay deleted.
Durability#
Maintenance jobs live in the encrypted jobs DB (deltaglider_jobs.db, the maintenance_jobs and failures tables) and the proxy re-queues them on boot. A proxy restart in the middle of a job therefore resumes the job and does not leave a half-migrated bucket behind. On a graceful stop (SIGTERM or SIGINT, which Kubernetes sends on every rolling deploy), the proxy waits up to 10 seconds for the running job to reach the end of its current object. The job then goes back to queued with its cursor, and the next boot resumes it at once. The write gate of the job stays armed during the stop, because the job is still active and the next boot continues it. The stop never settles or unwinds the job. After a hard kill, the next boot resumes the job only when its lease lapses (60 seconds by default, set by advanced.jobs.lease_ttl; see Job leases). Every maintenance job checks for a cancel, and saves its progress, every 20 objects, not only at the end of a 1,000-object page. On a filesystem destination, a migrate writes each copy without waiting for the disk, and it makes the copies durable in one batch every 20 objects, before it saves its cursor. A saved cursor therefore never points past a copy that a crash can lose. A resumed migrate copies its first page again instead of trusting the copies that it finds on the destination, because a crash can leave a copy with its metadata but without its data. Every other write (client PUTs, replication, lifecycle, re-encryption, reference.bin) still waits for the disk before it answers. A cancel before the routing flip, or a failure that the data causes (for example a copy on the destination that differs from the source), releases the write gate on the source bucket at once, deletes the copies that this job wrote to the destination (it finds them by the job id in their dg-migration metadata, so objects that the job did not write stay), and removes the staging route. A failure because a backend did not answer is different. A migrate sends a failed HEAD or listing request again a few times, and it never reads an error from the destination as a missing copy. When the backend still does not answer, the job fails but keeps the copies that it made, because the failure says nothing about them. The error message says so: a new migrate with "Make the destination an exact mirror" ("target": "mirror") reuses those copies, because it copies only the objects that differ. The source is never deleted on a failed or cancelled run. A stop can interrupt a cancel before the worker acts on it: the job then stays cancelling, and the next boot runs the cancel, with the same unwind, instead of marking the job cancelled without it. A migration that waits for its backend, or that a graceful stop handed back, is queued again, but it already made copies. A cancel of such a migration therefore turns it cancelling, and the worker runs the same unwind; only a job that never ran is cancelled at once. All three subsystems share the same leader-lease, failure-ring, and zombie-run machinery (replication's lease upgrades to a cross-instance S3 lease when a coordination bucket is configured; lifecycle and maintenance leases stay node-local), and all paginated work goes through one cursor state machine with crash-resume. When a page listing fails because the backend is slow, breaks off, or answers with a 5xx status, the job lists the same page again with the same cursor, up to 5 times with a growing pause. Only a listing that the backend refuses as invalid, on the first page of a resumed run, restarts the run from the top, and only once.
A migrate or backfill job works on 4 objects of a page at the same time, and a re-encrypt job works on 8. Each saves its cursor only after every object of the page ends. The total of a re-encrypt or backfill progress bar comes from the bucket's usage counter when a refresh scanned the bucket. Otherwise the job counts the objects with a second listing of the bucket that runs next to its work, and the bar becomes determinate when that count ends. The count costs one LIST request per 1,000 objects, which is small next to the one or more requests per object of the job. A migrate counts the source bucket the same way during its copy phase. Its percent covers the copy as 0 to 49 and the verify phase as 50 to 98, and the flip and the cleanup show 99. The row of a running job reports its phase (stage, copy, verify, flip or cleanup for a migrate; objects for a backfill; objects, references and the optional purge-versions for a re-encrypt), and a completed row reports none. A re-encrypt job finds the delta reference.bin files during the same listing. Only a job that resumes in the middle of the bucket lists the bucket again to find them. While the backend health check marks a backend of the job as unavailable (see How to diagnose backend connectivity), the job sends no requests to that backend. The job goes back to queued with its cursor and the cause in its error, and no worker claims it again for 30 seconds. Its write gate stays armed while it waits.
Related#
- Replication: rule shape, triggers, conflict policies.
- Lifecycle rules: expiration and transition rule shape, guardrails.
- About encryption at rest: what re-encryption rewrites.
- Admin API: the full endpoint table.
- About jobs, write gates, and durability
- How to move a bucket to another backend
- How to rotate or change encryption keys