Skip to content

feat(s3): serve the bucket policy operations through versitygw's handlers - #194

Merged
pyropy merged 38 commits into
srdjan/feat/iam-hilt-error-mappingsfrom
srdjan/feat/iam-bucket-policy-ops
Oct 14, 2026
Merged

pyropy merged 38 commits into
srdjan/feat/iam-hilt-error-mappingsfrom
srdjan/feat/iam-bucket-policy-ops

Conversation

@pyropy

@pyropy pyropy commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Serves GET, PUT and DELETE /{bucket}?policy through versitygw's own route table, auth and controllers. ingot's backend forwards each operation to Hilt's /s3/bucket/policy. The policy document is now an AWS bucket policy (fil-forge/hilt#96), so versitygw's PutBucketPolicy validator accepts it and rejects anything that isn't AWS-shaped before Hilt sees it. Hilt's stricter checks still decide.

  • s3frontend/policy.go: PutBucketPolicy, GetBucketPolicy and DeleteBucketPolicy forward the signed request from reqscope. GetBucketPolicy forwards only a signed GET ?policy. versitygw's own access checks get NoSuchBucketPolicy, so they never reach Hilt or evaluate a policy, and an anonymous request is AccessDenied.
  • server.go: a policyHeaders middleware sets the ETag Hilt returned and application/json on a GET, because the backend interface returns no headers. Preconditions reach Hilt in the signed request.
  • iam: GetUserAccount resolves arn:aws:iam:::user/<id>, so versitygw's validator accepts ARN principals. Hilt checks that the principal exists.
  • policy_routes.go and its test are deleted.
  • itest/forge_bucket_policy_test.go sends AWS-shaped documents and covers a bare-name principal (refused by versitygw) and a missing Version (refused by Hilt).

A policy request now reaches Hilt twice: versitygw's auth calls /s3/request/authorize, then the backend calls /s3/bucket/policy.

Change log

e2e: the policies-local suite passed against a fresh smelt running hilt#96 and ingot#194, with the console from fil-one/fil-one#812 (102 of 103 passed; PU3 was flaky on its first attempt while the fresh Owner org was still unnamed).

References

🤖 Generated with Claude Code

@pyropy

pyropy commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-12T18:03:54.955978Z 39d9737 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 06b3229ff3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread policy_routes.go Resolved
@pyropy
pyropy force-pushed the srdjan/feat/iam-bucket-policy-ops branch from 06b3229 to 2a810d4 Compare September 30, 2026 15:22
@pyropy
pyropy force-pushed the srdjan/feat/iam-bucket-policy-ops branch 2 times, most recently from 7dd416b to 51b19bd Compare October 1, 2026 12:11
@pyropy

pyropy commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@pyropy
pyropy force-pushed the srdjan/feat/iam-bucket-policy-ops branch from 51b19bd to bed7fc2 Compare October 1, 2026 12:15

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 51b19bdd05

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread inmem/store.go Resolved
@pyropy
pyropy force-pushed the srdjan/feat/iam-bucket-policy-ops branch from d68d3f3 to 4f776ff Compare October 1, 2026 12:58
@pyropy
pyropy force-pushed the srdjan/feat/iam-bucket-policy-ops branch from 4f776ff to 919e46f Compare October 1, 2026 14:06
@pyropy

pyropy commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: 919e46f58b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@pyropy
pyropy force-pushed the srdjan/feat/iam-bucket-policy-ops branch from 919e46f to 1443e96 Compare October 1, 2026 14:24
@pyropy
pyropy force-pushed the srdjan/feat/iam-bucket-policy-ops branch 2 times, most recently from 3c66ea4 to e66184b Compare October 1, 2026 15:18
pyropy added a commit to fil-one/fil-one that referenced this pull request Oct 1, 2026
The gateway renders a refused x-bucket-policy header with the code a
refused PutBucketPolicy body gets, MalformedPolicy, from
fil-forge/ingot#194 on; InvalidArgument stays accepted until that is on
main.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@pyropy
pyropy force-pushed the srdjan/feat/iam-bucket-policy-ops branch from e66184b to 7db9029 Compare October 2, 2026 11:19
@pyropy
pyropy marked this pull request as ready for review October 2, 2026 11:19
pyropy and others added 12 commits October 14, 2026 13:23
…d answered

An object GET whose query carries a policy key kept being labeled
application/json. The middleware now requires the ETag holder the policy
backend fills.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/s3/bucket/policy now returns the policy as a libforge BucketPolicy.
GetBucketPolicy marshals it to the AWS JSON document, the same bytes
Hilt stores.

Pins hilt ee26a9b (#96 branch) and libforge 12ca555 (#80 branch),
temporary pins to re-pin before merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The InvalidBucketPolicy error name and the ARN helpers the policy itest
uses now come from libforge's commands/s3/bucket, where Hilt moved them.

Pins hilt 24fef2d (#96 branch) and libforge 64602c9 (#80 branch),
temporary pins to re-pin before merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… on the wire)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ngSet)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n hilt 23320b8d, libforge 44def4f

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Ingot tags the document itself with libforge's ETag, the same function Hilt
stores and checks If-Match with: a GET tags the document Hilt returned, a
PUT the body Hilt accepted. The /s3/bucket/policy receipt carries no tag.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ge 222cd95

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@pyropy
pyropy force-pushed the srdjan/feat/iam-bucket-policy-ops branch from f098999 to 1b99c33 Compare October 14, 2026 11:24
@pyropy
pyropy merged commit 1c47c14 into main Oct 14, 2026
20 checks passed
@pyropy
pyropy deleted the srdjan/feat/iam-bucket-policy-ops branch October 14, 2026 11:39
fil-forge-bot Bot added a commit to fil-forge/infra-nodes that referenced this pull request Oct 14, 2026
Published from fil-forge/ingot#194

- Digest: `sha256:0fefac05d4bf51eac58b1ce4a20e938ec0cdfcdf112b1835bfc93c198a197d32`
- Commit: fil-forge/ingot@1c47c14
- Publish run: https://github.com/fil-forge/ingot/actions/runs/38558109059

Merging rewrites [`nodes/dev/apps/versions.env`](https://github.com/fil-forge/infra-nodes/blob/main/nodes/dev/apps/versions.env) and queues the image. The node picks
it up on its next reconcile pass, waits for a safe proving window and then
restarts the service, so the deploy happens well after this merges.

Co-authored-by: fil-forge-bot[bot] <318653112+fil-forge-bot[bot]@users.noreply.github.com>
fil-forge-bot Bot added a commit to fil-forge/infra-nodes that referenced this pull request Oct 14, 2026
Sets every staging pin in `nodes/staging/eu-central-3/apps/versions.env` to what
`nodes/dev/apps/versions.env` pins on `main`. Merging this is the promotion: the node picks
it up on its next reconcile pass, waits for a safe proving window and restarts each
service that changed.

This pull request is rebuilt on every push to `main` and closed once staging matches
dev, so edits to it are overwritten unless they are pushed to `bot/promote-staging` by hand.

## ingot

[`sha256:4fdff18`](https://github.com/orgs/fil-forge/packages/container/ingot/1378629238) → [`sha256:d686c7d`](https://github.com/orgs/fil-forge/packages/container/ingot/1385513652)

What fil-forge/ingot brings over staging's pin, [`6071feb..431ad37`](fil-forge/ingot@6071feb...431ad37):

- fil-forge/ingot#177 ([66e9659](fil-forge/ingot@66e9659))
- fil-forge/ingot#178 ([5b3e0b6](fil-forge/ingot@5b3e0b6))
- fil-forge/ingot#194 ([1c47c14](fil-forge/ingot@1c47c14))
- fil-forge/ingot#259 ([431ad37](fil-forge/ingot@431ad37))
fil-forge-bot Bot added a commit to fil-forge/infra-nodes that referenced this pull request Oct 14, 2026
Sets every staging pin in `nodes/staging/eu-central-3/apps/versions.env` to what
`nodes/dev/apps/versions.env` pins on `main`. Merging this is the promotion: the node picks
it up on its next reconcile pass, waits for a safe proving window and restarts each
service that changed.

This pull request is rebuilt on every push to `main` and closed once staging matches
dev, so edits to it are overwritten unless they are pushed to `bot/promote-staging` by hand.

## ingot

[`sha256:4fdff18`](https://github.com/orgs/fil-forge/packages/container/ingot/1378629238) → [`sha256:e890d57`](https://github.com/orgs/fil-forge/packages/container/ingot/1385622164)

What fil-forge/ingot brings over staging's pin, [`6071feb..8a9e399`](fil-forge/ingot@6071feb...8a9e399):

- fil-forge/ingot#177 ([66e9659](fil-forge/ingot@66e9659))
- fil-forge/ingot#178 ([5b3e0b6](fil-forge/ingot@5b3e0b6))
- fil-forge/ingot#194 ([1c47c14](fil-forge/ingot@1c47c14))
- fil-forge/ingot#259 ([431ad37](fil-forge/ingot@431ad37))
- fil-forge/ingot#260 ([8a9e399](fil-forge/ingot@8a9e399))
fil-forge-bot Bot added a commit to fil-forge/infra-nodes that referenced this pull request Oct 14, 2026
Sets every staging pin in `nodes/staging/eu-central-3/apps/versions.env` to what
`nodes/dev/apps/versions.env` pins on `main`. Merging this is the promotion: the node picks
it up on its next reconcile pass, waits for a safe proving window and restarts each
service that changed.

This pull request is rebuilt on every push to `main` and closed once staging matches
dev, so edits to it are overwritten unless they are pushed to `bot/promote-staging` by hand.

## ingot

[`sha256:4fdff18`](https://github.com/orgs/fil-forge/packages/container/ingot/1378629238) → [`sha256:24f1dfd`](https://github.com/orgs/fil-forge/packages/container/ingot/1385707121)

What fil-forge/ingot brings over staging's pin, [`6071feb..7fab0fb`](fil-forge/ingot@6071feb...7fab0fb):

- fil-forge/ingot#177 ([66e9659](fil-forge/ingot@66e9659))
- fil-forge/ingot#178 ([5b3e0b6](fil-forge/ingot@5b3e0b6))
- fil-forge/ingot#194 ([1c47c14](fil-forge/ingot@1c47c14))
- fil-forge/ingot#259 ([431ad37](fil-forge/ingot@431ad37))
- fil-forge/ingot#260 ([8a9e399](fil-forge/ingot@8a9e399))
- fil-forge/ingot#258 ([7fab0fb](fil-forge/ingot@7fab0fb))
fil-forge-bot Bot added a commit to fil-forge/infra-nodes that referenced this pull request Oct 14, 2026
Sets every staging pin in `nodes/staging/eu-central-3/apps/versions.env` to what
`nodes/dev/apps/versions.env` pins on `main`. Merging this is the promotion: the node picks
it up on its next reconcile pass, waits for a safe proving window and restarts each
service that changed.

This pull request is rebuilt on every push to `main` and closed once staging matches
dev, so edits to it are overwritten unless they are pushed to `bot/promote-staging` by hand.

## ingot

[`sha256:4fdff18`](https://github.com/orgs/fil-forge/packages/container/ingot/1378629238) → [`sha256:aa89c75`](https://github.com/orgs/fil-forge/packages/container/ingot/1385968292)

What fil-forge/ingot brings over staging's pin, [`6071feb..7b28323`](fil-forge/ingot@6071feb...7b28323):

- fil-forge/ingot#177 ([66e9659](fil-forge/ingot@66e9659))
- fil-forge/ingot#178 ([5b3e0b6](fil-forge/ingot@5b3e0b6))
- fil-forge/ingot#194 ([1c47c14](fil-forge/ingot@1c47c14))
- fil-forge/ingot#259 ([431ad37](fil-forge/ingot@431ad37))
- fil-forge/ingot#260 ([8a9e399](fil-forge/ingot@8a9e399))
- fil-forge/ingot#258 ([7fab0fb](fil-forge/ingot@7fab0fb))
- fil-forge/ingot#257 ([8f9fc3f](fil-forge/ingot@8f9fc3f))
- fil-forge/ingot#261 ([7b28323](fil-forge/ingot@7b28323))
fil-forge-bot Bot added a commit to fil-forge/infra-nodes that referenced this pull request Oct 14, 2026
Sets every staging pin in `nodes/staging/eu-central-3/apps/versions.env` to what
`nodes/dev/apps/versions.env` pins on `main`. Merging this is the promotion: the node picks
it up on its next reconcile pass, waits for a safe proving window and restarts each
service that changed.

This pull request is rebuilt on every push to `main` and closed once staging matches
dev, so edits to it are overwritten unless they are pushed to `bot/promote-staging` by hand.

## ingot

[`sha256:4fdff18`](https://github.com/orgs/fil-forge/packages/container/ingot/1378629238) → [`sha256:f629535`](https://github.com/orgs/fil-forge/packages/container/ingot/1386342616)

What fil-forge/ingot brings over staging's pin, [`6071feb..6331454`](fil-forge/ingot@6071feb...6331454):

- fil-forge/ingot#177 ([66e9659](fil-forge/ingot@66e9659))
- fil-forge/ingot#178 ([5b3e0b6](fil-forge/ingot@5b3e0b6))
- fil-forge/ingot#194 ([1c47c14](fil-forge/ingot@1c47c14))
- fil-forge/ingot#259 ([431ad37](fil-forge/ingot@431ad37))
- fil-forge/ingot#260 ([8a9e399](fil-forge/ingot@8a9e399))
- fil-forge/ingot#258 ([7fab0fb](fil-forge/ingot@7fab0fb))
- fil-forge/ingot#257 ([8f9fc3f](fil-forge/ingot@8f9fc3f))
- fil-forge/ingot#261 ([7b28323](fil-forge/ingot@7b28323))
- fil-forge/ingot#263 ([6331454](fil-forge/ingot@6331454))
pyropy added a commit to fil-one/fil-one that referenced this pull request Oct 15, 2026
The gateway renders a refused x-bucket-policy header with the code a
refused PutBucketPolicy body gets, MalformedPolicy, from
fil-forge/ingot#194 on; InvalidArgument stays accepted until that is on
main.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
bajtos pushed a commit to fil-forge/infra-nodes that referenced this pull request Oct 15, 2026
Sets every staging pin in `nodes/staging/eu-central-3/apps/versions.env` to what
`nodes/dev/apps/versions.env` pins on `main`. Merging this is the promotion: the node picks
it up on its next reconcile pass, waits for a safe proving window and restarts each
service that changed.

This pull request is rebuilt on every push to `main` and closed once staging matches
dev, so edits to it are overwritten unless they are pushed to `bot/promote-staging` by hand.

## ingot

[`sha256:4fdff18`](https://github.com/orgs/fil-forge/packages/container/ingot/1378629238) → [`sha256:f629535`](https://github.com/orgs/fil-forge/packages/container/ingot/1386342616)

What fil-forge/ingot brings over staging's pin, [`6071feb..6331454`](fil-forge/ingot@6071feb...6331454):

- fil-forge/ingot#177 ([66e9659](fil-forge/ingot@66e9659))
- fil-forge/ingot#178 ([5b3e0b6](fil-forge/ingot@5b3e0b6))
- fil-forge/ingot#194 ([1c47c14](fil-forge/ingot@1c47c14))
- fil-forge/ingot#259 ([431ad37](fil-forge/ingot@431ad37))
- fil-forge/ingot#260 ([8a9e399](fil-forge/ingot@8a9e399))
- fil-forge/ingot#258 ([7fab0fb](fil-forge/ingot@7fab0fb))
- fil-forge/ingot#257 ([8f9fc3f](fil-forge/ingot@8f9fc3f))
- fil-forge/ingot#261 ([7b28323](fil-forge/ingot@7b28323))
- fil-forge/ingot#263 ([6331454](fil-forge/ingot@6331454))

Co-authored-by: fil-forge-bot[bot] <318653112+fil-forge-bot[bot]@users.noreply.github.com>
pyropy added a commit to fil-one/fil-one that referenced this pull request Oct 15, 2026
The gateway renders a refused x-bucket-policy header with the code a
refused PutBucketPolicy body gets, MalformedPolicy, from
fil-forge/ingot#194 on; InvalidArgument stays accepted until that is on
main.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
pyropy added a commit to fil-one/RFC that referenced this pull request Oct 15, 2026
<!-- rfc-preview-link:start -->
[📖
Preview](https://github.com/fil-one/RFC/blob/srdjan%2Frfc-aws-policy-document/rfcs/2026-09-forge-s3-tenant-iam.md)
<!-- rfc-preview-link:end -->

The Forge bucket policy becomes an AWS bucket policy document, so AWS
tooling can build and check it and Ingot can serve the policy operations
through versitygw's own S3 handlers instead of answering them ahead of
versitygw's route table.

The changes in the tenant IAM RFC:

- The document gains `Version` and `Resource`. Principals are written
`arn:aws:iam:::user/<principalId>` with an empty account field, because
the bucket fixes the tenant. `{"AWS": "*"}` is a second spelling of
`"*"`.
- `Resource` names the whole bucket: the bucket ARN for `s3:ListBucket*`
and the `/*` ARN for object actions. Forge grants authority per bucket,
so key prefixes are refused.
- The refusal list now covers `Version`, `Resource`, ARN shape,
`Condition` and `NotPrincipal`.
- The canonical form and the ETag cover `Version` and `Resource`.
Compare-and-set is unchanged.
- The Ingot section says the operations go through versitygw's handlers
and that versitygw's own validator runs before Hilt's.
- The default Owner and Admin statements are gone. The console writes
one statement naming a bucket's creator under an ordinary label, a role
change touches no policy, the migration creates principals only, and the
orchestrator serving a region fixes its access model.

## References

- #30, the RFC this amends.
- fil-forge/ingot#194, the review thread that asked for AWS conformance.
- fil-forge/hilt#96, Hilt's implementation.
- fil-one/fil-one#696, the bucket access ADR. Its document shape is
updated on the console stack.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
pyropy added a commit to fil-one/fil-one that referenced this pull request Oct 16, 2026
…cket (#742)

* feat(backend): send the bucket's first policy in x-bucket-policy on create

On a region serving the `iam` access model, the create request carries
the bucket's first policy in the signed `x-bucket-policy` header, as the
tenant IAM RFC specifies: the storage system validates the document,
creates the bucket and stores the policy together, so no bucket outlives a
failed policy write. The header is added by a `build`-step middleware on
the one command, so SigV4 signs it and nothing else the client sends
carries it.

The document names the org's Owners with every action, and its Admins and
the creator with every action but the two retention writes. A refused
document answers 400 with the storage system's message; no bucket exists.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(backend): a refused create-time policy arrives as MalformedPolicy

The gateway renders a refused x-bucket-policy header with the code a
refused PutBucketPolicy body gets, MalformedPolicy, from
fil-forge/ingot#194 on; InvalidArgument stays accepted until that is on
main.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(backend): a create-time policy is audited as created when configuration fails

The create stores the bucket and its policy together; versioning and
retention setup run after it. A BucketConfigurationError therefore means
the policy exists, so the bucket_policy.created completion records it as
succeeded. The 500 response stays.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(backend): name the create-time policy helper getOrgDefaultPolicy

firstPolicyFor read as "the first of several"; the helper builds the
org's default policy for a new bucket, so it is named for that, as the
review asked.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(backend): name the create-time audit helper auditDefaultBucketPolicy

It records the default policy the console writes with a new bucket, and
its name now says so, matching getOrgDefaultPolicy beside it, as the
review asked.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(backend): count a create-time policy's statements by their AWS name

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(backend): write a new bucket's first policy after the create

RFC#30 drops the x-bucket-policy header. The console creates the bucket,
then writes the default policy with PutBucketPolicy and If-None-Match: *
before the versioning and object-lock calls, and answers the member after
both. A refused document still answers 400; any other failure of the
write answers 500.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(backend): name the first policy write and its tests by what they do

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(backend): write the first bucket policy through the shared write retry

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(backend): write a bucket's first policy from the one Forge orchestrator

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(backend): start a new bucket's policy with its creator alone

The first policy names the member who created the bucket and nobody
else, whatever their role. No member list is read on create.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(backend): name members and principals by UUID in the bucket creation tests

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(backend): inline the first-policy helpers

The create-bucket handler picks the creator statement where the region
serves policies, and createBucket writes it under If-None-Match: * with
the usual retry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants