Files
govoplan-forms-runtime/src/govoplan_forms_runtime/backend/manifest.py
T
zemion ec3ebd2d3d
Module Package Release / publish-packages (push) Successful in 12s
Release v0.1.17
2026-08-05 20:34:03 +02:00

417 lines
15 KiB
Python

from __future__ import annotations
from pathlib import Path
from govoplan_core.core.access import (
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
)
from govoplan_core.core.institutional import (
CAPABILITY_FORM_DEFINITIONS,
CAPABILITY_SERVICE_DEFINITIONS,
)
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,
ModuleInterfaceRequirement,
ModuleManifest,
NavItem,
PermissionDefinition,
RoleTemplate,
)
from govoplan_core.core.provider_governance import declared_module_architecture
from govoplan_core.core.views import ViewSurface
from govoplan_core.db.base import Base
from govoplan_forms_runtime.backend.db import models as runtime_models
from govoplan_forms_runtime.backend.service import (
CAPABILITY_FORMS_RUNTIME_POLICY_EVALUATOR,
CAPABILITY_FORMS_RUNTIME_REGISTRY,
CAPABILITY_FORMS_RUNTIME_SERVICE_LAUNCHER,
FormRuntimeService,
FormsServiceLauncher,
)
MODULE_ID = "forms_runtime"
MODULE_NAME = "Forms Runtime"
MODULE_VERSION = "0.1.17"
PARTICIPATE_SCOPE = "forms_runtime:submission:participate"
READ_SCOPE = "forms_runtime:workspace:read"
WRITE_SCOPE = "forms_runtime:workspace:write"
ADMIN_SCOPE = "forms_runtime:workspace:admin"
OPTIONAL_DEPENDENCIES = (
"files",
"approvals",
"workflow_engine",
"portal",
"cases",
"policy",
"audit",
)
def _permission(scope: str, label: str, description: str) -> PermissionDefinition:
module_id, resource, action = scope.split(":", 2)
return PermissionDefinition(
scope=scope,
label=label,
description=description,
category=MODULE_NAME,
level="tenant",
module_id=module_id,
resource=resource,
action=action,
)
PERMISSIONS = (
_permission(
PARTICIPATE_SCOPE,
"Complete assigned forms",
"Start, read, save, and submit the acting account's own Form instances.",
),
_permission(
READ_SCOPE,
"View form submissions",
"Read tenant Form instances, immutable history, and status evidence.",
),
_permission(
WRITE_SCOPE,
"Manage form submissions",
"Review, transition, and hand off tenant Form submissions.",
),
_permission(
ADMIN_SCOPE,
"Administer Forms Runtime",
"Administer Forms Runtime policy, recovery, and retirement.",
),
)
ROLE_TEMPLATES = (
RoleTemplate(
slug="forms_runtime_participant",
name="Forms participant",
description="Complete the acting account's own Forms.",
permissions=(PARTICIPATE_SCOPE,),
default_authenticated=True,
),
RoleTemplate(
slug="forms_runtime_manager",
name="Forms Runtime manager",
description="Review, transition, and hand off Form submissions.",
permissions=(PARTICIPATE_SCOPE, READ_SCOPE, WRITE_SCOPE),
),
RoleTemplate(
slug="forms_runtime_viewer",
name="Forms Runtime viewer",
description="Read Form submissions and their immutable history.",
permissions=(READ_SCOPE,),
),
)
def _router(context: ModuleContext):
from govoplan_forms_runtime.backend.router import create_router
return create_router(context.registry)
def _registry(context: ModuleContext) -> FormRuntimeService:
return FormRuntimeService(context.registry)
def _service_launcher(context: ModuleContext) -> FormsServiceLauncher:
return FormsServiceLauncher(context.registry)
def _tenant_summary(session, tenant_id: str) -> dict[str, int]:
current = session.query(runtime_models.FormInstanceRevision).filter(
runtime_models.FormInstanceRevision.tenant_id == tenant_id,
runtime_models.FormInstanceRevision.superseded_at.is_(None),
)
return {
"form_instances": current.count(),
"open_form_instances": current.filter(
runtime_models.FormInstanceRevision.status.in_(
("started", "draft", "submitted", "validated", "needs_review")
)
).count(),
}
manifest = ModuleManifest(
id=MODULE_ID,
name=MODULE_NAME,
version=MODULE_VERSION,
dependencies=("access", "forms"),
optional_dependencies=OPTIONAL_DEPENDENCIES,
required_capabilities=(
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
CAPABILITY_FORM_DEFINITIONS,
),
optional_capabilities=(
CAPABILITY_FORMS_RUNTIME_POLICY_EVALUATOR,
CAPABILITY_SERVICE_DEFINITIONS,
"cases.service_launcher",
"workflow_engine.service_launcher",
),
permissions=PERMISSIONS,
role_templates=ROLE_TEMPLATES,
route_factory=_router,
nav_items=(
NavItem(
path="/forms-runtime",
label="Forms",
icon="form",
required_any=(PARTICIPATE_SCOPE, READ_SCOPE),
order=37,
),
),
frontend=FrontendModule(
module_id=MODULE_ID,
package_name="@govoplan/forms-runtime-webui",
routes=(
FrontendRoute(
path="/forms-runtime",
component="FormsRuntimePage",
required_any=(PARTICIPATE_SCOPE, READ_SCOPE),
order=37,
),
FrontendRoute(
path="/forms-runtime/:instanceId",
component="FormInstancePage",
required_any=(PARTICIPATE_SCOPE, READ_SCOPE),
order=38,
),
),
nav_items=(
NavItem(
path="/forms-runtime",
label="Forms",
icon="form",
required_any=(PARTICIPATE_SCOPE, READ_SCOPE),
order=37,
),
),
view_surfaces=(
ViewSurface(
id="forms_runtime.navigation",
module_id=MODULE_ID,
kind="navigation",
label="Forms navigation",
order=10,
),
ViewSurface(
id="forms_runtime.workspace",
module_id=MODULE_ID,
kind="route",
label="Forms workspace",
order=20,
),
ViewSurface(
id="forms_runtime.instance",
module_id=MODULE_ID,
kind="route",
label="Form instance",
order=30,
),
),
),
provides_interfaces=(
ModuleInterfaceProvider(name="forms_runtime.registry", version="0.1.0"),
ModuleInterfaceProvider(name="forms_runtime.service_launcher", version="0.1.0"),
),
requires_interfaces=(
ModuleInterfaceRequirement(
name="forms.definitions",
version_min="0.1.0",
version_max_exclusive="0.2.0",
),
ModuleInterfaceRequirement(
name="services.definitions",
version_min="0.1.0",
version_max_exclusive="0.2.0",
optional=True,
),
ModuleInterfaceRequirement(
name="cases.service_launcher",
version_min="0.1.0",
version_max_exclusive="0.2.0",
optional=True,
),
ModuleInterfaceRequirement(
name="workflow_engine.service_launcher",
version_min="0.1.0",
version_max_exclusive="0.2.0",
optional=True,
),
),
capability_factories={
CAPABILITY_FORMS_RUNTIME_REGISTRY: _registry,
CAPABILITY_FORMS_RUNTIME_SERVICE_LAUNCHER: _service_launcher,
},
capability_documentation={
CAPABILITY_FORMS_RUNTIME_REGISTRY: CapabilityDocumentation(
label="Forms Runtime registry",
summary="Manages tenant-bound, revisioned Form instances and handoff evidence.",
contract_version="0.1.0",
),
CAPABILITY_FORMS_RUNTIME_SERVICE_LAUNCHER: CapabilityDocumentation(
label="Form-bound Service launcher",
summary="Starts a replay-safe Form instance from an exact published Service and Form revision.",
contract_version="0.1.0",
),
},
migration_spec=MigrationSpec(
module_id=MODULE_ID,
metadata=Base.metadata,
script_location=str(Path(__file__).with_name("migrations") / "versions"),
migration_after=("forms",),
retirement_supported=True,
retirement_provider=drop_table_retirement_provider(
runtime_models.FormHandoffEffect,
runtime_models.FormInstanceEvent,
runtime_models.FormInstanceRevision,
runtime_models.FormInstanceIdentity,
label=MODULE_NAME,
),
retirement_notes="Destructive retirement removes submissions and immutable status evidence and requires a verified database and referenced-evidence recovery plan.",
),
uninstall_guard_providers=(
persistent_table_uninstall_guard(
runtime_models.FormHandoffEffect,
runtime_models.FormInstanceIdentity,
runtime_models.FormInstanceRevision,
runtime_models.FormInstanceEvent,
label=MODULE_NAME,
),
),
tenant_summary_providers=(_tenant_summary,),
documentation=(
DocumentationTopic(
id="forms_runtime.submissions",
title="Complete and manage Forms",
summary="Save permitted drafts, submit validated values, and retain exact definition and handoff evidence.",
body=(
"Every instance resolves one immutable published Form revision. Draft and final values are validated on the server; final submission also enforces attachment, signature, and policy requirements. "
"Service launches retain the exact Service and binding. Native Case and Workflow handoffs persist intent before execution, use owner capabilities with stable provider keys, and require reconciliation after unknown outcomes. History, receipts, and handoffs are replay-safe and optimistic-concurrency guarded."
),
layer="configured",
documentation_types=("admin", "user"),
audience=("user", "operator", "module_admin", "product_owner"),
links=(
DocumentationLink(
label="Forms Runtime security and recovery",
href="govoplan-forms-runtime/docs/FORMS_RUNTIME_DOMAIN_BOUNDARY.md",
kind="repository",
),
),
metadata={
"seed": True,
"help_contexts": [
"forms_runtime.navigation",
"forms_runtime.workspace",
"forms_runtime.instance",
"forms_runtime.state.read-only",
"forms_runtime.state.permission-blocked",
],
"privacy_notes": [
"Form values are returned only through tenant-bound instance permissions and ownership rules.",
"Validation messages expose field-level diagnostics without disclosing unrelated submissions.",
"Handoff rows retain provider references and outcomes but do not bypass target-module authorization.",
],
},
),
DocumentationTopic(
id="forms_runtime.reference.fields-and-consequences",
title="Form values, submission, and handoff consequences",
summary="Runtime field behavior, immutable receipts, draft revisions, optional evidence, and recoverable external effects.",
body=(
"The active instance resolves one exact published Form definition revision. Visibility conditions alter presentation, "
"not server validation or authorization. Saving a permitted draft creates a new revision with its change reason. "
"Submitting validates values, attachments, signatures, and policy requirements and records an immutable receipt; it is "
"not an editable draft save. A Case or Workflow handoff records intent before calling its optional provider and uses a "
"stable idempotency key. Rejected effects may be retried. Unknown outcomes must be reconciled before retry to avoid a "
"duplicate target. Administrative compensation records verified absence and never deletes a remote target."
),
layer="configured",
documentation_types=("admin", "user"),
audience=("user", "operator", "module_admin", "auditor"),
related_modules=OPTIONAL_DEPENDENCIES,
links=(
DocumentationLink(
label="Forms Runtime security and recovery",
href="govoplan-forms-runtime/docs/FORMS_RUNTIME_DOMAIN_BOUNDARY.md",
kind="repository",
),
),
metadata={
"seed": True,
"help_contexts": [
"forms_runtime.field.dynamic-value",
"forms_runtime.field.change-reason",
"forms_runtime.field.handoff-kind",
"forms_runtime.field.target-binding",
"forms_runtime.action.save-draft",
"forms_runtime.action.submit",
"forms_runtime.action.start-handoff",
"forms_runtime.action.reconcile-handoff",
"forms_runtime.action.compensate-handoff",
],
"consequence_classes": {
"save_draft": "Creates an immutable draft revision with a change reason.",
"submit": "Validates the exact definition and creates an immutable submission receipt.",
"start_handoff": "Persists intent before invoking an optional Case or Workflow provider.",
"reconcile": "Resolves an outcome-unknown effect without unsafe duplicate execution.",
"compensate": "Records an administrative proof that no target effect exists.",
},
},
),
),
architecture=declared_module_architecture(
layer="human_work_procedure",
kind="runtime",
maturity="vertical_slice",
documentation_ref="docs/FORMS_RUNTIME_DOMAIN_BOUNDARY.md",
test_ref="tests/test_forms_runtime.py",
known_limits=(
"Anonymous public intake, concrete Files/signature adapters, and target kinds beyond the native Case/Workflow handoffs remain adapter depth; authenticated Portal entry is supported.",
),
supported_authority_modes=("native_authoritative",),
owned_concepts=(
"form instance",
"form submission",
"runtime validation",
"submission receipt",
"form handoff evidence",
),
non_owned_concepts=(
"form definition",
"file content",
"case",
"workflow definition",
"signature key custody",
),
reference_packages=("product.service-to-decision",),
migration_docs=("docs/FORMS_RUNTIME_DOMAIN_BOUNDARY.md",),
recovery_docs=("docs/FORMS_RUNTIME_DOMAIN_BOUNDARY.md",),
security_docs=("docs/FORMS_RUNTIME_DOMAIN_BOUNDARY.md",),
operations_docs=("docs/FORMS_RUNTIME_DOMAIN_BOUNDARY.md",),
),
)
def get_manifest() -> ModuleManifest:
return manifest