Skip to content

RFC 0052 — Plugin Portability Hooks

Summary

Define a plugin-owned portability hook system for export, import, and deletion. The platform owns orchestration and bundle format; each plugin owns its domain serialization, import validation, file inclusion policy, and cross-plugin reference handling.

This strengthens RFC 0007 user data portability and RFC 0033 user deletion for larger plugins with files, generated artifacts, external connections, and cross-plugin references.

Motivation

Simple platform-owned export works for core account data, but richer plugins need domain-specific behavior:

  • include or exclude large attachments and generated documents;
  • preserve opaque references to other plugins without forcing those plugins to export at the same time;
  • export secret metadata without plaintext secret values;
  • remap storage object IDs during import;
  • run deletion cleanup across plugin DB rows, storage objects, jobs, connections, and secrets.

Without plugin hooks, each new plugin either leaves portability incomplete or requires platform-specific export code.

Current state

  • RFC 0007 defines user data export and portability.
  • RFC 0033 defines user data deletion.
  • Account has a Data tab for export/delete flows.
  • RFC 0044 proposes plugin storage participation.
  • There is no stable plugin SDK hook for export/import/delete.

Proposed design

SDK surface

Add sdk.portability.provide():

ts
sdk.portability.provide({
  export: async (ctx) => PluginExportResult,
  import: async (ctx, bundle) => PluginImportResult,
  deleteUserData: async (ctx) => PluginDeletionResult,
});

The runtime injects tenant ID, user ID, plugin ID, and export options. Hook handlers run server-side only.

Export result

ts
interface PluginExportResult {
  manifest: {
    pluginId: string;
    pluginVersion: string;
    schemaVersion: number;
    exportedAt: string;
  };
  data: unknown;
  files?: Array<{ storageKey: string; exportPath: string }>;
  references?: PluginReference[];
  secretMetadata?: Array<{ label: string; provider: string; exists: boolean }>;
  warnings?: string[];
}

Plaintext secrets are never exported. Files are included only according to the user's export options and plugin policy.

Import behavior

Import is merge-oriented by default:

  • validate bundle plugin ID and schema version;
  • remap storage objects and generated IDs;
  • preserve external references as inert links unless providers are available;
  • do not recreate secrets or external connections automatically;
  • report warnings for skipped files, unsupported versions, or unavailable providers.

Plugins may reject imports that would overwrite existing domain records unless the platform later adds explicit overwrite modes.

Deletion behavior

Deletion hooks remove all owner-scoped plugin data for a user:

  • plugin database rows;
  • user-owned storage objects;
  • user-scoped secrets and connections;
  • queued jobs owned by the user;
  • cached references and generated artifacts;
  • provider-specific local state.

Deletion must be idempotent. Re-running the hook should succeed.

Platform orchestration

The platform:

  • enumerates installed plugins;
  • calls hooks with bounded timeouts;
  • records success/failure per plugin;
  • includes plugin warnings in the export report;
  • blocks account deletion completion until required hooks finish or are explicitly marked failed according to the accepted policy.

Security requirements

  • Export hooks run with the current user's scope, not platform-wide access.
  • Plaintext secrets are never exported.
  • Import hooks validate bundle shape and size before writing.
  • Deletion hooks are idempotent and fail closed on unknown ownership.
  • Export bundles identify plugin ID, plugin version, schema version, and generated-at timestamp.
  • Cross-plugin references are inert metadata; importing a reference does not grant access to the provider plugin.

Alternatives considered

Platform introspects every plugin table

Rejected. It cannot understand domain semantics, storage keys, references, or secret metadata safely.

Each plugin builds its own export UI

Rejected. Users need one Account-level portability flow across installed plugins.

Export everything including secrets

Rejected. Reconnecting external accounts is safer than exporting plaintext credentials.

Open questions

  1. Should export hooks stream data, or is JSON result plus file references enough for v1?
  2. Should account deletion fail hard if one plugin hook fails, or produce a recoverable admin task?
  3. Should import support dry-run previews?
  4. Should plugin export schema versions be declared in manifest metadata?

Adoption path

  1. Add portability hook registration to SDK/runtime.
  2. Define bundle format for plugin export results and files.
  3. Wire Account export/import/delete orchestration through registered hooks.
  4. Add storage, secret metadata, connection, and job cleanup helpers.
  5. Document plugin portability test requirements.

Changelog

VersionDateChange
0.1June 2026Initial draft

Open source under AGPL-3.0. Each Sovereign instance is independently operated.