feat: remove Fleet entity and fleet_id across the stack - #223
Conversation
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>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository YAML (base), Central YAML (inherited) Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
Amber reviewStatus: Complete VerdictCOMMENT. This is a clean, well-scoped spec-only change that correctly removes the Fleet entity and |
jsell-rh
left a comment
There was a problem hiding this comment.
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_idon the Gateway proto/model and regenerate the SDK, and #151 additionally adds drift-reprovision logic and a new test that treatsfleet_idas part of Gateway identity ("identity fleet ignored"). This PR's stated cascade removesfleet_idfrom 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-establishfleet_idafter 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)
- [Major] Spec registry (
index.spec.md) still listsFleetas a data-model entity, misdirecting/reconcile- Spec Consistency (index.spec.md L9, L31) - [Major] Global architecture still names Fleet as a source-of-truth entity - Spec Consistency (global-architecture.spec.md L141, L255, L1158)
- [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). |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
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>
Amber reviewStatus: Complete VerdictCOMMENT — This is a large but clean, mechanically-consistent removal of the Fleet/ The mechanics are well handled: Test Diff ScrutinyEvery modified assertion in a pre-existing test file was reviewed. The changes in Data destructiveness (informational)The five |
jsell-rh
left a comment
There was a problem hiding this comment.
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, andplugins/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 idempotentALTER TABLE ... ADD COLUMN IF NOT EXISTSspecifically becauseAutoMigrateon an existing table hits the gorm postgresColumnTypesintrospection bug that crashes the integration suite underPreferSimpleProtocol. 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 rawALTER TABLEform, or merge this PR first and rebase them onto it.
Findings Summary (ordered by severity, highest first)
- [Minor]
migrationDropFleetId/migrationDropFleetsTablehave no-opRollbackbodies; a downgrade cannot restore dropped data — call this out in release notes — Migration (migration.go L146, L164) - [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 { |
There was a problem hiding this comment.
[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 { |
There was a problem hiding this comment.
[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>
Amber reviewStatus: 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>
Amber reviewStatus: Complete VerdictAmber Analysis This PR cleanly removes the Fleet/Sector organizational unit and What I verified
Minor
|
jsell-rh
left a comment
There was a problem hiding this comment.
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/.tsartifacts are deleted;main.goplugin import and theauth-bypass-methodslist are updated; no non-generated Go code still referencesgw.FleetId. Protoreserved 3 / reserved "fleet_id"markers preserve gRPC wire compatibility. - Migrations are safe – the five
migrationDropFleetIdmigrations andmigrationDropFleetsTableare add-only, guarded withHasColumn/HasTable, and use date-sortable IDs. The basemigration()structs still declareFleetId, so a fresh integration DB creates then drops the column, meaning the drop path is genuinely exercised (not silently skipped). The rawALTER TABLE ... IF NOT EXISTSrewrite of the column-adding migrations, and the rationale (gormColumnTypesintrospection +PreferSimpleProtocolplaceholder bug), is well documented. - Test Diff Scrutiny – the modified assertions in
authorization_test.go,grpc_interceptor_test.go, andotel_test.godo not flip any guarantee. Each Fleet-based case is re-pointed to an equivalent global-scoped resource (GatewayReleaseService/gateway_releases) orGatewayService, 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 Isolationspec section does not weaken tenancy: Fleet was already documented as "not a security boundary", and the per-gateway RBACGateway Access Isolationsection remains the real boundary. Spec edits acrossdata-model,rbac-enforcement, andsecurityare internally consistent. - CI/e2e ordering – deferring seeding out of
up.shintoseed.sh(withKIND_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
Rollbackon every drop migration is a no-op, sodownwill not restorefleet_idcolumns or thefleetstable. 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)
- [Minor] Drop migrations have no-op rollbacks;
fleet_idcolumns and thefleetstable cannot be restored viadown— 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 { |
There was a problem hiding this comment.
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.


Summary
Removes the top-level Sector/Fleet organizational unit and the
fleet_idfield 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-gatewaygateway:owner/gateway:viewer), not by a fleet grouping. This realizes thelong-standing
TODO: Sector/Fleet removalindata-model.spec.md.Spec changes
data-model.spec.md— drop the Fleet entity (ERD + Fleet Lifecycle /Fleet-Scoped requirements), remove
fleet_idfrom all five entities, add aTop-Level Resources requirement, remove
/fleetsroutes, the Fleets CLIsection, all
--fleet-idflags, the Fleet apply-kind, the fleet-contextsection, and the Fleet design decision.
standards/security/security.spec.md— remove the now-false FleetIsolation requirement (
fleet_idquery scoping).security/rbac-enforcement.spec.md— drop "Fleets" from theplatform:admindenial list; reword the "Fleet is not a security boundary" row.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)
openapi.fleets.yaml+/fleetspaths; dropfleet_idfrom schemas; removeroleBindingsscope enum valuefleet; regenerate.DO NOT EDITgenerated output.plugins/fleets/; dropfleet_idfrom 5 plugins; proto fields markedreserved(wire compat); add-onlyDropColumn/DropTablemigrations; remove FleetService auth-bypass entries.AutoMigrateto raw idempotentALTER TABLE(works around a gorm postgresColumnTypes+PreferSimpleProtocolplaceholder bug that crashed the integration suite).--fleet-idflags +applyFleet case.WatchFleets/FleetReconciler(a no-op today);watchCount 5 → 4.scripts/kind/up.sh,tests/e2e/and pr-test bodies; drop hardcodedfleet_id: \"\"from the web console.Verification
go build✓go vet✓ integration suite ✓ (all plugins green)go build✓go vet✓go build✓go vet✓make check(repository policy) ✓Kept intentionally: proto
reserved \"fleet_id\"markers + encoded.pb.godescriptors (wire compat), the
migrationDropFleetIddrop-migrations, andgeneric English "fleet of gateways" wording in the e2e perf harness.
🤖 Generated with Claude Code