diff --git a/Repo-docs-MODULE-ARCHITECTURE.-.md b/Repo-docs-MODULE-ARCHITECTURE.-.md index 814dcae..5e4ca91 100644 --- a/Repo-docs-MODULE-ARCHITECTURE.-.md +++ b/Repo-docs-MODULE-ARCHITECTURE.-.md @@ -1,4 +1,4 @@ - + > Mirrored from `/mnt/DATA/git/govoplan-core/docs/MODULE_ARCHITECTURE.md`. > Origin: `repository`. @@ -20,6 +20,10 @@ Policy decision, source provenance, and explain-response contracts are tracked in [`POLICY_CONTRACTS.md`](POLICY_CONTRACTS.md). The experimental remote WebUI bundle loading design is tracked in [`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 @@ -31,6 +35,36 @@ The experimental remote WebUI bundle loading design is tracked in | Business modules | Public-sector workflows | campaigns, cases, forms, approvals, appointments | | 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 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 platform owners using module-prefixed table names. The old core route, admin-service, and access-security re-export modules have been removed. -Callers must use module-owned imports, the public `govoplan_access.auth` request -dependency API, or kernel capabilities. +Callers must use module-owned imports, the public `govoplan_core.auth` request +dependency facade, or kernel capabilities. The remaining platform compatibility surfaces are temporary until the matching 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, 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 The following contracts are the baseline API that modules can rely on: - `ModuleManifest` - `ModuleCompatibility` +- named interface contract provider/requirement metadata - module uninstall guard provider contract - `MigrationSpec` - route factory contract - capability factory contract - access DTO/protocol contracts in `govoplan_core.core.access` - 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 - WebUI module contribution contract - navigation metadata contract - command/event envelope contract - 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. +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 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 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 `govoplan_core.core.access`, including: @@ -121,25 +199,139 @@ Known access-related capability names are defined in - `security.secretProvider` - `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.administration`, and `access.governanceMaterializer`. `govoplan-tenancy` registers `tenancy.tenantResolver`. The minimal -authenticated platform set is now `tenancy` plus `access`; the registry -inserts `tenancy` before `access` when only feature modules are requested. +authenticated platform set is now `access`; tenancy is optional and adds tenant +administration plus tenant resolver behavior when installed. Feature modules should prefer these capabilities over direct reads of access/tenant ORM models when they need labels, group membership, default access provisioning, counts, audit actor labels, or tenant metadata. -FastAPI route dependencies for authenticated endpoints are access-owned and -published from `govoplan_access.auth`. Routers may import that public API for +Other stable runtime capabilities currently include: + +- `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:`. + +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 `require_any_scope`; they must not import access ORM models or `govoplan_access.backend.*` implementation internals. 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`, `access_roles`, `access_system_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 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 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 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: - `since`: opaque previous watermark. If omitted or expired, return a full snapshot with `full: true`. - `limit`: maximum number of changed items plus deleted markers. - `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: @@ -255,10 +474,61 @@ Modules should record changes with: - `payload`: small, non-secret routing metadata that helps determine whether a tombstone belongs to the requested view. -If a retained sequence window is introduced, endpoints must compare the -incoming watermark with the oldest retained sequence entry for their module and -collections. A watermark older than that window is not safe for incremental -replay, so the endpoint must return a full snapshot with `full: true`. +Sequence retention is explicit. Cleanup jobs must call +`prune_sequence_entries(...)` rather than deleting `core_change_sequence` rows +directly. Pruning records a retention floor per module, collection, and tenant +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: @@ -274,9 +544,49 @@ Concrete consumers: refreshes when version, job, issue, or delivery-attempt changes invalidate the workspace view. - `GET /api/v1/campaigns/{campaign_id}/jobs/delta`: returns a paginated job - table snapshot first, then stable row deltas for unfiltered job status - updates. Filtered, created, deleted, or stale-watermark requests fall back to - a full page snapshot so pagination remains correct. + table snapshot first, then stable row deltas for cursor-backed job pages. + The job list also supports offset compatibility for first visits to a page and + 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 @@ -318,6 +628,18 @@ The manifest should declare: - navigation metadata using serializable icon names - 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: ```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 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. - Register module metadata in `MigrationSpec` so core can discover it. - Optional module migrations may create multiple Alembic heads. Verification - should compare the database heads to the configured script heads instead of - assuming one linear revision when multiple modules are enabled. + should compare database heads to Alembic's resolved `heads` target instead + 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 or rewritten before a stable release; released revision IDs are immutable once an installation may have recorded them. Each stable release records its 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 @@ -356,17 +742,45 @@ check, maintenance-mode guard, replay state, and installer request queue. Package mutation is performed by `govoplan-module-installer` outside the 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 signature, channel, freshness, and replay rules before a catalog entry can be planned. Catalog entries may declare `license_features`; core checks those 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: - 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 +- 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 work - 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 -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: ```ts +const FilesPage = lazy(() => import("./features/files/FilesPage")); + export const filesModule: PlatformWebModule = { id: "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: - `settings` @@ -429,6 +855,31 @@ capabilities with `usePlatformUiCapabilities("admin.sections")`, filters them by `anyOf`/`allOf`, and renders them without importing the contributing module's 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 Icons are resolved centrally by core. @@ -474,7 +925,27 @@ Examples: ## 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: @@ -487,13 +958,17 @@ Rules: ### 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 - access source may not import files/mail/campaign internals - feature modules may not import access implementation internals - 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 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 evidence. - `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. 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, 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, attachment references, submission receipts, and handoff events @@ -642,6 +1117,16 @@ Boundary: - Reporting/dataflow may consume submitted data through governed DTOs or 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 `/` bindings and never writes either + owner's tables. + ### OpenDesk Integration Profile 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 configured path. - `POST /api/v1/admin/system/modules/install-plan/catalog/{module_id}` saves - a planned install row from a validated catalog entry. Catalog signature and - approved-channel policy are enforced before the row is saved. + a planned install or update row from a validated catalog entry. Installed + 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 planned non-destructive uninstall row for an installed module after it has 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: - 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 contract and current core version; - uninstalling `tenancy`, `access`, or `admin` is blocked; @@ -979,15 +1480,18 @@ Backend verification from core: ```bash 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 -./.venv/bin/python scripts/check_dependency_boundaries.py +/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 +/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: ```bash -cd /mnt/DATA/git/govoplan-core -bash scripts/check-module-matrix.sh +cd /mnt/DATA/git/govoplan +GOVOPLAN_CORE_ROOT=/mnt/DATA/git/govoplan-core bash tools/checks/check-module-matrix.sh ``` Core WebUI host verification: @@ -1001,7 +1505,7 @@ Clean generated `dist`, `.vite`, and source-tree `__pycache__` artifacts after v ## 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-release.txt` for tagged backend module installs