Skip to content

feat: remove Fleet entity and fleet_id across the stack - #223

Merged
markturansky merged 11 commits into
mainfrom
remove-fleet-data-model
Sep 1, 2026
Merged

feat: remove Fleet entity and fleet_id across the stack#223
markturansky merged 11 commits into
mainfrom
remove-fleet-data-model

Conversation

@markturansky

@markturansky markturansky commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

Removes the top-level Sector/Fleet organizational unit and the fleet_id
field from the platform end to end — spec, API/OpenAPI, SDKs, backend/gRPC,
CLI, control plane, deploy/e2e, web console, and docs. Gateways, clusters,
databases, releases, and networks become top-level resources; tenancy is
enforced by RBAC (platform-level gateway:creator/platform:admin + per-gateway
gateway:owner/gateway:viewer), not by a fleet grouping. This realizes the
long-standing TODO: Sector/Fleet removal in data-model.spec.md.

Originally opened as the desired-state (spec) change only; the full code
cascade has now been added to this branch so spec and implementation land
together.

Spec changes

  • data-model.spec.md — drop the Fleet entity (ERD + Fleet Lifecycle /
    Fleet-Scoped requirements), remove fleet_id from all five entities, add a
    Top-Level Resources requirement, remove /fleets routes, the Fleets CLI
    section, all --fleet-id flags, the Fleet apply-kind, the fleet-context
    section, and the Fleet design decision.
  • standards/security/security.spec.md — remove the now-false Fleet
    Isolation
    requirement (fleet_id query scoping).
  • security/rbac-enforcement.spec.md — drop "Fleets" from the
    platform:admin denial list; reword the "Fleet is not a security boundary" row.
  • Other platform specs + component docs — align control-plane, oidc,
    keycloak, database, local/openshift-dev, global-architecture, e2e specs and
    CLAUDE.md/DEVELOPMENT.md/skills with the removal (they had contradicted
    data-model.spec.md).

Code cascade (landed in this PR)

Wave Area Scope
2 API/OpenAPI Delete openapi.fleets.yaml + /fleets paths; drop fleet_id from schemas; remove roleBindings scope enum value fleet; regenerate.
3 SDK (Go + TS) Pure regeneration — all fleet files were DO NOT EDIT generated output.
4 BE + gRPC Delete plugins/fleets/; drop fleet_id from 5 plugins; proto fields marked reserved (wire compat); add-only DropColumn/DropTable migrations; remove FleetService auth-bypass entries.
4b Migration fix Rewrite 5 gateways column migrations from AutoMigrate to raw idempotent ALTER TABLE (works around a gorm postgres ColumnTypes + PreferSimpleProtocol placeholder bug that crashed the integration suite).
5 CLI Delete fleet command packages + --fleet-id flags + apply Fleet case.
6 Control plane Delete WatchFleets/FleetReconciler (a no-op today); watchCount 5 → 4.
7 deploy/e2e/web Fleet seeding/discovery removed from scripts/kind/up.sh, tests/e2e/ and pr-test bodies; drop hardcoded fleet_id: \"\" from the web console.

Verification

  • api-server: go buildgo vet ✓ integration suite ✓ (all plugins green)
  • control-plane: go buildgo vet
  • cli: go buildgo vet
  • make check (repository policy) ✓
  • web console: not runnable locally (Node 24 required, v22 present) — relying on CI

Kept intentionally: proto reserved \"fleet_id\" markers + encoded .pb.go
descriptors (wire compat), the migrationDropFleetId drop-migrations, and
generic English "fleet of gateways" wording in the e2e perf harness.

🤖 Generated with Claude Code

The data model previously included a top-level Sector/Fleet organizational
unit that grouped clusters, databases, releases, gateways, and networks via a
fleet_id. This layer is being removed: gateways, clusters, databases, releases,
and networks become top-level resources, and tenancy is enforced by RBAC
(platform-level creator/admin + per-gateway owner/viewer), not by a fleet
grouping.

- data-model.spec.md: drop the Fleet entity from the ERD, remove fleet_id FKs
  from all five entities, replace the Fleet-Scoped Resources requirement with a
  Top-Level Resources requirement, remove the /fleets API routes, the Fleets
  CLI section, all --fleet-id flags, the Fleet apply-kind, the fleet-context
  section, and the Fleet design decision.
- standards/security/security.spec.md: remove the now-false "Fleet Isolation"
  requirement (fleet_id query scoping); per-gateway Gateway Access Isolation is
  the tenant boundary.
- security/rbac-enforcement.spec.md: drop Fleets from the platform:admin denial
  list and reword the "Fleet is not a security boundary" design row.

This is the desired-state (spec) change only; the code cascade (OpenAPI, SDKs,
backend/gRPC, CLI, control plane, e2e) lands in follow-up PRs per the
full-stack pipeline.

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

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 18921919-63fa-4375-bdcc-561550d41909

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@jsell-rh

jsell-rh commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Amber review: comment

Amber review

Status: Complete

Verdict

COMMENT. This is a clean, well-scoped spec-only change that correctly removes the Fleet entity and fleet_id from data-model.spec.md, security.spec.md, and rbac-enforcement.spec.md, and the three edited files are internally consistent with each other. It stops short of a coherent desired-state, though: sibling specs still declare Fleet as a first-class entity and source of truth, so the spec layer now contradicts itself in a few places that should be reconciled before or alongside this landing.

@jsell-rh jsell-rh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict

COMMENT. This is a clean, well-scoped spec-only change that correctly removes the Fleet entity and fleet_id from data-model.spec.md, security.spec.md, and rbac-enforcement.spec.md, and the three edited files are internally consistent with each other. It stops short of a coherent desired-state, though: sibling specs still declare Fleet as a first-class entity and source of truth, so the spec layer now contradicts itself in a few places that should be reconciled before or alongside this landing.

Summary

The removal of the Fleet grouping in favor of RBAC-based tenancy is coherent and the prose in the three touched files reads well. My findings are all about desired-state consistency: the PR claims to be "the desired-state (spec) change," but it leaves Fleet referenced as a real entity in other spec files, including the machine-readable registry that drives /reconcile.

Findings

Major - Spec registry still lists Fleet as a data-model entity (Spec Consistency)

specs/index.spec.md L31 still lists Fleet as a Primary Entity for platform/data-model.spec.md, and L9 advertises "fleet management" in the platform overview. This registry is the machine-readable index the /reconcile skill consumes, so after this PR the automation is pointed at an entity the data model now forbids. This file is not in the diff, so the PR introduces the inconsistency without correcting it. Fix: drop Fleet from the L31 entity list and reword L9.

Major - Global architecture still names Fleet as a source-of-truth entity (Spec Consistency)

specs/platform/global-architecture.spec.md continues to describe PostgreSQL as the source of truth for "Fleet, Gateway, ManagedCluster resources" (L141, L255, L1158). These are the Fleet entity, not the colloquial "fleet of gateways," and they now contradict the data-model removal. Fix: drop Fleet from those source-of-truth enumerations.

Minor - e2e-testing spec still seeds a Fleet and reuses its id in create bodies (Spec Consistency)

specs/platform/e2e-testing.spec.md still specifies seeding "one fleet" and reusing "the seeded fleet ... ids" in each gateway create body (L797, L845, L865). This directly contradicts the new rule that create contracts "SHALL NOT include a fleet_id field." (Most other "fleet" mentions in that file mean a group of gateways and are fine.) Fix: remove the seeded-Fleet id from the create-body description.

None of these block the core change; they are consistency gaps in the spec layer that should be closed so the desired state is coherent.

Cross-PR coordination

The following in-flight work makes design decisions that are incompatible with removing Fleet, and needs a maintainer decision or a defined merge order:

  • #185 specifies net-new control-plane periodic world-synchronization work that enqueues and reconciles the Fleet entity (Fleet listed explicitly among the resources requiring inventory polling and desired-state enqueue). This PR removes Fleet and its follow-up plan deletes WatchFleets/FleetReconciler. Maintainers must decide whether world-sync includes Fleet and order the two specs so they do not describe syncing a removed entity.

  • #151 and #210 are competing interface changes to the Gateway data model: both retain fleet_id on the Gateway proto/model and regenerate the SDK, and #151 additionally adds drift-reprovision logic and a new test that treats fleet_id as part of Gateway identity ("identity fleet ignored"). This PR's stated cascade removes fleet_id from the Gateway contract and reserves the corresponding proto field number. The proto field-number reservation and the SDK regeneration must be coordinated, and the drift-identity field set must be reconciled, so these do not re-establish fleet_id after removal.

  • #207 applies a cross-cutting trace-correlation change to every plugin, including plugins/fleets/, which this PR's cascade plans to delete. This needs an ordering decision so the Fleet-plugin additions are not reintroduced after the plugin is removed.

Findings Summary (ordered by severity, highest first)

  1. [Major] Spec registry (index.spec.md) still lists Fleet as a data-model entity, misdirecting /reconcile - Spec Consistency (index.spec.md L9, L31)
  2. [Major] Global architecture still names Fleet as a source-of-truth entity - Spec Consistency (global-architecture.spec.md L141, L255, L1158)
  3. [Minor] e2e-testing spec still seeds a Fleet and reuses its id in gateway create bodies - Spec Consistency (e2e-testing.spec.md L797, L845, L865)

Convention Checklist

Convention Result
Spec internally consistent (edited files) Pass
Spec consistent across the desired-state layer Fail
Cross-references valid Pass
Security boundary correctly restated (RBAC vs fleet) Pass

The HyperShell API server provides a control plane for deploying and managing distributed API gateways across multiple Kubernetes clusters and cloud providers.

**TODO: Sector/Fleet removal** - The model previously included a top-level "Sector" organizational unit, but this abstraction is being removed. All gateways are part of the same fleet, and there's no need to sectorize. The API currently uses "fleet" terminology (`/api/hypershell/v1/fleets`), but this entire layer will be removed in a future PR. Gateways, clusters, databases, releases, and networks will become top-level resources without fleet/sector scoping.
Gateways, clusters, databases, releases, and networks are **top-level resources**. An earlier model included a top-level "Sector" (later renamed "Fleet") organizational unit that grouped these resources via a `fleet_id`; that layer has been removed. There is no sectorization: all gateways belong to the same platform, and tenancy is enforced by RBAC (platform-level `gateway:creator`/`platform:admin` and per-gateway `gateway:owner`/`gateway:viewer`), not by a fleet grouping. See [`security/rbac-enforcement.spec.md`](../security/rbac-enforcement.spec.md).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The removal here is correct and well-worded, but it makes the wider spec layer inconsistent. specs/index.spec.md (registry L31, and "fleet management" at L9) still lists Fleet as a primary data-model entity, and specs/platform/global-architecture.spec.md (L141, L255, L1158) still names Fleet as a source-of-truth entity. Since the registry drives the /reconcile skill, please update those siblings so the desired state does not contradict itself.

### Requirement: Top-Level Resources

### Requirement: Fleet-Scoped Resources
ManagedCluster, ManagedDatabase, GatewayRelease, Gateway, and GatewayNetwork SHALL be top-level resources. They SHALL NOT be scoped by a fleet or sector grouping, and their create and update contracts SHALL NOT include a `fleet_id` field.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This new rule ("create and update contracts SHALL NOT include a fleet_id") conflicts with specs/platform/e2e-testing.spec.md L797/L845/L865, which still specify seeding a Fleet and reusing "the seeded fleet ... ids" in each gateway create body. Recommend updating that spec so no gateway create body carries a fleet id.

user and others added 8 commits August 28, 2026 19:51
Wave 2 of the Fleet removal cascade. Deletes the Fleet resource definition
(openapi.fleets.yaml, /fleets paths, Fleet/FleetList/FleetPatchRequest
schemas) and strips the fleet_id field from Gateway, GatewayNetwork,
GatewayRelease, ManagedCluster, and ManagedDatabase (schema, create, and
patch requests). Drops the fleet_id property and the `fleet` scope enum
value from RoleBinding. Regenerates the Go OpenAPI client from the specs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wave 3 of the Fleet removal cascade. Regenerates the Go and TypeScript
SDKs from the updated OpenAPI spec: drops the Fleet resource (fleet.go,
fleet_api.go, fleet.ts, fleet_api.ts) and the fleet_id field from the
Gateway, GatewayNetwork, GatewayRelease, ManagedCluster, ManagedDatabase,
and RoleBinding types. Pure regeneration; no hand edits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rces

Wave 4 (backend + gRPC) of the fleet removal. PostgreSQL and the API
contract no longer model a Fleet tenant scope; resources are owned via
role bindings instead.

- Delete the fleets plugin (service, DAO, handlers, gRPC, migration)
  and drop its main.go side-effect import and OIDC watch bypass entry.
- Delete fleets.proto and its generated stubs; add reserved field
  numbers/names for fleet_id in the 5 entity protos to preserve wire
  compatibility (never reuse a field number or name).
- Strip FleetId from the 5 plugins (model, presenters, handlers,
  gRPC handlers) and remove the now-dead FindSoleInFleet lookup.
- Add drop migrations: DropColumn fleet_id on each entity (the column
  was NOT NULL with no default, so existing DBs must drop it) and
  DropTable fleets, appended without editing prior create migrations.
- Repoint Fleet-scoped RBAC tests to gateway_releases; adjust the
  embedded-OpenAPI operation-count assertion (42 -> 37).

Build and vet pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The five gateways migrations that add columns to the existing table used
tx.AutoMigrate, which invokes the postgres migrator's ColumnTypes
introspection. That query places a clause.Expr (CURRENT_SCHEMA()) after a
scalar bind parameter; under the testcontainer stack (gorm
PreferSimpleProtocol + lib/pq) the ordering breaks placeholder renumbering
and fails with "pq: got 2 parameters but the statement requires 1", which
crashed the whole gateways migration at setup.

Rewrite those five migrations (provisioning fields, supervisor_image,
credential_driver, console_address, active_sandbox_count) to idempotent raw
"ALTER TABLE gateways ADD COLUMN IF NOT EXISTS ..." DDL, matching the
managedDatabases pattern. Column types stay schema-equivalent to what
AutoMigrate produced (int -> bigint), and migration IDs are unchanged so
already-migrated environments are unaffected. The create and probe-based drop
migrations are untouched (they do not hit the ColumnTypes path).

Fixing the crash unmasked two credential_driver tests that compared the raw
input JSON byte-for-byte against the jsonb-canonicalized read-back; switch
those read-back assertions to MatchJSON so jsonb whitespace normalization is
compared semantically.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fleet was removed platform-wide. Delete the create/get/delete/list fleet
subcommands and their registrations, drop FleetsPath/FleetPath from the URL
helpers, remove the "Fleet" case from apply, drop the --fleet-id flag (field,
example, flag registration, and request mapping) from the six create
subcommands that carried it, and remove fleet_id from the default list columns
of the affected resources.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fleet was removed platform-wide and its gRPC service/message types no longer
exist in the API server proto. Delete FleetReconciler and the WatchFleets
watch loop, drop the fleet watch goroutine and fleetReconciler wiring from the
controller entrypoint, and decrement the base watch count from 5 to 4. Reword
the resolveDatabaseConfig error to reference the gateway instead of a fleet,
and update the stale "Fleet" span-kind labels in the otel tests to a live kind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…console

Fleet was removed platform-wide. Remove the FleetService/WatchFleets entry
from the kind and openshift auth-bypass-methods, delete the default-Fleet
creation and fleet_id wiring from the kind seeding script (readiness probe now
hits /gateways), and drop the fleet_id discovery and request fields from the
e2e and pr-test gateway-creation flows. In the web console, remove fleet_id
from the provisionGateway create payload and from the gateway test/e2e
fixtures, matching the fleet-less generated SDK type.

Arbitrary strings that merely reuse the word "fleet" are left as-is: the
route-template generalization example, the obsolete-route 404 test, and the
fan-out domain-probe event names.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Align documentation and specs with the Fleet removal so they no longer
contradict data-model.spec.md (the canonical removal spec):

- CLAUDE.md (root + api-server): drop Fleet row and fleet_id columns from
  domain-model tables; note resources are top-level with RBAC-based tenancy
- DEVELOPMENT.md: drop fleet lookup + fleet_id from the gateway create example
- specs/platform: remove FleetService/WatchFleets auth-bypass, fleet_id from
  gateway create/keycloak/database contracts and examples, and Fleet from
  control-plane watch/seed/source-of-truth descriptions
- skills (full-stack-pipeline, ibm-cluster, RECONCILE): drop fleet_id from
  examples and mark the reconcile ledger's Fleet requirements as removed

Generic English "fleet of gateways" wording (e2e scale-up) is left as-is;
proto reserved markers and drop-migrations are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@markturansky markturansky changed the title spec: remove Fleet entity and fleet_id from the data model feat: remove Fleet entity and fleet_id across the stack Aug 29, 2026
@jsell-rh

jsell-rh commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Amber review: comment

Amber review

Status: Complete

Verdict

COMMENT — This is a large but clean, mechanically-consistent removal of the Fleet/fleet_id concept across spec, API/OpenAPI, SDKs, gRPC, CLI, control plane, deploy/e2e, and web. The desired-state change is coherent, wire compatibility is preserved via proto reserved, and the migrations are idempotent. I found no blockers in this PR; the actionable items are cross-PR coordination and a couple of minor notes.

The mechanics are well handled: .pb.go fields are dropped with reserved 3; reserved "fleet_id" (wire compat), RBAC test cases that referenced the removed fleets resource were substituted with equivalent gateway_releases/gateways cases that preserve the original guarantees (viewer denied, creator allowed, empty bindings denied), and the historical create migrations are left intact while new guarded drop migrations remove the columns/table. Placement tests correctly drop FleetId assertions rather than flip a meaningful contract.

Test Diff Scrutiny

Every modified assertion in a pre-existing test file was reviewed. The changes in pkg/rbac/authorization_test.go, pkg/rbac/grpc_interceptor_test.go, and plugins/gateways/placement_test.go are substitutions/removals forced by the deletion of the Fleet type and FleetService, not silent contract weakenings. Each renamed test still proves the same authorization/placement invariant against a surviving resource. No optional→required tightening or accept→reject flip was introduced. This is consistent with the PR's stated intent.

Data destructiveness (informational)

The five migrationDropFleetId migrations plus migrationDropFleetsTable permanently drop fleet_id columns and the fleets table. This is intentional and matches the PR goal (tenancy moves to RBAC). The migrations are correctly guarded with HasColumn/HasTable, so they are idempotent and safe to re-run. Note only that the Rollback bodies are no-ops, so a downgrade will not restore the dropped data — acceptable for a permanent removal, but worth being explicit about in release notes.

@jsell-rh jsell-rh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict

COMMENT — This is a large but clean, mechanically-consistent removal of the Fleet/fleet_id concept across spec, API/OpenAPI, SDKs, gRPC, CLI, control plane, deploy/e2e, and web. The desired-state change is coherent, wire compatibility is preserved via proto reserved, and the migrations are idempotent. I found no blockers in this PR; the actionable items are cross-PR coordination and a couple of minor notes.

The mechanics are well handled: .pb.go fields are dropped with reserved 3; reserved "fleet_id" (wire compat), RBAC test cases that referenced the removed fleets resource were substituted with equivalent gateway_releases/gateways cases that preserve the original guarantees (viewer denied, creator allowed, empty bindings denied), and the historical create migrations are left intact while new guarded drop migrations remove the columns/table. Placement tests correctly drop FleetId assertions rather than flip a meaningful contract.

Test Diff Scrutiny

Every modified assertion in a pre-existing test file was reviewed. The changes in pkg/rbac/authorization_test.go, pkg/rbac/grpc_interceptor_test.go, and plugins/gateways/placement_test.go are substitutions/removals forced by the deletion of the Fleet type and FleetService, not silent contract weakenings. Each renamed test still proves the same authorization/placement invariant against a surviving resource. No optional→required tightening or accept→reject flip was introduced. This is consistent with the PR's stated intent.

Data destructiveness (informational)

The five migrationDropFleetId migrations plus migrationDropFleetsTable permanently drop fleet_id columns and the fleets table. This is intentional and matches the PR goal (tenancy moves to RBAC). The migrations are correctly guarded with HasColumn/HasTable, so they are idempotent and safe to re-run. Note only that the Rollback bodies are no-ops, so a downgrade will not restore the dropped data — acceptable for a permanent removal, but worth being explicit about in release notes.

Cross-PR coordination

  • #207 (reconcile-to-request trace correlation): This PR adds the trace-correlation feature onto the Fleet resource — it modifies plugins/fleets/service.go (fleet.CaptureTraceContext(ctx)), plugins/fleets/model.go, grpc_presenter.go, plugin.go, and plugins/fleets/migration.go, i.e. exactly the plugin this PR deletes wholesale. These two cannot both land as-is: maintainers must decide whether Fleet is removed, and if this PR lands first, #207 must drop its entire Fleet-plugin portion and re-target its trace work at the surviving resources. This is a design/ownership decision plus a merge-order dependency, not a textual merge conflict.

  • #210 (reconcile gateway version) and #151 (gate gateway re-provisioning): Both add a new gateways-table column migration using tx.AutoMigrate(&Gateway{}) on the already-existing table (migrationAddGatewayVersion, migrationAddGenerationTracking). This PR's Wave-4b fix rewrites every such migration to raw idempotent ALTER TABLE ... ADD COLUMN IF NOT EXISTS specifically because AutoMigrate on an existing table hits the gorm postgres ColumnTypes introspection bug that crashes the integration suite under PreferSimpleProtocol. If either PR merges alongside this one without adopting the raw-DDL pattern, it reintroduces the exact failure mode this PR is fixing. Coordinate so those migrations switch to the raw ALTER TABLE form, or merge this PR first and rebase them onto it.

Findings Summary (ordered by severity, highest first)

  1. [Minor] migrationDropFleetId / migrationDropFleetsTable have no-op Rollback bodies; a downgrade cannot restore dropped data — call this out in release notes — Migration (migration.go L146, L164)
  2. [Minor] Column drops still route through Migrator().HasColumn/DropColumn, whereas the additive migrations were deliberately moved to raw DDL to dodge the introspection bug; the integration suite is green, so this is only a consistency note — Migration (migration.go L141)

Convention Checklist

Convention Result
No panic() in production code Pass
Errors wrapped with fmt.Errorf context Pass
No secrets in logs or responses Pass
Input validated Pass
gRPC wire compatibility preserved (reserved) Pass
OpenAPI client not hand-edited (regenerated) Pass
Migrations idempotent / reconcile pattern Pass
Test diffs preserve prior guarantees Pass
Conventional commit messages Pass

}
return nil
},
Rollback: func(tx *gorm.DB) error {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Minor] The Rollback here is a no-op, so a downgrade after this migration will not restore the dropped fleet_id column or its data. That is reasonable for a deliberate, permanent removal, but please make the irreversibility explicit in release notes/changelog so operators don't assume a rollback recovers fleet associations. The HasColumn guard correctly keeps the migration idempotent.

}
return nil
},
Rollback: func(tx *gorm.DB) error {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Minor] Same no-op Rollback note for the fleets table drop — a downgrade won't recreate the table. Fine given the platform-wide removal; just document it.

kind-up deploys the baseline placeholder image first and, in parallel, CI
waits for the working-tree Konflux build and swaps it in afterward. Seeding
ran inside kind-up, i.e. against the baseline image, so any PR that changes a
create-request contract (here: removing the required fleet_id field) had its
seed rejected with HTTP 400 before the swap. Seeding then aborted, the cnpg
openshell-db ManagedDatabase was never created, and the e2e cnpg job failed at
"Could not discover CNPG database_id" -- a split-brain run that set the world
up with main's image but asserted against the PR's image.

Extract the seeding block from up.sh into scripts/kind/seed.sh (self-contained:
sources lib.sh, binds its own port-forward, honors KIND_SEED_STRICT). up.sh
seeds inline by default for local dev; CI sets KIND_SKIP_SEED=true on kind-up
and runs `make kind-seed` after "Swap component images", so the seed exercises
the PR's own request contract. KIND_SEED_STRICT makes a rejected create fail
the job at the seed step with the real HTTP error instead of surfacing later as
a confusing discovery miss.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jsell-rh

jsell-rh commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Amber review

Status: Stopped

The pull request head changed before Amber posted the review. A later job can review the new head.

Rebuilding up.sh in the previous commit dropped its executable bit, so
`make kind-up` (which runs the script directly) failed with "Permission
denied" / exit 127 before reaching the deferred seed step. Restore mode 0755.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jsell-rh

jsell-rh commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Amber review: approve

Amber review

Status: Complete

Verdict

Amber Analysis

This PR cleanly removes the Fleet/Sector organizational unit and fleet_id end-to-end (spec, OpenAPI/proto, SDKs, backend, CLI, control plane, deploy/e2e, web console, docs), realizing the long-standing TODO: Sector/Fleet removal. The execution is careful: destructive DB changes are guarded and idempotent, proto field numbers are reserved for wire compatibility, and the RBAC test coverage is re-pointed rather than deleted, so I found no blocking issues.

What I verified

  • No dangling references – all plugins/fleets, FleetService, WatchFleets, fleet_api, fleet.go/.ts artifacts are deleted; main.go plugin import and the auth-bypass-methods list are updated; no non-generated Go code still references gw.FleetId. Proto reserved 3 / reserved "fleet_id" markers preserve gRPC wire compatibility.
  • Migrations are safe – the five migrationDropFleetId migrations and migrationDropFleetsTable are add-only, guarded with HasColumn/HasTable, and use date-sortable IDs. The base migration() structs still declare FleetId, so a fresh integration DB creates then drops the column, meaning the drop path is genuinely exercised (not silently skipped). The raw ALTER TABLE ... IF NOT EXISTS rewrite of the column-adding migrations, and the rationale (gorm ColumnTypes introspection + PreferSimpleProtocol placeholder bug), is well documented.
  • Test Diff Scrutiny – the modified assertions in authorization_test.go, grpc_interceptor_test.go, and otel_test.go do not flip any guarantee. Each Fleet-based case is re-pointed to an equivalent global-scoped resource (GatewayReleaseService / gateway_releases) or GatewayService, preserving the same "viewer cannot access non-gateway resource", "creator/global can", "no bindings denied", and "delete-method detection" contracts. No optional→required tightening, no removed coverage.
  • Security posture unchanged – removing the Fleet Isolation spec section does not weaken tenancy: Fleet was already documented as "not a security boundary", and the per-gateway RBAC Gateway Access Isolation section remains the real boundary. Spec edits across data-model, rbac-enforcement, and security are internally consistent.
  • CI/e2e ordering – deferring seeding out of up.sh into seed.sh (with KIND_SKIP_SEED / KIND_SEED_STRICT) so the seed runs after the working-tree image swap is a sound fix for contract-change validation.

Minor

  • The Rollback on every drop migration is a no-op, so down will not restore fleet_id columns or the fleets table. This is the correct choice for an intentional destructive removal, but it should be an explicit, acknowledged decision (the data is unrecoverable via migration rollback). See inline note.

@jsell-rh jsell-rh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict

Amber Analysis

This PR cleanly removes the Fleet/Sector organizational unit and fleet_id end-to-end (spec, OpenAPI/proto, SDKs, backend, CLI, control plane, deploy/e2e, web console, docs), realizing the long-standing TODO: Sector/Fleet removal. The execution is careful: destructive DB changes are guarded and idempotent, proto field numbers are reserved for wire compatibility, and the RBAC test coverage is re-pointed rather than deleted, so I found no blocking issues.

What I verified

  • No dangling references – all plugins/fleets, FleetService, WatchFleets, fleet_api, fleet.go/.ts artifacts are deleted; main.go plugin import and the auth-bypass-methods list are updated; no non-generated Go code still references gw.FleetId. Proto reserved 3 / reserved "fleet_id" markers preserve gRPC wire compatibility.
  • Migrations are safe – the five migrationDropFleetId migrations and migrationDropFleetsTable are add-only, guarded with HasColumn/HasTable, and use date-sortable IDs. The base migration() structs still declare FleetId, so a fresh integration DB creates then drops the column, meaning the drop path is genuinely exercised (not silently skipped). The raw ALTER TABLE ... IF NOT EXISTS rewrite of the column-adding migrations, and the rationale (gorm ColumnTypes introspection + PreferSimpleProtocol placeholder bug), is well documented.
  • Test Diff Scrutiny – the modified assertions in authorization_test.go, grpc_interceptor_test.go, and otel_test.go do not flip any guarantee. Each Fleet-based case is re-pointed to an equivalent global-scoped resource (GatewayReleaseService / gateway_releases) or GatewayService, preserving the same "viewer cannot access non-gateway resource", "creator/global can", "no bindings denied", and "delete-method detection" contracts. No optional→required tightening, no removed coverage.
  • Security posture unchanged – removing the Fleet Isolation spec section does not weaken tenancy: Fleet was already documented as "not a security boundary", and the per-gateway RBAC Gateway Access Isolation section remains the real boundary. Spec edits across data-model, rbac-enforcement, and security are internally consistent.
  • CI/e2e ordering – deferring seeding out of up.sh into seed.sh (with KIND_SKIP_SEED / KIND_SEED_STRICT) so the seed runs after the working-tree image swap is a sound fix for contract-change validation.

Minor

  • The Rollback on every drop migration is a no-op, so down will not restore fleet_id columns or the fleets table. This is the correct choice for an intentional destructive removal, but it should be an explicit, acknowledged decision (the data is unrecoverable via migration rollback). See inline note.

Cross-PR coordination

A separate open pull request introduces a new GatewayProfile platform resource and, in doing so, is built on assumptions this PR removes: it keeps FleetId on the Gateway model/request structs and retains the Fleet-based RBAC test cases (TestIsAuthorized_GatewayViewerCannotAccessFleets, TestIsAuthorized_GatewayCreatorCanAccessFleets) that this PR renames/repurposes, while also editing the same RBAC authorization test files and the same gateways proto/model/migration surface. Because that PR treats Fleet/fleet_id as present and adds a new globally-scoped resource that must be placed in the four-role RBAC model, the maintainers need to decide a merge order and reconcile the design: whichever lands first, the other requires rework (drop fleet_id from the new gateway changes, rebase the RBAC tests onto the post-Fleet surface, and decide how GatewayProfile fits the platform-level allowlist that this PR reshapes). This is a design/ordering decision, not a mechanical merge conflict.

Findings Summary (ordered by severity, highest first)

  1. [Minor] Drop migrations have no-op rollbacks; fleet_id columns and the fleets table cannot be restored via down — confirm this is an accepted, irreversible removal (migration.go).

Convention Checklist

Convention Result
No panic() in production code Pass
Errors wrapped with fmt.Errorf context Pass
errors.IsNotFound / idempotent guards handled Pass
No secrets in logs or responses Pass
Reconcile pattern (idempotent migrations, guarded drops) Pass
Proto wire compatibility (reserved field numbers) Pass
OpenAPI client not manually edited (regenerated) Pass
Image references consistent across manifests Pass
Test Diff Scrutiny (no removed guarantees) Pass
Conventional commit messages Pass

}
return nil
},
Rollback: func(tx *gorm.DB) error {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The Rollback here (and in migrationDropFleetsTable, plus the four sibling migrationDropFleetId migrations in the other plugins) is a no-op, so down will not recreate the fleet_id column or repopulate its data. That is the right call for an intentional, destructive removal, but it makes the rollback irreversible — worth calling out explicitly so operators know a rollback leaves the schema without fleet_id rather than restoring it. The forward path itself is correctly guarded with HasColumn/HasTable and is idempotent.

@markturansky
markturansky added this pull request to the merge queue Sep 1, 2026
Merged via the queue into main with commit e4e4e55 Sep 1, 2026
23 checks passed
@markturansky
markturansky deleted the remove-fleet-data-model branch September 1, 2026 02:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants