feat: implement governed project portfolio
This commit is contained in:
@@ -1,16 +1,46 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from sqlalchemy import func
|
||||
|
||||
from govoplan_core.core.access import (
|
||||
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
|
||||
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
|
||||
)
|
||||
from govoplan_core.core.module_guards import (
|
||||
drop_table_retirement_provider,
|
||||
persistent_table_uninstall_guard,
|
||||
)
|
||||
from govoplan_core.core.modules import (
|
||||
CapabilityDocumentation,
|
||||
DocumentationLink,
|
||||
DocumentationTopic,
|
||||
FrontendModule,
|
||||
FrontendRoute,
|
||||
MigrationSpec,
|
||||
ModuleContext,
|
||||
ModuleInterfaceProvider,
|
||||
ModuleManifest,
|
||||
NavItem,
|
||||
PermissionDefinition,
|
||||
RoleTemplate,
|
||||
)
|
||||
from govoplan_core.core.provider_governance import (
|
||||
ModuleArchitectureDeclaration,
|
||||
ModuleArchitectureDocumentation,
|
||||
ModuleMaturityEvidence,
|
||||
)
|
||||
from govoplan_core.core.search import SearchSourceProviderRegistration
|
||||
from govoplan_core.core.views import ViewSurface
|
||||
from govoplan_core.db.base import Base
|
||||
from govoplan_projects.backend.acl import ProjectScopeAclProvider
|
||||
from govoplan_projects.backend.db import models as project_models
|
||||
from govoplan_projects.backend.search_source import create_projects_search_source
|
||||
from govoplan_projects.backend.service import (
|
||||
CAPABILITY_PROJECTS_REGISTRY,
|
||||
SqlProjectRegistry,
|
||||
)
|
||||
|
||||
|
||||
MODULE_ID = "projects"
|
||||
@@ -30,8 +60,97 @@ OPTIONAL_DEPENDENCIES = (
|
||||
"connectors",
|
||||
"search",
|
||||
"notifications",
|
||||
"reporting",
|
||||
"risk_compliance",
|
||||
)
|
||||
|
||||
ARCHITECTURE = ModuleArchitectureDeclaration(
|
||||
layer="domain_capability",
|
||||
kind="domain",
|
||||
maturity="vertical_slice",
|
||||
evidence=(
|
||||
ModuleMaturityEvidence(
|
||||
kind="documentation",
|
||||
reference="docs/PROJECTS_DOMAIN_BOUNDARY.md",
|
||||
summary=(
|
||||
"Defines portfolio, project, outcome, governance, and integration "
|
||||
"ownership."
|
||||
),
|
||||
),
|
||||
ModuleMaturityEvidence(
|
||||
kind="test",
|
||||
reference="tests/test_project_service.py",
|
||||
summary=(
|
||||
"Proves immutable revisions, OCC, idempotency, tenant isolation, "
|
||||
"restricted memberships, state transitions, and search rechecks."
|
||||
),
|
||||
),
|
||||
),
|
||||
known_limits=(
|
||||
"OpenProject transport and synchronization remain connector-owned provider work.",
|
||||
"Task execution remains in Tasks or Tickets; Projects links work without duplicating its lifecycle.",
|
||||
"The first WebUI edits core planning fields; advanced outcome, capacity, and benefit structures remain available through the governed API.",
|
||||
),
|
||||
supported_authority_modes=(
|
||||
"native_authoritative",
|
||||
"external_authoritative",
|
||||
"external_mirror",
|
||||
"governed_sync",
|
||||
"governance_overlay",
|
||||
"linked_reference",
|
||||
),
|
||||
owned_concepts=(
|
||||
"portfolio and project identity",
|
||||
"project goals and intended outcomes",
|
||||
"project dependencies and capacity assumptions",
|
||||
"project benefit and change-impact review",
|
||||
),
|
||||
non_owned_concepts=(
|
||||
"actionable task and ticket lifecycle",
|
||||
"risk and control lifecycle",
|
||||
"report calculation and presentation",
|
||||
"external project-system transport",
|
||||
),
|
||||
documentation=ModuleArchitectureDocumentation(
|
||||
operations=("docs/PROJECTS_DOMAIN_BOUNDARY.md",),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _router(context: ModuleContext):
|
||||
del context
|
||||
from govoplan_projects.backend.router import router
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _registry(context: ModuleContext) -> SqlProjectRegistry:
|
||||
del context
|
||||
return SqlProjectRegistry()
|
||||
|
||||
|
||||
def _tenant_summary(session, tenant_id: str) -> dict[str, int]:
|
||||
counts = {
|
||||
kind: count
|
||||
for kind, count in (
|
||||
session.query(
|
||||
project_models.ProjectObjectRevision.object_kind,
|
||||
func.count(),
|
||||
)
|
||||
.filter(
|
||||
project_models.ProjectObjectRevision.tenant_id == tenant_id,
|
||||
project_models.ProjectObjectRevision.superseded_at.is_(None),
|
||||
)
|
||||
.group_by(project_models.ProjectObjectRevision.object_kind)
|
||||
.all()
|
||||
)
|
||||
}
|
||||
return {
|
||||
"portfolios": int(counts.get("portfolio", 0)),
|
||||
"projects": int(counts.get("project", 0)),
|
||||
"milestones": int(counts.get("milestone", 0)),
|
||||
}
|
||||
|
||||
|
||||
def _permission(
|
||||
scope: str,
|
||||
@@ -52,9 +171,15 @@ def _permission(
|
||||
|
||||
|
||||
PERMISSIONS = (
|
||||
_permission(READ_SCOPE, "View projects", "Read accessible projects and portfolios."),
|
||||
_permission(WRITE_SCOPE, "Manage projects", "Create and update projects and milestones."),
|
||||
_permission(ADMIN_SCOPE, "Administer projects", "Configure project types and policies."),
|
||||
_permission(
|
||||
READ_SCOPE, "View projects", "Read accessible projects and portfolios."
|
||||
),
|
||||
_permission(
|
||||
WRITE_SCOPE, "Manage projects", "Create and update projects and milestones."
|
||||
),
|
||||
_permission(
|
||||
ADMIN_SCOPE, "Administer projects", "Configure project types and policies."
|
||||
),
|
||||
)
|
||||
|
||||
ROLE_TEMPLATES = (
|
||||
@@ -77,17 +202,19 @@ DOCUMENTATION = (
|
||||
id="projects.module-boundary",
|
||||
title="Projects module boundary",
|
||||
summary=(
|
||||
"Portfolios, projects, goals, milestones, participants, status, "
|
||||
"work structure, and project-level references."
|
||||
"Portfolios, projects, versioned goals and outcomes, milestones, "
|
||||
"dependencies, capacity, benefits, participants, status, and references."
|
||||
),
|
||||
body=(
|
||||
"Projects owns native project context. Tasks and Tickets own "
|
||||
"actionable work, Cases owns formal procedures, and Connectors "
|
||||
"owns OpenProject synchronization."
|
||||
"owns OpenProject synchronization. Reporting owns measured indicators; "
|
||||
"Risk Compliance owns risks and controls; Projects links those facts to "
|
||||
"planning, change impact, and benefit review."
|
||||
),
|
||||
layer="available",
|
||||
documentation_types=("admin",),
|
||||
audience=("operator", "module_admin", "product_owner"),
|
||||
documentation_types=("admin", "user"),
|
||||
audience=("user", "operator", "module_admin", "product_owner"),
|
||||
related_modules=OPTIONAL_DEPENDENCIES,
|
||||
links=(
|
||||
DocumentationLink(
|
||||
@@ -102,13 +229,17 @@ DOCUMENTATION = (
|
||||
"portfolio",
|
||||
"project",
|
||||
"milestone",
|
||||
"versioned goal and intended outcome",
|
||||
"dependency and capacity assumption",
|
||||
"benefit review",
|
||||
"project participant",
|
||||
"project resource link",
|
||||
"external project reference",
|
||||
],
|
||||
"first_slice": (
|
||||
"Implement project identity, status, milestones, participants, "
|
||||
"resource links, and OpenProject reference mapping."
|
||||
"Implement project and portfolio identity, status, milestones, "
|
||||
"participants, outcome/benefit intent, dependency/resource links, "
|
||||
"and OpenProject reference mapping."
|
||||
),
|
||||
},
|
||||
),
|
||||
@@ -126,7 +257,123 @@ manifest = ModuleManifest(
|
||||
),
|
||||
permissions=PERMISSIONS,
|
||||
role_templates=ROLE_TEMPLATES,
|
||||
route_factory=_router,
|
||||
nav_items=(
|
||||
NavItem(
|
||||
path="/projects",
|
||||
label="Projects",
|
||||
icon="folder-kanban",
|
||||
required_any=(READ_SCOPE,),
|
||||
order=36,
|
||||
),
|
||||
),
|
||||
frontend=FrontendModule(
|
||||
module_id=MODULE_ID,
|
||||
package_name="@govoplan/projects-webui",
|
||||
routes=(
|
||||
FrontendRoute(
|
||||
path="/projects",
|
||||
component="ProjectsPage",
|
||||
required_any=(READ_SCOPE,),
|
||||
order=36,
|
||||
),
|
||||
),
|
||||
nav_items=(
|
||||
NavItem(
|
||||
path="/projects",
|
||||
label="Projects",
|
||||
icon="folder-kanban",
|
||||
required_any=(READ_SCOPE,),
|
||||
order=36,
|
||||
),
|
||||
),
|
||||
view_surfaces=(
|
||||
ViewSurface(
|
||||
id="projects.navigation",
|
||||
module_id=MODULE_ID,
|
||||
kind="navigation",
|
||||
label="Projects navigation",
|
||||
order=10,
|
||||
),
|
||||
ViewSurface(
|
||||
id="projects.workspace",
|
||||
module_id=MODULE_ID,
|
||||
kind="route",
|
||||
label="Projects workspace",
|
||||
order=20,
|
||||
),
|
||||
ViewSurface(
|
||||
id="projects.portfolios",
|
||||
module_id=MODULE_ID,
|
||||
kind="section",
|
||||
label="Portfolio planning",
|
||||
parent_id="projects.workspace",
|
||||
order=30,
|
||||
),
|
||||
ViewSurface(
|
||||
id="projects.outcomes",
|
||||
module_id=MODULE_ID,
|
||||
kind="section",
|
||||
label="Outcomes and benefits",
|
||||
parent_id="projects.workspace",
|
||||
order=40,
|
||||
),
|
||||
),
|
||||
),
|
||||
provides_interfaces=(
|
||||
ModuleInterfaceProvider(name="projects.registry", version="0.1.0"),
|
||||
),
|
||||
capability_factories={CAPABILITY_PROJECTS_REGISTRY: _registry},
|
||||
capability_documentation={
|
||||
CAPABILITY_PROJECTS_REGISTRY: CapabilityDocumentation(
|
||||
label="Projects registry",
|
||||
summary=(
|
||||
"Persists portfolios, projects, milestones, planning intent, "
|
||||
"membership grants, revisions, and lifecycle events."
|
||||
),
|
||||
contract_version="0.1.0",
|
||||
),
|
||||
},
|
||||
search_sources=(
|
||||
SearchSourceProviderRegistration(
|
||||
id="projects.objects",
|
||||
factory=create_projects_search_source,
|
||||
),
|
||||
),
|
||||
migration_spec=MigrationSpec(
|
||||
module_id=MODULE_ID,
|
||||
metadata=Base.metadata,
|
||||
script_location=str(Path(__file__).with_name("migrations") / "versions"),
|
||||
retirement_supported=True,
|
||||
retirement_provider=drop_table_retirement_provider(
|
||||
project_models.ProjectObjectEvent,
|
||||
project_models.ProjectMembershipGrant,
|
||||
project_models.ProjectObjectRevision,
|
||||
project_models.ProjectObjectIdentity,
|
||||
label="Projects",
|
||||
),
|
||||
retirement_notes=(
|
||||
"Destructive retirement requires a database snapshot and removes "
|
||||
"Projects identities, immutable revisions, memberships, and events."
|
||||
),
|
||||
),
|
||||
uninstall_guard_providers=(
|
||||
persistent_table_uninstall_guard(
|
||||
project_models.ProjectObjectIdentity,
|
||||
project_models.ProjectObjectRevision,
|
||||
project_models.ProjectMembershipGrant,
|
||||
project_models.ProjectObjectEvent,
|
||||
label="Projects",
|
||||
),
|
||||
),
|
||||
resource_acl_providers=(
|
||||
ProjectScopeAclProvider("portfolio"),
|
||||
ProjectScopeAclProvider("project"),
|
||||
ProjectScopeAclProvider("milestone"),
|
||||
),
|
||||
tenant_summary_providers=(_tenant_summary,),
|
||||
documentation=DOCUMENTATION,
|
||||
architecture=ARCHITECTURE,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user