Add project is_active lifecycle state to Platform API#2864
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughProjects now have a persisted active state controlled by configuration. Creation and activation updates flow through repositories and services, inactive projects are rejected by dependent operations, and project operations are exposed to external plugins. ChangesProject activation lifecycle
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Plugin
participant ProjectService
participant ProjectRepository
participant AuditRepository
Plugin->>ProjectService: SetProjectActive(handle, organizationID, actor, isActive)
ProjectService->>ProjectRepository: Load project and update active state
ProjectService->>AuditRepository: Record UPDATE audit entry
ProjectService-->>Plugin: Return updated project
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.12.2)level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies" Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
platform-api/internal/service/llm.go (1)
1391-1404: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winMake project resolution mandatory here.
req.ProjectIdis required, butprojectUUIDis initialized from the raw handle and the active-project check is conditional ons.projectRepo != nil. A service instance with a nil repository can therefore create a proxy for an inactive or nonexistent project and persist the handle inProjectUUID, bypassing the lifecycle gate and UUID contract. Fail fast when the repository is unavailable and always resolve the project before creation, as the MCP path does.🤖 Prompt for 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. In `@platform-api/internal/service/llm.go` around lines 1391 - 1404, Update the surrounding proxy-creation method to require s.projectRepo before proceeding, returning an appropriate internal error when it is nil. Always resolve req.ProjectId through GetProjectByHandleAndOrgID, enforce project existence, organization ownership, and IsActive, then assign the resolved project.ID to projectUUID; never fall back to the raw request handle.
🧹 Nitpick comments (1)
platform-api/internal/service/organization_test.go (1)
67-67: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCover the inactive bootstrap path.
The helper always uses
ActivateOnCreate: true; add a false-config case asserting the organization’s default project is persisted inactive.🤖 Prompt for 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. In `@platform-api/internal/service/organization_test.go` at line 67, Extend the organization setup test helper coverage to include a configuration with ActivateOnCreate set to false, and assert that the organization’s default project is persisted as inactive. Preserve the existing active-configuration case and use the existing organization/default-project persistence assertions.
🤖 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 `@platform-api/internal/database/schema.postgres.sql`:
- Line 40: Add projects.is_active through the external migration path, including
adding and backfilling existing rows before rollout, rather than relying only on
fresh-install DDL. Apply this migration for
platform-api/internal/database/schema.postgres.sql (line 40),
platform-api/internal/database/schema.sql (line 40),
platform-api/internal/database/schema.sqlite.sql (line 40), and
platform-api/internal/database/schema.sqlserver.sql (line 41), preserving the
default active value required by repository reads and updates.
In `@platform-api/internal/model/project.go`:
- Line 31: Add a versioned forward migration for the projects schema that
creates the projects.is_active column on non-SQLite databases and backfills
existing rows as active. Ensure the migration is registered in the project’s
migration sequence and is safe for existing deployments.
In `@platform-api/internal/repository/interfaces.go`:
- Line 50: Update SetProjectActive in
platform-api/internal/repository/interfaces.go:50-50 to accept orgID, then
update the SetProjectActive implementation in
platform-api/internal/repository/project.go:186-198 to pass that organization ID
and include organization_uuid in the UPDATE predicate alongside the project
UUID.
In `@platform-api/pdk/deps.go`:
- Around line 78-79: Update ProjectService.CreateProject to validate that req is
non-nil before reading req.DisplayName or any other request fields, returning
the service’s standard validation error for nil input instead of panicking.
Preserve the existing project creation flow for valid requests.
- Around line 84-85: Update the SetProjectActive service and
ProjectRepo.SetProjectActive persistence flow to write the supplied actor to
updated_by in the same atomic update as is_active and updated_at. Ensure the
returned project reflects the persisted actor metadata while preserving the
existing activation behavior.
---
Outside diff comments:
In `@platform-api/internal/service/llm.go`:
- Around line 1391-1404: Update the surrounding proxy-creation method to require
s.projectRepo before proceeding, returning an appropriate internal error when it
is nil. Always resolve req.ProjectId through GetProjectByHandleAndOrgID, enforce
project existence, organization ownership, and IsActive, then assign the
resolved project.ID to projectUUID; never fall back to the raw request handle.
---
Nitpick comments:
In `@platform-api/internal/service/organization_test.go`:
- Line 67: Extend the organization setup test helper coverage to include a
configuration with ActivateOnCreate set to false, and assert that the
organization’s default project is persisted as inactive. Preserve the existing
active-configuration case and use the existing organization/default-project
persistence assertions.
🪄 Autofix (Beta)
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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 779a6479-83ce-489c-9a29-a2a556c155c9
📒 Files selected for processing (23)
platform-api/config/config.goplatform-api/config/default_config.goplatform-api/internal/apperror/catalog.goplatform-api/internal/apperror/codes.goplatform-api/internal/database/schema.postgres.sqlplatform-api/internal/database/schema.sqlplatform-api/internal/database/schema.sqlite.sqlplatform-api/internal/database/schema.sqlserver.sqlplatform-api/internal/model/project.goplatform-api/internal/repository/interfaces.goplatform-api/internal/repository/project.goplatform-api/internal/repository/project_test.goplatform-api/internal/server/server.goplatform-api/internal/service/api.goplatform-api/internal/service/application.goplatform-api/internal/service/artifact_import.goplatform-api/internal/service/llm.goplatform-api/internal/service/llm_test.goplatform-api/internal/service/mcp.goplatform-api/internal/service/organization.goplatform-api/internal/service/organization_test.goplatform-api/internal/service/project.goplatform-api/pdk/deps.go
| display_name VARCHAR(255) NOT NULL, | ||
| organization_uuid VARCHAR(40) NOT NULL, | ||
| description VARCHAR(1023), | ||
| is_active SMALLINT NOT NULL DEFAULT 1, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Add projects.is_active through the external migration path before rollout. Fresh-install DDL does not alter existing tables, but repository reads and updates now require this column.
platform-api/internal/database/schema.postgres.sql#L40-L40: add and backfill the PostgreSQL column through the external migration process.platform-api/internal/database/schema.sql#L40-L40: add and backfill the generic database column through the external migration process.platform-api/internal/database/schema.sqlite.sql#L40-L40: add and backfill the SQLite column before deployment.platform-api/internal/database/schema.sqlserver.sql#L41-L41: add and backfill the SQL Server column before deployment.
📍 Affects 4 files
platform-api/internal/database/schema.postgres.sql#L40-L40(this comment)platform-api/internal/database/schema.sql#L40-L40platform-api/internal/database/schema.sqlite.sql#L40-L40platform-api/internal/database/schema.sqlserver.sql#L41-L41
🤖 Prompt for 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.
In `@platform-api/internal/database/schema.postgres.sql` at line 40, Add
projects.is_active through the external migration path, including adding and
backfilling existing rows before rollout, rather than relying only on
fresh-install DDL. Apply this migration for
platform-api/internal/database/schema.postgres.sql (line 40),
platform-api/internal/database/schema.sql (line 40),
platform-api/internal/database/schema.sqlite.sql (line 40), and
platform-api/internal/database/schema.sqlserver.sql (line 41), preserving the
default active value required by repository reads and updates.
Source: Learnings
| Name string `json:"displayName" db:"display_name"` | ||
| OrganizationID string `json:"organizationId" db:"organization_uuid"` // FK to Organization.ID | ||
| Description string `json:"description" db:"description"` | ||
| IsActive bool `json:"isActive" db:"is_active"` |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Add a forward migration for existing deployments.
This field requires projects.is_active, but non-SQLite deployments do not auto-run DDL. Existing databases will fail project reads/inserts without a versioned migration that adds and backfills is_active as active.
🤖 Prompt for 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.
In `@platform-api/internal/model/project.go` at line 31, Add a versioned forward
migration for the projects schema that creates the projects.is_active column on
non-SQLite databases and backfills existing rows as active. Ensure the migration
is registered in the project’s migration sequence and is safe for existing
deployments.
| GetProjectByHandleAndOrgID(handle, orgID string) (*model.Project, error) | ||
| GetProjectsByOrganizationID(orgID string) ([]*model.Project, error) | ||
| UpdateProject(project *model.Project) error | ||
| SetProjectActive(projectId string, isActive bool) error |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Enforce organization scope in the activation mutation. The contract omits orgID, and the implementation updates by project UUID alone.
platform-api/internal/repository/interfaces.go#L50-L50: add organization scope toSetProjectActive.platform-api/internal/repository/project.go#L186-L198: addorganization_uuidto theUPDATEpredicate and pass the organization ID through.
📍 Affects 2 files
platform-api/internal/repository/interfaces.go#L50-L50(this comment)platform-api/internal/repository/project.go#L186-L198
🤖 Prompt for 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.
In `@platform-api/internal/repository/interfaces.go` at line 50, Update
SetProjectActive in platform-api/internal/repository/interfaces.go:50-50 to
accept orgID, then update the SetProjectActive implementation in
platform-api/internal/repository/project.go:186-198 to pass that organization ID
and include organization_uuid in the UPDATE predicate alongside the project
UUID.
Source: Coding guidelines
| // CreateProject creates a project in an organization (Create). | ||
| CreateProject(req *api.CreateProjectRequest, organizationID, actor string) (*api.Project, error) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Validate nil project requests before dereferencing.
ProjectService.CreateProject immediately reads req.DisplayName; a plugin passing nil can trigger a panic instead of a validation error. Add a nil check at the service boundary before accessing request fields.
🤖 Prompt for 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.
In `@platform-api/pdk/deps.go` around lines 78 - 79, Update
ProjectService.CreateProject to validate that req is non-nil before reading
req.DisplayName or any other request fields, returning the service’s standard
validation error for nil input instead of panicking. Preserve the existing
project creation flow for valid requests.
| // SetProjectActive flips a project's active state within an organization. | ||
| SetProjectActive(handle, orgID, actor string, isActive bool) (*api.Project, error) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Persist the actor when changing project activation state.
SetProjectActive accepts actor, but the service only uses it for auditing; ProjectRepo.SetProjectActive updates is_active and updated_at, not updated_by. Persist the actor atomically with the state change so returned and stored project metadata remains accurate.
🤖 Prompt for 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.
In `@platform-api/pdk/deps.go` around lines 84 - 85, Update the SetProjectActive
service and ProjectRepo.SetProjectActive persistence flow to write the supplied
actor to updated_by in the same atomic update as is_active and updated_at.
Ensure the returned project reflects the persisted actor metadata while
preserving the existing activation behavior.
Purpose
Projects in the Platform API are created synchronously and are immediately usable,
with no notion of "provisioned elsewhere yet". The upcoming cloud deployment-pipeline
work needs every APIP project mirrored 1:1 to an external system (an OpenChoreo
project), and that mirroring is asynchronous — there is a window where the APIP
project row exists but its external counterpart does not. During that window the
project must not be usable.
This PR introduces a lifecycle state so a project can exist before its external
provisioning completes and only become usable once activated. It is a behavioral
change to the Platform API (project-scoped operations start depending on project
state), so it is intentionally minimal and behavior-preserving for OSS/on-prem.
Resolves N/A (tracked internally; discussion doc shared with the team).
Goals
is_activeflag to projects.every operation behaves exactly as before.
counterpart is provisioned — driven entirely from an external plugin, with the
smallest possible surface added to the public repo.
Approach
is_activecolumn (default active) to all four dialectschemas and to
model.Project; read/write it across every repository query path,plus a
SetProjectActivemethod. The flag is internal only and is deliberatelynot exposed on the public
api.Project/OpenAPI — on-prem projects are alwaysactive, so no public consumer needs it.
projects.activate_on_create(defaulttrue)honored by
CreateProjectand by the org-bootstrap default project. A cloud buildsets it
false.application, LLM proxy, MCP proxy, artifact import) is rejected with
409 PROJECT_NOT_ACTIVE. Reads, lists, and delete stay open.Projectscapability group onpdk.Deps(create / get / set-active / delete) — mirroring the existing
Gatewaysgroup — sothe cloud project-sync plugin (built on PR Adding external plugin support #2814's pluggable model, from a pinned
version) can drive project lifecycle through the stable public contract. No cloud
logic lands in this repo.
User stories
(OpenChoreo) counterpart has been provisioned.
Documentation
N/A — no public API or user-facing surface changes (the flag is internal; the public
API response schema is unchanged).
Automation tests
Security checks
Samples
N/A
Related PRs
pdkmodel this builds on)Test environment
go build,go vet, andgo test ./...all pass; schema change authored for sqlite/postgres/sqlserver/generic dialects.