initial commit
This commit is contained in:
35
.gitea/ISSUE_TEMPLATE/bug_report.md
Normal file
35
.gitea/ISSUE_TEMPLATE/bug_report.md
Normal 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:
|
||||
1
.gitea/ISSUE_TEMPLATE/config.yaml
Normal file
1
.gitea/ISSUE_TEMPLATE/config.yaml
Normal file
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
27
.gitea/ISSUE_TEMPLATE/docs_workflow.md
Normal file
27
.gitea/ISSUE_TEMPLATE/docs_workflow.md
Normal 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?
|
||||
32
.gitea/ISSUE_TEMPLATE/feature_request.md
Normal file
32
.gitea/ISSUE_TEMPLATE/feature_request.md
Normal 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:
|
||||
28
.gitea/ISSUE_TEMPLATE/task.md
Normal file
28
.gitea/ISSUE_TEMPLATE/task.md
Normal 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:
|
||||
25
.gitea/ISSUE_TEMPLATE/tech_debt.md
Normal file
25
.gitea/ISSUE_TEMPLATE/tech_debt.md
Normal 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:
|
||||
15
.gitea/PULL_REQUEST_TEMPLATE.md
Normal file
15
.gitea/PULL_REQUEST_TEMPLATE.md
Normal file
@@ -0,0 +1,15 @@
|
||||
## Issue
|
||||
|
||||
Closes #
|
||||
|
||||
## Summary
|
||||
|
||||
-
|
||||
|
||||
## Verification
|
||||
|
||||
-
|
||||
|
||||
## Notes
|
||||
|
||||
Follow-up issues:
|
||||
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal 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
69
README.md
Normal 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)
|
||||
98
docs/DISTRIBUTION_LISTS_ARCHITECTURE.md
Normal file
98
docs/DISTRIBUTION_LISTS_ARCHITECTURE.md
Normal 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.
|
||||
75
docs/IMPLEMENTATION_PLAN.md
Normal file
75
docs/IMPLEMENTATION_PLAN.md
Normal 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
23
pyproject.toml
Normal 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"
|
||||
5
src/govoplan_dist_lists/__init__.py
Normal file
5
src/govoplan_dist_lists/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""GovOPlaN distribution lists module."""
|
||||
|
||||
__all__ = ["__version__"]
|
||||
|
||||
__version__ = "0.1.8"
|
||||
1
src/govoplan_dist_lists/backend/__init__.py
Normal file
1
src/govoplan_dist_lists/backend/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Backend integration for the distribution lists module."""
|
||||
95
src/govoplan_dist_lists/backend/manifest.py
Normal file
95
src/govoplan_dist_lists/backend/manifest.py
Normal 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
|
||||
1
src/govoplan_dist_lists/py.typed
Normal file
1
src/govoplan_dist_lists/py.typed
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
25
tests/test_manifest.py
Normal file
25
tests/test_manifest.py
Normal 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()
|
||||
Reference in New Issue
Block a user