From c92844e06ebff1c6204e236017a2e82c57ee873b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Leite?= Date: Wed, 29 Jul 2026 22:02:37 -0300 Subject: [PATCH 1/2] docs: refresh architecture and roadmaps for v0.2.51 --- book/src/images/roadmap.svg | 10 ++++----- book/src/roadmap.md | 24 ++++++++++++++++----- docs/ARCHITECTURE.md | 42 ++++++++++++++++++++++++++++++------- docs/ROADMAP.md | 7 ++++++- docs/images/roadmap.svg | 10 ++++----- 5 files changed, 69 insertions(+), 24 deletions(-) diff --git a/book/src/images/roadmap.svg b/book/src/images/roadmap.svg index f6e00b27..7346abb2 100644 --- a/book/src/images/roadmap.svg +++ b/book/src/images/roadmap.svg @@ -1,8 +1,8 @@ - + Ruscker — roadmap - @@ -64,12 +64,12 @@ v0.1.1 - - v0.2.x · latest — full audit (v0.2.5) + the admin design-handoff sprint (Appearance, Apps, Media) + Post-phase polish · admin UX · scoped Editors · schedules · security + reliability @@ -77,7 +77,7 @@ Phase 8 · External auth - OIDC · SAML · LDAP · per-app ACLs (RBAC already shipped) + OIDC · SAML · LDAP (scoped RBAC + per-app ACLs already shipped) planned · optional diff --git a/book/src/roadmap.md b/book/src/roadmap.md index c2173be4..4b3413b8 100644 --- a/book/src/roadmap.md +++ b/book/src/roadmap.md @@ -8,7 +8,7 @@ live on a real deployment before the next. Phase 8 (external auth) is the main optional, demand-driven work left. For what changed in each release, see the [release notes](./news.md). -![Roadmap timeline: phases 0–7 are done — 0 to 5 shipped in v0.1.0 (scaffolding, landing page, persistence + admin CRUD, proxy + Docker backend, monitoring dashboard, production polish); phase 6 (multi-host scheduling, app visibility, sub-path mounting) across v0.1.1–v0.1.2; phase 7 (HA / multi-instance) in v0.1.1. Post-1.0 point releases add demo forks, URL-rewrite modernization, unified credentials, the media library, portal logos, admin UX polish, and security + perf fixes. Phase 8 (external auth) is planned and optional.](images/roadmap.svg) +![Roadmap timeline: phases 0–7 are done — 0 to 5 shipped in v0.1.0 (scaffolding, landing page, persistence + admin CRUD, proxy + Docker backend, monitoring dashboard, production polish); phase 6 (multi-host scheduling, app visibility, sub-path mounting) across v0.1.1–v0.1.2; phase 7 (HA / multi-instance) in v0.1.1. Post-phase releases add admin and portal UX, group-scoped Editor delegation, scheduled jobs, viewer-local admin timestamps, timezone-aware cron, and security + reliability fixes. Phase 8 contains only the planned, optional external identity-provider integrations.](images/roadmap.svg) ## Shipped @@ -181,6 +181,20 @@ error + exit code instead of a generic "no port binding". Plus catalog cards expand their description on hover, and the dashboard's stop/restart actions show in-progress feedback. +**Operations and delegated administration.** The Admin panel now runs +scheduled, run-to-completion ETL jobs with history and alerting. Cron is +evaluated on the IANA wall clock selected by `server.timezone`, preserving +UTC when the setting is absent; the Schedules page uses that same clock. +Other persisted admin timestamps stay in UTC and are converted in the +browser to each viewer's timezone. + +Editor delegation is row-scoped from current group memberships across +Applications, Users, and Groups. Open apps remain available to every +Editor, Media is shared, and an out-of-scope target returns `404`. +Admins and the break-glass token remain unrestricted. Account deletion, +CSV user import, MFA reset, and creating, renaming, or deleting a group +remain Admin-only. + ## Planned (optional) Demand-driven — Ruscker is complete and useful without it. @@ -206,10 +220,10 @@ Tracked in the GitHub issues; picked up as real usage asks for them: long-session flap has since shipped.)* ### Phase 8 — External auth -OIDC (Keycloak / Auth0 / Google), SAML, and LDAP, plus per-app access -lists ("only group X can use this app"). The coarse Viewer / Editor / -Admin RBAC already shipped in Phase 5; this is the federated-identity -and fine-grained-ACL layer on top. +OIDC (Keycloak / Auth0 / Google), SAML, and LDAP. Per-app access lists +and the Viewer / Editor / Admin model already ship, including +group-scoped Editor delegation across Applications, Users, and Groups. +This phase is only the federated-identity layer on top. ## Explicitly out of scope diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e204edf9..b267171d 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -102,6 +102,15 @@ per request. User/group/profile mutations invalidate the cache with a generation counter, preventing an in-flight stale read from repopulating revoked identity data. +Admin-panel authorization keeps identity and row scope separate: +`crate::auth` answers who the caller is and which role the session carries; +`scope::EditorScope` answers which Application, User, and Group rows that +caller may see or change. It refetches an Editor's groups on every request +and fails closed; an out-of-scope target looks absent (`404`). Admin and +break-glass sessions are unrestricted, open apps are shared by all Editors, +and Media is shared. Account deletion, CSV user import, MFA reset, and group +creation/rename/deletion remain Admin-only. + The request guard then applies these boundaries in order: 1. `Spec::access_allows` enforces per-user/per-group access server-side. @@ -127,10 +136,16 @@ Postgres: `jobs::spawn` starts one scheduler loop per process when both a catalog DB and container backend exist (the local Docker backend runs jobs; the -multi-host backend does not, so a due schedule there records an error). Every 30 seconds it loads enabled `schedules`; -only the `LeaderElector` winner may fire in HA. `db::schedules::mark_fired` -atomically advances `last_run_at` before execution, so a split-brain second -runner loses the claim and a crash does not double-fire the occurrence. +multi-host backend does not, so a due schedule there records an error). +Every 30 seconds it loads enabled `schedules`; only the `LeaderElector` +winner may fire in HA. Cron expressions are evaluated against the IANA wall +clock named by `server.timezone`; when it is absent, the historical UTC +behaviour is preserved. An invalid name also falls back to UTC with a +validation warning. The Schedules page calls the same `next_occurrence` +function and renders firing times in that operational zone. +`db::schedules::mark_fired` atomically advances `last_run_at` before +execution, so a split-brain second runner loses the claim and a crash does +not double-fire the occurrence. A new schedule anchors at `created_at` (no fire-on-create). If downtime spans several cron occurrences, the next tick collapses them to one firing. The job @@ -141,6 +156,15 @@ per-schedule timeout defaults to one hour in the backend. Results, duration, exit code, and log tail land in `schedule_runs`; failures enqueue the `job-failed` alert webhook. +These are deliberately two different time models. Stored instants remain +UTC. Ordinary admin tables emit those instants as `