diff --git a/.env.example b/.env.example index f33b12a..fb708a1 100644 --- a/.env.example +++ b/.env.example @@ -2,17 +2,24 @@ # Copy to a deployment-local .env or secret store. Do not commit populated secrets. 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 MASTER_KEY_B64= 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_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,poll,scheduling,connectors,datasources,dataflow,workflow,views,notifications,docs,ops +ENABLED_MODULES=tenancy,organizations,identity,idm,access,admin,dashboard,policy,audit,files,mail,campaigns,calendar,poll,scheduling,connectors,datasources,dataflow,workflow,views,postbox,notifications,docs,ops CELERY_ENABLED=true 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 GOVOPLAN_CONNECTOR_ALLOW_PRIVATE_NETWORKS=false @@ -24,10 +31,14 @@ GOVOPLAN_HTTP_MAX_REQUEST_BODY_BYTES=536870912 GOVOPLAN_HTTP_HSTS_SECONDS=31536000 AUTH_LOGIN_THROTTLE_ENABLED=true +AUTH_ACTIVITY_TOUCH_INTERVAL_SECONDS=300 AUTH_LOGIN_THROTTLE_IDENTITY_LIMIT=10 AUTH_LOGIN_THROTTLE_CLIENT_LIMIT=100 AUTH_LOGIN_THROTTLE_WINDOW_SECONDS=900 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 GOVOPLAN_TRUSTED_HOSTS=govoplan.example.org diff --git a/docs/gitea-labels.json b/docs/gitea-labels.json index 02ebb56..5529974 100644 --- a/docs/gitea-labels.json +++ b/docs/gitea-labels.json @@ -149,6 +149,12 @@ "description": "GovOPlaN core runner, shared primitives, shell, or extension points.", "exclusive": false }, + { + "name": "module/dashboard", + "color": "1d76db", + "description": "GovOPlaN Dashboard module behavior or integration.", + "exclusive": false + }, { "name": "module/dataflow", "color": "1d76db", @@ -167,6 +173,12 @@ "description": "GovOPlaN Dms module behavior or integration.", "exclusive": false }, + { + "name": "module/docs", + "color": "c5def5", + "description": "GovOPlaN Docs module behavior or integration.", + "exclusive": false + }, { "name": "module/dist-lists", "color": "0e8a16", diff --git a/requirements-dev.txt b/requirements-dev.txt index 50c1aa9..933b7f9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -24,6 +24,7 @@ -e ../govoplan-dataflow -e ../govoplan-workflow -e ../govoplan-views +-e ../govoplan-postbox -e ../govoplan-poll -e ../govoplan-scheduling -e ../govoplan-notifications @@ -37,6 +38,7 @@ idna>=3.15 jsonschema>=4,<5 pip>=26.1.2 pip-audit>=2.9,<3 -pytest>=8,<9 +pytest>=9.0.3,<10 +pygments>=2.20,<3 python-multipart>=0.0.31 ruff>=0.14,<1 diff --git a/requirements-release-tests.txt b/requirements-release-tests.txt index 74f3cdf..ca122a1 100644 --- a/requirements-release-tests.txt +++ b/requirements-release-tests.txt @@ -1,4 +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>=8,<9 +pytest>=9.0.3,<10 +pygments>=2.20,<3 diff --git a/tools/checks/check-focused.sh b/tools/checks/check-focused.sh index b763017..c540080 100644 --- a/tools/checks/check-focused.sh +++ b/tools/checks/check-focused.sh @@ -74,6 +74,8 @@ PY "$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 tests.test_api_smoke.ApiSmokeTests.test_mailbox_message_listing_reports_total_count @@ -92,6 +94,12 @@ cd /mnt/DATA/git/govoplan-datasources/webui 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 "$NPM" run test:mail-ui diff --git a/tools/checks/check-security-audit.sh b/tools/checks/check-security-audit.sh index b063922..facdedb 100644 --- a/tools/checks/check-security-audit.sh +++ b/tools/checks/check-security-audit.sh @@ -496,7 +496,12 @@ run_bandit() { fi if [[ "${#PY_TEST_ROOTS[@]}" -gt 0 ]]; then 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=$? [[ "$test_status" -le 1 ]] || status=2 fi @@ -512,7 +517,11 @@ run_ruff_security() { fi if [[ "${#PY_TEST_ROOTS[@]}" -gt 0 ]]; then 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=$? [[ "$test_status" -le 1 ]] || status=2 fi @@ -582,14 +591,32 @@ run_trivy() { run_pip_audit_manifests() { local status=0 for repo in "${REPOS[@]}"; do - [[ -f "$repo/requirements.txt" ]] || continue - local name - name="$(safe_name "$repo")" - prepare_machine_report "$REPORTS_DIR/pip-audit-$name.json" || return 2 - # 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 requirements.txt --progress-spinner off --format json --output "$REPORTS_DIR/pip-audit-$name.json") - accumulate_exit_status status "$?" + local repo_name + repo_name="$(safe_name "$repo")" + while IFS= read -r -d '' requirements_file; do + local manifest_name report_path + manifest_name="$(safe_name "$requirements_file")" + report_path="$REPORTS_DIR/pip-audit-$repo_name-$manifest_name.json" + prepare_machine_report "$report_path" || return 2 + # 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 return "$status" } @@ -597,12 +624,38 @@ run_pip_audit_manifests() { run_npm_audit_manifests() { local status=0 for repo in "${REPOS[@]}"; do - [[ -f "$repo/webui/package-lock.json" ]] || continue - local name - name="$(safe_name "$repo")" - prepare_machine_report "$REPORTS_DIR/npm-audit-$name.json" || return 2 - (cd "$repo/webui" && npm audit --omit=dev --json > "$REPORTS_DIR/npm-audit-$name.json") - accumulate_exit_status status "$?" + local repo_name + repo_name="$(safe_name "$repo")" + while IFS= read -r -d '' lock_file; do + local lock_dir lock_name runtime_report all_report + lock_dir="$(dirname "$lock_file")" + 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 return "$status" }