Aurum 2.0 & Aurora — raport techniczny
Architektura modułowej platformy, izolowanych pluginów i wizualnego edytora
Przegląd
Techniczny opis architektury Aurum 2.0, runtime pluginów, Aurora Editor, granic bezpieczeństwa oraz procesu wdrażania.
Używane technologie
Cel platformy
Aurum 2.0 oddziela unikalne doświadczenie użytkownika od powtarzalnej pracy operacyjnej. Serwisy nadal powstają jako niezależne aplikacje Vue i Nuxt, natomiast treści, uprawnienia, dane domenowe, publikacja oraz wdrożenia korzystają ze wspólnych kontraktów.
Platforma nie jest związana z jedną branżą. Funkcje biznesowe są dostarczane przez wersjonowane pluginy, dzięki czemu kolejne domeny mogą rozwijać się bez rozbudowywania rdzenia.
Mały rdzeń i pluginy produktowe
Rdzeń Aurum odpowiada za elementy wymagające wspólnego zaufania:
- tożsamość, sesje i role;
- katalog pluginów i ich wersje;
- audyt oraz obserwowalność;
- walidację kontraktów;
- kontrolowaną aktywację nowych wersji.
Plugin wnosi kompletną możliwość produktową: endpointy backendowe, interfejs administracyjny, konfigurację, uprawnienia i prywatną przestrzeń danych. Aurora Editor, Contact Messages czy plugin domenowy są równorzędnymi rozszerzeniami platformy.
Izolacja i kod wspomagany przez AI
Kod wygenerowany lub zmodyfikowany z pomocą AI nie otrzymuje dodatkowego zaufania. Każdy plugin przechodzi przez tę samą granicę kontraktową:
- Manifest deklaruje zgodność SDK, konfigurację, endpointy oraz uprawnienia.
- Dostęp jest przyznawany per capability zgodnie z zasadą minimalnych uprawnień.
- Dane i migracje pozostają prywatne dla pluginu.
- Kandydat jest sprawdzany przed aktywacją i przejęciem ruchu.
Takie podejście nie zakłada bezbłędności wygenerowanego kodu. Ogranicza natomiast obszar potencjalnych konsekwencji i utrudnia przypadkowe sprzężenie modułów.
Aurora Editor
Aurora zamienia finalną stronę w powierzchnię edycji. Deweloper oznacza edytowalne elementy stabilnymi kluczami, wartościami domyślnymi i kontekstem dla redaktora. Layout, responsywność oraz sposób renderowania pozostają własnością aplikacji klienckiej.
Osadzona strona nie otrzymuje tokenu administracyjnego i nie zapisuje danych bezpośrednio. Komunikuje intencje edycji do zaufanej aplikacji nadrzędnej przez wersjonowany protokół wiadomości z:
- kontrolą origin;
- handshake opartym o nonce i identyfikator sesji;
- walidacją schematów;
- identyfikatorami korelacyjnymi;
- wersjonowanymi aktualizacjami stanu.
Szkice, publikacja i historia
Aurora zarządza szkicem, autosave, rewizją, konfliktami oraz stanem opublikowanym. Zapis zawiera oczekiwaną rewizję i klucz idempotencji. Konflikt nie nadpisuje cicho zmian innej osoby.
Publikacja tworzy niezmienny release wskazanej rewizji. Rollback powstaje jako kolejny release oparty na wcześniejszym snapshotcie, dzięki czemu historia pozostaje liniowa i audytowalna.
Bezpieczna aktywacja
Nowa wersja pluginu jest przygotowywana poza aktywnym ruchem. Aurum waliduje manifest, konfigurację, trasy backendowe i metadane interfejsu. Dopiero kompletny kandydat zastępuje poprzednią wersję atomowo.
Jeżeli aktywacja się nie powiedzie, ostatnia zdrowa wersja pozostaje aktywna. Operator Kubernetes i GitOps łączą wersję obrazu, konfigurację oraz oczekiwany stan klastra w jeden audytowalny proces.
Manifest backendowego pluginu
Plugin jest zwykłym modułem TypeScript eksportującym manifest zdefiniowany przez @aurum/backend-plugin-sdk. Kontrakt zawiera semantyczną wersję pluginu, zakres kompatybilności SDK, katalog uprawnień, schemat konfiguracji i endpointy.
Poniższy fragment odpowiada strukturze pluginu contact-message:
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: "Kontakt",
version: "1.0.0",
sdkVersion: "^1.0.0",
permissions: [READ, WRITE],
configurationSchema: {
type: "object",
required: [
"retentionDays",
"publicRateLimit",
"publicRateLimitWindowSeconds",
"allowedOrigins",
],
additionalProperties: false,
properties: {
retentionDays: { type: "integer" },
publicRateLimit: { type: "integer" },
publicRateLimitWindowSeconds: { type: "integer" },
allowedOrigins: { type: "array" },
},
},
backend: {
endpoints: [
{
method: "GET",
path: "/api/contact-messages",
requireAuth: true,
requirePermission: [READ],
handler: async (request) => {
return request.platform.storage.find(
"contactMessages",
{},
{ sort: { createdAt: -1 } },
);
},
},
],
},
});
sdkVersion jest zakresem SemVer, a nie numerem dekoracyjnym. Registry odrzuca manifest, jeżeli aktywny runtime nie spełnia deklarowanego zakresu. Identyfikatory uprawnień muszą być unikalne, a każdy endpoint jawnie określa uwierzytelnienie i wymagane capabilities.
Endpoint publiczny z kontrolą origin i rate limitingiem
Publiczny formularz kontaktowy nie omija runtime. Endpoint może być anonimowy, ale nadal korzysta z walidowanej konfiguracji, limitera izolowanego per plugin, prywatnego storage i audytu:
{
method: "POST",
path: "/api/contact-messages",
requireAuth: false,
requirePermission: [],
handler: async (request, reply) => {
const config = request.platform.configuration;
const origin = request.headers.origin;
if (
origin &&
!config.allowedOrigins.includes(origin)
) {
return reply.code(403).send({
error: "Origin is not allowed",
});
}
const accepted = await request.platform.rateLimit.consume(
request.ip,
{
limit: config.publicRateLimit,
windowMilliseconds:
config.publicRateLimitWindowSeconds * 1_000,
},
);
if (!accepted) {
return reply.code(429).send({
error: "Too many contact messages",
});
}
const message = {
_id: crypto.randomUUID(),
...request.body,
read: false,
createdAt: new Date().toISOString(),
};
await request.platform.storage.insertOne(
"contactMessages",
message,
);
await request.platform.audit.write(
"contact_message.created",
{ messageId: message._id },
);
return reply.code(201).send(message);
},
}
Capability API zamiast surowego dostępu
Handler nie otrzymuje instancji klienta MongoDB ani swobodnego dostępu do serwera Fastify. Runtime wstrzykuje ograniczony PlatformCapabilities:
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>;
};
}
Warstwa storage automatycznie namespace’uje kolekcje identyfikatorem pluginu. Wywołanie storage.find("messages") nie może odczytać kolekcji messages należącej do innego rozszerzenia.
Uprawnienia per endpoint i scope
Endpoint może wymagać wszystkich capabilities lub dowolnej z nich. Opcjonalny scopeParam łączy parametr trasy z ograniczonym grantem użytkownika:
{
method: "PATCH",
path: "/api/projects/:projectId/content/:id",
requireAuth: true,
requirePermission: [
"content.read",
"content.write",
],
permissionMode: "all",
scopeParam: "projectId",
handler: updateContent,
}
Grant content.write dla scope gardenia nie daje prawa zapisu w scope park-43. Weryfikacja odbywa się w gatewayu przed uruchomieniem handlera pluginu.
Deklaratywny plugin-set.yaml
Operator obserwuje zasoby AurumPluginSet w grupie aurum.dev/v1alpha1. Zestaw opisuje docelowe Deploymenty oraz dokładne obrazy pluginów:
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
Plugin musi używać dokładnie jednego poprawnego digest albo tag. Dla wdrożeń produkcyjnych digest wiąże deklarację z niezmiennym obrazem. Duplikaty id, niepoprawne nazwy oraz względny pluginMountPath są odrzucane przed zmianą Deploymentu.
Jak operator modyfikuje Deployment
Dla każdego targetu operator:
- tworzy współdzielony wolumen
emptyDirz limitem; - montuje go tylko do zadeklarowanej ścieżki pluginów;
- generuje kontrolowany init container dla każdego obrazu;
- przekazuje identyfikator, wersję i konfigurację jako jawne zmienne;
- oblicza SHA-256 całego zestawu i zapisuje go w adnotacji PodTemplate.
Uproszczona transformacja wygląda tak:
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 });
Zmiana hasha powoduje kontrolowany rollout Kubernetes. Status custom resource przechodzi do Ready dopiero po zakończeniu reconcile; błąd walidacji lub modyfikacji ustawia fazę Error z komunikatem.
Deklaracja treści po stronie Nuxt
Aurora nie analizuje przypadkowo całego DOM. Edytowalne punkty są jawne i typowane w kodzie strony:
<EditableText
translation-key="website.hero.title"
fallback="Dom — przestrzeń Twojego oddechu"
context="Główny nagłówek sekcji hero."
/>
Integracja aplikacji ustala scope publikacji oraz origin zaufanego rodzica:
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,
});
});
W trybie publicznym Nuxt pobiera tylko opublikowany snapshot dla scope’u:
tenant / application / environment / locale
default / gardenia / production / pl
Rezultat
Architektura pozwala rozwijać platformę w dwóch niezależnych kierunkach: zespoły frontendowe zachowują pełną swobodę projektowania stron, a możliwości operacyjne mogą być ponownie wykorzystywane jako izolowane produkty.
Aurum standaryzuje zaufanie, kontrakty i dostarczanie. Pluginy definiują domenę. Aurora pozwala redaktorowi pracować bezpośrednio w finalnym doświadczeniu.