Sync Repo-docs-MODULE-ARCHITECTURE from project files

2026-08-01 16:31:36 +02:00
parent 532aab43c7
commit 416d6bfa7d
+538 -34
@@ -1,4 +1,4 @@
<!-- codex-wiki-sync:ef8e3029a6f4d806786e3d2a --> <!-- codex-wiki-sync:cfc41e352b119bd8d96e1266 -->
> Mirrored from `/mnt/DATA/git/govoplan-core/docs/MODULE_ARCHITECTURE.md`. > Mirrored from `/mnt/DATA/git/govoplan-core/docs/MODULE_ARCHITECTURE.md`.
> Origin: `repository`. > Origin: `repository`.
@@ -20,6 +20,10 @@ Policy decision, source provenance, and explain-response contracts are tracked
in [`POLICY_CONTRACTS.md`](POLICY_CONTRACTS.md). in [`POLICY_CONTRACTS.md`](POLICY_CONTRACTS.md).
The experimental remote WebUI bundle loading design is tracked in The experimental remote WebUI bundle loading design is tracked in
[`REMOTE_WEBUI_BUNDLES.md`](REMOTE_WEBUI_BUNDLES.md). [`REMOTE_WEBUI_BUNDLES.md`](REMOTE_WEBUI_BUNDLES.md).
The cross-product semantic layers, source-authority modes, and candidate
Mandates, Services, Parties, and Decisions boundaries are canonical in the
meta repository's
[`INSTITUTIONAL_GOVERNANCE_TARGET_ARCHITECTURE.md`](../../govoplan/docs/INSTITUTIONAL_GOVERNANCE_TARGET_ARCHITECTURE.md).
## Layer Model ## Layer Model
@@ -31,6 +35,36 @@ The experimental remote WebUI bundle loading design is tracked in
| Business modules | Public-sector workflows | campaigns, cases, forms, approvals, appointments | | Business modules | Public-sector workflows | campaigns, cases, forms, approvals, appointments |
| Connector modules | External system integration | FIT-Connect, XÖV/XTA, DMS/eAkte, ERP, IDM | | Connector modules | External system integration | FIT-Connect, XÖV/XTA, DMS/eAkte, ERP, IDM |
This table is the technical composition model. The product portfolio uses a
more detailed institutional layer model, but it does not change dependency
direction: Core provides contracts and composition; modules own semantics;
packages compose modules.
## Institutional Semantic Boundaries
Cross-module references must keep these answers distinct:
- Organizations owns where structures, units, and functions exist.
- Identity owns who a subject is; Access owns accounts, roles, permissions,
and authorization decisions; IDM owns effective function assignments.
- A Mandates capability will answer why a unit or function is competent for a
task, jurisdiction, subject, or period. It must not become another RBAC
system.
- A Services capability will own versioned institutional service definitions;
Portal presents and starts them.
- A Parties capability will own procedure-local participant roles,
representation, and delivery authority; it must reference rather than copy
Identity, Organizations, and Addresses subjects.
- A Decisions capability will own formal institutional outcomes and their
authority, facts, rules, reasoning, effects, correction, and review.
Approvals owns review gates, Committee owns deliberation/votes, and Workflow
Engine owns coordination.
Start each missing concept as a versioned DTO/provider contract used by a
bounded journey. A repository is justified only when the concept gains
independent persistence, lifecycle, security/operations behavior, release
reason, and reuse. Core must not store these domain objects.
## Kernel Responsibilities ## Kernel Responsibilities
The kernel target owns: The kernel target owns:
@@ -54,8 +88,8 @@ helpers, and secret helpers. The extracted access implementation lives in
`govoplan-access`; live ORM table definitions have been split across their `govoplan-access`; live ORM table definitions have been split across their
platform owners using module-prefixed table names. The old core route, platform owners using module-prefixed table names. The old core route,
admin-service, and access-security re-export modules have been removed. admin-service, and access-security re-export modules have been removed.
Callers must use module-owned imports, the public `govoplan_access.auth` request Callers must use module-owned imports, the public `govoplan_core.auth` request
dependency API, or kernel capabilities. dependency facade, or kernel capabilities.
The remaining platform compatibility surfaces are temporary until the matching The remaining platform compatibility surfaces are temporary until the matching
platform modules are fully self-contained: platform modules are fully self-contained:
@@ -81,32 +115,76 @@ The compatibility/deprecation plan for the current split line is:
- reject new cross-module imports that bypass manifests, capabilities, events, - reject new cross-module imports that bypass manifests, capabilities, events,
or public module APIs or public module APIs
The retention windows and removal checklist for database bridges,
configuration/export schemas, and runtime/API aliases are defined in
`COMPATIBILITY_POLICY.md`.
## Stable Kernel Contracts ## Stable Kernel Contracts
The following contracts are the baseline API that modules can rely on: The following contracts are the baseline API that modules can rely on:
- `ModuleManifest` - `ModuleManifest`
- `ModuleCompatibility` - `ModuleCompatibility`
- named interface contract provider/requirement metadata
- module uninstall guard provider contract - module uninstall guard provider contract
- `MigrationSpec` - `MigrationSpec`
- route factory contract - route factory contract
- capability factory contract - capability factory contract
- access DTO/protocol contracts in `govoplan_core.core.access` - access DTO/protocol contracts in `govoplan_core.core.access`
- resource ACL provider contract - resource ACL provider contract
- tenant summary provider contract - bounded reference-option search provider contract
- single-tenant and optional batched tenant summary provider contracts
- tenant delete-veto provider contract - tenant delete-veto provider contract
- WebUI module contribution contract - WebUI module contribution contract
- navigation metadata contract - navigation metadata contract
- command/event envelope contract - command/event envelope contract
- policy decision and source provenance contract in `govoplan_core.core.policy` - policy decision and source provenance contract in `govoplan_core.core.policy`
- external object reference and integration-maturity contract in
`govoplan_core.core.external_references`
- action/effect preview and execution contract in
`govoplan_core.core.automation`
- workflow definition contribution and runtime-worker contracts
Changes to these contracts must be versioned or accompanied by compatibility shims. Changes to these contracts must be versioned or accompanied by compatibility shims.
Tenant list pages prefer `tenant_summary_batch_providers`. A batch provider
receives the unique tenant IDs on the current page and returns count mappings
keyed by tenant ID. Missing tenant keys mean that the provider has no counts for
that tenant; provider errors remain visible. Modules that expose only the
single-tenant contract remain compatible through a per-tenant fallback.
Destructive tenant lifecycle planning deliberately continues to use the
single-tenant path so it invokes every registered provider for the target
tenant, independent of ordinary list-page projections.
This list is the Milestone A kernel-contract freeze baseline. New module work This list is the Milestone A kernel-contract freeze baseline. New module work
may extend the kernel by adding explicit contracts, but existing contracts must may extend the kernel by adding explicit contracts, but existing contracts must
remain source-compatible through the 0.1.x split line unless a migration shim remain source-compatible through the 0.1.x split line unless a migration shim
and deprecation note are provided. and deprecation note are provided.
### Architecture Metadata
`ModuleManifest.architecture` is the backward-compatible, versioned product-
portfolio declaration for:
- module kind and institutional architecture layer;
- evidence-backed maturity claim (`concept`, `scaffold`, `vertical_slice`,
`reference_ready`, `supported`, or `lts`);
- owned and explicitly non-owned concepts;
- supported source-authority modes;
- reference packages, tested providers, and known limits;
- migration, upgrade, recovery, security, operations, and documentation
evidence references.
Core validates the claim and all provider references during registry startup.
`reference_ready`, `supported`, and `lts` claims require a named reference
package and the cumulative evidence set; target-tested providers additionally
require provider evidence. A supported module with migrations must include
migration evidence. Signed release catalogs retain and revalidate the
declaration. The meta manifest check validates repository evidence paths and
provides an opt-in `--require-architecture` rollout gate. Platform metadata,
Docs, and Ops project the same declaration. A module cannot make itself
supported solely by changing its maturity string.
Known access-related capability names are defined in Known access-related capability names are defined in
`govoplan_core.core.access`, including: `govoplan_core.core.access`, including:
@@ -121,25 +199,139 @@ Known access-related capability names are defined in
- `security.secretProvider` - `security.secretProvider`
- `audit.sink` - `audit.sink`
`govoplan-access` currently registers `access.principalResolver`, `govoplan-access` currently registers `auth.principalResolver`,
`auth.permissionEvaluator`, `access.principalResolver`,
`access.permissionEvaluator`, `access.directory`, `access.tenantProvisioner`, `access.permissionEvaluator`, `access.directory`, `access.tenantProvisioner`,
`access.administration`, and `access.governanceMaterializer`. `access.administration`, and `access.governanceMaterializer`.
`govoplan-tenancy` registers `tenancy.tenantResolver`. The minimal `govoplan-tenancy` registers `tenancy.tenantResolver`. The minimal
authenticated platform set is now `tenancy` plus `access`; the registry authenticated platform set is now `access`; tenancy is optional and adds tenant
inserts `tenancy` before `access` when only feature modules are requested. administration plus tenant resolver behavior when installed.
Feature modules should prefer these capabilities over direct reads of Feature modules should prefer these capabilities over direct reads of
access/tenant ORM models when they need labels, group membership, default access/tenant ORM models when they need labels, group membership, default
access provisioning, counts, audit actor labels, or tenant metadata. access provisioning, counts, audit actor labels, or tenant metadata.
FastAPI route dependencies for authenticated endpoints are access-owned and Other stable runtime capabilities currently include:
published from `govoplan_access.auth`. Routers may import that public API for
- `identity.directory` and `identity.search`
- `organizations.directory`
- `idm.directory`
- `calendar.outbox` and `calendar.scheduling`
- `poll.scheduling`
- `notifications.dispatch`
- `workflow.definitionContributions` and `workflow.runtimeWorker`
Modules contribute reusable process baselines through
`ModuleManifest.workflow_definitions`. Each contribution pins its origin module
and version, stable key, schema and content hash, native graph/BPMN content,
governance ceilings, execution mode, and required capabilities/interfaces.
`govoplan-workflow-engine` reconciles these declarations idempotently. A module
upgrade appends a baseline revision without replacing the active revision or
mutating a local override; the optional `govoplan-workflow` package supplies
the comparison, derivation, and reset UI.
### Named Interface Contracts
Capabilities are runtime objects. Named interface contracts are compatibility
metadata. A module uses them when it depends on a versioned cross-module API
shape but should not hard-code a package or repository release line.
Manifest fields:
- `provides_interfaces`: contracts this module provides, each with `name` and
`version`
- `requires_interfaces`: contracts this module needs, each with `name`,
optional `version_min`, optional `version_max_exclusive`, and optional
`optional: true`
Interface names use dot-separated lower-case identifiers such as
`files.spaces` or `mail.delivery`. A requirement range is interpreted as
`>= version_min` and `< version_max_exclusive`; the exclusive upper bound is
intended for SemVer major-version lines. Missing optional interfaces are
allowed, but an installed provider with an incompatible version blocks
activation because the integration would otherwise bind to an unsafe API.
### Source Authority And Provider Operations
Integration maturity and configured authority are independent. The existing
external-reference maturity ladder describes whether an adapter can discover,
link, search, read, publish, synchronize, migrate, or replace. A binding must
also state whether GovOPlaN is native authoritative, the external system is
authoritative, GovOPlaN keeps a mirror, both sides use governed sync, GovOPlaN
adds only a governance overlay, or the object is link-only.
`ModuleManifest.external_providers` composes existing contracts rather than
replacing them. Each declaration describes owned object/field groups, authority modes,
operations, revisions, freshness, health, limits, idempotency, conflicts,
outcome-unknown handling, evidence, correction/compensation, reconciliation,
outage behavior, classification, purpose, retention, and secret requirements.
Core owns the typed declaration and validation. Connectors and domain modules
own the actual protocol and domain behavior; configuration packages select the
effective mode and block missing/incompatible/unhealthy providers; Docs and Ops
explain the result. Effect-capable declarations fail validation unless their
retry, concurrency, outcome-unknown, correction, reconciliation, evidence,
audit, timeout, outage, classification, purpose, retention, and secret behavior
is explicit.
Declarations are release-time capability claims. Configured state is projected
separately through `ModuleManifest.external_provider_state_providers`. A state
provider receives a bounded tenant context and returns one sanitized observation
per configured binding: stable binding reference, effective authority mode,
active/configured state, health, freshness, conflict, recovery readiness,
observation/last-success time, and scalar metrics. Core validates and aggregates
those observations, isolates provider failures, and never accepts URLs,
credentials, or arbitrary nested metadata as runtime-state fields. Docs removes
binding-level detail from ordinary-user projections; Ops may show the full
sanitized operator projection.
Configuration-package preflight selects the exact requested binding from this
runtime state before evaluating authority, health, freshness, and recovery. A
healthy sibling binding therefore cannot mask an unhealthy required binding.
Providers with multiple configurations must use non-secret, stable references
such as `calendar:sync-source:<id>`.
Current named interfaces, generated from the source manifests by the workspace
contract checks, are:
- `addresses.contact_writer`, `addresses.lookup`, `addresses.recipient_source`
- `calendar.outbox`, `calendar.scheduling`
- `campaigns.access`, `campaigns.delivery_tasks`,
`campaigns.mail_policy_context`, `campaigns.policy_context`,
`campaigns.retention`
- `dist_lists.expand`, `dist_lists.source`, `dist_lists.writer`
- `evaluation.feedback`, `evaluation.result_aggregation`, `evaluation.scoring`
- `files.access`, `files.campaign_attachments`
- `mail.campaign_delivery`
- `notifications.dispatch`
- `poll.availability_matrix`, `poll.option_selection`,
`poll.response_collection`, `poll.signed_participation`,
`poll.workflow_context`
- `rest.function_publication`
- `scheduling.candidate_slots`, `scheduling.decision_handoff`
- `soap.operation_publication`
Core validates named interface contracts in three places:
- registry activation rejects missing required interfaces and incompatible
providers
- installer preflight reports the same failures before a module set is
activated
- signed catalog validation normalizes the metadata and warns when catalog
entries cannot satisfy each other's ranges
Module-id dependencies still decide startup ordering and mandatory package
presence. Named interfaces decide whether the versions in the active module
set are compatible.
FastAPI route dependencies for authenticated endpoints are imported from the
core `govoplan_core.auth` facade. Routers may import that public API for
`ApiPrincipal`, `get_api_principal`, `has_scope`, `require_scope`, and `ApiPrincipal`, `get_api_principal`, `has_scope`, `require_scope`, and
`require_any_scope`; they must not import access ORM models or `require_any_scope`; they must not import access ORM models or
`govoplan_access.backend.*` implementation internals. `govoplan_access.backend.*` implementation internals.
Current live table ownership: Current live table ownership:
- `govoplan-tenancy`: `tenancy_tenants` - core scope table: `core_scopes` (used by access/core baseline; managed by
`govoplan-tenancy` behavior when the tenancy module is installed)
- `govoplan-access`: `access_accounts`, `access_users`, `access_groups`, - `govoplan-access`: `access_accounts`, `access_users`, `access_groups`,
`access_roles`, `access_system_role_assignments`, `access_roles`, `access_system_role_assignments`,
`access_user_group_memberships`, `access_user_role_assignments`, `access_user_group_memberships`, `access_user_role_assignments`,
@@ -204,6 +396,23 @@ unsafe methods.
This avoids retransmitting unchanged snapshots. It does not identify which row This avoids retransmitting unchanged snapshots. It does not identify which row
changed inside a collection. changed inside a collection.
### Mutation Preconditions
Weak response ETags are cache validators only. Mutable aggregates expose a
separate positive, monotonic revision and an opaque strong ETag generated by
`govoplan_core.core.concurrency.strong_resource_etag`. HTTP mutations send that
strong ETag in `If-Match`; capability and worker calls carry the equivalent
typed `expected_revision`.
Core's compare-and-set primitive advances the revision in the same transaction
as the domain mutation. A missing HTTP precondition is `428 Precondition
Required`, a stale HTTP precondition is `412 Precondition Failed`, and a
domain/reconciliation conflict is `409 Conflict`. Conflict responses contain
bounded resource and revision metadata rather than the complete current
object. Modules may opt into the conservative three-way merge helper, but must
declare protected workflow, delivery, ownership, lock, evidence, signature,
and cryptographic paths that can never be merged automatically.
### Delta Collections ### Delta Collections
Collection endpoints that can expose row-level changes should use the shared Collection endpoints that can expose row-level changes should use the shared
@@ -237,12 +446,22 @@ Fields:
- `full`: true when the response is a full snapshot rather than an incremental - `full`: true when the response is a full snapshot rather than an incremental
delta. delta.
Section-level settings endpoints use the same contract but replace `items`
with:
- `item`: the full settings object when `full: true`.
- `sections`: a map of changed settings sections when `full: false`.
- `changed_sections`: ordered section identifiers the client can merge into its
local settings object.
Recommended query parameters: Recommended query parameters:
- `since`: opaque previous watermark. If omitted or expired, return a full - `since`: opaque previous watermark. If omitted or expired, return a full
snapshot with `full: true`. snapshot with `full: true`.
- `limit`: maximum number of changed items plus deleted markers. - `limit`: maximum number of changed items plus deleted markers.
- `include_deleted`: whether deleted markers should be returned. - `include_deleted`: whether deleted markers should be returned.
- `cursor`: opaque keyset cursor for table pages where offset shifts would make
row-level merging unsafe.
Modules should record changes with: Modules should record changes with:
@@ -255,10 +474,61 @@ Modules should record changes with:
- `payload`: small, non-secret routing metadata that helps determine whether a - `payload`: small, non-secret routing metadata that helps determine whether a
tombstone belongs to the requested view. tombstone belongs to the requested view.
If a retained sequence window is introduced, endpoints must compare the Sequence retention is explicit. Cleanup jobs must call
incoming watermark with the oldest retained sequence entry for their module and `prune_sequence_entries(...)` rather than deleting `core_change_sequence` rows
collections. A watermark older than that window is not safe for incremental directly. Pruning records a retention floor per module, collection, and tenant
replay, so the endpoint must return a full snapshot with `full: true`. scope. Endpoints compare incoming watermarks with that floor; a watermark older
than the floor is not safe for incremental replay, so the endpoint must return a
full snapshot with `full: true`. A first-use `seq:0` watermark remains valid
until such a floor exists, even if unrelated collections have advanced the
global sequence.
### Bounded Reference Selectors
Cross-module selectors use the module-neutral contract in
`govoplan_core.core.references`; consumers must not load an optional module's
complete directory and filter it in memory.
- Providers receive a normalized `ReferenceSearchRequest` with `kind`,
`tenant_id`, `query`, `selected_values`, `limit`, optional opaque `cursor`,
and policy context.
- Providers apply visibility and text filtering before materializing rows and
return `ReferenceSearchPage(options, next_cursor, has_more)`.
- A page contains at most the requested bounded search results. Already-selected
references are retained in addition to that bound so historical values remain
readable and removable even when they are inactive, deleted, or outside the
current search page.
- API consumers expose `next_cursor` and `has_more`. The current searchable
selector requests the first bounded page for each query; later load-more UI
can use the same cursor without changing the provider contract.
- `access.reference_options` supplies SQL-backed account, membership, and group
searches. When it is absent, Core degrades to the legacy Access directory or
to principal-only/unavailable references without importing Access.
- The shared WebUI `apiReferenceOptionProvider` resolves selected values in
chunks of at most 200, preventing a large existing selection from turning
into an unbounded request.
### Cursor/Keyset Pages
Offset pagination remains supported for compatibility and for first page loads,
but it is not safe as the merge anchor for row-level deltas on page 2 and later.
When a delta-capable table can be paged beyond the first page, the endpoint
should expose keyset cursors:
- Core provides `encode_keyset_cursor`, `decode_keyset_cursor`, and
`keyset_query_fingerprint` in `govoplan_core.core.pagination`.
- Cursors are opaque to clients and contain the endpoint scope, query
fingerprint, and last-row keyset values.
- The fingerprint must include every query input that changes membership or
order: scope, tenant, page size, sort column, sort direction, and filters.
- Reusing a cursor with different sort or filter parameters must fail with a
client error rather than returning a mismatched slice.
- Responses may still include `page`, `page_size`, `pages`, and `total` for
existing UI components, but `cursor` identifies the current slice and
`next_cursor` is the safe anchor for the next slice.
- The first visit to an arbitrary page can use offset compatibility. The
response should include the start cursor for that page so later reloads and
delta requests use keyset semantics.
Concrete consumers: Concrete consumers:
@@ -274,9 +544,49 @@ Concrete consumers:
refreshes when version, job, issue, or delivery-attempt changes invalidate the refreshes when version, job, issue, or delivery-attempt changes invalidate the
workspace view. workspace view.
- `GET /api/v1/campaigns/{campaign_id}/jobs/delta`: returns a paginated job - `GET /api/v1/campaigns/{campaign_id}/jobs/delta`: returns a paginated job
table snapshot first, then stable row deltas for unfiltered job status table snapshot first, then stable row deltas for cursor-backed job pages.
updates. Filtered, created, deleted, or stale-watermark requests fall back to The job list also supports offset compatibility for first visits to a page and
a full page snapshot so pagination remains correct. returns `cursor`/`next_cursor` for stable reloads. Filtered, created, deleted,
or stale-watermark requests fall back to a full page snapshot when pagination
membership can shift.
- `GET /api/v1/admin/users/delta`, `/groups/delta`, `/roles/delta`,
`/system/roles/delta`, `/system/accounts/delta`, and `/api-keys/delta`:
return access administration row deltas with tombstones where rows leave the
visible view.
- `GET /api/v1/admin/system/settings/delta`: returns section deltas for system
defaults, tenant capability flags, language packages, privacy retention
policy, maintenance mode, and raw settings.
- `GET /api/v1/admin/tenant/settings/delta`: returns tenant-local setting
sections and also reports language-section changes when system language
packages or enabled language codes change.
- `GET /api/v1/admin/configuration-changes/delta`: returns changed
configuration requests and history records.
- `GET /api/v1/admin/audit` and `/api/v1/admin/audit/delta`: return append-only
audit events using the same scope, sort, and filter query parameters. The list
supports offset compatibility plus `cursor`/`next_cursor` keyset paging; the
delta endpoint can replay changes against a cursor-backed slice.
- `GET /api/v1/mail/settings/delta`: returns mail profile row deltas plus the
current scoped mail profile policy when profile-policy dependencies changed.
The WebUI consumes this for system, tenant, user, group, and campaign mail
settings panels.
- `GET /api/v1/files/connectors/settings/delta`: returns file connector
profile, credential, connector-space, and scoped connector-policy deltas.
Credential changes also include referencing profiles because profile rows
display credential-derived state.
Open retrofit scope:
- Additional module-specific settings pages should expose section deltas as
their settings APIs stabilize. Remaining likely candidates are future
booking/resource configuration pages and settings pages introduced by new
modules.
- Remaining high-volume tables should adopt the cursor/keyset contract before
enabling arbitrary-page row deltas. Current rollout follow-ups:
`govoplan-files#22` for large file-space server windows,
`govoplan-mail#9` for provider-aware mailbox message cursors,
`govoplan-calendar#7` for event-window deltas,
`govoplan-tenancy#1` for tenant administration row deltas, and
`govoplan-admin#2` for governance/module-operation list deltas.
## Module Responsibilities ## Module Responsibilities
@@ -318,6 +628,18 @@ The manifest should declare:
- navigation metadata using serializable icon names - navigation metadata using serializable icon names
- uninstall guard providers for data, migration, worker, or scheduler vetoes - uninstall guard providers for data, migration, worker, or scheduler vetoes
A tenant-level managed `RoleTemplate` may set `default_authenticated=True`
only when every authenticated tenant member must receive that narrow baseline
while the contributing module is installed. Access derives the explicit grant
from the active manifest set during authorization without mutating the request
transaction. It may materialize a non-assignable role row for administration,
but no per-user assignment is required and role edits cannot remove the
baseline.
This is not a shortcut for feature authorization: keep the template narrow and
continue to enforce each domain action's own permission and resource policy.
System-level, unmanaged, wildcard-bearing, or slug-colliding automatic
templates are rejected by registry validation.
Backend nav metadata must use icon-name strings, not frontend components: Backend nav metadata must use icon-name strings, not frontend components:
```python ```python
@@ -330,6 +652,29 @@ NavItem(
) )
``` ```
Core validates manifest shape when the platform registry is built. The current
supported manifest contract version is `1`, and frontend asset manifests use
contract version `1`. Registry validation rejects unsupported contract versions,
invalid module ids, duplicate dependency declarations, self-dependencies,
mismatched migration/frontend metadata, invalid frontend package names, and
frontend/nav routes that do not declare usable paths and labels.
Backend route contributions are also validated before they are mounted. Startup
routers and live module activation fail fast if two routers register the same
HTTP method and path. That keeps OpenAPI output and FastAPI route order from
silently masking a module collision.
Tenant deletion and cleanup use the registry-owned delete-veto contract. A
module that owns tenant-bound data may declare `delete_veto_providers` on its
manifest for resource types such as `tenant` or `group`. Providers receive
`(session, tenant_id, resource_id)` and should return `DeleteVetoIssue`, an
iterable of `DeleteVetoIssue`, or `None`; older exception-based providers are
still treated as blocking vetoes. Core attributes each issue to the provider
module and adds resource context before the tenancy module exposes the issues
through the deletion plan. `blocker` issues prevent destructive or retire
operations, `warning` issues explain retained data, and `info` issues document
non-blocking lifecycle facts.
## Database And Migrations ## Database And Migrations
Core owns the database/session lifecycle. Modules access the database through core session dependencies and register their models/migrations through their manifest. Core owns the database/session lifecycle. Modules access the database through core session dependencies and register their models/migrations through their manifest.
@@ -342,12 +687,53 @@ Rules:
- Keep cross-module foreign-key assumptions explicit and conservative. - Keep cross-module foreign-key assumptions explicit and conservative.
- Register module metadata in `MigrationSpec` so core can discover it. - Register module metadata in `MigrationSpec` so core can discover it.
- Optional module migrations may create multiple Alembic heads. Verification - Optional module migrations may create multiple Alembic heads. Verification
should compare the database heads to the configured script heads instead of should compare database heads to Alembic's resolved `heads` target instead
assuming one linear revision when multiple modules are enabled. of assuming one linear revision when multiple modules are enabled. Owner
heads can be dependency parents and therefore may not all appear in
`alembic_version` after a full-graph upgrade.
- Treat migrations as release artifacts. Unreleased migrations may be squashed - Treat migrations as release artifacts. Unreleased migrations may be squashed
or rewritten before a stable release; released revision IDs are immutable or rewritten before a stable release; released revision IDs are immutable
once an installation may have recorded them. Each stable release records its once an installation may have recorded them. Each stable release records its
public migration heads in `docs/migration-release-baselines.json`. public migration heads in `docs/migration-release-baselines.json`.
- GovOPlaN keeps two Alembic tracks. The default `release` track loads
`versions` directories with reviewed release baselines and release-to-release
step-up migrations. The explicit `dev` track loads `dev_versions`
directories with the detailed development chain. Do not load both tracks for
one migration run, and do not switch a database between tracks unless it is a
disposable development database.
### Shared State And Runtime Ordering
Multi-host application roles use the `shared` state profile. In that profile,
PostgreSQL, Redis, a stable installation identifier, and S3-compatible object
storage are mandatory. Module durable artifacts must use Core's object-storage
contract and module-owned opaque key namespaces; node-local paths are limited
to temporary materialization. Same-host replicas may use the `host-shared`
profile and one shared volume.
Only the migration command mutates schema. PostgreSQL migration runs acquire a
deployment-wide advisory lock before module pre-tasks, Alembic, and post-tasks.
API, worker, and scheduler roles wait for exact configured migration heads and
fail closed instead of applying migrations during startup.
Runtime roles register identity, software/module composition, queues, heartbeat,
and drain state in PostgreSQL. Singleton work must use a distributed lease and
validate its monotonically increasing fencing token at the consequential
commit. See `STATE_AND_RECOVERY_CONTRACT.md` for the complete contract.
### Recovery Evidence
Operations spanning transactions, object storage, queues, or external systems
must choose an explicit Core recovery mode: atomic, compensation,
snapshot-restore, forward-recovery, or irreversible. Plans require verification
steps and mode-specific recovery material. Use idempotency keys, append-only
evidence checkpoints, and a runtime fence where work may race across nodes.
The recovery ledger is a shared primitive, not automatic coverage. A module may
claim its guarantees only after its operation records preconditions before side
effects, transitions partial/unknown outcomes honestly, and records verified
completion or recovery. Plaintext secrets must never enter recovery metadata or
evidence.
## Install, Uninstall, And Catalogs ## Install, Uninstall, And Catalogs
@@ -356,17 +742,45 @@ check, maintenance-mode guard, replay state, and installer request queue.
Package mutation is performed by `govoplan-module-installer` outside the Package mutation is performed by `govoplan-module-installer` outside the
FastAPI request process. FastAPI request process.
Official catalogs can be served as static JSON from `govoplan-web`, but core Official catalogs can be served as static JSON from `addideas-govoplan-website`, but core
does not trust the website by location alone. A catalog must pass the configured does not trust the website by location alone. A catalog must pass the configured
signature, channel, freshness, and replay rules before a catalog entry can be signature, channel, freshness, and replay rules before a catalog entry can be
planned. Catalog entries may declare `license_features`; core checks those planned. Catalog entries may declare `license_features`; core checks those
against the configured offline license before adding the entry to the install against the configured offline license before adding the entry to the install
plan. plan. Catalog entries may also declare `migration_safety` as `automatic`,
`requires_review`, `forward_only`, or `destructive`; forward-only and
destructive entries require explicit operator acknowledgement in the install
plan before installer preflight allows activation. Forward-only and destructive
catalog entries must also declare a tested recovery path. Catalog update entries
can define direct-update windows with `current_version_min` and
`current_version_max_exclusive`, mark intermediate `bridge_release` targets, and
explicitly opt into reviewed downgrade or same-version package-refresh plans.
Module migration order can be declared with `migration_after` and
`migration_before` in manifests or release catalogs; installer preflight turns
that metadata, module dependencies, and named interface relationships into an
ordered migration plan.
Modules that need live-data work outside Alembic schema revisions may declare
`migration_tasks` on `MigrationSpec`. This is deliberately narrower than a
general lifecycle hook system. Each task has a stable `task_id`, one of four
phases (`pre_migration_check`, `pre_migration_prepare`,
`post_migration_backfill`, `post_migration_verify`), a short operator-facing
summary, a task version, safety metadata, and an idempotent executor. Installer
preflight blocks non-idempotent tasks, forward-only/destructive tasks without
operator acknowledgement, and installed manifest tasks that have no executor.
Catalog task metadata is surfaced before activation as pending because the
executor can only be verified after the package is installed.
Modules should provide: Modules should provide:
- pinned backend and WebUI package refs for official catalog entries - pinned backend and WebUI package refs for official catalog entries
- module dependency metadata for catalog target-state planning
- migration-safety metadata for catalog update planning
- migration task metadata when live-data checks, preparation, backfills, or
verification must run around Alembic
- compatibility metadata in the module manifest - compatibility metadata in the module manifest
- named interface contracts in the manifest and catalog entry when the module
provides or consumes cross-module APIs
- lifecycle hooks when a runtime enable/disable action needs module-specific - lifecycle hooks when a runtime enable/disable action needs module-specific
work work
- uninstall guards for persistent data, active workers, schedulers, or external - uninstall guards for persistent data, active workers, schedulers, or external
@@ -379,11 +793,18 @@ Uninstall remains non-destructive unless the operator explicitly requests
## WebUI Contract ## WebUI Contract
A WebUI module exports a `PlatformWebModule` from its package. The object contributes local/fallback metadata and route render functions. A WebUI module exports a `PlatformWebModule` from its package. The object
contributes local/fallback metadata and route render functions. The package
must ship `src/module.ts` with the default contribution export: Core's Vite
host imports that descriptor directly after the backend reports the module as
enabled. This keeps package-root re-exports from pulling page implementations
into the initial shell.
Example: Example:
```ts ```ts
const FilesPage = lazy(() => import("./features/files/FilesPage"));
export const filesModule: PlatformWebModule = { export const filesModule: PlatformWebModule = {
id: "files", id: "files",
label: "Files", label: "Files",
@@ -398,6 +819,11 @@ export const filesModule: PlatformWebModule = {
}; };
``` ```
Route pages and substantial panels must use stable lazy imports. Core supplies
the shared loading and retryable error state around route rendering. The
initial static import closure and largest asynchronous chunk are enforced by
the budgets documented in [WEBUI_BUNDLE_BUDGETS.md](WEBUI_BUNDLE_BUDGETS.md).
WebUI modules receive only the core route context: WebUI modules receive only the core route context:
- `settings` - `settings`
@@ -429,6 +855,31 @@ capabilities with `usePlatformUiCapabilities("admin.sections")`, filters them
by `anyOf`/`allOf`, and renders them without importing the contributing module's by `anyOf`/`allOf`, and renders them without importing the contributing module's
components directly. components directly.
The configurable dashboard follows the same pattern. Core contributes only a
minimal `/dashboard` fallback when no `dashboard` WebUI module is active. The
`govoplan-dashboard` module owns the real `/dashboard` route and collects
widgets exposed through the `dashboard.widgets` capability:
```ts
const dashboardWidgets: DashboardWidgetsUiCapability = {
widgets: [
{
id: "ops.health",
title: "Operations health",
moduleId: "ops",
defaultSize: "wide",
anyOf: ["ops:operations:read"],
render: ({ settings, refreshKey }) => createElement(OpsHealthWidget, { settings, refreshKey })
}
]
};
```
Dashboard widgets are module contributions, not cross-module imports. A widget
may render components from its own module and core components only. The
dashboard module is responsible for layout, visibility, refresh context, and
future server-side layout persistence.
## Icon Rules ## Icon Rules
Icons are resolved centrally by core. Icons are resolved centrally by core.
@@ -474,7 +925,27 @@ Examples:
## Cross-Module Integration ## Cross-Module Integration
A module can declare required dependencies and optional dependencies. Optional behavior should be enabled by module presence and permissions, not by importing another module's WebUI internals. A module can declare required module dependencies, optional module
dependencies, required capabilities, and optional capabilities. Required module
dependencies are reserved for unavoidable startup ownership, such as a module
that cannot import or mount without another module package. Most runtime
relationships should be expressed as capabilities instead.
Auth/principal access is a capability contract, not a reason to hard-depend on
the `govoplan-access` repository. Current routers import `govoplan_core.auth`;
that facade delegates to access today and is the migration point for a future
provider-neutral auth kernel. Feature manifests should require
`auth.principalResolver` and `auth.permissionEvaluator`, while access remains
the default installed provider.
Tenancy is optional. Existing scoped data still uses `tenant_id` as a scope
identifier, backed by the core-owned `core_scopes` table. Access and
organizations must not import the tenancy package or declare a hard dependency
on it. Tenancy-specific administration and tenant resolver behavior live behind
the tenancy module and its capabilities.
Optional behavior should be enabled by module presence, capabilities, and
permissions, not by importing another module's WebUI internals.
Rules: Rules:
@@ -487,13 +958,17 @@ Rules:
### Dependency Boundary Enforcement ### Dependency Boundary Enforcement
The repository includes `scripts/check_dependency_boundaries.py`. It enforces the current baseline: The meta repository includes `tools/checks/check_dependency_boundaries.py`. It enforces the current baseline:
- kernel/core source may not add new direct imports of files/mail/campaign internals - kernel/core source may not add new direct imports of files/mail/campaign internals
- access source may not import files/mail/campaign internals - access source may not import files/mail/campaign internals
- feature modules may not import access implementation internals - feature modules may not import access implementation internals
- feature modules may not add new direct imports of sibling feature modules - feature modules may not add new direct imports of sibling feature modules
- FastAPI routers may import the published `govoplan_access.auth` dependency API - feature WebUI packages may not depend on or import sibling feature WebUI packages
- core WebUI may list module packages as host dependencies, but core WebUI source
may not import feature WebUI internals directly; module loading stays
declarative through the module contribution contract
- FastAPI routers import the core `govoplan_core.auth` dependency facade
- the transitional allowlist is expected to stay empty - the transitional allowlist is expected to stay empty
Any future exception is extraction debt and must be temporary, documented in the Any future exception is extraction debt and must be temporary, documented in the
@@ -566,7 +1041,7 @@ First slice:
- `govoplan-files` owns file-backed governed locations and uploaded/stored file - `govoplan-files` owns file-backed governed locations and uploaded/stored file
evidence. evidence.
- `govoplan-reporting` owns report/data views and scheduled outputs. - `govoplan-reporting` owns report/data views and scheduled outputs.
- `govoplan-workflow` owns process state, approvals, scheduling of process - `govoplan-workflow-engine` owns process state, approvals, scheduling of process
steps, and human review. steps, and human review.
Future `govoplan-datasources` is justified when GovOPlaN needs a broad source Future `govoplan-datasources` is justified when GovOPlaN needs a broad source
@@ -629,7 +1104,7 @@ from workflow semantics.
- form definitions, schemas, validation rules, field visibility rules, - form definitions, schemas, validation rules, field visibility rules,
localization, versioning, admin editing, and reusable form package fragments localization, versioning, admin editing, and reusable form package fragments
`govoplan-forms-runtime` owns, when implemented: `govoplan-forms-runtime` owns:
- public/internal submissions, drafts, submitted values, validation evidence, - public/internal submissions, drafts, submitted values, validation evidence,
attachment references, submission receipts, and handoff events attachment references, submission receipts, and handoff events
@@ -642,6 +1117,16 @@ Boundary:
- Reporting/dataflow may consume submitted data through governed DTOs or - Reporting/dataflow may consume submitted data through governed DTOs or
source lifecycle contracts. source lifecycle contracts.
Implemented contract:
- Core owns the provider-neutral `FormDefinition`/`FormFieldDefinition` DTOs.
- Forms persists immutable exact definitions and provides `forms.definitions`.
- Forms Runtime resolves that capability, persists revisioned instances and
events, validates draft/final values, and provides
`forms_runtime.service_launcher`.
- Portal delegates exact `<form-id>/<revision>` bindings and never writes either
owner's tables.
### OpenDesk Integration Profile ### OpenDesk Integration Profile
Tracking: `govoplan-core#195`, `govoplan-connectors#5`, Tracking: `govoplan-core#195`, `govoplan-connectors#5`,
@@ -798,8 +1283,21 @@ The package install-plan API records operator intent only:
also reports catalog validity, channel, signature, trust state, and the also reports catalog validity, channel, signature, trust state, and the
configured path. configured path.
- `POST /api/v1/admin/system/modules/install-plan/catalog/{module_id}` saves - `POST /api/v1/admin/system/modules/install-plan/catalog/{module_id}` saves
a planned install row from a validated catalog entry. Catalog signature and a planned install or update row from a validated catalog entry. Installed
approved-channel policy are enforced before the row is saved. modules are planned as updates. Catalog signature and approved-channel policy
are enforced before the row is saved. When the selected catalog row requires
companion dependency or interface-provider updates, the endpoint adds those
rows to the plan automatically. The saved plan row can also carry a
data-safety acknowledgement used by preflight for forward-only or destructive
catalog entries.
- Install-plan preflight returns a structured `target_plan` summary so the
admin UI can show current version, target version, package refs,
migration-safety level, update-window and bridge metadata, recovery metadata,
and acknowledgement state without requiring JSON editing.
- Install-plan preflight also returns a structured `migration_plan` summary with
target enabled modules and ordered module migration steps. When the installer
runs with migration enabled, the database migration command receives that
target module set and ordered module list.
- `POST /api/v1/admin/system/modules/{module_id}/uninstall-plan` saves a - `POST /api/v1/admin/system/modules/{module_id}/uninstall-plan` saves a
planned non-destructive uninstall row for an installed module after it has planned non-destructive uninstall row for an installed module after it has
been disabled. The Python distribution name is resolved from the installed been disabled. The Python distribution name is resolved from the installed
@@ -873,6 +1371,9 @@ the same restart/health set after restoring package and database snapshots.
The installer preflight is intentionally conservative: The installer preflight is intentionally conservative:
- maintenance mode must be active; - maintenance mode must be active;
- the `shared` state profile blocks in-place package mutation; clustered
installations must roll one verified immutable module composition across all
replicas;
- installed module manifests must be compatible with the supported manifest - installed module manifests must be compatible with the supported manifest
contract and current core version; contract and current core version;
- uninstalling `tenancy`, `access`, or `admin` is blocked; - uninstalling `tenancy`, `access`, or `admin` is blocked;
@@ -979,15 +1480,18 @@ Backend verification from core:
```bash ```bash
cd /mnt/DATA/git/govoplan-core cd /mnt/DATA/git/govoplan-core
./.venv/bin/python -m compileall src/govoplan_core ../govoplan-access/src/govoplan_access ../govoplan-admin/src/govoplan_admin ../govoplan-tenancy/src/govoplan_tenancy ../govoplan-policy/src/govoplan_policy ../govoplan-audit/src/govoplan_audit ../govoplan-files/src/govoplan_files ../govoplan-mail/src/govoplan_mail ../govoplan-campaign/src/govoplan_campaign /mnt/DATA/git/govoplan/.venv/bin/python -m compileall src/govoplan_core ../govoplan-access/src/govoplan_access ../govoplan-admin/src/govoplan_admin ../govoplan-tenancy/src/govoplan_tenancy ../govoplan-policy/src/govoplan_policy ../govoplan-audit/src/govoplan_audit ../govoplan-dashboard/src/govoplan_dashboard ../govoplan-files/src/govoplan_files ../govoplan-mail/src/govoplan_mail ../govoplan-campaign/src/govoplan_campaign
./.venv/bin/python scripts/check_dependency_boundaries.py /mnt/DATA/git/govoplan/tools/checks/check_dependency_boundaries.py
``` ```
`govoplan/tools/checks/check-focused.sh` runs npm with an isolated temporary npm user config
so developer-local npm settings do not create release-check warning noise.
Focused module contract and permutation verification: Focused module contract and permutation verification:
```bash ```bash
cd /mnt/DATA/git/govoplan-core cd /mnt/DATA/git/govoplan
bash scripts/check-module-matrix.sh GOVOPLAN_CORE_ROOT=/mnt/DATA/git/govoplan-core bash tools/checks/check-module-matrix.sh
``` ```
Core WebUI host verification: Core WebUI host verification:
@@ -1001,7 +1505,7 @@ Clean generated `dist`, `.vite`, and source-tree `__pycache__` artifacts after v
## Release Dependency Rules ## Release Dependency Rules
Local development may use editable Python installs and local WebUI `file:` dependencies so sibling module changes reload quickly. Release builds must use tagged git refs or published packages instead. Core provides: Local development may use editable Python installs and local WebUI `file:` dependencies so sibling module changes reload quickly. Release builds must use tagged git refs or published packages instead. The meta repository provides:
- `requirements-dev.txt` for local editable backend installs - `requirements-dev.txt` for local editable backend installs
- `requirements-release.txt` for tagged backend module installs - `requirements-release.txt` for tagged backend module installs