diff --git a/src/ScadaLink.Commons/Interfaces/Transport/IAuditCorrelationContext.cs b/src/ScadaLink.Commons/Interfaces/Transport/IAuditCorrelationContext.cs
new file mode 100644
index 0000000..f042822
--- /dev/null
+++ b/src/ScadaLink.Commons/Interfaces/Transport/IAuditCorrelationContext.cs
@@ -0,0 +1,11 @@
+namespace ScadaLink.Commons.Interfaces.Transport;
+
+///
+/// Scoped service the bundle importer sets to thread a BundleImportId through to
+/// the audit log entries emitted by the audited repository methods invoked during
+/// ApplyAsync. AuditService reads this and stamps every AuditLogEntry it writes.
+///
+public interface IAuditCorrelationContext
+{
+ Guid? BundleImportId { get; set; }
+}
diff --git a/src/ScadaLink.Commons/Interfaces/Transport/IBundleExporter.cs b/src/ScadaLink.Commons/Interfaces/Transport/IBundleExporter.cs
new file mode 100644
index 0000000..e5b0684
--- /dev/null
+++ b/src/ScadaLink.Commons/Interfaces/Transport/IBundleExporter.cs
@@ -0,0 +1,13 @@
+using ScadaLink.Commons.Types.Transport;
+
+namespace ScadaLink.Commons.Interfaces.Transport;
+
+public interface IBundleExporter
+{
+ Task ExportAsync(
+ ExportSelection selection,
+ string user,
+ string sourceEnvironment,
+ string? passphrase,
+ CancellationToken cancellationToken = default);
+}
diff --git a/src/ScadaLink.Commons/Interfaces/Transport/IBundleImporter.cs b/src/ScadaLink.Commons/Interfaces/Transport/IBundleImporter.cs
new file mode 100644
index 0000000..d259d39
--- /dev/null
+++ b/src/ScadaLink.Commons/Interfaces/Transport/IBundleImporter.cs
@@ -0,0 +1,14 @@
+using ScadaLink.Commons.Types.Transport;
+
+namespace ScadaLink.Commons.Interfaces.Transport;
+
+public interface IBundleImporter
+{
+ Task LoadAsync(Stream bundleStream, string? passphrase, CancellationToken ct = default);
+ Task PreviewAsync(Guid sessionId, CancellationToken ct = default);
+ Task ApplyAsync(
+ Guid sessionId,
+ IReadOnlyList resolutions,
+ string user,
+ CancellationToken ct = default);
+}
diff --git a/src/ScadaLink.Commons/Interfaces/Transport/IBundleSessionStore.cs b/src/ScadaLink.Commons/Interfaces/Transport/IBundleSessionStore.cs
new file mode 100644
index 0000000..0f8a178
--- /dev/null
+++ b/src/ScadaLink.Commons/Interfaces/Transport/IBundleSessionStore.cs
@@ -0,0 +1,11 @@
+using ScadaLink.Commons.Types.Transport;
+
+namespace ScadaLink.Commons.Interfaces.Transport;
+
+public interface IBundleSessionStore
+{
+ BundleSession Open(BundleSession session);
+ BundleSession? Get(Guid sessionId);
+ void Remove(Guid sessionId);
+ void EvictExpired();
+}
diff --git a/src/ScadaLink.Commons/Types/Transport/BundleSession.cs b/src/ScadaLink.Commons/Types/Transport/BundleSession.cs
new file mode 100644
index 0000000..b495c0e
--- /dev/null
+++ b/src/ScadaLink.Commons/Types/Transport/BundleSession.cs
@@ -0,0 +1,11 @@
+namespace ScadaLink.Commons.Types.Transport;
+
+public sealed class BundleSession
+{
+ public Guid SessionId { get; init; }
+ public BundleManifest Manifest { get; init; } = null!;
+ public byte[] DecryptedContent { get; init; } = Array.Empty();
+ public DateTimeOffset ExpiresAt { get; init; }
+ public int FailedUnlockAttempts { get; set; }
+ public bool Locked => FailedUnlockAttempts >= 3;
+}
diff --git a/src/ScadaLink.Commons/Types/Transport/ExportSelection.cs b/src/ScadaLink.Commons/Types/Transport/ExportSelection.cs
new file mode 100644
index 0000000..0c7ce1b
--- /dev/null
+++ b/src/ScadaLink.Commons/Types/Transport/ExportSelection.cs
@@ -0,0 +1,12 @@
+namespace ScadaLink.Commons.Types.Transport;
+
+public sealed record ExportSelection(
+ IReadOnlyList TemplateIds,
+ IReadOnlyList SharedScriptIds,
+ IReadOnlyList ExternalSystemIds,
+ IReadOnlyList DatabaseConnectionIds,
+ IReadOnlyList NotificationListIds,
+ IReadOnlyList SmtpConfigurationIds,
+ IReadOnlyList ApiKeyIds,
+ IReadOnlyList ApiMethodIds,
+ bool IncludeDependencies);
diff --git a/src/ScadaLink.Commons/Types/Transport/ImportPreview.cs b/src/ScadaLink.Commons/Types/Transport/ImportPreview.cs
new file mode 100644
index 0000000..1ee54b6
--- /dev/null
+++ b/src/ScadaLink.Commons/Types/Transport/ImportPreview.cs
@@ -0,0 +1,16 @@
+namespace ScadaLink.Commons.Types.Transport;
+
+public enum ConflictKind { Identical, Modified, New, Blocker }
+
+public sealed record ImportPreviewItem(
+ string EntityType,
+ string Name,
+ int? ExistingVersion,
+ int? IncomingVersion,
+ ConflictKind Kind,
+ string? FieldDiffJson,
+ string? BlockerReason);
+
+public sealed record ImportPreview(
+ Guid SessionId,
+ IReadOnlyList Items);
diff --git a/src/ScadaLink.Commons/Types/Transport/ImportResolution.cs b/src/ScadaLink.Commons/Types/Transport/ImportResolution.cs
new file mode 100644
index 0000000..5d3958c
--- /dev/null
+++ b/src/ScadaLink.Commons/Types/Transport/ImportResolution.cs
@@ -0,0 +1,9 @@
+namespace ScadaLink.Commons.Types.Transport;
+
+public enum ResolutionAction { Add, Overwrite, Skip, Rename }
+
+public sealed record ImportResolution(
+ string EntityType,
+ string Name,
+ ResolutionAction Action,
+ string? RenameTo);
diff --git a/src/ScadaLink.Commons/Types/Transport/ImportResult.cs b/src/ScadaLink.Commons/Types/Transport/ImportResult.cs
new file mode 100644
index 0000000..34db172
--- /dev/null
+++ b/src/ScadaLink.Commons/Types/Transport/ImportResult.cs
@@ -0,0 +1,10 @@
+namespace ScadaLink.Commons.Types.Transport;
+
+public sealed record ImportResult(
+ Guid BundleImportId,
+ int Added,
+ int Overwritten,
+ int Skipped,
+ int Renamed,
+ IReadOnlyList StaleInstanceIds,
+ string AuditEventCorrelation);