Repository navigation
Conversation
a9b7317 to
7683bf3
Compare
7683bf3 to
9b4bd1c
Compare
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9b4bd1c26b
ℹ️ 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".
9b4bd1c to
0d70c0a
Compare
0d70c0a to
df66705
Compare
df66705 to
4041a5f
Compare
4041a5f to
f05025a
Compare
f05025a to
cf9d7ab
Compare
4f27216 to
06f29c8
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 06f29c8508
ℹ️ 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".
06f29c8 to
b4bac7d
Compare
b4bac7d to
a18de58
Compare
262ed28 to
20f32e2
Compare
20f32e2 to
879b80d
Compare
879b80d to
5ea3fa3
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 72cd621124
ℹ️ 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".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 939a8d09ba
ℹ️ 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".
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
PostgreSQL policy and delegation updates can commit independently, leaving authorization inconsistent after a late policy-write failure.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Adds bucket-policy business logic, principal policy/access reads, and CLI support.
Changes:
- Implements policy CRUD, validation, and delegation rotation.
- Adds principal policy/access API and management client methods.
- Adds
hilt client policy list|access.
| File | Description |
|---|---|
pkg/store/principal/principal.go |
Adds principal ID collection helper. |
pkg/store/errors.go |
Centralizes contention detection. |
pkg/fx/api.go |
Wires policy services and routes. |
pkg/client/management/management.go |
Adds principal policy/access requests. |
pkg/client/management/management_test.go |
Tests new client methods. |
pkg/api/types.go |
Defines policy/access response types. |
pkg/api/service/principal/service.go |
Uses shared contention detection. |
pkg/api/service/bucketpolicy/service.go |
Implements policy operations and rotation. |
pkg/api/service/bucketpolicy/service_test.go |
Covers policy service behavior. |
pkg/api/service/bucketpolicy/errors.go |
Defines policy service errors. |
pkg/api/policies.go |
Adds principal read handlers. |
pkg/api/policies_test.go |
Tests policy read routes. |
cmd/client/root.go |
Registers policy commands. |
cmd/client/policy/root.go |
Defines policy command group. |
cmd/client/policy/principal.go |
Implements list/access commands. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
GET, PUT and DELETE /tenants/:tenantId/buckets/:bucketName/policy manage a bucket's policy document. GET and PUT answer with a strong ETag over the canonical encoding, and every write states the version it expects: If-Match with that tag replaces or deletes, If-None-Match: * writes the first policy. A tag that does not match the stored one is 412 and writes nothing. A write carrying neither header, both, or an If-None-Match other than *, is 400; 428 is not used. A document naming a principal the tenant does not have, an action outside the policy vocabulary, or no statement at all is 422. A bucket that does not exist or belongs to another tenant is 404, and so is a bucket with no policy, under its own error name so a caller can tell the two apart. The mappers put that name in the body as code, as the tenant and access-key mappers do. Two reads answer from the same documents. GET /tenants/:tenantId/principals/:userId/policies lists every policy with a statement naming the principal or the wildcard, through the store's index. GET /tenants/:tenantId/principals/:userId/access reports the actions the principal holds on each bucket, omitting the buckets it cannot reach. Both are computed from the store on every call and make no network call. PUT and DELETE hand the store a callback that compares the old and new documents over the tenant's principals, the wildcard expanding to all of them, locks the row of every principal whose actions changed, and rotates the delegations of each of their keys over the bucket: the delegations the key held over the bucket are revoked through the revocation service, in one request for the whole write, and the ones the new actions map to are stored in their place, so the gateway drops what it cached for the key and re-reads the policy on its next request. A principal's first grant on the bucket revokes nothing. The callback runs inside the store's transaction, so the document commits only once the rotation has succeeded and a failed publish leaves the old document and the old delegations in place. The principal rows are held so a key created for one of them meanwhile is either rotated or created from the committed policy. The rotation runs under one 8 s deadline, below the 10 s a share-locked reader of the bucket waits, so a wildcard policy on a tenant with many principals cannot lock the bucket's data path out. The callback lists the tenant's principals itself, under the bucket lock, so a principal created while the write was under way is rotated too. The management client gains the policy calls, which carry and return the ETag, and `hilt client policy` gets get, create, replace, delete, list and access. A create answers 201 and a replace 200, which the RFC leaves open. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The memory principal store now bounds the Lock wait, so the cycle this test pins resolves the way it resolves on Postgres: the policy write waits on the principal the removal holds, gives up with ErrConcurrentChange, and the removal finishes. The test required both to succeed, which only ever held on memory. TestPrincipalStorePostgresLockTimeout pins the opposite for Postgres, so the two backends now agree. What the test is for is unchanged: the two writes must not wedge. Shortening LockWait keeps it at a tenth of a second instead of sitting out the bound. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bucket policies are read and written over S3 (GetBucketPolicy, PutBucketPolicy, DeleteBucketPolicy) and reach Hilt as /s3/bucket/policy invocations, so the partner-key routes, their client methods and the CLI commands go. The policy service stays, gains an Unconditional write option for a PutBucketPolicy without a precondition, and still serves the two principal reads. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…l to both finish The removal now strips its policies before it locks the principal, so the two no longer wait on each other. The test goes back to requiring both writes to succeed, which is the contract, and no longer shortens the memory principal store's lock wait to break a cycle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The write validated its document against a principal list read before the store took the bucket, and only the Postgres store checked the named principals again. On memory a principal removed in that gap stayed named in the committed policy, and re-creating the id inherited its access. The store callback now requires every principal the document names to be in the list it re-reads under the bucket lock, so both backends refuse the write as an invalid policy. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
writeError mapped every invalid-argument failure from the store to an invalid policy naming a removed principal. A bucket deleted between the lookup and the write is now reported as a missing bucket when the store returns record-not-found for it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nd publisher The principal service now revokes and deletes a removed principal's keys in one delegation write, and takes the delegation store and the revocation publisher to do it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The principal routes unescape the parameter, since Echo hands it over as it came and the management client escapes each path segment. The two policy read routes still passed it raw, so a principalId holding "/" was looked up as the literal "a%2Fb" and answered 404. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…outes The management policy routes went with the move to the S3 operations; the constant naming their path stayed and staticcheck reports it unused. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…write's grants On Postgres the add waits on the tenant lock the write holds, so a key created for the principal afterwards reads the committed policy. The callback comment credited the principal's first authorize with waiting on the bucket lock, which a listing across buckets cannot take; the tenant lock is what orders the two. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…icy routes' tenantId The package comment documented a "policy" package that does not exist. The principal policy read routes pass the tenantId through tenantParam like every other tenant route. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… key created meanwhile waits for it Two Postgres tests pin what the joined transaction gives the policy write. A write cancelled after its rotation ran and its principal lock call returned, before the document is written, leaves the old document with its own grants: the rotation's delegation writes rolled back with it, where they used to commit on their own and leave the stored policy paired with another policy's grants. A key created for an existing principal while the first policy naming it is written, parked at the same point, waits on the principal row the write holds until its commit and is then created from the committed policy, which closes the gap filed as FIL-1389. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tore's fn renames Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…access reads The principal routes now refuse an id holding "/" before the handler runs, so the access and policies reads answer InvalidPrincipalID for one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bucket policy service returns plain types; the handlers map them to the PrincipalPolicy and BucketAccess wire structs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ListIDsByTenant replaces the ExternalIDs helper, so a policy write reads only external_id instead of whole principal rows it then discards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>


Adds the bucket policy service and the two principal reads computed from policies. A write rotates the affected principals' key delegations inside the store transaction, so the document commits only once revocations are published.
Get,Put,Write,Delete, with anUnconditional()optionGET .../principals/{id}/policiesand.../accesshilt client policy list|access🤖 Generated with Claude Code