chore(release): document hardening and verify complete source package composition
This commit is contained in:
@@ -86,6 +86,16 @@ contract. The coordinated release synchronizes `peerDependencies` and
|
|||||||
tag, then synchronizes each lockfile root from the final package metadata. A
|
tag, then synchronizes each lockfile root from the final package metadata. A
|
||||||
distinct root package remains independent.
|
distinct root package remains independent.
|
||||||
|
|
||||||
|
Every module referenced by Core's Git-based `package.release.json` must expose
|
||||||
|
its WebUI identity at the repository root, including matching peer requirements
|
||||||
|
and `webui/`-prefixed entry exports (also CSS subpaths). npm resolves Git
|
||||||
|
dependencies from the repository root, while the native-package workflow packs
|
||||||
|
`webui/`; success in one path does not verify the other. Run
|
||||||
|
`python tools/checks/check-webui-package-facades.py` after changing either
|
||||||
|
manifest or the release composition. The focused gate also runs this check.
|
||||||
|
Adding or correcting a facade in an already published repository requires a
|
||||||
|
new patch tag; never repair an existing immutable tag in place.
|
||||||
|
|
||||||
It builds one wheel and, where applicable, one npm tarball. The workflow records
|
It builds one wheel and, where applicable, one npm tarball. The workflow records
|
||||||
the source tag, source commit, filename, size, and SHA-256 in
|
the source tag, source commit, filename, size, and SHA-256 in
|
||||||
`package-artifacts.json` before publishing. Gitea rejects a second upload of the
|
`package-artifacts.json` before publishing. Gitea rejects a second upload of the
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# GovOPlaN 0.1.45 — usability, reliability and security hardening
|
||||||
|
|
||||||
|
Release coordination: [GovOPlaN #51](https://git.add-ideas.de/GovOPlaN/govoplan/issues/51).
|
||||||
|
The exact independently versioned composition is recorded in
|
||||||
|
`packages/govoplan-meta/pyproject.toml`; unchanged modules retain their versions.
|
||||||
|
This source release does not by itself establish a deployed or independently
|
||||||
|
approved production environment. Package, signed catalog and runtime publication
|
||||||
|
results are recorded separately in the coordination issue.
|
||||||
|
|
||||||
|
## Runtime publication hold
|
||||||
|
|
||||||
|
The [runtime image audit](../security/RUNTIME_IMAGE_AUDIT_2026-09-08.md) completed
|
||||||
|
eleven registry-only amd64 scans, but found unresolved vulnerabilities and
|
||||||
|
inventory gaps. Runtime publication remains held separately from this source
|
||||||
|
release. Patch-only image updates are insufficient; maintained minor-line
|
||||||
|
changes, narrowly evidenced finding decisions, arm64/final-layer scans and
|
||||||
|
deployment checks remain necessary. No audited candidate was automatically
|
||||||
|
adopted and no image was executed during those scans.
|
||||||
|
The remaining gates are tracked in
|
||||||
|
[GovOPlaN #52](https://git.add-ideas.de/GovOPlaN/govoplan/issues/52).
|
||||||
|
|
||||||
|
## Included changes
|
||||||
|
|
||||||
|
- Shared page/action placement, reusable navigation grouping/editing, table and
|
||||||
|
dialog sizing, field alignment, multi-select filters and predictable tree
|
||||||
|
selection. Files, Mail, Search, Notifications and domain pages use the same
|
||||||
|
contracts, with browser regression coverage.
|
||||||
|
- Campaign draft saving and independent Mail/ZIP-policy repair, persistent and
|
||||||
|
bulk message review, clearer delivery eligibility, bounded configurable
|
||||||
|
synchronous delivery, guarded workerless recovery, lightweight SMTP/IMAP
|
||||||
|
progress, reused IMAP connections and recipient-complete reporting.
|
||||||
|
- Files archive staging/reuse, unpacking previously uploaded archives, numeric
|
||||||
|
progress and bounded traversal. Optional native archive acceleration retains
|
||||||
|
the same validation rules; portable fallbacks remain available.
|
||||||
|
- Mail credential references and IMAP folder-name decoding; help topics can be
|
||||||
|
found by area and tags without expanding every occurrence of the same topic.
|
||||||
|
- Authentication provenance/scope and browser-cache hardening, patched rich-text
|
||||||
|
dependencies, spreadsheet/archive/template/Dataflow resource limits, batched
|
||||||
|
Docs/Notifications queries and safe Reporting bind names. See the
|
||||||
|
[security/performance review](../security/SECURITY_PERFORMANCE_REVIEW_2026-09-08.md)
|
||||||
|
for measurements, test evidence and remaining limitations.
|
||||||
|
- A deterministic governance-journey clock fixture, fresh-process Campaign
|
||||||
|
import coverage, and a new Cases patch aligning its root npm facade with its
|
||||||
|
Python/WebUI package. Historical published tags are not rewritten.
|
||||||
|
- Git-root WebUI package facades are aligned with their owning packages, with
|
||||||
|
a cross-composition parity check. Tasks is included in default module
|
||||||
|
discovery; it remains subject to enabled modules and normal permissions.
|
||||||
|
|
||||||
|
## Upgrade and verification
|
||||||
|
|
||||||
|
Back up the database and file storage before upgrading. Apply the complete
|
||||||
|
selected migration graph before starting the new API/workers. This release
|
||||||
|
includes additive repair migrations `c58a2d7e9f10` (Core ownership history) and
|
||||||
|
`d8f1b4e7a0c3` (Access external-function mappings), plus Campaign delivery-state
|
||||||
|
migrations. Existing business evidence is retained; a schema downgrade is not
|
||||||
|
a substitute for a reviewed backup/restore plan. Restart API and worker
|
||||||
|
processes together after upgrading their matching packages.
|
||||||
|
|
||||||
|
Updated UI consumers require Core 0.1.45 where they use its new shared contracts.
|
||||||
|
Tenant keys that previously relied on unintended system permissions/wildcards
|
||||||
|
must be corrected; the release does not preserve that unsafe behavior. Extremely
|
||||||
|
sparse spreadsheets, oversized generated output and excessive archive paths
|
||||||
|
can now fail early with a diagnostic.
|
||||||
|
|
||||||
|
For archive staging across multiple hosts, provide shared POSIX storage with
|
||||||
|
working locks or sticky routing. Background delivery still needs configured
|
||||||
|
workers; increasing the synchronous limit does not create a worker or guarantee
|
||||||
|
delivery after a process failure. An unknown SMTP outcome must be reconciled,
|
||||||
|
not automatically resent.
|
||||||
|
|
||||||
|
After deployment, manually verify login/logout and least-privilege API keys,
|
||||||
|
Campaign Settings and independent Mail/ZIP saves, archive upload/unpack,
|
||||||
|
recipient-complete reports, and SMTP/IMAP progress with an explicitly approved
|
||||||
|
test mailbox. No release verification sends real campaign mail automatically.
|
||||||
|
|
||||||
|
Hard process isolation, forced-password-change/recovery enforcement, bounded
|
||||||
|
Xrechnung subprocess output and large-history pagination remain separate open
|
||||||
|
issues. This release is not a claim that all security or performance debt is
|
||||||
|
resolved. Production-image scans and multi-host evidence must refer to the
|
||||||
|
actual signed runtime being deployed.
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
# Runtime image candidate audit — 8 September 2026
|
||||||
|
|
||||||
|
Release coordination: [GovOPlaN #51](https://git.add-ideas.de/GovOPlaN/govoplan/issues/51).
|
||||||
|
Canonical remediation: [GovOPlaN #52](https://git.add-ideas.de/GovOPlaN/govoplan/issues/52).
|
||||||
|
This follow-up to the [source security/performance review](SECURITY_PERFORMANCE_REVIEW_2026-09-08.md)
|
||||||
|
records registry-only scans of nine proposed runtime dependencies and two
|
||||||
|
same-minor patch candidates. **Runtime publication is held:** patch-only updates
|
||||||
|
do not resolve the baseline. Source/package publication is a separate outcome.
|
||||||
|
No images were executed, rebuilt, selected for CI, or published by this audit.
|
||||||
|
|
||||||
|
## Method and reproducible evidence
|
||||||
|
|
||||||
|
Official Trivy **0.74.0** was installed only in a private local task directory,
|
||||||
|
without sudo or Docker access. Its Linux-64bit release archive matched both the
|
||||||
|
official checksums file and GitHub release asset metadata:
|
||||||
|
|
||||||
|
- Archive SHA256: `2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a`.
|
||||||
|
- Checksums-file SHA256: `bc701c3c3ee8b9acbea2c23257e41381e3854888f51281616a6ba5dc96963821`.
|
||||||
|
- Vulnerability database schema 2, updated `2026-09-07T19:06:01.154199452Z`,
|
||||||
|
downloaded from `mirror.gcr.io/aquasec/trivy-db:2`.
|
||||||
|
- Scan flags: `--image-src remote --platform linux/amd64 --scanners vuln
|
||||||
|
--format json --no-progress --timeout 8m --max-image-size 2GB --exit-code 0`.
|
||||||
|
Findings were counted from validated JSON; exit zero did not mean clean.
|
||||||
|
- Existing Docker credentials were not read; no private keys or secrets were
|
||||||
|
used. Checksums over official HTTPS metadata were verified, not independent
|
||||||
|
Sigstore signatures. See the [official release](https://github.com/aquasecurity/trivy/releases/tag/v0.74.0)
|
||||||
|
and [registry-only scan documentation](https://trivy.dev/docs/latest/target/container_image/).
|
||||||
|
|
||||||
|
Raw evidence is retained locally, not committed:
|
||||||
|
`/home/zemion/.cache/govoplan-trivy-remote.yKZgjDOg/scan/`.
|
||||||
|
It contains eleven `reports/*-amd64.json` reports/logs, scanner scripts,
|
||||||
|
`patch-candidate-inspection.json`, exact successor registry indices, and
|
||||||
|
`evidence-checksums.json`. Summary SHA256 values:
|
||||||
|
|
||||||
|
- `summary.json`: `f2785a731d637452ab9c0b1f5399772c0f8828a63ca83d5fa7496abdad1c757a`.
|
||||||
|
- `patch-summary.json`: `b3fc6273fcdad98864040ccdf3477ecf379afd46e9f94444b1f2910f48c1d85b`.
|
||||||
|
|
||||||
|
All eleven executions succeeded without timeout/rate-limit failure. Initial
|
||||||
|
summary fields distinguish `scan_execution_complete: true` from
|
||||||
|
`coverage_complete: false`: Garage has no detectable package inventory.
|
||||||
|
Checksums preserve evidence identity, not indefinite storage availability.
|
||||||
|
|
||||||
|
## Exact requested pins and results
|
||||||
|
|
||||||
|
All references below use `docker.io/`. Counts are package-vulnerability records,
|
||||||
|
not distinct CVEs or confirmed exploitable application defects. A vulnerability
|
||||||
|
can appear against several installed packages. Unfixed/unknown records remain.
|
||||||
|
|
||||||
|
| Image tag | Exact index SHA256 | Critical / High / Medium / Low / Unknown | Fixable C/H |
|
||||||
|
| --- | --- | --- | ---: |
|
||||||
|
| `library/python:3.12-slim-bookworm` | `782412e85d0f0984994c290652577d4018aff08145c85b262bb63dc0c7522254` | 5 / 55 / 102 / 103 / 5 | 0 |
|
||||||
|
| `library/postgres:16-alpine` | `cf78e76683b9ca8c5733cbbdce6c9262b45b6767934dd0a95e671f9a0fc20685` | 1 / 30 / 28 / 14 / 1 | 31 |
|
||||||
|
| `library/redis:7-alpine` | `ff02b58f971e7d7d156a1267e283fcbbeee91773b6aa36c49dac28ecfe28eadf` | 0 / 0 / 0 / 0 / 0 | 0 |
|
||||||
|
| `nginxinc/nginx-unprivileged:1.29-alpine` | `0c79d56aee561a1d81c63f00eee5fb5fe29279560cdc55e91425133104c7fbe6` | 0 / 33 / 74 / 37 / 20 | 33 |
|
||||||
|
| `library/haproxy:3.2.21-alpine` | `66e25cc9a8332635f4e897f7f4b1e5622c25f09f0ee23cddc6ce9bdb3a24772a` | 0 / 2 / 6 / 12 / 0 | 2 |
|
||||||
|
| `library/caddy:2.10.2-alpine` | `4c6e91c6ed0e2fa03efd5b44747b625fec79bc9cd06ac5235a779726618e530d` | 7 / 75 / 67 / 37 / 4 | 82 |
|
||||||
|
| `dxflrs/garage:v2.3.0` | `866bd13ed2038ba7e7190e840482bc27234c4afaf77be8cfa439ae088c1e4690` | **Unknown: no inventory** | — |
|
||||||
|
| `greenmail/standalone:2.1.9` | `3ac5a83dd6727cf95e4d50e18907fb8ee7bbf5f67e8534714dee2fb1b5b2e1d4` | 0 / 0 / 116 / 35 / 0 | 0 |
|
||||||
|
| `tonistiigi/binfmt:qemu-v10.2.3-68` | `400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0` | 0 / 9 / 2 / 1 / 1 | 9 |
|
||||||
|
|
||||||
|
## Patch-only options and limits
|
||||||
|
|
||||||
|
Complete publisher tag listings were inspected for nginx 1.29, Caddy 2.10,
|
||||||
|
HAProxy 3.2, GreenMail 2.1 and binfmt qemu10.2. Two newer candidates were
|
||||||
|
scanned; their registry index bytes matched both registry and publisher digests,
|
||||||
|
and contained amd64 and arm64 manifests:
|
||||||
|
|
||||||
|
- `library/haproxy:3.2.23-alpine@sha256:6343ce34a132a5dceaa24767d739df2bd519f8f7c1079ae39e4821334e8eb42e`:
|
||||||
|
same Alpine 3.24.1, 24 detected OS packages, zero reported findings. This is
|
||||||
|
a useful candidate, not a completed compatibility test or application audit.
|
||||||
|
- `greenmail/standalone:2.1.13@sha256:3df66b7edd01c8a301343ca5e3601d8674760d4708655573560c24745e624fb2`:
|
||||||
|
upstream changes Ubuntu 22.04 to Debian 13.6; **3 C / 80 H / 98 M / 85 L /
|
||||||
|
5 unknown**, 30 fixable C/H records. Not selected as a no-base-change update.
|
||||||
|
- nginx's newest matching Alpine patch is already 1.29.8 at the scanned pin;
|
||||||
|
Caddy 2.10 remains 2.10.2; binfmt qemu10.2 remains 10.2.3-68. No newer matching
|
||||||
|
publisher images were found. The current [official Caddy image catalogue](https://raw.githubusercontent.com/docker-library/official-images/master/library/caddy)
|
||||||
|
uses 2.11.4; switching minor lines requires new scans and compatibility checks.
|
||||||
|
|
||||||
|
Priority remediation: Caddy's own seven HIGH records require fixes through
|
||||||
|
2.11.4, with additional bundled Go/library fixes that must be re-scanned;
|
||||||
|
nginx's packages include curl/libcurl fixes through 8.22.0-r0, OpenSSL 3.5.8-r0,
|
||||||
|
c-ares 1.34.8-r0, expat 2.8.1-r0 and libuuid 2.41.6-r1. PostgreSQL's OS records
|
||||||
|
require OpenSSL 3.5.8-r0 and libuuid 2.42.3-r1; its CRITICAL plus 21 HIGH Go
|
||||||
|
records concern the **gosu helper**, not PostgreSQL server code. binfmt's nine
|
||||||
|
HIGH records concern its Go 1.26.4 build, with fixes through 1.26.6. Package
|
||||||
|
presence does not establish vulnerable-symbol reachability. No unscanned tag
|
||||||
|
is claimed to meet every fix requirement.
|
||||||
|
|
||||||
|
## Python triage and coverage caveats
|
||||||
|
|
||||||
|
Python image metadata identifies CPython 3.12.14, but Trivy inventories only
|
||||||
|
Debian packages and pip, **not CPython/stdlib**. All 60 C/H records concern
|
||||||
|
Debian packages: 21 CVEs, 50 `affected` records, 9 `fix_deferred`, 1
|
||||||
|
`will_not_fix`, without a recorded fixed Bookworm version. Five util-linux CVEs
|
||||||
|
repeat across eight binary packages. These remain installed; they are not all
|
||||||
|
removed build dependencies. Pip 25.0.1 separately has five MEDIUM/one LOW
|
||||||
|
records, with fixes through 26.2.0; it is install tooling, and the API image uses
|
||||||
|
an offline `--no-index` wheelhouse rather than an arbitrary package index.
|
||||||
|
|
||||||
|
Narrow triage examples, **not blanket exemptions**:
|
||||||
|
|
||||||
|
- Debian states [CVE-2023-45853](https://security-tracker.debian.org/tracker/CVE-2023-45853)
|
||||||
|
does not affect the built Bookworm zlib binaries because vulnerable minizip
|
||||||
|
code is not included. Other bundled minizip implementations are separate.
|
||||||
|
- [CVE-2026-8376](https://security-tracker.debian.org/tracker/CVE-2026-8376)
|
||||||
|
explicitly requires 32-bit Perl; this scan targets amd64.
|
||||||
|
- [CVE-2025-7458](https://security-tracker.debian.org/tracker/CVE-2025-7458)
|
||||||
|
requires crafted arbitrary SQLite SQL; the managed runtime uses PostgreSQL,
|
||||||
|
but alternate SQLite use must be reviewed.
|
||||||
|
- Perl's regex and Archive::Tar records need exact binary/module applicability
|
||||||
|
checks; vendor-deferred status alone is not a finding dismissal.
|
||||||
|
|
||||||
|
Only amd64 was scanned. arm64, newly built GovOPlaN API/Web layers and optional
|
||||||
|
dependency combinations remain unverified. Garage has no inventory; Redis,
|
||||||
|
HAProxy and PostgreSQL source-built executables, CPython and QEMU static
|
||||||
|
binaries need supplemental SBOM/source coverage. Zero detected OS findings is
|
||||||
|
not zero application vulnerabilities. Trivy also lacks Alpine 3.24 EOL metadata
|
||||||
|
and nginx CVE-2026-80256 detail; unknowns are retained. There were no runtime,
|
||||||
|
exploitability, secret, misconfiguration, malware or signature-policy checks.
|
||||||
|
|
||||||
|
Before lifting the runtime hold: approve and test maintained image-line changes
|
||||||
|
where necessary, fix or narrowly disposition findings with evidence, close
|
||||||
|
inventory gaps, scan both architectures and final runtime layers, then run
|
||||||
|
deployment/ingress smoke checks. Do not silently change base OS, use unpinned
|
||||||
|
`latest`, rebuild third-party images, or accept all HIGH/CRITICAL findings.
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
# Security and performance review — 8 September 2026
|
||||||
|
|
||||||
|
Coordinated status: [Core #296](https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/296).
|
||||||
|
This records a workspace-wide automated scan, targeted manual boundary review,
|
||||||
|
and a verified implementation pass. It is not a penetration test, an exhaustive
|
||||||
|
line-by-line review, or a security certification. The audit was completed on
|
||||||
|
local, unpublished changes, preserving existing worktree changes. Subsequent
|
||||||
|
release preparation/publication is tracked in
|
||||||
|
[GovOPlaN #51](https://git.add-ideas.de/GovOPlaN/govoplan/issues/51) and the
|
||||||
|
[0.1.45 release notes](../releases/0.1.45.md).
|
||||||
|
|
||||||
|
## Implemented findings
|
||||||
|
|
||||||
|
| Area | Finding and change | Evidence / ownership |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Authentication — high | Preserve service-account provenance and current scope ceilings instead of recalculating them as ordinary membership permissions. Tenant API keys cannot retain canonical system permissions or unsafe wildcard grants. | Previously failing isolated regressions; [Access #21](https://git.add-ideas.de/GovOPlaN/govoplan-access/issues/21). |
|
||||||
|
| Authentication — medium | Warm-cache API keys must follow the same explicit-header credential rules as cold authentication. A session cookie cannot turn an API key into a session credential. | Regression covering source-dependent authentication. |
|
||||||
|
| Browser authority/cache — medium | Clear reusable data on auth changes and write settlement; fence late 200/304 writes and obsolete 401 side effects. Honor server no-store/no-cache and explicit fresh-read requests. Interactive login/logout remove retained automation keys that could shadow cookie-session identity. | 23 real-client regressions. Unchanged settings keep their object identity, preventing profile-fetch loops. Core `docs/API_CLIENT_CACHE_CONTRACT.md`; owning Access EN/DE session/field documentation. |
|
||||||
|
| Spreadsheet resource exhaustion | Validate actual XLSX coordinates before openpyxl traversal; ignore misleading declared dimensions; count blank row gaps toward the existing limits. | [Connectors #18](https://git.add-ideas.de/GovOPlaN/govoplan-connectors/issues/18), 13 tests and 2 subtests. |
|
||||||
|
| Template resource exhaustion | Enforce the existing 5 MiB output budget during substitution and item construction, including UTF-8, HTML escaping and separators. | [Templates #7](https://git.add-ideas.de/GovOPlaN/govoplan-templates/issues/7), full 20 tests; independent 3,000-case valid-output comparison. |
|
||||||
|
| Archive resource exhaustion | Inspect regular TAR member limits before traversing payloads. Limit archive paths to 4,096 UTF-8 bytes / 128 components and count derived directories against entry limits. | [Files #46](https://git.add-ideas.de/GovOPlaN/govoplan-files/issues/46), 55 archive and 15 documentation tests. Extension-header decoding still needs stronger isolation. |
|
||||||
|
| Dataflow resource exhaustion | Reject LPAD/RPAD target lengths above the existing 1,000,000-byte preview budget before fill evaluation/allocation. Preserve final serialized-byte checks. | [Dataflow #22](https://git.add-ideas.de/GovOPlaN/govoplan-dataflow/issues/22), full 104 tests and 39 subtests; 7 new guard tests independently rerun. |
|
||||||
|
| Docs performance / defense in depth | Batch revision reads per request, avoid loading pending draft bodies for readers, and validate tenant/entry/publication consistency while retaining owner/audience checks. | [Docs #22](https://git.add-ideas.de/GovOPlaN/govoplan-docs/issues/22), full 39 tests. |
|
||||||
|
| Notifications performance / defense in depth | Batch delivery-attempt loading while preserving recipient checks and rejecting inconsistent attempt references, including already-loaded relationships. | [Notifications #6](https://git.add-ideas.de/GovOPlaN/govoplan-notifications/issues/6), full 23 tests. |
|
||||||
|
| Session-list performance | Apply active/expiry predicates and the existing 100-row cap in SQL, before loading session history. | Query-shape regression in Access. |
|
||||||
|
| Reporting correctness | Use structural bind-name suffixes for recursive calculated measures, preserving valid dotted/hyphenated public keys and parameter uniqueness. | [Reporting #10](https://git.add-ideas.de/GovOPlaN/govoplan-reporting/issues/10), full 29 tests. |
|
||||||
|
| Audit hygiene | Redact Gitleaks logs and machine reports on current, history and legacy scanner paths. | 13 audit-wrapper tests enforce the flag. |
|
||||||
|
|
||||||
|
All changed module workflows/limits have owning EN/DE DocumentationTopic updates.
|
||||||
|
Independent review found no concrete regression in the backend changes.
|
||||||
|
|
||||||
|
## Measured performance changes
|
||||||
|
|
||||||
|
These are SQL-query counts in isolated 40-item fixtures, not production latency
|
||||||
|
or throughput claims. Authorization is still evaluated for each request.
|
||||||
|
|
||||||
|
| Projection | Before | After |
|
||||||
|
| --- | ---: | ---: |
|
||||||
|
| Docs reader entries | 41 SELECTs | 2 SELECTs |
|
||||||
|
| Docs editor entries | 81 SELECTs | 2 SELECTs |
|
||||||
|
| Notification list with attempts | 41 SELECTs | 2 SELECTs |
|
||||||
|
|
||||||
|
The Docs 401-entry batching regression uses 3 SELECTs. Resource guards reject
|
||||||
|
oversized work before the formerly expensive allocation/traversal. This does
|
||||||
|
not make every legitimate upload or campaign faster. Honoring no-cache can
|
||||||
|
increase server validation requests; ETags still avoid retransmitting unchanged
|
||||||
|
bodies. That authorization/freshness trade-off is deliberate.
|
||||||
|
|
||||||
|
The original audit snapshot measured 517,380 initial JavaScript bytes and
|
||||||
|
164,119 gzip bytes. Release preparation's pure-defaults split reduces this to
|
||||||
|
516,730 initial bytes and 163,908 gzip bytes. Restoring the missing Tasks
|
||||||
|
descriptor then measures 516,987 initial / 163,976 gzip bytes with all 46 module
|
||||||
|
descriptors lazy, within the unchanged 524,288 / 164,128 caps. The gzip margin is still small; future
|
||||||
|
startup work should reduce eager dependencies, not raise the cap automatically.
|
||||||
|
The full 209-case browser suite passed before the split, followed by 13 focused
|
||||||
|
browser checks after it. Radon recorded 238 rank-D-or-higher entries; complexity
|
||||||
|
is a review-priority signal, not a performance measurement.
|
||||||
|
|
||||||
|
## Dependency remediation
|
||||||
|
|
||||||
|
Core's full npm audit went from 30 affected package entries to zero. Most initial
|
||||||
|
entries were transitive effects of the same Tiptap advisory, not 30 independent
|
||||||
|
application exploits. The website went from two affected entries to zero; both
|
||||||
|
Mail lockfiles also report zero.
|
||||||
|
|
||||||
|
- Tiptap packages are aligned at 3.31.3, with direct minimum ranges raised to
|
||||||
|
3.30.4 in both development and release manifests, with a parity regression.
|
||||||
|
Added an actual installed-library prototype-attribute regression for
|
||||||
|
the [maintainer's security advisory](https://github.com/ueberdosis/tiptap/security/advisories/GHSA-cp6q-959q-f8rh).
|
||||||
|
- Core now resolves xmldom 0.9.12, browserslist 4.28.9 and nanoid 3.3.18.
|
||||||
|
The website's affected browserslist/nanoid dependencies are patched too.
|
||||||
|
- Development/audit requirements now require pip >=26.2; the local development
|
||||||
|
environment uses 26.2.1. The installed audit originally flagged
|
||||||
|
[CVE-2026-13346](https://github.com/advisories/GHSA-qwm4-qh6w-59xr), requiring an
|
||||||
|
attacker-controlled package index. This is an installation-tool vulnerability,
|
||||||
|
not evidence of an exposed application endpoint.
|
||||||
|
|
||||||
|
The final installed Python audit enumerated 188 distributions: 137 were
|
||||||
|
auditable with zero known vulnerabilities, and 51 local distributions were not
|
||||||
|
available in PyPI. Those skips are covered by source review, not by a claim of
|
||||||
|
dependency-advisory coverage. Production images and every optional dependency
|
||||||
|
combination were not independently resolved or scanned.
|
||||||
|
|
||||||
|
## Scan coverage and limitations
|
||||||
|
|
||||||
|
Evidence directory:
|
||||||
|
`/mnt/DATA/tmp/govoplan-security-performance-20260908-gsk8jn/`.
|
||||||
|
|
||||||
|
The final `final-quick/manifest.json` captures 79 repositories, tool versions,
|
||||||
|
start/end repository fingerprints, report checksums, 168 report artifacts and
|
||||||
|
163 validated JSON/SARIF reports. It records an unchanged workspace, complete
|
||||||
|
coverage for its four required scanners, no execution errors and no missing
|
||||||
|
reports. It ran in report-only mode: exit zero does **not** mean zero warnings.
|
||||||
|
|
||||||
|
- Final production Bandit: 447,008 Python lines; 67 warnings (63 low, 4 medium),
|
||||||
|
no high findings. Ruff security rules: 54 warnings. SQL-construction warnings
|
||||||
|
were reviewed against identifier/operator validation and bound values in
|
||||||
|
DuckDB/Reporting; no injection fix was warranted there. XML import warnings
|
||||||
|
were checked: feed/BPMN input parsing uses defusedxml; stdlib imports support
|
||||||
|
types/output construction. Operator-owned fenced-run argv is not a public
|
||||||
|
arbitrary-command endpoint. Xrechnung output buffering remains a follow-up.
|
||||||
|
Assertions and error-swallowing markers remain review/maintenance warnings,
|
||||||
|
not proof that all such code is harmless.
|
||||||
|
- Final local Semgrep rules: no findings. The broader OWASP-rule pass applied
|
||||||
|
272 rules to 4,169 tracked targets. Its seven warnings recommended weakening
|
||||||
|
owner-only 0700 permissions; they were rejected as false positives. One
|
||||||
|
Calendar rule timeout was rerun with a 60-second budget: zero findings/errors.
|
||||||
|
Bash and conformance TypeScript checks passed despite two scanner-specific
|
||||||
|
parser limitations. Ignored/dependency/generated paths are not a complete
|
||||||
|
line-by-line source audit.
|
||||||
|
- Gitleaks: 79 Git histories plus 79 worktrees, 158 redacted reports, zero
|
||||||
|
detected secrets. This does not establish that deployed credentials are safe
|
||||||
|
or that formerly exposed credentials have been rotated.
|
||||||
|
- Tool versions included Semgrep 1.176.1, Bandit 1.9.4, Ruff 0.15.21 and
|
||||||
|
Gitleaks 8.30.1. The downloaded Gitleaks binary archive matched the official
|
||||||
|
release SHA-256 before execution.
|
||||||
|
- The containerized full-toolbox path could not access Docker's daemon. Its
|
||||||
|
full-mode Trivy/misconfiguration and additional OSV scans were **not** run.
|
||||||
|
A subsequent [registry-only runtime image audit](RUNTIME_IMAGE_AUDIT_2026-09-08.md)
|
||||||
|
successfully scanned nine pinned candidates and two same-minor successors
|
||||||
|
for amd64 without Docker. It found unresolved vulnerabilities and inventory
|
||||||
|
gaps; runtime publication is held. This does not complete full-toolbox,
|
||||||
|
arm64, final-runtime-image or deployment coverage.
|
||||||
|
|
||||||
|
No live application probes, database changes, file operations, mail sends,
|
||||||
|
IMAP appends, imports, notification delivery, deployments, commits or pushes
|
||||||
|
were performed. Browser tests used isolated mocked fixtures. Package installs,
|
||||||
|
builds and temporary audit-tool installation were local development operations.
|
||||||
|
|
||||||
|
## Verification and remaining work
|
||||||
|
|
||||||
|
- 209/209 browser conformance tests pass; production Core/website builds,
|
||||||
|
conformance TypeScript, 24 Core client/dependency regressions, 4 real-client
|
||||||
|
Files reload checks, and 72/72 manifest checks pass.
|
||||||
|
- Access's full 91-test suite passed before the final documentation-only update;
|
||||||
|
the final documentation suite passed all 4 tests. Other module counts appear
|
||||||
|
above. The new authentication/resource tests include demonstrated pre-fix
|
||||||
|
failures rather than only structural assertions.
|
||||||
|
- The original focused workspace run stopped at the institutional
|
||||||
|
governance/Portal fixture (`tests/test_institutional_governance_journey.py:223`,
|
||||||
|
`IndexError`). Release preparation fixes its mixed clocks using the existing
|
||||||
|
temporal context, retaining validity-boundary exclusions; 7 journey tests and
|
||||||
|
ambient-year checks pass. Tracked in
|
||||||
|
[Meta #50](https://git.add-ideas.de/GovOPlaN/govoplan/issues/50).
|
||||||
|
- Campaign's apparent host-path issue was ruled out by existing tracked
|
||||||
|
API/build/snapshot guards and 11 passing tests under normal initialization.
|
||||||
|
Release preparation fixes the standalone import cycle through a deferred
|
||||||
|
resolver import without changing validation rules. Fresh-process coverage,
|
||||||
|
all 11 path tests and Campaign's full 611-test suite pass.
|
||||||
|
|
||||||
|
Next coordinated work:
|
||||||
|
|
||||||
|
1. [Hard resource isolation — Core #297](https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/297):
|
||||||
|
regex CPU, aggregate allocation, TAR extension metadata, bounded workers and
|
||||||
|
cancellation, followed by production-like concurrent load tests.
|
||||||
|
2. [Forced password change/recovery — Access #22](https://git.add-ideas.de/GovOPlaN/govoplan-access/issues/22):
|
||||||
|
the current flag is advisory only. Do not enable enforcement without a usable
|
||||||
|
local-password/recovery flow and external-provider rules.
|
||||||
|
3. [Bound subprocess output — Xrechnung #2](https://git.add-ideas.de/GovOPlaN/govoplan-xrechnung/issues/2):
|
||||||
|
enforce the existing 2 MiB limit while draining stdout/stderr, not afterwards.
|
||||||
|
4. [Workflow revision batching/history projection — Workflow Engine #3](https://git.add-ideas.de/GovOPlaN/govoplan-workflow-engine/issues/3):
|
||||||
|
batch evidence lookups; separately define explicit history pagination and
|
||||||
|
authorized-total semantics. Docs/notification history volumes also remain.
|
||||||
|
5. Resolve the [runtime image audit](RUNTIME_IMAGE_AUDIT_2026-09-08.md) findings
|
||||||
|
tracked in [Meta #52](https://git.add-ideas.de/GovOPlaN/govoplan/issues/52)
|
||||||
|
and coverage gaps before lifting its publication hold; complete deployment
|
||||||
|
audits, review exposed development credentials and worker quotas, and
|
||||||
|
benchmark realistic tenant sizes/concurrency. The sanctions
|
||||||
|
transport's fixed HTTPS/redirect allowlist is not a demonstrated arbitrary-URL
|
||||||
|
issue, but migration to Core's pinned egress transport remains desirable.
|
||||||
|
|
||||||
|
Operational compatibility: tenant keys relying on accidental system/wildcard
|
||||||
|
permissions must be corrected rather than weakening the guard. Extreme sparse
|
||||||
|
spreadsheets, overly deep/long archive paths and oversized padding intermediates
|
||||||
|
can now fail early with diagnostics. No stored documents or configurations were
|
||||||
|
deleted or silently migrated.
|
||||||
@@ -13,7 +13,7 @@ dependencies = [
|
|||||||
"govoplan-core[server]==0.1.45",
|
"govoplan-core[server]==0.1.45",
|
||||||
"govoplan-tenancy==0.1.22",
|
"govoplan-tenancy==0.1.22",
|
||||||
"govoplan-organizations==0.1.21",
|
"govoplan-organizations==0.1.21",
|
||||||
"govoplan-identity==0.1.20",
|
"govoplan-identity==0.1.21",
|
||||||
"govoplan-idm==0.1.25",
|
"govoplan-idm==0.1.25",
|
||||||
"govoplan-access==0.1.25",
|
"govoplan-access==0.1.25",
|
||||||
"govoplan-admin==0.1.23",
|
"govoplan-admin==0.1.23",
|
||||||
@@ -31,7 +31,7 @@ dependencies = [
|
|||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
full = [
|
full = [
|
||||||
"govoplan-addresses==0.1.22",
|
"govoplan-addresses==0.1.22",
|
||||||
"govoplan-approvals==0.1.20",
|
"govoplan-approvals==0.1.21",
|
||||||
"govoplan-assets==0.1.20",
|
"govoplan-assets==0.1.20",
|
||||||
"govoplan-booking==0.1.20",
|
"govoplan-booking==0.1.20",
|
||||||
"govoplan-cases==0.1.24",
|
"govoplan-cases==0.1.24",
|
||||||
@@ -45,30 +45,30 @@ full = [
|
|||||||
"govoplan-decisions==0.1.19",
|
"govoplan-decisions==0.1.19",
|
||||||
"govoplan-dist-lists==0.1.21",
|
"govoplan-dist-lists==0.1.21",
|
||||||
"govoplan-dms==0.1.20",
|
"govoplan-dms==0.1.20",
|
||||||
"govoplan-encryption==0.1.19",
|
"govoplan-encryption==0.1.20",
|
||||||
"govoplan-erp==0.1.20",
|
"govoplan-erp==0.1.20",
|
||||||
"govoplan-evaluation==0.1.20",
|
"govoplan-evaluation==0.1.20",
|
||||||
"govoplan-facilities==0.1.20",
|
"govoplan-facilities==0.1.20",
|
||||||
"govoplan-fit-connect==0.1.20",
|
"govoplan-fit-connect==0.1.20",
|
||||||
"govoplan-forms==0.1.22",
|
"govoplan-forms==0.1.23",
|
||||||
"govoplan-forms-runtime==0.1.20",
|
"govoplan-forms-runtime==0.1.21",
|
||||||
"govoplan-grants==0.1.20",
|
"govoplan-grants==0.1.20",
|
||||||
"govoplan-helpdesk==0.1.21",
|
"govoplan-helpdesk==0.1.21",
|
||||||
"govoplan-identity-trust==0.1.20",
|
"govoplan-identity-trust==0.1.21",
|
||||||
"govoplan-inspections==0.1.20",
|
"govoplan-inspections==0.1.20",
|
||||||
"govoplan-learning==0.1.20",
|
"govoplan-learning==0.1.20",
|
||||||
"govoplan-mandates==0.1.19",
|
"govoplan-mandates==0.1.19",
|
||||||
"govoplan-notifications==0.1.20",
|
"govoplan-notifications==0.1.20",
|
||||||
"govoplan-parties==0.1.19",
|
"govoplan-parties==0.1.19",
|
||||||
"govoplan-payments==0.1.21",
|
"govoplan-payments==0.1.22",
|
||||||
"govoplan-permits==0.1.20",
|
"govoplan-permits==0.1.20",
|
||||||
"govoplan-poll==0.1.20",
|
"govoplan-poll==0.1.20",
|
||||||
"govoplan-portal==0.1.21",
|
"govoplan-portal==0.1.22",
|
||||||
"govoplan-postbox==0.1.22",
|
"govoplan-postbox==0.1.23",
|
||||||
"govoplan-procurement==0.1.20",
|
"govoplan-procurement==0.1.20",
|
||||||
"govoplan-projects==0.1.19",
|
"govoplan-projects==0.1.20",
|
||||||
"govoplan-quick-access==0.1.20",
|
"govoplan-quick-access==0.1.21",
|
||||||
"govoplan-records==0.1.23",
|
"govoplan-records==0.1.24",
|
||||||
"govoplan-reporting==0.1.21",
|
"govoplan-reporting==0.1.21",
|
||||||
"govoplan-resources==0.1.20",
|
"govoplan-resources==0.1.20",
|
||||||
"govoplan-rest==0.1.19",
|
"govoplan-rest==0.1.19",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
../govoplan-core[server]
|
../govoplan-core[server]
|
||||||
govoplan-tenancy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-tenancy.git@v0.1.22
|
govoplan-tenancy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-tenancy.git@v0.1.22
|
||||||
govoplan-organizations @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git@v0.1.21
|
govoplan-organizations @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git@v0.1.21
|
||||||
govoplan-identity @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-identity.git@v0.1.20
|
govoplan-identity @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-identity.git@v0.1.21
|
||||||
govoplan-idm @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git@v0.1.25
|
govoplan-idm @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git@v0.1.25
|
||||||
govoplan-access @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git@v0.1.25
|
govoplan-access @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git@v0.1.25
|
||||||
govoplan-admin @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git@v0.1.23
|
govoplan-admin @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git@v0.1.23
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import runpy
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
META_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
CHECK = runpy.run_path(str(META_ROOT / "tools/checks/check-webui-package-facades.py"))
|
||||||
|
|
||||||
|
|
||||||
|
class WebuiPackageFacadeTests(unittest.TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.temporary = tempfile.TemporaryDirectory()
|
||||||
|
self.addCleanup(self.temporary.cleanup)
|
||||||
|
self.repository = Path(self.temporary.name) / "govoplan-example"
|
||||||
|
source = self.repository / "webui/src"
|
||||||
|
source.mkdir(parents=True)
|
||||||
|
(source / "index.ts").write_text("export {};\n", encoding="utf-8")
|
||||||
|
(source / "styles.css").write_text(":root {}\n", encoding="utf-8")
|
||||||
|
self.webui = {
|
||||||
|
"name": "@govoplan/example-webui", "version": "0.1.2", "type": "module",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"exports": {".": {"import": "./src/index.ts"}, "./styles.css": "./src/styles.css"},
|
||||||
|
"peerDependencies": {"@govoplan/core-webui": "^0.1.45"},
|
||||||
|
"peerDependenciesMeta": {"@govoplan/core-webui": {"optional": True}},
|
||||||
|
}
|
||||||
|
self.root = {**self.webui, **{
|
||||||
|
field: CHECK["prefixed_entries"](self.webui[field])
|
||||||
|
for field in CHECK["ENTRY_FIELDS"] if field in self.webui
|
||||||
|
}}
|
||||||
|
self.write_manifests()
|
||||||
|
|
||||||
|
def write_manifests(self) -> None:
|
||||||
|
(self.repository / "webui/package.json").write_text(json.dumps(self.webui), encoding="utf-8")
|
||||||
|
(self.repository / "package.json").write_text(json.dumps(self.root), encoding="utf-8")
|
||||||
|
|
||||||
|
def issues(self) -> list[str]:
|
||||||
|
return CHECK["facade_issues"](self.repository, package_name="@govoplan/example-webui")
|
||||||
|
|
||||||
|
def test_matching_conditional_and_css_entries_are_accepted(self) -> None:
|
||||||
|
self.assertEqual([], self.issues())
|
||||||
|
|
||||||
|
def test_missing_root_or_generic_package_is_rejected(self) -> None:
|
||||||
|
(self.repository / "package.json").unlink()
|
||||||
|
self.assertIn("cannot read", " ".join(self.issues()))
|
||||||
|
self.root = {"name": "@govoplan/example", "version": "0.1.2"}
|
||||||
|
self.write_manifests()
|
||||||
|
self.assertIn("root name differs", " ".join(self.issues()))
|
||||||
|
self.assertIn("no WebUI entry point", " ".join(self.issues()))
|
||||||
|
|
||||||
|
def test_peer_drift_or_missing_entry_is_rejected(self) -> None:
|
||||||
|
self.root["peerDependencies"] = {"@govoplan/core-webui": "^9.0.0"}
|
||||||
|
self.write_manifests()
|
||||||
|
self.assertIn("peerDependencies differs", " ".join(self.issues()))
|
||||||
|
(self.repository / "webui/src/styles.css").unlink()
|
||||||
|
self.assertIn("missing exports entry", " ".join(self.issues()))
|
||||||
|
|
||||||
|
def test_entry_cannot_escape_webui_even_if_it_exists(self) -> None:
|
||||||
|
(self.repository / "outside.ts").write_text("export {};\n", encoding="utf-8")
|
||||||
|
self.root["main"] = "webui/../outside.ts"
|
||||||
|
self.write_manifests()
|
||||||
|
self.assertIn("escapes webui/", " ".join(self.issues()))
|
||||||
|
|
||||||
|
def test_release_composition_checks_only_declared_module_sources(self) -> None:
|
||||||
|
core = self.repository.parent / "govoplan-core/webui"
|
||||||
|
core.mkdir(parents=True)
|
||||||
|
(core / "package.release.json").write_text(json.dumps({"dependencies": {
|
||||||
|
"react": "19.2.7",
|
||||||
|
"@govoplan/example-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-example.git#v0.1.2",
|
||||||
|
}}), encoding="utf-8")
|
||||||
|
self.assertEqual((1, []), CHECK["check_composition"](self.repository.parent))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -47,6 +47,8 @@ cd "$META_ROOT"
|
|||||||
"$PYTHON" tools/inventory/platform-interface-inventory.py --strict-declarations --strict-endpoints
|
"$PYTHON" tools/inventory/platform-interface-inventory.py --strict-declarations --strict-endpoints
|
||||||
"$PYTHON" tools/repo/sync-module-package-workflows.py --check
|
"$PYTHON" tools/repo/sync-module-package-workflows.py --check
|
||||||
"$PYTHON" tools/release/generate-developer-meta-package.py --check
|
"$PYTHON" tools/release/generate-developer-meta-package.py --check
|
||||||
|
"$PYTHON" tools/checks/check-webui-package-facades.py
|
||||||
|
"$PYTHON" -m unittest tests.test_webui_package_facades
|
||||||
"$PYTHON" -m unittest tests.test_module_package_workflows tests.test_package_registry_release
|
"$PYTHON" -m unittest tests.test_module_package_workflows tests.test_package_registry_release
|
||||||
"$PYTHON" -m unittest tests.test_deployment_installer
|
"$PYTHON" -m unittest tests.test_deployment_installer
|
||||||
"$PYTHON" -m unittest tests.test_capability_fit_evidence
|
"$PYTHON" -m unittest tests.test_capability_fit_evidence
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check that release Git dependencies expose their owning WebUI package."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
META_ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
GIT_REPOSITORY = re.compile(r"/(govoplan-[a-z0-9-]+)\.git#v[^/]+$")
|
||||||
|
PARITY_FIELDS = (
|
||||||
|
"name", "version", "type", "dependencies", "optionalDependencies",
|
||||||
|
"peerDependencies", "peerDependenciesMeta",
|
||||||
|
)
|
||||||
|
ENTRY_FIELDS = ("main", "module", "types", "exports")
|
||||||
|
|
||||||
|
|
||||||
|
def prefixed_entries(value: object) -> object:
|
||||||
|
if isinstance(value, str):
|
||||||
|
return "./webui/" + value[2:] if value.startswith("./") else "webui/" + value
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return {key: prefixed_entries(item) for key, item in value.items()}
|
||||||
|
if isinstance(value, list):
|
||||||
|
return [prefixed_entries(item) for item in value]
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def entry_paths(value: object) -> list[str]:
|
||||||
|
if isinstance(value, str):
|
||||||
|
return [value]
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return [path for item in value.values() for path in entry_paths(item)]
|
||||||
|
if isinstance(value, list):
|
||||||
|
return [path for item in value for path in entry_paths(item)]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def facade_issues(repository: Path, *, package_name: str) -> list[str]:
|
||||||
|
issues: list[str] = []
|
||||||
|
try:
|
||||||
|
root = json.loads((repository / "package.json").read_text(encoding="utf-8"))
|
||||||
|
webui = json.loads((repository / "webui/package.json").read_text(encoding="utf-8"))
|
||||||
|
except (OSError, ValueError) as exc:
|
||||||
|
return [f"{repository.name}: cannot read package facades: {exc}"]
|
||||||
|
if not isinstance(root, dict) or not isinstance(webui, dict):
|
||||||
|
return [f"{repository.name}: package manifests must be JSON objects"]
|
||||||
|
if webui.get("name") != package_name:
|
||||||
|
issues.append(f"{repository.name}: WebUI name does not match {package_name}")
|
||||||
|
for field in PARITY_FIELDS:
|
||||||
|
if root.get(field) != webui.get(field):
|
||||||
|
issues.append(f"{repository.name}: root {field} differs from owning WebUI package")
|
||||||
|
for field in ENTRY_FIELDS:
|
||||||
|
expected = prefixed_entries(webui.get(field))
|
||||||
|
if root.get(field) != expected:
|
||||||
|
issues.append(f"{repository.name}: root {field} must target the corresponding webui/ entry")
|
||||||
|
for entry in entry_paths(root.get(field)):
|
||||||
|
path = repository / entry
|
||||||
|
if not path.resolve().is_relative_to((repository / "webui").resolve()):
|
||||||
|
issues.append(f"{repository.name}: {field} entry escapes webui/: {entry}")
|
||||||
|
elif "*" not in entry and not path.is_file():
|
||||||
|
issues.append(f"{repository.name}: missing {field} entry: {entry}")
|
||||||
|
if not root.get("exports") and not root.get("main"):
|
||||||
|
issues.append(f"{repository.name}: root package has no WebUI entry point")
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
def check_composition(workspace: Path, *, core_root: Path | None = None) -> tuple[int, list[str]]:
|
||||||
|
core = core_root or workspace / "govoplan-core"
|
||||||
|
release = json.loads((core / "webui/package.release.json").read_text(encoding="utf-8"))
|
||||||
|
checked = 0
|
||||||
|
issues: list[str] = []
|
||||||
|
for name, reference in release.get("dependencies", {}).items():
|
||||||
|
if not name.startswith("@govoplan/"):
|
||||||
|
continue
|
||||||
|
match = GIT_REPOSITORY.search(reference) if isinstance(reference, str) else None
|
||||||
|
if match is None:
|
||||||
|
issues.append(f"{name}: release dependency is not a versioned GovOPlaN Git source")
|
||||||
|
continue
|
||||||
|
checked += 1
|
||||||
|
issues.extend(facade_issues(workspace / match.group(1), package_name=name))
|
||||||
|
return checked, issues
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--workspace-root", type=Path, default=META_ROOT.parent)
|
||||||
|
parser.add_argument("--core-root", type=Path)
|
||||||
|
args = parser.parse_args()
|
||||||
|
checked, issues = check_composition(args.workspace_root, core_root=args.core_root)
|
||||||
|
if issues:
|
||||||
|
print("\n".join(issues))
|
||||||
|
return 1
|
||||||
|
print(f"Release WebUI package facade checks passed for {checked} Git dependencies")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user