Skip to content

OSAC-3995: Public Storage Tier API — fulfillment-service backend - #292

Open
wgordon17 wants to merge 44 commits into
osac-project:mainfrom
wgordon17:feat/OSAC-3014-public-storage-tier-api
Open

OSAC-3995: Public Storage Tier API — fulfillment-service backend#292
wgordon17 wants to merge 44 commits into
osac-project:mainfrom
wgordon17:feat/OSAC-3014-public-storage-tier-api

Conversation

@wgordon17

@wgordon17 wgordon17 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

OSAC-3995: Public Storage Tier API — fulfillment-service backend

Jira: https://redhat.atlassian.net/browse/OSAC-3995 (child of epic OSAC-3014)

Summary

Adds a public, tenant-facing StorageTiers gRPC service (Get+List only) to
fulfillment-service, wrapping the existing private StorageTier API via the same
builder/delegate/mapper pattern already used for BareMetalInstanceTypes. Tenants can now browse
available storage tiers to choose one when provisioning a ComputeInstance.

Also removes storage quota tracking entirely and moves protocol from the private
BackendAssociation message up to the tier's own spec, since it's a property of the tier as a
whole rather than of an individual backend association. This includes a data migration so
pre-existing rows don't silently lose their protocol value, and updates every affected call site
across the private/public servers, the admin CLI, and the osac-installer local-storage
registration hook.

Changes

  • New public.v1.StorageTier/StorageTierSpec/StorageTierStatus proto types and StorageTiers
    service (Get/List only, no Create/Update/Delete — matches the BareMetalInstanceTypes/
    ExternalIPPools precedent for read-only platform-catalog resources).
  • StorageTiersServer implementation with two-layer CEL filter validation to prevent tenants from
    probing excluded private fields via the filter side-channel (a scoped fix for BUG-018; the
    broader pattern across 24 other servers is tracked separately in OSAC-3609).
  • OPA allowlist entries, REST gateway registration, and CLI commands (get storagetier,
    describe storagetier switched to the public client).
  • Removes quota_gib from the private BackendAssociation message and the (not-yet-shipped)
    public StorageTierSpec field, with a backfill migration for storage_tiers/
    archived_storage_tiers so existing rows are correctly cleaned up rather than silently
    corrupted.
  • Moves protocol from BackendAssociation to StorageTierSpec on both the private and
    public proto, updating the public server's flattening logic, the admin CLI, both server test
    suites, the integration tests, and the osac-installer local-storage registration hook (which
    also had a pre-existing command-injection vector in the same line, fixed alongside the field
    move).
  • Since protocol now lives at the same path (this.spec.protocol) on both schemas, filtering by
    protocol is now forwardable to the private delegate — previously rejected as a schema-mismatch.

Testing

  • Unit tests: internal/servers/storage_tiers_server_test.go,
    private_storage_tiers_server_test.go — builder validation, List/Get delegation and field
    flattening, pagination, both filter-validation layers (including a new test proving the
    now-shared protocol filter path is genuinely forwarded and discriminates correctly, not just
    "isn't rejected"), defensive handling of malformed backend-association counts, and a
    schema-drift regression test.
  • Migration tests: internal/database/migrations/100_relocate_storage_tier_protocol_remove_quota_test.go
    — backfill correctness for nested and legacy flat-shape rows in both storage_tiers and
    archived_storage_tiers, including the zero-backend default case, run against a real Postgres
    instance.
  • Integration tests: it/it_public_storage_tiers_test.go — end-to-end List/Get, pagination,
    filtering (including the new protocol-forwarding path), the private-API permission boundary, and
    documented BUG-005 pass-through behavior.
  • Coverage: Comprehensive against all behavioral paths in the test strategy. Full local unit
    suite (internal/..., 2000+ specs across two full runs) and repo-wide build/lint pass. The full
    local integration suite could not be completed on the development machine (traced to a
    Docker-Desktop-for-Mac-specific networking hang in an unrelated, pre-existing CLI-login test —
    confirmed unrelated to this PR's code, since the identical test already passes in this PR's own
    CI in ~13 minutes) — CI on this push is the source of truth for the integration suite.

Acceptance Criteria

  • Public StorageTiers/Get RPC implemented in fulfillment-service
  • Public StorageTiers/List RPC implemented in fulfillment-service
  • Distinct public.v1.StorageTier message, mirrors private minus backend_id
  • OPA policies enforce tenant-scoped access to tiers
  • Proto definitions follow OSAC API conventions (spec/status, pagination)
  • Unit tests for public tier endpoints
  • Integration tests verify tenant can list but not modify tiers
  • Storage quota implementation removed — fulfillment-service and osac-installer portions (private proto, DB backfill, admin CLI, local-storage hook). The osac-operator/osac-aap portion is a separate follow-up PR, blocked until this PR merges and a new fulfillment-service tag publishes a new Buf Schema Registry label that osac-operator can regenerate against.
  • protocol moved from BackendAssociation to the storage tier's own spec — fulfillment-service and osac-installer portions; osac-operator's copy is unaffected until the same follow-up PR (its own generated bindings are still pinned to the old shape, so it isn't broken by this change today)

Summary by CodeRabbit

  • New Features

    • Added public Storage Tier APIs for listing and retrieving tiers, with filtering, sorting, pagination, and HTTP access.
    • Added CLI commands to create, list, retrieve, and describe storage tiers.
    • Storage tiers now expose protocol, bandwidth, encryption, lifecycle state, and status details.
    • Enabled client permissions for Storage Tier list and get operations.
  • Changes

    • Protocol configuration now applies at the storage-tier level.
    • Removed storage quota configuration from storage-tier creation and provisioning workflows.
    • Added validation requiring a supported storage protocol.

@openshift-ci-robot

openshift-ci-robot commented Aug 12, 2026

Copy link
Copy Markdown

@wgordon17: This pull request references OSAC-3995 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "5.0.0" version, but no target version was set.

Details

In response to this:

OSAC-3995: Public Storage Tier API — fulfillment-service backend

Jira: https://redhat.atlassian.net/browse/OSAC-3995 (child of epic OSAC-3014)

Summary

Adds a public, tenant-facing StorageTiers gRPC service (Get+List only) to
fulfillment-service, wrapping the existing private StorageTier API via the same
builder/delegate/mapper pattern already used for BareMetalInstanceTypes. Tenants can now browse
available storage tiers (protocol, bandwidth, quota, encryption) to choose one when provisioning a
ComputeInstance.

Changes

  • New public.v1.StorageTier/StorageTierSpec/StorageTierStatus proto types and StorageTiers
    service (Get/List only, no Create/Update/Delete — matches the BareMetalInstanceTypes/
    ExternalIPPools precedent for read-only platform-catalog resources).
  • StorageTiersServer implementation with two-layer CEL filter validation to prevent tenants from
    probing excluded private fields via the filter side-channel (a scoped fix for BUG-018; the
    broader pattern across 24 other servers is tracked separately in OSAC-3609).
  • OPA allowlist entries, REST gateway registration, and CLI commands (get storagetier,
    describe storagetier switched to the public client).

Testing

  • Unit tests: internal/servers/storage_tiers_server_test.go — builder validation, List/Get
    delegation and field flattening, pagination, both filter-validation layers, defensive handling
    of malformed backend-association counts, and a schema-drift regression test.
  • Integration tests: it/it_public_storage_tiers_test.go — end-to-end List/Get, pagination,
    filtering, the private-API permission boundary, and documented BUG-005 pass-through behavior.
  • Coverage: Comprehensive against all behavioral paths in the test strategy; full local
    validation (build, gofmt, go vet, unit suite, integration suite) passed.

Acceptance Criteria

  • Public StorageTiers/Get RPC implemented in fulfillment-service
  • Public StorageTiers/List RPC implemented in fulfillment-service
  • Distinct public.v1.StorageTier message, mirrors private minus backend_id
  • OPA policies enforce tenant-scoped access to tiers
  • Proto definitions follow OSAC API conventions (spec/status, pagination)
  • Unit tests for public tier endpoints
  • Integration tests verify tenant can list but not modify tiers

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The PR adds public StorageTiers List and Get APIs, moves protocol configuration to StorageTierSpec, removes quota data and provisioning, and updates migration, CLI, gateway, authorization, operator, installer, and test coverage.

Changes

Storage-tier contracts and migration

Layer / File(s) Summary
Storage-tier contracts and migration
fulfillment-service/proto/{private,public}/osac/.../v1/storage_tier*.proto, fulfillment-service/internal/database/migrations/*
Adds public storage-tier resources and retrieval RPCs. Private associations no longer contain protocol or quota fields. Migration 102 relocates protocol data and reshapes active and archived records.
Tier-level protocol propagation
fulfillment-service/internal/cmd/cli/create/storagetier/*, fulfillment-service/internal/cmd/service/start/grpcserver/*, osac-operator/..., osac-installer/...
Creation, resolution, installer payloads, operator mappings, and tests use the tier-level protocol. Unspecified protocol input is rejected.
Provisioning and quota removal
osac-aap/..., osac-operator/pkg/provisioning/*
Removes quota fields, quota conversion, quota validation, VAST quota tasks, and quota examples. QoS bandwidth settings remain.

Public Storage Tiers

Layer / File(s) Summary
Public storage-tier server adapter
fulfillment-service/internal/servers/storage_tiers_server.go, fulfillment-service/internal/servers/storage_tiers_server_test.go, fulfillment-service/it/it_public_storage_tiers_test.go
Adds public List and Get handling. The server validates public filters, delegates to the private server, flattens valid tiers, omits malformed List items, and maps unknown protocols to public UNSPECIFIED.
CLI and gateway exposure
fulfillment-service/internal/cmd/cli/{get,describe}/storagetier/*, fulfillment-service/internal/cmd/service/start/{grpcserver,restgateway}/*, fulfillment-service/internal/auth/*, fulfillment-service/docs/AUTH.md
Adds public retrieval and description commands, registers gRPC and REST handlers, and permits client-level StorageTiers List and Get access.
Repository validation support
.github/workflows/check-generated-code.yaml
Adds generated-code workflow path detection for osac-metering/metering-service.

Estimated code review effort: 5 (Critical) | ~120 minutes

Merge Risk: 🟠 High · up to 3c13b

This PR adds tenant-facing storage-tier reads and changes persisted storage-tier schema data. Merge readiness remains high risk because conflicting protocol values may be silently lost during migration, the installer Job is not constrained to non-root execution, and pagination metadata can mislead consumers when malformed tiers exist.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant RESTGateway
  participant StorageTiersServer
  participant PrivateStorageTiersServer
  participant StorageBackendsDAO
  Client->>RESTGateway: Request public List or Get
  RESTGateway->>StorageTiersServer: Translated gRPC request
  StorageTiersServer->>PrivateStorageTiersServer: Validated delegated request
  PrivateStorageTiersServer->>StorageBackendsDAO: Resolve backend associations
  StorageBackendsDAO-->>PrivateStorageTiersServer: Backend data
  PrivateStorageTiersServer-->>StorageTiersServer: Private tier result
  StorageTiersServer-->>Client: Flattened public StorageTier response
Loading

Suggested reviewers: vladikr, eliorerz

🚥 Pre-merge checks | ✅ 10 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.37% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 24 files. (20 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (10 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: the public Storage Tier API in the fulfillment-service backend. It is concise and specific.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
No-Hardcoded-Secrets ✅ Passed No hardcoded secret was introduced. The only added credential literal is const testBackendPassword = "secret" in *_test.go files. Those identifiers use the test prefix, and the value is not a re…
No-Weak-Crypto ✅ Passed No weak-crypto usage was introduced. The aggregate diff from origin/main contains no MD5, SHA-1, DES, 3DES, RC4, Blowfish, or ECB identifiers, no crypto-package imports or cipher calls, and no added…
No-Injection-Vectors ✅ Passed No prohibited injection vector was introduced. The changed installer hook passes the API-derived BACKEND_ID through a quoted environment variable and reads it with os.environ; it no longer interpo…
Container-Privileges ✅ Passed No prohibited container privilege setting was introduced. The only changed Kubernetes manifest is the local-storage hook, and its security context is unchanged from origin/main: `allowPrivilegeEscal…
No-Sensitive-Data-In-Logs ✅ Passed No changed production log statement records a password, token, API key, PII, session ID, or backend endpoint. The new server logs only mapper errors, a storage-tier ID with an association count, and a…
Ai-Attribution ✅ Passed AI use is explicitly documented in the PR and commit history. The pull-request range contains multiple Assisted-by: Claude Code <noreply@anthropic.com> trailers, including the initial implementation…
Full details: Docstring Coverage

Explanation

Docstring coverage is 47.37% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 24 files. (20 skipped: 20 unsupported.)

Full details: No-Hardcoded-Secrets

Explanation

No hardcoded secret was introduced. The only added credential literal is const testBackendPassword = "secret" in *_test.go files. Those identifiers use the test prefix, and the value is not a real-secret format. Other additions contain no API-key, token, private-key, credential URL, or long base64/hex secret literal.

Full details: No-Weak-Crypto

Explanation

No weak-crypto usage was introduced. The aggregate diff from origin/main contains no MD5, SHA-1, DES, 3DES, RC4, Blowfish, or ECB identifiers, no crypto-package imports or cipher calls, and no added secret/token comparisons. The only hash-related change is the fulfillment-service/internal/database/migrations.sha256 integrity value; SHA-256 is not a flagged algorithm.

Full details: No-Injection-Vectors

Explanation

No prohibited injection vector was introduced. The changed installer hook passes the API-derived BACKEND_ID through a quoted environment variable and reads it with os.environ; it no longer interpolates the value into Python source. The added SQL migration uses static SQL and does not concatenate input. No added shell=True, pickle.loads, yaml.load, os.system, or dangerouslySetInnerHTML usage was found.

Full details: Container-Privileges

Explanation

No prohibited container privilege setting was introduced. The only changed Kubernetes manifest is the local-storage hook, and its security context is unchanged from origin/main: allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, seccompProfile: RuntimeDefault, and capabilities.drop: ["ALL"]. The PR diff adds no privileged: true, host namespace settings, SYS_ADMIN, root user setting, or allowPrivilegeEscalation: true.

Full details: No-Sensitive-Data-In-Logs

Explanation

No changed production log statement records a password, token, API key, PII, session ID, or backend endpoint. The new server logs only mapper errors, a storage-tier ID with an association count, and an enum number. The installer prints only the local resource ID and fixed validation text. The generated gateway endpoint log is standard boilerplate and its FromEndpoint helper is not used by the service; the REST gateway uses the shared connection registration path. CLI rendering is user output, not application logging.

Full details: Ai-Attribution

Explanation

AI use is explicitly documented in the PR and commit history. The pull-request range contains multiple Assisted-by: Claude Code &lt;noreply@anthropic.com&gt; trailers, including the initial implementation commits and follow-up changes. No Co-Authored-By trailer for an AI tool appears in the 42 pull-request commits.

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 12, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:43 PM UTC · Completed 4:00 PM UTC

Commit: ad0f7d5 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [CI coverage regression] .github/workflows/check-generated-code.yaml:90 — The PR adds osac-metering/metering-service to the workflow's path triggers (lines 9–14) and the changes job's path filter (lines 22–28), but does not add it to the matrix.component list (lines 87–92), which only contains fulfillment-service and osac-operator. The should-run step uses contains(fromJSON(needs.changes.outputs.changes), matrix.component) to decide whether to run, so osac-metering/metering-service changes will trigger the workflow but no matrix job will ever match it. The generated-code check for metering will never actually execute.
    Remediation: Add osac-metering/metering-service to the matrix.component list so the path filter is functional.

  • [protected-path] .github/workflows/check-generated-code.yaml — This PR modifies a file under the .github/ protected path. The change is linked to OSAC-3995 and the PR description explains the rationale (adding generated-code checks for osac-metering). Human approval is required for protected-path changes regardless of context.

Low

  • [scope-creep] .github/workflows/check-generated-code.yaml:9 — The osac-metering CI path triggers and the cleanapi dummy package stub (osac-metering/metering-service/internal/dummy/cleanapi/cleanapi.go) are infrastructure fixes necessitated by the private proto gaining the cleanapi annotation (buf.validate import for the new not_in constraint on protocol). While they are a direct consequence of the proto change in this PR rather than unrelated scope creep, they could be mentioned in the PR description for clarity.

  • [Command Injection (Residual)] osac-installer/charts/osac/templates/hooks/register-local-storage.yaml:51 — Line 51 still interpolates ${BACKEND_RESP} (a mktemp path) into a Python string literal via shell expansion. The PR correctly fixed the BACKEND_ID interpolation by using os.environ (line 74), but did not apply the same pattern to BACKEND_RESP. Since mktemp returns paths like /tmp/tmp.XXXXXXXX with no shell-special characters, this is not practically exploitable, but for defense-in-depth consistency the same os.environ pattern could be applied.

  • [convention-consistency] fulfillment-service/internal/cmd/cli/get/storagetier/get_storagetier_cmd.go:95 — When get storagetier <name> resolves to a single tier, it renders a table via renderTierTable. The sibling get externalippool command renders a detail view for single-item gets, while describe provides the detailed output. Both approaches are legitimate UX choices; this is a minor deviation from the sibling pattern.

  • [convention-consistency] fulfillment-service/internal/cmd/cli/get/storagetier/get_storagetier_cmd.go:115 — The renderTierTable function takes *terminal.Console as the writer parameter, while renderStorageTier in the describe command takes io.Writer. Since *terminal.Console implements io.Writer, this is not a functional issue, but the inconsistency within the same resource's CLI commands is worth noting.

  • [edge-case] fulfillment-service/internal/servers/storage_tiers_server.go:193 — The List method calls SetOffset unconditionally (passing 0 when unset), while SetLimit uses a HasLimit guard. The asymmetry is benign since offset=0 is the default behavior, but diverges from the pattern used for limit.

Previous run

Review

Findings

Medium

  • [proto backward compatibility] fulfillment-service/proto/private/osac/private/v1/storage_tier_type.proto:37BackendAssociation.protocol (field 2) and BackendAssociation.quota_gib (field 5) are removed and reserved. In-repo consumers (osac-operator, osac-metering) regenerate their code in this PR, so they are covered. However, osac-test-infra (external E2E test repo) may still construct BackendAssociation messages with the old field layout — reserved fields are silently ignored on the wire, so tests would produce assertion mismatches rather than crashes.
    Remediation: Verify osac-test-infra has no references to BackendAssociation.protocol or BackendAssociation.quota_gib. If it does, coordinate a lockstep update.

  • [protected-path] .github/workflows/check-generated-code.yaml — This PR modifies a file under the .github/ protected path (adds osac-metering/metering-service trigger paths to the check-generated-code CI workflow). The change is legitimate infrastructure work supporting the proto regeneration. Human approval is always required for protected-path changes, regardless of context.

Low

  • [cross-repo contract] osac-operator/pkg/provisioning/extra_vars_context.go:42TierDefinition.QuotaGiB is removed. The mono-repo coordination (osac-operator + osac-aap) is complete within this PR. External repos (osac-test-infra, osac-ui) should be checked for stale references to quota_gib / quota_bytes.

  • [Rendering consistency] fulfillment-service/internal/cmd/cli/get/storagetier/get_storagetier_cmd.go:102get storagetier <name> renders a single-item table row, while the peer subcommand get externalippool <name> uses a detailed key-value view (renderPoolDetail). Minor UX inconsistency — the describe storagetier command already provides the detailed view, so users have an alternative path.

  • [scope-boundary] osac-aap/collections/ansible_collections/osac/templates/roles/vast_storage/tasks/create_quotas.yaml — The PR body states the osac-aap portion of quota removal is a "separate follow-up PR," but create_quotas.yaml is fully deleted and all quota validation/creation is removed from the VAST storage tasks in this PR. The PR body's follow-up scope statement could be tightened to clarify only the osac-operator-side quota removal remains.

Previous run (2)

Review

Findings

Medium

  • [protected-path] .github/workflows/check-generated-code.yaml — This PR modifies a CI workflow under the .github/ protected path. The change adds osac-metering/metering-service path filters to the generated-code-check workflow, justified by the transitive proto dependency introduced when storage_tier_type.proto added buf/validate/validate.proto (which requires the cleanapi module override). The rationale is sound, but human approval is always required for protected-path changes, regardless of context.

Low

  • [pattern-inconsistency] fulfillment-service/internal/cmd/cli/get/storagetier/get_storagetier_cmd.go — The get storagetier <ref> single-item case renders using the compact table format (same as listing), whereas the established pattern in sibling commands (e.g., get externalippool <ref>) uses a detail key-value view for single-item lookups. Since describe storagetier already provides the detailed view, this may be intentional, but it diverges from the CLI pattern where get <type> <ref> and describe <type> <ref> show the same detail format.

  • [edge-case] fulfillment-service/internal/servers/storage_tiers_server.go:222 — The Total-adjustment logic uses request.GetOffset() <= 0 which treats negative offsets the same as zero. If a negative offset were to pass through the private delegate (unlikely but possible), the condition would incorrectly trigger the Total adjustment path. The risk is minimal since the upstream delegate likely normalizes or rejects negative offsets.

  • [scope-creep] osac-metering/metering-service/buf.gen.yaml — The osac-metering changes (buf.gen.yaml managed override for the cleanapi module + dummy package + CI workflow filter addition) are a transitive dependency fix rather than a direct part of the StorageTier API feature. They are required because storage_tier_type.proto now imports buf/validate/validate.proto through the new cleanapi annotation. The scope creep is minimal and well-bounded, but worth noting in the PR description for reviewer awareness.

Previous run (3)

Review

Reason: stale-head

The review agent reviewed commit 8bed7300d95df9cf7e63c08ca84641e3912fc512 but the PR HEAD is now f3eb15ebd41bbd78f4cde12a8d0372070b3308a8. This review was discarded to avoid approving unreviewed code.

Previous run (4)

Review

Findings

Medium

  • [protected-path] .github/workflows/check-generated-code.yaml — This PR modifies a file under the .github/ protected path. The change adds osac-metering/metering-service path filters to the check-generated-code CI workflow, which is directly related to the proto restructuring in this PR (osac-metering's generated bindings are regenerated across 9 .pb.go files). Human approval is always required for protected-path changes, regardless of context.

Low

  • [scope creep] .github/workflows/check-generated-code.yaml:9 — The osac-metering/metering-service CI path filter addition is not referenced by OSAC-3995. However, it is a supporting change for this PR's proto restructuring: the BackendAssociation field removals and StorageTierSpec.protocol addition cause osac-metering's generated bindings to be regenerated, making the CI coverage extension necessary. See also: [protected-path] finding at this location.
Previous run (5)

Review

Findings

Medium

  • [protected-path] .github/workflows/check-generated-code.yaml — This PR modifies a file under the .github/ protected path. The change adds osac-metering/metering-service path triggers to the generated-code check workflow. The PR links to OSAC-3995 and explains the rationale. Human approval is always required for protected-path changes, regardless of context.

  • [protobuf backward compatibility] fulfillment-service/proto/private/osac/private/v1/storage_tier_type.proto:29BackendAssociation.protocol (field 2) and BackendAssociation.quota_gib (field 5) are removed and replaced with reserved declarations. This is a wire-breaking change for any consumer still referencing these fields. If osac-operator or any other consumer (e.g., osac-test-infra E2E tests) regenerates bindings against the updated proto before their follow-up PR lands, code referencing BackendAssociation.Protocol or BackendAssociation.QuotaGib will fail to compile.
    Remediation: Ensure follow-up PRs for all consumers are tracked and landed promptly.

  • [protobuf backward compatibility] fulfillment-service/proto/private/osac/private/v1/storage_tier_type.proto:51StorageTierSpec gains a new required field protocol (field 3) with buf.validate constraint not_in:[0] that rejects UNSPECIFIED. Any existing client that creates or updates a StorageTier without setting spec.protocol will now receive InvalidArgument. The osac-operator and osac-installer are updated in this PR, but any other private API consumer not included (e.g., osac-test-infra) will fail.
    Remediation: Verify all private API consumers are updated to supply spec.protocol.

Low

  • [CEL filter oracle prevention] fulfillment-service/internal/servers/storage_tiers_server.go:199 — The order parameter from the public request is forwarded directly to the private delegate without validation against the public schema. While the filter path has robust two-layer validation, the order path could allow a tenant to probe for private field existence by ordering on private-only paths. The practical risk is low since ordering reveals field existence at most, not values.
    Remediation: Consider validating the order parameter against the public schema for consistency.

  • [CLI pattern inconsistency] fulfillment-service/internal/cmd/cli/get/storagetier/get_storagetier_cmd.go:100 — The get storagetier <name> path renders a single-item result using the same table view (renderTierTable) as the listing path. The externalippool get subcommand distinguishes these: listing uses renderPoolTable while single-item uses renderPoolDetail. Since describe storagetier already provides the detailed view, this may be intentional.

  • [Consumer completeness] .github/workflows/check-generated-code.yaml:9 — Adding osac-metering/metering-service paths to the CI workflow creates a latent expectation: the next PR touching metering-service files will trigger buf generate, which will produce .pb.go files that differ from what is currently committed (the metering service's bindings still reflect the pre-change proto schema). That PR will need to include a regeneration step.

  • [missing test coverage] fulfillment-service/internal/cmd/cli/get/get_cmd.go:69 — The storagetier subcommand was added to get_cmd.go without a corresponding update to get_cmd_test.go to verify the subcommand registration. The describe_cmd_test.go has such verification (private-API annotation tests).

Previous run (6)

Review

Findings

Medium

  • [protected-path] .github/workflows/check-generated-code.yaml — This PR modifies a file under the .github/ protected path (adding osac-metering path filters to the check-generated-code CI workflow). The linked issue (OSAC-3995) and PR description explain the rationale: proto changes caused regeneration of metering's generated bindings, requiring CI coverage. Human approval is always required for protected-path changes.

Low

  • [protobuf backward compatibility] fulfillment-service/proto/private/osac/private/v1/storage_tier_type.proto:39 — Removing BackendAssociation.protocol (field 2) and quota_gib (field 5) is wire-breaking for consumers using the old schema. Risk is heavily mitigated: all in-repo consumers are updated in this PR, field numbers are properly reserved, the private API has no external consumers, and the mono-repo's single-branch deployment pattern ensures all components land together. Verify deployment ordering: database migration 102 must complete before new binaries.

  • [edge-case] fulfillment-service/internal/servers/storage_tiers_server.go:239List Total adjustment only corrects when the entire result set fits in one page. Paginated results may include malformed tiers in Total that were silently dropped from Items. Intentional documented trade-off with explicit test coverage.

  • [command-injection-fix] osac-installer/charts/osac/templates/hooks/register-local-storage.yaml:72 — Positive security improvement: fixes a pre-existing command injection vector by passing BACKEND_ID via environment variable (os.environ) instead of shell interpolation into Python string literals.

  • [scope-expansion] .github/workflows/check-generated-code.yaml — CI workflow adds osac-metering/metering-service path filters. Reasonable housekeeping forced by proto changes affecting metering's generated bindings.

  • [scope-expansion] fulfillment-service/internal/cmd/cli/get/storagetier/get_storagetier_cmd.go — New get storagetier CLI command beyond stated acceptance criteria. Follows naturally from the public API introduction and uses established CLI patterns.

  • [public-api-design] fulfillment-service/proto/public/osac/public/v1/storage_tier_type.proto — Flattened public StorageTierSpec embeds a single-backend assumption. Conscious design choice with server-side enforcement (toPublicTier returns Internal for ≠1 backends) and explicit test coverage.

  • [public-api-field-numbering] fulfillment-service/proto/public/osac/public/v1/storage_tier_type.proto:76 — Reserves field 5 (quota_gib) in a brand-new message. Defensive forward-looking choice aligning with private schema numbering.

Previous run (7)

Review

Findings

Medium

  • [protected-path] .github/workflows/check-generated-code.yaml — This PR modifies a file under the .github/ protected path (adds osac-metering/metering-service to the generated-code CI workflow). The change is explained in the PR context (metering proto bindings were regenerated as part of this PR's proto changes), but human approval is always required for protected-path changes regardless of context.

  • [protobuf backward compatibility] fulfillment-service/proto/private/osac/private/v1/storage_tier_type.proto — The private proto removes protocol (field 2) and quota_gib (field 5) from BackendAssociation with correct reserved directives preventing wire-format collisions. The osac-operator's generated Go bindings are still pinned to the old proto shape. The PR author acknowledges this and notes a follow-up PR will regenerate them after this PR merges and a new Buf Schema Registry label is published. The operator's source code already reads from the new location (tier.GetSpec().GetProtocol()), so it is forward-compatible.

Low

  • [command-injection] osac-installer/charts/osac/templates/hooks/register-local-storage.yaml:51 — The backend-creation success path still interpolates ${BACKEND_RESP} directly into a python3 -c string (open('${BACKEND_RESP}')). While BACKEND_RESP comes from mktemp (guaranteed safe path), this is inconsistent with the PR's own fix on the tier-verification path, which moved to environment-variable-based passing (env BACKEND_ID=... python3 -c "... os.environ[...]..."). Pre-existing, zero actual risk, but a defense-in-depth consistency improvement.
    Remediation: env RESP_FILE="${BACKEND_RESP}" python3 -c "import sys,json,os; print(json.load(open(os.environ['RESP_FILE']))['id'])"

  • [comment-completeness] fulfillment-service/internal/servers/storage_tiers_server.go:35 — The comment on storageTierUnforwardableFilterFields explains why protocol is excluded from the rejection list ("its path matches on both schemas") but omits description, which is excluded for the same reason. The regression test at storage_tiers_server_test.go correctly documents both exceptions.
    Remediation: Change the parenthetical to: (description and protocol are excluded — their paths match on both schemas).

  • [scope-creep] .github/workflows/check-generated-code.yaml — Adding osac-metering/metering-service to the generated-code CI workflow is tangential to the OSAC-3995 storage tier feature. It carries no risk and is useful infrastructure cleanup, but it is not part of the linked issue's scope.

Previous run (8)

Review

Findings

Medium

  • [protected-path] .github/workflows/check-generated-code.yaml — This PR modifies a file under the .github/ protected path (adding osac-metering/metering-service to the CI workflow trigger paths and matrix). The change is related to the proto changes in this PR causing osac-metering's generated code to be regenerated, and the PR body documents the rationale. Human approval is always required for protected-path changes regardless of context.

Low

  • [error-handling] fulfillment-service/internal/servers/storage_tiers_server.go — In the List method, when toPublicTier fails for a private item, the error is swallowed with continue. Both current failure paths in toPublicTier do log specific details internally (mapper error and unexpected backend count), so no information is currently lost, but the pattern is fragile if new failure modes are added without logging.

  • [pagination-inconsistency] fulfillment-service/internal/servers/storage_tiers_server.go — The Total adjustment logic only corrects the count when the entire result set fits on a single page (offset<=0 && len(privateItems)==int(total)). For paginated requests containing malformed tiers, Total may be higher than the actual retrievable item count. This is a documented design tradeoff with comprehensive test coverage.

  • [CEL-filter-bypass] fulfillment-service/internal/servers/storage_tiers_server.go — The Layer 2 filterReferencesAnyField function could theoretically be bypassed by a CEL expression accessing an unforwardable field through a function call return value (where resolveSelectPath returns empty). This is effectively mitigated by Layer 1, which validates against the public schema that lacks backends entirely.

  • [migration-no-backfill] fulfillment-service/internal/database/migrations/102_relocate_storage_tier_protocol_remove_quota.up.sql — The migration strips protocol from backend associations but does NOT copy it to spec.protocol. The comment explicitly states "no backfill -- no real deployment has data." If this assumption is incorrect, protocol information is irreversibly lost (no down migration exists). Migration tests confirm this is intentional.

  • [CEL-filter-error-disclosure] fulfillment-service/internal/servers/storage_tiers_server.go — Layer 1 filter validation returns the raw FilterTranslator.Translate() error to the tenant via invalid filter: %v. Since validation runs against the public schema, private field names cannot leak, but raw CEL compilation diagnostics are exposed. This follows the pattern used by other servers in the codebase.

  • [scope-description-mismatch] osac-operator/internal/controller/storage_tier_definitions.go — The PR body's Acceptance Criteria states the osac-operator/osac-aap portion is a "separate follow-up PR," but the diff includes handwritten changes in both components (6 osac-operator files, 9+ osac-aap files). The description is unclear — the follow-up may refer specifically to BSR label regeneration rather than these handwritten changes. Consider clarifying the PR body.

  • [scope-creep] .github/workflows/check-generated-code.yaml — Adding osac-metering to the CI workflow is tangential to the OSAC-3995 feature, though reasonable since the proto changes cause osac-metering's generated code to change.

  • [pattern-inconsistency] fulfillment-service/internal/cmd/cli/get/storagetier/get_storagetier_cmd.go — The new get storagetier command renders a single item using renderTierTable (table view), whereas the get externalippool precedent uses a key-value detail view for single-item lookups. However, since describe storagetier already provides the detailed view, and kubectl's get typically shows table output even for a single resource, this approach is defensible.

  • [missing-doc] fulfillment-service/docs/AUTH.md — The new public StorageTiers service and its REST endpoints (/api/fulfillment/v1/storage_tiers) have no dedicated user-facing documentation page. AUTH.md is updated and CLI help text is present, but there is no standalone guide. This follows existing precedent (HostTypes, InstanceTypes also lack dedicated docs).

  • [external-consumer-impact] fulfillment-service/proto/private/osac/private/v1/storage_tier_type.proto — External repos (osac-test-infra, osac-ui) that depend on the private proto will get compile errors on regeneration due to BackendAssociation.protocol and BackendAssociation.quota_gib being reserved. Until they regenerate, existing compiled binaries will silently drop these fields at the wire level. Coordination with external repo maintainers is recommended.

Previous run (9)

Review

Findings

Medium

  • [protected-path] .github/workflows/check-generated-code.yaml — This file is under .github/, a protected governance path. The change adds osac-metering/metering-service to the generated-code CI workflow, which is a necessary consequence of the StorageProtocol enum change in the private proto (osac-metering consumes the generated bindings). The linked Jira issue (OSAC-3995) and PR description provide context for this change. Human approval is required for all protected-path changes regardless of context.

Low

  • [migration-intent-gap] fulfillment-service/internal/database/migrations/102_relocate_storage_tier_protocol_remove_quota.up.sql — The PR description states "This includes a data migration so pre-existing rows don't silently lose their protocol value," but the migration does NOT backfill protocol into spec.protocol — it strips it from backends and leaves spec.protocol unset. The SQL comment correctly documents this: "no backfill -- no real deployment has data." The implementation is internally consistent, but the PR description is misleading on this point.

  • [pattern-inconsistency] fulfillment-service/internal/cmd/cli/get/storagetier/get_storagetier_cmd.go:88 — The get storagetier command uses renderTierTable for both listing all tiers and displaying a single looked-up tier. The established pattern (see get/externalippool) uses separate rendering functions for list (table) and single-item (key-value detail) views. The describe command already provides the detailed view, so this is a minor UX inconsistency rather than a bug.

  • [pattern-inconsistency] fulfillment-service/internal/cmd/cli/get/storagetier/get_storagetier_cmd.go:42 — Uses inline string literals for Short, Long, and Example instead of named package-level constants (shortHelp, longHelp), which is the pattern used by sibling commands (describe storagetier, create storagetier, get externalippool).

  • [pattern-inconsistency] fulfillment-service/internal/servers/storage_tiers_server.go:27 — The List and Get methods use mixed variable declaration styles (explicit var + assign vs :=), which is inconsistent with the servers package's prevailing pattern of named returns with bare returns.

Previous run (10)

Review

Findings

Low

  • [data-loss-in-migration] fulfillment-service/internal/database/migrations/102_relocate_storage_tier_protocol_remove_quota.up.sql:20 — The migration strips protocol from backends[] but does not backfill spec.protocol with the old backend-level value. The comment states "no backfill -- no real deployment has data." If any pre-production environment has storage tiers with protocol set on backend associations, that value would be permanently lost after migration. The migration test suite deliberately verifies this is intentional behavior. Consider adding a precondition guard that fails the migration if any row has a non-zero protocol value in backends, as a safety net.

  • [stale-reference] osac-metering/metering-service/internal/api/osac/private/v1/storage_tier_type.pb.go — The osac-metering component's generated protobuf code was not regenerated in this PR. BackendAssociation still has Protocol (field 2) and QuotaGib (field 5), and StorageTierSpec lacks the new Protocol field. While metering application code does not directly reference these fields (no compile failure), any deserialized StorageTier data from the updated fulfillment-service will have spec.protocol silently dropped. Regenerate in a follow-up.

  • [naming-convention] fulfillment-service/internal/cmd/service/start/grpcserver/register_servers.go:810 — The log message for the new public storage tiers server says "Creating storage tiers server" without the "public" qualifier. The private server already says "Creating private storage tiers server." While the codebase is inconsistent about this pattern (many public servers omit the qualifier), adding "public" would improve clarity.

  • [scope-creep] fulfillment-service/proto/public/osac/public/v1/storage_tier_type.proto — The PR bundles three tightly coupled concerns: (1) new public StorageTiers API, (2) quota removal, (3) protocol relocation from BackendAssociation to StorageTierSpec. These are coherent — the public API requires the protocol relocation, and quota removal is cleanup of a dead field — but verifying that OSAC-3995 explicitly authorizes all three would strengthen traceability.

  • [proto-backward-compatibility] fulfillment-service/proto/private/osac/private/v1/storage_tier_type.proto:35 — The removal of protocol (field 2) and quota_gib (field 5) from BackendAssociation uses correct reserved declarations for wire compatibility. Both known in-repo consumers (osac-operator, osac-metering) are addressed (operator regenerated in this PR, metering noted above). Verify no additional external consumers reference these fields via the buf.build/osac-project/private-api BSR module.

Previous run (11)

Review

Findings

Medium

  • [stale-reference] osac-metering/metering-service/internal/api/osac/private/v1/storage_tier_type.pb.go — The osac-metering module's generated Go code was not regenerated in this PR. The private proto storage_tier_type.proto moves protocol from BackendAssociation to StorageTierSpec and removes quota_gib, but osac-metering's generated bindings still reflect the old schema shape. If osac-metering processes protobuf-serialized StorageTier messages from the updated fulfillment-service (e.g., via gRPC Watch), the new spec.protocol field (tag 3) would be treated as unknown and silently dropped, while the removed backends[].protocol (tag 2, now reserved) will never arrive. Currently the non-generated metering code does not directly use these accessors, so the runtime impact is zero at present — but the embedded proto descriptor is out of sync.
    Remediation: Run buf generate in osac-metering/metering-service/ to regenerate the Go code from the updated private proto.

Low

  • [data-loss-migration] fulfillment-service/internal/database/migrations/102_relocate_storage_tier_protocol_remove_quota.up.sql — The migration strips protocol and quota_gib from backends[] but does not backfill spec.protocol with the backend's former protocol value. The migration comments document this as intentional: "no backfill — no real deployment has data." If any pre-production environment has storage tiers with protocol values set at the backend level, those values will become STORAGE_PROTOCOL_UNSPECIFIED after migration.

  • [style-conventions] fulfillment-service/internal/cmd/cli/get/storagetier/get_storagetier_cmd.go — When a single tier is resolved by name or ID, get storagetier renders a table (renderTierTable) rather than a key-value detail view. The established pattern in get externalippool uses a detail view for single-item lookups (renderPoolDetail) and a table for list mode. Since describe storagetier already provides a detail view, the two commands produce near-identical output for single items.

  • [scope-creep] osac-operator/internal/api/osac/private/v1/baremetal_instance_type.pb.go — The diff includes generated-code changes for BareMetalInstanceStatus, ClusterTemplate, Secret, and Cluster types unrelated to the storage tier feature. These are expected artifacts from running buf generate on the full proto package, which picks up upstream proto changes merged into main.

Previous run (12)

Review

Findings

Low

  • [Command Injection (Residual)] osac-installer/charts/osac/templates/hooks/register-local-storage.yaml — Step 1 still interpolates ${BACKEND_RESP} (mktemp path) directly into a Python code string via open('${BACKEND_RESP}'). While the value originates from mktemp and cannot be attacker-controlled, this is the same pattern the PR explicitly fixed in Step 2 for ${BACKEND_ID}. Consider passing BACKEND_RESP via environment variable for consistency with the hardened pattern applied elsewhere.

  • [scope-creep] osac-operator/pkg/provisioning/extra_vars_context.go — Quota removal (QuotaGiB / quota_bytes) is a separate functional change bundled with the public API feature. The PR description explicitly documents this, and the removal is coherent with the API design (the public StorageTierSpec deliberately excludes quota). Verify that OSAC-3995's acceptance criteria explicitly authorize quota removal for traceability.

  • [naming-convention] fulfillment-service/internal/cmd/cli/describe/storagetier/describe_storagetier_cmd.goRenderStorageTier is exported so get/storagetier can import it, creating a cross-package dependency between CLI subcommands. Every other describe subcommand uses unexported render functions. Consider extracting to a shared internal package or duplicating the logic.

  • [protobuf backward compatibility] fulfillment-service/proto/private/osac/private/v1/storage_tier_type.protoBackendAssociation.protocol (field 2) and quota_gib (field 5) are removed with proper reserved declarations. All in-repo consumers are updated in this PR. Ensure release notes document the private API shape change for any out-of-tree tooling.

  • [edge-case] fulfillment-service/internal/servers/storage_tiers_server.go — The List Total adjustment condition len(privateItems) == int(total) to detect single-page results is theoretically vulnerable to a concurrent deletion race between count and fetch. Practically safe for admin-managed storage tiers, and extensively tested.

  • [comment-style] fulfillment-service/internal/cmd/service/start/grpcserver/register_servers.go — The public storage tiers server registration logs "Creating storage tiers server", identical to the private server's log line above. Other server pairs in this file use distinguishable messages (e.g., "Creating private cluster templates server" vs "Creating cluster templates server").


Labels: PR adds Go-implemented public StorageTiers gRPC server, CLI commands, and tests — matches convention of applying tech labels alongside existing type/domain labels.

Previous run (13)

Review

Findings

High

  • [test-integrity] fulfillment-service/internal/database/migrations/102_relocate_storage_tier_protocol_remove_quota_test.go:31 — All five test cases call tool.Migrate(ctx, 101) but the migration under test is number 102. The DescribeMigration helper creates the database at the previous migration level (101). Calling Migrate(ctx, 101) is a no-op because the database is already at version 101, so migration 102 is never applied. The tool.Migrate wrapper treats ErrNoChange as success (database_tool.go:420), so the test silently passes without ever exercising the migration SQL. Every other migration test in this codebase correctly uses its own migration number (e.g., migration 47→47, migration 81→81, migration 87→87, migration 96→96).
    Remediation: Replace all five occurrences of tool.Migrate(ctx, 101) with tool.Migrate(ctx, 102) — at file lines 31, 59, 78, 100, and 135.

Low

  • [command-injection-fix] osac-installer/charts/osac/templates/hooks/register-local-storage.yaml:72 — The register-local-storage Helm hook switches from printf-based JSON construction to Python json.dumps(), fixing a potential command injection vector where BACKEND_ID with JSON metacharacters could corrupt the JSON structure. The verification code also switches from inline shell variable interpolation to os.environ. Both changes are genuine security improvements.

  • [proto-enum-alignment] fulfillment-service/proto/public/osac/public/v1/storage_tier_type.proto:22 — The public StorageProtocol enum defines the same values (UNSPECIFIED=0, NFS=1, BLOCK=2) as the private enum. The server uses an explicit switch-based mapping (toPublicStorageProtocol) rather than a numeric cast, with a logged fallback and regression test coverage.

  • [installer-sync] osac-installer/charts/osac/templates/hooks/register-local-storage.yaml:72 — The Helm hook that registers the 'local' StorageTier is correctly updated to place protocol at the top-level spec. This change is backward-incompatible with older fulfillment-service versions; the installer must be deployed in sync with the new fulfillment-service.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (14)

Review

Findings

Low

  • [cross-repo contract: osac-test-infra] fulfillment-service/proto/private/osac/private/v1/storage_tier_type.proto — The external osac-test-infra repo may have E2E tests that create StorageTiers via the private API using the old BackendAssociation shape (with protocol or quota_gib on the backend). If so, those tests would need updating since the fields are now reserved. Given that "no real, persistent OSAC deployment has StorageTier data today" (per the PR description), this is likely a non-issue, but worth verifying.

  • [Residual shell interpolation in Python code] osac-installer/charts/osac/templates/hooks/register-local-storage.yaml:52 — The BACKEND_ID extraction still uses python3 -c with ${BACKEND_RESP} interpolated directly into the Python string. While safe (the value comes from mktemp), this is inconsistent with the env-based pattern the PR correctly adopts for BACKEND_ID in the tier-creation and verification paths. A follow-up consistency fix would replace this with the env RESP_FILE=... python3 -c 'os.environ[...]' pattern.

  • [Command injection (fixed)] osac-installer/charts/osac/templates/hooks/register-local-storage.yaml:89 — The PR correctly fixes a pre-existing command injection vector by switching from printf with shell-interpolated ${BACKEND_ID} directly inside Python string literals to env BACKEND_ID=... python3 -c 'os.environ[...]'. Both the tier-creation path and the 409-conflict verification path are fixed.

  • [proto backward compatibility] fulfillment-service/proto/private/osac/private/v1/storage_tier_type.proto:31protocol (field 2) and quota_gib (field 5) are properly reserved in BackendAssociation, following protobuf backward-compatibility best practices. Migration 101 handles existing DB rows. No production deployments are affected.

Previous run (15)

Review

Findings

Low

  • [cross-package coupling] fulfillment-service/internal/cmd/cli/get/storagetier/get_storagetier_cmd.go:28 — The new get storagetier command imports describe/storagetier to reuse RenderStorageTier. No other get subcommand in this codebase imports from describe. The coupling is intentional and documented (the exported function's doc comment explains the sharing), but this establishes a new cross-package dependency pattern in the CLI layer.

  • [test assertion style] fulfillment-service/internal/servers/storage_tiers_server_test.go:65 — The "Fails if attribution logic is not set" test uses Expect(err.Error()).To(ContainSubstring(...)) while all other builder failure tests in the same file use Expect(err).To(MatchError(...)). Minor inconsistency.
    Remediation: Change to Expect(err).To(MatchError("attribution logic is mandatory")).

Previous run (16)

Looks good to me

Findings

Low

  • [cross-repo backward compatibility] osac-operator/pkg/provisioning/aap_provider.go — Removing quota_bytes from the AAP extra_vars payload is a contract change with osac-aap. The osac-aap changes in this PR properly remove the quota handling, and AGENTS.md documents that cross-component features land in a single branch/PR, so this is mitigated by the mono-repo deployment model.

  • [data exposure] fulfillment-service/internal/servers/storage_tiers_server.go — The Layer 2 CEL filter rejection list (storageTierUnforwardableFilterFields) is maintained manually. However, the schema-drift regression test programmatically verifies every StorageTierSpec field is accounted for, making this effectively a compile-time guard.

  • [data exposure] fulfillment-service/internal/servers/storage_tiers_server.go:184 — CEL filter compilation error messages could expose public schema field names, but the public proto schema is already discoverable via gRPC reflection.

  • [scope-coherence] fulfillment-service/internal/cmd/cli/get/get_cmd.go:71 — PR adds osac get storagetier CLI command and moves describe storagetier from private to public API without explicitly mentioning these in the PR description. These are natural companions to the public API feature.

  • [test-assertion-style] fulfillment-service/internal/servers/storage_tiers_server_test.go — The attribution logic builder test uses ContainSubstring while all other builder validation tests in the same block use MatchError. Consider aligning for consistency.

  • [describe-command-consistency] fulfillment-service/internal/cmd/cli/describe/storagetier/describe_storagetier_cmd.goRenderStorageTier is exported for cross-package sharing (the get command imports describe to reuse it). This is a novel pattern in the CLI tree but is pragmatic and well-documented with a comment.

Info

  • [permission change] fulfillment-service/internal/auth/policies/authz.rego:223 — Two new OPA allowlist entries grant StorageTiers/Get and StorageTiers/List to all authenticated users with client permissions. This follows the established catalog-resource pattern (HostTypes, InstanceTypes).

  • [command injection fix] osac-installer/charts/osac/templates/hooks/register-local-storage.yaml — The old printf/shell-interpolation pattern for constructing JSON has been replaced with Python's json.dumps and os.environ, correctly fixing a command injection vector.

  • [migration correctness] fulfillment-service/internal/database/migrations/100_relocate_storage_tier_protocol_remove_quota.up.sql:34 — Migration takes protocol from the first backend association only. This is acceptable because the private server enforces exactly one backend per tier at create time.

Previous run (17)

Review

Findings

Medium

  • [protobuf backward compatibility] fulfillment-service/proto/private/osac/private/v1/storage_tier_type.proto — The protocol field (number 2) and quota_gib field (number 5) are removed from BackendAssociation and correctly reserved with both field number and name reservations. The protocol field is relocated to StorageTierSpec as field number 3. The deployment ordering dependency (fulfillment-service must be deployed before osac-operator picks up the new proto via a new BSR label) is documented in the PR body but worth capturing in release notes so operators are aware of the sequencing requirement.

Low

  • [Command Injection] osac-installer/charts/osac/templates/hooks/register-local-storage.yaml:75 — The BACKEND_ID variable is interpolated into a printf format string to construct TIER_BODY. While the value originates from the internal API (which produces UUID-format IDs, making exploitation impractical), the verification step on line 90 already uses the safer env BACKEND_ID=... python3 -c pattern. Aligning the creation path with the same env-based approach would be a consistent hardening improvement.

  • [cross-repo contract] osac-operator/pkg/provisioning/aap_provider.go:387 — The quota_bytes key is removed from tierDefinitionsToExtraVars. Both sides of this contract (osac-operator and osac-aap) are updated in the same PR, and osac-aap already guarded against missing quota_bytes via selectattr('quota_bytes', 'defined'). VAST quotas previously created may become orphaned — consider noting this in release documentation so operators can plan cleanup.

Previous run (18)

Review

Findings

High

  • [stale-reference] osac-operator/internal/controller/storage_tier_definitions.go:119 — The osac-operator reads protocol from the backend association level (assoc.GetProtocol()), but this PR moves protocol from BackendAssociation to StorageTierSpec and reserves field number 2. After deployment, assoc.GetProtocol() will always return STORAGE_PROTOCOL_UNSPECIFIED (0) for newly created or migrated tiers. The same consumer references assoc.GetQuotaGib() at line 134, which is also removed. The PR body acknowledges the osac-operator follow-up is blocked on this PR merging, but the runtime behavior change during the rollout window needs evaluation.
    Remediation: Verify that no osac-operator code path makes provisioning decisions based on BackendAssociation.protocol being non-zero during the rollout window, and ensure the follow-up PR is tracked.

Medium

  • [breaking-api] fulfillment-service/proto/private/osac/private/v1/storage_tier_type.proto:37BackendAssociation removes protocol (field 2) and quota_gib (field 5), both properly marked reserved. The database migration strips quota_gib from existing data, making removal irreversible. Until the osac-operator regenerates against the new BSR label, its compiled code will read zero values for these fields. See also: [stale-reference] finding above.
    Remediation: Confirm no consumer outside fulfillment-service reads quota_gib for any purpose.

  • [edge-case] fulfillment-service/internal/database/migrations/100_relocate_storage_tier_protocol_remove_quota.up.sql:33 — The migration promotes only backends->0->'protocol' to the spec level, silently discarding any differing protocol on subsequent backends. While the private server enforces exactly one backend, DAO bypass paths and archived historical data could contain multi-backend tiers that would lose their secondary backend protocols.

  • [data-exposure] fulfillment-service/internal/servers/storage_tiers_server.go:213 — The List method returns CEL filter compilation errors to the caller verbatim via grpcstatus.Errorf(grpccodes.InvalidArgument, "invalid filter: %v", err). The FilterTranslator error may reveal internal schema details.
    Remediation: Return a generic error message to the caller (e.g., "invalid filter expression") and log the detailed error server-side, matching the pattern already used by toPublicTier.

  • [api-pattern-consistency] fulfillment-service/internal/servers/storage_tiers_server.go:231 — The List method unconditionally forwards the limit value without checking request.HasLimit(). Other public server List implementations (instance_types_server.go, host_types_server.go, bare_metal_instance_types_server.go) guard with if request.HasLimit(). Without this guard, a client that omits limit forwards 0, which may cause empty responses instead of returning all items.
    Remediation: Wrap the limit forwarding in if request.HasLimit() { privateRequest.SetLimit(request.GetLimit()) }, and apply the same pattern to offset and order for consistency.

Low

  • [edge-case] fulfillment-service/internal/database/migrations/100_relocate_storage_tier_protocol_remove_quota.up.sql:48 — In the archived_storage_tiers ELSE branch (pre-migration-77 flat shape), the || concatenation precedence could let leftover top-level keys overwrite newly constructed spec/status objects, though this scenario is practically impossible given the data model constraints.

  • [naming-convention] fulfillment-service/internal/servers/storage_tiers_server.go:66 — The doc comment on storageTierUnforwardableFilterFields says "lists public StorageTierSpec field paths" but the values are full CEL dotted paths (e.g., this.spec.max_read_bandwidth_mbs).

  • [command-injection-residual] osac-installer/charts/osac/templates/hooks/register-local-storage.yaml:59 — Line 59 (unchanged in this PR) still interpolates ${BACKEND_ID} directly into the python3 -c string, while the fix at line 89 correctly uses os.environ. Follow-up: apply the same safe pattern.

  • [cross-package-coupling] fulfillment-service/internal/cmd/cli/get/storagetier/get_storagetier_cmd.go:24 — The get storagetier command imports RenderStorageTier from the describe/storagetier package. No other get subcommand cross-imports a describe package's render function, though the sharing is deliberate and documented.

  • [guideline-deviation] fulfillment-service/docs/API.md:315 — API.md states public services must declare five standard methods, but StorageTiers only declares List and Get. This is a defensible deviation for a read-only catalog resource but the guideline should be updated to document the exemption.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (19)

Review

Findings

Medium

  • [pagination inconsistency] fulfillment-service/internal/servers/storage_tiers_server.go:239 — When List drops malformed tiers (those with ≠1 backend association), response.SetSize() is recalculated from len(publicItems), but response.SetTotal() is passed through from the private response unchanged. This means Total may be higher than the sum of all pages' Size values, violating the pagination contract. A client paginating through results will see Total claim N tiers exist but receive fewer than N across all pages. This only manifests when admin-created tiers have corrupted backend data, and the code deliberately chooses graceful degradation (log and omit) over failing the whole listing — but the pagination metadata should reflect the actual returned count.
    Remediation: Either subtract the count of dropped items from Total, or document in the proto comment that Total reflects the backend count and may exceed the sum of public items when internal data is malformed.

  • [cross-repo backward compatibility] fulfillment-service/proto/private/osac/private/v1/storage_tier_type.proto:39 — The protocol field (number 2) and quota_gib field (number 5) are removed from BackendAssociation with correct reserved declarations. However, the osac-operator (pinned to buf.build/osac-project/private-api:v0.0.85) still has generated bindings with BackendAssociation.Protocol at field 2. Once the fulfillment-service is deployed with migration 100 (which moves protocol from spec.backends[].protocol to spec.protocol in the database), the operator's storage_tier_definitions.go:119 call to assoc.GetProtocol() will return STORAGE_PROTOCOL_UNSPECIFIED for all tiers because the server no longer populates that wire field. The PR body correctly acknowledges this as expected until a follow-up PR, but the deployment ordering requirement should be documented explicitly.
    Remediation: Document that the osac-operator must be bumped to a BSR version containing these proto changes before or atomically with deploying the fulfillment-service migration. Consider whether the migration and operator update must be coordinated in the same release.

Low

  • [edge-case] fulfillment-service/internal/database/migrations/100_relocate_storage_tier_protocol_remove_quota.up.sql:56 — In the archived_storage_tiers migration for the legacy flat shape (the else branch), the expression (data - 'description' - 'backends' - 'state') preserves any additional top-level keys from the old flat data by merging them into the result via ||. This could produce unexpected results for unanticipated shapes, though the risk is low since this is archived data frozen before migration 77.

  • [command-injection] osac-installer/charts/osac/templates/hooks/register-local-storage.yaml:237 — The fix correctly replaces direct shell variable interpolation of BACKEND_ID inside a python3 -c inline script with environment variable passing via env BACKEND_ID. The original was not exploitable (BACKEND_ID originates from a tempfile-based JSON parse, not user-controlled data), but the fix is a good defense-in-depth improvement.

  • [scope-coherence] fulfillment-service/proto/private/osac/private/v1/storage_tier_type.proto — The PR bundles three logically distinct changes: (1) new public StorageTiers API, (2) protocol relocation from BackendAssociation to StorageTierSpec, and (3) quota_gib removal. While bundling is pragmatic (the public API's flattened shape depends on protocol being at spec-level), these are separate work items worth tracking individually.

  • [public-api-field-numbering] fulfillment-service/proto/public/osac/public/v1/storage_tier_type.proto:67 — The public StorageTierSpec.protocol uses field number 2, while the private StorageTierSpec.protocol uses field number 3. This is intentional and correct since these are independently defined messages in separate packages, and the server's toPublicTier mapping handles this correctly — noted for future reference.

Previous run (20)

Review

Findings

Medium

  • [API contract / pagination consistency] fulfillment-service/internal/servers/storage_tiers_server.go:232 — When List silently omits malformed tiers (those with zero or more than one backend association), Total is set from privateResponse.GetTotal() (which counts the omitted tiers) while Size reflects only the successfully-mapped items. This creates an inconsistency: a pagination client summing page sizes will never reach Total, potentially causing stuck-pagination loops. Other public servers use the same SetTotal(privateResponse.GetTotal()) pattern, but they never skip items during mapping, so the inconsistency is unique to this server.
    Remediation: Adjust Total to subtract dropped items (e.g., response.SetTotal(privateResponse.GetTotal() - int32(len(privateItems) - len(publicItems)))), or document that total is approximate when tiers with malformed backend data exist.

  • [stale-convention-doc] fulfillment-service/docs/API.md:267 — API.md states "Services of the public API must always declare the following five methods" (Create, List, Get, Update, Delete). The new StorageTiers service only declares Get and List, following the read-only catalog pattern also used by HostTypes, InstanceTypes, and BareMetalInstanceTypes. The documented convention should acknowledge this established exception.
    Remediation: Add a note to the "Standard methods" section in API.md acknowledging that read-only catalog/platform services may declare only Get and List when Create/Update/Delete are managed exclusively through the private API.

Low

  • [order clause forwarding] fulfillment-service/internal/servers/storage_tiers_server.go:207 — The order parameter is forwarded verbatim to the private delegate without validation. The DAO currently hardcodes ordering by id (ignoring the parameter entirely), so this is safe today. If the DAO is later enhanced to support user-supplied ordering, this code path would need the same two-layer validation applied to the filter parameter. All other public servers forward order the same way, so this is a codebase-wide future-proofing concern, not StorageTiers-specific.

  • [proto filter documentation] fulfillment-service/proto/public/osac/public/v1/storage_tiers_service.proto:48 — The filter field documentation does not mention that filtering by spec.protocol, spec.max_read_bandwidth_mbs, spec.max_write_bandwidth_mbs, spec.quota_gib, and spec.encryption_enabled is explicitly unsupported and will return InvalidArgument. Users would discover this limitation only at runtime.

  • [public-enum-scope] fulfillment-service/proto/public/osac/public/v1/storage_tier_type.proto:21 — The StorageProtocol enum is defined inline in storage_tier_type.proto rather than in a shared file. If a public Volume API is added later that also needs StorageProtocol, the enum would need to be moved to a shared storage_common_type.proto. This is a source reorganization, not a wire-breaking change, and is acceptable for now given there is no public Volume API.

Previous run (21)

Review

Findings

Low

  • [CEL Filter Oracle - Order Parameter] fulfillment-service/internal/servers/storage_tiers_server.go:178 — The order parameter from the public request is forwarded verbatim to the private delegate without the same two-layer validation applied to filter. Currently unexploitable (the DAO hardcodes const order = "id" and ignores user-provided order values), but creates an asymmetry worth noting for defense-in-depth. This is a pre-existing pattern across all public-to-private delegation servers, not unique to this PR.
    Remediation: Apply the same validation to the order parameter as is applied to filter, or explicitly clear/drop the order field before forwarding.

  • [stale-doc] fulfillment-service/docs/AUTH.md:574 — The client permissions list in AUTH.md does not include the newly-added StorageTiers/Get and StorageTiers/List endpoints. A reader using this doc to understand client access would not know StorageTiers exists. This extends pre-existing documentation drift (several other services are also missing from this list).
    Remediation: Add Storage Tiers: Get, List to the Client Users list in AUTH.md.

  • [enum-duplication] fulfillment-service/proto/public/osac/public/v1/storage_tier_type.proto:21 — The StorageProtocol enum is defined independently in both osac.public.v1 and osac.private.v1. Values match today (UNSPECIFIED=0, NFS=1, BLOCK=2), and the server uses an explicit switch with a logged fallback for unknown values, which is the correct defensive pattern. If a new protocol is added to the private enum without updating the public one, it will silently map to UNSPECIFIED.

  • [comment-style] fulfillment-service/internal/cmd/cli/describe/storagetier/describe_storagetier_cmd.go — The run method omits the step-by-step inline section comments (// Get the context:, // Get the console:, etc.) present in established describe commands like describe instancetype. Both styles coexist in the codebase, so this is not a violation.


Labels: PR adds new public StorageTiers API to the fulfillment-service storage subsystem

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment storage enhancement New feature or request labels Aug 12, 2026
@wgordon17
wgordon17 force-pushed the feat/OSAC-3014-public-storage-tier-api branch from ad0f7d5 to ac29b87 Compare August 12, 2026 17:31
@wgordon17
wgordon17 marked this pull request as ready for review August 12, 2026 17:31
@openshift-ci
openshift-ci Bot requested review from eliorerz and vladikr August 12, 2026 17:31
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 12, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 5:32 PM UTC · Ended 5:35 PM UTC

Commit: ac29b87 · View workflow run →

coderabbitai[bot]
coderabbitai Bot previously requested changes Aug 12, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@fulfillment-service/internal/cmd/cli/get/storagetier/get_storagetier_cmd.go`:
- Around line 106-159: Update renderTierTable and renderTierDetail in
fulfillment-service/internal/cmd/cli/get/storagetier/get_storagetier_cmd.go
(lines 106-159) to return errors from every fmt.Fprint* call and
tabwriter.Writer.Flush, then propagate those errors through renderStorageTier
and the respective command at lines 66-84. Apply the same output-error
propagation to the storage-tier describe command in
fulfillment-service/internal/cmd/cli/describe/storagetier/describe_storagetier_cmd.go
(lines 88-120), ensuring broken-pipe failures are returned instead of producing
success.

In `@fulfillment-service/internal/servers/storage_tiers_server.go`:
- Around line 214-223: Update the List conversion loop around privateItems and
toPublicTier to log malformed tiers and continue instead of returning the
conversion error; build the result slice with only successfully converted items
and adjust size to match the filtered items count. Keep Get’s existing Internal
error behavior unchanged, and update the affected List test expectations
accordingly.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8696598c-879a-4f50-b239-5a4d1b122027

📥 Commits

Reviewing files that changed from the base of the PR and between 81ecdd8 and ac29b87.

⛔ Files ignored due to path filters (6)
  • fulfillment-service/internal/api/osac/public/v1/storage_tier_type.pb.go is excluded by !**/*.pb.go
  • fulfillment-service/internal/api/osac/public/v1/storage_tier_type_protoopaque.pb.go is excluded by !**/*.pb.go
  • fulfillment-service/internal/api/osac/public/v1/storage_tiers_service.pb.go is excluded by !**/*.pb.go
  • fulfillment-service/internal/api/osac/public/v1/storage_tiers_service.pb.gw.go is excluded by !**/*.pb.gw.go
  • fulfillment-service/internal/api/osac/public/v1/storage_tiers_service_grpc.pb.go is excluded by !**/*.pb.go
  • fulfillment-service/internal/api/osac/public/v1/storage_tiers_service_protoopaque.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (11)
  • fulfillment-service/internal/auth/policies/authz.rego
  • fulfillment-service/internal/cmd/cli/describe/storagetier/describe_storagetier_cmd.go
  • fulfillment-service/internal/cmd/cli/get/get_cmd.go
  • fulfillment-service/internal/cmd/cli/get/storagetier/get_storagetier_cmd.go
  • fulfillment-service/internal/cmd/service/start/grpcserver/start_grpc_server_cmd.go
  • fulfillment-service/internal/cmd/service/start/restgateway/start_rest_gateway_cmd.go
  • fulfillment-service/internal/servers/storage_tiers_server.go
  • fulfillment-service/internal/servers/storage_tiers_server_test.go
  • fulfillment-service/it/it_public_storage_tiers_test.go
  • fulfillment-service/proto/public/osac/public/v1/storage_tier_type.proto
  • fulfillment-service/proto/public/osac/public/v1/storage_tiers_service.proto

Comment thread fulfillment-service/internal/servers/storage_tiers_server.go
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 12, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 5:36 PM UTC · Ended 5:45 PM UTC

Commit: 528f129 · View workflow run →

wgordon17 added a commit to wgordon17/osac that referenced this pull request Aug 12, 2026
A tenant can't act on a malformed tier (0 or >1 backend associations) -- that's a cloud-provider-admin data problem, not something the caller can fix. Failing the entire List response for every tenant over one corrupted admin-side row is bad UX. List now logs and omits malformed tiers (adjusting size to match); Get is unchanged and still returns Internal for the specific tier requested.

Addresses CodeRabbit review feedback on PR osac-project#292.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: Will Gordon <wgordon@redhat.com>
@omer-vishlitzky
omer-vishlitzky dismissed coderabbitai[bot]’s stale review August 12, 2026 17:44

Auto-dismissed: only Prow labels gate merging

GetQuotaGib() no longer exists on the public StorageTierSpec. Removes the
detail-view Quota line from both describe and get, and the compact-table
QUOTA column from get. Protocol rendering is untouched -- same data path,
no functional change.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: Will Gordon <wgordon@redhat.com>
… field

Moves protocol out of the backends array element into the top-level spec
object in both the create payload and the idempotency-check parsing logic,
matching the private proto restructure. Left unfixed, this hook would still
return HTTP 2xx on first run (creating a tier with protocol silently unset)
then fail its own idempotency check on every subsequent hook re-run.

Also fixes two issues found while rewriting this line: BACKEND_ID was
bash-spliced directly into the python3 -c source string, letting a crafted
backend_id value break out of the string literal and execute inside this
admin-scoped hook -- now passed via an environment variable instead. And
the idempotency check's `stderr.write(...) or sys.exit(1)` never actually
exited on failure, since write() returns a truthy byte count that
short-circuits the `or` -- fixed to unconditionally exit.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: Will Gordon <wgordon@redhat.com>
describe storagetier was switched to the public StorageTiers API client
back when it was first added, but was still registered with
help.MarkPrivateAPI -- hiding it from `osac describe --help` for any
tenant not running with --private, even though it works fine for them
and its sibling `get storagetier` command is correctly discoverable. A
test locked in the stale contract by asserting storagetier must carry
the private-API annotation. Removes the wrapper and updates the test's
expectation to match.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: Will Gordon <wgordon@redhat.com>
Fixes 6 pr-review findings on PR osac-project#292: test coverage for
toPublicStorageProtocol's default branch, a Total/Size divergence in
StorageTiersServer.List when malformed tiers are dropped, duplicate
get/describe storagetier rendering logic, a missing describe storagetier
test file, an opaque BUG-005 test reference, and a grammar fix in the
local-storage installer hook. A follow-up quality-gate pass found and
fixed a pagination-consistency bug in the Total fix itself (only adjust
Total when the response provably covers the entire result set) plus a
related simplification and test-helper deduplication.
osac-operator's buf.gen.yaml now reads fulfillment-service's proto
locally (c8f4cb7 migrated it off the BSR pin), removing the
tag-and-republish dependency that previously blocked this change.
Regenerates the private StorageTier client and updates
resolveTierDefinitions() to read protocol from the tier's spec instead
of its first backend association, and drops the removed quota_gib
field from TierDefinition and its extra_vars serialization.
Drops VAST quota creation/validation now that the StorageTier API no
longer carries a quota field, removing the corresponding examples and
documentation across playbooks, roles, and samples.
Aligns the StorageTier creation payload with the same env-var pattern
already used for the existing-tier verification path, and produces
correctly-escaped JSON via json.dumps instead of raw printf
substitution.
Renumbers the relocate-protocol-and-remove-quota migration to 101 to
resolve a collision with a migration added on main, and migrates the
StorageTiersServer's filter validator to the new non-generic
FilterTranslator API (SetDescriptor instead of a type parameter).

Also fixes a latent bug this surfaced: List unconditionally called
SetLimit on the private request, marking it present with value 0 even
when the public request had no limit set, causing the underlying
GenericServer's HasLimit() check to apply a literal zero-row limit.
Guarded the call the same way GenericServer itself does.
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 27, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 9:34 AM UTC · Ended 10:13 AM UTC

Commit: e1cd61d · View workflow run →

@zszabo-rh

Copy link
Copy Markdown
Contributor

/lgtm
/approve

@openshift-ci

openshift-ci Bot commented Aug 27, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: akshaynadkarni, wgordon17, zszabo-rh

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:
  • OWNERS [akshaynadkarni,wgordon17,zszabo-rh]

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@github-actions

Copy link
Copy Markdown

E2E on lgtm

Label lgtm applied — starting expensive e2e (PR run replay).

  • Started: 3/3

@fullsend-ai-review fullsend-ai-review 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.

Note: The following review comments could not be posted on the diff (GitHub returned 422) and are included here instead:

  • .github/workflows/check-generated-code.yaml (file-level): Line 90 · [medium] CI coverage regression

The PR adds osac-metering/metering-service to the workflow path triggers and changes job path filter, but does not add it to the matrix.component list. The generated-code check for metering will never run because no matrix job matches it.

Suggested fix: Add osac-metering/metering-service to the matrix.component list.

  • .github/workflows/check-generated-code.yaml (file-level): Line 9 · [low] scope-creep

The osac-metering CI path triggers and cleanapi dummy package stub are infrastructure fixes necessitated by the private proto gaining the cleanapi annotation. They are a direct consequence of the proto change but could be mentioned in the PR description.

  • osac-installer/charts/osac/templates/hooks/register-local-storage.yaml (file-level): Line 51 · [low] Command Injection (Residual)

Line 51 still interpolates ${BACKEND_RESP} (a mktemp path) into a Python string literal. The PR correctly fixed BACKEND_ID interpolation by using os.environ, but did not apply the same pattern to BACKEND_RESP. Not practically exploitable since mktemp paths have no shell-special characters.

Suggested fix: Consider passing BACKEND_RESP via environment variable for defense-in-depth consistency.

@fullsend-ai-review fullsend-ai-review 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.

See the review comment for full details.

return err
}

renderTierTable(c.console, []*publicv1.StorageTier{tier})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] convention-consistency

When get storagetier resolves to a single tier, it renders a table. The sibling get externalippool command renders a detail view for single-item gets. Both approaches are legitimate UX choices.

osac get storagetier tier-abc123`

// renderTierTable writes a compact table of storage tiers — used when listing all tiers.
func renderTierTable(w *terminal.Console, tiers []*publicv1.StorageTier) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] convention-consistency

renderTierTable takes *terminal.Console while renderStorageTier in the describe command takes io.Writer. Since *terminal.Console implements io.Writer, this is not functional but is an inconsistency within the same resource CLI commands.

Suggested fix: Pick one writer type consistently for both get and describe render functions.

}

// Create private request with same parameters:
privateRequest := &privatev1.StorageTiersListRequest{}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] edge-case

The List method calls SetOffset unconditionally (passing 0 when unset) while SetLimit uses a HasLimit guard. The asymmetry is benign since offset=0 is the default behavior.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:34 AM UTC · Completed 10:13 AM UTC

Commit: e1cd61d · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $13.64

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved enhancement New feature or request go Pull requests that update go code jira/valid-reference lgtm requires-manual-review Review requires human judgment storage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants