initial commit

This commit is contained in:
2026-07-14 08:33:28 +02:00
commit c7bea69e26
17 changed files with 568 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
---
name: "Bug"
about: "Report a reproducible defect, regression, or incorrect behavior"
title: "[Bug] "
labels:
- type/bug
- status/triage
- module/dist-lists
---
## Scope
- Repository:
- Area/module:
- Affected version or commit:
## Behavior
Expected:
Actual:
## Reproduction
1.
2.
3.
## Evidence
Logs, screenshots, traces, or failing test output:
## Verification Target
Command or workflow that should pass when fixed:

View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View File

@@ -0,0 +1,27 @@
---
name: "Docs / workflow"
about: "Request documentation, process, or developer workflow changes"
title: "[Docs] "
labels:
- type/docs
- status/triage
- module/dist-lists
- area/docs
---
## Scope
- Repository:
- Document or workflow:
## Current State
What is missing, unclear, duplicated, or stale?
## Desired State
What should the docs or workflow make clear?
## Verification Target
How should this be checked?

View File

@@ -0,0 +1,32 @@
---
name: "Feature"
about: "Propose new user-visible behavior or platform capability"
title: "[Feature] "
labels:
- type/feature
- status/triage
- module/dist-lists
---
## Problem
What user, operator, or developer problem should this solve?
## Proposed Capability
What should exist when this is done?
## Ownership
- Owning repository:
- Related module repositories:
- Extension point or integration boundary:
## Acceptance Criteria
- [ ]
- [ ]
## Verification Target
Command, scenario, or UI flow that should prove completion:

View File

@@ -0,0 +1,28 @@
---
name: "Task"
about: "Track implementation, maintenance, or migration work"
title: "[Task] "
labels:
- type/task
- status/triage
- module/dist-lists
---
## Objective
What needs to be completed?
## Scope
- Owning repository:
- In-scope:
- Out-of-scope:
## Checklist
- [ ]
- [ ]
## Verification Target
Command or manual check:

View File

@@ -0,0 +1,25 @@
---
name: "Tech debt"
about: "Track cleanup, refactoring, risk reduction, or deferred engineering work"
title: "[Debt] "
labels:
- type/debt
- status/triage
- module/dist-lists
---
## Current Cost
What does this make harder, riskier, slower, or more fragile?
## Desired Shape
What should the code, tests, or architecture look like afterwards?
## Constraints
What behavior, compatibility, or module boundary must be preserved?
## Verification Target
Focused checks that should pass:

View File

@@ -0,0 +1,15 @@
## Issue
Closes #
## Summary
-
## Verification
-
## Notes
Follow-up issues:

13
.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
.venv/
__pycache__/
*.py[cod]
*.egg-info/
.pytest_cache/
.ruff_cache/
dist/
build/
node_modules/
coverage/
.env
.env.*
!.env.example

69
README.md Normal file
View File

@@ -0,0 +1,69 @@
# GovOPlaN Distribution Lists
<!-- govoplan-repository-type:start -->
**Repository type:** module (domain).
<!-- govoplan-repository-type:end -->
`govoplan-dist-lists` will own reusable operational distribution lists
(`Verteiler`) for GovOPlaN. Distribution lists are broader than address lists:
they can resolve contacts, raw addresses, identities, users, groups,
organization units, functions, roles, and later nested lists into immutable
recipient snapshots for campaigns, postbox messages, notifications, scheduling,
polls, consultations, approvals, workflow, and case operations.
The module is intentionally initialized as a documentation and manifest seed
first. Classical address lists remain in `govoplan-addresses`; this module will
take over the mixed operational recipient model once the address-owned list
primitive is stable.
## Boundary
Distribution Lists owns:
- reusable mixed recipient definitions
- German-administration `Verteiler` semantics
- expansion of mixed entries into concrete delivery/recipient targets
- snapshot DTOs with source revision and provenance
- stale-source detection for expanded entries
- eligibility and policy explanations for disabled recipient entries
- audit evidence for who was resolved at execution time
Distribution Lists does not own:
- contacts, vCard/CardDAV address books, or address-only lists; those belong in
`govoplan-addresses`
- mail transport, queues, or mailbox access; those belong in `govoplan-mail`
- campaign versions, message rendering, or delivery evidence; those belong in
`govoplan-campaign`
- workflow/Umlauf execution state, ordering, deadlines, escalation, or task
state; those belong in `govoplan-workflow` and `govoplan-tasks`
- global identity, organization, tenancy, access, or policy engines
## Key Distinction
```text
AddressBook -> contacts and contact points
AddressList -> address-domain grouping of contacts/contact methods
DistributionList -> operational Verteiler with mixed recipient entry types
Umlauf -> workflow execution over recipients, actors, tasks, and deadlines
CampaignVersion/PostboxMessage/etc. -> immutable snapshot of expanded recipients
```
## Development Install
```bash
cd /mnt/DATA/git/govoplan
./.venv/bin/python -m pip install -e ../govoplan-dist-lists
```
Focused manifest verification:
```bash
cd /mnt/DATA/git/govoplan-dist-lists
PYTHONPATH=src:/mnt/DATA/git/govoplan-core/src /mnt/DATA/git/govoplan/.venv/bin/python -m unittest discover -s tests
```
## Design Documents
- [Distribution lists architecture](docs/DISTRIBUTION_LISTS_ARCHITECTURE.md)
- [Implementation plan](docs/IMPLEMENTATION_PLAN.md)

View File

@@ -0,0 +1,98 @@
# GovOPlaN Distribution Lists Architecture
## Decision
`govoplan-dist-lists` owns reusable operational distribution lists
(`Verteiler`). This module is deliberately separate from `govoplan-addresses`:
address lists are plain address-domain groupings, while distribution lists are
cross-module recipient definitions that may resolve through address books,
identity, organizations, groups, functions, roles, raw addresses, and other
future provider capabilities.
## Vocabulary
- **Address book:** stores contacts and contact points such as email and postal
addresses.
- **Address list:** an address-owned list of contacts/contact methods from
address books.
- **Distribution list / Verteiler:** a reusable operational recipient
definition with mixed entry types.
- **Umlauf:** an execution workflow over recipients or actors with order,
state, deadlines, decisions, and escalation. Distribution lists define who;
workflow/tasks define what happens.
- **Snapshot:** immutable expansion evidence stored by the consuming module at
send, publish, decision, notification, or execution time.
## Ownership
Distribution Lists owns:
- distribution-list definitions
- mixed recipient entries
- provider-neutral recipient entry DTOs
- expansion plans and expansion results
- immutable expansion snapshots and stale-source warnings
- nested-list handling, cycle detection, and expansion limits
- source provenance and traceability
- policy/read-only explanation payloads for unavailable entries
It does not own:
- contact storage or vCard/CardDAV sync
- identity or organization master data
- delivery transports
- campaign versioning
- workflow task execution
## Entry Types
Initial supported entry types should be provider-neutral:
- `address_contact`
- `address_email`
- `raw_email`
- `raw_postal_address`
- `identity_principal`
- `organization_unit`
- `group`
- `function`
- `role`
- `distribution_list`
Each entry should carry a stable source reference, display label, optional
delivery-channel hints, provenance, and a policy/readiness state.
## Capability Direction
The first capabilities should be:
- `distLists.source`: list visible distribution lists as reusable recipient
sources.
- `distLists.expand`: expand a distribution list into immutable recipient rows.
- `distLists.writer`: explain whether a caller can create or change a list.
Consumers must resolve those capabilities through core and must not import
distribution-list ORM or service internals.
## Consumers
Likely consumers:
- Campaign: campaign recipient sources and send snapshots.
- Mail: ad-hoc recipient picking and reusable send groups.
- Postbox: recipient routing and publish snapshots.
- Notifications: notification target groups.
- Scheduling: invitation target groups.
- Poll/Evaluation/Consultation: invited participants and response audiences.
- Workflow/Tasks: Umlauf participant lists and escalation targets.
- Cases/Permits: case routing and responsible-function targeting.
## Relationship To Address Lists
`govoplan-addresses` should implement classical address lists first. A
distribution list may later include an address list as an entry, but the
address module remains the owner of contact records and address-only grouping.
The distribution-list module owns mixed operational routing. This prevents
campaign-specific recipient logic from becoming the platform's de facto
Verteiler implementation.

View File

@@ -0,0 +1,75 @@
# GovOPlaN Distribution Lists Implementation Plan
## Milestone 0: Module Seed
Goal: establish repository identity, module boundary, and installable manifest.
Tasks:
- [x] initialize repository type and package metadata
- [x] document boundary between address lists, distribution lists, and Umlauf
- [x] add a no-op module manifest with durable documentation topics
- [ ] create Gitea issues for the implementation milestones
## Milestone 1: Contract And DTOs
Goal: define mixed recipient entries without implementing every provider.
Tasks:
- define distribution-list, entry, expansion-plan, and expansion-result DTOs
- define source reference format for address, identity, organization, group,
function, role, raw, and nested-list entries
- define policy/read-only explanation payloads
- define snapshot evidence shape
- add cycle detection and expansion-limit semantics
## Milestone 2: Local Distribution Lists
Goal: implement local, manually maintained distribution lists.
Tasks:
- add database tables and migrations
- add CRUD routes and permissions
- add local raw email/postal entries
- add references to address contacts and address lists via capabilities
- expose `distLists.source` and `distLists.expand`
- add tests for expansion and snapshots
## Milestone 3: Provider Integrations
Goal: allow distribution lists to resolve through installed modules.
Tasks:
- integrate with `addresses.lookup` and address-list source capability
- integrate with identity/principal resolution
- integrate with organizations/groups/functions once those modules expose
provider capabilities
- preserve module independence when optional providers are absent
## Milestone 4: UI
Goal: make distribution lists practical for administrators and operators.
Tasks:
- add list management UI
- add mixed entry editor with provider-specific search
- show disabled/read-only entries with hover explanations
- show expansion preview and stale-source warnings
- expose picker components to campaign, mail, postbox, notifications,
scheduling, poll, and workflow consumers
## Milestone 5: Umlauf Integration
Goal: support administrative circulation workflows without confusing list
definition with execution.
Tasks:
- let workflow/tasks consume distribution-list expansion snapshots
- support ordered participant sequences where workflow owns state
- add escalation and substitution hooks through workflow, not distribution lists
- preserve immutable recipient evidence for every execution

23
pyproject.toml Normal file
View File

@@ -0,0 +1,23 @@
[build-system]
requires = ["setuptools>=69", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "govoplan-dist-lists"
version = "0.1.8"
description = "GovOPlaN operational distribution lists and Verteiler module seed."
readme = "README.md"
requires-python = ">=3.12"
authors = [{ name = "GovOPlaN" }]
dependencies = [
"govoplan-core>=0.1.8",
]
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
govoplan_dist_lists = ["py.typed"]
[project.entry-points."govoplan.modules"]
dist_lists = "govoplan_dist_lists.backend.manifest:get_manifest"

View File

@@ -0,0 +1,5 @@
"""GovOPlaN distribution lists module."""
__all__ = ["__version__"]
__version__ = "0.1.8"

View File

@@ -0,0 +1 @@
"""Backend integration for the distribution lists module."""

View File

@@ -0,0 +1,95 @@
from __future__ import annotations
from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER
from govoplan_core.core.modules import DocumentationTopic, ModuleInterfaceProvider, ModuleManifest, PermissionDefinition, RoleTemplate
MODULE_ID = "dist_lists"
MODULE_NAME = "Distribution Lists"
MODULE_VERSION = "0.1.8"
READ_SCOPE = "dist_lists:list:read"
WRITE_SCOPE = "dist_lists:list:write"
ADMIN_SCOPE = "dist_lists:list:admin"
def _permission(scope: str, label: str, description: str) -> PermissionDefinition:
module_id, resource, action = scope.split(":", 2)
return PermissionDefinition(
scope=scope,
label=label,
description=description,
category=MODULE_NAME,
level="tenant",
module_id=module_id,
resource=resource,
action=action,
)
PERMISSIONS = (
_permission(READ_SCOPE, "View distribution lists", "Read operational distribution lists and expansion previews."),
_permission(WRITE_SCOPE, "Manage distribution lists", "Create and edit operational distribution lists."),
_permission(ADMIN_SCOPE, "Administer distribution lists", "Configure distribution-list policies and provider integrations."),
)
ROLE_TEMPLATES = (
RoleTemplate(
slug="distribution_list_manager",
name="Distribution list manager",
description="Create, manage, preview, and reuse operational distribution lists.",
permissions=(READ_SCOPE, WRITE_SCOPE),
),
)
DOCUMENTATION = (
DocumentationTopic(
id="dist-lists.boundary",
title="Distribution list boundary",
summary="Distribution lists model operational Verteiler separately from address-book lists and workflow Umlauf execution.",
body=(
"Distribution Lists owns reusable mixed recipient definitions and expansion snapshots. "
"Address lists remain in the addresses module and represent address-domain groupings only. "
"Workflow and Tasks own Umlauf execution state, ordering, deadlines, escalation, and completion."
),
layer="available",
documentation_types=("admin", "user"),
audience=("operator", "module_admin", "product_owner"),
related_modules=("addresses", "campaigns", "mail", "postbox", "notifications", "scheduling", "poll", "workflow", "tasks"),
metadata={"seed": True},
),
)
manifest = ModuleManifest(
id=MODULE_ID,
name=MODULE_NAME,
version=MODULE_VERSION,
dependencies=(),
optional_dependencies=(
"addresses",
"access",
"identity",
"organizations",
"campaigns",
"mail",
"postbox",
"notifications",
"scheduling",
"poll",
"workflow",
"tasks",
),
optional_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
provides_interfaces=(
ModuleInterfaceProvider(name="distLists.source", version=MODULE_VERSION),
ModuleInterfaceProvider(name="distLists.expand", version=MODULE_VERSION),
ModuleInterfaceProvider(name="distLists.writer", version=MODULE_VERSION),
),
permissions=PERMISSIONS,
role_templates=ROLE_TEMPLATES,
documentation=DOCUMENTATION,
)
def get_manifest() -> ModuleManifest:
return manifest

View File

@@ -0,0 +1 @@

25
tests/test_manifest.py Normal file
View File

@@ -0,0 +1,25 @@
from __future__ import annotations
import unittest
from govoplan_core.core.modules import ModuleManifest
from govoplan_dist_lists.backend.manifest import get_manifest
class DistributionListsManifestTests(unittest.TestCase):
def test_manifest_contract(self) -> None:
manifest = get_manifest()
self.assertIsInstance(manifest, ModuleManifest)
self.assertEqual(manifest.id, "dist_lists")
self.assertFalse(manifest.dependencies)
self.assertIn("addresses", manifest.optional_dependencies)
self.assertIn("auth.principalResolver", manifest.optional_capabilities)
self.assertIn("distLists.expand", {interface.name for interface in manifest.provides_interfaces})
self.assertIn("dist_lists:list:read", {permission.scope for permission in manifest.permissions})
self.assertIsNone(manifest.route_factory)
self.assertIsNone(manifest.migration_spec)
if __name__ == "__main__":
unittest.main()