Refine function-bound postbox architecture
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
|
||||
## Scope
|
||||
|
||||
This repository owns the `postbox` module: in-platform postboxes, role-organization-bound access, postbox messages, postbox directory APIs, internal and portal postbox surfaces, campaign postbox integration, postbox-owned migrations, and future `@govoplan/postbox-webui`.
|
||||
This repository owns the `postbox` module: in-platform postboxes, function-organization-bound access, postbox messages, postbox directory APIs, internal and portal postbox surfaces, campaign postbox integration, postbox-owned migrations, and future `@govoplan/postbox-webui`.
|
||||
|
||||
Postboxes are not login-bound mailboxes. They are platform-owned communication and access containers whose visibility is derived from organizational role assignments, explicit postbox bindings, and capability contracts.
|
||||
Postboxes are not login-bound mailboxes. They are platform-owned communication and access containers whose visibility is derived primarily from effective identity-to-organization-function assignments, explicit postbox bindings, and capability contracts.
|
||||
|
||||
## Local Commands
|
||||
|
||||
@@ -25,7 +25,8 @@ tools/checks/check-focused.sh
|
||||
## Working Rules
|
||||
|
||||
- Keep postbox behavior in this module, not core.
|
||||
- Derive role-organization access through core/access contracts; do not duplicate RBAC membership logic locally.
|
||||
- Resolve units and functions through Organizations and effective identity-to-function assignments through IDM. Use Core/Access for generic Postbox action authorization; do not turn a function assignment into an RBAC role merely to open its function postbox.
|
||||
- Do not duplicate identity, organization, assignment, hierarchy, or RBAC logic locally.
|
||||
- Do not import mail, files, campaign, or portal internals. Use manifests, capabilities, events, API routes, and typed DTOs.
|
||||
- Treat postbox access changes as auditable security events, especially when access changes because a role assignment changes.
|
||||
- Treat postbox access changes as auditable security events, especially when access changes because a function assignment, delegation, acting context, or generic Postbox permission changes.
|
||||
- Keep campaign delivery, file evidence, and portal usage optional behind capability boundaries.
|
||||
|
||||
41
README.md
41
README.md
@@ -4,7 +4,8 @@
|
||||
**Repository type:** module (domain).
|
||||
<!-- govoplan-repository-type:end -->
|
||||
|
||||
GovOPlaN Postbox provides platform-owned postboxes for internal work, portals, campaign flows, and role-bound organizational communication.
|
||||
GovOPlaN Postbox provides platform-owned postboxes for internal work, portals,
|
||||
campaign flows, and function-bound organizational communication.
|
||||
|
||||
## Ownership
|
||||
|
||||
@@ -13,21 +14,28 @@ This repository owns:
|
||||
- backend module manifest `postbox`
|
||||
- postbox permissions and policy checks
|
||||
- postbox, binding, message, participant, attachment-reference, and audit-facing data models
|
||||
- role-organization-bound access resolution for postboxes
|
||||
- function-organization-bound access resolution for postboxes through
|
||||
normalized Identity, IDM, Organizations, and Core/Access contracts
|
||||
- API routes for postbox directory, messages, access checks, and administration
|
||||
- optional integration capabilities for campaign, files, portal, notification, and mail-facing workflows
|
||||
- future WebUI package `@govoplan/postbox-webui`
|
||||
|
||||
Core owns auth, tenants, RBAC evaluation, database/session primitives, module discovery, migrations, CSRF/API helpers, and shell layout. Access owns identities, users, groups, roles, memberships, and administrative RBAC surfaces.
|
||||
Core owns auth, tenants, RBAC evaluation, database/session primitives, module
|
||||
discovery, migrations, CSRF/API helpers, and shell layout. Identity owns
|
||||
identities and account links. Organizations owns units, structures, function
|
||||
types, and concrete functions. IDM owns effective identity-to-function
|
||||
assignments, delegation, and acting-for facts. Access owns generic application
|
||||
roles, permissions, and administrative RBAC surfaces.
|
||||
|
||||
## Role-bound postboxes
|
||||
## Function-bound postboxes
|
||||
|
||||
A function-bound postbox is linked to an organizational unit and one or more
|
||||
institutional functions. It can exist with zero, one, or several current
|
||||
incumbents. A person can access it only while their identity has an effective
|
||||
assignment or time-bounded delegation in that organizational context. Access
|
||||
is not tied to a login mailbox, personal email address, or static user
|
||||
assignment.
|
||||
A function-bound postbox has a stable institutional address linked to an
|
||||
organizational unit and one or more functions. It can exist with zero, one, or
|
||||
several current incumbents. A person can access it only while their identity
|
||||
has an effective assignment or time-bounded delegation in that organizational
|
||||
context and their account may perform the relevant Postbox action. Access is
|
||||
not tied to a login mailbox, personal email address, static user assignment, or
|
||||
function-to-RBAC-role mapping.
|
||||
|
||||
When the assignment changes, postbox access and encrypted key grants change
|
||||
with it. The postbox keeps durable content and evidence history through
|
||||
@@ -35,6 +43,19 @@ vacancy, hand-over, and delegation; multiple incumbents receive independently
|
||||
auditable access. Revocation prevents future platform key access but cannot
|
||||
erase plaintext already fetched or exported.
|
||||
|
||||
Reusable Postbox templates may target a function type and organization scope.
|
||||
Unit-specific addresses are resolved lazily and remain stable through vacancy
|
||||
and reassignment. Exact postboxes remain available for exceptional
|
||||
responsibilities or case/service contexts.
|
||||
|
||||
Users holding several functions may group selected postboxes into unified
|
||||
inbox views. These are query projections only: messages, address, read state,
|
||||
retention, and evidence remain attached to their source postboxes.
|
||||
|
||||
Hierarchy propagation is off by default. Explicit copy, attention/escalation,
|
||||
and shared-visibility rules are distinct, bounded, classification-aware, and
|
||||
snapshotted when a message is delivered.
|
||||
|
||||
## Module integration
|
||||
|
||||
Backend entry point:
|
||||
|
||||
@@ -19,23 +19,26 @@ manifests, external-recipient tokens, or honest retraction semantics. The
|
||||
cross-module target architecture is recorded in
|
||||
`govoplan-core/docs/POSTBOX_E2EE_ARCHITECTURE.md`.
|
||||
|
||||
## Function/Role-Organization-Bound Access
|
||||
## Function-Organization-Bound Access
|
||||
|
||||
The special access pattern is a postbox linked to an organizational unit and
|
||||
one or more roles or functions. A person can access that postbox while their
|
||||
account has an effective matching function assignment in that organizational
|
||||
unit, or while a matching function maps to one of the required roles.
|
||||
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 role: `Case Clerk`
|
||||
- Required function: `Case Clerk`
|
||||
- Postbox: `District Office North / Case Clerk Intake`
|
||||
|
||||
Any identity/account currently holding the `Case Clerk` function or a mapped
|
||||
role for `District Office North` can see the postbox. When the function,
|
||||
delegation, or role mapping is removed or expires, access disappears without
|
||||
moving messages or reassigning a mailbox.
|
||||
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;
|
||||
@@ -47,10 +50,10 @@ its history.
|
||||
This makes postboxes useful for responsibilities that outlive individuals:
|
||||
|
||||
- intake desks
|
||||
- role-based service queues
|
||||
- function-based service queues
|
||||
- campaign sender or response desks
|
||||
- portal message inboxes for organizational responsibilities
|
||||
- file or evidence drops linked to a role in an organization
|
||||
- file or evidence drops linked to a function in an organization
|
||||
|
||||
### Incumbency, hand-over, and delegation
|
||||
|
||||
@@ -70,19 +73,33 @@ This makes postboxes useful for responsibilities that outlive individuals:
|
||||
replacement, or new version is a new linked object with provenance while the
|
||||
previous signed/ciphertext manifest remains governed by retention policy.
|
||||
|
||||
## Authorization Model
|
||||
## Directory And Authorization Model
|
||||
|
||||
Postbox authorization should be derived from access-owned identity and role data through core/access contracts. The postbox module stores postbox bindings and postbox-specific permissions, but it should not duplicate membership, group, or role resolution.
|
||||
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, role id, or role key
|
||||
- required function id or function type id
|
||||
- actor identity id
|
||||
- current effective function assignments, delegations, and roles from the
|
||||
access module
|
||||
- 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:
|
||||
@@ -93,27 +110,100 @@ The expected result is a narrow access decision:
|
||||
- can attach or link files
|
||||
- can administer bindings
|
||||
|
||||
Access changes must be auditable because a person can gain or lose postbox visibility through role assignment changes rather than direct postbox membership edits.
|
||||
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 access kernel capabilities:
|
||||
Runtime integration must use the kernel capabilities:
|
||||
|
||||
- `access.semanticDirectory` to inspect identity/account/function facts.
|
||||
- `access.explanation` to attach identity/account/function/role/right
|
||||
provenance to access decisions.
|
||||
- `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 access ORM models or duplicate function/role
|
||||
resolution. Acting-in-place access should require an explicit selected acting
|
||||
context once Access exposes that runtime selector.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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:
|
||||
|
||||
- `Postbox`: the addressable container.
|
||||
- `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.
|
||||
@@ -130,8 +220,10 @@ Postbox should expose narrow capabilities through core:
|
||||
|
||||
Optional consumers:
|
||||
|
||||
- Campaign can use postboxes for campaign sender context, reply intake, review queues, and role-bound access to campaign artifacts.
|
||||
- Files can expose file references to a postbox when the actor's role grants access.
|
||||
- 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.
|
||||
|
||||
@@ -173,10 +265,11 @@ The postbox module should emit audit events for:
|
||||
- retention holds, retention expiry, export, and destruction decisions
|
||||
|
||||
Privacy behavior must separate current access from historical evidence. Losing
|
||||
a role 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.
|
||||
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,
|
||||
@@ -212,9 +305,9 @@ The first implementation should define the backend manifest, permissions, DTOs,
|
||||
The WebUI should start as an administration and inbox surface:
|
||||
|
||||
- postbox directory
|
||||
- role-bound access explanation
|
||||
- function-bound access explanation
|
||||
- message list and message detail
|
||||
- binding editor for organization and role links
|
||||
- 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.
|
||||
|
||||
Reference in New Issue
Block a user