fix(transport): remove the never-read Transport:SchemaVersionMajor option (review 2026-10-21 04/U1, 08/O3)

This commit is contained in:
Joseph Doherty
2026-10-22 09:17:07 -04:00
parent 3304381636
commit fad08f7ce7
6 changed files with 10 additions and 16 deletions
+1 -2
View File
@@ -27,7 +27,7 @@ bundle.scadabundle
`manifest.json` is always plaintext so the import wizard can display source provenance and artifact counts before the operator supplies a passphrase. `BundleManifest` carries: `BundleFormatVersion`, `SchemaVersion`, `CreatedAtUtc`, `SourceEnvironment`, `ExportedBy`, `ScadaBridgeVersion`, `ContentHash` (`sha256:<hex>` of the raw content bytes), optional `Encryption` metadata, a `Summary` (artifact counts by type), and a `Contents` list (one `ManifestContentEntry` per artifact with its `dependsOn` edges).
`BundleFormatVersion` is an integer gate: the importer requires `BundleFormatVersion` to equal `ManifestBuilder.CurrentBundleFormatVersion` (currently `1`) and rejects any other value — higher or lower — with `ManifestValidationResult.UnsupportedFormatVersion`. `TransportOptions.SchemaVersionMajor` is not read during import. Unknown entity types in `Contents` produce a preview-row classification of "unsupported" rather than aborting the whole import.
`BundleFormatVersion` is an integer gate: the importer requires `BundleFormatVersion` to equal `ManifestBuilder.CurrentBundleFormatVersion` (currently `1`) and rejects any other value — higher or lower — with `ManifestValidationResult.UnsupportedFormatVersion`. There is no configurable schema-version option; both versions are code constants. Unknown entity types in `Contents` produce a preview-row classification of "unsupported" rather than aborting the whole import.
### Encrypted vs plaintext bundles
@@ -229,7 +229,6 @@ After import, template changes propagate to deployed instances through revision-
| Key | Default | Description |
|-----|---------|-------------|
| `SourceEnvironment` | `"scadabridge"` | Environment label stamped in `manifest.json` and used in export filenames. |
| `SchemaVersionMajor` | `1` | Major schema version stamped in exported manifests. Not read by the importer; import version-gating uses `ManifestBuilder.CurrentBundleFormatVersion` directly. |
| `BundleSessionTtlMinutes` | `30` | TTL for an in-progress import session. |
| `MaxBundleSizeMb` | `100` | Upload size cap; enforced before any decompression. |
| `MaxBundleEntryCount` | `4` | Maximum ZIP entries (a valid bundle has exactly 2). |
+1 -1
View File
@@ -498,7 +498,7 @@ The `manifest.json` file is always present in the ZIP root and is never encrypte
| Field | Description |
|---|---|
| `bundleFormatVersion` | Integer. Importer hard-refuses any value higher than what its `TransportOptions.SchemaVersionMajor` supports. Stays `1` through T18. |
| `bundleFormatVersion` | Integer. Importer hard-refuses any value other than the code constant `ManifestBuilder.CurrentBundleFormatVersion`. Stays `1` through T18. The schema and format versions are code constants by design — there is no `Transport:SchemaVersionMajor` option (removed 2026-10-22; it was validated but never read). |
| `schemaVersion` | Semver string. Minor increments are additive-only and accepted by older importers. T18 bumped this `1.0 → 1.1` (adding the `sites`/`dataConnections`/`instances` content arrays); a pre-T18 importer lists those new entries as "skipped — unsupported in this version" since `ManifestValidator` gates only on `bundleFormatVersion`. |
| `createdAtUtc` | ISO-8601 UTC timestamp of when the export was created. |
| `sourceEnvironment` | The `SourceEnvironment` name of the exporting cluster (from `TransportOptions`). Displayed in the import wizard and required to be retyped at the confirm step. |
@@ -84,8 +84,7 @@
"MaxBundleSizeMb": 100,
"MaxUnlockAttemptsPerSession": 3,
"MaxUnlockAttemptsPerIpPerHour": 10,
"Pbkdf2Iterations": 600000,
"SchemaVersionMajor": 1
"Pbkdf2Iterations": 600000
},
"Logging": {
"MinimumLevel": "Information"
@@ -41,8 +41,6 @@ public sealed class TransportOptions
public int MaxUnlockAttemptsPerIpPerHour { get; set; } = 10;
/// <summary>Gets or sets the PBKDF2 iteration count used for passphrase key derivation.</summary>
public int Pbkdf2Iterations { get; set; } = 600_000;
/// <summary>Gets or sets the major version of the bundle schema this instance produces and accepts.</summary>
public int SchemaVersionMajor { get; set; } = 1;
/// <summary>
/// Human-readable name of the cluster/environment producing bundles. Stamped
@@ -60,10 +60,6 @@ public sealed class TransportOptionsValidator : OptionsValidatorBase<TransportOp
$"ScadaBridge:Transport:Pbkdf2Iterations must be at least {MinPbkdf2Iterations:N0} " +
$"(was {options.Pbkdf2Iterations}); a lower value silently weakens bundle key derivation.");
builder.RequireThat(options.SchemaVersionMajor > 0,
$"ScadaBridge:Transport:SchemaVersionMajor must be positive " +
$"(was {options.SchemaVersionMajor}); it is the major bundle-schema version this instance emits and accepts.");
builder.RequireThat(!string.IsNullOrWhiteSpace(options.SourceEnvironment),
"ScadaBridge:Transport:SourceEnvironment must be a non-empty name; it is stamped into " +
"BundleManifest.SourceEnvironment and the export filename.");
@@ -132,13 +132,15 @@ public class TransportOptionsValidatorTests
Assert.Contains("MaxUnlockAttemptsPerIpPerHour", result.FailureMessage);
}
/// <summary>Verifies that a zero major schema version is rejected.</summary>
/// <summary>
/// Verifies that there is no configurable schema-version option: the bundle schema
/// version is the code constant <c>ManifestBuilder.CurrentSchemaVersion</c>, so an
/// option would be a validated no-op (review 2026-10-21 04/U1, 08/O3).
/// </summary>
[Fact]
public void ZeroSchemaVersionMajor_IsRejected()
public void SchemaVersion_IsNotAConfigurableOption()
{
var result = Validate(new TransportOptions { SchemaVersionMajor = 0 });
Assert.True(result.Failed);
Assert.Contains("SchemaVersionMajor", result.FailureMessage);
Assert.Null(typeof(TransportOptions).GetProperty("SchemaVersionMajor"));
}
/// <summary>Verifies that a blank source environment is rejected.</summary>