467 lines
24 KiB
Markdown
467 lines
24 KiB
Markdown
# Postbox Concept
|
|
|
|
## Purpose
|
|
|
|
GovOPlaN Postbox provides in-platform postboxes that are addressable containers for messages, files, workflow evidence, and operational handoff. They can be used internally, exposed through portals, and connected to campaign workflows.
|
|
|
|
The key distinction from a mailbox is ownership. A mailbox is usually bound to
|
|
a login, user credential, or external mail account. A GovOPlaN postbox is a
|
|
durable communication and content-access container bound to institutional
|
|
context: primarily a function in an organizational unit, and where explicitly
|
|
needed a role, process, portal, campaign, or service responsibility. It may look
|
|
like an inbox for a message task or like a vault for content shared with the
|
|
current holders of that responsibility; neither form is owned by one account.
|
|
|
|
The strategic target is a policy-selectable administrative postbox. The current
|
|
implementation offers unencrypted content, an institution-managed Encryption
|
|
envelope, and a strict external E2EE boundary. E2EE messages contain only an
|
|
external ciphertext reference, signed-manifest reference, wrapped recipient
|
|
keys, and a verified plaintext digest; an approved producer or client owns the
|
|
actual cryptographic operation and private-key custody. GovOPlaN cannot decrypt
|
|
that profile. The institution-managed envelope remains server-readable by
|
|
authorized institutional key holders. Subjects, routing, participants, and
|
|
attachment references remain visible in every profile. The cross-module target
|
|
architecture is recorded in
|
|
`govoplan-core/docs/POSTBOX_E2EE_ARCHITECTURE.md`.
|
|
|
|
## Function-Organization-Bound Access
|
|
|
|
The primary access pattern is a postbox linked to an organizational unit and
|
|
one or more institutional functions. A person can access that postbox while
|
|
their identity/account has an effective matching function assignment in that
|
|
organizational unit and their account may perform the requested Postbox action.
|
|
Opening a function postbox does not require mapping the function to an RBAC
|
|
role.
|
|
|
|
Example:
|
|
|
|
- Organizational unit: `District Office North`
|
|
- Required function: `Case Clerk`
|
|
- Postbox: `District Office North / Case Clerk Intake`
|
|
|
|
Any identity/account currently holding the `Case Clerk` function for `District
|
|
Office North` can see the postbox if it also satisfies the generic Postbox
|
|
permission and applicable policy. When the function assignment or delegation
|
|
is removed or expires, access disappears without moving messages or
|
|
reassigning a mailbox.
|
|
|
|
The postbox exists independently of its holders. It remains addressable while
|
|
the function is vacant and may accept durable deliveries according to policy;
|
|
the UI must then show that no current human holder can open or act on the
|
|
content. Assigning one or several incumbents grants access in their explicit
|
|
function context. It does not transfer ownership of the container or rewrite
|
|
its history.
|
|
|
|
This makes postboxes useful for responsibilities that outlive individuals:
|
|
|
|
- intake desks
|
|
- function-based service queues
|
|
- campaign sender or response desks
|
|
- portal message inboxes for organizational responsibilities
|
|
- file or evidence drops linked to a function in an organization
|
|
|
|
### Incumbency, hand-over, and delegation
|
|
|
|
- Zero, one, or several people may hold a function at the same time.
|
|
- A postbox can remain vacant without being deleted, redirected to a personal
|
|
account, or losing content.
|
|
- A new assignment can grant access to the function's permitted history through
|
|
a current key epoch. Which historical epochs a new incumbent receives is an
|
|
explicit postbox policy, not an accidental consequence of account creation.
|
|
- A delegation is a time-bounded access grant in the represented function
|
|
context. Expiry removes future platform key access and action authority; it
|
|
cannot destroy plaintext or exports already obtained.
|
|
- Hand-over and revocation rotate the function/postbox key epoch. Per-content
|
|
data keys should normally be rewrapped; policy may require content
|
|
re-encryption for a stronger rotation event.
|
|
- Stored content is not silently substituted or overwritten. A correction,
|
|
replacement, or new version is a new linked object with provenance while the
|
|
previous signed/ciphertext manifest remains governed by retention policy.
|
|
|
|
## Directory And Authorization Model
|
|
|
|
The normalized ownership boundary is:
|
|
|
|
- Organizations owns units, structures, function types, and concrete
|
|
functions.
|
|
- Identity owns identities and account links.
|
|
- IDM owns effective identity-to-function assignments, validity, delegation,
|
|
acting-for context, and assignment lifecycle facts.
|
|
- Core/Access authorizes generic Postbox actions.
|
|
- Postbox owns templates, stable addresses, containers, messages, routing,
|
|
visibility decisions, grouping preferences, and retention.
|
|
|
|
The Postbox module stores postbox bindings and postbox-specific decisions, but
|
|
it must not duplicate identity, organization, assignment, hierarchy, or RBAC
|
|
resolution.
|
|
|
|
The minimum authorization inputs are:
|
|
|
|
- postbox id
|
|
- tenant id
|
|
- organizational unit id
|
|
- required function id or function type id
|
|
- actor identity id
|
|
- current effective function assignments, delegations, and acting context from
|
|
IDM
|
|
- generic Postbox permissions from Core/Access
|
|
- optional explicit administrative grants for postbox administration
|
|
|
|
The expected result is a narrow access decision:
|
|
|
|
- can discover
|
|
- can read
|
|
- can send or reply
|
|
- can attach or link files
|
|
- can administer bindings
|
|
|
|
Access changes must be auditable because a person can gain or lose postbox
|
|
visibility through function-assignment changes rather than direct postbox
|
|
membership edits.
|
|
|
|
Runtime integration must use the kernel capabilities:
|
|
|
|
- `identity.directory` to resolve identities and account links.
|
|
- `idm.directory` to resolve effective function assignments.
|
|
- `organizations.directory` to resolve function, function-type, unit, and
|
|
hierarchy facts.
|
|
- the Core/Access permission evaluator for generic Postbox actions.
|
|
- access explanation/audit contracts to attach permission and acting-context
|
|
provenance where available.
|
|
|
|
Postbox must not import Identity, IDM, Organizations, or Access ORM models.
|
|
Acting-in-place access requires an explicit selected acting context. A function
|
|
assignment is an organizational responsibility fact; it does not grant
|
|
unrelated application permissions.
|
|
|
|
## Templates And Stable Addresses
|
|
|
|
A reusable Postbox template can target a function type and an organization
|
|
scope, such as a unit type, structure, or subtree. Postbox resolves a stable
|
|
unit-specific address from the tenant, template revision, concrete unit,
|
|
concrete function, and optional case/service context.
|
|
|
|
Subtree scope is explicit about the Organizations structure and may restrict
|
|
the hierarchical relation types used within that structure. It does not infer
|
|
scope from the legacy `parent_id` when an administrator creates or revises a
|
|
template. This prevents an administrative, reporting, and project hierarchy
|
|
from being confused when they contain the same units.
|
|
|
|
Before saving a draft, administrators can run a read-only impact preview. It
|
|
uses the same scope, function matching, address rendering, and incumbent rules
|
|
as materialization and reports ready targets, already materialized addresses,
|
|
vacancies, collisions, cycles, depth limits, and ambiguous paths. The preview
|
|
does not create a template, address, Postbox, or delivery. A large result is
|
|
bounded in the UI while its aggregate counts remain visible.
|
|
|
|
Addresses should be resolved lazily and idempotently rather than eagerly
|
|
creating empty containers for every unit. They remain durable through vacancy
|
|
and reassignment. A delivery snapshots the template revision and normalized
|
|
organization/function references so later hierarchy changes do not rewrite
|
|
history.
|
|
|
|
Exact postboxes remain useful for exceptional responsibilities that do not
|
|
belong to a reusable function type.
|
|
|
|
## Unified Inbox Projections
|
|
|
|
A user with several functions can group selected visible postboxes into named
|
|
unified inbox views and keep other responsibilities separate. Grouping is a
|
|
query projection only. It never merges source containers, messages, read or
|
|
acknowledgement state, retention, encryption keys, or audit evidence.
|
|
|
|
The optional Tasks module may aggregate available unread Postbox messages into
|
|
the common work inbox. This is a current, permission-rechecked projection of a
|
|
personal read receipt, not a copied task or message. Reading the message in
|
|
Postbox removes the projection; Postbox remains authoritative for content,
|
|
access, acknowledgement, reply, retention, and evidence.
|
|
|
|
Grouping summaries calculate total and unread counts over the currently
|
|
visible source Postboxes in one tenant-bounded query. Hidden sources retained
|
|
for later reassignment do not leak counts into the projection.
|
|
|
|
Every item and action continues to show the source function, unit, postbox,
|
|
assignment/delegation context, and classification. Policy may require some
|
|
postboxes to remain separate. Every exact Postbox and immutable template
|
|
revision therefore selects one grouping rule: allow combining, combine only
|
|
with the same classification, or always remain separate. The configured reason
|
|
is returned as constraint provenance. Rules are checked both when saving a
|
|
personal grouping and when reading any aggregate projection, so an existing
|
|
preference cannot bypass a later policy or assignment change.
|
|
|
|
The route parameter `?grouping=<personal-grouping-id>` is the stable,
|
|
permission-neutral selector for a task-focused View. Postbox ignores an unknown
|
|
or no-longer-visible selection, rechecks all sources, and never treats the View
|
|
as authority. Temporarily unavailable source preferences remain stored without
|
|
returning their metadata or counts and become eligible again only after current
|
|
access is restored.
|
|
|
|
## Hierarchy Routing
|
|
|
|
Hierarchy behavior is disabled by default. The system distinguishes:
|
|
|
|
- a linked copy delivered to a parent function postbox
|
|
- attention or escalation metadata sent to a parent responsibility
|
|
- shared visibility over the original message
|
|
|
|
These have different privacy, retention, acknowledgement, and audit effects
|
|
and must not be treated as synonyms.
|
|
|
|
The first production slice should implement explicit linked-copy routing with
|
|
a selected structure, target function mapping, maximum depth, stop condition,
|
|
classification gate, loop protection, and delivery-time route snapshot.
|
|
Organization changes do not retroactively expose old messages.
|
|
|
|
Vacancy is a visible delivery/attention state rather than an automatic grant
|
|
to an unrelated personal account. Policy may trigger a bounded escalation
|
|
after a delay.
|
|
|
|
The implemented policy keeps the three semantics separate:
|
|
|
|
- `linked_copy` can target the nearest matching ancestor or every bounded
|
|
matching ancestor in one selected structure.
|
|
- `attention` currently supports delayed vacancy escalation over the remaining
|
|
delivery-time target snapshot.
|
|
- `shared_visibility` remains explicitly disabled until its access and
|
|
encryption semantics are implemented.
|
|
|
|
Routing is off unless an immutable template revision enables it and supplies a
|
|
target template, target function type, producer allowlist, classification
|
|
allowlist, depth, and structure. Optional relation, stop-unit, stop-unit-type,
|
|
expiry, and maximum-retention gates narrow the route further. The dry-run API
|
|
returns blocked and unavailable candidates without materializing addresses.
|
|
Delivery materializes only frozen candidates, stores path-edge provenance, and
|
|
creates source-preserving copies with independent read and acknowledgement
|
|
receipts.
|
|
|
|
## Campaign Distribution
|
|
|
|
Campaign can use Postbox as an explicit delivery channel through
|
|
`postbox.delivery`. A campaign may select Mail, Postbox, both, or a configured
|
|
fallback order for a target. It must never switch channels silently.
|
|
|
|
Validation and build preview stable function/unit/context destinations,
|
|
vacancies, hierarchy-copy effects, classifications, and duplicates. Delivery
|
|
uses idempotency keys and returns per-target evidence. Postbox owns acceptance,
|
|
routing, message state, read/acknowledgement state, and postbox ids; Campaign
|
|
owns campaign preparation, jobs, recipient reports, and channel-attempt
|
|
evidence.
|
|
|
|
## Domain Objects
|
|
|
|
The initial domain model should stay small:
|
|
|
|
- `PostboxTemplate` and immutable revisions: reusable function/scope
|
|
configuration.
|
|
- `PostboxAddress`: the stable tenant/function/unit/context destination.
|
|
- `Postbox`: the addressable container, materialized when needed.
|
|
- `PostboxBinding`: the binding to organization, role, portal, campaign, service, or explicit context.
|
|
- `PostboxMessage`: a platform-native message or message reference.
|
|
- `PostboxParticipant`: normalized sender, recipient, author, or actor reference.
|
|
- `PostboxAttachmentRef`: reference to a file, evidence item, generated campaign artifact, or external attachment.
|
|
- `PostboxDelivery` and `PostboxRoute`: idempotent producer acceptance and
|
|
linked copy/escalation provenance.
|
|
- `PostboxGrouping`: a per-user source-preserving inbox projection.
|
|
- `PostboxAccessEvent`: auditable record of access-affecting changes and sensitive actions.
|
|
|
|
Messages and files should be linked by stable ids and typed references. The postbox module should not import file, mail, or campaign internals.
|
|
|
|
## Capability Boundaries
|
|
|
|
Postbox should expose narrow capabilities through core:
|
|
|
|
- `postbox.directory`: find postboxes visible to an actor.
|
|
- `postbox.access`: answer access decisions for a postbox/action pair.
|
|
- `postbox.messages`: create, list, and read postbox messages through DTOs.
|
|
- `postbox.delivery`: accept messages or delivery artifacts from other modules.
|
|
- `postbox.evidence`: link durable evidence references without owning the evidence store.
|
|
|
|
Optional consumers:
|
|
|
|
- Campaign can use postboxes for function-targeted delivery, sender context,
|
|
reply intake, review queues, and access to campaign artifacts.
|
|
- Files can expose file references to a postbox when the actor has effective
|
|
source-postbox access.
|
|
- Portal can show portal-facing postboxes without owning the postbox access model.
|
|
- Mail can bridge external mailbox delivery into postboxes when configured, without making postboxes mailbox-bound.
|
|
- Notifications can receive generic, metadata-only Postbox lifecycle commands
|
|
for current IDM holders. It owns user preferences, quiet periods, and future
|
|
email/push channel policy; Postbox remains correct when it is absent.
|
|
|
|
## Operational Rules
|
|
|
|
- Current function assignment and delegation state controls current access.
|
|
- Historical message records remain durable even when no current person holds
|
|
the function; vacancy is a visible attention/access state, not a missing
|
|
postbox.
|
|
- Multiple incumbents receive independent device-bound key grants and remain
|
|
distinguishable in access and action evidence.
|
|
- Delegation start, expiry, withdrawal, key grant, and key-epoch rotation are
|
|
separate auditable events.
|
|
- The periodic Postbox worker batches IDM incumbency resolution and reconciles
|
|
assignment/delegation visibility, vacancy, and reassignment through a durable
|
|
cursor. First deployment establishes a quiet baseline; later changes and
|
|
failed notification handoffs are deduplicated and retryable.
|
|
- Notification and platform-event payloads contain stable Postbox, message,
|
|
route, delivery, function, assignment, classification, and producer
|
|
provenance only. Subjects, bodies, participant addresses, attachment details,
|
|
ciphertext, and key material never enter lifecycle payloads.
|
|
- Producers mark actionable deliveries through the typed `action_required`
|
|
delivery flag; Postbox emits a dedicated event and raises the generic in-app
|
|
priority without copying the producer's subject or body.
|
|
- Notification links do not preserve authority. Assignment expiry, withdrawal,
|
|
classification policy, and generic Access permissions are rechecked when the
|
|
target is opened.
|
|
- Administration of bindings should require explicit postbox administration permission plus access/RBAC authority for the target organization.
|
|
- Sensitive access decisions and binding changes should emit audit events.
|
|
- Retention rules should be postbox-owned but able to reference campaign, file, and portal provenance.
|
|
- Expiry, withdrawal, and retraction UI must distinguish future access control
|
|
from already fetched or decrypted plaintext.
|
|
- Message metadata should preserve room for ciphertext manifests, wrapped keys,
|
|
key epochs, recipient device references, and external capability tokens even
|
|
before full E2EE ships.
|
|
|
|
### Retention, Audit, And Privacy
|
|
|
|
Postbox retention is owned by the postbox module because postbox messages are
|
|
platform-native communication records, not mailbox folders and not ordinary file
|
|
shares. Retention policies may reference provenance from campaign, file, portal,
|
|
mail, or workflow modules, but those modules should pass stable ids and typed
|
|
evidence references through capabilities instead of giving postbox direct access
|
|
to their internals.
|
|
|
|
The postbox module should emit audit events for:
|
|
|
|
- postbox creation, archival, and destructive retirement
|
|
- binding creation, changes, expiry, and removal
|
|
- sensitive access checks when an actor gains or loses visibility
|
|
- message creation, read/download of sensitive content, attachment linking, and
|
|
delivery handoff
|
|
- retention holds, retention expiry, export, and destruction decisions
|
|
|
|
Privacy behavior must separate current access from historical evidence. Losing
|
|
a function assignment or generic Postbox permission removes future visibility,
|
|
but it does not rewrite the fact that a person previously accessed a message or
|
|
that a message existed. Deletion and destructive retention actions must
|
|
preserve legally required audit/evidence records while removing or redacting
|
|
content according to the effective policy.
|
|
|
|
When E2EE is enabled later, retention and audit metadata must remain operable
|
|
without decrypting message content. UI copy should be honest: expiry,
|
|
withdrawal, or revocation can prevent future platform access, but it cannot
|
|
guarantee removal of plaintext already fetched, exported, printed, or delivered
|
|
outside the platform.
|
|
|
|
### Migration And Compatibility Ownership
|
|
|
|
Postbox-owned tables, DTOs, migrations, and capability names belong in
|
|
`govoplan-postbox`. Core may temporarily contain compatibility imports or
|
|
legacy migration references only when needed to keep existing installations
|
|
upgradable while code is being extracted.
|
|
|
|
Compatibility code must be narrow and documented:
|
|
|
|
- new postbox behavior is implemented in `govoplan-postbox`
|
|
- old import paths may re-export postbox DTOs or helpers during a transition,
|
|
but must not become active owners of postbox logic
|
|
- migrations that move tables to postbox ownership must preserve existing data
|
|
and have explicit downgrade/retirement notes
|
|
- optional integrations with campaign, files, portal, or mail remain capability
|
|
contracts, not direct imports
|
|
|
|
Once supported release migrations have crossed the compatibility window, legacy
|
|
core import aliases and old table ownership comments should be removed through
|
|
a normal cleanup issue.
|
|
|
|
## First Implementation Shape
|
|
|
|
The first implementation should define the backend manifest, permissions, DTOs, and migrations before building rich UI. A minimal API can then support directory lookup, access checks, message creation, message listing, and binding administration.
|
|
|
|
The WebUI should start as an administration and inbox surface:
|
|
|
|
- postbox directory
|
|
- function-bound access explanation
|
|
- message list and message detail
|
|
- template and binding editor for organization/function links
|
|
- audit-visible administrative actions
|
|
|
|
Campaign, files, portal, and mail behavior should arrive as optional integrations after the core postbox model is stable.
|
|
|
|
### Configurable content-protection profiles
|
|
|
|
An exact Postbox or template revision selects one profile. The administration
|
|
surface recommends the managed profile and requires its vault explicitly; the
|
|
API retains the legacy plaintext default when an older integration omits these
|
|
new fields so an upgrade cannot make an unavailable Encryption module block
|
|
existing automation.
|
|
|
|
- `server_envelope_v1` is the recommended standard. New local message bodies
|
|
are encrypted through the optional `encryption.content_cipher` capability,
|
|
stored in `body_ciphertext`, and linked to an owner-bound envelope in the
|
|
institution's selected vault. Authorized reads ask that capability to open
|
|
the exact tenant, message, and envelope tuple. Missing Encryption, unavailable
|
|
or destroyed keys, tampering, and resource mismatch fail closed.
|
|
- `external_e2ee_v1` is a server-blind storage contract. Clear bodies are
|
|
rejected. A producer must provide a ciphertext reference, signed manifest,
|
|
wrapped recipient keys for the current key epoch, and `sha256` content digest.
|
|
The server retains and authorizes those artifacts but has no private key with
|
|
which to decrypt them.
|
|
- `plaintext_v1` stores the body without content encryption. It remains
|
|
available for deployments that explicitly choose transport and
|
|
infrastructure controls only.
|
|
|
|
No profile hides operational metadata. Subjects, senders, participants,
|
|
routing, timestamps, classifications, attachment references, receipts,
|
|
retention state, and access evidence remain server-visible. Native browser or
|
|
device enrollment, private-key custody, offline recovery, and independently
|
|
reviewed cryptographic clients are not bundled by Postbox; an institution that
|
|
selects E2EE must provide and govern that client/provider boundary.
|
|
|
|
### Protection and hand-over policy
|
|
|
|
Each Postbox snapshots policy for the choices that cannot safely be inferred:
|
|
|
|
- a new incumbent sees all retained history, content since assignment, or a
|
|
bounded look-back period;
|
|
- ordinary and compromise rotations select key rewrapping or full content
|
|
re-encryption;
|
|
- recovery, hand-over, emergency access, export, and destruction name the
|
|
required user-consent, institutional key-holder, or dual-control authority
|
|
and quorum;
|
|
- external retrieval requires strong identity, email plus a one-time code, or
|
|
may be disabled; and
|
|
- vacancy escalation is always metadata-only and never gives an unrelated
|
|
personal account content access.
|
|
|
|
The defaults are deliberately conservative: history since assignment,
|
|
ordinary rewrapping, re-encryption after compromise, two-person institutional
|
|
recovery, dual-control hand-over/emergency/export/destruction, strong external
|
|
identity, and metadata-only vacancy escalation. These are product defaults, not
|
|
hard-coded policy decisions; administrators can change them per template or
|
|
exact Postbox.
|
|
|
|
### Governed profile transitions
|
|
|
|
A profile change increments the Postbox key epoch and applies immediately to
|
|
new messages. The administrator chooses whether retained history stays under
|
|
its existing profile or is migrated. Every transition records an idempotency
|
|
key, source and target profiles/vaults, user-consent and/or institutional
|
|
authorization evidence, quorum, reason, immutable configuration snapshot,
|
|
message digests, and per-message outcome.
|
|
|
|
Plaintext-to-managed and managed-to-plaintext migrations can complete through
|
|
the configured Encryption capability. Managed decrypt, export, and
|
|
re-encryption operations are also written to the Encryption migration ledger;
|
|
old envelopes are not merely orphaned. A transition to or from E2EE pauses each
|
|
historical message until an approved external client supplies the ciphertext or
|
|
plaintext transform and evidence. Postbox checks the immutable SHA-256 digest
|
|
before committing the new representation. Leaving E2EE requires user-consent
|
|
evidence; changing institution-managed history requires institutional
|
|
key-holder evidence; dual control can require both. Previously viewed, copied,
|
|
printed, or exported cleartext cannot be recalled and must be acknowledged.
|
|
|
|
Database recovery of managed messages requires Postbox and Encryption tables
|
|
from the same consistency point plus the provider/deployment key. Recovery of
|
|
E2EE content additionally depends on the institution's external private-key
|
|
custody and client procedures.
|