diff --git a/AGENTS.md b/AGENTS.md index 6b5732f..58747b8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/README.md b/README.md index 872c424..b887a4c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ **Repository type:** module (domain). -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: diff --git a/docs/POSTBOX_CONCEPT.md b/docs/POSTBOX_CONCEPT.md index 60f8fa7..de5d1c8 100644 --- a/docs/POSTBOX_CONCEPT.md +++ b/docs/POSTBOX_CONCEPT.md @@ -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.