commit 3e4d945b701f884c78028ea6c836bb0da7a49c69 Author: Albrecht Degering Date: Tue Jul 7 20:43:20 2026 +0200 Seed postbox workflow and concept docs diff --git a/.gitea/ISSUE_TEMPLATE/bug_report.md b/.gitea/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..6d24c01 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,35 @@ +--- +name: "Bug" +about: "Report a reproducible defect, regression, or incorrect behavior" +title: "[Bug] " +labels: + - type/bug + - status/triage + - module/postbox +--- + +## 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: diff --git a/.gitea/ISSUE_TEMPLATE/config.yaml b/.gitea/ISSUE_TEMPLATE/config.yaml new file mode 100644 index 0000000..3ba13e0 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/config.yaml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.gitea/ISSUE_TEMPLATE/docs_workflow.md b/.gitea/ISSUE_TEMPLATE/docs_workflow.md new file mode 100644 index 0000000..503d7e7 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/docs_workflow.md @@ -0,0 +1,27 @@ +--- +name: "Docs / workflow" +about: "Request documentation, process, or developer workflow changes" +title: "[Docs] " +labels: + - type/docs + - status/triage + - module/postbox + - 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? diff --git a/.gitea/ISSUE_TEMPLATE/feature_request.md b/.gitea/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..ecb939e --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,32 @@ +--- +name: "Feature" +about: "Propose new user-visible behavior or platform capability" +title: "[Feature] " +labels: + - type/feature + - status/triage + - module/postbox +--- + +## 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: diff --git a/.gitea/ISSUE_TEMPLATE/task.md b/.gitea/ISSUE_TEMPLATE/task.md new file mode 100644 index 0000000..5274d9d --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/task.md @@ -0,0 +1,28 @@ +--- +name: "Task" +about: "Track implementation, maintenance, or migration work" +title: "[Task] " +labels: + - type/task + - status/triage + - module/postbox +--- + +## Objective + +What needs to be completed? + +## Scope + +- Owning repository: +- In-scope: +- Out-of-scope: + +## Checklist + +- [ ] +- [ ] + +## Verification Target + +Command or manual check: diff --git a/.gitea/ISSUE_TEMPLATE/tech_debt.md b/.gitea/ISSUE_TEMPLATE/tech_debt.md new file mode 100644 index 0000000..751bc8f --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/tech_debt.md @@ -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/postbox +--- + +## 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: diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..1984736 --- /dev/null +++ b/.gitea/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,15 @@ +## Issue + +Closes # + +## Summary + +- + +## Verification + +- + +## Notes + +Follow-up issues: diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ff0c802 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,31 @@ +# GovOPlaN Postbox Codex Guide + +## 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`. + +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. + +## Local Commands + +Install and run through core: + +```bash +cd /mnt/DATA/git/govoplan-core +./.venv/bin/python -m pip install -r requirements-dev.txt +``` + +For combined checks once implementation starts, run: + +```bash +cd /mnt/DATA/git/govoplan-core +./scripts/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. +- 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. +- Keep campaign delivery, file evidence, and portal usage optional behind capability boundaries. diff --git a/README.md b/README.md new file mode 100644 index 0000000..bf75bd8 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# govoplan-postbox + +GovOPlaN Postbox provides platform-owned postboxes for internal work, portals, campaign flows, and role-bound organizational communication. + +## Ownership + +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 +- 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. + +## Role-bound postboxes + +A role-bound postbox is linked to an organizational unit and one or more roles. A person can access that postbox while their identity has an effective matching role in that organizational unit. Access is not tied to a login mailbox, personal email address, or static user assignment. + +When the role assignment changes, postbox access changes with it. The postbox keeps durable message and evidence history, while authorization remains derived from current platform role state. + +## Module integration + +Backend entry point: + +```toml +[project.entry-points."govoplan.modules"] +postbox = "govoplan_postbox.backend.manifest:get_manifest" +``` + +Frontend package: + +```text +@govoplan/postbox-webui +``` + +Platform RBAC, module capability contracts, and governance rules are documented in `govoplan-core/docs/`. diff --git a/docs/POSTBOX_BACKLOG.md b/docs/POSTBOX_BACKLOG.md new file mode 100644 index 0000000..9be1f5b --- /dev/null +++ b/docs/POSTBOX_BACKLOG.md @@ -0,0 +1,27 @@ +# Postbox Backlog + +## P1 Immediate + +- [ ] Define the `postbox` backend manifest, permissions, module metadata, and capability names. +- [ ] Design the postbox, binding, message, participant, attachment-reference, and audit-event data model. +- [ ] Implement role-organization-bound access checks through core/access contracts. +- [ ] Add API DTOs and routes for postbox directory, access checks, message listing, message creation, and binding administration. +- [ ] Create focused tests for access changes when role assignments are granted, revoked, or expire. + +## P2 Module Integration + +- [ ] Define optional campaign integration for sender context, response intake, and artifact handoff. +- [ ] Define optional files integration for postbox-visible file and evidence references. +- [ ] Define optional portal integration for portal-facing role-bound postboxes. +- [ ] Define optional mail bridge semantics without making postboxes login-bound mailboxes. + +## P2 WebUI + +- [ ] Build the postbox administration route contribution and navigation metadata. +- [ ] Build an inbox view for visible postboxes, messages, and role-bound access explanation. +- [ ] Build a binding editor for organization and role links with audit-visible changes. + +## P3 Governance + +- [ ] Document retention, audit, and privacy rules for postbox messages and evidence references. +- [ ] Document migration ownership and compatibility imports while postbox code is extracted from core if needed. diff --git a/docs/POSTBOX_CONCEPT.md b/docs/POSTBOX_CONCEPT.md new file mode 100644 index 0000000..5a4ce3e --- /dev/null +++ b/docs/POSTBOX_CONCEPT.md @@ -0,0 +1,103 @@ +# 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 bound to platform context: organization, role, process, portal, campaign, or service responsibility. + +## Role-Organization-Bound Access + +The special access pattern is a postbox linked to an organizational unit and one or more roles. A person can access that postbox while their identity has an effective matching role in that organizational unit. + +Example: + +- Organizational unit: `District Office North` +- Required role: `Case Clerk` +- Postbox: `District Office North / Case Clerk Intake` + +Any identity currently holding the `Case Clerk` role for `District Office North` can see the postbox. When the role is removed or expires, access disappears without moving messages or reassigning a mailbox. + +This makes postboxes useful for responsibilities that outlive individuals: + +- intake desks +- role-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 + +## 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 minimum authorization inputs are: + +- postbox id +- tenant id +- organizational unit id +- required role id or role key +- actor identity id +- current effective role assignments from the access module +- 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 role assignment changes rather than direct postbox membership edits. + +## Domain Objects + +The initial domain model should stay small: + +- `Postbox`: the addressable container. +- `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. +- `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 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. +- 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. + +## Operational Rules + +- Current role state controls current access. +- Historical message records remain durable even when no current person holds the role. +- 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. + +## 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 +- role-bound access explanation +- message list and message detail +- binding editor for organization and role links +- audit-visible administrative actions + +Campaign, files, portal, and mail behavior should arrive as optional integrations after the core postbox model is stable. diff --git a/docs/gitea-labels.json b/docs/gitea-labels.json new file mode 100644 index 0000000..3f5994a --- /dev/null +++ b/docs/gitea-labels.json @@ -0,0 +1,404 @@ +[ + { + "name": "type/bug", + "color": "d73a4a", + "description": "A reproducible defect, regression, or incorrect behavior.", + "exclusive": true + }, + { + "name": "type/feature", + "color": "0e8a16", + "description": "New user-visible behavior or platform capability.", + "exclusive": true + }, + { + "name": "type/task", + "color": "1d76db", + "description": "Implementation, maintenance, migration, or operational work.", + "exclusive": true + }, + { + "name": "type/debt", + "color": "fbca04", + "description": "Cleanup, refactoring, risk reduction, or deferred engineering work.", + "exclusive": true + }, + { + "name": "type/docs", + "color": "5319e7", + "description": "Documentation, process, or developer workflow work.", + "exclusive": true + }, + { + "name": "priority/p0", + "color": "b60205", + "description": "Immediate stop-the-line priority.", + "exclusive": true + }, + { + "name": "priority/p1", + "color": "d93f0b", + "description": "High priority for the next focused work window.", + "exclusive": true + }, + { + "name": "priority/p2", + "color": "fbca04", + "description": "Normal planned priority.", + "exclusive": true + }, + { + "name": "priority/p3", + "color": "c2e0c6", + "description": "Low priority or opportunistic cleanup.", + "exclusive": true + }, + { + "name": "status/triage", + "color": "d4c5f9", + "description": "Needs review, ownership, priority, or acceptance criteria.", + "exclusive": true + }, + { + "name": "status/ready", + "color": "0e8a16", + "description": "Ready for implementation.", + "exclusive": true + }, + { + "name": "status/in-progress", + "color": "0052cc", + "description": "Currently being worked.", + "exclusive": true + }, + { + "name": "status/blocked", + "color": "b60205", + "description": "Cannot progress without a decision, dependency, credential, or external change.", + "exclusive": true + }, + { + "name": "status/needs-info", + "color": "f9d0c4", + "description": "Needs clarifying input before implementation can proceed safely.", + "exclusive": true + }, + { + "name": "module/access", + "color": "0e8a16", + "description": "GovOPlaN access, identity, authentication, RBAC, and administration behavior.", + "exclusive": false + }, + { + "name": "module/addresses", + "color": "5319e7", + "description": "GovOPlaN Addresses module behavior or integration.", + "exclusive": false + }, + { + "name": "module/admin", + "color": "006b75", + "description": "GovOPlaN Admin module behavior or integration.", + "exclusive": false + }, + { + "name": "module/appointments", + "color": "d876e3", + "description": "GovOPlaN Appointments module behavior or integration.", + "exclusive": false + }, + { + "name": "module/audit", + "color": "0e8a16", + "description": "GovOPlaN Audit module behavior or integration.", + "exclusive": false + }, + { + "name": "module/calendar", + "color": "bfd4f2", + "description": "GovOPlaN Calendar module behavior or integration.", + "exclusive": false + }, + { + "name": "module/campaign", + "color": "d876e3", + "description": "GovOPlaN campaign module behavior or integration.", + "exclusive": false + }, + { + "name": "module/cases", + "color": "1d76db", + "description": "GovOPlaN Cases module behavior or integration.", + "exclusive": false + }, + { + "name": "module/connectors", + "color": "c2e0c6", + "description": "GovOPlaN Connectors module behavior or integration.", + "exclusive": false + }, + { + "name": "module/core", + "color": "0052cc", + "description": "GovOPlaN core runner, shared primitives, shell, or extension points.", + "exclusive": false + }, + { + "name": "module/dms", + "color": "c5def5", + "description": "GovOPlaN Dms module behavior or integration.", + "exclusive": false + }, + { + "name": "module/erp", + "color": "fef2c0", + "description": "GovOPlaN Erp module behavior or integration.", + "exclusive": false + }, + { + "name": "module/files", + "color": "006b75", + "description": "GovOPlaN files module behavior or integration.", + "exclusive": false + }, + { + "name": "module/fit-connect", + "color": "fbca04", + "description": "GovOPlaN Fit Connect module behavior or integration.", + "exclusive": false + }, + { + "name": "module/forms", + "color": "f9d0c4", + "description": "GovOPlaN Forms module behavior or integration.", + "exclusive": false + }, + { + "name": "module/identity-trust", + "color": "d4c5f9", + "description": "GovOPlaN Identity Trust module behavior or integration.", + "exclusive": false + }, + { + "name": "module/idm", + "color": "0052cc", + "description": "GovOPlaN Idm module behavior or integration.", + "exclusive": false + }, + { + "name": "module/ledger", + "color": "5319e7", + "description": "GovOPlaN Ledger module behavior or integration.", + "exclusive": false + }, + { + "name": "module/mail", + "color": "5319e7", + "description": "GovOPlaN mail module behavior or integration.", + "exclusive": false + }, + { + "name": "module/notifications", + "color": "d876e3", + "description": "GovOPlaN Notifications module behavior or integration.", + "exclusive": false + }, + { + "name": "module/ops", + "color": "0e8a16", + "description": "GovOPlaN Ops module behavior or integration.", + "exclusive": false + }, + { + "name": "module/payments", + "color": "bfd4f2", + "description": "GovOPlaN Payments module behavior or integration.", + "exclusive": false + }, + { + "name": "module/policy", + "color": "d93f0b", + "description": "GovOPlaN Policy module behavior or integration.", + "exclusive": false + }, + { + "name": "module/postbox", + "color": "006b75", + "description": "GovOPlaN postbox module behavior or integration.", + "exclusive": false + }, + { + "name": "module/portal", + "color": "1d76db", + "description": "GovOPlaN Portal module behavior or integration.", + "exclusive": false + }, + { + "name": "module/reporting", + "color": "c2e0c6", + "description": "GovOPlaN Reporting module behavior or integration.", + "exclusive": false + }, + { + "name": "module/search", + "color": "bfdadc", + "description": "GovOPlaN Search module behavior or integration.", + "exclusive": false + }, + { + "name": "module/scheduling", + "color": "d93f0b", + "description": "GovOPlaN Scheduling module behavior or integration.", + "exclusive": false + }, + { + "name": "module/tasks", + "color": "c5def5", + "description": "GovOPlaN Tasks module behavior or integration.", + "exclusive": false + }, + { + "name": "module/templates", + "color": "fef2c0", + "description": "GovOPlaN Templates module behavior or integration.", + "exclusive": false + }, + { + "name": "module/tenancy", + "color": "e4e669", + "description": "GovOPlaN Tenancy module behavior or integration.", + "exclusive": false + }, + { + "name": "module/web", + "color": "bfd4f2", + "description": "GovOPlaN public website, product page, or publication content.", + "exclusive": false + }, + { + "name": "module/workflow", + "color": "f9d0c4", + "description": "GovOPlaN Workflow module behavior or integration.", + "exclusive": false + }, + { + "name": "module/xoev", + "color": "d4c5f9", + "description": "GovOPlaN Xoev module behavior or integration.", + "exclusive": false + }, + { + "name": "module/xrechnung", + "color": "0052cc", + "description": "GovOPlaN Xrechnung module behavior or integration.", + "exclusive": false + }, + { + "name": "module/xta-osci", + "color": "5319e7", + "description": "GovOPlaN Xta Osci module behavior or integration.", + "exclusive": false + }, + { + "name": "area/auth", + "color": "bfd4f2", + "description": "Authentication, sessions, access bootstrap, or login behavior.", + "exclusive": false + }, + { + "name": "area/tenancy", + "color": "bfdadc", + "description": "Tenant boundaries, provisioning, or tenant-scoped data behavior.", + "exclusive": false + }, + { + "name": "area/rbac", + "color": "c5def5", + "description": "Permissions, roles, delegation, or authorization policy.", + "exclusive": false + }, + { + "name": "area/governance", + "color": "fef2c0", + "description": "Governance policy, audit, privacy, retention, or compliance behavior.", + "exclusive": false + }, + { + "name": "area/module-system", + "color": "bfe5bf", + "description": "Module discovery, manifests, capabilities, routing, or optional integrations.", + "exclusive": false + }, + { + "name": "area/migrations", + "color": "e4e669", + "description": "Alembic migrations, schema bootstrap, or persistence evolution.", + "exclusive": false + }, + { + "name": "area/webui", + "color": "1d76db", + "description": "Shared WebUI shell, frontend components, routing, or frontend tests.", + "exclusive": false + }, + { + "name": "area/api", + "color": "5319e7", + "description": "HTTP API contracts, routers, schemas, or API smoke behavior.", + "exclusive": false + }, + { + "name": "area/db", + "color": "0e8a16", + "description": "Database sessions, models, transactions, or persistence primitives.", + "exclusive": false + }, + { + "name": "area/devex", + "color": "bfd4f2", + "description": "Local developer workflow, scripts, tests, tooling, or release helpers.", + "exclusive": false + }, + { + "name": "area/release", + "color": "c2e0c6", + "description": "Versioning, release locks, tags, packaging, or dependency pins.", + "exclusive": false + }, + { + "name": "area/docs", + "color": "5319e7", + "description": "Durable documentation and project guidance.", + "exclusive": false + }, + { + "name": "area/marketing", + "color": "f9d0c4", + "description": "Public website, product messaging, publication copy, or legal page content.", + "exclusive": false + }, + { + "name": "source/todo-scan", + "color": "cccccc", + "description": "Imported from inline TODO/FIXME/HACK markers by the Gitea TODO importer.", + "exclusive": false + }, + { + "name": "source/backlog-import", + "color": "bfdadc", + "description": "Imported from markdown backlog, roadmap, plan, or TODO files.", + "exclusive": false + }, + { + "name": "codex/ready", + "color": "0e8a16", + "description": "Suitable for Codex to pick up with the existing issue context.", + "exclusive": false + }, + { + "name": "codex/needs-human", + "color": "f9d0c4", + "description": "Needs an explicit human decision before Codex should implement.", + "exclusive": false + } +]