Skip to content

Add project is_active lifecycle state to Platform API#2864

Open
dakshina99 wants to merge 1 commit into
wso2:mainfrom
dakshina99:apip-project-is-active-lifecycle
Open

Add project is_active lifecycle state to Platform API#2864
dakshina99 wants to merge 1 commit into
wso2:mainfrom
dakshina99:apip-project-is-active-lifecycle

Conversation

@dakshina99

Copy link
Copy Markdown

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

  • Add an internal is_active flag to projects.
  • Keep OSS/on-prem behavior identical: projects are created active by default, and
    every operation behaves exactly as before.
  • Let a cloud build create projects inactive and activate them once their external
    counterpart is provisioned — driven entirely from an external plugin, with the
    smallest possible surface added to the public repo.

Approach

  • Data model: add an is_active column (default active) to all four dialect
    schemas and to model.Project; read/write it across every repository query path,
    plus a SetProjectActive method. The flag is internal only and is deliberately
    not exposed on the public api.Project/OpenAPI — on-prem projects are always
    active, so no public consumer needs it.
  • Default behavior: new config projects.activate_on_create (default true)
    honored by CreateProject and by the org-bootstrap default project. A cloud build
    sets it false.
  • Gating: creating/importing resources under an inactive project (REST API,
    application, LLM proxy, MCP proxy, artifact import) is rejected with
    409 PROJECT_NOT_ACTIVE. Reads, lists, and delete stay open.
  • Extensibility: expose a Projects capability group on pdk.Deps
    (create / get / set-active / delete) — mirroring the existing Gateways group — so
    the 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.

⚠️ Migration note for reviewers: the schema is applied via
CREATE TABLE IF NOT EXISTS with no ALTER/versioned migration, so this additive
column reaches fresh installs cleanly but not existing deployments' tables.
An in-place upgrade would fail project queries until the column is added. A
migration story (e.g. startup ALTER TABLE … ADD COLUMN IF NOT EXISTS per dialect)
should be agreed before rollout to any existing environment — this is the first
additive column to hit that gap and is worth settling as a general policy.

User stories

  • As a cloud operator, a project created via APIP is not usable until its external
    (OpenChoreo) counterpart has been provisioned.
  • As an on-prem user, nothing changes — projects are usable the moment they are created.

Documentation

N/A — no public API or user-facing surface changes (the flag is internal; the public
API response schema is unchanged).

Automation tests

  • Unit tests

    Added internal/repository/project_test.gois_active round-trip across all
    read paths (get-by-uuid/handle, list, org list) and SetProjectActive.
    Added TestLLMProxyServiceCreateFailsWhenProjectInactive — asserts the
    409 PROJECT_NOT_ACTIVE guard fires (before provider validation).
    Updated affected fixtures (llm_test.go, organization_test.go).

  • Integration tests

    N/A for this PR.

Security checks

  • Followed secure coding standards in the WSO2 secure engineering guidelines? yes
  • Ran FindSecurityBugs plugin and verified report? no (no security-sensitive change; Go backend)
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets? yes

Samples

N/A

Related PRs

Test environment

  • Go (repo toolchain), SQLite (unit tests). go build, go vet, and go test ./...
    all pass; schema change authored for sqlite/postgres/sqlserver/generic dialects.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Projects 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.

Changes

Project activation lifecycle

Layer / File(s) Summary
Persist project activation state
platform-api/internal/database/schema*, platform-api/internal/model/project.go, platform-api/internal/repository/*
Project schemas and models include is_active; repositories persist, read, list, update, and test the active flag.
Initialize and change project status
platform-api/config/*, platform-api/internal/service/project.go, platform-api/internal/service/organization.go, platform-api/internal/server/server.go
Configuration controls activation on creation, while project services create and update activation state with audit handling.
Reject operations for inactive projects
platform-api/internal/apperror/*, platform-api/internal/service/{api,application,artifact_import,llm,mcp}.go, platform-api/internal/service/llm_test.go
Dependent creation and import flows return ProjectNotActive for inactive projects, with LLM coverage for the new guard.
Expose project operations to plugins
platform-api/pdk/deps.go, platform-api/internal/server/server.go
The PDK adds organization-scoped project operations and receives the configured ProjectService.

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
Loading

Possibly related PRs

Suggested reviewers: ashera96, renuka-fernando, malinthaprasan

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding project active-state lifecycle support to the Platform API.
Description check ✅ Passed The description covers all required sections and includes the purpose, goals, approach, tests, security, and environment details.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 win

Make project resolution mandatory here.

req.ProjectId is required, but projectUUID is initialized from the raw handle and the active-project check is conditional on s.projectRepo != nil. A service instance with a nil repository can therefore create a proxy for an inactive or nonexistent project and persist the handle in ProjectUUID, 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 win

Cover 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3864bef and 390fceb.

📒 Files selected for processing (23)
  • platform-api/config/config.go
  • platform-api/config/default_config.go
  • platform-api/internal/apperror/catalog.go
  • platform-api/internal/apperror/codes.go
  • platform-api/internal/database/schema.postgres.sql
  • platform-api/internal/database/schema.sql
  • platform-api/internal/database/schema.sqlite.sql
  • platform-api/internal/database/schema.sqlserver.sql
  • platform-api/internal/model/project.go
  • platform-api/internal/repository/interfaces.go
  • platform-api/internal/repository/project.go
  • platform-api/internal/repository/project_test.go
  • platform-api/internal/server/server.go
  • platform-api/internal/service/api.go
  • platform-api/internal/service/application.go
  • platform-api/internal/service/artifact_import.go
  • platform-api/internal/service/llm.go
  • platform-api/internal/service/llm_test.go
  • platform-api/internal/service/mcp.go
  • platform-api/internal/service/organization.go
  • platform-api/internal/service/organization_test.go
  • platform-api/internal/service/project.go
  • platform-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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ 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-L40
  • platform-api/internal/database/schema.sqlite.sql#L40-L40
  • platform-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"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 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 to SetProjectActive.
  • platform-api/internal/repository/project.go#L186-L198: add organization_uuid to the UPDATE predicate 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

Comment thread platform-api/pdk/deps.go
Comment on lines +78 to +79
// CreateProject creates a project in an organization (Create).
CreateProject(req *api.CreateProjectRequest, organizationID, actor string) (*api.Project, error)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 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.

Comment thread platform-api/pdk/deps.go
Comment on lines +84 to +85
// SetProjectActive flips a project's active state within an organization.
SetProjectActive(handle, orgID, actor string, isActive bool) (*api.Project, error)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ 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.

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.

1 participant