Back to list

Aurum 2.0 & Aurora — technical report

Architecture of a modular platform, isolated plugins, and a visual editor

Platform EngineeringRuntime PluginsVisual EditorSecurityKubernetes

Overview

A technical account of the Aurum 2.0 architecture, plugin runtime, Aurora Editor, security boundaries, and delivery model.

Technologies Used

Vue.js
Nuxt
TypeScript
MongoDB
Kubernetes

Platform objective

Aurum 2.0 separates bespoke customer experience from repeatable operational work. Websites remain independent Vue and Nuxt applications while content, permissions, domain data, publishing, and delivery use shared contracts.

The platform is not tied to one industry. Business capabilities ship as versioned plugins, allowing new domains to evolve without expanding the core.

A small core and product plugins

The Aurum core owns the concerns that require shared trust:

  • identity, sessions, and roles;
  • the plugin catalog and versions;
  • audit and observability;
  • contract validation;
  • controlled activation.

A plugin contributes a complete product capability: backend endpoints, administration UI, configuration, permissions, and private data storage. Aurora Editor, Contact Messages, and domain-specific modules are peers in the same runtime.

Isolation and AI-assisted code

Code generated or modified with AI receives no additional trust. Every plugin crosses the same explicit contract boundary:

  1. Its manifest declares SDK compatibility, configuration, endpoints, and permissions.
  2. Access is granted per capability according to least privilege.
  3. Data and migrations remain private to the plugin.
  4. A candidate is verified before activation and active traffic.

The model does not assume generated code is flawless. It contains the possible consequences of a mistake and prevents accidental coupling between modules.

Aurora Editor

Aurora turns the final website into the editing surface. Developers mark editable elements with stable keys, fallback values, and editorial context. Layout, responsiveness, and rendering remain owned by the client application.

The embedded website receives no administration token and performs no direct persistence. It sends editing intent to the trusted parent through a versioned message protocol with origin control, nonce-based handshakes, schema validation, correlation identifiers, and revisioned state updates.

Drafts, publishing, and history

Aurora owns drafts, autosave, revisions, conflicts, and published state. Writes contain an expected revision and idempotency key, so concurrent work cannot be silently overwritten.

Publishing creates an immutable release for a specific revision. A rollback becomes a new release based on an earlier snapshot, preserving a linear and auditable history.

Safe activation

A new plugin version is prepared away from active traffic. Aurum validates its manifest, configuration, backend routes, and interface metadata. Only a complete candidate replaces the previous version atomically.

If activation fails, the last healthy version remains active. The Kubernetes operator and GitOps connect the image version, configuration, and expected cluster state in one auditable process.

Backend plugin manifest

A plugin is a TypeScript module exporting a manifest defined through @aurum/backend-plugin-sdk. The contract contains the plugin version, SDK compatibility range, permission catalog, configuration schema, and endpoints:

import {
  definePlugin,
  type PluginManifest,
} from "@aurum/backend-plugin-sdk";

const READ = "contact-messages.read";
const WRITE = "contact-messages.write";

export const manifest: PluginManifest = definePlugin({
  id: "contact-message",
  name: "Contact",
  version: "1.0.0",
  sdkVersion: "^1.0.0",
  permissions: [READ, WRITE],
  configurationSchema: {
    type: "object",
    required: [
      "retentionDays",
      "publicRateLimit",
      "allowedOrigins",
    ],
    additionalProperties: false,
    properties: {
      retentionDays: { type: "integer" },
      publicRateLimit: { type: "integer" },
      allowedOrigins: { type: "array" },
    },
  },
  backend: {
    endpoints: [{
      method: "GET",
      path: "/api/contact-messages",
      requireAuth: true,
      requirePermission: [READ],
      handler: async (request) =>
        request.platform.storage.find(
          "contactMessages",
          {},
          { sort: { createdAt: -1 } },
        ),
    }],
  },
});

The registry rejects incompatible SDK ranges, duplicate permissions, invalid endpoint contracts, and plugin-owned runtime fields.

Capability API instead of raw infrastructure

Handlers receive narrow runtime-owned capabilities rather than unrestricted Fastify or MongoDB objects:

export interface PlatformCapabilities {
  readonly pluginId: string;
  readonly requestId: string;
  readonly configuration:
    Readonly<Record<string, unknown>>;

  readonly audit: {
    write(
      event: string,
      details?: Record<string, unknown>,
    ): Promise<void>;
  };

  readonly storage: {
    find<T>(
      collection: string,
      filter?: Record<string, unknown>,
    ): Promise<T[]>;
    insertOne<T>(
      collection: string,
      document: T,
    ): Promise<void>;
    ensureIndex(
      collection: string,
      keys: Record<string, 1 | -1>,
      options?: {
        unique?: boolean;
        expireAfterSeconds?: number;
      },
    ): Promise<void>;
  };

  readonly rateLimit: {
    consume(
      subject: string,
      policy: {
        limit: number;
        windowMilliseconds: number;
      },
    ): Promise<boolean>;
  };
}

Storage collections are automatically namespaced by plugin identity. storage.find("messages") cannot read the same logical collection owned by another extension.

Endpoint permissions and scope

An endpoint can require all declared capabilities or any one of them. scopeParam connects a route parameter to a scoped user grant:

{
  method: "PATCH",
  path: "/api/projects/:projectId/content/:id",
  requireAuth: true,
  requirePermission: [
    "content.read",
    "content.write",
  ],
  permissionMode: "all",
  scopeParam: "projectId",
  handler: updateContent,
}

A content.write grant scoped to gardenia does not authorize writes to park-43. The gateway evaluates the grant before invoking plugin code.

Declarative plugin-set.yaml

The operator watches AurumPluginSet resources in aurum.dev/v1alpha1:

apiVersion: aurum.dev/v1alpha1
kind: AurumPluginSet
metadata:
  name: portfolio-platform
  namespace: gardenia
spec:
  applicationRef:
    targets:
      - deployment: backend
        container: backend
        pluginMountPath: /app/plugins
      - deployment: frontend
        container: nginx
        pluginMountPath: /usr/share/nginx/html/plugins

  plugins:
    - id: contact-message
      image: europe-west1-docker.pkg.dev/platform/aurum/contact-message
      version: 1.0.0
      digest: sha256:2f36f2c7e5b7c18d81d73ca5e5a7e3b1d9b08a358af7f9b0b1f44f617db45a12
      configuration:
        retentionDays: 365
        publicRateLimit: 5
        publicRateLimitWindowSeconds: 60
        allowedOrigins:
          - https://gardenia.example

    - id: real-estate
      image: europe-west1-docker.pkg.dev/platform/aurum/real-estate
      version: 1.0.0
      tag: 1.0.0

Each plugin must use exactly one valid digest or tag. Production deployments can pin immutable digests. Duplicate IDs, invalid names, and relative mount paths fail validation before any Deployment is changed.

Deployment reconciliation

For every target, the operator creates a size-limited shared volume, mounts it read-only in the application container, generates controlled init containers, and adds a SHA-256 hash to the PodTemplate:

const pluginMount = {
  name: "aurum-plugins",
  mountPath: target.pluginMountPath,
  readOnly: true,
};

template.spec.volumes = [{
  name: "aurum-plugins",
  emptyDir: { sizeLimit: "256Mi" },
}];

template.spec.initContainers = set.spec.plugins.map(
  (plugin) => ({
    name: `aurum-plugin-${plugin.id}`,
    image: plugin.digest
      ? `${plugin.image}@${plugin.digest}`
      : `${plugin.image}:${plugin.tag}`,
    env: [
      { name: "PLUGIN_ID", value: plugin.id },
      { name: "PLUGIN_VERSION", value: plugin.version },
      {
        name: "PLUGIN_CONFIGURATION",
        value: JSON.stringify(plugin.configuration ?? {}),
      },
    ],
    volumeMounts: [{
      name: "aurum-plugins",
      mountPath: "/plugins",
    }],
  }),
);

template.metadata.annotations[
  "aurum.dev/plugin-hash"
] = sha256({ target, plugins: set.spec.plugins });

A hash change triggers a Kubernetes rollout. The custom resource reaches Ready after reconciliation; validation or mutation errors produce an Error phase with a diagnostic message.

Declaring Aurora content in Nuxt

Aurora does not scrape arbitrary DOM content. Editable points are explicit in website code:

<EditableText
  translation-key="website.hero.title"
  fallback="A home built around breathing room"
  context="Primary headline of the hero section."
/>

The Nuxt integration supplies the publication scope and trusted parent origin:

export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig();
  const state = useState(
    "aurora-published-state",
    () => ({
      mode: "view",
      activeLocale:
        config.public.auroraDefaultLocale,
      translations: {},
    }),
  );

  installAurora({
    state,
    applicationId:
      config.public.auroraApplicationId,
    parentOrigin: new URL(
      config.public.auroraParentOrigin,
    ).origin,
  });
});

The public Nuxt application fetches only the published snapshot for a tenant/application/environment/locale scope.

Result

The architecture allows the platform to grow in two independent directions: frontend teams retain full freedom over customer-facing design, while operational capabilities are reused as isolated products.

Aurum standardizes trust, contracts, and delivery. Plugins define the domain. Aurora lets editors work directly in the final experience.

Interested in a similar project?

Get in touch