Compare commits

...

21 Commits

Author SHA1 Message Date
1aea3e7c4f fix: repair dev modules and scoped release git trust
Some checks are pending
Dependency Audit / dependency-audit (push) Waiting to run
Security Audit / security-audit (push) Waiting to run
2026-07-29 20:54:58 +02:00
3f9567af18 Gate Search on PostgreSQL in module CI
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Security Audit / security-audit (push) Has been cancelled
2026-07-29 20:37:08 +02:00
de16f11ce8 test(datasources): include immutable payload schema
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Security Audit / security-audit (push) Has been cancelled
2026-07-29 20:13:49 +02:00
9d6cdff4b8 CSS changes for release console
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Security Audit / security-audit (push) Has been cancelled
2026-07-29 19:35:49 +02:00
aa4050c0ca feat: expose focused backend reload mode
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Security Audit / security-audit (push) Has been cancelled
2026-07-29 18:52:55 +02:00
d3cdbd8c7a feat: include risk compliance in dev composition
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Security Audit / security-audit (push) Has been cancelled
2026-07-29 18:46:53 +02:00
7115c4711d chore: register search wiki projects and tickets
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Security Audit / security-audit (push) Has been cancelled
2026-07-29 15:50:35 +02:00
a3beca6fc5 chore: harden audit checks and issue taxonomy
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Security Audit / security-audit (push) Has been cancelled
2026-07-29 14:16:27 +02:00
11d45bce25 Register governed Views module
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Security Audit / security-audit (push) Has been cancelled
2026-07-28 21:04:55 +02:00
7b6135b89b Document governed Views architecture
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Security Audit / security-audit (push) Has been cancelled
2026-07-28 19:05:37 +02:00
5b79e7d377 test: verify pinned dataflow publication composition
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Security Audit / security-audit (push) Has been cancelled
2026-07-28 13:48:11 +02:00
6afb8fea76 fix: exclude generated artifacts from wiki sync
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Security Audit / security-audit (push) Has been cancelled
2026-07-28 12:52:46 +02:00
163b35c0af feat: integrate datasource and workflow modules
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Security Audit / security-audit (push) Has been cancelled
2026-07-28 12:45:05 +02:00
603e07cec5 Integrate Connectors and Dataflow validation
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Security Audit / security-audit (push) Has been cancelled
2026-07-28 11:14:38 +02:00
97dfd333c6 Register Dataflow module and work taxonomy
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Security Audit / security-audit (push) Has been cancelled
2026-07-28 01:34:06 +02:00
ba88c574b9 chore: move repositories to GovOPlaN organization
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Security Audit / security-audit (push) Has been cancelled
2026-07-27 15:46:51 +02:00
8d292184d4 ci: execute tagged module pytest suites
All checks were successful
Security Audit / security-audit (push) Successful in 4m3s
Security Audit Toolbox Update / toolbox-update (push) Successful in 4s
Dependency Audit / dependency-audit (push) Successful in 1m51s
2026-07-23 01:18:47 +02:00
52bd3527cd ci: install release test harness dependencies
Some checks failed
Dependency Audit / dependency-audit (push) Successful in 1m37s
Security Audit / security-audit (push) Has been cancelled
2026-07-23 01:15:07 +02:00
ff49dabf8f ci: retain installed artifact checks in matrix
All checks were successful
Dependency Audit / dependency-audit (push) Successful in 1m42s
Security Audit / security-audit (push) Successful in 4m7s
2026-07-23 00:57:24 +02:00
cd15aa514e ci: run artifact-aware module matrix tests
Some checks failed
Dependency Audit / dependency-audit (push) Successful in 1m36s
Security Audit / security-audit (push) Has been cancelled
2026-07-23 00:53:16 +02:00
a042baa1d3 fix(release): verify tags in mounted workspaces 2026-07-23 00:53:16 +02:00
65 changed files with 1608 additions and 342 deletions

View File

@@ -2,17 +2,24 @@
# Copy to a deployment-local .env or secret store. Do not commit populated secrets. # Copy to a deployment-local .env or secret store. Do not commit populated secrets.
APP_ENV=production APP_ENV=production
# Live graph changes are useful in development. Production should apply saved
# module state through a coordinated restart of all API and worker processes.
GOVOPLAN_MODULE_LIVE_APPLY_ENABLED=
GOVOPLAN_INSTALL_PROFILE=self-hosted GOVOPLAN_INSTALL_PROFILE=self-hosted
MASTER_KEY_B64=<generate-with-govoplan-config-env-template-generate-secrets> MASTER_KEY_B64=<generate-with-govoplan-config-env-template-generate-secrets>
DATABASE_URL=postgresql+psycopg://govoplan:change-me@127.0.0.1:5432/govoplan DATABASE_URL=postgresql+psycopg://govoplan:change-me@127.0.0.1:5432/govoplan
GOVOPLAN_DATABASE_URL_PGTOOLS=postgresql://govoplan:change-me@127.0.0.1:5432/govoplan GOVOPLAN_DATABASE_URL_PGTOOLS=postgresql://govoplan:change-me@127.0.0.1:5432/govoplan
GOVOPLAN_DB_POOL_SIZE=5
GOVOPLAN_DB_MAX_OVERFLOW=10
GOVOPLAN_DB_POOL_TIMEOUT_SECONDS=30
GOVOPLAN_DB_POOL_RECYCLE_SECONDS=1800
ENABLED_MODULES=tenancy,organizations,identity,access,admin,dashboard,policy,audit,files,mail,campaigns,calendar,docs,ops ENABLED_MODULES=tenancy,organizations,identity,idm,access,admin,dashboard,policy,audit,files,mail,campaigns,calendar,poll,scheduling,connectors,datasources,dataflow,workflow,views,search,risk_compliance,postbox,notifications,docs,ops
CELERY_ENABLED=true CELERY_ENABLED=true
REDIS_URL=redis://127.0.0.1:6379/0 REDIS_URL=redis://127.0.0.1:6379/0
CELERY_QUEUES=send_email,append_sent,notifications,calendar,default CELERY_QUEUES=send_email,append_sent,notifications,calendar,dataflow,events,default
CALENDAR_OUTBOX_TERMINAL_RETENTION_DAYS=90 CALENDAR_OUTBOX_TERMINAL_RETENTION_DAYS=90
GOVOPLAN_CONNECTOR_ALLOW_PRIVATE_NETWORKS=false GOVOPLAN_CONNECTOR_ALLOW_PRIVATE_NETWORKS=false
@@ -24,10 +31,14 @@ GOVOPLAN_HTTP_MAX_REQUEST_BODY_BYTES=536870912
GOVOPLAN_HTTP_HSTS_SECONDS=31536000 GOVOPLAN_HTTP_HSTS_SECONDS=31536000
AUTH_LOGIN_THROTTLE_ENABLED=true AUTH_LOGIN_THROTTLE_ENABLED=true
AUTH_ACTIVITY_TOUCH_INTERVAL_SECONDS=300
AUTH_LOGIN_THROTTLE_IDENTITY_LIMIT=10 AUTH_LOGIN_THROTTLE_IDENTITY_LIMIT=10
AUTH_LOGIN_THROTTLE_CLIENT_LIMIT=100 AUTH_LOGIN_THROTTLE_CLIENT_LIMIT=100
AUTH_LOGIN_THROTTLE_WINDOW_SECONDS=900 AUTH_LOGIN_THROTTLE_WINDOW_SECONDS=900
AUTH_LOGIN_THROTTLE_REDIS_RETRY_SECONDS=30 AUTH_LOGIN_THROTTLE_REDIS_RETRY_SECONDS=30
# Production startup fails without Redis unless this explicit single-process
# risk acknowledgement is enabled.
GOVOPLAN_ALLOW_PROCESS_LOCAL_LOGIN_THROTTLE=false
CORS_ORIGINS=https://govoplan.example.org CORS_ORIGINS=https://govoplan.example.org
GOVOPLAN_TRUSTED_HOSTS=govoplan.example.org GOVOPLAN_TRUSTED_HOSTS=govoplan.example.org

View File

@@ -23,8 +23,8 @@ jobs:
node-version: "22" node-version: "22"
- name: Use anonymous HTTPS for public GovOPlaN repositories - name: Use anonymous HTTPS for public GovOPlaN repositories
run: | run: |
git config --global --add url."https://git.add-ideas.de/add-ideas/govoplan".insteadOf "git@git.add-ideas.de:add-ideas/govoplan" git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "git@git.add-ideas.de:GovOPlaN/govoplan"
git config --global --add url."https://git.add-ideas.de/add-ideas/govoplan".insteadOf "ssh://git@git.add-ideas.de/add-ideas/govoplan" git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "ssh://git@git.add-ideas.de/GovOPlaN/govoplan"
- name: Bootstrap GovOPlaN repositories - name: Bootstrap GovOPlaN repositories
working-directory: govoplan working-directory: govoplan
run: python tools/repo/bootstrap-repositories.py --parent .. --transport public-https --exclude-repo addideas-govoplan-website run: python tools/repo/bootstrap-repositories.py --parent .. --transport public-https --exclude-repo addideas-govoplan-website

View File

@@ -7,6 +7,18 @@ on:
jobs: jobs:
module-matrix: module-matrix:
runs-on: ubuntu-latest runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: govoplan_test
POSTGRES_USER: govoplan
POSTGRES_PASSWORD: govoplan_test
options: >-
--health-cmd "pg_isready -U govoplan -d govoplan_test"
--health-interval 5s
--health-timeout 5s
--health-retries 20
steps: steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with: with:
@@ -19,8 +31,8 @@ jobs:
node-version: "22" node-version: "22"
- name: Use anonymous HTTPS for public GovOPlaN repositories - name: Use anonymous HTTPS for public GovOPlaN repositories
run: | run: |
git config --global --add url."https://git.add-ideas.de/add-ideas/govoplan".insteadOf "git@git.add-ideas.de:add-ideas/govoplan" git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "git@git.add-ideas.de:GovOPlaN/govoplan"
git config --global --add url."https://git.add-ideas.de/add-ideas/govoplan".insteadOf "ssh://git@git.add-ideas.de/add-ideas/govoplan" git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "ssh://git@git.add-ideas.de/GovOPlaN/govoplan"
- name: Bootstrap GovOPlaN repositories - name: Bootstrap GovOPlaN repositories
working-directory: govoplan working-directory: govoplan
run: python tools/repo/bootstrap-repositories.py --parent .. --transport public-https --exclude-repo addideas-govoplan-website run: python tools/repo/bootstrap-repositories.py --parent .. --transport public-https --exclude-repo addideas-govoplan-website
@@ -33,6 +45,21 @@ jobs:
- name: Install WebUI release dependencies with test scripts - name: Install WebUI release dependencies with test scripts
working-directory: govoplan working-directory: govoplan
run: bash tools/release/install-webui-release-dependencies.sh ../govoplan-core/webui run: bash tools/release/install-webui-release-dependencies.sh ../govoplan-core/webui
- name: Validate Search against PostgreSQL
working-directory: govoplan
env:
GOVOPLAN_SEARCH_POSTGRES_URL: postgresql+psycopg://govoplan:govoplan_test@postgres:5432/govoplan_test
run: |
GOVOPLAN_CORE_ROOT="$PWD/../govoplan-core" .venv/bin/python tools/checks/postgres-integration-check.py \
--database-url "$GOVOPLAN_SEARCH_POSTGRES_URL" \
--module-set search=tenancy,access,search \
--reset-schema \
--skip-retirement-atomicity
PYTHONPATH="$PWD/../govoplan-search/src:$PWD/../govoplan-core/src" \
.venv/bin/python -m unittest discover \
-s ../govoplan-search/tests \
-p test_postgres_search.py \
-v
- name: Run module matrix and contract tests - name: Run module matrix and contract tests
working-directory: govoplan working-directory: govoplan
run: GOVOPLAN_CORE_ROOT="$PWD/../govoplan-core" PYTHON="$PWD/.venv/bin/python" bash tools/checks/check-module-matrix.sh run: GOVOPLAN_CORE_ROOT="$PWD/../govoplan-core" PYTHON="$PWD/.venv/bin/python" bash tools/checks/check-module-matrix.sh

View File

@@ -18,8 +18,8 @@ jobs:
node-version: "22" node-version: "22"
- name: Use anonymous HTTPS for public GovOPlaN repositories - name: Use anonymous HTTPS for public GovOPlaN repositories
run: | run: |
git config --global --add url."https://git.add-ideas.de/add-ideas/govoplan".insteadOf "git@git.add-ideas.de:add-ideas/govoplan" git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "git@git.add-ideas.de:GovOPlaN/govoplan"
git config --global --add url."https://git.add-ideas.de/add-ideas/govoplan".insteadOf "ssh://git@git.add-ideas.de/add-ideas/govoplan" git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "ssh://git@git.add-ideas.de/GovOPlaN/govoplan"
- name: Bootstrap GovOPlaN repositories - name: Bootstrap GovOPlaN repositories
working-directory: govoplan working-directory: govoplan
run: python tools/repo/bootstrap-repositories.py --parent .. --transport public-https --exclude-repo addideas-govoplan-website run: python tools/repo/bootstrap-repositories.py --parent .. --transport public-https --exclude-repo addideas-govoplan-website
@@ -28,7 +28,7 @@ jobs:
run: | run: |
python -m venv .venv python -m venv .venv
.venv/bin/python tools/repo/sync-python-environment.py --requirements requirements-release.txt --python .venv/bin/python --upgrade-pip .venv/bin/python tools/repo/sync-python-environment.py --requirements requirements-release.txt --python .venv/bin/python --upgrade-pip
.venv/bin/python -m pip install '../govoplan-core[dev]' .venv/bin/python -m pip install -r requirements-release-tests.txt '../govoplan-core[dev]'
- name: Install WebUI release dependencies - name: Install WebUI release dependencies
working-directory: govoplan working-directory: govoplan
run: bash tools/release/install-webui-release-dependencies.sh ../govoplan-core/webui run: bash tools/release/install-webui-release-dependencies.sh ../govoplan-core/webui

View File

@@ -22,8 +22,8 @@ jobs:
path: govoplan path: govoplan
- name: Use anonymous HTTPS for public GovOPlaN repositories - name: Use anonymous HTTPS for public GovOPlaN repositories
run: | run: |
git config --global --add url."https://git.add-ideas.de/add-ideas/govoplan".insteadOf "git@git.add-ideas.de:add-ideas/govoplan" git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "git@git.add-ideas.de:GovOPlaN/govoplan"
git config --global --add url."https://git.add-ideas.de/add-ideas/govoplan".insteadOf "ssh://git@git.add-ideas.de/add-ideas/govoplan" git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "ssh://git@git.add-ideas.de/GovOPlaN/govoplan"
- name: Configure SSH for repository bootstrap - name: Configure SSH for repository bootstrap
env: env:
GOVOPLAN_RELEASE_SSH_KEY_B64: ${{ secrets.GOVOPLAN_RELEASE_SSH_KEY_B64 }} GOVOPLAN_RELEASE_SSH_KEY_B64: ${{ secrets.GOVOPLAN_RELEASE_SSH_KEY_B64 }}

View File

@@ -4,10 +4,10 @@
**Repository type:** system (meta). **Repository type:** system (meta).
<!-- govoplan-repository-type:end --> <!-- govoplan-repository-type:end -->
[![Module Matrix](https://git.add-ideas.de/add-ideas/govoplan/actions/workflows/module-matrix.yml/badge.svg?branch=main)](https://git.add-ideas.de/add-ideas/govoplan/actions?workflow=module-matrix.yml&actor=0&status=0) [![Module Matrix](https://git.add-ideas.de/GovOPlaN/govoplan/actions/workflows/module-matrix.yml/badge.svg?branch=main)](https://git.add-ideas.de/GovOPlaN/govoplan/actions?workflow=module-matrix.yml&actor=0&status=0)
[![Release Integration](https://git.add-ideas.de/add-ideas/govoplan/actions/workflows/release-integration.yml/badge.svg?branch=main)](https://git.add-ideas.de/add-ideas/govoplan/actions?workflow=release-integration.yml&actor=0&status=0) [![Release Integration](https://git.add-ideas.de/GovOPlaN/govoplan/actions/workflows/release-integration.yml/badge.svg?branch=main)](https://git.add-ideas.de/GovOPlaN/govoplan/actions?workflow=release-integration.yml&actor=0&status=0)
[![Dependency Audit](https://git.add-ideas.de/add-ideas/govoplan/actions/workflows/dependency-audit.yml/badge.svg?branch=main)](https://git.add-ideas.de/add-ideas/govoplan/actions?workflow=dependency-audit.yml&actor=0&status=0) [![Dependency Audit](https://git.add-ideas.de/GovOPlaN/govoplan/actions/workflows/dependency-audit.yml/badge.svg?branch=main)](https://git.add-ideas.de/GovOPlaN/govoplan/actions?workflow=dependency-audit.yml&actor=0&status=0)
[![Security Audit](https://git.add-ideas.de/add-ideas/govoplan/actions/workflows/security-audit.yml/badge.svg?branch=main)](https://git.add-ideas.de/add-ideas/govoplan/actions?workflow=security-audit.yml&actor=0&status=0) [![Security Audit](https://git.add-ideas.de/GovOPlaN/govoplan/actions/workflows/security-audit.yml/badge.svg?branch=main)](https://git.add-ideas.de/GovOPlaN/govoplan/actions?workflow=security-audit.yml&actor=0&status=0)
This is the GovOPlaN meta repository. It is the operator entry point for This is the GovOPlaN meta repository. It is the operator entry point for
whole-product development, release orchestration, repository bootstrap, and whole-product development, release orchestration, repository bootstrap, and
@@ -41,6 +41,16 @@ Open the WebUI in a browser after launch only when explicitly requested:
GOVOPLAN_OPEN_BROWSER=1 ./tools/launch/launch-dev.sh GOVOPLAN_OPEN_BROWSER=1 ./tools/launch/launch-dev.sh
``` ```
Limit backend reload triggers during focused module work without changing the
enabled module graph:
```sh
GOVOPLAN_BACKEND_RELOAD_MODULES=calendar,campaign ./tools/launch/launch-dev.sh
```
Set `GOVOPLAN_BACKEND_RELOAD_MODULES=none` to watch only core/config sources.
Leaving it unset keeps the broad default and watches all enabled modules.
Start the shared development PostgreSQL service: Start the shared development PostgreSQL service:
```sh ```sh
@@ -151,6 +161,9 @@ Meta ownership and module install/contract boundaries are documented in
`docs/META_REPO_SCAN.md` and `docs/MODULE_CONTRACTS_AND_INSTALLS.md`. `docs/META_REPO_SCAN.md` and `docs/MODULE_CONTRACTS_AND_INSTALLS.md`.
Frontend layout principles for module pages are documented in Frontend layout principles for module pages are documented in
`docs/FRONTEND_LAYOUT_PRINCIPLES.md`. `docs/FRONTEND_LAYOUT_PRINCIPLES.md`.
The provider-neutral datasource boundary and reusable Dataflow/Workflow graph
contract are documented in
`docs/DATASOURCE_AND_DEFINITION_GRAPH_ARCHITECTURE.md`.
The cross-product destination, stakeholder visions, configuration archetypes, The cross-product destination, stakeholder visions, configuration archetypes,
connected outcome stories, and capability horizons are documented in connected outcome stories, and capability horizons are documented in
the [Connected Governance Platform Roadmap](docs/CONNECTED_GOVERNANCE_PLATFORM_ROADMAP.md). the [Connected Governance Platform Roadmap](docs/CONNECTED_GOVERNANCE_PLATFORM_ROADMAP.md).

View File

@@ -57,7 +57,7 @@ profile intentionally runs only PostgreSQL and Redis in containers; API,
WebUI, worker, and scheduler processes still run from editable source trees. A WebUI, worker, and scheduler processes still run from editable source trees. A
target deployment must supply TLS termination, process supervision, secret target deployment must supply TLS termination, process supervision, secret
injection, monitoring, backup storage, and recovery procedures. The open injection, monitoring, backup storage, and recovery procedures. The open
[Core backup/restore issue #29](https://git.add-ideas.de/add-ideas/govoplan-core/issues/29) [Core backup/restore issue #29](https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/29)
is a production gate. Identity federation, external audit export, and a tested is a production gate. Identity federation, external audit export, and a tested
disaster-recovery plan are also not complete. disaster-recovery plan are also not complete.
@@ -134,7 +134,7 @@ persistence, and durable shared or object storage. Run one scheduler only when a
selected module needs scheduled recovery. Multiple scheduler replicas require selected module needs scheduled recovery. Multiple scheduler replicas require
leader election or an external lock, which is not established by this report. leader election or an external lock, which is not established by this report.
This topology is a recommendation from the [Ops scalability profiles](https://git.add-ideas.de/add-ideas/govoplan-ops/src/branch/main/docs/SCALABILITY_PROFILES.md), This topology is a recommendation from the [Ops scalability profiles](https://git.add-ideas.de/GovOPlaN/govoplan-ops/src/branch/main/docs/SCALABILITY_PROFILES.md),
not a currently shipped production Compose/Kubernetes/systemd package. not a currently shipped production Compose/Kubernetes/systemd package.
## Functional capability matrix ## Functional capability matrix
@@ -154,7 +154,7 @@ not a currently shipped production Compose/Kubernetes/systemd package.
| Configured-system documentation and Ops status pages | `verified` | Docs/Ops manifests contribute protected routes and WebUI packages; Ops code checks database, Redis, workers, storage and deployment-security settings. | This does not replace external monitoring or a target runbook. | | Configured-system documentation and Ops status pages | `verified` | Docs/Ops manifests contribute protected routes and WebUI packages; Ops code checks database, Redis, workers, storage and deployment-security settings. | This does not replace external monitoring or a target runbook. |
| Calendar/CalDAV integration | `partial` | Calendar `v0.1.8` supplies the catalogued storage/sync foundation. The durable external-write outbox, worker recovery, reconciliation, and retention work is committed, tested, and pushed on Calendar `main` after that tag. | The post-tag outbox work is remote-integrated source, not local-only WIP, but it is not in the signed stable package baseline and has not passed a target CalDAV drill. Bulk synchronized-calendar migration semantics remain separate work. | | Calendar/CalDAV integration | `partial` | Calendar `v0.1.8` supplies the catalogued storage/sync foundation. The durable external-write outbox, worker recovery, reconciliation, and retention work is committed, tested, and pushed on Calendar `main` after that tag. | The post-tag outbox work is remote-integrated source, not local-only WIP, but it is not in the signed stable package baseline and has not passed a target CalDAV drill. Bulk synchronized-calendar migration semantics remain separate work. |
| External LDAP/AD, OIDC/SAML or SCIM identity integration | `scaffold` | IDM owns normalized assignment APIs and documents connector boundaries. | Provider connectors, login callback flow and target directory reconciliation are not an implemented end-to-end capability. Use local accounts for this pilot. | | External LDAP/AD, OIDC/SAML or SCIM identity integration | `scaffold` | IDM owns normalized assignment APIs and documents connector boundaries. | Provider connectors, login callback flow and target directory reconciliation are not an implemented end-to-end capability. Use local accounts for this pilot. |
| Export-control/embargo-list screening | `planned` | Product-level [GovOPlaN #12](https://git.add-ideas.de/add-ideas/govoplan/issues/12) defines the consumer-independent user story. | No screening provider, list provenance, matching policy, review flow or legal evidence exists in this composition. | | Export-control/embargo-list screening | `planned` | Product-level [GovOPlaN #12](https://git.add-ideas.de/GovOPlaN/govoplan/issues/12) defines the consumer-independent user story. | No screening provider, list provenance, matching policy, review flow or legal evidence exists in this composition. |
| Workflow-driven journeys and views | `planned` | Workflow contracts/concepts exist outside this assessment. | Explicitly postponed. Do not include Workflow in pilot or production claims from this report. | | Workflow-driven journeys and views | `planned` | Workflow contracts/concepts exist outside this assessment. | Explicitly postponed. Do not include Workflow in pilot or production claims from this report. |
## Infrastructure matrix ## Infrastructure matrix
@@ -177,13 +177,13 @@ not a currently shipped production Compose/Kubernetes/systemd package.
| Health/readiness | `verified` | `/health`, protected `/health/details`, and Ops checks for DB, Redis, workers, storage, maintenance and cookie/CORS posture. | Add external probes and distinguish liveness from dependency readiness for the chosen orchestrator. | | Health/readiness | `verified` | `/health`, protected `/health/details`, and Ops checks for DB, Redis, workers, storage, maintenance and cookie/CORS posture. | Add external probes and distinguish liveness from dependency readiness for the chosen orchestrator. |
| Metrics, logs and alerting | `partial` | Correlation IDs, slow-request/query metrics in logs, worker inspection and operator status are implemented. | No bundled metrics exporter, log collector, dashboards, queue-depth alerts, pager route or SLO is verified. | | Metrics, logs and alerting | `partial` | Correlation IDs, slow-request/query metrics in logs, worker inspection and operator status are implemented. | No bundled metrics exporter, log collector, dashboards, queue-depth alerts, pager route or SLO is verified. |
| Audit | `partial` | Local audit tables and retry outbox are verified. | Retention, tamper-evident export, privileged access review and SIEM integration are unproved. | | Audit | `partial` | Local audit tables and retry outbox are verified. | Retention, tamper-evident export, privileged access review and SIEM integration are unproved. |
| Backup and restore | `partial` | Operator guide and installer hooks describe `pg_dump`/`pg_restore`; SQLite and simulated installer rollback drills exist. | [Core #29](https://git.add-ideas.de/add-ideas/govoplan-core/issues/29) remains open. No target PostgreSQL + files + secrets restore drill or measured RTO/RPO exists. | | Backup and restore | `partial` | Operator guide and installer hooks describe `pg_dump`/`pg_restore`; SQLite and simulated installer rollback drills exist. | [Core #29](https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/29) remains open. No target PostgreSQL + files + secrets restore drill or measured RTO/RPO exists. |
| Disaster recovery | `not_assessed` | The Ops guide asks for RPO/RTO and restore drills. | No agreed RPO/RTO, off-site copy, failover topology, dependency recovery order, communications plan or exercise evidence was supplied. | | Disaster recovery | `not_assessed` | The Ops guide asks for RPO/RTO and restore drills. | No agreed RPO/RTO, off-site copy, failover topology, dependency recovery order, communications plan or exercise evidence was supplied. |
The scalability and sizing documentation delivered the documentation portions The scalability and sizing documentation delivered the documentation portions
of [Core #217](https://git.add-ideas.de/add-ideas/govoplan-core/issues/217) and of [Core #217](https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/217) and
[Core #219](https://git.add-ideas.de/add-ideas/govoplan-core/issues/219). [Core #219](https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/219).
[Core #28](https://git.add-ideas.de/add-ideas/govoplan-core/issues/28) records the [Core #28](https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/28) records the
operator-documentation slice. These closed tickets are evidence of documented operator-documentation slice. These closed tickets are evidence of documented
models, not evidence that an organization's production environment has passed models, not evidence that an organization's production environment has passed
them. them.
@@ -584,13 +584,13 @@ source tests or signed release metadata.
- [Production-like profile](../dev/production-like/README.md) and - [Production-like profile](../dev/production-like/README.md) and
[Compose dependencies](../dev/production-like/docker-compose.yml) [Compose dependencies](../dev/production-like/docker-compose.yml)
- [Module contracts and install boundaries](MODULE_CONTRACTS_AND_INSTALLS.md) - [Module contracts and install boundaries](MODULE_CONTRACTS_AND_INSTALLS.md)
- [Core deployment operator guide](https://git.add-ideas.de/add-ideas/govoplan-core/src/branch/main/docs/DEPLOYMENT_OPERATOR_GUIDE.md) - [Core deployment operator guide](https://git.add-ideas.de/GovOPlaN/govoplan-core/src/branch/main/docs/DEPLOYMENT_OPERATOR_GUIDE.md)
- [Ops scalability profiles](https://git.add-ideas.de/add-ideas/govoplan-ops/src/branch/main/docs/SCALABILITY_PROFILES.md) - [Ops scalability profiles](https://git.add-ideas.de/GovOPlaN/govoplan-ops/src/branch/main/docs/SCALABILITY_PROFILES.md)
- Actual module manifests in the pinned repositories and the static contract - Actual module manifests in the pinned repositories and the static contract
checker in this meta repository checker in this meta repository
- Core module-system/API smoke/auth/install-config tests, plus focused Campaign, - Core module-system/API smoke/auth/install-config tests, plus focused Campaign,
Files, Mail, Audit and Addresses tests Files, Mail, Audit and Addresses tests
- [Campaign delivery runbook](https://git.add-ideas.de/add-ideas/govoplan-campaign/src/branch/main/docs/CAMPAIGN_DELIVERY_RUNBOOK.md) - [Campaign delivery runbook](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/src/branch/main/docs/CAMPAIGN_DELIVERY_RUNBOOK.md)
- [Live signed stable catalog](https://govoplan.add-ideas.de/catalogs/v1/channels/stable.json) - [Live signed stable catalog](https://govoplan.add-ideas.de/catalogs/v1/channels/stable.json)
and [published keyring](https://govoplan.add-ideas.de/catalogs/v1/keyring.json), and [published keyring](https://govoplan.add-ideas.de/catalogs/v1/keyring.json),
verified against a separately provisioned local trust keyring verified against a separately provisioned local trust keyring

View File

@@ -10,7 +10,7 @@ baseline to that destination.
It is a durable direction, not a release promise or a substitute for issue It is a durable direction, not a release promise or a substitute for issue
tracking. Live work state belongs in Gitea issues. The tracking. Live work state belongs in Gitea issues. The
[Core master roadmap](https://git.add-ideas.de/add-ideas/govoplan-core/src/branch/main/docs/GOVOPLAN_MASTER_ROADMAP.md) [Core master roadmap](https://git.add-ideas.de/GovOPlaN/govoplan-core/src/branch/main/docs/GOVOPLAN_MASTER_ROADMAP.md)
remains the technical module and wave sequence; this document supplies the remains the technical module and wave sequence; this document supplies the
cross-product vision that sequence serves. cross-product vision that sequence serves.
@@ -448,7 +448,7 @@ without creating disconnected ticket systems.
- As a manager, I can distinguish demand, backlog, SLA risk, recurring cause, - As a manager, I can distinguish demand, backlog, SLA risk, recurring cause,
and verified resolution. and verified resolution.
**Composition.** Issue reporting, helpdesk, cases, tasks, facilities, assets, **Composition.** Tickets, helpdesk profiles, cases, tasks, facilities, assets,
inspections, booking, resources, calendar, files, notifications, connectors, inspections, booking, resources, calendar, files, notifications, connectors,
reporting, policy, and audit. reporting, policy, and audit.
@@ -530,7 +530,7 @@ access, no hidden cross-module coupling, correction rather than fictional undo,
and a durable action/effect trail. and a durable action/effect trail.
**Roadmap role.** The canonical product story is **Roadmap role.** The canonical product story is
[meta issue #12](https://git.add-ideas.de/add-ideas/govoplan/issues/12). It is a [meta issue #12](https://git.add-ideas.de/GovOPlaN/govoplan/issues/12). It is a
future cross-product configuration and contract program, not Campaign work and future cross-product configuration and contract program, not Campaign work and
not a claim that screening is implemented today. not a claim that screening is implemented today.
@@ -1279,8 +1279,9 @@ them; they do not block the product vision today.
- Which exact HIS/CampusOnline interfaces, student-statistics fields and - Which exact HIS/CampusOnline interfaces, student-statistics fields and
official keys, accepted calculation, freeze/correction policy, privacy official keys, accepted calculation, freeze/correction policy, privacy
profile, and drill-down level should define the first analytical data product? profile, and drill-down level should define the first analytical data product?
- After the first source-to-report proof, do repeated source/dataflow contracts - Which database, REST, directory, and managed-file providers should follow the
justify separate `govoplan-datasources` and `govoplan-dataflow` modules? implemented separation of `govoplan-connectors`, `govoplan-datasources`, and
`govoplan-dataflow`, and which quality/promotion policy should prove it first?
- Which collaborative editor should be the first target, and should its first - Which collaborative editor should be the first target, and should its first
accepted experience emphasize concurrent editing, controlled check-out, or accepted experience emphasize concurrent editing, controlled check-out, or
both? both?
@@ -1381,7 +1382,7 @@ integration. Conversely, 30 repositories had no open issue; for many
later-wave modules this meant no implementation program had been opened, not later-wave modules this meant no implementation program had been opened, not
that the capability was complete. that the capability was complete.
[Poll #2](https://git.add-ideas.de/add-ideas/govoplan-poll/issues/2) was a clear [Poll #2](https://git.add-ideas.de/GovOPlaN/govoplan-poll/issues/2) was a clear
tracker-drift example: its configurable transition engine, agreed transition tracker-drift example: its configurable transition engine, agreed transition
matrix/history, idempotent keyed retries, re-decision audit, archive/unarchive, matrix/history, idempotent keyed retries, re-decision audit, archive/unarchive,
and preservation behavior were implemented and pushed while the issue still and preservation behavior were implemented and pushed while the issue still
@@ -1389,27 +1390,27 @@ reported `needs-info`.
Issue anchors that informed the bridge from the baseline into this roadmap: Issue anchors that informed the bridge from the baseline into this roadmap:
- [Meta #10](https://git.add-ideas.de/add-ideas/govoplan/issues/10) for the - [Meta #10](https://git.add-ideas.de/GovOPlaN/govoplan/issues/10) for the
capability/infrastructure assessment and its target proof; capability/infrastructure assessment and its target proof;
- [Meta #11](https://git.add-ideas.de/add-ideas/govoplan/issues/11) for the - [Meta #11](https://git.add-ideas.de/GovOPlaN/govoplan/issues/11) for the
universal interface and focused-view direction; universal interface and focused-view direction;
- [Core #225](https://git.add-ideas.de/add-ideas/govoplan-core/issues/225) for - [Core #225](https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/225) for
guided, safe configuration; guided, safe configuration;
- [Core #29](https://git.add-ideas.de/add-ideas/govoplan-core/issues/29) for the - [Core #29](https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/29) for the
backup/restore production gate; backup/restore production gate;
- [Core #263](https://git.add-ideas.de/add-ideas/govoplan-core/issues/263) and - [Core #263](https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/263) and
[Campaign #63](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/63), [Campaign #63](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/63),
[#62](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/62), [#62](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/62),
[#65](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/65), and [#65](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/65), and
[#69](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/69) for the [#69](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/69) for the
reference interface/delivery vocabulary and behavior; reference interface/delivery vocabulary and behavior;
- [Poll #1](https://git.add-ideas.de/add-ideas/govoplan-poll/issues/1) for the - [Poll #1](https://git.add-ideas.de/GovOPlaN/govoplan-poll/issues/1) for the
database-enforced respondent invariant exposed by Scheduling; database-enforced respondent invariant exposed by Scheduling;
- [Connectors #6](https://git.add-ideas.de/add-ideas/govoplan-connectors/issues/6) - [Connectors #6](https://git.add-ideas.de/GovOPlaN/govoplan-connectors/issues/6)
for the governed connector configuration/simulation foundation; for the governed connector configuration/simulation foundation;
- [Meta #9](https://git.add-ideas.de/add-ideas/govoplan/issues/9) for the first - [Meta #9](https://git.add-ideas.de/GovOPlaN/govoplan/issues/9) for the first
permit-to-payment reference process; and permit-to-payment reference process; and
- [Meta #12](https://git.add-ideas.de/add-ideas/govoplan/issues/12) for the - [Meta #12](https://git.add-ideas.de/GovOPlaN/govoplan/issues/12) for the
deliberately deferred, consumer-independent export-control story. deliberately deferred, consumer-independent export-control story.
Live Gitea issue state remains canonical. These dated facts explain the roadmap Live Gitea issue state remains canonical. These dated facts explain the roadmap

View File

@@ -0,0 +1,82 @@
# Datasource And Definition Graph Architecture
## Two-Layer Data Boundary
GovOPlaN separates governed data identity from external acquisition:
| Layer | Owner | Responsibilities |
| --- | --- | --- |
| Datasource layer | `govoplan-datasources` | Catalogue, tenant visibility, live/cached/static mode, staging, immutable materializations, frozen states, schema, fingerprints, provenance, and bounded reads |
| Connector layer | `govoplan-connectors` and protocol/provider modules | External protocols, endpoints, connection profiles, credentials, discovery, provider health, source-side filtering, and query pushdown |
Connectors publish versioned datasource origins. Datasources registers those
origins and presents one stable capability to Dataflow, Workflow, Reporting,
Risk Compliance, and other consumers. Consumers must not import connector
implementations or retain credentials.
The initial provider path is:
1. Connectors imports a bounded JSON/CSV snapshot and exposes it as an origin.
2. Datasources registers it as live or cached, or accepts a direct static upload
through staging.
3. Cached refreshes and static promotions append immutable materializations.
4. Any datasource may expose a frozen state for reproducible execution evidence.
5. Dataflow stores an opaque datasource reference, state policy, and expected
fingerprint.
6. A pinned Dataflow run may publish a complete bounded result as a new
immutable materialization through an idempotent Datasources capability.
Database, REST/HTTP, LDAP/directory, managed file, watched-directory, feed, and
stream providers fit behind the same origin contract. Provider-specific
configuration remains in Connectors.
## Shared Definition Graph
Core owns domain-neutral graph primitives:
- nodes, typed ports, edges, and configuration field descriptors;
- node libraries and category labels;
- graph size, connectivity, cycle, and node-count constraints;
- shared backend validation and frontend connection checks.
Domain modules own their semantics:
- Dataflow provides load, combine, filter, transform, and output nodes. Its
graph is acyclic and has one output.
- Workflow provides trigger, activity, review, decision, wait, module-action,
Dataflow, and outcome nodes. It permits governed loops and has exactly one
trigger plus one or more outcomes.
This division permits a shared editor shell without making Workflow a special
kind of Dataflow or leaking either module into Core.
## Current Implementation
- Core graph and datasource contracts are versioned at `0.1.0`.
- Workflow exposes a reusable graph editor, node-library discovery, validation,
tenant-isolated definitions, immutable revisions, and activation pinning.
- Datasources exposes catalogue, origins, staging, promotion, preview,
materialization history, refresh, freeze, retirement, and producer
publication APIs.
- Datasources WebUI exposes all current lifecycle views.
- Connectors adapts existing tabular snapshots to datasource origins.
- Dataflow consumes only Datasources catalogue/lifecycle capabilities and can
request current, live, or latest-frozen state.
- Dataflow exposes a pinned run-lifecycle capability and a Run/Publish surface.
Its first synchronous runner records lineage and terminal state, publishes
only complete bounded results, and retains output datasource/materialization
references.
- The focused composition check proves Connector origin -> Datasource ->
pinned Dataflow run -> frozen published materialization, including replay.
## Next Slices
1. Add persisted Workflow instances, resumable transitions, human activities,
retry policy, and event subscriptions against pinned definition revisions.
2. Add SQL database and governed REST origin providers with credential-envelope
references and bounded pushdown.
3. Add managed-file and directory origins.
4. Add datasource quality rules, schema compatibility policy, retention, and
promotion approvals.
5. Add asynchronous Dataflow workers and durable artifact-backed outputs for
runs that exceed the synchronous row/time/byte limits.

View File

@@ -8,11 +8,11 @@ The same pattern is reusable outside GovOPlaN for any project where Codex works
The repository contains Gitea issue templates in `.gitea/ISSUE_TEMPLATE`, a pull request template in `.gitea/PULL_REQUEST_TEMPLATE.md`, and the label taxonomy in `docs/gitea-labels.json`. The repository contains Gitea issue templates in `.gitea/ISSUE_TEMPLATE`, a pull request template in `.gitea/PULL_REQUEST_TEMPLATE.md`, and the label taxonomy in `docs/gitea-labels.json`.
The scripts infer this repository from `origin` (`git@git.add-ideas.de:add-ideas/govoplan.git`). Override inference when needed: The scripts infer this repository from `origin` (`git@git.add-ideas.de:GovOPlaN/govoplan.git`). Override inference when needed:
```bash ```bash
export GITEA_URL=https://git.add-ideas.de export GITEA_URL=https://git.add-ideas.de
export GITEA_OWNER=add-ideas export GITEA_OWNER=GovOPlaN
export GITEA_REPO=govoplan export GITEA_REPO=govoplan
export GITEA_TOKEN=... export GITEA_TOKEN=...
``` ```
@@ -23,7 +23,7 @@ The API scripts also read `GITEA_*` values from the target repository's `.env` f
GITEA_TOKEN=... GITEA_TOKEN=...
# Optional if origin inference is not enough: # Optional if origin inference is not enough:
GITEA_URL=https://git.add-ideas.de GITEA_URL=https://git.add-ideas.de
GITEA_OWNER=add-ideas GITEA_OWNER=GovOPlaN
GITEA_REPO=govoplan GITEA_REPO=govoplan
``` ```

View File

@@ -444,25 +444,25 @@ reason to infer that a pattern is satisfied.
and does not duplicate a central component. and does not duplicate a central component.
- Behavioral/accessibility evidence is linked from the rollout matrix and issue. - Behavioral/accessibility evidence is linked from the rollout matrix and issue.
- Configured-system help can reach the applicable pattern or reference topic - Configured-system help can reach the applicable pattern or reference topic
when [Docs #15](https://git.add-ideas.de/add-ideas/govoplan-docs/issues/15) when [Docs #15](https://git.add-ideas.de/GovOPlaN/govoplan-docs/issues/15)
supplies that experience. supplies that experience.
## First Pilot: Campaign ## First Pilot: Campaign
[Campaign #74](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/74) [Campaign #74](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/74)
is the first full-domain audit and migration. It should prove patterns before is the first full-domain audit and migration. It should prove patterns before
generic extraction: generic extraction:
- [#59](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/59) and - [#59](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/59) and
[#73](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/73): stable, [#73](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/73): stable,
accessible preview and attachment-detail overlays accessible preview and attachment-detail overlays
- [#63](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/63): review - [#63](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/63): review
stages, outcomes, blockers, and intervention vocabulary stages, outcomes, blockers, and intervention vocabulary
- [#62](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/62): explicit - [#62](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/62): explicit
synchronous/asynchronous send mode and durable delivery progress synchronous/asynchronous send mode and durable delivery progress
- [#65](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/65): one - [#65](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/65): one
coherent report filtering and count-affordance model coherent report filtering and count-affordance model
- [#35](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/35): guided - [#35](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/35): guided
first-campaign entry first-campaign entry
These slices do not depend on the Workflow runtime. Campaign's current These slices do not depend on the Workflow runtime. Campaign's current

View File

@@ -59,16 +59,16 @@ The access guard column reports only the route-level declaration in
| Route | Owner / render evidence | Route-level access evidence | Primary task | Target archetype | Status / priority | | Route | Owner / render evidence | Route-level access evidence | Primary task | Target archetype | Status / priority |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| `/admin` | `govoplan-access` `AdminPage` | Any core `adminReadScopes` | Administer system and tenant concerns assembled from module sections | Administration/configuration | Contributed; unreviewed; P1 under [Core #225](https://git.add-ideas.de/add-ideas/govoplan-core/issues/225) | | `/admin` | `govoplan-access` `AdminPage` | Any core `adminReadScopes` | Administer system and tenant concerns assembled from module sections | Administration/configuration | Contributed; unreviewed; P1 under [Core #225](https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/225) |
| `/address-book` | `govoplan-addresses` `AddressBookPage` | `addresses:contact:read` | Browse and manage contacts, address books, and lists | Directory/list-detail | Contributed; unreviewed; P2 after Campaign | | `/address-book` | `govoplan-addresses` `AddressBookPage` | `addresses:contact:read` | Browse and manage contacts, address books, and lists | Directory/list-detail | Contributed; unreviewed; P2 after Campaign |
| `/calendar` | `govoplan-calendar` `CalendarPage` | `calendar:event:read` | Browse calendars/events and act on calendar data | Directory/list-detail | Contributed; metadata gap; unreviewed; P2 after Campaign | | `/calendar` | `govoplan-calendar` `CalendarPage` | `calendar:event:read` | Browse calendars/events and act on calendar data | Directory/list-detail | Contributed; metadata gap; unreviewed; P2 after Campaign |
| `/campaigns` | `govoplan-campaign` `CampaignListPage` | `campaigns:campaign:read` | Find, compare, create, and open campaigns | List-detail entry | Pilot; P1 [Campaign #74](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/74) | | `/campaigns` | `govoplan-campaign` `CampaignListPage` | `campaigns:campaign:read` | Find, compare, create, and open campaigns | List-detail entry | Pilot; P1 [Campaign #74](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/74) |
| `/campaigns/:campaignId/*` | `govoplan-campaign` `CampaignResourceRoute` and `CampaignWorkspace` | `campaigns:campaign:read`, plus resource probe | Configure, review, send, and inspect one campaign/version | List-detail workspace containing edit, review, monitoring, and evidence surfaces | Pilot; P1 Campaign #74 | | `/campaigns/:campaignId/*` | `govoplan-campaign` `CampaignResourceRoute` and `CampaignWorkspace` | `campaigns:campaign:read`, plus resource probe | Configure, review, send, and inspect one campaign/version | List-detail workspace containing edit, review, monitoring, and evidence surfaces | Pilot; P1 Campaign #74 |
| `/operator` | `govoplan-campaign` `OperatorQueuePage` | `campaigns:campaign:read` and any of queue, control, retry, or reconcile | Monitor and intervene in campaign jobs through authority-specific controls | Monitoring/work queue | Pilot; durable queue controls delivered in [Campaign #78](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/78); #74 audit remains | | `/operator` | `govoplan-campaign` `OperatorQueuePage` | `campaigns:campaign:read` and any of queue, control, retry, or reconcile | Monitor and intervene in campaign jobs through authority-specific controls | Monitoring/work queue | Pilot; durable queue controls delivered in [Campaign #78](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/78); #74 audit remains |
| `/reports` | `govoplan-campaign` `AggregateReportsPage` | `campaigns:report:read` | Compare privacy-protected cross-campaign outcome totals without recipient detail, diagnostics, export, or drill-down | Aggregate reporting | Pilot; aggregate-reader surface delivered in [Campaign #80](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/80); #74 audit remains | | `/reports` | `govoplan-campaign` `AggregateReportsPage` | `campaigns:report:read` | Compare privacy-protected cross-campaign outcome totals without recipient detail, diagnostics, export, or drill-down | Aggregate reporting | Pilot; aggregate-reader surface delivered in [Campaign #80](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/80); #74 audit remains |
| `/templates` | `govoplan-campaign` `TemplatesPage` | No route guard declared in `module.ts` | Browse/manage campaign templates | Directory/list-detail | Pilot audit; permission intent must be verified; P2 | | `/templates` | `govoplan-campaign` `TemplatesPage` | No route guard declared in `module.ts` | Browse/manage campaign templates | Directory/list-detail | Pilot audit; permission intent must be verified; P2 |
| `/dashboard` | `govoplan-dashboard` `DashboardPage` | No route-specific scope | Assemble module-provided actionable widgets | Dashboard | Contributed; unreviewed; P2 | | `/dashboard` | `govoplan-dashboard` `DashboardPage` | No route-specific scope | Assemble module-provided actionable widgets | Dashboard | Contributed; unreviewed; P2 |
| `/docs` | `govoplan-docs` `DocsPage` | Docs read or system/tenant settings read scopes | Read configured, available, and evidence-aware documentation | Documentation directory/reference | Contributed; unreviewed; P1 [Docs #15](https://git.add-ideas.de/add-ideas/govoplan-docs/issues/15) after initial pattern content | | `/docs` | `govoplan-docs` `DocsPage` | Docs read or system/tenant settings read scopes | Read configured, available, and evidence-aware documentation | Documentation directory/reference | Contributed; unreviewed; P1 [Docs #15](https://git.add-ideas.de/GovOPlaN/govoplan-docs/issues/15) after initial pattern content |
| `/files` | `govoplan-files` `FilesPage` | `files:file:read` | Browse folders/files and perform managed-file work | Directory/explorer | Contributed; metadata gap; unreviewed; P2 after Campaign | | `/files` | `govoplan-files` `FilesPage` | `files:file:read` | Browse folders/files and perform managed-file work | Directory/explorer | Contributed; metadata gap; unreviewed; P2 after Campaign |
| `/idm` | `govoplan-idm` `IdmPage` | Any IDM assignment/write or organization function-assign scope | Inspect and govern identity/function assignments | List-detail/configuration | Contributed; unreviewed; P2 | | `/idm` | `govoplan-idm` `IdmPage` | Any IDM assignment/write or organization function-assign scope | Inspect and govern identity/function assignments | List-detail/configuration | Contributed; unreviewed; P2 |
| `/mail` | `govoplan-mail` `MailboxPage` | `mail:mailbox:read` | Browse mailboxes and messages | Directory/list-detail | Contributed; metadata gap; unreviewed; P2 after Campaign | | `/mail` | `govoplan-mail` `MailboxPage` | `mail:mailbox:read` | Browse mailboxes and messages | Directory/list-detail | Contributed; metadata gap; unreviewed; P2 after Campaign |
@@ -138,7 +138,7 @@ above; they are not independent routes.
Campaign is detailed first because it exercises almost every archetype. The Campaign is detailed first because it exercises almost every archetype. The
recipient-data editor is now consolidated into the `recipients` section on recipient-data editor is now consolidated into the `recipients` section on
remote `main`; [Campaign #67](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/67) remote `main`; [Campaign #67](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/67)
records the accepted and verified integration boundary. records the accepted and verified integration boundary.
Campaign already consumes core primitives including `ModuleSubnav`, `Card`, Campaign already consumes core primitives including `ModuleSubnav`, `Card`,
@@ -150,25 +150,25 @@ prove that the composition or states satisfy the pattern.
| Surface / code evidence | Primary task | Target pattern | Material consequence/state | Known issue / rollout | | Surface / code evidence | Primary task | Target pattern | Material consequence/state | Known issue / rollout |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| Campaign list (`CampaignListPage`) | Find, compare, create, open | List-detail entry | Campaign lifecycle/status and creation | Audit in [#74](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/74); guided entry [#35](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/35) | | Campaign list (`CampaignListPage`) | Find, compare, create, open | List-detail entry | Campaign lifecycle/status and creation | Audit in [#74](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/74); guided entry [#35](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/35) |
| Overview (`CampaignOverviewPage`) | Understand/edit campaign identity, version, access, lifecycle | Object overview plus adaptive edit | Lock/archive/delete/access changes need real consequence and reversibility wording | #74 remaining audit | | Overview (`CampaignOverviewPage`) | Understand/edit campaign identity, version, access, lifecycle | Object overview plus adaptive edit | Lock/archive/delete/access changes need real consequence and reversibility wording | #74 remaining audit |
| Fields (`CampaignFieldsPage`) | Define recipient/template field schema | Structured editor | Schema changes can invalidate recipient/template data | #74 audit | | Fields (`CampaignFieldsPage`) | Define recipient/template field schema | Structured editor | Schema changes can invalidate recipient/template data | #74 audit |
| Attachments/files (`AttachmentsDataPage`, `AttachmentRulesOverlay`) | Select sources and attachment/ZIP rules | Directory chooser plus adaptive rule editor | Missing or mismatched files affect built messages | #74; attachment-detail [#59](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/59) | | Attachments/files (`AttachmentsDataPage`, `AttachmentRulesOverlay`) | Select sources and attachment/ZIP rules | Directory chooser plus adaptive rule editor | Missing or mismatched files affect built messages | #74; attachment-detail [#59](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/59) |
| Recipients (`RecipientDataPage`) | Select/import/map/edit recipients, address fields and per-recipient values/files | Import/mapping plus list-detail editor | Personal data, validation, bulk activation, file links | Consolidated editor delivered in #67; #74 remaining audit and guided entry #35 | | Recipients (`RecipientDataPage`) | Select/import/map/edit recipients, address fields and per-recipient values/files | Import/mapping plus list-detail editor | Personal data, validation, bulk activation, file links | Consolidated editor delivered in #67; #74 remaining audit and guided entry #35 |
| Template (`TemplateDataPage`, placeholder/expression dialogs) | Author subject/body and preview substitutions | Adaptive editor plus stable preview | Generated communication content and unresolved expressions | #74; stable overlay [#73](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/73) | | Template (`TemplateDataPage`, placeholder/expression dialogs) | Author subject/body and preview substitutions | Adaptive editor plus stable preview | Generated communication content and unresolved expressions | #74; stable overlay [#73](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/73) |
| Mail settings (`MailSettingsPage` settings view) | Select/configure campaign mail transport | Adaptive configuration | Credentials, SMTP/IMAP destinations, test outcomes | #74; align with Core #225 mail pattern | | Mail settings (`MailSettingsPage` settings view) | Select/configure campaign mail transport | Adaptive configuration | Credentials, SMTP/IMAP destinations, test outcomes | #74; align with Core #225 mail pattern |
| Campaign settings (`GlobalSettingsPage` settings view) | Configure campaign behavior | Adaptive configuration | Can alter validation/build/send behavior | #74 audit | | Campaign settings (`GlobalSettingsPage` settings view) | Configure campaign behavior | Adaptive configuration | Can alter validation/build/send behavior | #74 audit |
| Mail policy (`MailSettingsPage` policy view) | Inspect/override effective mail policy | Effective policy/provenance editor | Inheritance and locks affect allowed delivery | #74; Core #225 policy pattern | | Mail policy (`MailSettingsPage` policy view) | Inspect/override effective mail policy | Effective policy/provenance editor | Inheritance and locks affect allowed delivery | #74; Core #225 policy pattern |
| Campaign policy (`GlobalSettingsPage` policy view) | Inspect/override campaign policy | Effective policy/provenance editor | Inheritance, actor authority, and blocked edits | #74; Core #225 policy pattern | | Campaign policy (`GlobalSettingsPage` policy view) | Inspect/override campaign policy | Effective policy/provenance editor | Inheritance, actor authority, and blocked edits | #74; Core #225 policy pattern |
| Review/send (`ReviewSendPage`) | Validate, build, mock-test, confirm/send, inspect results | Guided review/decision plus durable progress | External communication, bounded synchronous execution, persisted queue mode, partial effects, retries, evidence | Bounded synchronous and explicit/persisted queued modes delivered in [#62](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/62) and [#79](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/79); [#63](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/63) wording and #74 audit remain | | Review/send (`ReviewSendPage`) | Validate, build, mock-test, confirm/send, inspect results | Guided review/decision plus durable progress | External communication, bounded synchronous execution, persisted queue mode, partial effects, retries, evidence | Bounded synchronous and explicit/persisted queued modes delivered in [#62](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/62) and [#79](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/79); [#63](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/63) wording and #74 audit remain |
| Message and attachment detail overlays | Inspect one built/mock message and its attachment links | Stable detail/review dialog | Personal data, exact outbound content, reviewed state | Delivered and verified in [#59](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/59) and [#73](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/73) | | Message and attachment detail overlays | Inspect one built/mock message and its attachment links | Stable detail/review dialog | Personal data, exact outbound content, reviewed state | Delivered and verified in [#59](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/59) and [#73](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/73) |
| Campaign report (`CampaignReportPage`) | Filter and inspect delivery outcomes | Reporting/list-detail | Partial, failed, explicitly excluded/skipped, SMTP/IMAP outcomes and retries | Server-owned filtering and counts delivered in [#65](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/65) with the full-result DataGrid contract from [Core #263](https://git.add-ideas.de/add-ideas/govoplan-core/issues/263); excluded semantics in [#66](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/66) | | Campaign report (`CampaignReportPage`) | Filter and inspect delivery outcomes | Reporting/list-detail | Partial, failed, explicitly excluded/skipped, SMTP/IMAP outcomes and retries | Server-owned filtering and counts delivered in [#65](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/65) with the full-result DataGrid contract from [Core #263](https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/263); excluded semantics in [#66](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/66) |
| Audit (`CampaignAuditPage`) | Inspect campaign evidence/history | Provenance timeline/report | Actor/action/effect trace | #74 audit | | Audit (`CampaignAuditPage`) | Inspect campaign evidence/history | Provenance timeline/report | Actor/action/effect trace | #74 audit |
| JSON (`CampaignJsonView`) | Inspect expert representation | Advanced diagnostics/reference | Raw data may contain personal/configuration values; not a primary editor | #74 privacy/redaction audit | | JSON (`CampaignJsonView`) | Inspect expert representation | Advanced diagnostics/reference | Raw data may contain personal/configuration values; not a primary editor | #74 privacy/redaction audit |
| Create wizard (`CreateWizard`) | Seed a campaign through basics, sender, fields, recipients, template, attachments, review, send | Guided setup | Current steps mix creation and later consequential delivery; completion semantics need audit | Guided first campaign [#35](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/35) | | Create wizard (`CreateWizard`) | Seed a campaign through basics, sender, fields, recipients, template, attachments, review, send | Guided setup | Current steps mix creation and later consequential delivery; completion semantics need audit | Guided first campaign [#35](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/35) |
| Review/send wizard routes | Alternate guided review/send shells | Guided review | Tracked routes exist; implementation relationship to `ReviewSendPage` must be established, not guessed | #74 inventory decision | | Review/send wizard routes | Alternate guided review/send shells | Guided review | Tracked routes exist; implementation relationship to `ReviewSendPage` must be established, not guessed | #74 inventory decision |
| Operator queue (`OperatorQueuePage`) | Monitor jobs and intervene | Monitoring/work queue | Campaign/version/job identity, historical active-version discovery, fixed action positions, authority-aware disabled states, exact non-overlapping queue counts, server-paged jobs, bounded refresh, retry/queue/reconcile per version, campaign-wide pause/resume/cancel, and leave/return progress | Durable operator controls delivered in [#78](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/78); #74 wording/accessibility audit remains | | Operator queue (`OperatorQueuePage`) | Monitor jobs and intervene | Monitoring/work queue | Campaign/version/job identity, historical active-version discovery, fixed action positions, authority-aware disabled states, exact non-overlapping queue counts, server-paged jobs, bounded refresh, retry/queue/reconcile per version, campaign-wide pause/resume/cancel, and leave/return progress | Durable operator controls delivered in [#78](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/78); #74 wording/accessibility audit remains |
| Aggregate reports (`AggregateReportsPage`) | Compare cross-campaign delivery outcomes | Privacy-preserving aggregate reporting | Tenant/campaign ACL, deployment/tenant small-cell policy, complementary and overlapping-cell suppression, explicit denominator, and no recipient detail/diagnostics/export/drill-down | Separate aggregate-reader surface delivered in [#80](https://git.add-ideas.de/add-ideas/govoplan-campaign/issues/80); not parity with the permission-gated per-campaign detail report | | Aggregate reports (`AggregateReportsPage`) | Compare cross-campaign delivery outcomes | Privacy-preserving aggregate reporting | Tenant/campaign ACL, deployment/tenant small-cell policy, complementary and overlapping-cell suppression, explicit denominator, and no recipient detail/diagnostics/export/drill-down | Separate aggregate-reader surface delivered in [#80](https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/80); not parity with the permission-gated per-campaign detail report |
| Templates route (`TemplatesPage`) | Browse template records | Directory/list-detail | Template availability and later generated outputs | #74 audit; verify missing route guard intent | | Templates route (`TemplatesPage`) | Browse template records | Directory/list-detail | Template availability and later generated outputs | #74 audit; verify missing route guard intent |
The five review stages currently named in code are `Validate and inspect`, The five review stages currently named in code are `Validate and inspect`,
@@ -186,7 +186,7 @@ The following local repositories contain a backend manifest but no
`govoplan-contracts`, `govoplan-dist-lists`, `govoplan-evaluation`, `govoplan-contracts`, `govoplan-dist-lists`, `govoplan-evaluation`,
`govoplan-facilities`, `govoplan-forms-runtime`, `govoplan-grants`, `govoplan-facilities`, `govoplan-forms-runtime`, `govoplan-grants`,
`govoplan-helpdesk`, `govoplan-identity`, `govoplan-inspections`, `govoplan-helpdesk`, `govoplan-identity`, `govoplan-inspections`,
`govoplan-issue-reporting`, `govoplan-learning`, `govoplan-permits`, `govoplan-tickets`, `govoplan-learning`, `govoplan-permits`,
`govoplan-poll`, `govoplan-procurement`, `govoplan-records`, `govoplan-poll`, `govoplan-procurement`, `govoplan-records`,
`govoplan-resources`, `govoplan-rest`, `govoplan-risk-compliance`, `govoplan-resources`, `govoplan-rest`, `govoplan-risk-compliance`,
`govoplan-soap`, `govoplan-tenancy`, and `govoplan-transparency`. `govoplan-soap`, `govoplan-tenancy`, and `govoplan-transparency`.
@@ -199,7 +199,7 @@ backend-only modules may remain intentionally headless.
| Order | Scope | Current evidence | Target | Owner / issue | Verification gate | Status | | Order | Scope | Current evidence | Target | Owner / issue | Verification gate | Status |
| --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- |
| 0 | Product grammar and route inventory | Doctrine, ledger, layout rules, module contract, current route sources | One reconciled pattern language and evidence inventory | Meta [#11](https://git.add-ideas.de/add-ideas/govoplan/issues/11) | Docs links/diff checks; issue/wiki sync after integration | Initial slice in this document | | 0 | Product grammar and route inventory | Doctrine, ledger, layout rules, module contract, current route sources | One reconciled pattern language and evidence inventory | Meta [#11](https://git.add-ideas.de/GovOPlaN/govoplan/issues/11) | Docs links/diff checks; issue/wiki sync after integration | Initial slice in this document |
| 1 | Campaign baseline integration | Recipient-editor WIP and tracker state have been reconciled with remote `main` | Integrated, testable baseline before migration claims | Campaign #67 and tracker cleanup | Backend and focused WebUI suites; issue evidence | Complete 2026-07-22 | | 1 | Campaign baseline integration | Recipient-editor WIP and tracker state have been reconciled with remote `main` | Integrated, testable baseline before migration claims | Campaign #67 and tracker cleanup | Backend and focused WebUI suites; issue evidence | Complete 2026-07-22 |
| 2 | Campaign previews/details | Stable shared dialog with bounded scrolling and fixed responsive preview workspace | Stable header/body/footer, accessible long-content detail | Campaign #59 and #73 | Review-preview and overlay structure tests | Complete 2026-07-22 | | 2 | Campaign previews/details | Stable shared dialog with bounded scrolling and fixed responsive preview workspace | Stable header/body/footer, accessible long-content detail | Campaign #59 and #73 | Review-preview and overlay structure tests | Complete 2026-07-22 |
| 3 | Campaign review/interventions | Five domain-owned stages with unresolved intervention language | Clear stages, outcomes, blockers, next actor/action, reviewed evidence | Campaign #63 | State matrix behavior/accessibility tests and agreed vocabulary | P1 needs product wording decision | | 3 | Campaign review/interventions | Five domain-owned stages with unresolved intervention language | Clear stages, outcomes, blockers, next actor/action, reviewed evidence | Campaign #63 | State matrix behavior/accessibility tests and agreed vocabulary | P1 needs product wording decision |

View File

@@ -114,7 +114,10 @@ For release validation:
```sh ```sh
./.venv/bin/python tools/repo/sync-python-environment.py --requirements requirements-release.txt --python ./.venv/bin/python ./.venv/bin/python tools/repo/sync-python-environment.py --requirements requirements-release.txt --python ./.venv/bin/python
./.venv/bin/python -m pip install -r requirements-release-tests.txt
``` ```
That install is necessary because the release environment intentionally resolves That install is necessary because the release environment intentionally resolves
tagged package refs, not local editable source trees. tagged package refs, not local editable source trees. The second requirements
file contains only the harness needed to execute tests from those immutable
source tags; it is not part of the deployable release dependency set.

View File

@@ -445,8 +445,9 @@ that first needs them:
- exact HIS/CampusOnline source endpoints, student-statistics keys and accepted - exact HIS/CampusOnline source endpoints, student-statistics keys and accepted
calculation, freeze/correction policy, privacy profile, and permitted calculation, freeze/correction policy, privacy profile, and permitted
drill-down level; drill-down level;
- whether repeated data-source and dataflow contracts justify separate - datasource provider selection and quality/promotion policy; the architecture
`govoplan-datasources` and `govoplan-dataflow` modules after the Stage 3 proof; now separates `govoplan-datasources` lifecycle from `govoplan-connectors`
acquisition and `govoplan-dataflow` transformation;
- first collaborative editor/provider and whether the first UX is concurrent - first collaborative editor/provider and whether the first UX is concurrent
editing, controlled check-out, or both; and editing, controlled check-out, or both; and
- first Records/archive target and approval/signature assurance level. - first Records/archive target and approval/signature assurance level.

View File

@@ -6,81 +6,86 @@ Generated from `repositories.json`. Use that JSON file as the machine-readable s
| Repository | Subtype | Local path | Gitea | | Repository | Subtype | Local path | Gitea |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `govoplan` | `meta` | `../govoplan` | [govoplan](https://git.add-ideas.de/add-ideas/govoplan) | | `govoplan` | `meta` | `../govoplan` | [govoplan](https://git.add-ideas.de/GovOPlaN/govoplan) |
| `govoplan-core` | `kernel` | `../govoplan-core` | [govoplan-core](https://git.add-ideas.de/add-ideas/govoplan-core) | | `govoplan-core` | `kernel` | `../govoplan-core` | [govoplan-core](https://git.add-ideas.de/GovOPlaN/govoplan-core) |
## Module ## Module
| Repository | Subtype | Local path | Gitea | | Repository | Subtype | Local path | Gitea |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `govoplan-access` | `platform` | `../govoplan-access` | [govoplan-access](https://git.add-ideas.de/add-ideas/govoplan-access) | | `govoplan-access` | `platform` | `../govoplan-access` | [govoplan-access](https://git.add-ideas.de/GovOPlaN/govoplan-access) |
| `govoplan-addresses` | `domain` | `../govoplan-addresses` | [govoplan-addresses](https://git.add-ideas.de/add-ideas/govoplan-addresses) | | `govoplan-addresses` | `domain` | `../govoplan-addresses` | [govoplan-addresses](https://git.add-ideas.de/GovOPlaN/govoplan-addresses) |
| `govoplan-admin` | `platform` | `../govoplan-admin` | [govoplan-admin](https://git.add-ideas.de/add-ideas/govoplan-admin) | | `govoplan-admin` | `platform` | `../govoplan-admin` | [govoplan-admin](https://git.add-ideas.de/GovOPlaN/govoplan-admin) |
| `govoplan-appointments` | `domain` | `../govoplan-appointments` | [govoplan-appointments](https://git.add-ideas.de/add-ideas/govoplan-appointments) | | `govoplan-appointments` | `domain` | `../govoplan-appointments` | [govoplan-appointments](https://git.add-ideas.de/GovOPlaN/govoplan-appointments) |
| `govoplan-approvals` | `domain` | `../govoplan-approvals` | [govoplan-approvals](https://git.add-ideas.de/add-ideas/govoplan-approvals) | | `govoplan-approvals` | `domain` | `../govoplan-approvals` | [govoplan-approvals](https://git.add-ideas.de/GovOPlaN/govoplan-approvals) |
| `govoplan-assets` | `domain` | `../govoplan-assets` | [govoplan-assets](https://git.add-ideas.de/add-ideas/govoplan-assets) | | `govoplan-assets` | `domain` | `../govoplan-assets` | [govoplan-assets](https://git.add-ideas.de/GovOPlaN/govoplan-assets) |
| `govoplan-audit` | `platform` | `../govoplan-audit` | [govoplan-audit](https://git.add-ideas.de/add-ideas/govoplan-audit) | | `govoplan-audit` | `platform` | `../govoplan-audit` | [govoplan-audit](https://git.add-ideas.de/GovOPlaN/govoplan-audit) |
| `govoplan-booking` | `domain` | `../govoplan-booking` | [govoplan-booking](https://git.add-ideas.de/add-ideas/govoplan-booking) | | `govoplan-booking` | `domain` | `../govoplan-booking` | [govoplan-booking](https://git.add-ideas.de/GovOPlaN/govoplan-booking) |
| `govoplan-calendar` | `domain` | `../govoplan-calendar` | [govoplan-calendar](https://git.add-ideas.de/add-ideas/govoplan-calendar) | | `govoplan-calendar` | `domain` | `../govoplan-calendar` | [govoplan-calendar](https://git.add-ideas.de/GovOPlaN/govoplan-calendar) |
| `govoplan-campaign` | `domain` | `../govoplan-campaign` | [govoplan-campaign](https://git.add-ideas.de/add-ideas/govoplan-campaign) | | `govoplan-campaign` | `domain` | `../govoplan-campaign` | [govoplan-campaign](https://git.add-ideas.de/GovOPlaN/govoplan-campaign) |
| `govoplan-cases` | `domain` | `../govoplan-cases` | [govoplan-cases](https://git.add-ideas.de/add-ideas/govoplan-cases) | | `govoplan-cases` | `domain` | `../govoplan-cases` | [govoplan-cases](https://git.add-ideas.de/GovOPlaN/govoplan-cases) |
| `govoplan-certificates` | `domain` | `../govoplan-certificates` | [govoplan-certificates](https://git.add-ideas.de/add-ideas/govoplan-certificates) | | `govoplan-certificates` | `domain` | `../govoplan-certificates` | [govoplan-certificates](https://git.add-ideas.de/GovOPlaN/govoplan-certificates) |
| `govoplan-committee` | `domain` | `../govoplan-committee` | [govoplan-committee](https://git.add-ideas.de/add-ideas/govoplan-committee) | | `govoplan-committee` | `domain` | `../govoplan-committee` | [govoplan-committee](https://git.add-ideas.de/GovOPlaN/govoplan-committee) |
| `govoplan-consultation` | `domain` | `../govoplan-consultation` | [govoplan-consultation](https://git.add-ideas.de/add-ideas/govoplan-consultation) | | `govoplan-consultation` | `domain` | `../govoplan-consultation` | [govoplan-consultation](https://git.add-ideas.de/GovOPlaN/govoplan-consultation) |
| `govoplan-contracts` | `domain` | `../govoplan-contracts` | [govoplan-contracts](https://git.add-ideas.de/add-ideas/govoplan-contracts) | | `govoplan-contracts` | `domain` | `../govoplan-contracts` | [govoplan-contracts](https://git.add-ideas.de/GovOPlaN/govoplan-contracts) |
| `govoplan-dashboard` | `platform` | `../govoplan-dashboard` | [govoplan-dashboard](https://git.add-ideas.de/add-ideas/govoplan-dashboard) | | `govoplan-dashboard` | `platform` | `../govoplan-dashboard` | [govoplan-dashboard](https://git.add-ideas.de/GovOPlaN/govoplan-dashboard) |
| `govoplan-dms` | `domain` | `../govoplan-dms` | [govoplan-dms](https://git.add-ideas.de/add-ideas/govoplan-dms) | | `govoplan-dataflow` | `platform` | `../govoplan-dataflow` | [govoplan-dataflow](https://git.add-ideas.de/GovOPlaN/govoplan-dataflow) |
| `govoplan-dist-lists` | `domain` | `../govoplan-dist-lists` | [govoplan-dist-lists](https://git.add-ideas.de/add-ideas/govoplan-dist-lists) | | `govoplan-datasources` | `platform` | `../govoplan-datasources` | [govoplan-datasources](https://git.add-ideas.de/GovOPlaN/govoplan-datasources) |
| `govoplan-docs` | `platform` | `../govoplan-docs` | [govoplan-docs](https://git.add-ideas.de/add-ideas/govoplan-docs) | | `govoplan-dms` | `domain` | `../govoplan-dms` | [govoplan-dms](https://git.add-ideas.de/GovOPlaN/govoplan-dms) |
| `govoplan-erp` | `domain` | `../govoplan-erp` | [govoplan-erp](https://git.add-ideas.de/add-ideas/govoplan-erp) | | `govoplan-dist-lists` | `domain` | `../govoplan-dist-lists` | [govoplan-dist-lists](https://git.add-ideas.de/GovOPlaN/govoplan-dist-lists) |
| `govoplan-evaluation` | `domain` | `../govoplan-evaluation` | [govoplan-evaluation](https://git.add-ideas.de/add-ideas/govoplan-evaluation) | | `govoplan-docs` | `platform` | `../govoplan-docs` | [govoplan-docs](https://git.add-ideas.de/GovOPlaN/govoplan-docs) |
| `govoplan-facilities` | `domain` | `../govoplan-facilities` | [govoplan-facilities](https://git.add-ideas.de/add-ideas/govoplan-facilities) | | `govoplan-erp` | `domain` | `../govoplan-erp` | [govoplan-erp](https://git.add-ideas.de/GovOPlaN/govoplan-erp) |
| `govoplan-files` | `domain` | `../govoplan-files` | [govoplan-files](https://git.add-ideas.de/add-ideas/govoplan-files) | | `govoplan-evaluation` | `domain` | `../govoplan-evaluation` | [govoplan-evaluation](https://git.add-ideas.de/GovOPlaN/govoplan-evaluation) |
| `govoplan-forms` | `domain` | `../govoplan-forms` | [govoplan-forms](https://git.add-ideas.de/add-ideas/govoplan-forms) | | `govoplan-facilities` | `domain` | `../govoplan-facilities` | [govoplan-facilities](https://git.add-ideas.de/GovOPlaN/govoplan-facilities) |
| `govoplan-forms-runtime` | `platform` | `../govoplan-forms-runtime` | [govoplan-forms-runtime](https://git.add-ideas.de/add-ideas/govoplan-forms-runtime) | | `govoplan-files` | `domain` | `../govoplan-files` | [govoplan-files](https://git.add-ideas.de/GovOPlaN/govoplan-files) |
| `govoplan-grants` | `domain` | `../govoplan-grants` | [govoplan-grants](https://git.add-ideas.de/add-ideas/govoplan-grants) | | `govoplan-forms` | `domain` | `../govoplan-forms` | [govoplan-forms](https://git.add-ideas.de/GovOPlaN/govoplan-forms) |
| `govoplan-helpdesk` | `domain` | `../govoplan-helpdesk` | [govoplan-helpdesk](https://git.add-ideas.de/add-ideas/govoplan-helpdesk) | | `govoplan-forms-runtime` | `platform` | `../govoplan-forms-runtime` | [govoplan-forms-runtime](https://git.add-ideas.de/GovOPlaN/govoplan-forms-runtime) |
| `govoplan-identity` | `platform` | `../govoplan-identity` | [govoplan-identity](https://git.add-ideas.de/add-ideas/govoplan-identity) | | `govoplan-grants` | `domain` | `../govoplan-grants` | [govoplan-grants](https://git.add-ideas.de/GovOPlaN/govoplan-grants) |
| `govoplan-identity-trust` | `platform` | `../govoplan-identity-trust` | [govoplan-identity-trust](https://git.add-ideas.de/add-ideas/govoplan-identity-trust) | | `govoplan-helpdesk` | `domain` | `../govoplan-helpdesk` | [govoplan-helpdesk](https://git.add-ideas.de/GovOPlaN/govoplan-helpdesk) |
| `govoplan-idm` | `platform` | `../govoplan-idm` | [govoplan-idm](https://git.add-ideas.de/add-ideas/govoplan-idm) | | `govoplan-identity` | `platform` | `../govoplan-identity` | [govoplan-identity](https://git.add-ideas.de/GovOPlaN/govoplan-identity) |
| `govoplan-inspections` | `domain` | `../govoplan-inspections` | [govoplan-inspections](https://git.add-ideas.de/add-ideas/govoplan-inspections) | | `govoplan-identity-trust` | `platform` | `../govoplan-identity-trust` | [govoplan-identity-trust](https://git.add-ideas.de/GovOPlaN/govoplan-identity-trust) |
| `govoplan-issue-reporting` | `domain` | `../govoplan-issue-reporting` | [govoplan-issue-reporting](https://git.add-ideas.de/add-ideas/govoplan-issue-reporting) | | `govoplan-idm` | `platform` | `../govoplan-idm` | [govoplan-idm](https://git.add-ideas.de/GovOPlaN/govoplan-idm) |
| `govoplan-learning` | `domain` | `../govoplan-learning` | [govoplan-learning](https://git.add-ideas.de/add-ideas/govoplan-learning) | | `govoplan-inspections` | `domain` | `../govoplan-inspections` | [govoplan-inspections](https://git.add-ideas.de/GovOPlaN/govoplan-inspections) |
| `govoplan-ledger` | `domain` | `../govoplan-ledger` | [govoplan-ledger](https://git.add-ideas.de/add-ideas/govoplan-ledger) | | `govoplan-learning` | `domain` | `../govoplan-learning` | [govoplan-learning](https://git.add-ideas.de/GovOPlaN/govoplan-learning) |
| `govoplan-mail` | `domain` | `../govoplan-mail` | [govoplan-mail](https://git.add-ideas.de/add-ideas/govoplan-mail) | | `govoplan-ledger` | `domain` | `../govoplan-ledger` | [govoplan-ledger](https://git.add-ideas.de/GovOPlaN/govoplan-ledger) |
| `govoplan-notifications` | `platform` | `../govoplan-notifications` | [govoplan-notifications](https://git.add-ideas.de/add-ideas/govoplan-notifications) | | `govoplan-mail` | `domain` | `../govoplan-mail` | [govoplan-mail](https://git.add-ideas.de/GovOPlaN/govoplan-mail) |
| `govoplan-ops` | `platform` | `../govoplan-ops` | [govoplan-ops](https://git.add-ideas.de/add-ideas/govoplan-ops) | | `govoplan-notifications` | `platform` | `../govoplan-notifications` | [govoplan-notifications](https://git.add-ideas.de/GovOPlaN/govoplan-notifications) |
| `govoplan-organizations` | `platform` | `../govoplan-organizations` | [govoplan-organizations](https://git.add-ideas.de/add-ideas/govoplan-organizations) | | `govoplan-ops` | `platform` | `../govoplan-ops` | [govoplan-ops](https://git.add-ideas.de/GovOPlaN/govoplan-ops) |
| `govoplan-payments` | `domain` | `../govoplan-payments` | [govoplan-payments](https://git.add-ideas.de/add-ideas/govoplan-payments) | | `govoplan-organizations` | `platform` | `../govoplan-organizations` | [govoplan-organizations](https://git.add-ideas.de/GovOPlaN/govoplan-organizations) |
| `govoplan-permits` | `domain` | `../govoplan-permits` | [govoplan-permits](https://git.add-ideas.de/add-ideas/govoplan-permits) | | `govoplan-payments` | `domain` | `../govoplan-payments` | [govoplan-payments](https://git.add-ideas.de/GovOPlaN/govoplan-payments) |
| `govoplan-policy` | `platform` | `../govoplan-policy` | [govoplan-policy](https://git.add-ideas.de/add-ideas/govoplan-policy) | | `govoplan-permits` | `domain` | `../govoplan-permits` | [govoplan-permits](https://git.add-ideas.de/GovOPlaN/govoplan-permits) |
| `govoplan-poll` | `domain` | `../govoplan-poll` | [govoplan-poll](https://git.add-ideas.de/add-ideas/govoplan-poll) | | `govoplan-policy` | `platform` | `../govoplan-policy` | [govoplan-policy](https://git.add-ideas.de/GovOPlaN/govoplan-policy) |
| `govoplan-portal` | `domain` | `../govoplan-portal` | [govoplan-portal](https://git.add-ideas.de/add-ideas/govoplan-portal) | | `govoplan-poll` | `domain` | `../govoplan-poll` | [govoplan-poll](https://git.add-ideas.de/GovOPlaN/govoplan-poll) |
| `govoplan-postbox` | `domain` | `../govoplan-postbox` | [govoplan-postbox](https://git.add-ideas.de/add-ideas/govoplan-postbox) | | `govoplan-portal` | `domain` | `../govoplan-portal` | [govoplan-portal](https://git.add-ideas.de/GovOPlaN/govoplan-portal) |
| `govoplan-procurement` | `domain` | `../govoplan-procurement` | [govoplan-procurement](https://git.add-ideas.de/add-ideas/govoplan-procurement) | | `govoplan-postbox` | `domain` | `../govoplan-postbox` | [govoplan-postbox](https://git.add-ideas.de/GovOPlaN/govoplan-postbox) |
| `govoplan-records` | `domain` | `../govoplan-records` | [govoplan-records](https://git.add-ideas.de/add-ideas/govoplan-records) | | `govoplan-procurement` | `domain` | `../govoplan-procurement` | [govoplan-procurement](https://git.add-ideas.de/GovOPlaN/govoplan-procurement) |
| `govoplan-reporting` | `domain` | `../govoplan-reporting` | [govoplan-reporting](https://git.add-ideas.de/add-ideas/govoplan-reporting) | | `govoplan-projects` | `domain` | `../govoplan-projects` | [govoplan-projects](https://git.add-ideas.de/GovOPlaN/govoplan-projects) |
| `govoplan-resources` | `domain` | `../govoplan-resources` | [govoplan-resources](https://git.add-ideas.de/add-ideas/govoplan-resources) | | `govoplan-records` | `domain` | `../govoplan-records` | [govoplan-records](https://git.add-ideas.de/GovOPlaN/govoplan-records) |
| `govoplan-risk-compliance` | `domain` | `../govoplan-risk-compliance` | [govoplan-risk-compliance](https://git.add-ideas.de/add-ideas/govoplan-risk-compliance) | | `govoplan-reporting` | `domain` | `../govoplan-reporting` | [govoplan-reporting](https://git.add-ideas.de/GovOPlaN/govoplan-reporting) |
| `govoplan-scheduling` | `domain` | `../govoplan-scheduling` | [govoplan-scheduling](https://git.add-ideas.de/add-ideas/govoplan-scheduling) | | `govoplan-resources` | `domain` | `../govoplan-resources` | [govoplan-resources](https://git.add-ideas.de/GovOPlaN/govoplan-resources) |
| `govoplan-search` | `platform` | `../govoplan-search` | [govoplan-search](https://git.add-ideas.de/add-ideas/govoplan-search) | | `govoplan-risk-compliance` | `domain` | `../govoplan-risk-compliance` | [govoplan-risk-compliance](https://git.add-ideas.de/GovOPlaN/govoplan-risk-compliance) |
| `govoplan-tasks` | `domain` | `../govoplan-tasks` | [govoplan-tasks](https://git.add-ideas.de/add-ideas/govoplan-tasks) | | `govoplan-scheduling` | `domain` | `../govoplan-scheduling` | [govoplan-scheduling](https://git.add-ideas.de/GovOPlaN/govoplan-scheduling) |
| `govoplan-templates` | `domain` | `../govoplan-templates` | [govoplan-templates](https://git.add-ideas.de/add-ideas/govoplan-templates) | | `govoplan-search` | `platform` | `../govoplan-search` | [govoplan-search](https://git.add-ideas.de/GovOPlaN/govoplan-search) |
| `govoplan-tenancy` | `platform` | `../govoplan-tenancy` | [govoplan-tenancy](https://git.add-ideas.de/add-ideas/govoplan-tenancy) | | `govoplan-tasks` | `domain` | `../govoplan-tasks` | [govoplan-tasks](https://git.add-ideas.de/GovOPlaN/govoplan-tasks) |
| `govoplan-transparency` | `domain` | `../govoplan-transparency` | [govoplan-transparency](https://git.add-ideas.de/add-ideas/govoplan-transparency) | | `govoplan-templates` | `domain` | `../govoplan-templates` | [govoplan-templates](https://git.add-ideas.de/GovOPlaN/govoplan-templates) |
| `govoplan-workflow` | `platform` | `../govoplan-workflow` | [govoplan-workflow](https://git.add-ideas.de/add-ideas/govoplan-workflow) | | `govoplan-tenancy` | `platform` | `../govoplan-tenancy` | [govoplan-tenancy](https://git.add-ideas.de/GovOPlaN/govoplan-tenancy) |
| `govoplan-tickets` | `domain` | `../govoplan-tickets` | [govoplan-tickets](https://git.add-ideas.de/GovOPlaN/govoplan-tickets) |
| `govoplan-transparency` | `domain` | `../govoplan-transparency` | [govoplan-transparency](https://git.add-ideas.de/GovOPlaN/govoplan-transparency) |
| `govoplan-views` | `platform` | `../govoplan-views` | [govoplan-views](https://git.add-ideas.de/GovOPlaN/govoplan-views) |
| `govoplan-wiki` | `domain` | `../govoplan-wiki` | [govoplan-wiki](https://git.add-ideas.de/GovOPlaN/govoplan-wiki) |
| `govoplan-workflow` | `platform` | `../govoplan-workflow` | [govoplan-workflow](https://git.add-ideas.de/GovOPlaN/govoplan-workflow) |
## Connector ## Connector
| Repository | Subtype | Local path | Gitea | | Repository | Subtype | Local path | Gitea |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `govoplan-connectors` | `connector-hub` | `../govoplan-connectors` | [govoplan-connectors](https://git.add-ideas.de/add-ideas/govoplan-connectors) | | `govoplan-connectors` | `connector-hub` | `../govoplan-connectors` | [govoplan-connectors](https://git.add-ideas.de/GovOPlaN/govoplan-connectors) |
| `govoplan-fit-connect` | `standard` | `../govoplan-fit-connect` | [govoplan-fit-connect](https://git.add-ideas.de/add-ideas/govoplan-fit-connect) | | `govoplan-fit-connect` | `standard` | `../govoplan-fit-connect` | [govoplan-fit-connect](https://git.add-ideas.de/GovOPlaN/govoplan-fit-connect) |
| `govoplan-rest` | `protocol` | `../govoplan-rest` | [govoplan-rest](https://git.add-ideas.de/add-ideas/govoplan-rest) | | `govoplan-rest` | `protocol` | `../govoplan-rest` | [govoplan-rest](https://git.add-ideas.de/GovOPlaN/govoplan-rest) |
| `govoplan-soap` | `protocol` | `../govoplan-soap` | [govoplan-soap](https://git.add-ideas.de/add-ideas/govoplan-soap) | | `govoplan-soap` | `protocol` | `../govoplan-soap` | [govoplan-soap](https://git.add-ideas.de/GovOPlaN/govoplan-soap) |
| `govoplan-xoev` | `standard` | `../govoplan-xoev` | [govoplan-xoev](https://git.add-ideas.de/add-ideas/govoplan-xoev) | | `govoplan-xoev` | `standard` | `../govoplan-xoev` | [govoplan-xoev](https://git.add-ideas.de/GovOPlaN/govoplan-xoev) |
| `govoplan-xrechnung` | `standard` | `../govoplan-xrechnung` | [govoplan-xrechnung](https://git.add-ideas.de/add-ideas/govoplan-xrechnung) | | `govoplan-xrechnung` | `standard` | `../govoplan-xrechnung` | [govoplan-xrechnung](https://git.add-ideas.de/GovOPlaN/govoplan-xrechnung) |
| `govoplan-xta-osci` | `standard` | `../govoplan-xta-osci` | [govoplan-xta-osci](https://git.add-ideas.de/add-ideas/govoplan-xta-osci) | | `govoplan-xta-osci` | `standard` | `../govoplan-xta-osci` | [govoplan-xta-osci](https://git.add-ideas.de/GovOPlaN/govoplan-xta-osci) |
## Website ## Website

View File

@@ -125,7 +125,7 @@ tools/checks/security-audit/run.sh --mode ci --scope current --strict
## Audit Burndown Workflow ## Audit Burndown Workflow
Treat Gitea issues as the active audit state. A full GovOPlaN audit should Treat Gitea issues as the active audit state. A full GovOPlaN audit should
produce one tracker issue in `add-ideas/govoplan` and child issues in the produce one tracker issue in `GovOPlaN/govoplan` and child issues in the
repository that owns each fix. repository that owns each fix.
Use the tracker issue for: Use the tracker issue for:

View File

@@ -19,7 +19,7 @@ This is a product-level story owned by the GovOPlaN platform rather than by an
individual domain module. It joins installation, module lifecycle, operations, individual domain module. It joins installation, module lifecycle, operations,
configuration packages, and release provenance into one administrator journey. configuration packages, and release provenance into one administrator journey.
The canonical backlog item is The canonical backlog item is
[GovOPlaN #13](https://git.add-ideas.de/add-ideas/govoplan/issues/13). [GovOPlaN #13](https://git.add-ideas.de/GovOPlaN/govoplan/issues/13).
## Terms ## Terms

137
docs/VIEWS_ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,137 @@
# GovOPlaN Views Architecture
## Purpose
GovOPlaN Views are governed presentation projections for a task,
responsibility, or workflow step. A View can reduce the visible modules,
navigation entries, routes, page sections, and commands to the interface
needed for the current job.
Views are optional. If `govoplan-views` is not installed or enabled, the normal
permission-derived interface remains unchanged.
## Security Boundary
A View is not an authorization mechanism.
- Access, tenant isolation, resource guards, and backend permission checks
remain authoritative.
- A View may hide an interface surface that the actor is otherwise allowed to
use.
- A View can never expose a route, action, tenant, or resource that normal
authorization denies.
- An authorized deep link outside the current View should offer an explicit
temporary escape or View switch. It must not be presented as a permission
denial.
This boundary lets Views improve focus without creating a second, weaker RBAC
system.
## Ownership
Core owns the versioned, module-neutral surface contract and WebUI runtime
hooks. Modules declare stable surfaces and use shared hooks to respect the
effective projection. Modules do not import `govoplan-views`.
`govoplan-views` owns:
- draft and immutable published View revisions
- system, tenant, group, and user assignments
- default, mandatory, and user-selectable Views
- active per-user View state
- effective projection resolution and provenance
- the View editor, preview, validation, and stale-surface diagnostics
Policy optionally owns inherited ceilings and explainable decisions. Workflow
optionally references a pinned View revision for an instance or step and may
narrow it further.
## Surface Contract
Modules announce only useful, semantic surfaces:
- module
- navigation item
- route or workspace
- section or panel
- command or action
Each descriptor has a stable namespaced id, parent id, kind, label, default
visibility, ordering, and dependency metadata where needed. Surface ids are
public module contracts, not CSS selectors, component paths, or arbitrary DOM
fragments.
The first release supports visible or hidden. Read-only states, layout
replacement, visual emphasis, and arbitrary styling are separate concerns and
are deferred.
## Effective Resolution
The effective interface is the intersection of:
1. installed and enabled modules
2. actor permissions and resource access
3. administrator and Policy ceilings
4. an assigned or user-selected View
5. an optional workflow instance or step overlay
Lower scopes and workflow overlays may narrow inherited visibility but cannot
broaden it. Every inherited, locked, hidden, unavailable, or stale choice
should carry provenance that the editor and runtime can explain.
Published View revisions are immutable. Active workflow instances pin the
revision they use. Unknown or retired surface ids produce diagnostics rather
than breaking startup. If no valid effective View can be resolved, the system
uses the last valid projection or the normal authorized interface and reports
the configuration problem to administrators.
## Workflow Behavior
A workflow definition may reference a View for the whole instance or a
particular step. Starting, resuming, or advancing the workflow activates the
appropriate projection. Users can intentionally leave focused mode and return
from an open-work widget or notification without losing workflow state.
Module handoffs carry the workflow and View context through Core contracts.
Workflow does not import the target module or the Views implementation.
## Delivery Order
1. Define the Core surface registry and runtime hooks.
2. Initialize `govoplan-views` and persist versioned definitions.
3. Add assignment, selection, resolution, provenance, and the editor.
4. Add Policy inheritance and administrator ceilings.
5. Add Workflow instance and step activation.
6. Adopt semantic section/action descriptors module by module.
## Implementation Status
Implemented in the initial Views slice:
- Core contract version `1`, stable module/navigation/route identifiers, custom
section/action descriptors, manifest validation, and platform API metadata
- shell navigation, route-boundary, settings, administration, dashboard-widget,
embedded-capability, and organization-action filtering
- `govoplan-views` definitions, immutable revisions, system/tenant/group/user
assignments, user selection, provenance, and stale-surface recovery
- a system and tenant administration editor with unsaved-change protection,
publish/archive controls, assignment management, and server-enforced lockout
prevention
- surface declarations for every currently installed module that contributes a
WebUI, including finer-grained shared administration and settings surfaces
Still intentionally separate:
- Policy-owned inherited ceilings and policy decision provenance
- workflow-instance and workflow-step activation of pinned View revisions
- read-only and layout-replacement projections beyond the version `1`
visible/hidden contract
## Gitea Work Packages
- `govoplan#17`: task-focused Views user story
- `govoplan#16`: initialize and implement `govoplan-views`
- `govoplan-core#271`: versioned surface and runtime contracts
- `govoplan-policy#9`: inheritance, ceilings, and provenance
- `govoplan-workflow#7`: workflow instance and step activation
- `govoplan-workflow#3`: focused workflow mode user story

View File

@@ -1,6 +1,6 @@
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.add-ideas.de/add-ideas/govoplan/src/branch/main/docs/capability-fit-boundary-evidence.schema.json", "$id": "https://git.add-ideas.de/GovOPlaN/govoplan/src/branch/main/docs/capability-fit-boundary-evidence.schema.json",
"title": "GovOPlaN externally issued capability-fit boundary evidence", "title": "GovOPlaN externally issued capability-fit boundary evidence",
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,

View File

@@ -507,7 +507,7 @@
{ {
"kind": "issue", "kind": "issue",
"scope": "documented_model", "scope": "documented_model",
"locator": "https://git.add-ideas.de/add-ideas/govoplan/issues/12" "locator": "https://git.add-ideas.de/GovOPlaN/govoplan/issues/12"
} }
], ],
"conditions": [], "conditions": [],
@@ -793,7 +793,7 @@
{ {
"kind": "issue", "kind": "issue",
"scope": "documented_model", "scope": "documented_model",
"locator": "https://git.add-ideas.de/add-ideas/govoplan-core/issues/29" "locator": "https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/29"
} }
], ],
"conditions": [], "conditions": [],

View File

@@ -1,6 +1,6 @@
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.add-ideas.de/add-ideas/govoplan/src/branch/main/docs/capability-fit-proof-authority-keyring.schema.json", "$id": "https://git.add-ideas.de/GovOPlaN/govoplan/src/branch/main/docs/capability-fit-proof-authority-keyring.schema.json",
"title": "GovOPlaN capability-fit proof authority keyring", "title": "GovOPlaN capability-fit proof authority keyring",
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,

View File

@@ -1,6 +1,6 @@
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.add-ideas.de/add-ideas/govoplan/src/branch/main/docs/capability-fit.schema.json", "$id": "https://git.add-ideas.de/GovOPlaN/govoplan/src/branch/main/docs/capability-fit.schema.json",
"title": "GovOPlaN capability and infrastructure fit assessment", "title": "GovOPlaN capability and infrastructure fit assessment",
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,

View File

@@ -149,12 +149,36 @@
"description": "GovOPlaN core runner, shared primitives, shell, or extension points.", "description": "GovOPlaN core runner, shared primitives, shell, or extension points.",
"exclusive": false "exclusive": false
}, },
{
"name": "module/dashboard",
"color": "1d76db",
"description": "GovOPlaN Dashboard module behavior or integration.",
"exclusive": false
},
{
"name": "module/dataflow",
"color": "1d76db",
"description": "GovOPlaN Dataflow module behavior or integration.",
"exclusive": false
},
{
"name": "module/datasources",
"color": "006b75",
"description": "GovOPlaN governed datasource contracts, catalogs, and integrations.",
"exclusive": false
},
{ {
"name": "module/dms", "name": "module/dms",
"color": "c5def5", "color": "c5def5",
"description": "GovOPlaN Dms module behavior or integration.", "description": "GovOPlaN Dms module behavior or integration.",
"exclusive": false "exclusive": false
}, },
{
"name": "module/docs",
"color": "c5def5",
"description": "GovOPlaN Docs module behavior or integration.",
"exclusive": false
},
{ {
"name": "module/dist-lists", "name": "module/dist-lists",
"color": "0e8a16", "color": "0e8a16",
@@ -191,6 +215,12 @@
"description": "GovOPlaN Forms module behavior or integration.", "description": "GovOPlaN Forms module behavior or integration.",
"exclusive": false "exclusive": false
}, },
{
"name": "module/helpdesk",
"color": "c2e0c6",
"description": "GovOPlaN Helpdesk module behavior or integration.",
"exclusive": false
},
{ {
"name": "module/identity-trust", "name": "module/identity-trust",
"color": "d4c5f9", "color": "d4c5f9",
@@ -275,12 +305,24 @@
"description": "GovOPlaN Postbox module behavior or integration.", "description": "GovOPlaN Postbox module behavior or integration.",
"exclusive": false "exclusive": false
}, },
{
"name": "module/projects",
"color": "5319e7",
"description": "GovOPlaN Projects module behavior or integration.",
"exclusive": false
},
{ {
"name": "module/reporting", "name": "module/reporting",
"color": "c2e0c6", "color": "c2e0c6",
"description": "GovOPlaN Reporting module behavior or integration.", "description": "GovOPlaN Reporting module behavior or integration.",
"exclusive": false "exclusive": false
}, },
{
"name": "module/risk-compliance",
"color": "b60205",
"description": "GovOPlaN Risk Compliance module behavior or integration.",
"exclusive": false
},
{ {
"name": "module/search", "name": "module/search",
"color": "bfdadc", "color": "bfdadc",
@@ -311,6 +353,24 @@
"description": "GovOPlaN Tenancy module behavior or integration.", "description": "GovOPlaN Tenancy module behavior or integration.",
"exclusive": false "exclusive": false
}, },
{
"name": "module/tickets",
"color": "0e8a16",
"description": "GovOPlaN Tickets module behavior or integration.",
"exclusive": false
},
{
"name": "module/views",
"color": "c5def5",
"description": "GovOPlaN governed task views, interface projections, and workflow view integration.",
"exclusive": false
},
{
"name": "module/wiki",
"color": "006b75",
"description": "GovOPlaN Wiki module behavior or integration.",
"exclusive": false
},
{ {
"name": "module/workflow", "name": "module/workflow",
"color": "f9d0c4", "color": "f9d0c4",

View File

@@ -1,6 +1,6 @@
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.add-ideas.de/add-ideas/govoplan/src/branch/main/docs/installed-composition-evidence.schema.json", "$id": "https://git.add-ideas.de/GovOPlaN/govoplan/src/branch/main/docs/installed-composition-evidence.schema.json",
"title": "GovOPlaN installed composition evidence", "title": "GovOPlaN installed composition evidence",
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,

View File

@@ -1,6 +1,6 @@
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.add-ideas.de/add-ideas/govoplan/src/branch/main/docs/installer-receipt-authority-keyring.schema.json", "$id": "https://git.add-ideas.de/GovOPlaN/govoplan/src/branch/main/docs/installer-receipt-authority-keyring.schema.json",
"title": "GovOPlaN installer receipt authority keyring", "title": "GovOPlaN installer receipt authority keyring",
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,

View File

@@ -1,6 +1,6 @@
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.add-ideas.de/add-ideas/govoplan/src/branch/main/docs/installer-receipt.schema.json", "$id": "https://git.add-ideas.de/GovOPlaN/govoplan/src/branch/main/docs/installer-receipt.schema.json",
"title": "GovOPlaN signed installer receipt", "title": "GovOPlaN signed installer receipt",
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,

View File

@@ -1,73 +1,78 @@
{ {
"version": 1, "version": 1,
"organization": "add-ideas", "organization": "GovOPlaN",
"default_parent": "/mnt/DATA/git", "default_parent": "/mnt/DATA/git",
"repositories": [ "repositories": [
{"name": "govoplan", "category": "system", "subtype": "meta", "remote": "git@git.add-ideas.de:add-ideas/govoplan.git", "path": "govoplan"}, {"name": "govoplan", "category": "system", "subtype": "meta", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan.git", "path": "govoplan"},
{"name": "govoplan-core", "category": "system", "subtype": "kernel", "remote": "git@git.add-ideas.de:add-ideas/govoplan-core.git", "path": "govoplan-core"}, {"name": "govoplan-core", "category": "system", "subtype": "kernel", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-core.git", "path": "govoplan-core"},
{"name": "govoplan-access", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:add-ideas/govoplan-access.git", "path": "govoplan-access"}, {"name": "govoplan-access", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-access.git", "path": "govoplan-access"},
{"name": "govoplan-addresses", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-addresses.git", "path": "govoplan-addresses"}, {"name": "govoplan-addresses", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-addresses.git", "path": "govoplan-addresses"},
{"name": "govoplan-admin", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:add-ideas/govoplan-admin.git", "path": "govoplan-admin"}, {"name": "govoplan-admin", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-admin.git", "path": "govoplan-admin"},
{"name": "govoplan-appointments", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-appointments.git", "path": "govoplan-appointments"}, {"name": "govoplan-appointments", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-appointments.git", "path": "govoplan-appointments"},
{"name": "govoplan-approvals", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-approvals.git", "path": "govoplan-approvals"}, {"name": "govoplan-approvals", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-approvals.git", "path": "govoplan-approvals"},
{"name": "govoplan-assets", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-assets.git", "path": "govoplan-assets"}, {"name": "govoplan-assets", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-assets.git", "path": "govoplan-assets"},
{"name": "govoplan-audit", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:add-ideas/govoplan-audit.git", "path": "govoplan-audit"}, {"name": "govoplan-audit", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-audit.git", "path": "govoplan-audit"},
{"name": "govoplan-booking", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-booking.git", "path": "govoplan-booking"}, {"name": "govoplan-booking", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-booking.git", "path": "govoplan-booking"},
{"name": "govoplan-calendar", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-calendar.git", "path": "govoplan-calendar"}, {"name": "govoplan-calendar", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-calendar.git", "path": "govoplan-calendar"},
{"name": "govoplan-campaign", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-campaign.git", "path": "govoplan-campaign"}, {"name": "govoplan-campaign", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-campaign.git", "path": "govoplan-campaign"},
{"name": "govoplan-cases", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-cases.git", "path": "govoplan-cases"}, {"name": "govoplan-cases", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-cases.git", "path": "govoplan-cases"},
{"name": "govoplan-certificates", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-certificates.git", "path": "govoplan-certificates"}, {"name": "govoplan-certificates", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-certificates.git", "path": "govoplan-certificates"},
{"name": "govoplan-committee", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-committee.git", "path": "govoplan-committee"}, {"name": "govoplan-committee", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-committee.git", "path": "govoplan-committee"},
{"name": "govoplan-connectors", "category": "connector", "subtype": "connector-hub", "remote": "git@git.add-ideas.de:add-ideas/govoplan-connectors.git", "path": "govoplan-connectors"}, {"name": "govoplan-connectors", "category": "connector", "subtype": "connector-hub", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-connectors.git", "path": "govoplan-connectors"},
{"name": "govoplan-consultation", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-consultation.git", "path": "govoplan-consultation"}, {"name": "govoplan-consultation", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-consultation.git", "path": "govoplan-consultation"},
{"name": "govoplan-contracts", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-contracts.git", "path": "govoplan-contracts"}, {"name": "govoplan-contracts", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-contracts.git", "path": "govoplan-contracts"},
{"name": "govoplan-dashboard", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:add-ideas/govoplan-dashboard.git", "path": "govoplan-dashboard"}, {"name": "govoplan-dashboard", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-dashboard.git", "path": "govoplan-dashboard"},
{"name": "govoplan-dms", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-dms.git", "path": "govoplan-dms"}, {"name": "govoplan-dataflow", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-dataflow.git", "path": "govoplan-dataflow"},
{"name": "govoplan-dist-lists", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-dist-lists.git", "path": "govoplan-dist-lists"}, {"name": "govoplan-datasources", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-datasources.git", "path": "govoplan-datasources"},
{"name": "govoplan-docs", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:add-ideas/govoplan-docs.git", "path": "govoplan-docs"}, {"name": "govoplan-dms", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-dms.git", "path": "govoplan-dms"},
{"name": "govoplan-erp", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-erp.git", "path": "govoplan-erp"}, {"name": "govoplan-dist-lists", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-dist-lists.git", "path": "govoplan-dist-lists"},
{"name": "govoplan-evaluation", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-evaluation.git", "path": "govoplan-evaluation"}, {"name": "govoplan-docs", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-docs.git", "path": "govoplan-docs"},
{"name": "govoplan-facilities", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-facilities.git", "path": "govoplan-facilities"}, {"name": "govoplan-erp", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-erp.git", "path": "govoplan-erp"},
{"name": "govoplan-files", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-files.git", "path": "govoplan-files"}, {"name": "govoplan-evaluation", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-evaluation.git", "path": "govoplan-evaluation"},
{"name": "govoplan-fit-connect", "category": "connector", "subtype": "standard", "remote": "git@git.add-ideas.de:add-ideas/govoplan-fit-connect.git", "path": "govoplan-fit-connect"}, {"name": "govoplan-facilities", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-facilities.git", "path": "govoplan-facilities"},
{"name": "govoplan-forms", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-forms.git", "path": "govoplan-forms"}, {"name": "govoplan-files", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-files.git", "path": "govoplan-files"},
{"name": "govoplan-forms-runtime", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:add-ideas/govoplan-forms-runtime.git", "path": "govoplan-forms-runtime"}, {"name": "govoplan-fit-connect", "category": "connector", "subtype": "standard", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-fit-connect.git", "path": "govoplan-fit-connect"},
{"name": "govoplan-grants", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-grants.git", "path": "govoplan-grants"}, {"name": "govoplan-forms", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-forms.git", "path": "govoplan-forms"},
{"name": "govoplan-helpdesk", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-helpdesk.git", "path": "govoplan-helpdesk"}, {"name": "govoplan-forms-runtime", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-forms-runtime.git", "path": "govoplan-forms-runtime"},
{"name": "govoplan-identity", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:add-ideas/govoplan-identity.git", "path": "govoplan-identity"}, {"name": "govoplan-grants", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-grants.git", "path": "govoplan-grants"},
{"name": "govoplan-identity-trust", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:add-ideas/govoplan-identity-trust.git", "path": "govoplan-identity-trust"}, {"name": "govoplan-helpdesk", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-helpdesk.git", "path": "govoplan-helpdesk"},
{"name": "govoplan-idm", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:add-ideas/govoplan-idm.git", "path": "govoplan-idm"}, {"name": "govoplan-identity", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-identity.git", "path": "govoplan-identity"},
{"name": "govoplan-inspections", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-inspections.git", "path": "govoplan-inspections"}, {"name": "govoplan-identity-trust", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-identity-trust.git", "path": "govoplan-identity-trust"},
{"name": "govoplan-issue-reporting", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-issue-reporting.git", "path": "govoplan-issue-reporting"}, {"name": "govoplan-idm", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-idm.git", "path": "govoplan-idm"},
{"name": "govoplan-learning", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-learning.git", "path": "govoplan-learning"}, {"name": "govoplan-inspections", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-inspections.git", "path": "govoplan-inspections"},
{"name": "govoplan-ledger", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-ledger.git", "path": "govoplan-ledger"}, {"name": "govoplan-learning", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-learning.git", "path": "govoplan-learning"},
{"name": "govoplan-mail", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-mail.git", "path": "govoplan-mail"}, {"name": "govoplan-ledger", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-ledger.git", "path": "govoplan-ledger"},
{"name": "govoplan-notifications", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:add-ideas/govoplan-notifications.git", "path": "govoplan-notifications"}, {"name": "govoplan-mail", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-mail.git", "path": "govoplan-mail"},
{"name": "govoplan-ops", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:add-ideas/govoplan-ops.git", "path": "govoplan-ops"}, {"name": "govoplan-notifications", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-notifications.git", "path": "govoplan-notifications"},
{"name": "govoplan-organizations", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:add-ideas/govoplan-organizations.git", "path": "govoplan-organizations"}, {"name": "govoplan-ops", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-ops.git", "path": "govoplan-ops"},
{"name": "govoplan-payments", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-payments.git", "path": "govoplan-payments"}, {"name": "govoplan-organizations", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-organizations.git", "path": "govoplan-organizations"},
{"name": "govoplan-permits", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-permits.git", "path": "govoplan-permits"}, {"name": "govoplan-payments", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-payments.git", "path": "govoplan-payments"},
{"name": "govoplan-policy", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:add-ideas/govoplan-policy.git", "path": "govoplan-policy"}, {"name": "govoplan-permits", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-permits.git", "path": "govoplan-permits"},
{"name": "govoplan-poll", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-poll.git", "path": "govoplan-poll"}, {"name": "govoplan-policy", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-policy.git", "path": "govoplan-policy"},
{"name": "govoplan-portal", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-portal.git", "path": "govoplan-portal"}, {"name": "govoplan-poll", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-poll.git", "path": "govoplan-poll"},
{"name": "govoplan-postbox", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-postbox.git", "path": "govoplan-postbox"}, {"name": "govoplan-portal", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-portal.git", "path": "govoplan-portal"},
{"name": "govoplan-procurement", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-procurement.git", "path": "govoplan-procurement"}, {"name": "govoplan-postbox", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-postbox.git", "path": "govoplan-postbox"},
{"name": "govoplan-records", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-records.git", "path": "govoplan-records"}, {"name": "govoplan-procurement", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-procurement.git", "path": "govoplan-procurement"},
{"name": "govoplan-reporting", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-reporting.git", "path": "govoplan-reporting"}, {"name": "govoplan-projects", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-projects.git", "path": "govoplan-projects"},
{"name": "govoplan-resources", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-resources.git", "path": "govoplan-resources"}, {"name": "govoplan-records", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-records.git", "path": "govoplan-records"},
{"name": "govoplan-rest", "category": "connector", "subtype": "protocol", "remote": "git@git.add-ideas.de:add-ideas/govoplan-rest.git", "path": "govoplan-rest"}, {"name": "govoplan-reporting", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-reporting.git", "path": "govoplan-reporting"},
{"name": "govoplan-risk-compliance", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-risk-compliance.git", "path": "govoplan-risk-compliance"}, {"name": "govoplan-resources", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-resources.git", "path": "govoplan-resources"},
{"name": "govoplan-scheduling", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-scheduling.git", "path": "govoplan-scheduling"}, {"name": "govoplan-rest", "category": "connector", "subtype": "protocol", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-rest.git", "path": "govoplan-rest"},
{"name": "govoplan-search", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:add-ideas/govoplan-search.git", "path": "govoplan-search"}, {"name": "govoplan-risk-compliance", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-risk-compliance.git", "path": "govoplan-risk-compliance"},
{"name": "govoplan-soap", "category": "connector", "subtype": "protocol", "remote": "git@git.add-ideas.de:add-ideas/govoplan-soap.git", "path": "govoplan-soap"}, {"name": "govoplan-scheduling", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-scheduling.git", "path": "govoplan-scheduling"},
{"name": "govoplan-tasks", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-tasks.git", "path": "govoplan-tasks"}, {"name": "govoplan-search", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-search.git", "path": "govoplan-search"},
{"name": "govoplan-templates", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-templates.git", "path": "govoplan-templates"}, {"name": "govoplan-soap", "category": "connector", "subtype": "protocol", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-soap.git", "path": "govoplan-soap"},
{"name": "govoplan-tenancy", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:add-ideas/govoplan-tenancy.git", "path": "govoplan-tenancy"}, {"name": "govoplan-tasks", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-tasks.git", "path": "govoplan-tasks"},
{"name": "govoplan-transparency", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:add-ideas/govoplan-transparency.git", "path": "govoplan-transparency"}, {"name": "govoplan-templates", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-templates.git", "path": "govoplan-templates"},
{"name": "govoplan-tenancy", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-tenancy.git", "path": "govoplan-tenancy"},
{"name": "govoplan-tickets", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-tickets.git", "path": "govoplan-tickets"},
{"name": "govoplan-transparency", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-transparency.git", "path": "govoplan-transparency"},
{"name": "govoplan-views", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-views.git", "path": "govoplan-views"},
{"name": "govoplan-wiki", "category": "module", "subtype": "domain", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-wiki.git", "path": "govoplan-wiki"},
{"name": "addideas-govoplan-website", "category": "website", "subtype": "public-site", "remote": "git@git.add-ideas.de:add-ideas/addideas-govoplan-website.git", "path": "addideas-govoplan-website", "bootstrap_transport": "registered"}, {"name": "addideas-govoplan-website", "category": "website", "subtype": "public-site", "remote": "git@git.add-ideas.de:add-ideas/addideas-govoplan-website.git", "path": "addideas-govoplan-website", "bootstrap_transport": "registered"},
{"name": "govoplan-workflow", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:add-ideas/govoplan-workflow.git", "path": "govoplan-workflow"}, {"name": "govoplan-workflow", "category": "module", "subtype": "platform", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-workflow.git", "path": "govoplan-workflow"},
{"name": "govoplan-xoev", "category": "connector", "subtype": "standard", "remote": "git@git.add-ideas.de:add-ideas/govoplan-xoev.git", "path": "govoplan-xoev"}, {"name": "govoplan-xoev", "category": "connector", "subtype": "standard", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-xoev.git", "path": "govoplan-xoev"},
{"name": "govoplan-xrechnung", "category": "connector", "subtype": "standard", "remote": "git@git.add-ideas.de:add-ideas/govoplan-xrechnung.git", "path": "govoplan-xrechnung"}, {"name": "govoplan-xrechnung", "category": "connector", "subtype": "standard", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-xrechnung.git", "path": "govoplan-xrechnung"},
{"name": "govoplan-xta-osci", "category": "connector", "subtype": "standard", "remote": "git@git.add-ideas.de:add-ideas/govoplan-xta-osci.git", "path": "govoplan-xta-osci"} {"name": "govoplan-xta-osci", "category": "connector", "subtype": "standard", "remote": "git@git.add-ideas.de:GovOPlaN/govoplan-xta-osci.git", "path": "govoplan-xta-osci"}
] ]
} }

View File

@@ -19,6 +19,14 @@
-e ../govoplan-mail -e ../govoplan-mail
-e ../govoplan-campaign -e ../govoplan-campaign
-e ../govoplan-calendar -e ../govoplan-calendar
-e ../govoplan-connectors
-e ../govoplan-datasources
-e ../govoplan-dataflow
-e ../govoplan-workflow
-e ../govoplan-views
-e ../govoplan-search
-e ../govoplan-risk-compliance
-e ../govoplan-postbox
-e ../govoplan-poll -e ../govoplan-poll
-e ../govoplan-scheduling -e ../govoplan-scheduling
-e ../govoplan-notifications -e ../govoplan-notifications
@@ -32,5 +40,7 @@ idna>=3.15
jsonschema>=4,<5 jsonschema>=4,<5
pip>=26.1.2 pip>=26.1.2
pip-audit>=2.9,<3 pip-audit>=2.9,<3
pytest>=9.0.3,<10
pygments>=2.20,<3
python-multipart>=0.0.31 python-multipart>=0.0.31
ruff>=0.14,<1 ruff>=0.14,<1

View File

@@ -0,0 +1,5 @@
# Test-harness dependencies used against immutable release source tags.
# Keep these separate from requirements-release.txt so they are not part of the
# deployable product dependency set.
pytest>=9.0.3,<10
pygments>=2.20,<3

View File

@@ -1,18 +1,18 @@
# Whole-product release install from immutable, independently versioned module tags. # Whole-product release install from immutable, independently versioned module tags.
# Only add a module after its referenced tag has been published. # Only add a module after its referenced tag has been published.
../govoplan-core[server] ../govoplan-core[server]
govoplan-tenancy @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-tenancy.git@v0.1.8 govoplan-tenancy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-tenancy.git@v0.1.8
govoplan-organizations @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-organizations.git@v0.1.8 govoplan-organizations @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git@v0.1.8
govoplan-identity @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-identity.git@v0.1.8 govoplan-identity @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-identity.git@v0.1.8
govoplan-idm @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-idm.git@v0.1.8 govoplan-idm @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git@v0.1.8
govoplan-access @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-access.git@v0.1.8 govoplan-access @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git@v0.1.8
govoplan-admin @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-admin.git@v0.1.8 govoplan-admin @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git@v0.1.8
govoplan-policy @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-policy.git@v0.1.8 govoplan-policy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git@v0.1.8
govoplan-audit @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-audit.git@v0.1.8 govoplan-audit @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git@v0.1.8
govoplan-dashboard @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-dashboard.git@v0.1.8 govoplan-dashboard @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-dashboard.git@v0.1.8
govoplan-files @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-files.git@v0.1.8 govoplan-files @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git@v0.1.8
govoplan-mail @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-mail.git@v0.1.10 govoplan-mail @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-mail.git@v0.1.10
govoplan-campaign @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-campaign.git@v0.1.11 govoplan-campaign @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git@v0.1.11
govoplan-calendar @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-calendar.git@v0.1.8 govoplan-calendar @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git@v0.1.8
govoplan-docs @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-docs.git@v0.1.8 govoplan-docs @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-docs.git@v0.1.8
govoplan-ops @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-ops.git@v0.1.8 govoplan-ops @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.git@v0.1.8

View File

@@ -0,0 +1,49 @@
from __future__ import annotations
import importlib.util
import pathlib
import sys
import tempfile
import unittest
ROOT = pathlib.Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "tools" / "gitea" / "gitea-sync-wiki.py"
TOOLS = SCRIPT.parent
if str(TOOLS) not in sys.path:
sys.path.insert(0, str(TOOLS))
SPEC = importlib.util.spec_from_file_location("gitea_sync_wiki", SCRIPT)
assert SPEC and SPEC.loader
wiki_sync = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = wiki_sync
SPEC.loader.exec_module(wiki_sync)
class WikiSourceDiscoveryTests(unittest.TestCase):
def test_generated_and_incidental_govoplan_files_are_not_docs(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = pathlib.Path(temporary)
paths = {
"readme": root / "README.md",
"architecture": root / "docs" / "DATASOURCE_ARCHITECTURE.md",
"plan": root / "workflow-plan.md",
"audit": root / "audit-reports" / "full" / "manifest.json",
"runtime": root / "runtime" / "release" / "manifest.json",
"incidental": root / "tools" / "semgrep" / "govoplan.yml",
"manifest": root / "manifest.json",
}
for path in paths.values():
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("content\n", encoding="utf-8")
self.assertTrue(wiki_sync.is_repo_doc(root, paths["readme"]))
self.assertTrue(wiki_sync.is_repo_doc(root, paths["architecture"]))
self.assertTrue(wiki_sync.is_repo_doc(root, paths["plan"]))
self.assertFalse(wiki_sync.is_repo_doc(root, paths["audit"]))
self.assertFalse(wiki_sync.is_repo_doc(root, paths["runtime"]))
self.assertFalse(wiki_sync.is_repo_doc(root, paths["incidental"]))
self.assertFalse(wiki_sync.is_repo_doc(root, paths["manifest"]))
if __name__ == "__main__":
unittest.main()

View File

@@ -77,6 +77,76 @@ class PythonEnvironmentSyncTests(unittest.TestCase):
self.assertIn(str(root / "govoplan-core"), command) self.assertIn(str(root / "govoplan-core"), command)
self.assertIn(str(root / "govoplan-access"), command) self.assertIn(str(root / "govoplan-access"), command)
def test_missing_editable_distribution_is_not_hidden_by_current_stamp(self) -> None:
sync = load_sync_module()
with tempfile.TemporaryDirectory(prefix="govoplan-python-sync-") as directory:
root = Path(directory)
project_root = root / "govoplan-probe-missing"
project_root.mkdir()
(project_root / "pyproject.toml").write_text(
"\n".join(
(
"[project]",
'name = "govoplan-probe-definitely-not-installed"',
'version = "0.1.0"',
"",
'[project.entry-points."govoplan.modules"]',
'probe_missing = "govoplan_probe.backend.manifest:get_manifest"',
"",
)
),
encoding="utf-8",
)
requirements = root / "requirements-dev.txt"
requirements.write_text("-e ./govoplan-probe-missing\n", encoding="utf-8")
entries = sync.local_requirement_entries(requirements)
validation = sync.validate_local_installations(sys.executable, entries)
plan = sync.build_environment_repair_plan(
python=sys.executable,
stale_requirements=validation.stale_requirements,
)
self.assertFalse(validation.current)
self.assertEqual(validation.stale_requirements, entries)
self.assertIn("distribution is not installed", validation.issues[0])
self.assertEqual(plan.mode, "Selective Python environment repair")
self.assertEqual(plan.commands[0][-2:], ("-e", str(project_root)))
def test_declared_module_entry_points_are_part_of_environment_validation(self) -> None:
sync = load_sync_module()
with tempfile.TemporaryDirectory(prefix="govoplan-python-sync-") as directory:
root = Path(directory)
project_root = root / "govoplan-probe"
project_root.mkdir()
(project_root / "pyproject.toml").write_text(
"\n".join(
(
"[project]",
'name = "govoplan-probe"',
'version = "0.1.0"',
"",
'[project.entry-points."govoplan.modules"]',
'probe = "govoplan_probe.backend.manifest:get_manifest"',
"",
)
),
encoding="utf-8",
)
requirements = root / "requirements-dev.txt"
requirements.write_text("-e ./govoplan-probe\n", encoding="utf-8")
expectations = sync.local_installation_expectations(
sync.local_requirement_entries(requirements)
)
self.assertEqual(len(expectations), 1)
self.assertEqual(expectations[0].distribution, "govoplan-probe")
self.assertEqual(
expectations[0].entry_points,
(("govoplan.modules", "probe", "govoplan_probe.backend.manifest:get_manifest"),),
)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -24,7 +24,7 @@ class ReleaseCatalogEntrySynthesisTests(unittest.TestCase):
"module_id": "access", "module_id": "access",
"version": "0.1.11", "version": "0.1.11",
"python_package": "govoplan-access", "python_package": "govoplan-access",
"python_ref": "govoplan-access @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-access.git@v0.1.11", "python_ref": "govoplan-access @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git@v0.1.11",
} }
], ],
} }
@@ -37,7 +37,7 @@ class ReleaseCatalogEntrySynthesisTests(unittest.TestCase):
"govoplan-scheduling": "0.1.11", "govoplan-scheduling": "0.1.11",
}, },
repo_contracts={}, repo_contracts={},
repository_base="git+ssh://git@git.add-ideas.de/add-ideas", repository_base="git+ssh://git@git.add-ideas.de/GovOPlaN",
workspace=META_ROOT.parent, workspace=META_ROOT.parent,
) )

View File

@@ -0,0 +1,39 @@
from __future__ import annotations
from pathlib import Path
import subprocess
import sys
import unittest
from unittest.mock import patch
META_ROOT = Path(__file__).resolve().parents[1]
RELEASE_TOOLS_ROOT = META_ROOT / "tools" / "release"
if str(RELEASE_TOOLS_ROOT) not in sys.path:
sys.path.insert(0, str(RELEASE_TOOLS_ROOT))
from govoplan_release import git_state # noqa: E402
class ReleaseGitStateTests(unittest.TestCase):
def test_git_trusts_only_the_resolved_repository_for_each_command(self) -> None:
repository = Path("/workspace/../workspace/govoplan-core")
completed = subprocess.CompletedProcess([], 0, "", "")
with patch.object(git_state.subprocess, "run", return_value=completed) as run:
result = git_state.git(repository, "status", "--porcelain")
self.assertEqual(result.returncode, 0)
command = run.call_args.args[0]
self.assertIn(f"safe.directory={repository.resolve()}", command)
self.assertNotIn("safe.directory=*", command)
self.assertEqual(run.call_args.kwargs["cwd"], repository)
self.assertEqual(
run.call_args.kwargs["env"]["GIT_CONFIG_GLOBAL"],
"/dev/null",
)
if __name__ == "__main__":
unittest.main()

View File

@@ -1,8 +1,10 @@
from __future__ import annotations from __future__ import annotations
import os
import tempfile import tempfile
import unittest import unittest
from pathlib import Path from pathlib import Path
from unittest.mock import patch
from tools.checks.release_integration import ( from tools.checks.release_integration import (
SOURCE_COUPLED_CORE_TESTS, SOURCE_COUPLED_CORE_TESTS,
@@ -10,6 +12,7 @@ from tools.checks.release_integration import (
artifact_contract_issues, artifact_contract_issues,
filter_test_suite, filter_test_suite,
release_package_names, release_package_names,
run_module_release_tests,
) )
@@ -32,9 +35,9 @@ class ReleaseIntegrationTests(unittest.TestCase):
"\n".join( "\n".join(
( (
"../govoplan-core[server]", "../govoplan-core[server]",
"govoplan-idm @ git+ssh://git@example.test/add-ideas/govoplan-idm.git@v0.1.8", "govoplan-idm @ git+ssh://git@example.test/GovOPlaN/govoplan-idm.git@v0.1.8",
"govoplan-campaign @ git+ssh://git@example.test/add-ideas/govoplan-campaign.git@v0.1.8", "govoplan-campaign @ git+ssh://git@example.test/GovOPlaN/govoplan-campaign.git@v0.1.8",
"govoplan-idm @ git+ssh://git@example.test/add-ideas/govoplan-idm.git@v0.1.8", "govoplan-idm @ git+ssh://git@example.test/GovOPlaN/govoplan-idm.git@v0.1.8",
"other-package==1.0", "other-package==1.0",
) )
), ),
@@ -89,8 +92,67 @@ class ReleaseIntegrationTests(unittest.TestCase):
self.assertLess(artifact_check, core_tests) self.assertLess(artifact_check, core_tests)
self.assertLess(core_tests, module_tests) self.assertLess(core_tests, module_tests)
self.assertNotIn("unittest discover", script)
self.assertNotIn('"$PYTHON" -m unittest \\\n tests.test_module_system', script) self.assertNotIn('"$PYTHON" -m unittest \\\n tests.test_module_system', script)
def test_tagged_module_runner_executes_pytest_functions_from_module_root(self) -> None:
with tempfile.TemporaryDirectory(prefix="govoplan-release-module-tests-") as temp_dir:
module_root = Path(temp_dir)
tests_root = module_root / "tests"
tests_root.mkdir()
marker = module_root / "pytest-function-ran"
(tests_root / "test_probe.py").write_text(
"""from pathlib import Path
import os
def test_pytest_style_function():
expected_root = Path(os.environ[\"GOVOPLAN_TEST_MODULE_ROOT\"])
assert Path.cwd() == expected_root
Path(os.environ[\"GOVOPLAN_TEST_MARKER\"]).write_text(\"ran\", encoding=\"utf-8\")
""",
encoding="utf-8",
)
with patch.dict(
os.environ,
{
"GOVOPLAN_TEST_MODULE_ROOT": str(module_root),
"GOVOPLAN_TEST_MARKER": str(marker),
},
):
status = run_module_release_tests(module_root)
self.assertEqual(status, 0)
self.assertEqual(marker.read_text(encoding="utf-8"), "ran")
def test_tagged_module_runner_skips_missing_test_tree(self) -> None:
with tempfile.TemporaryDirectory(prefix="govoplan-release-module-tests-") as temp_dir:
self.assertEqual(run_module_release_tests(Path(temp_dir)), 0)
def test_module_matrix_uses_artifact_aware_core_suite(self) -> None:
meta_root = Path(__file__).resolve().parents[1]
script = (meta_root / "tools" / "checks" / "check-module-matrix.sh").read_text(encoding="utf-8")
artifact_check = script.index('"$META_ROOT/tools/checks/release_integration.py" artifacts')
core_tests = script.index('"$META_ROOT/tools/checks/release_integration.py" core-tests')
self.assertLess(artifact_check, core_tests)
self.assertIn('--requirements "$META_ROOT/requirements-release.txt"', script)
self.assertIn('--core-root "$ROOT"', script)
self.assertNotIn('"$PYTHON" -m unittest tests.test_module_system', script)
def test_release_workflow_installs_tagged_source_test_harness(self) -> None:
meta_root = Path(__file__).resolve().parents[1]
workflow = (meta_root / ".gitea" / "workflows" / "release-integration.yml").read_text(encoding="utf-8")
requirements = (meta_root / "requirements-release-tests.txt").read_text(encoding="utf-8")
install = workflow.index("pip install -r requirements-release-tests.txt")
checks = workflow.index("bash tools/checks/check-release-integration.sh")
self.assertLess(install, checks)
self.assertIn("pytest>=9.0.3,<10", requirements)
self.assertNotIn("requirements-release-tests.txt", (meta_root / "requirements-release.txt").read_text(encoding="utf-8"))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -156,7 +156,7 @@ def dashboard(*, workspace: Path, version: str) -> ReleaseDashboard:
name="govoplan-files", name="govoplan-files",
category="module", category="module",
subtype="infrastructure", subtype="infrastructure",
remote="git@example.test:add-ideas/govoplan-files.git", remote="git@example.test:GovOPlaN/govoplan-files.git",
path="govoplan-files", path="govoplan-files",
), ),
absolute_path=str(workspace / "govoplan-files"), absolute_path=str(workspace / "govoplan-files"),

View File

@@ -28,23 +28,23 @@ class RepositoryBootstrapTests(unittest.TestCase):
bootstrap = load_bootstrap_module() bootstrap = load_bootstrap_module()
self.assertEqual( self.assertEqual(
"https://git.add-ideas.de/add-ideas/govoplan-core.git", "https://git.add-ideas.de/GovOPlaN/govoplan-core.git",
bootstrap.clone_remote( bootstrap.clone_remote(
"git@git.add-ideas.de:add-ideas/govoplan-core.git", "git@git.add-ideas.de:GovOPlaN/govoplan-core.git",
transport=bootstrap.PUBLIC_HTTPS_TRANSPORT, transport=bootstrap.PUBLIC_HTTPS_TRANSPORT,
), ),
) )
self.assertEqual( self.assertEqual(
"https://git.add-ideas.de/add-ideas/govoplan-core.git", "https://git.add-ideas.de/GovOPlaN/govoplan-core.git",
bootstrap.clone_remote( bootstrap.clone_remote(
"ssh://git@git.add-ideas.de/add-ideas/govoplan-core.git", "ssh://git@git.add-ideas.de/GovOPlaN/govoplan-core.git",
transport=bootstrap.PUBLIC_HTTPS_TRANSPORT, transport=bootstrap.PUBLIC_HTTPS_TRANSPORT,
), ),
) )
self.assertEqual( self.assertEqual(
"https://git.add-ideas.de/add-ideas/govoplan-core.git", "https://git.add-ideas.de/GovOPlaN/govoplan-core.git",
bootstrap.clone_remote( bootstrap.clone_remote(
"https://git.add-ideas.de/add-ideas/govoplan-core.git", "https://git.add-ideas.de/GovOPlaN/govoplan-core.git",
transport=bootstrap.PUBLIC_HTTPS_TRANSPORT, transport=bootstrap.PUBLIC_HTTPS_TRANSPORT,
), ),
) )
@@ -65,12 +65,12 @@ class RepositoryBootstrapTests(unittest.TestCase):
bootstrap = load_bootstrap_module() bootstrap = load_bootstrap_module()
unsafe_remotes = ( unsafe_remotes = (
"git@example.test:add-ideas/govoplan-core.git", "git@example.test:GovOPlaN/govoplan-core.git",
"https://token@git.add-ideas.de/add-ideas/govoplan-core.git", "https://token@git.add-ideas.de/GovOPlaN/govoplan-core.git",
"https://git.add-ideas.de:443/add-ideas/govoplan-core.git", "https://git.add-ideas.de:443/GovOPlaN/govoplan-core.git",
"https://git.add-ideas.de/add-ideas/../govoplan-core.git", "https://git.add-ideas.de/add-ideas/../govoplan-core.git",
"https://git.add-ideas.de/add-ideas/govoplan-core.git?ref=main", "https://git.add-ideas.de/GovOPlaN/govoplan-core.git?ref=main",
"ssh://root@git.add-ideas.de/add-ideas/govoplan-core.git", "ssh://root@git.add-ideas.de/GovOPlaN/govoplan-core.git",
) )
for remote in unsafe_remotes: for remote in unsafe_remotes:
with self.subTest(remote=remote), self.assertRaises(ValueError): with self.subTest(remote=remote), self.assertRaises(ValueError):
@@ -95,7 +95,7 @@ class RepositoryBootstrapTests(unittest.TestCase):
"path": "govoplan-core", "path": "govoplan-core",
"remote": ( "remote": (
"git@git.add-ideas.de:" "git@git.add-ideas.de:"
"add-ideas/govoplan-core.git" "GovOPlaN/govoplan-core.git"
), ),
} }
], ],
@@ -124,7 +124,7 @@ class RepositoryBootstrapTests(unittest.TestCase):
"credential.helper=", "credential.helper=",
"clone", "clone",
"--", "--",
"https://git.add-ideas.de/add-ideas/govoplan-core.git", "https://git.add-ideas.de/GovOPlaN/govoplan-core.git",
str(parent / "govoplan-core"), str(parent / "govoplan-core"),
], ],
check=True, check=True,
@@ -150,7 +150,7 @@ class RepositoryBootstrapTests(unittest.TestCase):
"path": "govoplan-core", "path": "govoplan-core",
"remote": ( "remote": (
"git@git.add-ideas.de:" "git@git.add-ideas.de:"
"add-ideas/govoplan-core.git" "GovOPlaN/govoplan-core.git"
), ),
}, },
{ {
@@ -241,7 +241,7 @@ class RepositoryBootstrapTests(unittest.TestCase):
"name": name, "name": name,
"path": name, "path": name,
"remote": ( "remote": (
"git@git.add-ideas.de:add-ideas/" "git@git.add-ideas.de:GovOPlaN/"
f"{name}.git" f"{name}.git"
), ),
} }
@@ -272,7 +272,7 @@ class RepositoryBootstrapTests(unittest.TestCase):
self.assertEqual(0, status) self.assertEqual(0, status)
runner.assert_called_once() runner.assert_called_once()
self.assertEqual( self.assertEqual(
"https://git.add-ideas.de/add-ideas/govoplan-core.git", "https://git.add-ideas.de/GovOPlaN/govoplan-core.git",
runner.call_args.args[0][-2], runner.call_args.args[0][-2],
) )
@@ -292,7 +292,7 @@ class RepositoryBootstrapTests(unittest.TestCase):
"path": "govoplan-core", "path": "govoplan-core",
"remote": ( "remote": (
"git@git.add-ideas.de:" "git@git.add-ideas.de:"
"add-ideas/govoplan-core.git" "GovOPlaN/govoplan-core.git"
), ),
} }
], ],
@@ -334,7 +334,7 @@ class RepositoryBootstrapTests(unittest.TestCase):
"path": "govoplan-core", "path": "govoplan-core",
"remote": ( "remote": (
"git@git.add-ideas.de:" "git@git.add-ideas.de:"
"add-ideas/govoplan-core.git" "GovOPlaN/govoplan-core.git"
), ),
} }
], ],

View File

@@ -28,8 +28,8 @@ def load_resolver_module():
class SecurityAuditMountResolverTests(unittest.TestCase): class SecurityAuditMountResolverTests(unittest.TestCase):
def setUp(self) -> None: def setUp(self) -> None:
self.resolver = load_resolver_module() self.resolver = load_resolver_module()
self.root = "/workspace/add-ideas/govoplan/govoplan" self.root = "/workspace/GovOPlaN/govoplan/govoplan"
self.workspace = "/workspace/add-ideas/govoplan" self.workspace = "/workspace/GovOPlaN/govoplan"
def test_resolves_only_the_named_workspace_volume_for_both_scopes(self) -> None: def test_resolves_only_the_named_workspace_volume_for_both_scopes(self) -> None:
mounts = [ mounts = [
@@ -136,7 +136,7 @@ class SecurityAuditMountResolverTests(unittest.TestCase):
{ {
"Type": "volume", "Type": "volume",
"Name": "wrong-workspace", "Name": "wrong-workspace",
"Destination": "/workspace/add-ideas/govoplan-other", "Destination": "/workspace/GovOPlaN/govoplan-other",
"RW": True, "RW": True,
} }
], ],

View File

@@ -6,6 +6,7 @@ import subprocess
import sys import sys
import tempfile import tempfile
import unittest import unittest
from unittest.mock import patch
META_ROOT = Path(__file__).resolve().parents[1] META_ROOT = Path(__file__).resolve().parents[1]
@@ -20,6 +21,7 @@ from govoplan_release.version_alignment import ( # noqa: E402
repository_version_issues, repository_version_issues,
selected_repository_version_issues, selected_repository_version_issues,
) )
from govoplan_release.git_state import sanitized_git_environment # noqa: E402
from govoplan_release.model import ( # noqa: E402 from govoplan_release.model import ( # noqa: E402
CatalogSnapshot, CatalogSnapshot,
DashboardSummary, DashboardSummary,
@@ -398,6 +400,68 @@ class VersionAlignmentTests(unittest.TestCase):
) )
self.assertTrue(all("must contain" in issue.message for issue in issues)) self.assertTrue(all("must contain" in issue.message for issue in issues))
def test_annotated_release_tags_work_with_sanitized_git_configuration(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
workspace = Path(tmp)
meta = workspace / "govoplan"
core = workspace / "govoplan-core"
module = workspace / "govoplan-example"
meta.mkdir()
(core / "webui").mkdir(parents=True)
(module / "webui").mkdir(parents=True)
backend_ref = "git+ssh://git@example.test/acme/govoplan-example.git@v1.2.3"
webui_ref = "git+ssh://git@example.test/acme/govoplan-example.git#v1.2.3"
(meta / "requirements-release.txt").write_text(f"govoplan-example @ {backend_ref}\n")
(module / "pyproject.toml").write_text('[project]\nname="govoplan-example"\nversion="1.2.3"\n')
(module / "webui" / "package.json").write_text(
'{"name":"@govoplan/example-webui","version":"1.2.3"}\n'
)
subprocess.run(["git", "init", "-q", str(module)], check=True)
subprocess.run(["git", "-C", str(module), "config", "user.email", "test@example.test"], check=True)
subprocess.run(["git", "-C", str(module), "config", "user.name", "Test"], check=True)
subprocess.run(["git", "-C", str(module), "add", "pyproject.toml", "webui/package.json"], check=True)
subprocess.run(["git", "-C", str(module), "commit", "-qm", "release"], check=True)
subprocess.run(
["git", "-C", str(module), "tag", "-a", "v1.2.3", "-m", "Release v1.2.3"],
check=True,
)
tagged_commit = subprocess.run(
["git", "-C", str(module), "rev-parse", "refs/tags/v1.2.3^{commit}"],
check=True,
text=True,
stdout=subprocess.PIPE,
).stdout.strip()
dependencies = {"@govoplan/example-webui": webui_ref}
(core / "pyproject.toml").write_text('[project]\nname="govoplan-core"\nversion="2.0.0"\n')
(core / "webui" / "package.release.json").write_text(
json.dumps({"version": "2.0.0", "dependencies": dependencies})
)
(core / "webui" / "package-lock.release.json").write_text(
json.dumps(
{
"packages": {
"": {"version": "2.0.0", "dependencies": dependencies},
"node_modules/@govoplan/example-webui": {
"version": "1.2.3",
"resolved": f"git+ssh://git@example.test/acme/govoplan-example.git#{tagged_commit}",
},
}
}
)
)
git_environment = sanitized_git_environment()
git_environment["GIT_TEST_ASSUME_DIFFERENT_OWNER"] = "1"
with patch(
"govoplan_release.version_alignment.sanitized_git_environment",
return_value=git_environment,
):
composition_issues = release_composition_issues(meta, core_root=core)
core_issues = repository_version_issues(core)
self.assertEqual((), composition_issues)
self.assertEqual((), core_issues)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -0,0 +1,264 @@
#!/usr/bin/env python3
"""Exercise connector -> datasource -> dataflow publication capabilities."""
from __future__ import annotations
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from govoplan_connectors.backend.db.models import ConnectorTabularSource
from govoplan_core.auth import ApiPrincipal
from govoplan_core.core.access import PrincipalRef
from govoplan_core.core.dataflows import (
DataflowPublicationTarget,
DataflowRunRequest,
dataflow_run_lifecycle,
)
from govoplan_core.core.datasources import (
DatasourceReadRequest,
datasource_catalogue,
datasource_lifecycle,
datasource_publication,
)
from govoplan_core.core.modules import ModuleContext
from govoplan_core.core.tabular_sources import (
TabularSnapshotInput,
tabular_snapshot_writer,
)
from govoplan_core.db.base import Base
from govoplan_core.server.registry import build_platform_registry
from govoplan_dataflow.backend.schemas import (
GraphEdge,
GraphNode,
GraphPosition,
PipelineGraph,
PipelineCreateRequest,
PipelinePreviewRequest,
)
from govoplan_dataflow.backend.db.models import (
DataflowPipeline,
DataflowPipelineRevision,
DataflowRun,
)
from govoplan_dataflow.backend.service import create_pipeline, preview_pipeline
from govoplan_datasources.backend.db.models import (
DatasourceMaterializationRecord,
DatasourcePayloadRecord,
DatasourcePayloadRowRecord,
DatasourcePublicationRecord,
DatasourceRecord,
DatasourceStageRecord,
)
def main() -> int:
registry = build_platform_registry(
("connectors", "datasources", "dataflow", "workflow")
)
registry.configure_capability_context(
ModuleContext(registry=registry, settings=object())
)
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(
engine,
tables=[
ConnectorTabularSource.__table__,
DatasourceRecord.__table__,
DatasourcePayloadRecord.__table__,
DatasourcePayloadRowRecord.__table__,
DatasourceMaterializationRecord.__table__,
DatasourceStageRecord.__table__,
DatasourcePublicationRecord.__table__,
DataflowPipeline.__table__,
DataflowPipelineRevision.__table__,
DataflowRun.__table__,
],
)
session_factory = sessionmaker(bind=engine)
with session_factory() as session:
principal = _principal()
writer = tabular_snapshot_writer(registry)
lifecycle = datasource_lifecycle(registry)
catalogue = datasource_catalogue(registry)
publisher = datasource_publication(registry)
runner = dataflow_run_lifecycle(registry)
if (
writer is None
or lifecycle is None
or catalogue is None
or publisher is None
or runner is None
):
raise RuntimeError("Datasource composition capabilities are incomplete.")
origin = writer.create_snapshot(
session,
principal,
snapshot=TabularSnapshotInput(
name="Monthly cases",
source_name="connector_monthly_cases",
rows=(
{"id": 1, "amount": 5},
{"id": 2, "amount": 15},
),
),
)
datasource = lifecycle.register_origin(
session,
principal,
origin_ref=origin.ref,
name="Monthly cases cache",
source_name="monthly_cases",
mode="cached",
)
result = preview_pipeline(
session,
tenant_id="tenant-1",
actor_id="account-1",
payload=PipelinePreviewRequest(
graph=_graph(
datasource_ref=datasource.ref,
fingerprint=datasource.fingerprint,
),
row_limit=100,
),
principal=principal,
registry=registry,
)
expected_rows = [
{"id": 1, "amount": 5},
{"id": 2, "amount": 15},
]
if result.status != "succeeded":
raise RuntimeError(f"Dataflow preview failed: {result.diagnostics}")
if result.rows != expected_rows:
raise RuntimeError(f"Unexpected Dataflow rows: {result.rows!r}")
if result.source_fingerprints[0]["source_ref"] != datasource.ref:
raise RuntimeError("Dataflow lineage did not retain the datasource reference.")
pipeline = create_pipeline(
session,
tenant_id="tenant-1",
actor_id="account-1",
payload=PipelineCreateRequest(
name="Monthly case output",
status="active",
graph=_graph(
datasource_ref=datasource.ref,
fingerprint=datasource.fingerprint,
),
editor_mode="graph",
),
)
run_request = DataflowRunRequest(
pipeline_ref=f"pipeline:{pipeline.id}",
revision=1,
idempotency_key="composition-run-1",
publication=DataflowPublicationTarget(
name="Monthly case result",
source_name="monthly_case_result",
freeze=True,
frozen_label="Composition evidence",
),
)
published = runner.start_run(
session,
principal,
request=run_request,
)
replayed = runner.start_run(
session,
principal,
request=run_request,
)
if published.status != "succeeded":
raise RuntimeError(f"Dataflow publication failed: {published.error}")
if replayed.ref != published.ref or not replayed.replayed:
raise RuntimeError("Dataflow run idempotency did not replay the prior run.")
if (
not published.output_datasource_ref
or not published.output_materialization_ref
):
raise RuntimeError("Dataflow publication did not retain output references.")
output = catalogue.read_datasource(
session,
principal,
request=DatasourceReadRequest(
datasource_ref=published.output_datasource_ref,
),
)
if list(output.rows) != expected_rows:
raise RuntimeError(
f"Unexpected published Dataflow rows: {list(output.rows)!r}"
)
if (
output.materialization is None
or output.materialization.ref != published.output_materialization_ref
or output.materialization.frozen_at is None
):
raise RuntimeError(
"Published Datasource materialization is not pinned and frozen."
)
engine.dispose()
print(
"Connector -> Datasources -> pinned Dataflow publication composition passed."
)
return 0
def _principal() -> ApiPrincipal:
return ApiPrincipal(
principal=PrincipalRef(
account_id="account-1",
membership_id="membership-1",
tenant_id="tenant-1",
scopes=frozenset(
{
"connectors:source:read",
"connectors:source:write",
"datasources:catalogue:read",
"datasources:source:write",
"datasources:stage:write",
"dataflow:pipeline:run",
}
),
),
account=object(),
user=object(),
)
def _graph(*, datasource_ref: str, fingerprint: str) -> PipelineGraph:
return PipelineGraph(
nodes=[
GraphNode(
id="source",
type="source.reference",
label="Cases",
position=GraphPosition(x=0, y=0),
config={
"source_ref": datasource_ref,
"source_name": "monthly_cases",
"expected_fingerprint": fingerprint,
"consistency": "current",
},
),
GraphNode(
id="output",
type="output",
label="Output",
position=GraphPosition(x=200, y=0),
config={},
),
],
edges=[
GraphEdge(
id="source-output",
source="source",
target="output",
)
],
)
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -69,6 +69,14 @@ PY
"$PYTHON" -c 'import govoplan_core.db.bootstrap; import govoplan_access.backend.admin.service; import govoplan_addresses.backend.manifest; import govoplan_files.backend.router; import govoplan_mail.backend.sending.imap; print("targeted backend imports passed")' "$PYTHON" -c 'import govoplan_core.db.bootstrap; import govoplan_access.backend.admin.service; import govoplan_addresses.backend.manifest; import govoplan_files.backend.router; import govoplan_mail.backend.sending.imap; print("targeted backend imports passed")'
"$META_ROOT/tools/checks/check_dependency_boundaries.py" "$META_ROOT/tools/checks/check_dependency_boundaries.py"
"$PYTHON" -m unittest tests.test_module_system "$PYTHON" -m unittest tests.test_module_system
"$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-connectors/tests
"$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-datasources/tests
"$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-dataflow/tests
"$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-workflow/tests
"$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-views/tests
"$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-dashboard/tests
"$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-postbox/tests
"$PYTHON" "$META_ROOT/tools/checks/check-datasource-composition.py"
"$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-mail/tests "$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-mail/tests
"$PYTHON" -m unittest tests.test_api_smoke.ApiSmokeTests.test_mailbox_message_listing_reports_total_count "$PYTHON" -m unittest tests.test_api_smoke.ApiSmokeTests.test_mailbox_message_listing_reports_total_count
@@ -77,6 +85,21 @@ cd "$ROOT/webui"
"$NPM" run test:module-capabilities "$NPM" run test:module-capabilities
"$NPM" run test:module-permutations "$NPM" run test:module-permutations
cd /mnt/DATA/git/govoplan-dataflow/webui
"$NPM" run test:structure
cd /mnt/DATA/git/govoplan-datasources/webui
"$NPM" run typecheck
cd /mnt/DATA/git/govoplan-workflow/webui
"$NPM" run typecheck
cd /mnt/DATA/git/govoplan-dashboard/webui
"$NPM" run test:dashboard-layout
cd /mnt/DATA/git/govoplan-postbox/webui
"$NPM" run test:ui-structure
cd /mnt/DATA/git/govoplan-mail/webui cd /mnt/DATA/git/govoplan-mail/webui
"$NPM" run test:mail-ui "$NPM" run test:mail-ui

View File

@@ -19,7 +19,10 @@ fi
cd "$ROOT" cd "$ROOT"
"$PYTHON" "$META_ROOT/tools/checks/check-contracts.py" --no-impact "$PYTHON" "$META_ROOT/tools/checks/check-contracts.py" --no-impact
"$PYTHON" -m unittest tests.test_module_system tests.test_access_contracts "$PYTHON" "$META_ROOT/tools/checks/release_integration.py" artifacts \
--requirements "$META_ROOT/requirements-release.txt"
"$PYTHON" "$META_ROOT/tools/checks/release_integration.py" core-tests \
--core-root "$ROOT"
"$PYTHON" "$META_ROOT/tools/checks/check_dependency_boundaries.py" "$PYTHON" "$META_ROOT/tools/checks/check_dependency_boundaries.py"
cd "$ROOT/webui" cd "$ROOT/webui"

View File

@@ -60,24 +60,6 @@ retry() {
done done
} }
run_discovered_tests() {
local suite_dir="$1"
local status
if ! find "$suite_dir" -type f -name 'test*.py' -print -quit | grep -q .; then
echo "No unittest test files found under $suite_dir; skipping."
return 0
fi
set +e
"$PYTHON" -m unittest discover -s "$suite_dir"
status=$?
set -e
if [[ "$status" == 5 ]]; then
echo "No unittest tests discovered under $suite_dir; skipping."
return 0
fi
return "$status"
}
install_cloned_webui_dependencies() { install_cloned_webui_dependencies() {
local webui_dir="$1" local webui_dir="$1"
if [[ ! -f "$webui_dir/package.json" ]]; then if [[ ! -f "$webui_dir/package.json" ]]; then
@@ -244,7 +226,7 @@ PY
run_step "Clone release module test sources" run_step "Clone release module test sources"
for repo in govoplan-mail govoplan-calendar govoplan-campaign; do for repo in govoplan-mail govoplan-calendar govoplan-campaign; do
repo_tag="$(release_tag_for_package "$repo")" repo_tag="$(release_tag_for_package "$repo")"
git clone --depth 1 --branch "$repo_tag" "git@git.add-ideas.de:add-ideas/${repo}.git" "$WORK_ROOT/$repo" git clone --depth 1 --branch "$repo_tag" "git@git.add-ideas.de:GovOPlaN/${repo}.git" "$WORK_ROOT/$repo"
done done
run_step "Run core backend release tests" run_step "Run core backend release tests"
@@ -253,9 +235,10 @@ run_step "Run core backend release tests"
"$PYTHON" "$META_ROOT/tools/checks/check_dependency_boundaries.py" "$PYTHON" "$META_ROOT/tools/checks/check_dependency_boundaries.py"
run_step "Run cloned module backend tests" run_step "Run cloned module backend tests"
run_discovered_tests "$WORK_ROOT/govoplan-mail/tests" for repo in govoplan-mail govoplan-calendar govoplan-campaign; do
run_discovered_tests "$WORK_ROOT/govoplan-calendar/tests" "$PYTHON" "$META_ROOT/tools/checks/release_integration.py" module-tests \
run_discovered_tests "$WORK_ROOT/govoplan-campaign/tests" --module-root "$WORK_ROOT/$repo"
done
run_step "Run core WebUI release tests and build" run_step "Run core WebUI release tests and build"
cd "$ROOT/webui" cd "$ROOT/webui"

View File

@@ -496,7 +496,12 @@ run_bandit() {
fi fi
if [[ "${#PY_TEST_ROOTS[@]}" -gt 0 ]]; then if [[ "${#PY_TEST_ROOTS[@]}" -gt 0 ]]; then
prepare_machine_report "$REPORTS_DIR/bandit-tests.json" || return 2 prepare_machine_report "$REPORTS_DIR/bandit-tests.json" || return 2
bandit -r "${PY_TEST_ROOTS[@]}" -f json -o "$REPORTS_DIR/bandit-tests.json" # Tests intentionally use assertions and synthetic credentials. Keep the
# separate test scan useful by excluding only those test-specific signals.
bandit -r "${PY_TEST_ROOTS[@]}" \
--skip B101,B105,B106,B107 \
-f json \
-o "$REPORTS_DIR/bandit-tests.json"
local test_status=$? local test_status=$?
[[ "$test_status" -le 1 ]] || status=2 [[ "$test_status" -le 1 ]] || status=2
fi fi
@@ -512,7 +517,11 @@ run_ruff_security() {
fi fi
if [[ "${#PY_TEST_ROOTS[@]}" -gt 0 ]]; then if [[ "${#PY_TEST_ROOTS[@]}" -gt 0 ]]; then
prepare_machine_report "$REPORTS_DIR/ruff-security-tests.json" || return 2 prepare_machine_report "$REPORTS_DIR/ruff-security-tests.json" || return 2
ruff check --select S --output-format json "${PY_TEST_ROOTS[@]}" > "$REPORTS_DIR/ruff-security-tests.json" ruff check \
--select S \
--ignore S101,S105,S106,S107 \
--output-format json \
"${PY_TEST_ROOTS[@]}" > "$REPORTS_DIR/ruff-security-tests.json"
local test_status=$? local test_status=$?
[[ "$test_status" -le 1 ]] || status=2 [[ "$test_status" -le 1 ]] || status=2
fi fi
@@ -582,14 +591,32 @@ run_trivy() {
run_pip_audit_manifests() { run_pip_audit_manifests() {
local status=0 local status=0
for repo in "${REPOS[@]}"; do for repo in "${REPOS[@]}"; do
[[ -f "$repo/requirements.txt" ]] || continue local repo_name
local name repo_name="$(safe_name "$repo")"
name="$(safe_name "$repo")" while IFS= read -r -d '' requirements_file; do
prepare_machine_report "$REPORTS_DIR/pip-audit-$name.json" || return 2 local manifest_name report_path
# Requirements may contain project-relative entries such as `.[server]`. manifest_name="$(safe_name "$requirements_file")"
# Resolve them from the owning repository instead of the meta-repository. report_path="$REPORTS_DIR/pip-audit-$repo_name-$manifest_name.json"
(cd "$repo" && pip-audit -r requirements.txt --progress-spinner off --format json --output "$REPORTS_DIR/pip-audit-$name.json") prepare_machine_report "$report_path" || return 2
accumulate_exit_status status "$?" # Requirements may contain project-relative entries such as `.[server]`.
# Resolve them from the owning repository instead of the meta-repository.
(
cd "$repo" &&
pip-audit \
-r "$(basename "$requirements_file")" \
--progress-spinner off \
--format json \
--output "$report_path"
)
accumulate_exit_status status "$?"
done < <(
find "$repo" \
-maxdepth 1 \
-type f \
-name 'requirements*.txt' \
-print0 |
sort -z
)
done done
return "$status" return "$status"
} }
@@ -597,12 +624,38 @@ run_pip_audit_manifests() {
run_npm_audit_manifests() { run_npm_audit_manifests() {
local status=0 local status=0
for repo in "${REPOS[@]}"; do for repo in "${REPOS[@]}"; do
[[ -f "$repo/webui/package-lock.json" ]] || continue local repo_name
local name repo_name="$(safe_name "$repo")"
name="$(safe_name "$repo")" while IFS= read -r -d '' lock_file; do
prepare_machine_report "$REPORTS_DIR/npm-audit-$name.json" || return 2 local lock_dir lock_name runtime_report all_report
(cd "$repo/webui" && npm audit --omit=dev --json > "$REPORTS_DIR/npm-audit-$name.json") lock_dir="$(dirname "$lock_file")"
accumulate_exit_status status "$?" lock_name="$(
printf '%s' "${lock_file#"$repo"/}" |
tr -c 'A-Za-z0-9_.-' '_'
)"
runtime_report="$REPORTS_DIR/npm-audit-runtime-$repo_name-$lock_name.json"
all_report="$REPORTS_DIR/npm-audit-all-$repo_name-$lock_name.json"
prepare_machine_report "$runtime_report" || return 2
(
cd "$lock_dir" &&
npm audit --omit=dev --json > "$runtime_report"
)
accumulate_exit_status status "$?"
prepare_machine_report "$all_report" || return 2
(
cd "$lock_dir" &&
npm audit --json > "$all_report"
)
accumulate_exit_status status "$?"
done < <(
find "$repo" \
-maxdepth 3 \
-type f \
-name package-lock.json \
-not -path '*/node_modules/*' \
-print0 |
sort -z
)
done done
return "$status" return "$status"
} }

View File

@@ -5,6 +5,7 @@ import argparse
import importlib.metadata as metadata import importlib.metadata as metadata
import os import os
import re import re
import subprocess
import sys import sys
import tempfile import tempfile
import unittest import unittest
@@ -354,6 +355,24 @@ def run_core_release_tests(core_root: Path) -> int:
return 0 if result.wasSuccessful() else 1 return 0 if result.wasSuccessful() else 1
def run_module_release_tests(module_root: Path) -> int:
resolved_root = module_root.resolve()
tests_root = resolved_root / "tests"
if not tests_root.is_dir() or not any(tests_root.rglob("test*.py")):
print(f"No test files found under {tests_root}; skipping.")
return 0
result = subprocess.run(
(sys.executable, "-m", "pytest", "-q", "tests"),
cwd=resolved_root,
check=False,
)
if result.returncode == 5:
print(f"No tests collected under {tests_root}; skipping.")
return 0
return result.returncode
def _parser() -> argparse.ArgumentParser: def _parser() -> argparse.ArgumentParser:
meta_root = Path(__file__).resolve().parents[2] meta_root = Path(__file__).resolve().parents[2]
parser = argparse.ArgumentParser(description="Artifact-aware GovOPlaN release integration checks") parser = argparse.ArgumentParser(description="Artifact-aware GovOPlaN release integration checks")
@@ -366,6 +385,8 @@ def _parser() -> argparse.ArgumentParser:
) )
core_parser = subparsers.add_parser("core-tests", help="Run Core tests that are valid for tagged module artifacts") core_parser = subparsers.add_parser("core-tests", help="Run Core tests that are valid for tagged module artifacts")
core_parser.add_argument("--core-root", type=Path, required=True) core_parser.add_argument("--core-root", type=Path, required=True)
module_parser = subparsers.add_parser("module-tests", help="Run tests from one tagged module source checkout")
module_parser.add_argument("--module-root", type=Path, required=True)
return parser return parser
@@ -373,7 +394,9 @@ def main(argv: Sequence[str] | None = None) -> int:
args = _parser().parse_args(argv) args = _parser().parse_args(argv)
if args.command == "artifacts": if args.command == "artifacts":
return run_installed_artifact_checks(args.requirements) return run_installed_artifact_checks(args.requirements)
return run_core_release_tests(args.core_root) if args.command == "core-tests":
return run_core_release_tests(args.core_root)
return run_module_release_tests(args.module_root)
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -27,12 +27,17 @@ EXCLUDED_PARTS = {
".git", ".git",
".gitea", ".gitea",
".venv", ".venv",
".mypy_cache",
".pytest_cache",
".ruff_cache",
"node_modules", "node_modules",
"__pycache__", "__pycache__",
"audit-reports",
"dist", "dist",
"build", "build",
".cache", ".cache",
".module-test-build", ".module-test-build",
"runtime",
} }
EXCLUDED_NAMES = { EXCLUDED_NAMES = {
"package-lock.json", "package-lock.json",
@@ -40,6 +45,14 @@ EXCLUDED_NAMES = {
"yarn.lock", "yarn.lock",
"uv.lock", "uv.lock",
} }
REPO_DOC_NAME_TOKENS = {
"architecture",
"backlog",
"plan",
"roadmap",
"todo",
"workflow",
}
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
@@ -216,7 +229,8 @@ def is_repo_doc(repo_root_path: pathlib.Path, path: pathlib.Path) -> bool:
return False return False
if rel.parts[0] in {"docs", "doc", "codex"} and path.suffix.lower() in {".md", ".txt", ".csv"}: if rel.parts[0] in {"docs", "doc", "codex"} and path.suffix.lower() in {".md", ".txt", ".csv"}:
return True return True
if re.search(r"(backlog|todo|roadmap|plan|architecture|workflow|manifest)", path.name, re.IGNORECASE): name_tokens = set(re.split(r"[^a-z0-9]+", path.stem.lower()))
if name_tokens & REPO_DOC_NAME_TOKENS:
return True return True
return False return False

View File

@@ -22,6 +22,7 @@ FRONTEND_USE_POLLING="${GOVOPLAN_FRONTEND_USE_POLLING:-1}"
FRONTEND_POLLING_INTERVAL="${GOVOPLAN_FRONTEND_POLLING_INTERVAL:-500}" FRONTEND_POLLING_INTERVAL="${GOVOPLAN_FRONTEND_POLLING_INTERVAL:-500}"
AUTO_SYNC_PYTHON="${GOVOPLAN_AUTO_SYNC_PYTHON:-1}" AUTO_SYNC_PYTHON="${GOVOPLAN_AUTO_SYNC_PYTHON:-1}"
DEV_DATABASE_BACKEND="${GOVOPLAN_DEV_DATABASE_BACKEND:-postgres}" DEV_DATABASE_BACKEND="${GOVOPLAN_DEV_DATABASE_BACKEND:-postgres}"
BACKEND_RELOAD_MODULES="${GOVOPLAN_BACKEND_RELOAD_MODULES:-}"
LOG_DIR="${GOVOPLAN_DEV_LOG_DIR:-$META_ROOT/runtime/dev-launcher}" LOG_DIR="${GOVOPLAN_DEV_LOG_DIR:-$META_ROOT/runtime/dev-launcher}"
BACKEND_LOG="$LOG_DIR/backend.log" BACKEND_LOG="$LOG_DIR/backend.log"
@@ -167,7 +168,16 @@ port_is_free "$BACKEND_HOST" "$BACKEND_PORT" || fail "$BACKEND_URL is already in
port_is_free "$FRONTEND_HOST" "$FRONTEND_PORT" || fail "$FRONTEND_URL is already in use" port_is_free "$FRONTEND_HOST" "$FRONTEND_PORT" || fail "$FRONTEND_URL is already in use"
printf 'Starting GovOPlaN backend at %s\n' "$BACKEND_URL" printf 'Starting GovOPlaN backend at %s\n' "$BACKEND_URL"
run_grouped "$ROOT" "$BACKEND_LOG" "$PYTHON" -m govoplan_core.devserver --host "$BACKEND_HOST" --port "$BACKEND_PORT" backend_args=(-m govoplan_core.devserver --host "$BACKEND_HOST" --port "$BACKEND_PORT")
if [ "$BACKEND_RELOAD_MODULES" = "none" ]; then
backend_args+=(--reload-core-only)
elif [ -n "$BACKEND_RELOAD_MODULES" ]; then
IFS=',' read -r -a reload_modules <<< "$BACKEND_RELOAD_MODULES"
for reload_module in "${reload_modules[@]}"; do
[ -n "$reload_module" ] && backend_args+=(--reload-module "$reload_module")
done
fi
run_grouped "$ROOT" "$BACKEND_LOG" "$PYTHON" "${backend_args[@]}"
backend_pid="$run_grouped_pid" backend_pid="$run_grouped_pid"
printf 'Waiting for %s/health\n' "$BACKEND_URL" printf 'Waiting for %s/health\n' "$BACKEND_URL"

View File

@@ -51,7 +51,7 @@ set +a
export APP_ENV="${APP_ENV:-staging}" export APP_ENV="${APP_ENV:-staging}"
export GOVOPLAN_INSTALL_PROFILE="${GOVOPLAN_INSTALL_PROFILE:-production-like}" export GOVOPLAN_INSTALL_PROFILE="${GOVOPLAN_INSTALL_PROFILE:-production-like}"
export ENABLED_MODULES="${ENABLED_MODULES:-tenancy,organizations,identity,access,admin,dashboard,policy,audit,campaigns,files,mail,calendar,poll,scheduling,notifications,docs,ops}" export ENABLED_MODULES="${ENABLED_MODULES:-tenancy,organizations,identity,access,admin,dashboard,policy,audit,campaigns,files,mail,calendar,poll,scheduling,connectors,datasources,dataflow,workflow,views,search,risk_compliance,notifications,docs,ops}"
export DATABASE_URL="${DATABASE_URL:-${GOVOPLAN_PRODUCTION_LIKE_DATABASE_URL:-postgresql+psycopg://govoplan:govoplan-dev@127.0.0.1:55433/govoplan}}" export DATABASE_URL="${DATABASE_URL:-${GOVOPLAN_PRODUCTION_LIKE_DATABASE_URL:-postgresql+psycopg://govoplan:govoplan-dev@127.0.0.1:55433/govoplan}}"
export GOVOPLAN_DATABASE_URL_PGTOOLS="${GOVOPLAN_DATABASE_URL_PGTOOLS:-${GOVOPLAN_PRODUCTION_LIKE_DATABASE_URL_PGTOOLS:-postgresql://govoplan:govoplan-dev@127.0.0.1:55433/govoplan}}" export GOVOPLAN_DATABASE_URL_PGTOOLS="${GOVOPLAN_DATABASE_URL_PGTOOLS:-${GOVOPLAN_PRODUCTION_LIKE_DATABASE_URL_PGTOOLS:-postgresql://govoplan:govoplan-dev@127.0.0.1:55433/govoplan}}"
export REDIS_URL="${REDIS_URL:-${GOVOPLAN_PRODUCTION_LIKE_REDIS_URL:-redis://127.0.0.1:56379/0}}" export REDIS_URL="${REDIS_URL:-${GOVOPLAN_PRODUCTION_LIKE_REDIS_URL:-redis://127.0.0.1:56379/0}}"

View File

@@ -66,7 +66,7 @@ export DATABASE_URL="${DATABASE_URL:-${GOVOPLAN_PRODUCTION_LIKE_DATABASE_URL:-po
export GOVOPLAN_DATABASE_URL_PGTOOLS="${GOVOPLAN_DATABASE_URL_PGTOOLS:-${GOVOPLAN_PRODUCTION_LIKE_DATABASE_URL_PGTOOLS:-postgresql://govoplan:govoplan-dev@127.0.0.1:55433/govoplan}}" export GOVOPLAN_DATABASE_URL_PGTOOLS="${GOVOPLAN_DATABASE_URL_PGTOOLS:-${GOVOPLAN_PRODUCTION_LIKE_DATABASE_URL_PGTOOLS:-postgresql://govoplan:govoplan-dev@127.0.0.1:55433/govoplan}}"
export REDIS_URL="${REDIS_URL:-${GOVOPLAN_PRODUCTION_LIKE_REDIS_URL:-redis://127.0.0.1:56379/0}}" export REDIS_URL="${REDIS_URL:-${GOVOPLAN_PRODUCTION_LIKE_REDIS_URL:-redis://127.0.0.1:56379/0}}"
export CELERY_ENABLED="${CELERY_ENABLED:-true}" export CELERY_ENABLED="${CELERY_ENABLED:-true}"
export ENABLED_MODULES="${ENABLED_MODULES:-tenancy,organizations,identity,access,admin,dashboard,policy,audit,campaigns,files,mail,calendar,poll,scheduling,notifications,docs,ops}" export ENABLED_MODULES="${ENABLED_MODULES:-tenancy,organizations,identity,access,admin,dashboard,policy,audit,campaigns,files,mail,calendar,poll,scheduling,connectors,datasources,dataflow,workflow,views,search,risk_compliance,notifications,docs,ops}"
export FILE_STORAGE_BACKEND="${FILE_STORAGE_BACKEND:-local}" export FILE_STORAGE_BACKEND="${FILE_STORAGE_BACKEND:-local}"
export FILE_STORAGE_LOCAL_ROOT="${FILE_STORAGE_LOCAL_ROOT:-$META_ROOT/runtime/production-like/files}" export FILE_STORAGE_LOCAL_ROOT="${FILE_STORAGE_LOCAL_ROOT:-$META_ROOT/runtime/production-like/files}"
export DEV_AUTO_MIGRATE_ENABLED="${DEV_AUTO_MIGRATE_ENABLED:-false}" export DEV_AUTO_MIGRATE_ENABLED="${DEV_AUTO_MIGRATE_ENABLED:-false}"

View File

@@ -26,7 +26,7 @@ from govoplan_core.server.registry import available_module_manifests # noqa: E4
from govoplan_release.version_alignment import selected_repository_version_issues # noqa: E402 from govoplan_release.version_alignment import selected_repository_version_issues # noqa: E402
GITEA_BASE = "git+ssh://git@git.add-ideas.de/add-ideas" GITEA_BASE = "git+ssh://git@git.add-ideas.de/GovOPlaN"
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)

View File

@@ -164,9 +164,10 @@ def git_success(path: Path, *args: str, timeout: int = 10) -> bool:
def git(path: Path, *args: str, timeout: int = 10) -> subprocess.CompletedProcess[str]: def git(path: Path, *args: str, timeout: int = 10) -> subprocess.CompletedProcess[str]:
command = scoped_git_command(path, *args)
try: try:
return subprocess.run( return subprocess.run(
["/usr/bin/git", "-c", "core.hooksPath=/dev/null", *args], command,
cwd=path, cwd=path,
check=False, check=False,
text=True, text=True,
@@ -176,7 +177,20 @@ def git(path: Path, *args: str, timeout: int = 10) -> subprocess.CompletedProces
env=sanitized_git_environment(), env=sanitized_git_environment(),
) )
except subprocess.TimeoutExpired as exc: except subprocess.TimeoutExpired as exc:
return subprocess.CompletedProcess(["/usr/bin/git", *args], 124, exc.stdout or "", exc.stderr or "git command timed out") return subprocess.CompletedProcess(command, 124, exc.stdout or "", exc.stderr or "git command timed out")
def scoped_git_command(path: Path, *args: str) -> tuple[str, ...]:
"""Trust exactly one resolved catalog checkout for one Git invocation."""
return (
"/usr/bin/git",
"-c",
"core.hooksPath=/dev/null",
"-c",
f"safe.directory={path.resolve()}",
*args,
)
def sanitized_git_environment( def sanitized_git_environment(

View File

@@ -1985,6 +1985,8 @@ def _git_bytes(
"-C", "-C",
str(path), str(path),
"-c", "-c",
f"safe.directory={path.resolve()}",
"-c",
"core.sharedRepository=0600", "core.sharedRepository=0600",
"-c", "-c",
"core.fsmonitor=false", "core.fsmonitor=false",

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
from pathlib import Path from pathlib import Path
import subprocess import subprocess
from .git_state import collect_repository_snapshot from .git_state import collect_repository_snapshot, scoped_git_command
from .repository_push import command_text, compact_output from .repository_push import command_text, compact_output
from .workspace import load_repository_specs, resolve_repo_path, resolve_workspace_root from .workspace import load_repository_specs, resolve_repo_path, resolve_workspace_root
@@ -131,7 +131,12 @@ def resolve_message(*, repo: str, version: str | None, message: str | None) -> s
def run(command: tuple[str, ...], *, cwd: Path) -> subprocess.CompletedProcess[str]: def run(command: tuple[str, ...], *, cwd: Path) -> subprocess.CompletedProcess[str]:
effective_command = (
scoped_git_command(cwd, *command[1:])
if command and Path(command[0]).name == "git"
else command
)
try: try:
return subprocess.run(command, cwd=cwd, check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=120) return subprocess.run(effective_command, cwd=cwd, check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=120)
except subprocess.TimeoutExpired as exc: except subprocess.TimeoutExpired as exc:
return subprocess.CompletedProcess(command, 124, exc.stdout or "", exc.stderr or "git command timed out") return subprocess.CompletedProcess(effective_command, 124, exc.stdout or "", exc.stderr or "git command timed out")

View File

@@ -6,7 +6,7 @@ from pathlib import Path
import shlex import shlex
import subprocess import subprocess
from .git_state import collect_repository_snapshot from .git_state import collect_repository_snapshot, scoped_git_command
from .workspace import load_repository_specs, resolve_repo_path, resolve_workspace_root from .workspace import load_repository_specs, resolve_repo_path, resolve_workspace_root
@@ -138,10 +138,15 @@ def command_text(command: tuple[str, ...]) -> str:
def run(command: tuple[str, ...], *, cwd: Path) -> subprocess.CompletedProcess[str]: def run(command: tuple[str, ...], *, cwd: Path) -> subprocess.CompletedProcess[str]:
effective_command = (
scoped_git_command(cwd, *command[1:])
if command and Path(command[0]).name == "git"
else command
)
try: try:
return subprocess.run(command, cwd=cwd, check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=120) return subprocess.run(effective_command, cwd=cwd, check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=120)
except subprocess.TimeoutExpired as exc: except subprocess.TimeoutExpired as exc:
return subprocess.CompletedProcess(command, 124, exc.stdout or "", exc.stderr or "git push timed out") return subprocess.CompletedProcess(effective_command, 124, exc.stdout or "", exc.stderr or "git push timed out")
def compact_output(value: str) -> str: def compact_output(value: str) -> str:

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
from pathlib import Path from pathlib import Path
import subprocess import subprocess
from .git_state import collect_repository_snapshot from .git_state import collect_repository_snapshot, scoped_git_command
from .repository_push import command_text, compact_output from .repository_push import command_text, compact_output
from .workspace import load_repository_specs, resolve_repo_path, resolve_workspace_root from .workspace import load_repository_specs, resolve_repo_path, resolve_workspace_root
@@ -93,7 +93,12 @@ def sync_repositories(
def run(command: tuple[str, ...], *, cwd: Path) -> subprocess.CompletedProcess[str]: def run(command: tuple[str, ...], *, cwd: Path) -> subprocess.CompletedProcess[str]:
effective_command = (
scoped_git_command(cwd, *command[1:])
if command and Path(command[0]).name == "git"
else command
)
try: try:
return subprocess.run(command, cwd=cwd, check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=120) return subprocess.run(effective_command, cwd=cwd, check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=120)
except subprocess.TimeoutExpired as exc: except subprocess.TimeoutExpired as exc:
return subprocess.CompletedProcess(command, 124, exc.stdout or "", exc.stderr or "git fetch timed out") return subprocess.CompletedProcess(effective_command, 124, exc.stdout or "", exc.stderr or "git fetch timed out")

View File

@@ -13,6 +13,7 @@ from .git_state import (
collect_repository_snapshot, collect_repository_snapshot,
git_text, git_text,
sanitized_git_environment, sanitized_git_environment,
scoped_git_command,
) )
from .model import RepositorySnapshot from .model import RepositorySnapshot
from .repository_push import command_text, compact_output from .repository_push import command_text, compact_output
@@ -479,12 +480,7 @@ def run(
effective_command = command effective_command = command
effective_environment = env effective_environment = env
if command and Path(command[0]).name == "git": if command and Path(command[0]).name == "git":
effective_command = ( effective_command = scoped_git_command(cwd, *command[1:])
"/usr/bin/git",
"-c",
"core.hooksPath=/dev/null",
*command[1:],
)
effective_environment = sanitized_git_environment(env) effective_environment = sanitized_git_environment(env)
try: try:
return subprocess.run( return subprocess.run(

View File

@@ -53,7 +53,7 @@ from .workspace import (
website_root, website_root,
) )
GITEA_BASE = "git+ssh://git@git.add-ideas.de/add-ideas" GITEA_BASE = "git+ssh://git@git.add-ideas.de/GovOPlaN"
MAX_BASE_JSON_BYTES = 16 * 1024 * 1024 MAX_BASE_JSON_BYTES = 16 * 1024 * 1024
_SIGNING_KEY_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$") _SIGNING_KEY_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$")

View File

@@ -16,6 +16,7 @@ from .git_state import (
collect_repository_snapshot, collect_repository_snapshot,
git_text, git_text,
sanitized_git_environment, sanitized_git_environment,
scoped_git_command,
) )
from .repository_tag import RemoteTagResult, ref_commit, remote_tag_commit from .repository_tag import RemoteTagResult, ref_commit, remote_tag_commit
from .version_alignment import repository_version_issues from .version_alignment import repository_version_issues
@@ -417,9 +418,10 @@ def _git_file(path: Path, tag: str, name: str) -> str | None:
def _git(path: Path, *args: str) -> subprocess.CompletedProcess[str]: def _git(path: Path, *args: str) -> subprocess.CompletedProcess[str]:
command = scoped_git_command(path, *args)
try: try:
return subprocess.run( return subprocess.run(
("/usr/bin/git", "-c", "core.hooksPath=/dev/null", *args), command,
cwd=path, cwd=path,
check=False, check=False,
text=True, text=True,
@@ -429,7 +431,7 @@ def _git(path: Path, *args: str) -> subprocess.CompletedProcess[str]:
env=sanitized_git_environment(), env=sanitized_git_environment(),
) )
except subprocess.TimeoutExpired as exc: except subprocess.TimeoutExpired as exc:
return subprocess.CompletedProcess(("/usr/bin/git", *args), 124, exc.stdout or "", exc.stderr or "Git command timed out") return subprocess.CompletedProcess(command, 124, exc.stdout or "", exc.stderr or "Git command timed out")
def _deduplicate(issues: list[SourceTagProvenanceIssue]) -> list[SourceTagProvenanceIssue]: def _deduplicate(issues: list[SourceTagProvenanceIssue]) -> list[SourceTagProvenanceIssue]:

View File

@@ -681,10 +681,22 @@ def _git_lock_resolution_issues(
def _git_tag_commit(repo_path: Path, tag: str) -> str | None: def _git_tag_commit(repo_path: Path, tag: str) -> str | None:
repo_path = repo_path.resolve()
if not (repo_path / ".git").exists(): if not (repo_path / ".git").exists():
return None return None
result = subprocess.run( result = subprocess.run(
["/usr/bin/git", "-C", str(repo_path), "rev-list", "-n", "1", tag], [
"/usr/bin/git",
"-c",
"core.hooksPath=/dev/null",
"-c",
f"safe.directory={repo_path}",
"-C",
str(repo_path),
"rev-parse",
"--verify",
f"refs/tags/{tag}^{{commit}}",
],
check=False, check=False,
text=True, text=True,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@@ -697,10 +709,22 @@ def _git_tag_commit(repo_path: Path, tag: str) -> str | None:
def _git_tag_file(repo_path: Path, tag: str, relative_path: str) -> str | None: def _git_tag_file(repo_path: Path, tag: str, relative_path: str) -> str | None:
repo_path = repo_path.resolve()
if not (repo_path / ".git").exists(): if not (repo_path / ".git").exists():
return None return None
result = subprocess.run( result = subprocess.run(
["/usr/bin/git", "-C", str(repo_path), "show", f"{tag}:{relative_path}"], [
"/usr/bin/git",
"-c",
"core.hooksPath=/dev/null",
"-c",
f"safe.directory={repo_path}",
"-C",
str(repo_path),
"cat-file",
"blob",
f"refs/tags/{tag}^{{commit}}:{relative_path}",
],
check=False, check=False,
text=True, text=True,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,

View File

@@ -85,8 +85,11 @@ PACKAGE_MODULE_REPOS=(
"$PARENT/govoplan-certificates" "$PARENT/govoplan-certificates"
"$PARENT/govoplan-committee" "$PARENT/govoplan-committee"
"$PARENT/govoplan-consultation" "$PARENT/govoplan-consultation"
"$PARENT/govoplan-connectors"
"$PARENT/govoplan-contracts" "$PARENT/govoplan-contracts"
"$PARENT/govoplan-dashboard" "$PARENT/govoplan-dashboard"
"$PARENT/govoplan-dataflow"
"$PARENT/govoplan-datasources"
"$PARENT/govoplan-docs" "$PARENT/govoplan-docs"
"$PARENT/govoplan-facilities" "$PARENT/govoplan-facilities"
"$PARENT/govoplan-files" "$PARENT/govoplan-files"
@@ -96,7 +99,7 @@ PACKAGE_MODULE_REPOS=(
"$PARENT/govoplan-identity" "$PARENT/govoplan-identity"
"$PARENT/govoplan-idm" "$PARENT/govoplan-idm"
"$PARENT/govoplan-inspections" "$PARENT/govoplan-inspections"
"$PARENT/govoplan-issue-reporting" "$PARENT/govoplan-tickets"
"$PARENT/govoplan-learning" "$PARENT/govoplan-learning"
"$PARENT/govoplan-mail" "$PARENT/govoplan-mail"
"$PARENT/govoplan-ops" "$PARENT/govoplan-ops"
@@ -111,12 +114,14 @@ PACKAGE_MODULE_REPOS=(
"$PARENT/govoplan-tenancy" "$PARENT/govoplan-tenancy"
"$PARENT/govoplan-transparency" "$PARENT/govoplan-transparency"
"$PARENT/govoplan-evaluation" "$PARENT/govoplan-evaluation"
"$PARENT/govoplan-views"
"$PARENT/govoplan-workflow"
) )
TAG_ONLY_MODULE_REPOS=( TAG_ONLY_MODULE_REPOS=(
"$PARENT/govoplan-addresses" "$PARENT/govoplan-addresses"
"$PARENT/govoplan-appointments" "$PARENT/govoplan-appointments"
"$PARENT/govoplan-cases" "$PARENT/govoplan-cases"
"$PARENT/govoplan-connectors" "$PARENT/govoplan-projects"
"$PARENT/govoplan-dms" "$PARENT/govoplan-dms"
"$PARENT/govoplan-dist-lists" "$PARENT/govoplan-dist-lists"
"$PARENT/govoplan-erp" "$PARENT/govoplan-erp"
@@ -132,10 +137,10 @@ TAG_ONLY_MODULE_REPOS=(
"$PARENT/govoplan-rest" "$PARENT/govoplan-rest"
"$PARENT/govoplan-scheduling" "$PARENT/govoplan-scheduling"
"$PARENT/govoplan-search" "$PARENT/govoplan-search"
"$PARENT/govoplan-wiki"
"$PARENT/govoplan-soap" "$PARENT/govoplan-soap"
"$PARENT/govoplan-tasks" "$PARENT/govoplan-tasks"
"$PARENT/govoplan-templates" "$PARENT/govoplan-templates"
"$PARENT/govoplan-workflow"
"$PARENT/govoplan-xoev" "$PARENT/govoplan-xoev"
"$PARENT/govoplan-xrechnung" "$PARENT/govoplan-xrechnung"
"$PARENT/govoplan-xta-osci" "$PARENT/govoplan-xta-osci"

View File

@@ -76,7 +76,7 @@ def main() -> int:
) )
selective.add_argument("--public-base-url", default="https://govoplan.add-ideas.de") selective.add_argument("--public-base-url", default="https://govoplan.add-ideas.de")
selective.add_argument( selective.add_argument(
"--repository-base", default="git+ssh://git@git.add-ideas.de/add-ideas" "--repository-base", default="git+ssh://git@git.add-ideas.de/GovOPlaN"
) )
selective.add_argument( selective.add_argument(
"--source-remote", "--source-remote",

View File

@@ -68,7 +68,7 @@ class CatalogCandidateRequest(BaseModel):
expires_days: int = 90 expires_days: int = 90
sequence: int | None = None sequence: int | None = None
public_base_url: str = "https://govoplan.add-ideas.de" public_base_url: str = "https://govoplan.add-ideas.de"
repository_base: str = "git+ssh://git@git.add-ideas.de/add-ideas" repository_base: str = "git+ssh://git@git.add-ideas.de/GovOPlaN"
source_remote: str = "origin" source_remote: str = "origin"
check_public: bool = True check_public: bool = True

View File

@@ -39,6 +39,7 @@
padding: 18px 28px; padding: 18px 28px;
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 10;
} }
header h1 { header h1 {
@@ -281,7 +282,7 @@
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
min-width: 1220px; min-width: 960px;
} }
th, th,

View File

@@ -16,7 +16,9 @@ REGISTERED_TRANSPORT = "registered"
PUBLIC_HTTPS_TRANSPORT = "public-https" PUBLIC_HTTPS_TRANSPORT = "public-https"
GITEA_SSH_PREFIX = "git@git.add-ideas.de:" GITEA_SSH_PREFIX = "git@git.add-ideas.de:"
GITEA_HTTPS_PREFIX = "https://git.add-ideas.de/" GITEA_HTTPS_PREFIX = "https://git.add-ideas.de/"
GITEA_REPOSITORY_PATH = re.compile(r"add-ideas/[a-z0-9][a-z0-9-]*[.]git") GITEA_REPOSITORY_PATH = re.compile(
r"(?:GovOPlaN|add-ideas)/[a-z0-9][a-z0-9-]*[.]git"
)
def parse_args(argv: list[str] | None = None) -> argparse.Namespace: def parse_args(argv: list[str] | None = None) -> argparse.Namespace:

View File

@@ -12,6 +12,7 @@ from pathlib import Path
import shlex import shlex
import subprocess import subprocess
import sys import sys
import tomllib
from typing import Any from typing import Any
META_ROOT = Path(__file__).resolve().parents[2] META_ROOT = Path(__file__).resolve().parents[2]
@@ -49,6 +50,23 @@ class InstallPlan:
warnings: tuple[str, ...] = () warnings: tuple[str, ...] = ()
@dataclass(frozen=True, slots=True)
class LocalInstallationExpectation:
requirement: RequirementEntry
distribution: str
entry_points: tuple[tuple[str, str, str], ...]
@dataclass(frozen=True, slots=True)
class EnvironmentValidation:
stale_requirements: tuple[RequirementEntry, ...] = ()
issues: tuple[str, ...] = ()
@property
def current(self) -> bool:
return not self.stale_requirements
def main() -> int: def main() -> int:
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument( parser.add_argument(
@@ -85,8 +103,9 @@ def main() -> int:
fingerprint = build_fingerprint(requirements=requirements, python=python, local_requirements=local_requirements) fingerprint = build_fingerprint(requirements=requirements, python=python, local_requirements=local_requirements)
previous = load_stamp(stamp) previous = load_stamp(stamp)
current = previous.get("digest") == fingerprint["digest"] current = previous.get("digest") == fingerprint["digest"]
environment = validate_local_installations(python, local_requirements)
if current and not args.force: if current and environment.current and not args.force:
payload = stamp_payload( payload = stamp_payload(
requirements=requirements, requirements=requirements,
python=python, python=python,
@@ -107,20 +126,31 @@ def main() -> int:
print(f"Python environment is current for {requirements.name}.") print(f"Python environment is current for {requirements.name}.")
return 0 return 0
print(f"Python environment metadata is stale for {requirements.name}.") if current and not environment.current:
print(f"Python environment metadata is current for {requirements.name}, but installed packages need repair.")
else:
print(f"Python environment metadata is stale for {requirements.name}.")
print(f"Tracked inputs: {len(fingerprint['inputs'])}") print(f"Tracked inputs: {len(fingerprint['inputs'])}")
for issue in environment.issues:
print(f"warning: {issue}", file=sys.stderr)
if args.check: if args.check:
return 1 return 1
plan = build_install_plan( if current and environment.stale_requirements and not args.force:
previous=previous, plan = build_environment_repair_plan(
fingerprint=fingerprint, python=python,
requirements=requirements, stale_requirements=environment.stale_requirements,
python=python, )
local_requirements=local_requirements, else:
force=args.force, plan = build_install_plan(
) previous=previous,
fingerprint=fingerprint,
requirements=requirements,
python=python,
local_requirements=local_requirements,
force=args.force,
)
print(f"{plan.mode}: {plan.reason}") print(f"{plan.mode}: {plan.reason}")
for warning in plan.warnings: for warning in plan.warnings:
@@ -141,6 +171,13 @@ def main() -> int:
for command in commands: for command in commands:
subprocess.run(command, check=True) subprocess.run(command, check=True)
repaired_environment = validate_local_installations(python, local_requirements)
if not repaired_environment.current:
for issue in repaired_environment.issues:
print(f"error: {issue}", file=sys.stderr)
print("Python environment synchronization did not install all local package metadata.", file=sys.stderr)
return 2
write_stamp( write_stamp(
stamp, stamp,
stamp_payload( stamp_payload(
@@ -297,6 +334,127 @@ def build_install_plan(
) )
def build_environment_repair_plan(
*,
python: str,
stale_requirements: tuple[RequirementEntry, ...],
) -> InstallPlan:
command = (
python,
"-m",
"pip",
"install",
*(
argument
for requirement in stale_requirements
for argument in requirement.install_args
),
)
return InstallPlan(
"Selective Python environment repair",
f"reinstalling {len(stale_requirements)} missing or stale local distribution(s).",
(command,),
)
def validate_local_installations(
python: str,
local_requirements: tuple[RequirementEntry, ...],
) -> EnvironmentValidation:
expectations = local_installation_expectations(local_requirements)
if not expectations:
return EnvironmentValidation()
probe_input = [
{
"key": expectation.requirement.key,
"distribution": expectation.distribution,
"entry_points": [
{"group": group, "name": name, "value": value}
for group, name, value in expectation.entry_points
],
}
for expectation in expectations
]
try:
result = subprocess.run(
(python, "-c", _LOCAL_INSTALLATION_PROBE),
check=False,
text=True,
input=json.dumps(probe_input),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=20,
)
except (OSError, subprocess.TimeoutExpired) as exc:
return EnvironmentValidation(
stale_requirements=tuple(item.requirement for item in expectations),
issues=(f"could not inspect installed local distributions ({type(exc).__name__})",),
)
if result.returncode != 0:
detail = result.stderr.strip() or result.stdout.strip() or "installation probe failed"
return EnvironmentValidation(
stale_requirements=tuple(item.requirement for item in expectations),
issues=(f"could not inspect installed local distributions: {detail}",),
)
try:
payload = json.loads(result.stdout)
except json.JSONDecodeError:
return EnvironmentValidation(
stale_requirements=tuple(item.requirement for item in expectations),
issues=("installed local distribution probe returned invalid JSON",),
)
raw_issues = payload.get("issues") if isinstance(payload, dict) else None
issues_by_key = raw_issues if isinstance(raw_issues, dict) else {}
stale = tuple(
expectation.requirement
for expectation in expectations
if expectation.requirement.key in issues_by_key
)
issues = tuple(
f"{expectation.distribution}: {issues_by_key[expectation.requirement.key]}"
for expectation in expectations
if expectation.requirement.key in issues_by_key
)
return EnvironmentValidation(stale_requirements=stale, issues=issues)
def local_installation_expectations(
local_requirements: tuple[RequirementEntry, ...],
) -> tuple[LocalInstallationExpectation, ...]:
expectations: list[LocalInstallationExpectation] = []
for requirement in local_requirements:
if requirement.pyproject is None:
continue
with Path(requirement.pyproject).open("rb") as handle:
payload = tomllib.load(handle)
project = payload.get("project")
if not isinstance(project, dict):
continue
distribution = project.get("name")
if not isinstance(distribution, str) or not distribution.strip():
continue
declared_entry_points: list[tuple[str, str, str]] = []
entry_point_groups = project.get("entry-points")
if isinstance(entry_point_groups, dict):
for group, entries in entry_point_groups.items():
if not isinstance(group, str) or not isinstance(entries, dict):
continue
for name, value in entries.items():
if isinstance(name, str) and isinstance(value, str):
declared_entry_points.append((group, name, value))
expectations.append(
LocalInstallationExpectation(
requirement=requirement,
distribution=distribution,
entry_points=tuple(sorted(declared_entry_points)),
)
)
return tuple(expectations)
def requirement_delta_plan(previous_entries: object, current_entries: tuple[RequirementEntry, ...]) -> InstallPlan | None: def requirement_delta_plan(previous_entries: object, current_entries: tuple[RequirementEntry, ...]) -> InstallPlan | None:
parsed_previous = previous_requirement_entries(previous_entries) parsed_previous = previous_requirement_entries(previous_entries)
if parsed_previous is None: if parsed_previous is None:
@@ -481,5 +639,35 @@ def format_command(command: tuple[str, ...]) -> str:
return " ".join(shlex.quote(item) for item in command) return " ".join(shlex.quote(item) for item in command)
_LOCAL_INSTALLATION_PROBE = """
import importlib.metadata
import json
import sys
expectations = json.load(sys.stdin)
issues = {}
for expectation in expectations:
key = expectation["key"]
distribution_name = expectation["distribution"]
try:
distribution = importlib.metadata.distribution(distribution_name)
except importlib.metadata.PackageNotFoundError:
issues[key] = "distribution is not installed"
continue
installed = {
(entry_point.group, entry_point.name): entry_point.value
for entry_point in distribution.entry_points
}
mismatches = []
for entry_point in expectation["entry_points"]:
identity = (entry_point["group"], entry_point["name"])
if installed.get(identity) != entry_point["value"]:
mismatches.append(f"{identity[0]}:{identity[1]}")
if mismatches:
issues[key] = "entry-point metadata is missing or stale: " + ", ".join(mismatches)
print(json.dumps({"issues": issues}, sort_keys=True))
"""
if __name__ == "__main__": if __name__ == "__main__":
raise SystemExit(main()) raise SystemExit(main())