Files
govoplan-addresses/docs/ADDRESS_MODULE_ARCHITECTURE.md
T

211 lines
9.5 KiB
Markdown

# GovOPlaN Addresses Module Architecture
## Decision
`govoplan-addresses` owns reusable contact and recipient-source data. Campaigns,
mail, scheduling, portal, postbox, forms, reporting, and other modules consume
address data through core-mediated capabilities and APIs, not by importing
address-module internals.
The implementation reference for contact data is vCard. CardDAV is the primary
address-book sync protocol. LDAP/Active Directory, Exchange/Microsoft 365,
Google Contacts, CSV/XLSX, LDIF, and batch vCard import/export are connector
targets layered on top of the same local model and sync contracts.
## Ownership
`govoplan-addresses` owns:
- scoped address books
- vCard-compatible contacts and postal/email/phone contact-point data
- vCard import/export and vCard-compatible field mapping
- reusable recipient sources and classical address lists
- contact tags, categories, communication preferences, consent, and legal basis
- deduplication, merge, address quality checks, and suppression lists
- contact provenance, audit history, soft delete, and restore
- external-source bindings, sync state, conflicts, and read-only source markers
It does not own:
- campaign-local recipient snapshots and evidence
- mail transport, mailbox access, or delivery queues
- calendar events or iCalendar event storage
- global identity authentication or authorization decisions
- IDM identities, typed groups, effective-dated relationships, or identity
lifecycle state
- organization structures, units, function definitions, or function
assignments
- operational distribution lists/`Verteiler` with mixed address contacts, IDM
identities/groups, functions, raw targets, Dataflow rows, and nested lists
## Scopes
Address books can live in these scopes:
- `user`: personal address books and remembered contacts
- `group`: team/shared address books
- `tenant`: tenant-wide directories and approved shared lists
- `system`: platform-wide public/shared directories where policy allows it
The scope determines visibility, default permissions, sync credentials, and
whether downstream modules may reuse or mutate entries.
## Data Model Principles
The canonical model should preserve enough vCard semantics to round-trip common
fields:
- name components and formatted names
- nicknames and display names
- email addresses, phone numbers, postal addresses, URLs, notes, categories
- organization, title, role, department, `KIND`, and `RELATED` values needed for
vCard round-trip compatibility
- birthday/anniversary where allowed by policy
- photos/avatars where storage and privacy policy allow them
- calendar or scheduling addresses where present
- source IDs, revisions, ETags, sync tokens, and provenance
The model should support both normalized query fields and a preserved original
representation for import/export and conflict handling.
The local baseline implements scoped address books, contacts, normalized
email/phone/postal-address tables, tags, source kind/reference fields,
first-class source payload/revision fields, and provenance JSON. Imported
vCards preserve raw source payload and revision metadata for audit/debugging.
Sync sources, attempt state, tombstones, conflicts, and diagnostics are now
first-class backend tables and API resources. Connector-specific diffing,
CardDAV discovery, and conflict-resolution UI remain part of the connector
milestones.
## Capabilities
The first stable capabilities are:
- `addresses.recipient_source`: return immutable recipient snapshots for
campaigns, forms, reporting, and other send/build workflows.
- `addresses.lookup`: provide read-only lookup and autocomplete for mail,
campaign, scheduling, postbox, portal, and case workflows.
- `addresses.contact_writer`: provide address-book-scoped write target decisions
and contact creation for local or otherwise writable sources.
Capabilities use DTOs and source IDs. Consumers must not receive ORM objects or
write address tables directly. Consumers that need historical evidence must
store their own immutable snapshot with source ID, source revision, and
provenance; they must not treat live address records as historical evidence.
`addresses.recipient_source` exposes both complete address books and classical
address lists. Address-book sources use `addresses:address_book:<id>`.
Address-list sources use `addresses:address_list:<id>` and include the
address-list entry ID in each recipient's provenance. The current snapshot DTO
is email-recipient oriented; postal-only list entries are valid address-list
members but are skipped by the email recipient-source path until postal
recipient DTOs are added.
The writer capability is intentionally address-book specific. It answers
whether the current principal may perform an operation such as `create_contact`,
`update_contact`, or `delete_contact` against a concrete address book. The
decision payload includes:
- `allowed`
- stable `reason`
- user-facing `message`
- required scopes
- source kind
- read-only state
- scope and tenant provenance
Policy modules or connector sync state may later add inputs to this decision,
but consumers must continue to call the address capability/API instead of
importing policy logic or address services directly. Disabled or read-only UI
actions should surface the returned `message` on hover.
## Sync Model
Every synced address book tracks or can track:
- connector type and external account/source
- external address-book ID and display name
- local address-book scope
- sync direction: read-only, one-way import, one-way export, two-way
- sync token, ETag/revision, last successful sync, last attempted sync
- deleted markers/tombstones
- conflict status and resolution decision
- connector diagnostics and rate-limit/backoff state
Sync conflict UX must show the local value, remote value, source, timestamp, and
available action. Silent overwrite is not acceptable.
Sync infrastructure is intentionally connector-neutral. CardDAV, LDAP,
Exchange/Microsoft 365, Google Contacts, CSV/XLSX/LDIF import profiles, and
future connectors must write through `addresses_sync_sources` and related
records instead of inventing connector-specific status tables. Connector jobs
may mark a source `running`, `succeeded`, `failed`, or `conflict`; read-only and
one-way-import sources propagate a read-only decision to the owning address
book, which in turn blocks normal contact writes through the existing writer
capability/API.
The first CardDAV implementation supports discovery, source binding, dry-run
preview, inbound vCard sync, outbound create/update/delete for writable
sources, sync-token/full-sync fallback, tombstones, diagnostics, and persisted
conflicts. Outbound writes use ETag preconditions; stale local state must become
a conflict instead of silently overwriting remote data. The first conflict
review UI compares stored local and remote field payloads and can apply a
stored remote vCard payload or a manual per-field local/remote merge payload.
Source disconnect/delete removes the source binding and related sync records
while keeping local contacts. Because API-managed CardDAV credentials are
encrypted in the source row, the same transaction physically removes their
ciphertext and emits non-secret credential-deletion audit evidence. Destructive
module retirement audits all remaining owned credential material before table
removal. An unowned legacy reference is detached rather than passed to an
external secret provider.
## Connector Direction
Implement connectors in this order:
1. vCard import/export and batch import.
2. CardDAV address books.
3. LDAP/Active Directory read-only directories.
4. Exchange/Microsoft 365 and Google Contacts.
5. CSV/XLSX/LDIF import mapping profiles.
Connector runtime behavior should reuse shared connector concepts where useful:
configured endpoints, credentials, dry-run, diagnostics, rate limits, and audit
events.
## Cross-Module Integration
Campaigns should consume `addresses.recipient_source` through the platform
registry and freeze snapshots into campaign versions. Mail should consume
`addresses.lookup` for autocomplete and `addresses.contact_writer` for "add
contact" workflows. Scheduling should use lookup for attendees and organizers.
Portal, postbox, cases, forms, and reporting should link to contact records by
stable IDs while keeping their own domain evidence. Cross-module UI must hide
write actions when no writable target exists, or show the writer decision
message when a disabled action remains visible for context.
Operational distribution lists and reusable dynamic segments belong in
`govoplan-dist-lists`. They may consume address lists as one entry type, but
they own mixed recipient expansion for address contacts, IDM identities and
typed groups, organization units, functions/effective incumbents, raw targets,
Dataflow-backed rows, and nested lists. Workflow owns `Umlauf` execution state;
distribution lists define who is included, not how work circulates.
Organizations owns unit and function definitions. IDM owns effective-dated
identity-to-function assignments and typed group relationships. Identity
lifecycle status is not a business audience status; a selectable business
status is represented by a group, function, or effective-dated relationship.
Addresses may link contact points to stable provider references without copying
those provider-owned facts.
## Deferred Work
The following are valuable but not required for the first functional milestone:
- automatic deduplication and merge suggestions
- two-way sync conflict UI
- Microsoft/Google connectors
- richer vCard `KIND`/`RELATED` round-trip and provider-reference linking
- advanced consent-policy automation
- contact activity timeline across all modules