Sync Repo-docs-MODULE-ARCHITECTURE from project files
+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`.
|
||||
> 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:<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
|
||||
`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 `<form-id>/<revision>` 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
|
||||
|
||||
Reference in New Issue
Block a user