Add a summary job that runs after all CI jobs (even on failure),
parses the pytest/vitest/Playwright JUnit reports plus coverage.xml,
and renders a consolidated Markdown summary to $GITHUB_STEP_SUMMARY:
job results, test counts, durations, backend coverage, failure
details, and the artifact inventory. Enable a Playwright JUnit
reporter and include it in the playwright-report artifact.
Starting core with docker-compose.prod.yml also started the bundled
postgres container, whose 'postgres' network alias shadowed a shared
instance of the same name on the same Docker network — the live site
silently talked to an empty local database instead of DATABASE_HOST.
- Base file: postgres profiles are now [all, postgres] (core dropped);
the dev override unions 'core' back in, so the documented dev
workflow (make up, --profile core) is unchanged
- migrate's postgres dependency is required: false — still health-gated
when the bundled container is active, skipped when profile-inactive
- prod override documents the behavior; opt in explicitly with
--profile postgres (or --profile all) for a bundled production DB
- Docs: database.md/deployment.md/configuration.md/README/.env.example
updated; upgrading.md gains a one-time cleanup runbook (full down to
remove the previously started hub-postgres container)
- /health/ready returns 503 (not 200) when not ready; body no longer leaks
raw backend error text (api + web tiers)
- SYSTEM_MAINTENANCE now gates the web /api/* proxy (503 MAINTENANCE)
instead of only hiding the SPA UI
- channel mutations invalidate messages/packets/packet_groups/dashboard
caches so visibility/key changes apply immediately (was stale up to 1h)
- async DB engine: pool_size/max_overflow/pre_ping matching the sync
engine (survives Postgres restarts); adispose() awaited from the API
lifespan (async connections were leaked by the sync dispose)
- webhooks: permanent 4xx (except 408/429) fail fast instead of retrying;
dispatch queue bounded at 1000 with drop-oldest + warning; shutdown
logs dropped events; matches_event evaluated once
- SPA: apiGet/apiPostForm redirect to OIDC login on 401; chart date
labels and route timestamps rendered in UTC/configured timezone
- e2e compose collector healthcheck uses 'meshcore-hub health collector'
(image has no pgrep)
- docs/upgrading.md: fold unreleased notes into v0.18.0 and document the
above
- api CLI single-process path now passes spam_detection_enabled /
spam_score_threshold to create_app() so the messages hide-filter
actually runs in the default (Docker) deployment
- OIDC role names (oidc_role_admin/operator/member/test) moved from
WebSettings to CommonSettings and wired onto API app.state; the API
tier previously matched hardcoded role names, ignoring customized
OIDC_ROLE_* config in authz, channel visibility, and tag/adoption
checks (oidc_roles_claim stays web-only)
- /metrics handler is now sync def so collection runs in the
threadpool instead of blocking the event loop; hoisted fragile
in-loop imports
- evaluate_route_history gates max_path_length on the packet's true
hop count (RawPacket.path_len) instead of the prefix-filtered hop
list, so history day buckets agree with snapshot evaluation
- reload_keys clears the decode cache: negatively-cached packets for
a newly added channel key were permanently dropped by the normalizer
Also documents 'sudo npx playwright install-deps chromium' in the
AGENTS.md e2e setup.
- /map/data: resolve viewer OIDC session and apply the endpoint access
mapping before fetching profiles; serve Cache-Control: private when
OIDC is enabled (payload varies by viewer)
- /user/profile/me + /user/profile/{id}: trust X-User-Id only with a
valid API key (new optional_user_identity dependency); closes the
OIDC subject-identifier leak via forged headers
- OIDC_SESSION_SECRET is now mandatory when OIDC is enabled (hard fail,
no insecure-dev-secret fallback)
- /metrics denies unauthenticated access when no API read key is set;
new METRICS_PUBLIC opt-in (Settings, CLI, compose, env factory)
- CORS: unset CORS_ORIGINS keeps wildcard compat but drops
allow_credentials (invalid combination in browsers) + warns
- Web tier: SecurityHeadersMiddleware (nosniff, X-Frame-Options DENY,
Referrer-Policy, Permissions-Policy) + nonce-based CSP; nonce applied
to spa.html inline scripts; WEB_SECURITY_HEADERS kill switch and
WEB_CSP_EXTRA for custom tile hosts; API emits nosniff
Breaking changes documented in docs/upgrading.md.
The map endpoint fetched a single limit=500 page (the API's hard max,
sorted last_seen desc), so networks with more than 500 nodes silently
hid stale/offline nodes from the unfiltered map — including adopted
infrastructure, which still appeared under the adopted_by filter
because that filter narrows the query before the limit applies.
Follow offset/total pagination via a new _fetch_all_pages helper (40
pages / 20k nodes safety cap, partial results on later-page failure)
for both /api/v1/nodes and /api/v1/user/profiles, and report the
API-reported total in debug.total_nodes.