This roadmap tracks the product milestones needed to move Aetherheim from the current secure backend foundation to a usable 1.0 CMS.
1.0 must be a secure, installable CMS that can run manually, behind Fluxheim, or in rootless Podman with the Wolfi image. It should be usable without plugin bloat and must ship with the core site, content, identity, theme, asset, security, and MCP foundations enabled by default.
Required 1.0 scope:
- Secure first-run installer.
- Site identity settings: title, tagline, public URL, canonical URL, public URL aliases for serving the same single site on multiple configured domains, logo, icon, admin email, registration policy, default role, language, locale, timezone, date format, time format, week start, currency, measurement system, reading settings, writing defaults, media defaults, permalinks, privacy, maintenance, discussion settings, and a standards-based web app manifest generated from public-safe site identity fields.
- Passkey-first administrator identity with recovery policy.
- Admin session enforcement and role/capability checks on all privileged routes.
- Content model for posts, pages, revisions, comments, taxonomy, menus,
redirects, SEO metadata, proofs, public feeds/sitemaps, and search
descriptors. The generated
robots.txtshould be owner-overridable for public sites while private and maintenance modes still forceDisallow: /. Revisions must be integrity-verified and restorable through an audited admin workflow, and content status changes must be available as narrow audited operations. Scheduled content must use a first-classscheduled_atdatetime, remain private until due, and expose an audited cron-friendly publish-due operation. Draft, scheduled, and archived content must have an authenticated HTML preview route that always emits noindex robots metadata. Redirects must have narrow audited enable/disable controls that do not require rewriting the redirect target. - Comment moderation must include a global admin queue with status filtering, hashed commenter emails, and public rendering limited to approved comments.
- Asset metadata, binary upload, derivative metadata, safe upload validation, audited archive/unarchive lifecycle controls, immutable public asset serving, and storage abstraction. Metadata stripping and WebP/AVIF generation remain 1.2 worker milestones.
- Token-based themes and semantic layout rendering.
- Sandboxed plugin manifest registry and capability model foundation. Runtime Wasm execution remains a 1.4 milestone.
- MCP tool, resource, prompt, grant, invocation, and audit workflow foundation.
- Federation actor and activity foundation.
- Network/multi-site data model foundation: network config, reserved site keys, domain allowlists, per-site status, and future-safe routing modes. 1.0 remains single-site at runtime; multi-site routing and network administration ship later after the admin/editor surface is stable. The long-term network model must be first-party, not plugin-dependent: operators should be able to run subdirectory sites, subdomain sites, and fully mapped custom domains from the core admin/API surface with clear validation and safe defaults.
- Single-site domain aliases are in 1.0 scope and are separate from multi-site: a single configured site may render itself on multiple explicitly configured public origins, with generated public URLs and canonical metadata using the matching request host. Aliases must not expand administrator/WebAuthn origins.
- Production deployment documentation for manual, Fluxheim reverse proxy, and rootless Podman/Wolfi operation.
- Native binary portability is a standing engineering constraint from 1.0 onward. Linux remains the production Tier 1 target, but new core code should keep macOS, Windows, and BSD in mind, avoid unnecessary Linux-only runtime assumptions, and isolate platform-specific behavior behind small guarded modules or feature gates. Windows production support requires ACL hardening for secret files before it can be advertised as equivalent to Unix targets.
- Structured application logging with
tracing. Prometheus metrics and OpenTelemetry/OTLP trace export are optional post-1.0 observability features, not required for the 1.0 release gate. - Read-only configuration export for instance recovery that includes site and security settings but excludes passkeys, recovery codes, sessions, installer tokens, and other secret material.
- Full release gate:
cargo fmt,cargo clippy,cargo test,cargo deny,cargo audit,cargo license, and local SurrealDB/Fluxheim smoke.
Aetherheim 1.0 must include a first-run installer, but it must avoid legacy installer weaknesses such as browser-submitted database secrets, writable application config from the web UI, default administrator names, password-first setup, and a reusable install endpoint.
Installer goals:
- Make the first launch friendly for non-expert operators.
- Keep secrets out of browser forms, logs, analytics, crash reports, and audit events.
- Support manual deployment, Fluxheim reverse proxy deployment, and rootless Podman/Wolfi deployment.
- Be idempotent and resumable without allowing reinstallation over an existing site.
- Disable itself automatically after successful installation.
Installer state:
- Add a schemafull
install_staterecord in SurrealDB with exactly one active site setup state. - Track
status:not_started,environment_ready,admin_pending,site_pending,complete,locked. After environment validation, the installer collects site settings first;admin_pendingmeans site setup is complete and first-admin registration is the remaining blocking step. - Track
instance_id,schema_version,created_at,updated_at,completed_at, and a non-secret setup fingerprint. - Never store the installer token, database password, passkey challenge secret, or recovery secret in plaintext.
Installer entry points:
GET /_aetherheim/installreturns the installer shell only when installation is incomplete.GET /_aetherheim/install/statusreturns safe setup progress and environment checks.POST /_aetherheim/install/tokenverifies the one-time installer token and setup fingerprint before showing the remaining setup steps.POST /_aetherheim/install/environmentverifies database connectivity, migration status, base URL, reverse proxy headers, filesystem/storage readiness, and HTTPS expectations.POST /_aetherheim/install/sitesaves the minimal launch-time site identity and search visibility from the friendly installer. The backend still accepts the full validated site config shape so the admin panel can manage advanced localization, permalink, privacy, discussion, media, reading, writing, and alias settings after setup.POST /_aetherheim/install/admin/passkey/optionscreates a WebAuthn registration challenge for the first administrator after site setup is accepted, so origin and public site data are already fixed.POST /_aetherheim/install/admin/passkey/finishconsumes the one-time challenge before creating the first admin account, access role, passkey credential, and one-time recovery codes.POST /_aetherheim/install/admin/password/optionsis a non-default fallback for operators without usable passkey support. It creates a one-time password setup challenge and returns a TOTP enrollment secret for an authenticator app.POST /_aetherheim/install/admin/password/finishconsumes that challenge only after email, password, and TOTP verification, then creates the first admin, the isolated password+MFA credential, and one-time recovery codes.POST /_aetherheim/install/completeremains a guarded finalizer for resumable setup states, but the normal 1.0 browser flow locks the installer automatically when first-admin passkey registration succeeds.
Installer token model:
- On first boot, generate a high-entropy one-time installer token if no complete install exists.
- Write the token to
AETHERHEIM_STORAGE_DIR/installer-tokenby default, or to an explicitly configured rootless Podman secret/local file path, with0600permissions. - Log only the token file path or token source, never the token value.
- Require the token for all mutating installer endpoints until the first admin credential is registered.
- Store only a keyed hash of the token.
- Rotate or destroy the token after admin creation, including best-effort removal of file-backed tokens.
- Refuse token auth after
install_state.status = "complete".
Installer security controls:
- Installer routes are unavailable when installation is complete.
- Mutating installer routes require same-origin checks, CSRF protection, strict no-store caching, strict CSP, and rate limiting.
- The installer must not accept database credentials from a public browser form. Database connection configuration comes from environment variables, Podman secrets, or a local operator-owned config file.
- The installer must validate the effective public URL and reverse proxy headers before passkey registration so WebAuthn origins are correct.
- First administrator setup is passkey-first. A small fallback link may create the first admin with email+password only when TOTP MFA is enrolled and verified in the same flow; password-only admin setup is forbidden.
- Password fallback credentials live in a dedicated
SCHEMAFULLSurrealDB table withPERMISSIONS NONE, a unique keyed email hash, an app-peppered password value that SurrealDB stores withcrypto::argon2::generate, and an encrypted TOTP secret. Raw passwords, TOTP secrets, and setup challenges must never be written to audit events, logs, or exported config. - First administrator setup must return one-time recovery codes exactly once; only hashes and short prefixes are stored after the response.
- Passkey registration stores server-side WebAuthn ceremony state only in the private installer challenge table and binds challenge completion to the setup fingerprint, origin, expiry, and single-use consumption.
- Non-WebAuthn passkey fixture completion is allowed only when
AETHERHEIM_ALLOW_INSECURE_INSTALLER_PASSKEY_FIXTURE=1is set for local smoke testing,SURREALDB_PASS=change-me-local-onlyis in use, and the local-only insecure deployment opt-in is enabled. - The default admin username must not be
admin; the UI should require an explicit unique username and display name. - The browser installer collects only the one-time token, verifies the server database configuration, then asks for site title, first admin username, email, passkey or password+TOTP fallback, and search engine visibility. Locale, timezone, date/time formats, week start, permalink pattern, aliases, privacy pages, discussion, media, reading, writing, and registration policies move to the admin settings UI with secure defaults applied during setup.
- Privacy and terms page references must resolve to published public pages and be exposed through both the public HTML surface and public site metadata API. Full GDPR/ePrivacy consent management is planned as an opt-in compliance mode after 1.0 so the launch installer stays small and predictable.
- All installer operations produce audit events without recording secrets.
- Re-running the installer against an existing initialized database returns a locked response and never overwrites content or users.
Installer UX flow:
- Prove operator control with the one-time installer token.
- Show the server-loaded database name, user, host, and password-present status, then verify the active connection. The browser does not submit database secrets.
- Create the site title and first administrator with passkey-first setup, or email+password+TOTP fallback when passkeys are unavailable.
- Choose whether public search engines may index the site.
- Display one-time recovery codes and open the admin dashboard after automatic installer lockout.
1.0 acceptance criteria:
- A fresh database opens the installer and an initialized database never does.
- Setup works through Fluxheim and directly against the local app.
- A complete install creates one admin role, one admin user, either one passkey credential or one password+TOTP fallback credential, first-admin recovery codes, site config, security policy, and audit events.
- The installer token cannot be reused after admin creation.
- Secrets do not appear in API responses, logs, audit events, or smoke artifacts.
- Full checks pass with installer smoke coverage.
- Leptos admin shell with SSR and selective hydration. The 1.0 admin remains a secure server-rendered operational interface; 1.1 introduces the richer reactive workspace after the core CMS flows are stable.
- Tailwind CSS v4 styling with CSS-first
@themevariables and OKLCh design tokens sourced from the Aetherheim theme model. Avoid legacy JavaScript Tailwind configuration unless a build-time integration makes it unavoidable. - Search-first administration with a global command palette for common content, media, identity, settings, cache, audit, and MCP actions. Keyboard access and capability filtering are release blockers.
- First-party cache operations so site owners do not need a separate cache plugin for common blog operations: show Fluxheim cache status, hit/miss health, memory/disk policy, public media cache eligibility, and safe purge or warm actions for one URL, one asset, content-related URLs after publish, or all public media cache. Admin/auth/install/API responses must remain uncacheable, every purge/warm action must be audited, and the UI should make it clear when no supported reverse-proxy cache is configured.
- Bento-style dashboard layout for high-signal operational cards: publishing status, drafts, scheduled content, moderation queue, storage health, security events, installer/runtime status, background jobs, and MCP activity.
- Collapsible, minimal navigation that works without visual clutter on desktop and mobile. The interface may use translucent surfaces and modern depth, but readability, contrast, and predictable layout win over decorative effects.
- Fine-grained Leptos signals for local UI state, loading states, optimistic table updates, and command-palette interactions.
- Accessible data table foundation with filtering, sorting, pagination, optimistic row operations, rollback on failed mutations, and no secret/raw internal fields in visible rows.
- Lucide icon set for admin actions, with text labels or tooltips where icons are not self-explanatory.
- Content editor for posts and pages.
- Semantic layout editor foundation.
- Media library UI.
- Comment moderation UI.
- Menu and taxonomy management UI.
- Admin API calls continue to use the existing audited REST/MCP foundation in 1.1. GraphQL remains optional and should only be added if it provides a clear typed query advantage over the existing SurrealDB-backed REST resources.
- The admin UI must remain EUPL-1.2, and newly created source files must carry the project license header.
- Server-side theme rendering.
- Design-token editor.
- Critical CSS generation.
- Asset binary upload and derivative workers, including generated favicon, Apple touch icon, and web app icon derivatives from a configured site icon.
- Portable theme export/import.
Aetherheim themes must be portable presentation packages, not native code that links into the core server. The core application owns routing, authentication, data loading, cache policy, CSP, public SEO metadata, and admin/editor UI. Theme authors provide safe presentation inputs:
- Design tokens as structured JSON: color, typography, spacing, radii, motion, layout density, dark-mode variants, and component defaults.
- CSS or generated CSS assets that are validated, minified, fingerprinted, and served as immutable public assets. Tailwind-compatible output is acceptable, but the stable contract is CSS plus design tokens, not a Tailwind build system dependency.
- Semantic layout definitions as structured JSON trees that map to approved server-side components, named slots, and typed props.
- Optional safe template files may be added after the token/layout renderer is stable. If added, the template engine must be sandboxed and data-only: no arbitrary file access, no native code execution, no database queries from templates, no environment access, and no inline script execution by default.
- Optional theme components must cross a hard ABI boundary, such as sandboxed Wasm with declared props schemas and capabilities. They must not be loaded as native Rust or Leptos code inside the server process.
Leptos is Aetherheim's core/admin implementation detail. Third-party theme authors should not be required or allowed to write Leptos components that are dynamically linked into the running CMS. The public theme developer experience should feel familiar: tokens, HTML-like semantic templates when available, slots/widgets, CSS, and packaged assets.
Theme slots are the safe replacement for legacy sidebars/widget areas. A theme
may declare named zones such as header.primary, sidebar.primary,
content.after, and footer.primary. The database stores which core blocks or
capability-bound plugin widgets are assigned to those slots. The renderer then
expands the slot server-side with sanitized HTML and an audited capability
boundary.
Public rendering pipeline:
- Resolve the request, site config, locale, active theme, route content, and current consent/privacy state.
- Build a typed render context from SurrealDB records.
- Run approved plugin filters only at explicitly defined hook checkpoints.
- Render semantic layout nodes, slots, and optional safe templates.
- Emit static HTML plus fingerprinted CSS/assets with strict CSP and cache headers.
Themes may style output and define structure, but they do not own security, identity, database access, routing trust, cache bypass policy, or plugin capability grants.
Aetherheim must include a first-party GDPR/ePrivacy mode that operators can enable for EU/EEA deployments or for any site that wants stricter privacy defaults. This is a product feature, not legal advice: the CMS should provide secure controls and audit trails, while site owners remain responsible for their specific lawful basis, privacy notice, and processor obligations.
Planning assumptions from current EU guidance:
- Consent must be freely given, specific, informed, unambiguous, visible, easy to understand, and withdrawable. Refusal must not cause unfair disadvantage.
- Non-essential cookies, local storage, tracking pixels, embedded third-party media, analytics identifiers, personalization, and behavioural advertising must not activate before consent when consent is the chosen lawful basis.
- Necessary technical cookies such as session, security, CSRF, consent-state, and passkey ceremony cookies are separated from optional categories and do not require the same opt-in.
- Behavioural advertising requires explicit opt-in. Contextual or non-personal advertising should be the default ad mode when GDPR mode is enabled.
- The consent UI must offer equal-weight accept, reject, and customize choices, support granular category choices, and provide a persistent way to change or withdraw consent.
Required GDPR mode features:
- Site setting
privacy.compliance_mode = off | gdprplus operator-facing region notes. No IP geolocation is required for the first release; the operator chooses whether GDPR mode applies globally or only to selected locales/domains when the network layer ships. - Consent category registry: necessary, preferences, analytics, embedded media, personalization, advertising, and future custom categories.
- Server-rendered consent banner and preference center that works without third-party JavaScript. The banner must be themeable but cannot be disabled by themes or plugins while GDPR mode is active.
- Consent state cookie that stores only a compact consent version and category decisions. Avoid user identifiers in the consent cookie.
- Consent event ledger in SurrealDB for authenticated users and optional anonymous proof records: policy version, categories, decision, locale, timestamp, user agent hash, IP prefix or no-IP mode, and source surface. Raw IP addresses should not be stored by default.
- Public API for current consent policy, categories, and user decision state.
- Plugin/theme capability gates: optional scripts, pixels, embeds, ad tags, and analytics hooks declare their category and are blocked until consent allows them.
- Advertisement controls: no ads, contextual/non-personal ads, or personalized ads. Personalized ads are disabled until explicit advertising consent exists.
- First-party analytics privacy mode with cookieless or aggregate-only operation available before consent, and richer analytics only after analytics consent.
- Data subject request workflow: export, rectification notes, deletion, restriction, objection, consent withdrawal, and account closure queues with administrator review, audit events, and retention-safe deletion.
- Retention policy registry for comments, audit logs, sessions, consent events, analytics aggregates, newsletter events, and membership billing references.
- Processor/subprocessor registry and data transfer notes so exported privacy notices can list active integrations, AI providers, email providers, payment processors, analytics tools, and ad partners.
- Privacy notice generator that uses configured site identity, contact details, processor registry, retention policy, enabled integrations, cookie categories, legal pages, and locale.
- Full smoke tests for consent defaults: optional scripts blocked before consent, reject-all state respected, category opt-in respected, withdrawal respected, and no admin email, tokens, passkeys, IP addresses, or content bodies leaked into public consent APIs.
Future network-aware GDPR features:
- Per-domain/per-locale compliance mode for network sites.
- Cross-site consent sharing only inside an explicitly configured first-party network with clear user disclosure.
- Data processing inventory per network site and shared processor catalog.
- Membership/newsletter consent records tied to purpose-specific lawful bases.
- Wasmtime execution host.
- Plugin SDK.
- Capability-bound host calls.
- Hook execution timeouts and memory limits.
- Plugin quarantine and audit workflows.
- Stable hook registry with three explicit hook classes:
filter:*hooks for data transformation,action:*hooks for audited events, andrender:*hooks for bounded visual fragments or widget output. - Initial filter hooks should cover content rendering, SEO metadata, route decisions that are safe to modify, search indexing payloads, and asset metadata enrichment.
- Initial action hooks should cover content publication, content status changes, asset upload completion, comment submission/moderation, authentication attempts, cache purge/warm requests, and recovery/security setting changes.
- Initial render hooks should cover public head metadata, public footer fragments, configured theme slots, and admin dashboard widgets. Render hooks must return sanitized fragments plus declared asset/category requirements; they cannot directly mutate the server DOM or bypass CSP/GDPR gates.
- Hook payloads are versioned typed structures serialized across the Wasm ABI, most likely JSON for early SDK ergonomics with room for a stricter binary format later. A plugin never receives raw Rust pointers, database handles, environment variables, session secrets, passkey material, recovery codes, or unrestricted filesystem/network access.
- Hook execution order must be deterministic by priority and plugin identity. Failed plugins must fail closed for security-sensitive hooks, fail isolated for normal content hooks, and produce safe audit/log events without leaking payload secrets.
- Runtime limits are mandatory: per-hook wall-clock timeout, fuel/instruction limits where supported, memory limits, output-size limits, recursion limits, and per-request total plugin budget. A plugin panic or timeout must not crash the host server.
- Capabilities are deny-by-default and declared in the plugin manifest. Examples
include
content.read,content.suggest_update,metadata.write,asset.read_public,cache.purge_public,webhook.send, andrender.fragment. Network, filesystem, outbound HTTP, cache mutation, and write-like operations require explicit host calls and audit events. - The PDK should let plugin authors write Rust first, while keeping the ABI open to any language that compiles to Wasm. Authors should implement stable exported hook functions against Aetherheim payload types; they should not import Leptos or link against Aetherheim core internals.
- Extism or similar higher-level runtimes may be evaluated for SDK ergonomics, but the architectural requirement is a hard Wasm sandbox boundary. The final runtime choice must pass the same audit, license, resource-limit, and capability-gating requirements as direct Wasmtime usage.
- Plugin/theme license exception. Before the runtime is declared stable, Aetherheim should add a clearly separated plugin/theme exception notice without modifying the standard EUPL-1.2 text. The exception should permit independently developed plugins and themes to link, interface with, or execute through the officially documented public API, REST/MCP interface, or sandboxed Wasm ABI under license terms chosen by their authors, while modifications to Aetherheim core remain EUPL-1.2.
- The legal exception must match a hard technical boundary: plugins and themes covered by the exception use documented APIs or sandboxed Wasm modules, cannot require native Rust linking into the server process, and cannot rely on private internal interfaces. Bundled core modules, copied core code, and patches to Aetherheim itself stay under the core license.
- Publish the exception text in a dedicated licensing document, README summary, package metadata, and marketplace/plugin SDK documentation after legal review. The documentation should explain that the exception is a permission from the copyright holders, not a change to the EUPL-1.2 license text.
- AI metadata and internal-link suggestions.
- Native graph-linked translations.
- Human review workflow.
- Glossary and brand voice records.
- Hreflang generation.
- ActivityPub inbox/outbox processing.
- Cross-site comment workflows.
- MCP resource and prompt expansion.
- Content signing verification UI.
- Multi-instance search and shared identity foundations.
- Multi-site network administration with network-level settings, site creation, suspension/archive states, reserved slugs, subdirectory/subdomain routing, and domain-mapped sites. The goal is a major usability improvement over plugin-based multi-site setups: subdomains, subdirectories, and new mapped domains should be routine core operations with validation, audit events, and no fragile install-time plugin stack.
- Network-scoped users and capabilities with separate instance operator, network administrator, site administrator, editor, author, and member roles.
- Network-wide theme and plugin availability controls with per-site activation.
- Per-site storage isolation, upload quotas, and safe export/import workflows.
- Native memberships with passkey or passwordless email-link login, free/paid access tiers, protected content rules, and member account management.
- Newsletter publishing and delivery workflows with unsubscribe, bounce, complaint, and consent/audit records.
- First-party privacy-preserving analytics for traffic, content engagement, search, newsletter performance, and membership growth.
- Webhook and integration registry with signed deliveries, retry policy, dead-letter audit logs, and scoped API keys.
- Prometheus-compatible metrics endpoint with safe default labels, request counts, latency histograms, response status classes, rate-limit counters, auth/installer lockout counters, database operation timing, background job counters, and asset processing metrics.
- OpenTelemetry support for OTLP traces over gRPC and HTTP, with configurable service name, environment, sample rate, resource attributes, and exporter endpoint.
- Trace propagation across reverse proxy requests using W3C
traceparentandtracestateheaders while avoiding user identifiers, session tokens, passkey data, recovery data, content bodies, and installer tokens in spans. - Local development profile for Jaeger
cr.jaegertracing.io/jaegertracing/jaeger:2.17.0on ports4317,4318, and16686, plus Prometheus on port9090. - Deployment examples for running observability behind Fluxheim or on a private operator network, with metrics disabled by default until explicitly enabled.
- Smoke tests that verify metrics exposure and OTLP export only when the local observability stack is explicitly enabled.