Skip to content

Commit fe96c88

Browse files
authored
adapter/sqs: AdminPeekQueue backend (Phase 3) (#794)
## Summary Phase 3 of `docs/design/2026_05_16_proposed_admin_purge_queue.md`: backend `AdminPeekQueue` method on `*adapter.SQSServer`. - Returns up to `opts.Limit` currently-visible messages (`visible_at <= now`); pure read, no receive-count bump, no receipt-handle minting, no visibility-timer change. - Partitioned FIFO support via rotated sequential scanning across partitions, reusing the receive path's `receiveFanoutCounters` for fairness. - Versioned JSON + base64url cursor pins `Generation`, `StartPartition`, `Partition`, and `LastKey`; stale-generation cursors are rejected so a purge between pages forces the SPA to refresh from the front. - `AdminRole.canRead()` — peek requires non-zero role; read-only principals can triage, purge stays on `canWrite()`. - Pre-existing `goconst` regression in `sqs_messages.go` (the validator switch's inline `"String"` / `"Number"` literals) fixed by extracting `sqsAttributeBaseTypeString` / `sqsAttributeBaseTypeNumber` alongside the existing `sqsAttributeBaseTypeBinary`. **Caller audit (semantic-change rule):** no existing function signatures changed. `canRead` is a new method; `AdminPeekQueue` is a new entrypoint. The validator switch's constant extraction is a literal-for-named rename (byte-identical wire behaviour). ## Behavior change - New `AdminPeekQueue` admin method. Not yet wired to an HTTP handler (Phase 4) — this PR only adds the adapter surface. - No change to SigV4 paths, `AdminPurgeQueue`, or `AdminDescribeQueue`. ## Risk Low. Pure-read entry; the only writes I touch are the constant rename in `sqs_messages.go` (byte-identical to the prior inline literals). The cursor codec is hard-capped at 512 bytes after encoding; oversize cursors surface as `ErrAdminSQSValidation` from the decoder. ## Self-review (5 passes) 1. **Data loss** — Read-only entry. The constant extraction in `sqs_messages.go` is a literal-for-named rename with no behaviour change. 2. **Concurrency** — Leader-only via `isVerifiedSQSLeader`. Single `nextTxnReadTS` threaded into `loadQueueMetaAt`, `ScanAt`, and per-row `GetAt`, so the page is a consistent MVCC snapshot. The cursor's stored Generation is checked against the live meta on every continuation page — a purge between pages produces `ErrAdminSQSValidation` rather than rows from a purged generation. 3. **Performance** — `O(opts.Limit)` `ScanAt` round-trips + per-row `GetAt`. Hard caps: `Limit <= 100`, body <= 256 KiB. Cursor is bounded at 512 bytes. No hot-path allocations. 4. **Consistency** — Cursor encodes `Generation`; mismatch surfaces as `ErrAdminSQSValidation`. Partition rotation uses the same `receiveFanoutCounters` the receive path uses, so peek's partition fairness matches the data plane's. Data records are read via `sqsMsgDataKeyDispatch`, so partitioned FIFO data keys are routed to the same partition the vis-index entry was found under. 5. **Test coverage** — 22 new tests (happy path / no-bump-on-peek / truncation / Limit clamp / cursor round-trip / stale-gen / four malformed-cursor classes / read-only allowed / role-less denied / missing queue / empty name / delayed-message hidden / FIFO attribute projection / typed MessageAttribute (String+Binary) round-trip / partitioned FIFO pagination / cursor codec round-trip+empty+oversize / preparePeekCursor fresh+stale / clampPeekLimit+clampPeekBodyBytes truth tables / projectPeekedAttributes nil-safety / canRead truth table). `go test -race` clean. ## Test plan - [x] `go test -race -count=1 ./adapter/...` (targeted: `TestAdminPeekQueue*`, `TestPeekCursorCodec*`, `TestClampPeek*`, `TestPreparePeekCursor*`, `TestEncodePeekCursor*`, `TestProjectPeekedAttributes*`, `TestAdminRole_CanRead`, `TestAdminPurge*`, `TestAdminQueueSummary*`, `TestSQSServer_PartitionedFIFO*`, `TestSQSServer_Send*`) - [x] `make lint` clean on `./adapter/...` (0 issues) - [ ] CI on this PR ## Out of scope (follow-ups) - **Throttle integration**: dedicated per-queue admin-peek bucket (`bucketActionAdminPeek` + `resolveActionConfig` explicit case + `*adminPeekThrottledError` + `*PeekThrottledError`). Leader-only + `Limit <= 100` already bound the steady-state cost; the dedicated bucket adds a per-queue cap that should land alongside the SPA wiring so the metric has a real consumer. - **Phase 4**: HTTP handler at `GET /admin/api/v1/sqs/queues/{name}/messages`, bridge in `main_admin.go`, integration tests. - **`principalForReadSensitive` live `RoleStore` re-check** (design doc Goal 8): blocked on the wider `RoleStore` plumbing — neither `AdminPurgeQueue` nor `AdminDeleteQueue` do a live re-check today; peek will inherit that pattern when the broader work lands. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Admin API to peek SQS queue messages without modifying them: pagination with continuation tokens, configurable body truncation while reporting original size, full message attribute visibility, supports standard and FIFO queues. * **Bug Fix / Permissions** * Read-only admin roles are now allowed to perform non-destructive admin reads (peek). * **Polish** * Improved message-attribute typing/handling for more reliable attribute projection. <!-- review_stack_entry_start --> [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/bootjp/elastickv/pull/794?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2 parents c9a7870 + ddb4e6e commit fe96c88

4 files changed

Lines changed: 1714 additions & 1 deletion

File tree

adapter/dynamodb_admin.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,16 @@ const (
8383
// future "delete-only" tier reads consistently across the package.
8484
func (r AdminRole) canWrite() bool { return r == AdminRoleFull }
8585

86+
// canRead reports whether the role authorises non-destructive but
87+
// sensitive reads — e.g. SQS AdminPeekQueue, which exposes message
88+
// bodies / attributes. Both AdminRoleReadOnly and AdminRoleFull
89+
// satisfy this gate; the zero value (unauthenticated / role-less
90+
// principal) does not. List / Describe paths use the looser
91+
// session-auth gate because their output is queue metadata already
92+
// shown on the SPA list page; peek is divergent because the payload
93+
// is the message bodies themselves.
94+
func (r AdminRole) canRead() bool { return r == AdminRoleReadOnly || r == AdminRoleFull }
95+
8696
// AdminPrincipal is the authentication context every admin write
8797
// entrypoint takes. The adapter re-evaluates authorisation against
8898
// this principal *itself* — it does not trust the caller to have

0 commit comments

Comments
 (0)