From 41a5ecf14aa00ca18c789d7a1e7218e6435fe9d4 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 15 Aug 2026 12:56:12 -0400 Subject: [PATCH] feat(external-systems): deploy per-system retry policy to sites + CLI/management surface (register row 27) An external system's MaxRetries/RetryDelay existed on the central entity but never reached a site and had no management surface, so every ExternalSystem.CachedCall buffered with the S&F engine default (DefaultMaxRetries 50 x DefaultRetryInterval 30s ~= 25 min to park) whatever central had configured. The sibling DatabaseConnectionArtifact had always carried its equivalents. The consumption end was already wired: ExternalSystemClient.CachedCallAsync already read system.MaxRetries/RetryDelay and passed them to EnqueueAsync with the 0-as-unset convention. Only the transport and persistence were missing. Commons - ExternalSystemArtifact gains MaxRetries/RetryDelay as optional positional params (0 / TimeSpan.Zero = unset), matching DatabaseConnectionArtifact rather than going nullable, so it maps 1:1 onto the entity and the proto3 scalars. - Create/UpdateExternalSystemCommand gain nullable MaxRetries/RetryDelay, following the existing TimeoutSeconds "omit to preserve" convention. Both additive. Central - ManagementActor create maps both; update preserves-if-null. - ArtifactDeploymentService projects them onto the artifact. Wire - site_command.proto ExternalSystemArtifactDto gains max_retries=7 / retry_delay=8 (google.protobuf.Duration), mirroring DatabaseConnectionArtifactDto. Additive: a pre-existing site decodes them as 0/absent, which IS the unset value, so old behaviour is preserved. Vendored C# regenerated via docker/regen-proto.sh sitecommand; csproj Protobuf item re-commented, no message types added or removed. Site - external_systems gains max_retries / retry_delay_ms via AddColumnIfMissing, DEFAULT 0 = unset. Storage upsert, repository SELECT/mapping (new ordinals 6/7 appended so existing ones are untouched), apply path, and the legacy migrator's explicit column list all carry them. Replicated-table decision: external_systems IS in SiteLocalDbSetup.ReplicatedTables, so this was checked against the AckTime/native_alarm_state precedent. Real columns are correct here, not a JSON bag: the table has no general-purpose config column (auth_configuration and method_definitions are both semantically specific), timeout_seconds already set the additive-column precedent on this exact table, and sibling database_connections stores the same two values as plain columns. Verified in LocalDb source that RegisterReplicated re-reads PRAGMA table_info every boot and its install script is DROP-then-CREATE, so capture triggers rebuild over the new column set automatically, and SiteStorageSchema.Apply runs before registration on the same boot. The only cost is the ReplicatedTable.ComputeDigest change, which the sync handshake compares fail-closed - a mixed-version pair refuses to sync loudly instead of diverging silently. Both-nodes-together is already the standing site-pair rule, so no new operational constraint; recorded in topology-guide.md. Also fixed: ArtifactDiff.CompareExternalSystem never compared MaxRetries/RetryDelay, so a bundle import that changed a system's retry budget previewed as "Identical" - harmless while the values were inert, a live behaviour change now. No EF migration: the central ExternalSystemDefinitions columns already exist (InitialSchema). Central UI already had both form fields. Tests: site storage round-trip + unset-default, site apply path, central artifact projection, proto round-trip (via the shared sample), management create/preserve, CLI option parse + alias, and diff reporting. Fail-before verified on the two core tests (expected 7 got 0 on the read path; expected 9 got 0 on the apply path). Docs: Component-ExternalSystemGateway, Component-StoreAndForward, Component-CLI, components/StoreAndForward, components/SiteRuntime, deployment/topology-guide, CLI README (which was also missing --timeout-seconds entirely). --- docs/components/SiteRuntime.md | 2 +- docs/components/StoreAndForward.md | 2 + docs/deployment/topology-guide.md | 12 + docs/requirements/Component-CLI.md | 4 +- .../Component-ExternalSystemGateway.md | 2 +- .../requirements/Component-StoreAndForward.md | 13 + .../Commands/ExternalSystemCommands.cs | 22 +- src/ZB.MOM.WW.ScadaBridge.CLI/README.md | 20 +- .../Artifacts/ExternalSystemArtifact.cs | 34 +- .../Management/ExternalSystemCommands.cs | 19 +- .../Grpc/SiteCommandDtoMapper.cs | 8 +- .../Protos/site_command.proto | 5 + .../SiteCommandGrpc/SiteCommand.cs | 947 ++++++++++-------- .../ArtifactDeploymentService.cs | 7 +- .../SiteLocalDbLegacyMigrator.cs | 2 +- .../ManagementActor.cs | 9 +- .../Actors/DeploymentManagerActor.cs | 3 +- .../Persistence/SiteStorageSchema.cs | 22 + .../Persistence/SiteStorageService.cs | 18 +- .../SiteExternalSystemRepository.cs | 11 +- .../Import/ArtifactDiff.cs | 5 + .../ExternalSystemRetryOptionTests.cs | 102 ++ .../SiteCommandSamples.cs | 6 +- .../ArtifactDeploymentServiceTests.cs | 33 + .../ManagementActorTests.cs | 55 + .../Actors/DeploymentManagerActorTests.cs | 29 + .../Repositories/SiteRepositoryTests.cs | 46 + .../Import/ArtifactDiffTests.cs | 51 + 28 files changed, 1036 insertions(+), 453 deletions(-) create mode 100644 tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/Commands/ExternalSystemRetryOptionTests.cs diff --git a/docs/components/SiteRuntime.md b/docs/components/SiteRuntime.md index 1203acbe..fd9dcdfa 100644 --- a/docs/components/SiteRuntime.md +++ b/docs/components/SiteRuntime.md @@ -205,7 +205,7 @@ Both `AlarmActor` and `NativeAlarmActor` tell the `InstanceActor` an `AlarmState | `static_attribute_overrides` | Runtime attribute writes (`SetAttribute` on static attrs) | Yes — cleared by `ClearStaticOverridesAsync` | | `native_alarm_state` | Mirrored native alarm conditions (survives failover) | Yes — cleared by `ClearNativeAlarmsForInstanceAsync` | | `shared_scripts` | Shared script code from artifact deployments | No | -| `external_systems` | External system definitions | No | +| `external_systems` | External system definitions (incl. `timeout_seconds`, and `max_retries`/`retry_delay_ms` — the deployed store-and-forward retry policy, `0` = use the engine default) | No | | `database_connections` | Database connection strings | No | | `data_connection_definitions` | OPC UA / MxGateway endpoint definitions | No | | `notification_lists` | Notification list definitions | No | diff --git a/docs/components/StoreAndForward.md b/docs/components/StoreAndForward.md index a770579f..29e4ea69 100644 --- a/docs/components/StoreAndForward.md +++ b/docs/components/StoreAndForward.md @@ -51,6 +51,8 @@ The retry interval is fixed — not exponential. There is no maximum buffer size `StoreAndForwardMessage.MaxRetries` controls how many background-sweep attempts the engine makes before parking. `MaxRetries = 0` means **no limit** — the message retries on every sweep until delivered and is never parked for retry exhaustion. It is not a "never retry" value; callers that want unbounded retry pass `maxRetries: 0` explicitly. The `EnqueueAsync` `maxRetries` parameter defaults to `StoreAndForwardOptions.DefaultMaxRetries` (50). +**The source-entity `0` means the opposite, and the gateways convert.** On an `ExternalSystemDefinition` / `DatabaseConnectionDefinition`, `MaxRetries = 0` (and `RetryDelay = TimeSpan.Zero`) mean *unset*, not *unbounded* — so `ExternalSystemClient.CachedCallAsync` and `DatabaseGateway` pass `system.MaxRetries > 0 ? system.MaxRetries : null`, letting the bounded engine default apply. Forwarding the entity's `0` verbatim would make every unconfigured cached call retry forever. The external system's values only actually reach a site as of deferred-work row 27; before that the artifact did not carry them. + ### Messages not cleared on instance deletion When an instance is deleted, its buffered S&F messages are not removed. `StoreAndForwardMessage.OriginInstanceName` records the originating instance at enqueue time so the buffer can continue to deliver and so the central UI can attribute parked messages even after the instance is gone. diff --git a/docs/deployment/topology-guide.md b/docs/deployment/topology-guide.md index d06e5211..dec3c32c 100644 --- a/docs/deployment/topology-guide.md +++ b/docs/deployment/topology-guide.md @@ -265,6 +265,18 @@ will run, but they will not converge, and the divergence is silent. Stop both nodes of a site pair, upgrade both, then start both. +**A release that adds a column to a replicated table is the same constraint, enforced louder.** +`ReplicatedTable.ComputeDigest` hashes every column name and declared type, and the sync handshake +compares those digests fail-closed — so a mixed-version pair whose builds disagree about a table's +columns does not diverge silently, it refuses to sync with a `LocalDbSchemaMismatchException`. +Stopping and starting both nodes together, which is already the rule above, is the whole fix; no +manual trigger or oplog migration is needed, because `RegisterReplicated` re-reads +`PRAGMA table_info` on every boot and rebuilds the capture triggers (DROP + CREATE) over the +current column set. The site schema's own `AddColumnIfMissing` runs before registration on that +same boot, so a node picks up the new columns and the matching triggers in one start. +(Most recent instance: `external_systems` gained `max_retries` / `retry_delay_ms` for the +per-system store-and-forward retry policy.) + **Related bound — do not leave one node of a pair offline for long.** A node absent for longer than `LocalDb:Replication:TombstoneRetention` (default **7 days**) can **resurrect deleted rows** when it rejoins: deletes replicate as HLC-ordered tombstones, and once a tombstone is pruned there is diff --git a/docs/requirements/Component-CLI.md b/docs/requirements/Component-CLI.md index 1b209303..72edc23e 100644 --- a/docs/requirements/Component-CLI.md +++ b/docs/requirements/Component-CLI.md @@ -194,8 +194,8 @@ scadabridge data-connection certs remove --site --thumbprint ``` scadabridge external-system list scadabridge external-system get --id -scadabridge external-system create --name --endpoint-url --auth-type [--auth-config ] -scadabridge external-system update --id [--name ] [--endpoint-url ] [--auth-type ] [--auth-config ] +scadabridge external-system create --name --endpoint-url --auth-type [--auth-config ] [--timeout-seconds ] [--max-retries ] [--retry-delay-seconds ] +scadabridge external-system update --id [--name ] [--endpoint-url ] [--auth-type ] [--auth-config ] [--timeout-seconds ] [--max-retries ] [--retry-delay-seconds ] scadabridge external-system delete --id scadabridge external-system method list --external-system-id scadabridge external-system method get --id diff --git a/docs/requirements/Component-ExternalSystemGateway.md b/docs/requirements/Component-ExternalSystemGateway.md index b578f67d..2e100873 100644 --- a/docs/requirements/Component-ExternalSystemGateway.md +++ b/docs/requirements/Component-ExternalSystemGateway.md @@ -42,7 +42,7 @@ Each external system definition includes: **Legacy colon form (deprecated):** a value that does not start with `{` is parsed with the historical colon-split rules for backward compatibility — API Key as `HeaderName:KeyValue` (or a bare `KeyValue` using the default `X-API-Key` header), Basic Auth as `username:password`. This form cannot represent keys or passwords that contain `:` and is retained only so existing rows keep working; new definitions should use the JSON form. Malformed JSON (a value that starts with `{` but does not parse, or yields no usable `key`/`username`) is **not** an exception — the request is sent without the auth header and a warning is logged (the configuration value itself is never logged). - **Timeout**: Per-system timeout for all method calls (e.g., 30 seconds). Applies to the HTTP request round-trip. `0` (the default) means "unset" — the gateway's configured `DefaultHttpTimeout` applies instead. Set via the Central UI External System form's "Timeout (seconds)" field; ships to sites in the deployment artifact like the other definition fields. -- **Retry Settings**: Max retry count, fixed time between retries (used by Store-and-Forward Engine for transient failures only). +- **Retry Settings**: Max retry count, fixed time between retries (used by Store-and-Forward Engine for transient failures only). Both are **`0` = unset**, in which case the engine's `DefaultMaxRetries` (50) and `DefaultRetryInterval` (30 s) apply — note this is the inverse of the S&F engine's own `MaxRetries` contract, where a stored `0` means *unbounded* retry; the gateway translates an unset `0` to "no per-system override" rather than passing it through. Authored via the Central UI External System form ("Max Retries" / "Retry Delay (seconds)") or `external-system create|update --max-retries --retry-delay-seconds`, and **deployed to sites in the external-system artifact** alongside the timeout, so the site's cached-call buffering honours the configured policy. (Before the row-27 fix the artifact carried no retry fields and the site table had no columns for them, so a centrally-configured policy was silently ignored and every cached call used the engine default.) - **Method Definitions**: List of available API methods, each with: - Method name. - **HTTP method**: GET, POST, PUT, PATCH, or DELETE. diff --git a/docs/requirements/Component-StoreAndForward.md b/docs/requirements/Component-StoreAndForward.md index 669c583c..589939ec 100644 --- a/docs/requirements/Component-StoreAndForward.md +++ b/docs/requirements/Component-StoreAndForward.md @@ -63,6 +63,19 @@ For the external-system-call and cached-database-write categories, retry setting - **External systems**: Each external system definition includes max retry count and time between retries. - **Cached database writes**: Each database connection definition includes max retry count and time between retries. +Both are carried to the site in the deployment artifact and read back out of the site's own +tables by the gateway, which passes them to `EnqueueAsync`. An **unset** source-entity value +(`MaxRetries` `0` / `RetryDelay` `TimeSpan.Zero`) is passed as `null`, so the engine's +`DefaultMaxRetries` (50) and `DefaultRetryInterval` (30 s) apply — roughly 25 minutes to park. +The translation is deliberate and must not be removed: the engine reads a stored `MaxRetries` +of `0` as **no limit**, so forwarding an unconfigured entity's `0` verbatim would turn every +cached call into an unbounded retry loop. + +> Until deferred-work row 27 was fixed, this was true of database connections only. +> `ExternalSystemArtifact` carried no retry fields and the site `external_systems` table had no +> columns for them, so an external system's centrally-configured policy never reached the site +> and every external-system cached call silently used the 50 x 30 s default. + The **notification** category retries differently: it has no source-entity setting. The site→central forward uses a single fixed retry interval configured in the host `appsettings.json`. This interval is infrastructure config for reaching the central cluster, not a per-notification-list setting. It applies uniformly to every buffered notification regardless of its target list. A buffered notification is retried at that interval until central acks it; the engine's `DefaultMaxRetries` cap still applies (matching the cached-call categories) and a notification whose retries are exhausted under a sustained central outage parks like any other buffered message. The cap is sized so the normal central-recovery window stays well inside it — central, once reachable, owns delivery, retry, and parking from the ack point on. The retry interval is **fixed** (not exponential backoff). Fixed interval is sufficient for the expected use cases. diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/ExternalSystemCommands.cs b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/ExternalSystemCommands.cs index 3df4dc10..d3c908f8 100644 --- a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/ExternalSystemCommands.cs +++ b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/ExternalSystemCommands.cs @@ -50,6 +50,9 @@ public static class ExternalSystemCommands var authTypeOption = new Option("--auth-type") { Description = "Auth type", Required = true }; var authConfigOption = new Option("--auth-config") { Description = "Auth configuration JSON" }; var timeoutOption = new Option("--timeout-seconds") { Description = "Per-system HTTP timeout in seconds (0 = use gateway default); omit to preserve" }; + var maxRetriesOption = new Option("--max-retries") { Description = "Store-and-forward retry budget for cached calls (0 = use engine default); omit to preserve" }; + var retryDelayOption = new Option("--retry-delay-seconds") { Description = "Fixed interval between store-and-forward retries, in seconds (0 = use engine default); omit to preserve" }; + retryDelayOption.Aliases.Add("--retry-delay"); var cmd = new Command("update") { Description = "Update an external system" }; cmd.Add(idOption); @@ -58,6 +61,8 @@ public static class ExternalSystemCommands cmd.Add(authTypeOption); cmd.Add(authConfigOption); cmd.Add(timeoutOption); + cmd.Add(maxRetriesOption); + cmd.Add(retryDelayOption); cmd.SetAction(async (ParseResult result) => { var id = result.GetValue(idOption); @@ -66,9 +71,13 @@ public static class ExternalSystemCommands var authType = result.GetValue(authTypeOption)!; var authConfig = result.GetValue(authConfigOption); var timeoutSeconds = result.GetValue(timeoutOption); + var maxRetries = result.GetValue(maxRetriesOption); + var retryDelaySeconds = result.GetValue(retryDelayOption); return await CommandHelpers.ExecuteCommandAsync( result, urlOption, formatOption, usernameOption, passwordOption, - new UpdateExternalSystemCommand(id, name, endpointUrl, authType, authConfig, timeoutSeconds)); + new UpdateExternalSystemCommand(id, name, endpointUrl, authType, authConfig, timeoutSeconds, + maxRetries, + retryDelaySeconds is { } rd ? TimeSpan.FromSeconds(rd) : null)); }); return cmd; } @@ -91,6 +100,9 @@ public static class ExternalSystemCommands var authTypeOption = new Option("--auth-type") { Description = "Auth type (ApiKey, BasicAuth)", Required = true }; var authConfigOption = new Option("--auth-config") { Description = "Auth configuration JSON" }; var timeoutOption = new Option("--timeout-seconds") { Description = "Per-system HTTP timeout in seconds (0 = use gateway default)" }; + var maxRetriesOption = new Option("--max-retries") { Description = "Store-and-forward retry budget for cached calls (0 = use engine default)" }; + var retryDelayOption = new Option("--retry-delay-seconds") { Description = "Fixed interval between store-and-forward retries, in seconds (0 = use engine default)" }; + retryDelayOption.Aliases.Add("--retry-delay"); var cmd = new Command("create") { Description = "Create an external system" }; cmd.Add(nameOption); @@ -98,6 +110,8 @@ public static class ExternalSystemCommands cmd.Add(authTypeOption); cmd.Add(authConfigOption); cmd.Add(timeoutOption); + cmd.Add(maxRetriesOption); + cmd.Add(retryDelayOption); cmd.SetAction(async (ParseResult result) => { var name = result.GetValue(nameOption)!; @@ -105,9 +119,13 @@ public static class ExternalSystemCommands var authType = result.GetValue(authTypeOption)!; var authConfig = result.GetValue(authConfigOption); var timeoutSeconds = result.GetValue(timeoutOption); + var maxRetries = result.GetValue(maxRetriesOption); + var retryDelaySeconds = result.GetValue(retryDelayOption); return await CommandHelpers.ExecuteCommandAsync( result, urlOption, formatOption, usernameOption, passwordOption, - new CreateExternalSystemCommand(name, endpointUrl, authType, authConfig, timeoutSeconds)); + new CreateExternalSystemCommand(name, endpointUrl, authType, authConfig, timeoutSeconds, + maxRetries, + retryDelaySeconds is { } rd ? TimeSpan.FromSeconds(rd) : null)); }); return cmd; } diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/README.md b/src/ZB.MOM.WW.ScadaBridge.CLI/README.md index 0f94170a..4a518319 100644 --- a/src/ZB.MOM.WW.ScadaBridge.CLI/README.md +++ b/src/ZB.MOM.WW.ScadaBridge.CLI/README.md @@ -1206,7 +1206,7 @@ scadabridge --url external-system list Register an external HTTP system that scripts can call. ```sh -scadabridge --url external-system create --name --endpoint-url --auth-type [--auth-config ] +scadabridge --url external-system create --name --endpoint-url --auth-type [--auth-config ] [--timeout-seconds ] [--max-retries ] [--retry-delay-seconds ] ``` | Option | Required | Description | @@ -1215,6 +1215,13 @@ scadabridge --url external-system create --name --endpoint-url external-system update --id --name --endpoint-url --auth-type [--auth-config ] +scadabridge --url external-system update --id --name --endpoint-url --auth-type [--auth-config ] [--timeout-seconds ] [--max-retries ] [--retry-delay-seconds ] ``` | Option | Required | Description | @@ -1231,7 +1238,14 @@ scadabridge --url external-system update --id --name --endp | `--name` | yes | Display name | | `--endpoint-url` | yes | Base URL | | `--auth-type` | yes | Authentication type | -| `--auth-config` | no | Updated auth credentials as a JSON string | +| `--auth-config` | no | Updated auth credentials as a JSON string; omit to preserve the stored value | +| `--timeout-seconds` | no | Per-system HTTP round-trip timeout; omit to preserve | +| `--max-retries` | no | Store-and-forward retry budget for this system's `CachedCall`s; omit to preserve | +| `--retry-delay-seconds` | no | Fixed interval between store-and-forward retries; omit to preserve. Alias: `--retry-delay` | + +Note the asymmetry with the required fields above: the four optional settings follow an +**omit-to-preserve** rule, so a rename does not silently reset a system's auth config, +timeout, or retry policy. #### `external-system delete` diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Artifacts/ExternalSystemArtifact.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Artifacts/ExternalSystemArtifact.cs index 6e689145..09ec9fd3 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Artifacts/ExternalSystemArtifact.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Artifacts/ExternalSystemArtifact.cs @@ -1,9 +1,41 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts; +/// +/// A single external HTTP system, flattened for deployment to a site. +/// +/// Unique system name — the key scripts call it by. +/// Base URL of the system's HTTP endpoint. +/// Authentication type identifier (e.g. ApiKey, BasicAuth). +/// JSON-serialized authentication configuration for . +/// JSON-serialized method list, or null when the system defines none. +/// +/// Per-system HTTP round-trip timeout in seconds. 0 = unset — the gateway's configured +/// DefaultHttpTimeout applies. +/// +/// +/// Store-and-Forward retry budget for this system's cached calls. 0 = unset — the engine's +/// DefaultMaxRetries applies. +/// +/// 0 means "unset", not "no limit", at THIS layer — the inverse of +/// StoreAndForwardMessage.MaxRetries, where a stored 0 means unbounded retry. The two +/// meanings are reconciled in ExternalSystemClient.CachedCallAsync, which passes a 0 +/// through as null so the bounded engine default applies rather than an unbounded retry +/// loop. Keeping "unset" as 0 here (rather than a nullable) matches +/// , the non-nullable +/// ExternalSystemDefinition.MaxRetries entity property this is projected from, and the +/// proto3 scalar it is carried on. +/// +/// +/// +/// Fixed interval between Store-and-Forward retry sweeps for this system's cached calls. +/// = unset — the engine's DefaultRetryInterval applies. +/// public record ExternalSystemArtifact( string Name, string EndpointUrl, string AuthType, string? AuthConfiguration, string? MethodDefinitionsJson, - int TimeoutSeconds = 0); + int TimeoutSeconds = 0, + int MaxRetries = 0, + TimeSpan RetryDelay = default); diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/ExternalSystemCommands.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/ExternalSystemCommands.cs index 856749b6..8cea22ec 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/ExternalSystemCommands.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/ExternalSystemCommands.cs @@ -2,8 +2,23 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management; public record ListExternalSystemsCommand; public record GetExternalSystemCommand(int ExternalSystemId); -public record CreateExternalSystemCommand(string Name, string EndpointUrl, string AuthType, string? AuthConfiguration, int? TimeoutSeconds = null); -public record UpdateExternalSystemCommand(int ExternalSystemId, string Name, string EndpointUrl, string AuthType, string? AuthConfiguration, int? TimeoutSeconds = null); +/// +/// Creates an external HTTP system. +/// +/// +/// and are the Store-and-Forward +/// retry policy applied to this system's ExternalSystem.CachedCall buffering. Both are +/// nullable on the wire so "omit" is distinguishable from "set to 0"; a null (or an explicit 0) +/// stores the entity's unset value, which the site gateway translates into the engine's +/// DefaultMaxRetries/DefaultRetryInterval. +/// +public record CreateExternalSystemCommand(string Name, string EndpointUrl, string AuthType, string? AuthConfiguration, int? TimeoutSeconds = null, int? MaxRetries = null, TimeSpan? RetryDelay = null); + +/// +/// Updates an external HTTP system. Nullable members follow the "omit to preserve" convention — +/// a null leaves the stored value untouched. +/// +public record UpdateExternalSystemCommand(int ExternalSystemId, string Name, string EndpointUrl, string AuthType, string? AuthConfiguration, int? TimeoutSeconds = null, int? MaxRetries = null, TimeSpan? RetryDelay = null); public record DeleteExternalSystemCommand(int ExternalSystemId); // External System Methods diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteCommandDtoMapper.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteCommandDtoMapper.cs index 97508ba8..357fe918 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteCommandDtoMapper.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteCommandDtoMapper.cs @@ -506,7 +506,9 @@ public static class SiteCommandDtoMapper AuthType = artifact.AuthType, AuthConfiguration = OrEmpty(artifact.AuthConfiguration), MethodDefinitionsJson = OrEmpty(artifact.MethodDefinitionsJson), - TimeoutSeconds = artifact.TimeoutSeconds + TimeoutSeconds = artifact.TimeoutSeconds, + MaxRetries = artifact.MaxRetries, + RetryDelay = Duration.FromTimeSpan(artifact.RetryDelay) }; } @@ -523,7 +525,9 @@ public static class SiteCommandDtoMapper dto.AuthType, NullIfEmpty(dto.AuthConfiguration), NullIfEmpty(dto.MethodDefinitionsJson), - dto.TimeoutSeconds); + dto.TimeoutSeconds, + dto.MaxRetries, + dto.RetryDelay?.ToTimeSpan() ?? TimeSpan.Zero); } /// Projects a onto the wire. diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Protos/site_command.proto b/src/ZB.MOM.WW.ScadaBridge.Communication/Protos/site_command.proto index 8c6fa912..813a09ad 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Communication/Protos/site_command.proto +++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Protos/site_command.proto @@ -224,6 +224,11 @@ message ExternalSystemArtifactDto { string auth_configuration = 4; // empty string represents null string method_definitions_json = 5; // empty string represents null int32 timeout_seconds = 6; + // Store-and-Forward retry policy for this system's cached calls. Additive (fields 7-8); + // a pre-existing site decodes them as 0/absent, which is exactly the "unset" value that + // makes the engine defaults apply — i.e. the old behaviour. + int32 max_retries = 7; + google.protobuf.Duration retry_delay = 8; } message DatabaseConnectionArtifactDto { diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/SiteCommandGrpc/SiteCommand.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/SiteCommandGrpc/SiteCommand.cs index fda7176e..fa79f645 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Communication/SiteCommandGrpc/SiteCommand.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Communication/SiteCommandGrpc/SiteCommand.cs @@ -64,438 +64,439 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc { "Lmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAibwoXU2hhcmVkU2NyaXB0QXJ0", "aWZhY3REdG8SDAoEbmFtZRgBIAEoCRIMCgRjb2RlGAIgASgJEh0KFXBhcmFt", "ZXRlcl9kZWZpbml0aW9ucxgDIAEoCRIZChFyZXR1cm5fZGVmaW5pdGlvbhgE", - "IAEoCSKoAQoZRXh0ZXJuYWxTeXN0ZW1BcnRpZmFjdER0bxIMCgRuYW1lGAEg", + "IAEoCSLtAQoZRXh0ZXJuYWxTeXN0ZW1BcnRpZmFjdER0bxIMCgRuYW1lGAEg", "ASgJEhQKDGVuZHBvaW50X3VybBgCIAEoCRIRCglhdXRoX3R5cGUYAyABKAkS", "GgoSYXV0aF9jb25maWd1cmF0aW9uGAQgASgJEh8KF21ldGhvZF9kZWZpbml0", - "aW9uc19qc29uGAUgASgJEhcKD3RpbWVvdXRfc2Vjb25kcxgGIAEoBSKNAQod", - "RGF0YWJhc2VDb25uZWN0aW9uQXJ0aWZhY3REdG8SDAoEbmFtZRgBIAEoCRIZ", - "ChFjb25uZWN0aW9uX3N0cmluZxgCIAEoCRITCgttYXhfcmV0cmllcxgDIAEo", - "BRIuCgtyZXRyeV9kZWxheRgEIAEoCzIZLmdvb2dsZS5wcm90b2J1Zi5EdXJh", - "dGlvbiJFChtOb3RpZmljYXRpb25MaXN0QXJ0aWZhY3REdG8SDAoEbmFtZRgB", - "IAEoCRIYChByZWNpcGllbnRfZW1haWxzGAIgAygJIqABChlEYXRhQ29ubmVj", - "dGlvbkFydGlmYWN0RHRvEgwKBG5hbWUYASABKAkSEAoIcHJvdG9jb2wYAiAB", - "KAkSIgoacHJpbWFyeV9jb25maWd1cmF0aW9uX2pzb24YAyABKAkSIQoZYmFj", - "a3VwX2NvbmZpZ3VyYXRpb25fanNvbhgEIAEoCRIcChRmYWlsb3Zlcl9yZXRy", - "eV9jb3VudBgFIAEoBSKtAQocU210cENvbmZpZ3VyYXRpb25BcnRpZmFjdER0", - "bxIMCgRuYW1lGAEgASgJEg4KBnNlcnZlchgCIAEoCRIMCgRwb3J0GAMgASgF", - "EhEKCWF1dGhfbW9kZRgEIAEoCRIUCgxmcm9tX2FkZHJlc3MYBSABKAkSEAoI", - "dXNlcm5hbWUYBiABKAkSEAoIcGFzc3dvcmQYByABKAkSFAoMb2F1dGhfY29u", - "ZmlnGAggASgJImEKG1NoYXJlZFNjcmlwdEFydGlmYWN0TGlzdER0bxJCCgVp", - "dGVtcxgBIAMoCzIzLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlNoYXJl", - "ZFNjcmlwdEFydGlmYWN0RHRvImUKHUV4dGVybmFsU3lzdGVtQXJ0aWZhY3RM", - "aXN0RHRvEkQKBWl0ZW1zGAEgAygLMjUuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1h", - "bmQudjEuRXh0ZXJuYWxTeXN0ZW1BcnRpZmFjdER0byJtCiFEYXRhYmFzZUNv", - "bm5lY3Rpb25BcnRpZmFjdExpc3REdG8SSAoFaXRlbXMYASADKAsyOS5zY2Fk", - "YWJyaWRnZS5zaXRlY29tbWFuZC52MS5EYXRhYmFzZUNvbm5lY3Rpb25BcnRp", - "ZmFjdER0byJpCh9Ob3RpZmljYXRpb25MaXN0QXJ0aWZhY3RMaXN0RHRvEkYK", - "BWl0ZW1zGAEgAygLMjcuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuTm90", - "aWZpY2F0aW9uTGlzdEFydGlmYWN0RHRvImUKHURhdGFDb25uZWN0aW9uQXJ0", - "aWZhY3RMaXN0RHRvEkQKBWl0ZW1zGAEgAygLMjUuc2NhZGFicmlkZ2Uuc2l0", - "ZWNvbW1hbmQudjEuRGF0YUNvbm5lY3Rpb25BcnRpZmFjdER0byJrCiBTbXRw", - "Q29uZmlndXJhdGlvbkFydGlmYWN0TGlzdER0bxJHCgVpdGVtcxgBIAMoCzI4", - "LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlNtdHBDb25maWd1cmF0aW9u", - "QXJ0aWZhY3REdG8i7QQKGURlcGxveUFydGlmYWN0c0NvbW1hbmREdG8SFQoN", - "ZGVwbG95bWVudF9pZBgBIAEoCRJPCg5zaGFyZWRfc2NyaXB0cxgCIAEoCzI3", - "LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlNoYXJlZFNjcmlwdEFydGlm", - "YWN0TGlzdER0bxJTChBleHRlcm5hbF9zeXN0ZW1zGAMgASgLMjkuc2NhZGFi", - "cmlkZ2Uuc2l0ZWNvbW1hbmQudjEuRXh0ZXJuYWxTeXN0ZW1BcnRpZmFjdExp", - "c3REdG8SWwoUZGF0YWJhc2VfY29ubmVjdGlvbnMYBCABKAsyPS5zY2FkYWJy", - "aWRnZS5zaXRlY29tbWFuZC52MS5EYXRhYmFzZUNvbm5lY3Rpb25BcnRpZmFj", - "dExpc3REdG8SVwoSbm90aWZpY2F0aW9uX2xpc3RzGAUgASgLMjsuc2NhZGFi", + "aW9uc19qc29uGAUgASgJEhcKD3RpbWVvdXRfc2Vjb25kcxgGIAEoBRITCgtt", + "YXhfcmV0cmllcxgHIAEoBRIuCgtyZXRyeV9kZWxheRgIIAEoCzIZLmdvb2ds", + "ZS5wcm90b2J1Zi5EdXJhdGlvbiKNAQodRGF0YWJhc2VDb25uZWN0aW9uQXJ0", + "aWZhY3REdG8SDAoEbmFtZRgBIAEoCRIZChFjb25uZWN0aW9uX3N0cmluZxgC", + "IAEoCRITCgttYXhfcmV0cmllcxgDIAEoBRIuCgtyZXRyeV9kZWxheRgEIAEo", + "CzIZLmdvb2dsZS5wcm90b2J1Zi5EdXJhdGlvbiJFChtOb3RpZmljYXRpb25M", + "aXN0QXJ0aWZhY3REdG8SDAoEbmFtZRgBIAEoCRIYChByZWNpcGllbnRfZW1h", + "aWxzGAIgAygJIqABChlEYXRhQ29ubmVjdGlvbkFydGlmYWN0RHRvEgwKBG5h", + "bWUYASABKAkSEAoIcHJvdG9jb2wYAiABKAkSIgoacHJpbWFyeV9jb25maWd1", + "cmF0aW9uX2pzb24YAyABKAkSIQoZYmFja3VwX2NvbmZpZ3VyYXRpb25fanNv", + "bhgEIAEoCRIcChRmYWlsb3Zlcl9yZXRyeV9jb3VudBgFIAEoBSKtAQocU210", + "cENvbmZpZ3VyYXRpb25BcnRpZmFjdER0bxIMCgRuYW1lGAEgASgJEg4KBnNl", + "cnZlchgCIAEoCRIMCgRwb3J0GAMgASgFEhEKCWF1dGhfbW9kZRgEIAEoCRIU", + "Cgxmcm9tX2FkZHJlc3MYBSABKAkSEAoIdXNlcm5hbWUYBiABKAkSEAoIcGFz", + "c3dvcmQYByABKAkSFAoMb2F1dGhfY29uZmlnGAggASgJImEKG1NoYXJlZFNj", + "cmlwdEFydGlmYWN0TGlzdER0bxJCCgVpdGVtcxgBIAMoCzIzLnNjYWRhYnJp", + "ZGdlLnNpdGVjb21tYW5kLnYxLlNoYXJlZFNjcmlwdEFydGlmYWN0RHRvImUK", + "HUV4dGVybmFsU3lzdGVtQXJ0aWZhY3RMaXN0RHRvEkQKBWl0ZW1zGAEgAygL", + "MjUuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuRXh0ZXJuYWxTeXN0ZW1B", + "cnRpZmFjdER0byJtCiFEYXRhYmFzZUNvbm5lY3Rpb25BcnRpZmFjdExpc3RE", + "dG8SSAoFaXRlbXMYASADKAsyOS5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52", + "MS5EYXRhYmFzZUNvbm5lY3Rpb25BcnRpZmFjdER0byJpCh9Ob3RpZmljYXRp", + "b25MaXN0QXJ0aWZhY3RMaXN0RHRvEkYKBWl0ZW1zGAEgAygLMjcuc2NhZGFi", "cmlkZ2Uuc2l0ZWNvbW1hbmQudjEuTm90aWZpY2F0aW9uTGlzdEFydGlmYWN0", - "TGlzdER0bxJTChBkYXRhX2Nvbm5lY3Rpb25zGAYgASgLMjkuc2NhZGFicmlk", - "Z2Uuc2l0ZWNvbW1hbmQudjEuRGF0YUNvbm5lY3Rpb25BcnRpZmFjdExpc3RE", - "dG8SWQoTc210cF9jb25maWd1cmF0aW9ucxgHIAEoCzI8LnNjYWRhYnJpZGdl", - "LnNpdGVjb21tYW5kLnYxLlNtdHBDb25maWd1cmF0aW9uQXJ0aWZhY3RMaXN0", - "RHRvEi0KCXRpbWVzdGFtcBgIIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1l", - "c3RhbXAi2QEKG0RlcGxveW1lbnRTdGF0dXNSZXNwb25zZUR0bxIVCg1kZXBs", - "b3ltZW50X2lkGAEgASgJEhwKFGluc3RhbmNlX3VuaXF1ZV9uYW1lGAIgASgJ", - "Ej8KBnN0YXR1cxgDIAEoDjIvLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYx", - "LkRlcGxveW1lbnRTdGF0dXNEdG8SFQoNZXJyb3JfbWVzc2FnZRgEIAEoCRIt", - "Cgl0aW1lc3RhbXAYBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1w", - "IqcBChxJbnN0YW5jZUxpZmVjeWNsZVJlc3BvbnNlRHRvEhIKCmNvbW1hbmRf", - "aWQYASABKAkSHAoUaW5zdGFuY2VfdW5pcXVlX25hbWUYAiABKAkSDwoHc3Vj", + "RHRvImUKHURhdGFDb25uZWN0aW9uQXJ0aWZhY3RMaXN0RHRvEkQKBWl0ZW1z", + "GAEgAygLMjUuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuRGF0YUNvbm5l", + "Y3Rpb25BcnRpZmFjdER0byJrCiBTbXRwQ29uZmlndXJhdGlvbkFydGlmYWN0", + "TGlzdER0bxJHCgVpdGVtcxgBIAMoCzI4LnNjYWRhYnJpZGdlLnNpdGVjb21t", + "YW5kLnYxLlNtdHBDb25maWd1cmF0aW9uQXJ0aWZhY3REdG8i7QQKGURlcGxv", + "eUFydGlmYWN0c0NvbW1hbmREdG8SFQoNZGVwbG95bWVudF9pZBgBIAEoCRJP", + "Cg5zaGFyZWRfc2NyaXB0cxgCIAEoCzI3LnNjYWRhYnJpZGdlLnNpdGVjb21t", + "YW5kLnYxLlNoYXJlZFNjcmlwdEFydGlmYWN0TGlzdER0bxJTChBleHRlcm5h", + "bF9zeXN0ZW1zGAMgASgLMjkuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEu", + "RXh0ZXJuYWxTeXN0ZW1BcnRpZmFjdExpc3REdG8SWwoUZGF0YWJhc2VfY29u", + "bmVjdGlvbnMYBCABKAsyPS5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5E", + "YXRhYmFzZUNvbm5lY3Rpb25BcnRpZmFjdExpc3REdG8SVwoSbm90aWZpY2F0", + "aW9uX2xpc3RzGAUgASgLMjsuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEu", + "Tm90aWZpY2F0aW9uTGlzdEFydGlmYWN0TGlzdER0bxJTChBkYXRhX2Nvbm5l", + "Y3Rpb25zGAYgASgLMjkuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuRGF0", + "YUNvbm5lY3Rpb25BcnRpZmFjdExpc3REdG8SWQoTc210cF9jb25maWd1cmF0", + "aW9ucxgHIAEoCzI8LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlNtdHBD", + "b25maWd1cmF0aW9uQXJ0aWZhY3RMaXN0RHRvEi0KCXRpbWVzdGFtcBgIIAEo", + "CzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAi2QEKG0RlcGxveW1lbnRT", + "dGF0dXNSZXNwb25zZUR0bxIVCg1kZXBsb3ltZW50X2lkGAEgASgJEhwKFGlu", + "c3RhbmNlX3VuaXF1ZV9uYW1lGAIgASgJEj8KBnN0YXR1cxgDIAEoDjIvLnNj", + "YWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkRlcGxveW1lbnRTdGF0dXNEdG8S", + "FQoNZXJyb3JfbWVzc2FnZRgEIAEoCRItCgl0aW1lc3RhbXAYBSABKAsyGi5n", + "b29nbGUucHJvdG9idWYuVGltZXN0YW1wIqcBChxJbnN0YW5jZUxpZmVjeWNs", + "ZVJlc3BvbnNlRHRvEhIKCmNvbW1hbmRfaWQYASABKAkSHAoUaW5zdGFuY2Vf", + "dW5pcXVlX25hbWUYAiABKAkSDwoHc3VjY2VzcxgDIAEoCBIVCg1lcnJvcl9t", + "ZXNzYWdlGAQgASgJEi0KCXRpbWVzdGFtcBgFIAEoCzIaLmdvb2dsZS5wcm90", + "b2J1Zi5UaW1lc3RhbXAi2QEKH0RlcGxveW1lbnRTdGF0ZVF1ZXJ5UmVzcG9u", + "c2VEdG8SFgoOY29ycmVsYXRpb25faWQYASABKAkSHAoUaW5zdGFuY2VfdW5p", + "cXVlX25hbWUYAiABKAkSEwoLaXNfZGVwbG95ZWQYAyABKAgSHQoVYXBwbGll", + "ZF9kZXBsb3ltZW50X2lkGAQgASgJEh0KFWFwcGxpZWRfcmV2aXNpb25faGFz", + "aBgFIAEoCRItCgl0aW1lc3RhbXAYBiABKAsyGi5nb29nbGUucHJvdG9idWYu", + "VGltZXN0YW1wIp4BCh1BcnRpZmFjdERlcGxveW1lbnRSZXNwb25zZUR0bxIV", + "Cg1kZXBsb3ltZW50X2lkGAEgASgJEg8KB3NpdGVfaWQYAiABKAkSDwoHc3Vj", "Y2VzcxgDIAEoCBIVCg1lcnJvcl9tZXNzYWdlGAQgASgJEi0KCXRpbWVzdGFt", - "cBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAi2QEKH0RlcGxv", - "eW1lbnRTdGF0ZVF1ZXJ5UmVzcG9uc2VEdG8SFgoOY29ycmVsYXRpb25faWQY", - "ASABKAkSHAoUaW5zdGFuY2VfdW5pcXVlX25hbWUYAiABKAkSEwoLaXNfZGVw", - "bG95ZWQYAyABKAgSHQoVYXBwbGllZF9kZXBsb3ltZW50X2lkGAQgASgJEh0K", - "FWFwcGxpZWRfcmV2aXNpb25faGFzaBgFIAEoCRItCgl0aW1lc3RhbXAYBiAB", - "KAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIp4BCh1BcnRpZmFjdERl", - "cGxveW1lbnRSZXNwb25zZUR0bxIVCg1kZXBsb3ltZW50X2lkGAEgASgJEg8K", - "B3NpdGVfaWQYAiABKAkSDwoHc3VjY2VzcxgDIAEoCBIVCg1lcnJvcl9tZXNz", - "YWdlGAQgASgJEi0KCXRpbWVzdGFtcBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1", - "Zi5UaW1lc3RhbXAimgQKEExpZmVjeWNsZVJlcXVlc3QSVQoScmVmcmVzaF9k", - "ZXBsb3ltZW50GAEgASgLMjcuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEu", - "UmVmcmVzaERlcGxveW1lbnRDb21tYW5kRHRvSAASTwoPZW5hYmxlX2luc3Rh", - "bmNlGAIgASgLMjQuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuRW5hYmxl", - "SW5zdGFuY2VDb21tYW5kRHRvSAASUQoQZGlzYWJsZV9pbnN0YW5jZRgDIAEo", - "CzI1LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkRpc2FibGVJbnN0YW5j", - "ZUNvbW1hbmREdG9IABJPCg9kZWxldGVfaW5zdGFuY2UYBCABKAsyNC5zY2Fk", - "YWJyaWRnZS5zaXRlY29tbWFuZC52MS5EZWxldGVJbnN0YW5jZUNvbW1hbmRE", - "dG9IABJcChZkZXBsb3ltZW50X3N0YXRlX3F1ZXJ5GAUgASgLMjouc2NhZGFi", - "cmlkZ2Uuc2l0ZWNvbW1hbmQudjEuRGVwbG95bWVudFN0YXRlUXVlcnlSZXF1", - "ZXN0RHRvSAASUQoQZGVwbG95X2FydGlmYWN0cxgGIAEoCzI1LnNjYWRhYnJp", - "ZGdlLnNpdGVjb21tYW5kLnYxLkRlcGxveUFydGlmYWN0c0NvbW1hbmREdG9I", - "AEIJCgdjb21tYW5kIoADCg5MaWZlY3ljbGVSZXBseRJUChFkZXBsb3ltZW50", - "X3N0YXR1cxgBIAEoCzI3LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkRl", - "cGxveW1lbnRTdGF0dXNSZXNwb25zZUR0b0gAElYKEmluc3RhbmNlX2xpZmVj", - "eWNsZRgCIAEoCzI4LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkluc3Rh", - "bmNlTGlmZWN5Y2xlUmVzcG9uc2VEdG9IABJdChZkZXBsb3ltZW50X3N0YXRl", - "X3F1ZXJ5GAMgASgLMjsuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuRGVw", - "bG95bWVudFN0YXRlUXVlcnlSZXNwb25zZUR0b0gAElgKE2FydGlmYWN0X2Rl", - "cGxveW1lbnQYBCABKAsyOS5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5B", - "cnRpZmFjdERlcGxveW1lbnRSZXNwb25zZUR0b0gAQgcKBXJlcGx5InwKFEJy", - "b3dzZU5vZGVDb21tYW5kRHRvEhcKD2Nvbm5lY3Rpb25fbmFtZRgBIAEoCRIW", - "Cg5wYXJlbnRfbm9kZV9pZBgCIAEoCRIaChJjb250aW51YXRpb25fdG9rZW4Y", - "AyABKAkSFwoPc2l0ZV9pZGVudGlmaWVyGAQgASgJIoICCg1Ccm93c2VOb2Rl", - "RHRvEg8KB25vZGVfaWQYASABKAkSFAoMZGlzcGxheV9uYW1lGAIgASgJEkIK", - "Cm5vZGVfY2xhc3MYAyABKA4yLi5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52", - "MS5Ccm93c2VOb2RlQ2xhc3NEdG8SFAoMaGFzX2NoaWxkcmVuGAQgASgIEhEK", - "CWRhdGFfdHlwZRgFIAEoCRIvCgp2YWx1ZV9yYW5rGAYgASgLMhsuZ29vZ2xl", - "LnByb3RvYnVmLkludDMyVmFsdWUSLAoId3JpdGFibGUYByABKAsyGi5nb29n", - "bGUucHJvdG9idWYuQm9vbFZhbHVlImMKEEJyb3dzZUZhaWx1cmVEdG8SPgoE", - "a2luZBgBIAEoDjIwLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkJyb3dz", - "ZUZhaWx1cmVLaW5kRHRvEg8KB21lc3NhZ2UYAiABKAkiwAEKE0Jyb3dzZU5v", - "ZGVSZXN1bHREdG8SOwoIY2hpbGRyZW4YASADKAsyKS5zY2FkYWJyaWRnZS5z", - "aXRlY29tbWFuZC52MS5Ccm93c2VOb2RlRHRvEhEKCXRydW5jYXRlZBgCIAEo", - "CBI9CgdmYWlsdXJlGAMgASgLMiwuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQu", - "djEuQnJvd3NlRmFpbHVyZUR0bxIaChJjb250aW51YXRpb25fdG9rZW4YBCAB", - "KAkihwEKHFNlYXJjaEFkZHJlc3NTcGFjZUNvbW1hbmREdG8SFwoPY29ubmVj", - "dGlvbl9uYW1lGAEgASgJEg0KBXF1ZXJ5GAIgASgJEhEKCW1heF9kZXB0aBgD", - "IAEoBRITCgttYXhfcmVzdWx0cxgEIAEoBRIXCg9zaXRlX2lkZW50aWZpZXIY", - "BSABKAkiXQoUQWRkcmVzc1NwYWNlTWF0Y2hEdG8SNwoEbm9kZRgBIAEoCzIp", - "LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkJyb3dzZU5vZGVEdG8SDAoE", - "cGF0aBgCIAEoCSK0AQobU2VhcmNoQWRkcmVzc1NwYWNlUmVzdWx0RHRvEkEK", - "B21hdGNoZXMYASADKAsyMC5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5B", - "ZGRyZXNzU3BhY2VNYXRjaER0bxITCgtjYXBfcmVhY2hlZBgCIAEoCBI9Cgdm", - "YWlsdXJlGAMgASgLMiwuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuQnJv", - "d3NlRmFpbHVyZUR0byJFChdSZWFkVGFnVmFsdWVzQ29tbWFuZER0bxIXCg9j", - "b25uZWN0aW9uX25hbWUYASABKAkSEQoJdGFnX3BhdGhzGAIgAygJIsQBChFU", - "YWdSZWFkT3V0Y29tZUR0bxIQCgh0YWdfcGF0aBgBIAEoCRIPCgdzdWNjZXNz", - "GAIgASgIEjUKBXZhbHVlGAMgASgLMiYuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1h", - "bmQudjEuTG9vc2VWYWx1ZRIPCgdxdWFsaXR5GAQgASgJEi0KCXRpbWVzdGFt", - "cBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASFQoNZXJyb3Jf", - "bWVzc2FnZRgGIAEoCSJxChdSZWFkVGFnVmFsdWVzRmFpbHVyZUR0bxJFCgRr", - "aW5kGAEgASgOMjcuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUmVhZFRh", - "Z1ZhbHVlc0ZhaWx1cmVLaW5kRHRvEg8KB21lc3NhZ2UYAiABKAkinwEKFlJl", - "YWRUYWdWYWx1ZXNSZXN1bHREdG8SPwoIb3V0Y29tZXMYASADKAsyLS5zY2Fk", - "YWJyaWRnZS5zaXRlY29tbWFuZC52MS5UYWdSZWFkT3V0Y29tZUR0bxJECgdm", - "YWlsdXJlGAIgASgLMjMuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUmVh", - "ZFRhZ1ZhbHVlc0ZhaWx1cmVEdG8icwoYVmVyaWZ5RW5kcG9pbnRDb21tYW5k", - "RHRvEhcKD2Nvbm5lY3Rpb25fbmFtZRgBIAEoCRIQCghwcm90b2NvbBgCIAEo", - "CRITCgtjb25maWdfanNvbhgDIAEoCRIXCg9zaXRlX2lkZW50aWZpZXIYBCAB", - "KAkiwwEKEVNlcnZlckNlcnRJbmZvRHRvEhIKCnRodW1icHJpbnQYASABKAkS", - "DwoHc3ViamVjdBgCIAEoCRIOCgZpc3N1ZXIYAyABKAkSMgoObm90X2JlZm9y", - "ZV91dGMYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjEKDW5v", - "dF9hZnRlcl91dGMYBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1w", - "EhIKCmRlcl9iYXNlNjQYBiABKAkiWQoWVmVyaWZ5RmFpbHVyZUtpbmRWYWx1", - "ZRI/CgV2YWx1ZRgBIAEoDjIwLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYx", - "LlZlcmlmeUZhaWx1cmVLaW5kRHRvIsABChdWZXJpZnlFbmRwb2ludFJlc3Vs", - "dER0bxIPCgdzdWNjZXNzGAEgASgIEkgKDGZhaWx1cmVfa2luZBgCIAEoCzIy", - "LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlZlcmlmeUZhaWx1cmVLaW5k", - "VmFsdWUSDQoFZXJyb3IYAyABKAkSOwoEY2VydBgEIAEoCzItLnNjYWRhYnJp", - "ZGdlLnNpdGVjb21tYW5kLnYxLlNlcnZlckNlcnRJbmZvRHRvInUKGVRydXN0", - "U2VydmVyQ2VydENvbW1hbmREdG8SFwoPY29ubmVjdGlvbl9uYW1lGAEgASgJ", - "EhIKCmRlcl9iYXNlNjQYAiABKAkSEgoKdGh1bWJwcmludBgDIAEoCRIXCg9z", - "aXRlX2lkZW50aWZpZXIYBCABKAkiNAoZTGlzdFNlcnZlckNlcnRzQ29tbWFu", - "ZER0bxIXCg9zaXRlX2lkZW50aWZpZXIYASABKAkiSQoaUmVtb3ZlU2VydmVy", - "Q2VydENvbW1hbmREdG8SEgoKdGh1bWJwcmludBgBIAEoCRIXCg9zaXRlX2lk", - "ZW50aWZpZXIYAiABKAkiwgEKElRydXN0ZWRDZXJ0SW5mb0R0bxISCgp0aHVt", - "YnByaW50GAEgASgJEg8KB3N1YmplY3QYAiABKAkSDgoGaXNzdWVyGAMgASgJ", - "EjIKDm5vdF9iZWZvcmVfdXRjGAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRp", - "bWVzdGFtcBIxCg1ub3RfYWZ0ZXJfdXRjGAUgASgLMhouZ29vZ2xlLnByb3Rv", - "YnVmLlRpbWVzdGFtcBIQCghyZWplY3RlZBgGIAEoCCJXChZUcnVzdGVkQ2Vy", - "dEluZm9MaXN0RHRvEj0KBWl0ZW1zGAEgAygLMi4uc2NhZGFicmlkZ2Uuc2l0", - "ZWNvbW1hbmQudjEuVHJ1c3RlZENlcnRJbmZvRHRvIncKEkNlcnRUcnVzdFJl", - "c3VsdER0bxIPCgdzdWNjZXNzGAEgASgIEg0KBWVycm9yGAIgASgJEkEKBWNl", - "cnRzGAMgASgLMjIuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuVHJ1c3Rl", - "ZENlcnRJbmZvTGlzdER0byK9AQoSV3JpdGVUYWdSZXF1ZXN0RHRvEhYKDmNv", - "cnJlbGF0aW9uX2lkGAEgASgJEhcKD2Nvbm5lY3Rpb25fbmFtZRgCIAEoCRIQ", - "Cgh0YWdfcGF0aBgDIAEoCRI1CgV2YWx1ZRgEIAEoCzImLnNjYWRhYnJpZGdl", - "LnNpdGVjb21tYW5kLnYxLkxvb3NlVmFsdWUSLQoJdGltZXN0YW1wGAUgASgL", - "MhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKEAQoTV3JpdGVUYWdSZXNw", - "b25zZUR0bxIWCg5jb3JyZWxhdGlvbl9pZBgBIAEoCRIPCgdzdWNjZXNzGAIg", - "ASgIEhUKDWVycm9yX21lc3NhZ2UYAyABKAkSLQoJdGltZXN0YW1wGAQgASgL", - "MhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKgBQoMT3BjVWFSZXF1ZXN0", - "EkcKC2Jyb3dzZV9ub2RlGAEgASgLMjAuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1h", - "bmQudjEuQnJvd3NlTm9kZUNvbW1hbmREdG9IABJYChRzZWFyY2hfYWRkcmVz", - "c19zcGFjZRgCIAEoCzI4LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlNl", - "YXJjaEFkZHJlc3NTcGFjZUNvbW1hbmREdG9IABJOCg9yZWFkX3RhZ192YWx1", - "ZXMYAyABKAsyMy5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5SZWFkVGFn", - "VmFsdWVzQ29tbWFuZER0b0gAEk8KD3ZlcmlmeV9lbmRwb2ludBgEIAEoCzI0", - "LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlZlcmlmeUVuZHBvaW50Q29t", - "bWFuZER0b0gAElIKEXRydXN0X3NlcnZlcl9jZXJ0GAUgASgLMjUuc2NhZGFi", - "cmlkZ2Uuc2l0ZWNvbW1hbmQudjEuVHJ1c3RTZXJ2ZXJDZXJ0Q29tbWFuZER0", - "b0gAElIKEWxpc3Rfc2VydmVyX2NlcnRzGAYgASgLMjUuc2NhZGFicmlkZ2Uu", - "c2l0ZWNvbW1hbmQudjEuTGlzdFNlcnZlckNlcnRzQ29tbWFuZER0b0gAElQK", - "EnJlbW92ZV9zZXJ2ZXJfY2VydBgHIAEoCzI2LnNjYWRhYnJpZGdlLnNpdGVj", - "b21tYW5kLnYxLlJlbW92ZVNlcnZlckNlcnRDb21tYW5kRHRvSAASQwoJd3Jp", - "dGVfdGFnGAggASgLMi4uc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuV3Jp", - "dGVUYWdSZXF1ZXN0RHRvSABCCQoHY29tbWFuZCLhAwoKT3BjVWFSZXBseRJG", - "Cgticm93c2Vfbm9kZRgBIAEoCzIvLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5k", - "LnYxLkJyb3dzZU5vZGVSZXN1bHREdG9IABJXChRzZWFyY2hfYWRkcmVzc19z", - "cGFjZRgCIAEoCzI3LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlNlYXJj", - "aEFkZHJlc3NTcGFjZVJlc3VsdER0b0gAEk0KD3JlYWRfdGFnX3ZhbHVlcxgD", - "IAEoCzIyLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlJlYWRUYWdWYWx1", - "ZXNSZXN1bHREdG9IABJOCg92ZXJpZnlfZW5kcG9pbnQYBCABKAsyMy5zY2Fk", - "YWJyaWRnZS5zaXRlY29tbWFuZC52MS5WZXJpZnlFbmRwb2ludFJlc3VsdER0", - "b0gAEkQKCmNlcnRfdHJ1c3QYBSABKAsyLi5zY2FkYWJyaWRnZS5zaXRlY29t", - "bWFuZC52MS5DZXJ0VHJ1c3RSZXN1bHREdG9IABJECgl3cml0ZV90YWcYBiAB", - "KAsyLy5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5Xcml0ZVRhZ1Jlc3Bv", - "bnNlRHRvSABCBwoFcmVwbHkixQIKF0V2ZW50TG9nUXVlcnlSZXF1ZXN0RHRv", - "EhYKDmNvcnJlbGF0aW9uX2lkGAEgASgJEg8KB3NpdGVfaWQYAiABKAkSKAoE", - "ZnJvbRgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASJgoCdG8Y", - "BCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhIKCmV2ZW50X3R5", - "cGUYBSABKAkSEAoIc2V2ZXJpdHkYBiABKAkSEwoLaW5zdGFuY2VfaWQYByAB", - "KAkSFgoOa2V5d29yZF9maWx0ZXIYCCABKAkSGgoSY29udGludWF0aW9uX3Rv", - "a2VuGAkgASgJEhEKCXBhZ2Vfc2l6ZRgKIAEoBRItCgl0aW1lc3RhbXAYCyAB", - "KAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIroBChBFdmVudExvZ0Vu", - "dHJ5RHRvEgoKAmlkGAEgASgJEi0KCXRpbWVzdGFtcBgCIAEoCzIaLmdvb2ds", - "ZS5wcm90b2J1Zi5UaW1lc3RhbXASEgoKZXZlbnRfdHlwZRgDIAEoCRIQCghz", - "ZXZlcml0eRgEIAEoCRITCgtpbnN0YW5jZV9pZBgFIAEoCRIOCgZzb3VyY2UY", - "BiABKAkSDwoHbWVzc2FnZRgHIAEoCRIPCgdkZXRhaWxzGAggASgJIocCChhF", - "dmVudExvZ1F1ZXJ5UmVzcG9uc2VEdG8SFgoOY29ycmVsYXRpb25faWQYASAB", - "KAkSDwoHc2l0ZV9pZBgCIAEoCRI9CgdlbnRyaWVzGAMgAygLMiwuc2NhZGFi", - "cmlkZ2Uuc2l0ZWNvbW1hbmQudjEuRXZlbnRMb2dFbnRyeUR0bxIaChJjb250", - "aW51YXRpb25fdG9rZW4YBCABKAkSEAoIaGFzX21vcmUYBSABKAgSDwoHc3Vj", - "Y2VzcxgGIAEoCBIVCg1lcnJvcl9tZXNzYWdlGAcgASgJEi0KCXRpbWVzdGFt", - "cBgIIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiZAoXRGVidWdT", - "bmFwc2hvdFJlcXVlc3REdG8SHAoUaW5zdGFuY2VfdW5pcXVlX25hbWUYASAB", - "KAkSFgoOY29ycmVsYXRpb25faWQYAiABKAkSEwoLYWxhcm1zX29ubHkYAyAB", - "KAgiVAocU3Vic2NyaWJlRGVidWdWaWV3UmVxdWVzdER0bxIcChRpbnN0YW5j", - "ZV91bmlxdWVfbmFtZRgBIAEoCRIWCg5jb3JyZWxhdGlvbl9pZBgCIAEoCSJW", - "Ch5VbnN1YnNjcmliZURlYnVnVmlld1JlcXVlc3REdG8SHAoUaW5zdGFuY2Vf", - "dW5pcXVlX25hbWUYASABKAkSFgoOY29ycmVsYXRpb25faWQYAiABKAkiHAoa", - "VW5zdWJzY3JpYmVEZWJ1Z1ZpZXdBY2tEdG8i1AEKFkFsYXJtQ29uZGl0aW9u", - "U3RhdGVEdG8SDgoGYWN0aXZlGAEgASgIEhQKDGFja25vd2xlZGdlZBgCIAEo", - "CBItCgljb25maXJtZWQYAyABKAsyGi5nb29nbGUucHJvdG9idWYuQm9vbFZh", - "bHVlEj8KBnNoZWx2ZRgEIAEoDjIvLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5k", - "LnYxLkFsYXJtU2hlbHZlU3RhdGVEdG8SEgoKc3VwcHJlc3NlZBgFIAEoCBIQ", - "CghzZXZlcml0eRgGIAEoBSLdAQoWRGVidWdBdHRyaWJ1dGVWYWx1ZUR0bxIc", - "ChRpbnN0YW5jZV91bmlxdWVfbmFtZRgBIAEoCRIWCg5hdHRyaWJ1dGVfcGF0", - "aBgCIAEoCRIWCg5hdHRyaWJ1dGVfbmFtZRgDIAEoCRI1CgV2YWx1ZRgEIAEo", - "CzImLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkxvb3NlVmFsdWUSDwoH", - "cXVhbGl0eRgFIAEoCRItCgl0aW1lc3RhbXAYBiABKAsyGi5nb29nbGUucHJv", - "dG9idWYuVGltZXN0YW1wIq8FChJEZWJ1Z0FsYXJtU3RhdGVEdG8SHAoUaW5z", - "dGFuY2VfdW5pcXVlX25hbWUYASABKAkSEgoKYWxhcm1fbmFtZRgCIAEoCRI4", - "CgVzdGF0ZRgDIAEoDjIpLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkFs", - "YXJtU3RhdGVEdG8SEAoIcHJpb3JpdHkYBCABKAUSLQoJdGltZXN0YW1wGAUg", - "ASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBI4CgVsZXZlbBgGIAEo", - "DjIpLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkFsYXJtTGV2ZWxEdG8S", - "DwoHbWVzc2FnZRgHIAEoCRI2CgRraW5kGAggASgOMiguc2NhZGFicmlkZ2Uu", - "c2l0ZWNvbW1hbmQudjEuQWxhcm1LaW5kRHRvEkUKCWNvbmRpdGlvbhgJIAEo", - "CzIyLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkFsYXJtQ29uZGl0aW9u", - "U3RhdGVEdG8SGAoQc291cmNlX3JlZmVyZW5jZRgKIAEoCRIXCg9hbGFybV90", - "eXBlX25hbWUYCyABKAkSEAoIY2F0ZWdvcnkYDCABKAkSFQoNb3BlcmF0b3Jf", - "dXNlchgNIAEoCRIYChBvcGVyYXRvcl9jb21tZW50GA4gASgJEjcKE29yaWdp", - "bmFsX3JhaXNlX3RpbWUYDyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0", - "YW1wEhUKDWN1cnJlbnRfdmFsdWUYECABKAkSEwoLbGltaXRfdmFsdWUYESAB", - "KAkSJAocbmF0aXZlX3NvdXJjZV9jYW5vbmljYWxfbmFtZRgSIAEoCRIhChlp", - "c19jb25maWd1cmVkX3BsYWNlaG9sZGVyGBMgASgIIpwCChREZWJ1Z1ZpZXdT", - "bmFwc2hvdER0bxIcChRpbnN0YW5jZV91bmlxdWVfbmFtZRgBIAEoCRJMChBh", - "dHRyaWJ1dGVfdmFsdWVzGAIgAygLMjIuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1h", - "bmQudjEuRGVidWdBdHRyaWJ1dGVWYWx1ZUR0bxJECgxhbGFybV9zdGF0ZXMY", - "AyADKAsyLi5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5EZWJ1Z0FsYXJt", - "U3RhdGVEdG8SNgoSc25hcHNob3RfdGltZXN0YW1wGAQgASgLMhouZ29vZ2xl", - "LnByb3RvYnVmLlRpbWVzdGFtcBIaChJpbnN0YW5jZV9ub3RfZm91bmQYBSAB", - "KAgi8AIKDFF1ZXJ5UmVxdWVzdBJOCg9ldmVudF9sb2dfcXVlcnkYASABKAsy", - "My5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5FdmVudExvZ1F1ZXJ5UmVx", - "dWVzdER0b0gAEk0KDmRlYnVnX3NuYXBzaG90GAIgASgLMjMuc2NhZGFicmlk", - "Z2Uuc2l0ZWNvbW1hbmQudjEuRGVidWdTbmFwc2hvdFJlcXVlc3REdG9IABJY", - "ChRzdWJzY3JpYmVfZGVidWdfdmlldxgDIAEoCzI4LnNjYWRhYnJpZGdlLnNp", - "dGVjb21tYW5kLnYxLlN1YnNjcmliZURlYnVnVmlld1JlcXVlc3REdG9IABJc", - "ChZ1bnN1YnNjcmliZV9kZWJ1Z192aWV3GAQgASgLMjouc2NhZGFicmlkZ2Uu", - "c2l0ZWNvbW1hbmQudjEuVW5zdWJzY3JpYmVEZWJ1Z1ZpZXdSZXF1ZXN0RHRv", - "SABCCQoHY29tbWFuZCKRAgoKUXVlcnlSZXBseRJPCg9ldmVudF9sb2dfcXVl", - "cnkYASABKAsyNC5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5FdmVudExv", - "Z1F1ZXJ5UmVzcG9uc2VEdG9IABJPChNkZWJ1Z192aWV3X3NuYXBzaG90GAIg", - "ASgLMjAuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuRGVidWdWaWV3U25h", - "cHNob3REdG9IABJYChZ1bnN1YnNjcmliZV9kZWJ1Z192aWV3GAMgASgLMjYu", - "c2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuVW5zdWJzY3JpYmVEZWJ1Z1Zp", - "ZXdBY2tEdG9IAEIHCgVyZXBseSKeAQocUGFya2VkTWVzc2FnZVF1ZXJ5UmVx", - "dWVzdER0bxIWCg5jb3JyZWxhdGlvbl9pZBgBIAEoCRIPCgdzaXRlX2lkGAIg", - "ASgJEhMKC3BhZ2VfbnVtYmVyGAMgASgFEhEKCXBhZ2Vfc2l6ZRgEIAEoBRIt", - "Cgl0aW1lc3RhbXAYBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1w", - "IvICChVQYXJrZWRNZXNzYWdlRW50cnlEdG8SEgoKbWVzc2FnZV9pZBgBIAEo", - "CRIVCg10YXJnZXRfc3lzdGVtGAIgASgJEhMKC21ldGhvZF9uYW1lGAMgASgJ", - "EhUKDWVycm9yX21lc3NhZ2UYBCABKAkSFQoNYXR0ZW1wdF9jb3VudBgFIAEo", - "BRI2ChJvcmlnaW5hbF90aW1lc3RhbXAYBiABKAsyGi5nb29nbGUucHJvdG9i", - "dWYuVGltZXN0YW1wEjoKFmxhc3RfYXR0ZW1wdF90aW1lc3RhbXAYByABKAsy", - "Gi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhQKDG1heF9hdHRlbXB0cxgI", - "IAEoBRJICghjYXRlZ29yeRgJIAEoDjI2LnNjYWRhYnJpZGdlLnNpdGVjb21t", - "YW5kLnYxLlN0b3JlQW5kRm9yd2FyZENhdGVnb3J5RHRvEhcKD29yaWdpbl9p", - "bnN0YW5jZRgKIAEoCSKhAgodUGFya2VkTWVzc2FnZVF1ZXJ5UmVzcG9uc2VE", - "dG8SFgoOY29ycmVsYXRpb25faWQYASABKAkSDwoHc2l0ZV9pZBgCIAEoCRJD", - "CghtZXNzYWdlcxgDIAMoCzIxLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYx", - "LlBhcmtlZE1lc3NhZ2VFbnRyeUR0bxITCgt0b3RhbF9jb3VudBgEIAEoBRIT", - "CgtwYWdlX251bWJlchgFIAEoBRIRCglwYWdlX3NpemUYBiABKAUSDwoHc3Vj", - "Y2VzcxgHIAEoCBIVCg1lcnJvcl9tZXNzYWdlGAggASgJEi0KCXRpbWVzdGFt", - "cBgJIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiigEKHFBhcmtl", - "ZE1lc3NhZ2VSZXRyeVJlcXVlc3REdG8SFgoOY29ycmVsYXRpb25faWQYASAB", - "KAkSDwoHc2l0ZV9pZBgCIAEoCRISCgptZXNzYWdlX2lkGAMgASgJEi0KCXRp", - "bWVzdGFtcBgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiXwod", - "UGFya2VkTWVzc2FnZVJldHJ5UmVzcG9uc2VEdG8SFgoOY29ycmVsYXRpb25f", - "aWQYASABKAkSDwoHc3VjY2VzcxgCIAEoCBIVCg1lcnJvcl9tZXNzYWdlGAMg", - "ASgJIowBCh5QYXJrZWRNZXNzYWdlRGlzY2FyZFJlcXVlc3REdG8SFgoOY29y", - "cmVsYXRpb25faWQYASABKAkSDwoHc2l0ZV9pZBgCIAEoCRISCgptZXNzYWdl", - "X2lkGAMgASgJEi0KCXRpbWVzdGFtcBgEIAEoCzIaLmdvb2dsZS5wcm90b2J1", - "Zi5UaW1lc3RhbXAiYQofUGFya2VkTWVzc2FnZURpc2NhcmRSZXNwb25zZUR0", - "bxIWCg5jb3JyZWxhdGlvbl9pZBgBIAEoCRIPCgdzdWNjZXNzGAIgASgIEhUK", - "DWVycm9yX21lc3NhZ2UYAyABKAkiTwoXUmV0cnlQYXJrZWRPcGVyYXRpb25E", - "dG8SFgoOY29ycmVsYXRpb25faWQYASABKAkSHAoUdHJhY2tlZF9vcGVyYXRp", - "b25faWQYAiABKAkiUQoZRGlzY2FyZFBhcmtlZE9wZXJhdGlvbkR0bxIWCg5j", - "b3JyZWxhdGlvbl9pZBgBIAEoCRIcChR0cmFja2VkX29wZXJhdGlvbl9pZBgC", - "IAEoCSJdChtQYXJrZWRPcGVyYXRpb25BY3Rpb25BY2tEdG8SFgoOY29ycmVs", - "YXRpb25faWQYASABKAkSDwoHYXBwbGllZBgCIAEoCBIVCg1lcnJvcl9tZXNz", - "YWdlGAMgASgJIt4DCg1QYXJrZWRSZXF1ZXN0ElgKFHBhcmtlZF9tZXNzYWdl", - "X3F1ZXJ5GAEgASgLMjguc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUGFy", - "a2VkTWVzc2FnZVF1ZXJ5UmVxdWVzdER0b0gAElgKFHBhcmtlZF9tZXNzYWdl", - "X3JldHJ5GAIgASgLMjguc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUGFy", - "a2VkTWVzc2FnZVJldHJ5UmVxdWVzdER0b0gAElwKFnBhcmtlZF9tZXNzYWdl", - "X2Rpc2NhcmQYAyABKAsyOi5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5Q", - "YXJrZWRNZXNzYWdlRGlzY2FyZFJlcXVlc3REdG9IABJVChZyZXRyeV9wYXJr", - "ZWRfb3BlcmF0aW9uGAQgASgLMjMuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQu", - "djEuUmV0cnlQYXJrZWRPcGVyYXRpb25EdG9IABJZChhkaXNjYXJkX3Bhcmtl", - "ZF9vcGVyYXRpb24YBSABKAsyNS5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52", - "MS5EaXNjYXJkUGFya2VkT3BlcmF0aW9uRHRvSABCCQoHY29tbWFuZCKHAwoL", - "UGFya2VkUmVwbHkSWQoUcGFya2VkX21lc3NhZ2VfcXVlcnkYASABKAsyOS5z", - "Y2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5QYXJrZWRNZXNzYWdlUXVlcnlS", - "ZXNwb25zZUR0b0gAElkKFHBhcmtlZF9tZXNzYWdlX3JldHJ5GAIgASgLMjku", - "c2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUGFya2VkTWVzc2FnZVJldHJ5", - "UmVzcG9uc2VEdG9IABJdChZwYXJrZWRfbWVzc2FnZV9kaXNjYXJkGAMgASgL", - "Mjsuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUGFya2VkTWVzc2FnZURp", - "c2NhcmRSZXNwb25zZUR0b0gAEloKF3BhcmtlZF9vcGVyYXRpb25fYWN0aW9u", - "GAQgASgLMjcuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUGFya2VkT3Bl", - "cmF0aW9uQWN0aW9uQWNrRHRvSABCBwoFcmVwbHki7QEKFVJvdXRlVG9DYWxs", - "UmVxdWVzdER0bxIWCg5jb3JyZWxhdGlvbl9pZBgBIAEoCRIcChRpbnN0YW5j", - "ZV91bmlxdWVfbmFtZRgCIAEoCRITCgtzY3JpcHRfbmFtZRgDIAEoCRI9Cgpw", - "YXJhbWV0ZXJzGAQgASgLMikuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEu", - "TG9vc2VWYWx1ZU1hcBItCgl0aW1lc3RhbXAYBSABKAsyGi5nb29nbGUucHJv", - "dG9idWYuVGltZXN0YW1wEhsKE3BhcmVudF9leGVjdXRpb25faWQYBiABKAki", - "xQEKFlJvdXRlVG9DYWxsUmVzcG9uc2VEdG8SFgoOY29ycmVsYXRpb25faWQY", - "ASABKAkSDwoHc3VjY2VzcxgCIAEoCBI8CgxyZXR1cm5fdmFsdWUYAyABKAsy", - "Ji5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5Mb29zZVZhbHVlEhUKDWVy", - "cm9yX21lc3NhZ2UYBCABKAkSLQoJdGltZXN0YW1wGAUgASgLMhouZ29vZ2xl", - "LnByb3RvYnVmLlRpbWVzdGFtcCK7AQoeUm91dGVUb0dldEF0dHJpYnV0ZXNS", - "ZXF1ZXN0RHRvEhYKDmNvcnJlbGF0aW9uX2lkGAEgASgJEhwKFGluc3RhbmNl", - "X3VuaXF1ZV9uYW1lGAIgASgJEhcKD2F0dHJpYnV0ZV9uYW1lcxgDIAMoCRIt", - "Cgl0aW1lc3RhbXAYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1w", - "EhsKE3BhcmVudF9leGVjdXRpb25faWQYBSABKAkiywEKH1JvdXRlVG9HZXRB", - "dHRyaWJ1dGVzUmVzcG9uc2VEdG8SFgoOY29ycmVsYXRpb25faWQYASABKAkS", - "OQoGdmFsdWVzGAIgASgLMikuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEu", - "TG9vc2VWYWx1ZU1hcBIPCgdzdWNjZXNzGAMgASgIEhUKDWVycm9yX21lc3Nh", - "Z2UYBCABKAkSLQoJdGltZXN0YW1wGAUgASgLMhouZ29vZ2xlLnByb3RvYnVm", - "LlRpbWVzdGFtcCLFAgoeUm91dGVUb1NldEF0dHJpYnV0ZXNSZXF1ZXN0RHRv", - "EhYKDmNvcnJlbGF0aW9uX2lkGAEgASgJEhwKFGluc3RhbmNlX3VuaXF1ZV9u", - "YW1lGAIgASgJEmkKEGF0dHJpYnV0ZV92YWx1ZXMYAyADKAsyTy5zY2FkYWJy", - "aWRnZS5zaXRlY29tbWFuZC52MS5Sb3V0ZVRvU2V0QXR0cmlidXRlc1JlcXVl", - "c3REdG8uQXR0cmlidXRlVmFsdWVzRW50cnkSLQoJdGltZXN0YW1wGAQgASgL", - "MhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIbChNwYXJlbnRfZXhlY3V0", - "aW9uX2lkGAUgASgJGjYKFEF0dHJpYnV0ZVZhbHVlc0VudHJ5EgsKA2tleRgB", - "IAEoCRINCgV2YWx1ZRgCIAEoCToCOAEikAEKH1JvdXRlVG9TZXRBdHRyaWJ1", - "dGVzUmVzcG9uc2VEdG8SFgoOY29ycmVsYXRpb25faWQYASABKAkSDwoHc3Vj", - "Y2VzcxgCIAEoCBIVCg1lcnJvcl9tZXNzYWdlGAMgASgJEi0KCXRpbWVzdGFt", - "cBgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiwwIKIVJvdXRl", - "VG9XYWl0Rm9yQXR0cmlidXRlUmVxdWVzdER0bxIWCg5jb3JyZWxhdGlvbl9p", - "ZBgBIAEoCRIcChRpbnN0YW5jZV91bmlxdWVfbmFtZRgCIAEoCRIWCg5hdHRy", - "aWJ1dGVfbmFtZRgDIAEoCRI6ChR0YXJnZXRfdmFsdWVfZW5jb2RlZBgEIAEo", - "CzIcLmdvb2dsZS5wcm90b2J1Zi5TdHJpbmdWYWx1ZRIqCgd0aW1lb3V0GAUg", - "ASgLMhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uEi0KCXRpbWVzdGFtcBgG", - "IAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASGwoTcGFyZW50X2V4", - "ZWN1dGlvbl9pZBgHIAEoCRIcChRyZXF1aXJlX2dvb2RfcXVhbGl0eRgIIAEo", - "CCL/AQoiUm91dGVUb1dhaXRGb3JBdHRyaWJ1dGVSZXNwb25zZUR0bxIWCg5j", - "b3JyZWxhdGlvbl9pZBgBIAEoCRIPCgdtYXRjaGVkGAIgASgIEjUKBXZhbHVl", - "GAMgASgLMiYuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuTG9vc2VWYWx1", - "ZRIPCgdxdWFsaXR5GAQgASgJEhEKCXRpbWVkX291dBgFIAEoCBIPCgdzdWNj", - "ZXNzGAYgASgIEhUKDWVycm9yX21lc3NhZ2UYByABKAkSLQoJdGltZXN0YW1w", - "GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKJAwoMUm91dGVS", - "ZXF1ZXN0EkoKDXJvdXRlX3RvX2NhbGwYASABKAsyMS5zY2FkYWJyaWRnZS5z", - "aXRlY29tbWFuZC52MS5Sb3V0ZVRvQ2FsbFJlcXVlc3REdG9IABJdChdyb3V0", - "ZV90b19nZXRfYXR0cmlidXRlcxgCIAEoCzI6LnNjYWRhYnJpZGdlLnNpdGVj", - "b21tYW5kLnYxLlJvdXRlVG9HZXRBdHRyaWJ1dGVzUmVxdWVzdER0b0gAEl0K", - "F3JvdXRlX3RvX3NldF9hdHRyaWJ1dGVzGAMgASgLMjouc2NhZGFicmlkZ2Uu", - "c2l0ZWNvbW1hbmQudjEuUm91dGVUb1NldEF0dHJpYnV0ZXNSZXF1ZXN0RHRv", - "SAASZAobcm91dGVfdG9fd2FpdF9mb3JfYXR0cmlidXRlGAQgASgLMj0uc2Nh", - "ZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUm91dGVUb1dhaXRGb3JBdHRyaWJ1", - "dGVSZXF1ZXN0RHRvSABCCQoHY29tbWFuZCKJAwoKUm91dGVSZXBseRJLCg1y", - "b3V0ZV90b19jYWxsGAEgASgLMjIuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQu", - "djEuUm91dGVUb0NhbGxSZXNwb25zZUR0b0gAEl4KF3JvdXRlX3RvX2dldF9h", - "dHRyaWJ1dGVzGAIgASgLMjsuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEu", - "Um91dGVUb0dldEF0dHJpYnV0ZXNSZXNwb25zZUR0b0gAEl4KF3JvdXRlX3Rv", - "X3NldF9hdHRyaWJ1dGVzGAMgASgLMjsuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1h", - "bmQudjEuUm91dGVUb1NldEF0dHJpYnV0ZXNSZXNwb25zZUR0b0gAEmUKG3Jv", - "dXRlX3RvX3dhaXRfZm9yX2F0dHJpYnV0ZRgEIAEoCzI+LnNjYWRhYnJpZGdl", - "LnNpdGVjb21tYW5kLnYxLlJvdXRlVG9XYWl0Rm9yQXR0cmlidXRlUmVzcG9u", - "c2VEdG9IAEIHCgVyZXBseSJBChZUcmlnZ2VyU2l0ZUZhaWxvdmVyRHRvEhYK", - "DmNvcnJlbGF0aW9uX2lkGAEgASgJEg8KB3NpdGVfaWQYAiABKAkibQoSU2l0", - "ZUZhaWxvdmVyQWNrRHRvEhYKDmNvcnJlbGF0aW9uX2lkGAEgASgJEhAKCGFj", - "Y2VwdGVkGAIgASgIEhYKDnRhcmdldF9hZGRyZXNzGAMgASgJEhUKDWVycm9y", - "X21lc3NhZ2UYBCABKAkqywEKE0RlcGxveW1lbnRTdGF0dXNEdG8SJQohREVQ", - "TE9ZTUVOVF9TVEFUVVNfRFRPX1VOU1BFQ0lGSUVEEAASIQodREVQTE9ZTUVO", - "VF9TVEFUVVNfRFRPX1BFTkRJTkcQARIlCiFERVBMT1lNRU5UX1NUQVRVU19E", - "VE9fSU5fUFJPR1JFU1MQAhIhCh1ERVBMT1lNRU5UX1NUQVRVU19EVE9fU1VD", - "Q0VTUxADEiAKHERFUExPWU1FTlRfU1RBVFVTX0RUT19GQUlMRUQQBCrEAQoS", - "QnJvd3NlTm9kZUNsYXNzRHRvEiUKIUJST1dTRV9OT0RFX0NMQVNTX0RUT19V", - "TlNQRUNJRklFRBAAEiAKHEJST1dTRV9OT0RFX0NMQVNTX0RUT19PQkpFQ1QQ", - "ARIiCh5CUk9XU0VfTk9ERV9DTEFTU19EVE9fVkFSSUFCTEUQAhIgChxCUk9X", - "U0VfTk9ERV9DTEFTU19EVE9fTUVUSE9EEAMSHwobQlJPV1NFX05PREVfQ0xB", - "U1NfRFRPX09USEVSEAQqoQIKFEJyb3dzZUZhaWx1cmVLaW5kRHRvEicKI0JS", - "T1dTRV9GQUlMVVJFX0tJTkRfRFRPX1VOU1BFQ0lGSUVEEAASMAosQlJPV1NF", - "X0ZBSUxVUkVfS0lORF9EVE9fQ09OTkVDVElPTl9OT1RfRk9VTkQQARI0CjBC", - "Uk9XU0VfRkFJTFVSRV9LSU5EX0RUT19DT05ORUNUSU9OX05PVF9DT05ORUNU", - "RUQQAhIpCiVCUk9XU0VfRkFJTFVSRV9LSU5EX0RUT19OT1RfQlJPV1NBQkxF", - "EAMSIwofQlJPV1NFX0ZBSUxVUkVfS0lORF9EVE9fVElNRU9VVBAEEigKJEJS", - "T1dTRV9GQUlMVVJFX0tJTkRfRFRPX1NFUlZFUl9FUlJPUhAFKqoCChtSZWFk", - "VGFnVmFsdWVzRmFpbHVyZUtpbmREdG8SMAosUkVBRF9UQUdfVkFMVUVTX0ZB", - "SUxVUkVfS0lORF9EVE9fVU5TUEVDSUZJRUQQABI5CjVSRUFEX1RBR19WQUxV", - "RVNfRkFJTFVSRV9LSU5EX0RUT19DT05ORUNUSU9OX05PVF9GT1VORBABEj0K", - "OVJFQURfVEFHX1ZBTFVFU19GQUlMVVJFX0tJTkRfRFRPX0NPTk5FQ1RJT05f", - "Tk9UX0NPTk5FQ1RFRBACEiwKKFJFQURfVEFHX1ZBTFVFU19GQUlMVVJFX0tJ", - "TkRfRFRPX1RJTUVPVVQQAxIxCi1SRUFEX1RBR19WQUxVRVNfRkFJTFVSRV9L", - "SU5EX0RUT19TRVJWRVJfRVJST1IQBCqTAgoUVmVyaWZ5RmFpbHVyZUtpbmRE", - "dG8SJwojVkVSSUZZX0ZBSUxVUkVfS0lORF9EVE9fVU5TUEVDSUZJRUQQABIn", - "CiNWRVJJRllfRkFJTFVSRV9LSU5EX0RUT19VTlJFQUNIQUJMRRABEicKI1ZF", - "UklGWV9GQUlMVVJFX0tJTkRfRFRPX0FVVEhfRkFJTEVEEAISMQotVkVSSUZZ", - "X0ZBSUxVUkVfS0lORF9EVE9fVU5UUlVTVEVEX0NFUlRJRklDQVRFEAMSIwof", - "VkVSSUZZX0ZBSUxVUkVfS0lORF9EVE9fVElNRU9VVBAEEigKJFZFUklGWV9G", - "QUlMVVJFX0tJTkRfRFRPX1NFUlZFUl9FUlJPUhAFKuUBChpTdG9yZUFuZEZv", - "cndhcmRDYXRlZ29yeUR0bxIuCipTVE9SRV9BTkRfRk9SV0FSRF9DQVRFR09S", - "WV9EVE9fVU5TUEVDSUZJRUQQABIyCi5TVE9SRV9BTkRfRk9SV0FSRF9DQVRF", - "R09SWV9EVE9fRVhURVJOQUxfU1lTVEVNEAESLworU1RPUkVfQU5EX0ZPUldB", - "UkRfQ0FURUdPUllfRFRPX05PVElGSUNBVElPThACEjIKLlNUT1JFX0FORF9G", - "T1JXQVJEX0NBVEVHT1JZX0RUT19DQUNIRURfREJfV1JJVEUQAypoCg1BbGFy", - "bVN0YXRlRHRvEh8KG0FMQVJNX1NUQVRFX0RUT19VTlNQRUNJRklFRBAAEhoK", - "FkFMQVJNX1NUQVRFX0RUT19BQ1RJVkUQARIaChZBTEFSTV9TVEFURV9EVE9f", - "Tk9STUFMEAIquQEKDUFsYXJtTGV2ZWxEdG8SHwobQUxBUk1fTEVWRUxfRFRP", - "X1VOU1BFQ0lGSUVEEAASGAoUQUxBUk1fTEVWRUxfRFRPX05PTkUQARIXChNB", - "TEFSTV9MRVZFTF9EVE9fTE9XEAISGwoXQUxBUk1fTEVWRUxfRFRPX0xPV19M", - "T1cQAxIYChRBTEFSTV9MRVZFTF9EVE9fSElHSBAEEh0KGUFMQVJNX0xFVkVM", - "X0RUT19ISUdIX0hJR0gQBSqSAQoMQWxhcm1LaW5kRHRvEh4KGkFMQVJNX0tJ", - "TkRfRFRPX1VOU1BFQ0lGSUVEEAASGwoXQUxBUk1fS0lORF9EVE9fQ09NUFVU", - "RUQQARIgChxBTEFSTV9LSU5EX0RUT19OQVRJVkVfT1BDX1VBEAISIwofQUxB", - "Uk1fS0lORF9EVE9fTkFUSVZFX01YX0FDQ0VTUxADKugBChNBbGFybVNoZWx2", - "ZVN0YXRlRHRvEiYKIkFMQVJNX1NIRUxWRV9TVEFURV9EVE9fVU5TUEVDSUZJ", - "RUQQABIkCiBBTEFSTV9TSEVMVkVfU1RBVEVfRFRPX1VOU0hFTFZFRBABEisK", - "J0FMQVJNX1NIRUxWRV9TVEFURV9EVE9fT05FX1NIT1RfU0hFTFZFRBACEigK", - "JEFMQVJNX1NIRUxWRV9TVEFURV9EVE9fVElNRURfU0hFTFZFRBADEiwKKEFM", - "QVJNX1NIRUxWRV9TVEFURV9EVE9fUEVSTUFORU5UX1NIRUxWRUQQBDKEBQoS", - "U2l0ZUNvbW1hbmRTZXJ2aWNlEmwKEEV4ZWN1dGVMaWZlY3ljbGUSLC5zY2Fk", - "YWJyaWRnZS5zaXRlY29tbWFuZC52MS5MaWZlY3ljbGVSZXF1ZXN0Giouc2Nh", - "ZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuTGlmZWN5Y2xlUmVwbHkSYAoMRXhl", - "Y3V0ZU9wY1VhEiguc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuT3BjVWFS", - "ZXF1ZXN0GiYuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuT3BjVWFSZXBs", - "eRJgCgxFeGVjdXRlUXVlcnkSKC5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52", - "MS5RdWVyeVJlcXVlc3QaJi5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5R", - "dWVyeVJlcGx5EmMKDUV4ZWN1dGVQYXJrZWQSKS5zY2FkYWJyaWRnZS5zaXRl", - "Y29tbWFuZC52MS5QYXJrZWRSZXF1ZXN0Gicuc2NhZGFicmlkZ2Uuc2l0ZWNv", - "bW1hbmQudjEuUGFya2VkUmVwbHkSYAoMRXhlY3V0ZVJvdXRlEiguc2NhZGFi", - "cmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUm91dGVSZXF1ZXN0GiYuc2NhZGFicmlk", - "Z2Uuc2l0ZWNvbW1hbmQudjEuUm91dGVSZXBseRJ1Cg9UcmlnZ2VyRmFpbG92", - "ZXISMi5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5UcmlnZ2VyU2l0ZUZh", - "aWxvdmVyRHRvGi4uc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuU2l0ZUZh", - "aWxvdmVyQWNrRHRvQiuqAihaQi5NT00uV1cuU2NhZGFCcmlkZ2UuQ29tbXVu", - "aWNhdGlvbi5HcnBjYgZwcm90bzM=")); + "cBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAimgQKEExpZmVj", + "eWNsZVJlcXVlc3QSVQoScmVmcmVzaF9kZXBsb3ltZW50GAEgASgLMjcuc2Nh", + "ZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUmVmcmVzaERlcGxveW1lbnRDb21t", + "YW5kRHRvSAASTwoPZW5hYmxlX2luc3RhbmNlGAIgASgLMjQuc2NhZGFicmlk", + "Z2Uuc2l0ZWNvbW1hbmQudjEuRW5hYmxlSW5zdGFuY2VDb21tYW5kRHRvSAAS", + "UQoQZGlzYWJsZV9pbnN0YW5jZRgDIAEoCzI1LnNjYWRhYnJpZGdlLnNpdGVj", + "b21tYW5kLnYxLkRpc2FibGVJbnN0YW5jZUNvbW1hbmREdG9IABJPCg9kZWxl", + "dGVfaW5zdGFuY2UYBCABKAsyNC5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52", + "MS5EZWxldGVJbnN0YW5jZUNvbW1hbmREdG9IABJcChZkZXBsb3ltZW50X3N0", + "YXRlX3F1ZXJ5GAUgASgLMjouc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEu", + "RGVwbG95bWVudFN0YXRlUXVlcnlSZXF1ZXN0RHRvSAASUQoQZGVwbG95X2Fy", + "dGlmYWN0cxgGIAEoCzI1LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkRl", + "cGxveUFydGlmYWN0c0NvbW1hbmREdG9IAEIJCgdjb21tYW5kIoADCg5MaWZl", + "Y3ljbGVSZXBseRJUChFkZXBsb3ltZW50X3N0YXR1cxgBIAEoCzI3LnNjYWRh", + "YnJpZGdlLnNpdGVjb21tYW5kLnYxLkRlcGxveW1lbnRTdGF0dXNSZXNwb25z", + "ZUR0b0gAElYKEmluc3RhbmNlX2xpZmVjeWNsZRgCIAEoCzI4LnNjYWRhYnJp", + "ZGdlLnNpdGVjb21tYW5kLnYxLkluc3RhbmNlTGlmZWN5Y2xlUmVzcG9uc2VE", + "dG9IABJdChZkZXBsb3ltZW50X3N0YXRlX3F1ZXJ5GAMgASgLMjsuc2NhZGFi", + "cmlkZ2Uuc2l0ZWNvbW1hbmQudjEuRGVwbG95bWVudFN0YXRlUXVlcnlSZXNw", + "b25zZUR0b0gAElgKE2FydGlmYWN0X2RlcGxveW1lbnQYBCABKAsyOS5zY2Fk", + "YWJyaWRnZS5zaXRlY29tbWFuZC52MS5BcnRpZmFjdERlcGxveW1lbnRSZXNw", + "b25zZUR0b0gAQgcKBXJlcGx5InwKFEJyb3dzZU5vZGVDb21tYW5kRHRvEhcK", + "D2Nvbm5lY3Rpb25fbmFtZRgBIAEoCRIWCg5wYXJlbnRfbm9kZV9pZBgCIAEo", + "CRIaChJjb250aW51YXRpb25fdG9rZW4YAyABKAkSFwoPc2l0ZV9pZGVudGlm", + "aWVyGAQgASgJIoICCg1Ccm93c2VOb2RlRHRvEg8KB25vZGVfaWQYASABKAkS", + "FAoMZGlzcGxheV9uYW1lGAIgASgJEkIKCm5vZGVfY2xhc3MYAyABKA4yLi5z", + "Y2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5Ccm93c2VOb2RlQ2xhc3NEdG8S", + "FAoMaGFzX2NoaWxkcmVuGAQgASgIEhEKCWRhdGFfdHlwZRgFIAEoCRIvCgp2", + "YWx1ZV9yYW5rGAYgASgLMhsuZ29vZ2xlLnByb3RvYnVmLkludDMyVmFsdWUS", + "LAoId3JpdGFibGUYByABKAsyGi5nb29nbGUucHJvdG9idWYuQm9vbFZhbHVl", + "ImMKEEJyb3dzZUZhaWx1cmVEdG8SPgoEa2luZBgBIAEoDjIwLnNjYWRhYnJp", + "ZGdlLnNpdGVjb21tYW5kLnYxLkJyb3dzZUZhaWx1cmVLaW5kRHRvEg8KB21l", + "c3NhZ2UYAiABKAkiwAEKE0Jyb3dzZU5vZGVSZXN1bHREdG8SOwoIY2hpbGRy", + "ZW4YASADKAsyKS5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5Ccm93c2VO", + "b2RlRHRvEhEKCXRydW5jYXRlZBgCIAEoCBI9CgdmYWlsdXJlGAMgASgLMiwu", + "c2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuQnJvd3NlRmFpbHVyZUR0bxIa", + "ChJjb250aW51YXRpb25fdG9rZW4YBCABKAkihwEKHFNlYXJjaEFkZHJlc3NT", + "cGFjZUNvbW1hbmREdG8SFwoPY29ubmVjdGlvbl9uYW1lGAEgASgJEg0KBXF1", + "ZXJ5GAIgASgJEhEKCW1heF9kZXB0aBgDIAEoBRITCgttYXhfcmVzdWx0cxgE", + "IAEoBRIXCg9zaXRlX2lkZW50aWZpZXIYBSABKAkiXQoUQWRkcmVzc1NwYWNl", + "TWF0Y2hEdG8SNwoEbm9kZRgBIAEoCzIpLnNjYWRhYnJpZGdlLnNpdGVjb21t", + "YW5kLnYxLkJyb3dzZU5vZGVEdG8SDAoEcGF0aBgCIAEoCSK0AQobU2VhcmNo", + "QWRkcmVzc1NwYWNlUmVzdWx0RHRvEkEKB21hdGNoZXMYASADKAsyMC5zY2Fk", + "YWJyaWRnZS5zaXRlY29tbWFuZC52MS5BZGRyZXNzU3BhY2VNYXRjaER0bxIT", + "CgtjYXBfcmVhY2hlZBgCIAEoCBI9CgdmYWlsdXJlGAMgASgLMiwuc2NhZGFi", + "cmlkZ2Uuc2l0ZWNvbW1hbmQudjEuQnJvd3NlRmFpbHVyZUR0byJFChdSZWFk", + "VGFnVmFsdWVzQ29tbWFuZER0bxIXCg9jb25uZWN0aW9uX25hbWUYASABKAkS", + "EQoJdGFnX3BhdGhzGAIgAygJIsQBChFUYWdSZWFkT3V0Y29tZUR0bxIQCgh0", + "YWdfcGF0aBgBIAEoCRIPCgdzdWNjZXNzGAIgASgIEjUKBXZhbHVlGAMgASgL", + "MiYuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuTG9vc2VWYWx1ZRIPCgdx", + "dWFsaXR5GAQgASgJEi0KCXRpbWVzdGFtcBgFIAEoCzIaLmdvb2dsZS5wcm90", + "b2J1Zi5UaW1lc3RhbXASFQoNZXJyb3JfbWVzc2FnZRgGIAEoCSJxChdSZWFk", + "VGFnVmFsdWVzRmFpbHVyZUR0bxJFCgRraW5kGAEgASgOMjcuc2NhZGFicmlk", + "Z2Uuc2l0ZWNvbW1hbmQudjEuUmVhZFRhZ1ZhbHVlc0ZhaWx1cmVLaW5kRHRv", + "Eg8KB21lc3NhZ2UYAiABKAkinwEKFlJlYWRUYWdWYWx1ZXNSZXN1bHREdG8S", + "PwoIb3V0Y29tZXMYASADKAsyLS5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52", + "MS5UYWdSZWFkT3V0Y29tZUR0bxJECgdmYWlsdXJlGAIgASgLMjMuc2NhZGFi", + "cmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUmVhZFRhZ1ZhbHVlc0ZhaWx1cmVEdG8i", + "cwoYVmVyaWZ5RW5kcG9pbnRDb21tYW5kRHRvEhcKD2Nvbm5lY3Rpb25fbmFt", + "ZRgBIAEoCRIQCghwcm90b2NvbBgCIAEoCRITCgtjb25maWdfanNvbhgDIAEo", + "CRIXCg9zaXRlX2lkZW50aWZpZXIYBCABKAkiwwEKEVNlcnZlckNlcnRJbmZv", + "RHRvEhIKCnRodW1icHJpbnQYASABKAkSDwoHc3ViamVjdBgCIAEoCRIOCgZp", + "c3N1ZXIYAyABKAkSMgoObm90X2JlZm9yZV91dGMYBCABKAsyGi5nb29nbGUu", + "cHJvdG9idWYuVGltZXN0YW1wEjEKDW5vdF9hZnRlcl91dGMYBSABKAsyGi5n", + "b29nbGUucHJvdG9idWYuVGltZXN0YW1wEhIKCmRlcl9iYXNlNjQYBiABKAki", + "WQoWVmVyaWZ5RmFpbHVyZUtpbmRWYWx1ZRI/CgV2YWx1ZRgBIAEoDjIwLnNj", + "YWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlZlcmlmeUZhaWx1cmVLaW5kRHRv", + "IsABChdWZXJpZnlFbmRwb2ludFJlc3VsdER0bxIPCgdzdWNjZXNzGAEgASgI", + "EkgKDGZhaWx1cmVfa2luZBgCIAEoCzIyLnNjYWRhYnJpZGdlLnNpdGVjb21t", + "YW5kLnYxLlZlcmlmeUZhaWx1cmVLaW5kVmFsdWUSDQoFZXJyb3IYAyABKAkS", + "OwoEY2VydBgEIAEoCzItLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlNl", + "cnZlckNlcnRJbmZvRHRvInUKGVRydXN0U2VydmVyQ2VydENvbW1hbmREdG8S", + "FwoPY29ubmVjdGlvbl9uYW1lGAEgASgJEhIKCmRlcl9iYXNlNjQYAiABKAkS", + "EgoKdGh1bWJwcmludBgDIAEoCRIXCg9zaXRlX2lkZW50aWZpZXIYBCABKAki", + "NAoZTGlzdFNlcnZlckNlcnRzQ29tbWFuZER0bxIXCg9zaXRlX2lkZW50aWZp", + "ZXIYASABKAkiSQoaUmVtb3ZlU2VydmVyQ2VydENvbW1hbmREdG8SEgoKdGh1", + "bWJwcmludBgBIAEoCRIXCg9zaXRlX2lkZW50aWZpZXIYAiABKAkiwgEKElRy", + "dXN0ZWRDZXJ0SW5mb0R0bxISCgp0aHVtYnByaW50GAEgASgJEg8KB3N1Ympl", + "Y3QYAiABKAkSDgoGaXNzdWVyGAMgASgJEjIKDm5vdF9iZWZvcmVfdXRjGAQg", + "ASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIxCg1ub3RfYWZ0ZXJf", + "dXRjGAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIQCghyZWpl", + "Y3RlZBgGIAEoCCJXChZUcnVzdGVkQ2VydEluZm9MaXN0RHRvEj0KBWl0ZW1z", + "GAEgAygLMi4uc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuVHJ1c3RlZENl", + "cnRJbmZvRHRvIncKEkNlcnRUcnVzdFJlc3VsdER0bxIPCgdzdWNjZXNzGAEg", + "ASgIEg0KBWVycm9yGAIgASgJEkEKBWNlcnRzGAMgASgLMjIuc2NhZGFicmlk", + "Z2Uuc2l0ZWNvbW1hbmQudjEuVHJ1c3RlZENlcnRJbmZvTGlzdER0byK9AQoS", + "V3JpdGVUYWdSZXF1ZXN0RHRvEhYKDmNvcnJlbGF0aW9uX2lkGAEgASgJEhcK", + "D2Nvbm5lY3Rpb25fbmFtZRgCIAEoCRIQCgh0YWdfcGF0aBgDIAEoCRI1CgV2", + "YWx1ZRgEIAEoCzImLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkxvb3Nl", + "VmFsdWUSLQoJdGltZXN0YW1wGAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRp", + "bWVzdGFtcCKEAQoTV3JpdGVUYWdSZXNwb25zZUR0bxIWCg5jb3JyZWxhdGlv", + "bl9pZBgBIAEoCRIPCgdzdWNjZXNzGAIgASgIEhUKDWVycm9yX21lc3NhZ2UY", + "AyABKAkSLQoJdGltZXN0YW1wGAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRp", + "bWVzdGFtcCKgBQoMT3BjVWFSZXF1ZXN0EkcKC2Jyb3dzZV9ub2RlGAEgASgL", + "MjAuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuQnJvd3NlTm9kZUNvbW1h", + "bmREdG9IABJYChRzZWFyY2hfYWRkcmVzc19zcGFjZRgCIAEoCzI4LnNjYWRh", + "YnJpZGdlLnNpdGVjb21tYW5kLnYxLlNlYXJjaEFkZHJlc3NTcGFjZUNvbW1h", + "bmREdG9IABJOCg9yZWFkX3RhZ192YWx1ZXMYAyABKAsyMy5zY2FkYWJyaWRn", + "ZS5zaXRlY29tbWFuZC52MS5SZWFkVGFnVmFsdWVzQ29tbWFuZER0b0gAEk8K", + "D3ZlcmlmeV9lbmRwb2ludBgEIAEoCzI0LnNjYWRhYnJpZGdlLnNpdGVjb21t", + "YW5kLnYxLlZlcmlmeUVuZHBvaW50Q29tbWFuZER0b0gAElIKEXRydXN0X3Nl", + "cnZlcl9jZXJ0GAUgASgLMjUuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEu", + "VHJ1c3RTZXJ2ZXJDZXJ0Q29tbWFuZER0b0gAElIKEWxpc3Rfc2VydmVyX2Nl", + "cnRzGAYgASgLMjUuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuTGlzdFNl", + "cnZlckNlcnRzQ29tbWFuZER0b0gAElQKEnJlbW92ZV9zZXJ2ZXJfY2VydBgH", + "IAEoCzI2LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlJlbW92ZVNlcnZl", + "ckNlcnRDb21tYW5kRHRvSAASQwoJd3JpdGVfdGFnGAggASgLMi4uc2NhZGFi", + "cmlkZ2Uuc2l0ZWNvbW1hbmQudjEuV3JpdGVUYWdSZXF1ZXN0RHRvSABCCQoH", + "Y29tbWFuZCLhAwoKT3BjVWFSZXBseRJGCgticm93c2Vfbm9kZRgBIAEoCzIv", + "LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkJyb3dzZU5vZGVSZXN1bHRE", + "dG9IABJXChRzZWFyY2hfYWRkcmVzc19zcGFjZRgCIAEoCzI3LnNjYWRhYnJp", + "ZGdlLnNpdGVjb21tYW5kLnYxLlNlYXJjaEFkZHJlc3NTcGFjZVJlc3VsdER0", + "b0gAEk0KD3JlYWRfdGFnX3ZhbHVlcxgDIAEoCzIyLnNjYWRhYnJpZGdlLnNp", + "dGVjb21tYW5kLnYxLlJlYWRUYWdWYWx1ZXNSZXN1bHREdG9IABJOCg92ZXJp", + "ZnlfZW5kcG9pbnQYBCABKAsyMy5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52", + "MS5WZXJpZnlFbmRwb2ludFJlc3VsdER0b0gAEkQKCmNlcnRfdHJ1c3QYBSAB", + "KAsyLi5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5DZXJ0VHJ1c3RSZXN1", + "bHREdG9IABJECgl3cml0ZV90YWcYBiABKAsyLy5zY2FkYWJyaWRnZS5zaXRl", + "Y29tbWFuZC52MS5Xcml0ZVRhZ1Jlc3BvbnNlRHRvSABCBwoFcmVwbHkixQIK", + "F0V2ZW50TG9nUXVlcnlSZXF1ZXN0RHRvEhYKDmNvcnJlbGF0aW9uX2lkGAEg", + "ASgJEg8KB3NpdGVfaWQYAiABKAkSKAoEZnJvbRgDIAEoCzIaLmdvb2dsZS5w", + "cm90b2J1Zi5UaW1lc3RhbXASJgoCdG8YBCABKAsyGi5nb29nbGUucHJvdG9i", + "dWYuVGltZXN0YW1wEhIKCmV2ZW50X3R5cGUYBSABKAkSEAoIc2V2ZXJpdHkY", + "BiABKAkSEwoLaW5zdGFuY2VfaWQYByABKAkSFgoOa2V5d29yZF9maWx0ZXIY", + "CCABKAkSGgoSY29udGludWF0aW9uX3Rva2VuGAkgASgJEhEKCXBhZ2Vfc2l6", + "ZRgKIAEoBRItCgl0aW1lc3RhbXAYCyABKAsyGi5nb29nbGUucHJvdG9idWYu", + "VGltZXN0YW1wIroBChBFdmVudExvZ0VudHJ5RHRvEgoKAmlkGAEgASgJEi0K", + "CXRpbWVzdGFtcBgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAS", + "EgoKZXZlbnRfdHlwZRgDIAEoCRIQCghzZXZlcml0eRgEIAEoCRITCgtpbnN0", + "YW5jZV9pZBgFIAEoCRIOCgZzb3VyY2UYBiABKAkSDwoHbWVzc2FnZRgHIAEo", + "CRIPCgdkZXRhaWxzGAggASgJIocCChhFdmVudExvZ1F1ZXJ5UmVzcG9uc2VE", + "dG8SFgoOY29ycmVsYXRpb25faWQYASABKAkSDwoHc2l0ZV9pZBgCIAEoCRI9", + "CgdlbnRyaWVzGAMgAygLMiwuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEu", + "RXZlbnRMb2dFbnRyeUR0bxIaChJjb250aW51YXRpb25fdG9rZW4YBCABKAkS", + "EAoIaGFzX21vcmUYBSABKAgSDwoHc3VjY2VzcxgGIAEoCBIVCg1lcnJvcl9t", + "ZXNzYWdlGAcgASgJEi0KCXRpbWVzdGFtcBgIIAEoCzIaLmdvb2dsZS5wcm90", + "b2J1Zi5UaW1lc3RhbXAiZAoXRGVidWdTbmFwc2hvdFJlcXVlc3REdG8SHAoU", + "aW5zdGFuY2VfdW5pcXVlX25hbWUYASABKAkSFgoOY29ycmVsYXRpb25faWQY", + "AiABKAkSEwoLYWxhcm1zX29ubHkYAyABKAgiVAocU3Vic2NyaWJlRGVidWdW", + "aWV3UmVxdWVzdER0bxIcChRpbnN0YW5jZV91bmlxdWVfbmFtZRgBIAEoCRIW", + "Cg5jb3JyZWxhdGlvbl9pZBgCIAEoCSJWCh5VbnN1YnNjcmliZURlYnVnVmll", + "d1JlcXVlc3REdG8SHAoUaW5zdGFuY2VfdW5pcXVlX25hbWUYASABKAkSFgoO", + "Y29ycmVsYXRpb25faWQYAiABKAkiHAoaVW5zdWJzY3JpYmVEZWJ1Z1ZpZXdB", + "Y2tEdG8i1AEKFkFsYXJtQ29uZGl0aW9uU3RhdGVEdG8SDgoGYWN0aXZlGAEg", + "ASgIEhQKDGFja25vd2xlZGdlZBgCIAEoCBItCgljb25maXJtZWQYAyABKAsy", + "Gi5nb29nbGUucHJvdG9idWYuQm9vbFZhbHVlEj8KBnNoZWx2ZRgEIAEoDjIv", + "LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkFsYXJtU2hlbHZlU3RhdGVE", + "dG8SEgoKc3VwcHJlc3NlZBgFIAEoCBIQCghzZXZlcml0eRgGIAEoBSLdAQoW", + "RGVidWdBdHRyaWJ1dGVWYWx1ZUR0bxIcChRpbnN0YW5jZV91bmlxdWVfbmFt", + "ZRgBIAEoCRIWCg5hdHRyaWJ1dGVfcGF0aBgCIAEoCRIWCg5hdHRyaWJ1dGVf", + "bmFtZRgDIAEoCRI1CgV2YWx1ZRgEIAEoCzImLnNjYWRhYnJpZGdlLnNpdGVj", + "b21tYW5kLnYxLkxvb3NlVmFsdWUSDwoHcXVhbGl0eRgFIAEoCRItCgl0aW1l", + "c3RhbXAYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIq8FChJE", + "ZWJ1Z0FsYXJtU3RhdGVEdG8SHAoUaW5zdGFuY2VfdW5pcXVlX25hbWUYASAB", + "KAkSEgoKYWxhcm1fbmFtZRgCIAEoCRI4CgVzdGF0ZRgDIAEoDjIpLnNjYWRh", + "YnJpZGdlLnNpdGVjb21tYW5kLnYxLkFsYXJtU3RhdGVEdG8SEAoIcHJpb3Jp", + "dHkYBCABKAUSLQoJdGltZXN0YW1wGAUgASgLMhouZ29vZ2xlLnByb3RvYnVm", + "LlRpbWVzdGFtcBI4CgVsZXZlbBgGIAEoDjIpLnNjYWRhYnJpZGdlLnNpdGVj", + "b21tYW5kLnYxLkFsYXJtTGV2ZWxEdG8SDwoHbWVzc2FnZRgHIAEoCRI2CgRr", + "aW5kGAggASgOMiguc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuQWxhcm1L", + "aW5kRHRvEkUKCWNvbmRpdGlvbhgJIAEoCzIyLnNjYWRhYnJpZGdlLnNpdGVj", + "b21tYW5kLnYxLkFsYXJtQ29uZGl0aW9uU3RhdGVEdG8SGAoQc291cmNlX3Jl", + "ZmVyZW5jZRgKIAEoCRIXCg9hbGFybV90eXBlX25hbWUYCyABKAkSEAoIY2F0", + "ZWdvcnkYDCABKAkSFQoNb3BlcmF0b3JfdXNlchgNIAEoCRIYChBvcGVyYXRv", + "cl9jb21tZW50GA4gASgJEjcKE29yaWdpbmFsX3JhaXNlX3RpbWUYDyABKAsy", + "Gi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhUKDWN1cnJlbnRfdmFsdWUY", + "ECABKAkSEwoLbGltaXRfdmFsdWUYESABKAkSJAocbmF0aXZlX3NvdXJjZV9j", + "YW5vbmljYWxfbmFtZRgSIAEoCRIhChlpc19jb25maWd1cmVkX3BsYWNlaG9s", + "ZGVyGBMgASgIIpwCChREZWJ1Z1ZpZXdTbmFwc2hvdER0bxIcChRpbnN0YW5j", + "ZV91bmlxdWVfbmFtZRgBIAEoCRJMChBhdHRyaWJ1dGVfdmFsdWVzGAIgAygL", + "MjIuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuRGVidWdBdHRyaWJ1dGVW", + "YWx1ZUR0bxJECgxhbGFybV9zdGF0ZXMYAyADKAsyLi5zY2FkYWJyaWRnZS5z", + "aXRlY29tbWFuZC52MS5EZWJ1Z0FsYXJtU3RhdGVEdG8SNgoSc25hcHNob3Rf", + "dGltZXN0YW1wGAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIa", + "ChJpbnN0YW5jZV9ub3RfZm91bmQYBSABKAgi8AIKDFF1ZXJ5UmVxdWVzdBJO", + "Cg9ldmVudF9sb2dfcXVlcnkYASABKAsyMy5zY2FkYWJyaWRnZS5zaXRlY29t", + "bWFuZC52MS5FdmVudExvZ1F1ZXJ5UmVxdWVzdER0b0gAEk0KDmRlYnVnX3Nu", + "YXBzaG90GAIgASgLMjMuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuRGVi", + "dWdTbmFwc2hvdFJlcXVlc3REdG9IABJYChRzdWJzY3JpYmVfZGVidWdfdmll", + "dxgDIAEoCzI4LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlN1YnNjcmli", + "ZURlYnVnVmlld1JlcXVlc3REdG9IABJcChZ1bnN1YnNjcmliZV9kZWJ1Z192", + "aWV3GAQgASgLMjouc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuVW5zdWJz", + "Y3JpYmVEZWJ1Z1ZpZXdSZXF1ZXN0RHRvSABCCQoHY29tbWFuZCKRAgoKUXVl", + "cnlSZXBseRJPCg9ldmVudF9sb2dfcXVlcnkYASABKAsyNC5zY2FkYWJyaWRn", + "ZS5zaXRlY29tbWFuZC52MS5FdmVudExvZ1F1ZXJ5UmVzcG9uc2VEdG9IABJP", + "ChNkZWJ1Z192aWV3X3NuYXBzaG90GAIgASgLMjAuc2NhZGFicmlkZ2Uuc2l0", + "ZWNvbW1hbmQudjEuRGVidWdWaWV3U25hcHNob3REdG9IABJYChZ1bnN1YnNj", + "cmliZV9kZWJ1Z192aWV3GAMgASgLMjYuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1h", + "bmQudjEuVW5zdWJzY3JpYmVEZWJ1Z1ZpZXdBY2tEdG9IAEIHCgVyZXBseSKe", + "AQocUGFya2VkTWVzc2FnZVF1ZXJ5UmVxdWVzdER0bxIWCg5jb3JyZWxhdGlv", + "bl9pZBgBIAEoCRIPCgdzaXRlX2lkGAIgASgJEhMKC3BhZ2VfbnVtYmVyGAMg", + "ASgFEhEKCXBhZ2Vfc2l6ZRgEIAEoBRItCgl0aW1lc3RhbXAYBSABKAsyGi5n", + "b29nbGUucHJvdG9idWYuVGltZXN0YW1wIvICChVQYXJrZWRNZXNzYWdlRW50", + "cnlEdG8SEgoKbWVzc2FnZV9pZBgBIAEoCRIVCg10YXJnZXRfc3lzdGVtGAIg", + "ASgJEhMKC21ldGhvZF9uYW1lGAMgASgJEhUKDWVycm9yX21lc3NhZ2UYBCAB", + "KAkSFQoNYXR0ZW1wdF9jb3VudBgFIAEoBRI2ChJvcmlnaW5hbF90aW1lc3Rh", + "bXAYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjoKFmxhc3Rf", + "YXR0ZW1wdF90aW1lc3RhbXAYByABKAsyGi5nb29nbGUucHJvdG9idWYuVGlt", + "ZXN0YW1wEhQKDG1heF9hdHRlbXB0cxgIIAEoBRJICghjYXRlZ29yeRgJIAEo", + "DjI2LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlN0b3JlQW5kRm9yd2Fy", + "ZENhdGVnb3J5RHRvEhcKD29yaWdpbl9pbnN0YW5jZRgKIAEoCSKhAgodUGFy", + "a2VkTWVzc2FnZVF1ZXJ5UmVzcG9uc2VEdG8SFgoOY29ycmVsYXRpb25faWQY", + "ASABKAkSDwoHc2l0ZV9pZBgCIAEoCRJDCghtZXNzYWdlcxgDIAMoCzIxLnNj", + "YWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlBhcmtlZE1lc3NhZ2VFbnRyeUR0", + "bxITCgt0b3RhbF9jb3VudBgEIAEoBRITCgtwYWdlX251bWJlchgFIAEoBRIR", + "CglwYWdlX3NpemUYBiABKAUSDwoHc3VjY2VzcxgHIAEoCBIVCg1lcnJvcl9t", + "ZXNzYWdlGAggASgJEi0KCXRpbWVzdGFtcBgJIAEoCzIaLmdvb2dsZS5wcm90", + "b2J1Zi5UaW1lc3RhbXAiigEKHFBhcmtlZE1lc3NhZ2VSZXRyeVJlcXVlc3RE", + "dG8SFgoOY29ycmVsYXRpb25faWQYASABKAkSDwoHc2l0ZV9pZBgCIAEoCRIS", + "CgptZXNzYWdlX2lkGAMgASgJEi0KCXRpbWVzdGFtcBgEIAEoCzIaLmdvb2ds", + "ZS5wcm90b2J1Zi5UaW1lc3RhbXAiXwodUGFya2VkTWVzc2FnZVJldHJ5UmVz", + "cG9uc2VEdG8SFgoOY29ycmVsYXRpb25faWQYASABKAkSDwoHc3VjY2VzcxgC", + "IAEoCBIVCg1lcnJvcl9tZXNzYWdlGAMgASgJIowBCh5QYXJrZWRNZXNzYWdl", + "RGlzY2FyZFJlcXVlc3REdG8SFgoOY29ycmVsYXRpb25faWQYASABKAkSDwoH", + "c2l0ZV9pZBgCIAEoCRISCgptZXNzYWdlX2lkGAMgASgJEi0KCXRpbWVzdGFt", + "cBgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiYQofUGFya2Vk", + "TWVzc2FnZURpc2NhcmRSZXNwb25zZUR0bxIWCg5jb3JyZWxhdGlvbl9pZBgB", + "IAEoCRIPCgdzdWNjZXNzGAIgASgIEhUKDWVycm9yX21lc3NhZ2UYAyABKAki", + "TwoXUmV0cnlQYXJrZWRPcGVyYXRpb25EdG8SFgoOY29ycmVsYXRpb25faWQY", + "ASABKAkSHAoUdHJhY2tlZF9vcGVyYXRpb25faWQYAiABKAkiUQoZRGlzY2Fy", + "ZFBhcmtlZE9wZXJhdGlvbkR0bxIWCg5jb3JyZWxhdGlvbl9pZBgBIAEoCRIc", + "ChR0cmFja2VkX29wZXJhdGlvbl9pZBgCIAEoCSJdChtQYXJrZWRPcGVyYXRp", + "b25BY3Rpb25BY2tEdG8SFgoOY29ycmVsYXRpb25faWQYASABKAkSDwoHYXBw", + "bGllZBgCIAEoCBIVCg1lcnJvcl9tZXNzYWdlGAMgASgJIt4DCg1QYXJrZWRS", + "ZXF1ZXN0ElgKFHBhcmtlZF9tZXNzYWdlX3F1ZXJ5GAEgASgLMjguc2NhZGFi", + "cmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUGFya2VkTWVzc2FnZVF1ZXJ5UmVxdWVz", + "dER0b0gAElgKFHBhcmtlZF9tZXNzYWdlX3JldHJ5GAIgASgLMjguc2NhZGFi", + "cmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUGFya2VkTWVzc2FnZVJldHJ5UmVxdWVz", + "dER0b0gAElwKFnBhcmtlZF9tZXNzYWdlX2Rpc2NhcmQYAyABKAsyOi5zY2Fk", + "YWJyaWRnZS5zaXRlY29tbWFuZC52MS5QYXJrZWRNZXNzYWdlRGlzY2FyZFJl", + "cXVlc3REdG9IABJVChZyZXRyeV9wYXJrZWRfb3BlcmF0aW9uGAQgASgLMjMu", + "c2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUmV0cnlQYXJrZWRPcGVyYXRp", + "b25EdG9IABJZChhkaXNjYXJkX3BhcmtlZF9vcGVyYXRpb24YBSABKAsyNS5z", + "Y2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5EaXNjYXJkUGFya2VkT3BlcmF0", + "aW9uRHRvSABCCQoHY29tbWFuZCKHAwoLUGFya2VkUmVwbHkSWQoUcGFya2Vk", + "X21lc3NhZ2VfcXVlcnkYASABKAsyOS5zY2FkYWJyaWRnZS5zaXRlY29tbWFu", + "ZC52MS5QYXJrZWRNZXNzYWdlUXVlcnlSZXNwb25zZUR0b0gAElkKFHBhcmtl", + "ZF9tZXNzYWdlX3JldHJ5GAIgASgLMjkuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1h", + "bmQudjEuUGFya2VkTWVzc2FnZVJldHJ5UmVzcG9uc2VEdG9IABJdChZwYXJr", + "ZWRfbWVzc2FnZV9kaXNjYXJkGAMgASgLMjsuc2NhZGFicmlkZ2Uuc2l0ZWNv", + "bW1hbmQudjEuUGFya2VkTWVzc2FnZURpc2NhcmRSZXNwb25zZUR0b0gAEloK", + "F3BhcmtlZF9vcGVyYXRpb25fYWN0aW9uGAQgASgLMjcuc2NhZGFicmlkZ2Uu", + "c2l0ZWNvbW1hbmQudjEuUGFya2VkT3BlcmF0aW9uQWN0aW9uQWNrRHRvSABC", + "BwoFcmVwbHki7QEKFVJvdXRlVG9DYWxsUmVxdWVzdER0bxIWCg5jb3JyZWxh", + "dGlvbl9pZBgBIAEoCRIcChRpbnN0YW5jZV91bmlxdWVfbmFtZRgCIAEoCRIT", + "CgtzY3JpcHRfbmFtZRgDIAEoCRI9CgpwYXJhbWV0ZXJzGAQgASgLMikuc2Nh", + "ZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuTG9vc2VWYWx1ZU1hcBItCgl0aW1l", + "c3RhbXAYBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhsKE3Bh", + "cmVudF9leGVjdXRpb25faWQYBiABKAkixQEKFlJvdXRlVG9DYWxsUmVzcG9u", + "c2VEdG8SFgoOY29ycmVsYXRpb25faWQYASABKAkSDwoHc3VjY2VzcxgCIAEo", + "CBI8CgxyZXR1cm5fdmFsdWUYAyABKAsyJi5zY2FkYWJyaWRnZS5zaXRlY29t", + "bWFuZC52MS5Mb29zZVZhbHVlEhUKDWVycm9yX21lc3NhZ2UYBCABKAkSLQoJ", + "dGltZXN0YW1wGAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCK7", + "AQoeUm91dGVUb0dldEF0dHJpYnV0ZXNSZXF1ZXN0RHRvEhYKDmNvcnJlbGF0", + "aW9uX2lkGAEgASgJEhwKFGluc3RhbmNlX3VuaXF1ZV9uYW1lGAIgASgJEhcK", + "D2F0dHJpYnV0ZV9uYW1lcxgDIAMoCRItCgl0aW1lc3RhbXAYBCABKAsyGi5n", + "b29nbGUucHJvdG9idWYuVGltZXN0YW1wEhsKE3BhcmVudF9leGVjdXRpb25f", + "aWQYBSABKAkiywEKH1JvdXRlVG9HZXRBdHRyaWJ1dGVzUmVzcG9uc2VEdG8S", + "FgoOY29ycmVsYXRpb25faWQYASABKAkSOQoGdmFsdWVzGAIgASgLMikuc2Nh", + "ZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuTG9vc2VWYWx1ZU1hcBIPCgdzdWNj", + "ZXNzGAMgASgIEhUKDWVycm9yX21lc3NhZ2UYBCABKAkSLQoJdGltZXN0YW1w", + "GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCLFAgoeUm91dGVU", + "b1NldEF0dHJpYnV0ZXNSZXF1ZXN0RHRvEhYKDmNvcnJlbGF0aW9uX2lkGAEg", + "ASgJEhwKFGluc3RhbmNlX3VuaXF1ZV9uYW1lGAIgASgJEmkKEGF0dHJpYnV0", + "ZV92YWx1ZXMYAyADKAsyTy5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5S", + "b3V0ZVRvU2V0QXR0cmlidXRlc1JlcXVlc3REdG8uQXR0cmlidXRlVmFsdWVz", + "RW50cnkSLQoJdGltZXN0YW1wGAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRp", + "bWVzdGFtcBIbChNwYXJlbnRfZXhlY3V0aW9uX2lkGAUgASgJGjYKFEF0dHJp", + "YnV0ZVZhbHVlc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToC", + "OAEikAEKH1JvdXRlVG9TZXRBdHRyaWJ1dGVzUmVzcG9uc2VEdG8SFgoOY29y", + "cmVsYXRpb25faWQYASABKAkSDwoHc3VjY2VzcxgCIAEoCBIVCg1lcnJvcl9t", + "ZXNzYWdlGAMgASgJEi0KCXRpbWVzdGFtcBgEIAEoCzIaLmdvb2dsZS5wcm90", + "b2J1Zi5UaW1lc3RhbXAiwwIKIVJvdXRlVG9XYWl0Rm9yQXR0cmlidXRlUmVx", + "dWVzdER0bxIWCg5jb3JyZWxhdGlvbl9pZBgBIAEoCRIcChRpbnN0YW5jZV91", + "bmlxdWVfbmFtZRgCIAEoCRIWCg5hdHRyaWJ1dGVfbmFtZRgDIAEoCRI6ChR0", + "YXJnZXRfdmFsdWVfZW5jb2RlZBgEIAEoCzIcLmdvb2dsZS5wcm90b2J1Zi5T", + "dHJpbmdWYWx1ZRIqCgd0aW1lb3V0GAUgASgLMhkuZ29vZ2xlLnByb3RvYnVm", + "LkR1cmF0aW9uEi0KCXRpbWVzdGFtcBgGIAEoCzIaLmdvb2dsZS5wcm90b2J1", + "Zi5UaW1lc3RhbXASGwoTcGFyZW50X2V4ZWN1dGlvbl9pZBgHIAEoCRIcChRy", + "ZXF1aXJlX2dvb2RfcXVhbGl0eRgIIAEoCCL/AQoiUm91dGVUb1dhaXRGb3JB", + "dHRyaWJ1dGVSZXNwb25zZUR0bxIWCg5jb3JyZWxhdGlvbl9pZBgBIAEoCRIP", + "CgdtYXRjaGVkGAIgASgIEjUKBXZhbHVlGAMgASgLMiYuc2NhZGFicmlkZ2Uu", + "c2l0ZWNvbW1hbmQudjEuTG9vc2VWYWx1ZRIPCgdxdWFsaXR5GAQgASgJEhEK", + "CXRpbWVkX291dBgFIAEoCBIPCgdzdWNjZXNzGAYgASgIEhUKDWVycm9yX21l", + "c3NhZ2UYByABKAkSLQoJdGltZXN0YW1wGAggASgLMhouZ29vZ2xlLnByb3Rv", + "YnVmLlRpbWVzdGFtcCKJAwoMUm91dGVSZXF1ZXN0EkoKDXJvdXRlX3RvX2Nh", + "bGwYASABKAsyMS5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5Sb3V0ZVRv", + "Q2FsbFJlcXVlc3REdG9IABJdChdyb3V0ZV90b19nZXRfYXR0cmlidXRlcxgC", + "IAEoCzI6LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlJvdXRlVG9HZXRB", + "dHRyaWJ1dGVzUmVxdWVzdER0b0gAEl0KF3JvdXRlX3RvX3NldF9hdHRyaWJ1", + "dGVzGAMgASgLMjouc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUm91dGVU", + "b1NldEF0dHJpYnV0ZXNSZXF1ZXN0RHRvSAASZAobcm91dGVfdG9fd2FpdF9m", + "b3JfYXR0cmlidXRlGAQgASgLMj0uc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQu", + "djEuUm91dGVUb1dhaXRGb3JBdHRyaWJ1dGVSZXF1ZXN0RHRvSABCCQoHY29t", + "bWFuZCKJAwoKUm91dGVSZXBseRJLCg1yb3V0ZV90b19jYWxsGAEgASgLMjIu", + "c2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUm91dGVUb0NhbGxSZXNwb25z", + "ZUR0b0gAEl4KF3JvdXRlX3RvX2dldF9hdHRyaWJ1dGVzGAIgASgLMjsuc2Nh", + "ZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUm91dGVUb0dldEF0dHJpYnV0ZXNS", + "ZXNwb25zZUR0b0gAEl4KF3JvdXRlX3RvX3NldF9hdHRyaWJ1dGVzGAMgASgL", + "Mjsuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUm91dGVUb1NldEF0dHJp", + "YnV0ZXNSZXNwb25zZUR0b0gAEmUKG3JvdXRlX3RvX3dhaXRfZm9yX2F0dHJp", + "YnV0ZRgEIAEoCzI+LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlJvdXRl", + "VG9XYWl0Rm9yQXR0cmlidXRlUmVzcG9uc2VEdG9IAEIHCgVyZXBseSJBChZU", + "cmlnZ2VyU2l0ZUZhaWxvdmVyRHRvEhYKDmNvcnJlbGF0aW9uX2lkGAEgASgJ", + "Eg8KB3NpdGVfaWQYAiABKAkibQoSU2l0ZUZhaWxvdmVyQWNrRHRvEhYKDmNv", + "cnJlbGF0aW9uX2lkGAEgASgJEhAKCGFjY2VwdGVkGAIgASgIEhYKDnRhcmdl", + "dF9hZGRyZXNzGAMgASgJEhUKDWVycm9yX21lc3NhZ2UYBCABKAkqywEKE0Rl", + "cGxveW1lbnRTdGF0dXNEdG8SJQohREVQTE9ZTUVOVF9TVEFUVVNfRFRPX1VO", + "U1BFQ0lGSUVEEAASIQodREVQTE9ZTUVOVF9TVEFUVVNfRFRPX1BFTkRJTkcQ", + "ARIlCiFERVBMT1lNRU5UX1NUQVRVU19EVE9fSU5fUFJPR1JFU1MQAhIhCh1E", + "RVBMT1lNRU5UX1NUQVRVU19EVE9fU1VDQ0VTUxADEiAKHERFUExPWU1FTlRf", + "U1RBVFVTX0RUT19GQUlMRUQQBCrEAQoSQnJvd3NlTm9kZUNsYXNzRHRvEiUK", + "IUJST1dTRV9OT0RFX0NMQVNTX0RUT19VTlNQRUNJRklFRBAAEiAKHEJST1dT", + "RV9OT0RFX0NMQVNTX0RUT19PQkpFQ1QQARIiCh5CUk9XU0VfTk9ERV9DTEFT", + "U19EVE9fVkFSSUFCTEUQAhIgChxCUk9XU0VfTk9ERV9DTEFTU19EVE9fTUVU", + "SE9EEAMSHwobQlJPV1NFX05PREVfQ0xBU1NfRFRPX09USEVSEAQqoQIKFEJy", + "b3dzZUZhaWx1cmVLaW5kRHRvEicKI0JST1dTRV9GQUlMVVJFX0tJTkRfRFRP", + "X1VOU1BFQ0lGSUVEEAASMAosQlJPV1NFX0ZBSUxVUkVfS0lORF9EVE9fQ09O", + "TkVDVElPTl9OT1RfRk9VTkQQARI0CjBCUk9XU0VfRkFJTFVSRV9LSU5EX0RU", + "T19DT05ORUNUSU9OX05PVF9DT05ORUNURUQQAhIpCiVCUk9XU0VfRkFJTFVS", + "RV9LSU5EX0RUT19OT1RfQlJPV1NBQkxFEAMSIwofQlJPV1NFX0ZBSUxVUkVf", + "S0lORF9EVE9fVElNRU9VVBAEEigKJEJST1dTRV9GQUlMVVJFX0tJTkRfRFRP", + "X1NFUlZFUl9FUlJPUhAFKqoCChtSZWFkVGFnVmFsdWVzRmFpbHVyZUtpbmRE", + "dG8SMAosUkVBRF9UQUdfVkFMVUVTX0ZBSUxVUkVfS0lORF9EVE9fVU5TUEVD", + "SUZJRUQQABI5CjVSRUFEX1RBR19WQUxVRVNfRkFJTFVSRV9LSU5EX0RUT19D", + "T05ORUNUSU9OX05PVF9GT1VORBABEj0KOVJFQURfVEFHX1ZBTFVFU19GQUlM", + "VVJFX0tJTkRfRFRPX0NPTk5FQ1RJT05fTk9UX0NPTk5FQ1RFRBACEiwKKFJF", + "QURfVEFHX1ZBTFVFU19GQUlMVVJFX0tJTkRfRFRPX1RJTUVPVVQQAxIxCi1S", + "RUFEX1RBR19WQUxVRVNfRkFJTFVSRV9LSU5EX0RUT19TRVJWRVJfRVJST1IQ", + "BCqTAgoUVmVyaWZ5RmFpbHVyZUtpbmREdG8SJwojVkVSSUZZX0ZBSUxVUkVf", + "S0lORF9EVE9fVU5TUEVDSUZJRUQQABInCiNWRVJJRllfRkFJTFVSRV9LSU5E", + "X0RUT19VTlJFQUNIQUJMRRABEicKI1ZFUklGWV9GQUlMVVJFX0tJTkRfRFRP", + "X0FVVEhfRkFJTEVEEAISMQotVkVSSUZZX0ZBSUxVUkVfS0lORF9EVE9fVU5U", + "UlVTVEVEX0NFUlRJRklDQVRFEAMSIwofVkVSSUZZX0ZBSUxVUkVfS0lORF9E", + "VE9fVElNRU9VVBAEEigKJFZFUklGWV9GQUlMVVJFX0tJTkRfRFRPX1NFUlZF", + "Ul9FUlJPUhAFKuUBChpTdG9yZUFuZEZvcndhcmRDYXRlZ29yeUR0bxIuCipT", + "VE9SRV9BTkRfRk9SV0FSRF9DQVRFR09SWV9EVE9fVU5TUEVDSUZJRUQQABIy", + "Ci5TVE9SRV9BTkRfRk9SV0FSRF9DQVRFR09SWV9EVE9fRVhURVJOQUxfU1lT", + "VEVNEAESLworU1RPUkVfQU5EX0ZPUldBUkRfQ0FURUdPUllfRFRPX05PVElG", + "SUNBVElPThACEjIKLlNUT1JFX0FORF9GT1JXQVJEX0NBVEVHT1JZX0RUT19D", + "QUNIRURfREJfV1JJVEUQAypoCg1BbGFybVN0YXRlRHRvEh8KG0FMQVJNX1NU", + "QVRFX0RUT19VTlNQRUNJRklFRBAAEhoKFkFMQVJNX1NUQVRFX0RUT19BQ1RJ", + "VkUQARIaChZBTEFSTV9TVEFURV9EVE9fTk9STUFMEAIquQEKDUFsYXJtTGV2", + "ZWxEdG8SHwobQUxBUk1fTEVWRUxfRFRPX1VOU1BFQ0lGSUVEEAASGAoUQUxB", + "Uk1fTEVWRUxfRFRPX05PTkUQARIXChNBTEFSTV9MRVZFTF9EVE9fTE9XEAIS", + "GwoXQUxBUk1fTEVWRUxfRFRPX0xPV19MT1cQAxIYChRBTEFSTV9MRVZFTF9E", + "VE9fSElHSBAEEh0KGUFMQVJNX0xFVkVMX0RUT19ISUdIX0hJR0gQBSqSAQoM", + "QWxhcm1LaW5kRHRvEh4KGkFMQVJNX0tJTkRfRFRPX1VOU1BFQ0lGSUVEEAAS", + "GwoXQUxBUk1fS0lORF9EVE9fQ09NUFVURUQQARIgChxBTEFSTV9LSU5EX0RU", + "T19OQVRJVkVfT1BDX1VBEAISIwofQUxBUk1fS0lORF9EVE9fTkFUSVZFX01Y", + "X0FDQ0VTUxADKugBChNBbGFybVNoZWx2ZVN0YXRlRHRvEiYKIkFMQVJNX1NI", + "RUxWRV9TVEFURV9EVE9fVU5TUEVDSUZJRUQQABIkCiBBTEFSTV9TSEVMVkVf", + "U1RBVEVfRFRPX1VOU0hFTFZFRBABEisKJ0FMQVJNX1NIRUxWRV9TVEFURV9E", + "VE9fT05FX1NIT1RfU0hFTFZFRBACEigKJEFMQVJNX1NIRUxWRV9TVEFURV9E", + "VE9fVElNRURfU0hFTFZFRBADEiwKKEFMQVJNX1NIRUxWRV9TVEFURV9EVE9f", + "UEVSTUFORU5UX1NIRUxWRUQQBDKEBQoSU2l0ZUNvbW1hbmRTZXJ2aWNlEmwK", + "EEV4ZWN1dGVMaWZlY3ljbGUSLC5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52", + "MS5MaWZlY3ljbGVSZXF1ZXN0Giouc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQu", + "djEuTGlmZWN5Y2xlUmVwbHkSYAoMRXhlY3V0ZU9wY1VhEiguc2NhZGFicmlk", + "Z2Uuc2l0ZWNvbW1hbmQudjEuT3BjVWFSZXF1ZXN0GiYuc2NhZGFicmlkZ2Uu", + "c2l0ZWNvbW1hbmQudjEuT3BjVWFSZXBseRJgCgxFeGVjdXRlUXVlcnkSKC5z", + "Y2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5RdWVyeVJlcXVlc3QaJi5zY2Fk", + "YWJyaWRnZS5zaXRlY29tbWFuZC52MS5RdWVyeVJlcGx5EmMKDUV4ZWN1dGVQ", + "YXJrZWQSKS5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5QYXJrZWRSZXF1", + "ZXN0Gicuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUGFya2VkUmVwbHkS", + "YAoMRXhlY3V0ZVJvdXRlEiguc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEu", + "Um91dGVSZXF1ZXN0GiYuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUm91", + "dGVSZXBseRJ1Cg9UcmlnZ2VyRmFpbG92ZXISMi5zY2FkYWJyaWRnZS5zaXRl", + "Y29tbWFuZC52MS5UcmlnZ2VyU2l0ZUZhaWxvdmVyRHRvGi4uc2NhZGFicmlk", + "Z2Uuc2l0ZWNvbW1hbmQudjEuU2l0ZUZhaWxvdmVyQWNrRHRvQiuqAihaQi5N", + "T00uV1cuU2NhZGFCcmlkZ2UuQ29tbXVuaWNhdGlvbi5HcnBjYgZwcm90bzM=")); descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, new pbr::FileDescriptor[] { global::Google.Protobuf.WellKnownTypes.TimestampReflection.Descriptor, global::Google.Protobuf.WellKnownTypes.DurationReflection.Descriptor, global::Google.Protobuf.WellKnownTypes.WrappersReflection.Descriptor, }, new pbr::GeneratedClrTypeInfo(new[] {typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.DeploymentStatusDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.BrowseNodeClassDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.BrowseFailureKindDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.ReadTagValuesFailureKindDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.VerifyFailureKindDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.StoreAndForwardCategoryDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmStateDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmLevelDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmKindDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmShelveStateDto), }, null, new pbr::GeneratedClrTypeInfo[] { @@ -509,7 +510,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc { new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.DeleteInstanceCommandDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.DeleteInstanceCommandDto.Parser, new[]{ "CommandId", "InstanceUniqueName", "Timestamp" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.DeploymentStateQueryRequestDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.DeploymentStateQueryRequestDto.Parser, new[]{ "CorrelationId", "InstanceUniqueName", "Timestamp" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SharedScriptArtifactDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SharedScriptArtifactDto.Parser, new[]{ "Name", "Code", "ParameterDefinitions", "ReturnDefinition" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.ExternalSystemArtifactDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.ExternalSystemArtifactDto.Parser, new[]{ "Name", "EndpointUrl", "AuthType", "AuthConfiguration", "MethodDefinitionsJson", "TimeoutSeconds" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.ExternalSystemArtifactDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.ExternalSystemArtifactDto.Parser, new[]{ "Name", "EndpointUrl", "AuthType", "AuthConfiguration", "MethodDefinitionsJson", "TimeoutSeconds", "MaxRetries", "RetryDelay" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.DatabaseConnectionArtifactDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.DatabaseConnectionArtifactDto.Parser, new[]{ "Name", "ConnectionString", "MaxRetries", "RetryDelay" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.NotificationListArtifactDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.NotificationListArtifactDto.Parser, new[]{ "Name", "RecipientEmails" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.DataConnectionArtifactDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.DataConnectionArtifactDto.Parser, new[]{ "Name", "Protocol", "PrimaryConfigurationJson", "BackupConfigurationJson", "FailoverRetryCount" }, null, null, null, null), @@ -4205,6 +4206,8 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc { authConfiguration_ = other.authConfiguration_; methodDefinitionsJson_ = other.methodDefinitionsJson_; timeoutSeconds_ = other.timeoutSeconds_; + maxRetries_ = other.maxRetries_; + retryDelay_ = other.retryDelay_ != null ? other.retryDelay_.Clone() : null; _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } @@ -4292,6 +4295,35 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc { } } + /// Field number for the "max_retries" field. + public const int MaxRetriesFieldNumber = 7; + private int maxRetries_; + /// + /// Store-and-Forward retry policy for this system's cached calls. Additive (fields 7-8); + /// a pre-existing site decodes them as 0/absent, which is exactly the "unset" value that + /// makes the engine defaults apply — i.e. the old behaviour. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int MaxRetries { + get { return maxRetries_; } + set { + maxRetries_ = value; + } + } + + /// Field number for the "retry_delay" field. + public const int RetryDelayFieldNumber = 8; + private global::Google.Protobuf.WellKnownTypes.Duration retryDelay_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public global::Google.Protobuf.WellKnownTypes.Duration RetryDelay { + get { return retryDelay_; } + set { + retryDelay_ = value; + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] public override bool Equals(object other) { @@ -4313,6 +4345,8 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc { if (AuthConfiguration != other.AuthConfiguration) return false; if (MethodDefinitionsJson != other.MethodDefinitionsJson) return false; if (TimeoutSeconds != other.TimeoutSeconds) return false; + if (MaxRetries != other.MaxRetries) return false; + if (!object.Equals(RetryDelay, other.RetryDelay)) return false; return Equals(_unknownFields, other._unknownFields); } @@ -4326,6 +4360,8 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc { if (AuthConfiguration.Length != 0) hash ^= AuthConfiguration.GetHashCode(); if (MethodDefinitionsJson.Length != 0) hash ^= MethodDefinitionsJson.GetHashCode(); if (TimeoutSeconds != 0) hash ^= TimeoutSeconds.GetHashCode(); + if (MaxRetries != 0) hash ^= MaxRetries.GetHashCode(); + if (retryDelay_ != null) hash ^= RetryDelay.GetHashCode(); if (_unknownFields != null) { hash ^= _unknownFields.GetHashCode(); } @@ -4368,6 +4404,14 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc { output.WriteRawTag(48); output.WriteInt32(TimeoutSeconds); } + if (MaxRetries != 0) { + output.WriteRawTag(56); + output.WriteInt32(MaxRetries); + } + if (retryDelay_ != null) { + output.WriteRawTag(66); + output.WriteMessage(RetryDelay); + } if (_unknownFields != null) { _unknownFields.WriteTo(output); } @@ -4402,6 +4446,14 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc { output.WriteRawTag(48); output.WriteInt32(TimeoutSeconds); } + if (MaxRetries != 0) { + output.WriteRawTag(56); + output.WriteInt32(MaxRetries); + } + if (retryDelay_ != null) { + output.WriteRawTag(66); + output.WriteMessage(RetryDelay); + } if (_unknownFields != null) { _unknownFields.WriteTo(ref output); } @@ -4430,6 +4482,12 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc { if (TimeoutSeconds != 0) { size += 1 + pb::CodedOutputStream.ComputeInt32Size(TimeoutSeconds); } + if (MaxRetries != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(MaxRetries); + } + if (retryDelay_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(RetryDelay); + } if (_unknownFields != null) { size += _unknownFields.CalculateSize(); } @@ -4460,6 +4518,15 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc { if (other.TimeoutSeconds != 0) { TimeoutSeconds = other.TimeoutSeconds; } + if (other.MaxRetries != 0) { + MaxRetries = other.MaxRetries; + } + if (other.retryDelay_ != null) { + if (retryDelay_ == null) { + RetryDelay = new global::Google.Protobuf.WellKnownTypes.Duration(); + } + RetryDelay.MergeFrom(other.RetryDelay); + } _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } @@ -4503,6 +4570,17 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc { TimeoutSeconds = input.ReadInt32(); break; } + case 56: { + MaxRetries = input.ReadInt32(); + break; + } + case 66: { + if (retryDelay_ == null) { + RetryDelay = new global::Google.Protobuf.WellKnownTypes.Duration(); + } + input.ReadMessage(RetryDelay); + break; + } } } #endif @@ -4546,6 +4624,17 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc { TimeoutSeconds = input.ReadInt32(); break; } + case 56: { + MaxRetries = input.ReadInt32(); + break; + } + case 66: { + if (retryDelay_ == null) { + RetryDelay = new global::Google.Protobuf.WellKnownTypes.Duration(); + } + input.ReadMessage(RetryDelay); + break; + } } } } diff --git a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs index 33cfd3f1..2034c50e 100644 --- a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs +++ b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs @@ -192,8 +192,13 @@ public class ArtifactDeploymentService m.ReturnDefinition })) : null; + // MaxRetries/RetryDelay ride along so the site's Store-and-Forward buffering honours + // the centrally-configured per-system retry policy. Before they were carried, a site + // silently fell back to the engine defaults (50 x 30 s) for every cached call, whatever + // central had configured — the DatabaseConnectionArtifact below always carried them. externalSystemArtifacts.Add(new ExternalSystemArtifact( - es.Name, es.EndpointUrl, es.AuthType, es.AuthConfiguration, methodsJson, es.TimeoutSeconds)); + es.Name, es.EndpointUrl, es.AuthType, es.AuthConfiguration, methodsJson, es.TimeoutSeconds, + es.MaxRetries, es.RetryDelay)); } // Map database connections diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/SiteLocalDbLegacyMigrator.cs b/src/ZB.MOM.WW.ScadaBridge.Host/SiteLocalDbLegacyMigrator.cs index ce040715..e1fd1506 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Host/SiteLocalDbLegacyMigrator.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Host/SiteLocalDbLegacyMigrator.cs @@ -113,7 +113,7 @@ public static class SiteLocalDbLegacyMigrator new("external_systems", "name", [ "name", "endpoint_url", "auth_type", "auth_configuration", "method_definitions", - "updated_at", "timeout_seconds", + "updated_at", "timeout_seconds", "max_retries", "retry_delay_ms", ]), new("database_connections", "name", [ diff --git a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs index be4e1c36..572567c8 100644 --- a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs +++ b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs @@ -2021,7 +2021,11 @@ public class ManagementActor : ReceiveActor var def = new ExternalSystemDefinition(cmd.Name, cmd.EndpointUrl, cmd.AuthType) { AuthConfiguration = cmd.AuthConfiguration, - TimeoutSeconds = cmd.TimeoutSeconds ?? 0 + TimeoutSeconds = cmd.TimeoutSeconds ?? 0, + // 0 / TimeSpan.Zero is the entity's "unset" value — the site gateway maps it to the + // Store-and-Forward engine defaults rather than to a literal zero budget. + MaxRetries = cmd.MaxRetries ?? 0, + RetryDelay = cmd.RetryDelay ?? TimeSpan.Zero }; await repo.AddExternalSystemAsync(def); await repo.SaveChangesAsync(); @@ -2042,6 +2046,9 @@ public class ManagementActor : ReceiveActor // secret intact (mirrors the SMTP/SMS credential preserve-if-null rule). if (cmd.AuthConfiguration is not null) def.AuthConfiguration = cmd.AuthConfiguration; if (cmd.TimeoutSeconds is { } ts) def.TimeoutSeconds = ts; + // Same preserve-if-null rule for the Store-and-Forward retry policy. + if (cmd.MaxRetries is { } mr) def.MaxRetries = mr; + if (cmd.RetryDelay is { } rd) def.RetryDelay = rd; await repo.UpdateExternalSystemAsync(def); await repo.SaveChangesAsync(); var publicShape = ExternalSystemPublicShape(def); diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/DeploymentManagerActor.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/DeploymentManagerActor.cs index 8495149f..638b06ab 100644 --- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/DeploymentManagerActor.cs +++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/DeploymentManagerActor.cs @@ -2155,7 +2155,8 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers foreach (var es in command.ExternalSystems) { await _storage.StoreExternalSystemAsync(es.Name, es.EndpointUrl, - es.AuthType, es.AuthConfiguration, es.MethodDefinitionsJson, es.TimeoutSeconds); + es.AuthType, es.AuthConfiguration, es.MethodDefinitionsJson, es.TimeoutSeconds, + es.MaxRetries, es.RetryDelay); } await _storage.DeleteExternalSystemsExceptAsync( diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageSchema.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageSchema.cs index b54711b8..c7698250 100644 --- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageSchema.cs +++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageSchema.cs @@ -133,6 +133,28 @@ public static class SiteStorageSchema // Per-external-system call timeout (ExternalSystemGateway Timeout) — carried through the // artifact pipeline so site-side calls honor it; 0 = use the site default. AddColumnIfMissing(connection, "external_systems", "timeout_seconds", "INTEGER NOT NULL DEFAULT 0"); + + // Store-and-Forward retry policy, deployed per external system (deferred-work row 27). + // + // 0 = unset on BOTH columns, which is what makes this additive change behaviourally + // invisible until an operator actually configures a policy: an existing row backfilled + // with the DEFAULT reads back as 0, and ExternalSystemClient.CachedCallAsync maps a 0 + // to null so the engine's DefaultMaxRetries/DefaultRetryInterval still apply. (Contrast + // database_connections, whose equivalent columns default to 3/1000 — that table has + // never had an "unset means engine default" contract.) + // + // These land on a table in SiteLocalDbSetup.ReplicatedTables. That is safe and needs no + // trigger migration: RegisterReplicated re-reads PRAGMA table_info on EVERY boot and its + // install script is DROP-then-CREATE, so the capture triggers are rebuilt over the new + // column set automatically — and Apply() runs before RegisterReplicated, so the rebuild + // sees these columns on the same boot that adds them. What DOES change is the table's + // schema digest (ReplicatedTable.ComputeDigest hashes every column name + type), which + // the sync handshake compares fail-closed; a pair running mismatched builds refuses to + // sync loudly (LocalDbSchemaMismatchException) rather than corrupting anything. Both + // nodes must therefore be upgraded together — already the standing rule for a site pair + // (docs/deployment/topology-guide.md), so this introduces no new operational constraint. + AddColumnIfMissing(connection, "external_systems", "max_retries", "INTEGER NOT NULL DEFAULT 0"); + AddColumnIfMissing(connection, "external_systems", "retry_delay_ms", "INTEGER NOT NULL DEFAULT 0"); } /// diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageService.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageService.cs index 63f2c697..9e7325ca 100644 --- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageService.cs +++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageService.cs @@ -647,23 +647,33 @@ public class SiteStorageService /// Authentication configuration JSON, if applicable. /// JSON representation of available method definitions, if any. /// Per-system call timeout in seconds (0 = use the site default). + /// + /// Store-and-Forward retry budget for this system's cached calls (0 = unset — the engine's + /// DefaultMaxRetries applies). + /// + /// + /// Fixed interval between Store-and-Forward retry sweeps for this system's cached calls + /// ( = unset — the engine's DefaultRetryInterval applies). + /// /// A task that completes when the external system definition has been stored or updated. public async Task StoreExternalSystemAsync( string name, string endpointUrl, string authType, string? authConfig, string? methodDefs, - int timeoutSeconds = 0) + int timeoutSeconds = 0, int maxRetries = 0, TimeSpan retryDelay = default) { await using var connection = OpenConnection(); await using var command = connection.CreateCommand(); command.CommandText = @" - INSERT INTO external_systems (name, endpoint_url, auth_type, auth_configuration, method_definitions, timeout_seconds, updated_at) - VALUES (@name, @url, @authType, @authConfig, @methodDefs, @timeoutSeconds, @updatedAt) + INSERT INTO external_systems (name, endpoint_url, auth_type, auth_configuration, method_definitions, timeout_seconds, max_retries, retry_delay_ms, updated_at) + VALUES (@name, @url, @authType, @authConfig, @methodDefs, @timeoutSeconds, @maxRetries, @retryDelayMs, @updatedAt) ON CONFLICT(name) DO UPDATE SET endpoint_url = excluded.endpoint_url, auth_type = excluded.auth_type, auth_configuration = excluded.auth_configuration, method_definitions = excluded.method_definitions, timeout_seconds = excluded.timeout_seconds, + max_retries = excluded.max_retries, + retry_delay_ms = excluded.retry_delay_ms, updated_at = excluded.updated_at"; command.Parameters.AddWithValue("@name", name); @@ -672,6 +682,8 @@ public class SiteStorageService command.Parameters.AddWithValue("@authConfig", (object?)authConfig ?? DBNull.Value); command.Parameters.AddWithValue("@methodDefs", (object?)methodDefs ?? DBNull.Value); command.Parameters.AddWithValue("@timeoutSeconds", timeoutSeconds); + command.Parameters.AddWithValue("@maxRetries", maxRetries); + command.Parameters.AddWithValue("@retryDelayMs", (long)retryDelay.TotalMilliseconds); command.Parameters.AddWithValue("@updatedAt", DateTimeOffset.UtcNow.ToString("O")); await command.ExecuteNonQueryAsync(); diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/SiteExternalSystemRepository.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/SiteExternalSystemRepository.cs index e03801fb..fe4b738d 100644 --- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/SiteExternalSystemRepository.cs +++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/SiteExternalSystemRepository.cs @@ -250,7 +250,13 @@ public class SiteExternalSystemRepository : IExternalSystemRepository { Id = GenerateSyntheticId(name), AuthConfiguration = reader.IsDBNull(3) ? null : reader.GetString(3), - TimeoutSeconds = reader.IsDBNull(4) ? 0 : reader.GetInt32(4) + TimeoutSeconds = reader.IsDBNull(4) ? 0 : reader.GetInt32(4), + // Deployed Store-and-Forward retry policy (deferred-work row 27). Ordinals 6/7 are + // deliberately AFTER method_definitions (5) so the pre-existing indexes are untouched. + // 0 stays 0: it is the "unset" value ExternalSystemClient.CachedCallAsync converts to + // null so the engine defaults apply. + MaxRetries = reader.IsDBNull(6) ? 0 : reader.GetInt32(6), + RetryDelay = reader.IsDBNull(7) ? TimeSpan.Zero : TimeSpan.FromMilliseconds(reader.GetInt64(7)) }; } @@ -271,7 +277,8 @@ public class SiteExternalSystemRepository : IExternalSystemRepository await using var command = connection.CreateCommand(); command.CommandText = @" - SELECT name, endpoint_url, auth_type, auth_configuration, timeout_seconds, method_definitions + SELECT name, endpoint_url, auth_type, auth_configuration, timeout_seconds, method_definitions, + max_retries, retry_delay_ms FROM external_systems"; var byName = new Dictionary(StringComparer.Ordinal); diff --git a/src/ZB.MOM.WW.ScadaBridge.Transport/Import/ArtifactDiff.cs b/src/ZB.MOM.WW.ScadaBridge.Transport/Import/ArtifactDiff.cs index cee98bf3..fd5acb02 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Transport/Import/ArtifactDiff.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Transport/Import/ArtifactDiff.cs @@ -165,6 +165,11 @@ public sealed class ArtifactDiff var changes = new List(); AddIfDifferent(changes, "BaseUrl", existing.EndpointUrl, incoming.BaseUrl); AddIfDifferent(changes, "AuthType", existing.AuthType, incoming.AuthType); + // The bundle has always CARRIED the retry policy and the importer has always APPLIED it, + // but the diff omitted it — so an import that silently changed a system's retry budget + // previewed as "Identical". Mirrors CompareDatabaseConnection below. + AddIfDifferent(changes, "MaxRetries", existing.MaxRetries, incoming.MaxRetries); + AddIfDifferent(changes, "RetryDelay", existing.RetryDelay.ToString(), incoming.RetryDelay.ToString()); // Secrets: presence-only comparison (we never echo the value in the diff). var existingHasSecret = !string.IsNullOrEmpty(existing.AuthConfiguration); diff --git a/tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/Commands/ExternalSystemRetryOptionTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/Commands/ExternalSystemRetryOptionTests.cs new file mode 100644 index 00000000..a465e55e --- /dev/null +++ b/tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/Commands/ExternalSystemRetryOptionTests.cs @@ -0,0 +1,102 @@ +using System.CommandLine; +using ZB.MOM.WW.ScadaBridge.CLI.Commands; + +namespace ZB.MOM.WW.ScadaBridge.CLI.Tests.Commands; + +/// +/// Deferred-work row 27(b): external-system create and external-system update +/// must expose the per-system Store-and-Forward retry policy. Before this the fields existed +/// on the central entity but had NO management surface at all — the only way to set them was +/// a direct DB edit of ExternalSystemDefinitions. +/// +/// The delay flag is named --retry-delay-seconds to match the sibling +/// --timeout-seconds on these same commands (the repo-wide CLI convention is an explicit +/// unit suffix), with --retry-delay kept as an alias. +/// +/// +public class ExternalSystemRetryOptionTests +{ + private static readonly Option Url = new("--url") { Recursive = true }; + private static readonly Option Format = new("--format") { Recursive = true }; + private static readonly Option Username = new("--username") { Recursive = true }; + private static readonly Option Password = new("--password") { Recursive = true }; + + private static Command Group() => ExternalSystemCommands.Build(Url, Format, Username, Password); + + private static Option? FindOption(Command command, string name) + => command.Options.SingleOrDefault(o => o.Name == name); + + [Theory] + [InlineData("create")] + [InlineData("update")] + public void HasOptionalRetryPolicyOptions(string subcommand) + { + var cmd = Group().Subcommands.Single(c => c.Name == subcommand); + + var maxRetries = FindOption(cmd, "--max-retries"); + Assert.NotNull(maxRetries); + Assert.False(maxRetries!.Required); + + var retryDelay = FindOption(cmd, "--retry-delay-seconds"); + Assert.NotNull(retryDelay); + Assert.False(retryDelay!.Required); + } + + [Fact] + public void Create_ParsesRetryPolicyValues() + { + var parse = Group().Parse(new[] + { + "create", "--name", "Sys", "--endpoint-url", "http://x", "--auth-type", "none", + "--max-retries", "8", "--retry-delay-seconds", "60", + }); + + Assert.Empty(parse.Errors); + Assert.Equal(8, parse.GetValue("--max-retries")); + Assert.Equal(60, parse.GetValue("--retry-delay-seconds")); + } + + [Fact] + public void Update_ParsesRetryPolicyValues() + { + var parse = Group().Parse(new[] + { + "update", "--id", "7", "--name", "Sys", "--endpoint-url", "http://x", + "--auth-type", "none", "--max-retries", "3", "--retry-delay-seconds", "10", + }); + + Assert.Empty(parse.Errors); + Assert.Equal(3, parse.GetValue("--max-retries")); + Assert.Equal(10, parse.GetValue("--retry-delay-seconds")); + } + + [Fact] + public void RetryDelayAliasIsAccepted() + { + var parse = Group().Parse(new[] + { + "create", "--name", "Sys", "--endpoint-url", "http://x", "--auth-type", "none", + "--retry-delay", "45", + }); + + Assert.Empty(parse.Errors); + Assert.Equal(45, parse.GetValue("--retry-delay-seconds")); + } + + /// + /// Omission must stay distinguishable from an explicit 0 — that is what makes update's + /// "omit to preserve" rule and create's "fall back to the engine default" rule work. + /// + [Fact] + public void OmittedRetryPolicy_ParsesAsNull() + { + var parse = Group().Parse(new[] + { + "create", "--name", "Sys", "--endpoint-url", "http://x", "--auth-type", "none", + }); + + Assert.Empty(parse.Errors); + Assert.Null(parse.GetValue("--max-retries")); + Assert.Null(parse.GetValue("--retry-delay-seconds")); + } +} diff --git a/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/SiteCommandSamples.cs b/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/SiteCommandSamples.cs index bb9764a3..ab67bfbf 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/SiteCommandSamples.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/SiteCommandSamples.cs @@ -217,8 +217,12 @@ internal static class SiteCommandSamples private static SharedScriptArtifact SharedScript() => new("Calc", "return 1;", "{\"p\":\"int\"}", "{\"r\":\"int\"}"); + // MaxRetries/RetryDelay are non-default on purpose: they are the deferred-work-row-27 + // additions (proto fields 7-8), and the generic round-trip assertion is what pins them + // to the wire. A sample left at the defaults would round-trip vacuously. private static ExternalSystemArtifact ExternalSystem() => - new("Mes", "https://mes/api", "ApiKey", "{\"key\":\"x\"}", "[{\"name\":\"Post\"}]", 45); + new("Mes", "https://mes/api", "ApiKey", "{\"key\":\"x\"}", "[{\"name\":\"Post\"}]", 45, + MaxRetries: 12, RetryDelay: TimeSpan.FromSeconds(20)); private static DataConnectionArtifact DataConnection() => new("Plc1", "OpcUa", "{\"url\":\"opc.tcp://a\"}", "{\"url\":\"opc.tcp://b\"}", 5); diff --git a/tests/ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests/ArtifactDeploymentServiceTests.cs b/tests/ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests/ArtifactDeploymentServiceTests.cs index 4726a646..ae0b4e7c 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests/ArtifactDeploymentServiceTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests/ArtifactDeploymentServiceTests.cs @@ -11,6 +11,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services; using ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts; using ZB.MOM.WW.ScadaBridge.Communication; +using ZB.MOM.WW.ScadaBridge.Commons.Entities.ExternalSystems; namespace ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests; @@ -189,6 +190,38 @@ public class ArtifactDeploymentServiceTests : TestKit await _siteRepo.Received(1).GetDataConnectionsBySiteIdAsync(3, Arg.Any()); } + /// + /// Deferred-work row 27(a): the per-system retry policy must be projected onto the + /// artifact that ships to sites. Before the fix ExternalSystemArtifact had no such + /// fields, so a centrally-configured policy never left central and every site cached call + /// silently used the engine default (50 x 30 s) — while the sibling + /// DatabaseConnectionArtifact had always carried its equivalents. + /// + [Fact] + public async Task DeployedExternalSystemArtifact_CarriesRetryPolicy() + { + _externalSystemRepo.GetAllExternalSystemsAsync(Arg.Any()).Returns( + [ + new ExternalSystemDefinition("Mes", "https://mes", "ApiKey") + { + Id = 1, + MaxRetries = 6, + RetryDelay = TimeSpan.FromSeconds(120), + }, + ]); + + var recorder = new ArtifactProbeRecorder(); + var probe = Sys.ActorOf(Props.Create(() => new ArtifactProbeActor(recorder))); + var service = CreateServiceWithCommActor(probe); + + var result = await service.RetryForSiteAsync(1, "retry-site", "admin"); + + Assert.True(result.IsSuccess); + var artifact = Assert.Single(Assert.Single(recorder.Received).ExternalSystems!); + Assert.Equal(6, artifact.MaxRetries); + Assert.Equal(TimeSpan.FromSeconds(120), artifact.RetryDelay); + } + [Fact] public async Task RetryForSiteAsync_SingleSitePath_StillRunsTheGlobalQueriesOnce() { diff --git a/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs index 3fde780b..0dc3d93e 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs @@ -3968,6 +3968,61 @@ public class ManagementActorTests : TestKit, IDisposable Arg.Any()); } + /// + /// Deferred-work row 27(b): the retry policy had no management surface at all — the only + /// way to set it was a direct DB edit of ExternalSystemDefinitions. Create must now + /// map both fields onto the entity. + /// + [Fact] + public void CreateExternalSystem_MapsRetryPolicy() + { + var extRepo = Substitute.For(); + _services.AddScoped(_ => extRepo); + + var actor = CreateActor(); + var envelope = Envelope( + new CreateExternalSystemCommand("Sys", "http://x", "none", null, + TimeoutSeconds: 15, MaxRetries: 8, RetryDelay: TimeSpan.FromSeconds(60)), + "Designer"); + + actor.Tell(envelope); + + var response = ExpectMsg(TimeSpan.FromSeconds(5)); + Assert.Equal(envelope.CorrelationId, response.CorrelationId); + extRepo.Received(1).AddExternalSystemAsync( + Arg.Is( + d => d.MaxRetries == 8 && d.RetryDelay == TimeSpan.FromSeconds(60)), + Arg.Any()); + } + + /// + /// The retry policy follows the same "omit to preserve" rule as TimeoutSeconds, so a + /// partial update (e.g. renaming a system) cannot silently reset it to 0. + /// + [Fact] + public void UpdateExternalSystem_NullRetryPolicy_PreservesExisting() + { + var existing = new Commons.Entities.ExternalSystems.ExternalSystemDefinition("Sys", "http://x", "none") + { + MaxRetries = 11, + RetryDelay = TimeSpan.FromSeconds(33) + }; + var extRepo = Substitute.For(); + extRepo.GetExternalSystemByIdAsync(7, Arg.Any()).Returns(existing); + _services.AddScoped(_ => extRepo); + + var actor = CreateActor(); + var envelope = Envelope( + new UpdateExternalSystemCommand(7, "Renamed", "http://x", "none", null), + "Designer"); + + actor.Tell(envelope); + + ExpectMsg(TimeSpan.FromSeconds(5)); + Assert.Equal(11, existing.MaxRetries); + Assert.Equal(TimeSpan.FromSeconds(33), existing.RetryDelay); + } + // ======================================================================== // Area management role gate (arch-review C6): reconciled to any-of // [Designer, Deployer]. The Central UI creates areas inside the Deployment diff --git a/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Actors/DeploymentManagerActorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Actors/DeploymentManagerActorTests.cs index 9d1258ad..c8fae91c 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Actors/DeploymentManagerActorTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Actors/DeploymentManagerActorTests.cs @@ -997,6 +997,35 @@ public class DeploymentManagerActorTests : TestKit, IDisposable SmtpConfigurations: null, Timestamp: DateTimeOffset.UtcNow); + /// + /// Deferred-work row 27: the per-system Store-and-Forward retry policy must survive the + /// site APPLY path, not just the storage round-trip. Before the fix + /// ExternalSystemArtifact carried no retry fields at all, so a centrally-configured + /// policy was dropped on the floor here and every cached call fell back to the engine's + /// 50 x 30 s default. + /// + [Fact] + public async Task ApplyingArtifacts_PersistsExternalSystemRetryPolicy() + { + var manager = CreateDeploymentManager(); + + manager.Tell(ArtifactsCommand("dep-es-retry", externalSystems: + [ + new ExternalSystemArtifact("MES", "https://mes", "ApiKey", null, null, 0, + MaxRetries: 9, RetryDelay: TimeSpan.FromSeconds(45)) + ])); + Assert.True(ExpectMsg(TimeSpan.FromSeconds(10)).Success); + + await using var connection = _storage.CreateConnection(); + await using var command = connection.CreateCommand(); + command.CommandText = "SELECT max_retries, retry_delay_ms FROM external_systems WHERE name = 'MES'"; + await using var reader = await command.ExecuteReaderAsync(); + + Assert.True(await reader.ReadAsync()); + Assert.Equal(9, reader.GetInt32(0)); + Assert.Equal(45_000, reader.GetInt64(1)); + } + [Fact] public async Task ApplyingArtifacts_DeletesExternalSystemsAbsentFromTheSet() { diff --git a/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Repositories/SiteRepositoryTests.cs b/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Repositories/SiteRepositoryTests.cs index 201bf176..9c647ea9 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Repositories/SiteRepositoryTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Repositories/SiteRepositoryTests.cs @@ -86,6 +86,52 @@ public class SiteRepositoryTests : IDisposable Assert.Equal(7, found!.TimeoutSeconds); } + /// + /// Deferred-work row 27: the per-system Store-and-Forward retry policy + /// (MaxRetries/RetryDelay) must round-trip through the site SQLite store, + /// so ExternalSystemClient.CachedCallAsync can hand it to the engine instead of + /// silently falling back to DefaultMaxRetries (50) x DefaultRetryInterval (30 s). + /// Before the fix the site table had no such columns and this read back 0 / TimeSpan.Zero. + /// + [Fact] + public async Task SiteStorage_ExternalSystem_RetryPolicy_RoundTrips() + { + var storage = NewStorage(); + await storage.InitializeAsync(); + await storage.StoreExternalSystemAsync( + "MesApi", "https://mes.example.com", "ApiKey", null, null, + timeoutSeconds: 0, maxRetries: 7, retryDelay: TimeSpan.FromSeconds(90)); + + var repo = new SiteExternalSystemRepository(storage); + var found = await repo.GetExternalSystemByNameAsync("MesApi"); + + Assert.NotNull(found); + Assert.Equal(7, found!.MaxRetries); + Assert.Equal(TimeSpan.FromSeconds(90), found.RetryDelay); + } + + /// + /// The unset case stays unset: a system deployed without a retry policy must read back as + /// 0 / , which is the value the gateway translates to null so the + /// engine defaults apply. This is what makes the new columns behaviourally invisible until + /// an operator actually configures a policy. + /// + [Fact] + public async Task SiteStorage_ExternalSystem_RetryPolicy_DefaultsToUnset() + { + var storage = NewStorage(); + await storage.InitializeAsync(); + await storage.StoreExternalSystemAsync( + "PlainApi", "https://plain.example.com", "ApiKey", null, null); + + var repo = new SiteExternalSystemRepository(storage); + var found = await repo.GetExternalSystemByNameAsync("PlainApi"); + + Assert.NotNull(found); + Assert.Equal(0, found!.MaxRetries); + Assert.Equal(TimeSpan.Zero, found.RetryDelay); + } + /// /// SiteRuntime-007: the synthetic ID for an external system must be identical when /// the storage service and repository are re-created (simulating a process restart). diff --git a/tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/Import/ArtifactDiffTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/Import/ArtifactDiffTests.cs index 8a5306a8..3adf39ea 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/Import/ArtifactDiffTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/Import/ArtifactDiffTests.cs @@ -9,6 +9,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums; using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport; using ZB.MOM.WW.ScadaBridge.Transport.Import; using ZB.MOM.WW.ScadaBridge.Transport.Serialization; +using ZB.MOM.WW.ScadaBridge.Commons.Entities.ExternalSystems; namespace ZB.MOM.WW.ScadaBridge.Transport.Tests.Import; @@ -105,6 +106,56 @@ public sealed class ArtifactDiffTests Assert.Null(item.FieldDiffJson); } + // ============ Deferred-work row 27: ES retry policy in the diff ============ + + /// + /// The bundle has always CARRIED the external-system retry policy and + /// BundleImporter has always APPLIED it, but CompareExternalSystem never + /// compared it — so an import that silently changed a system's retry budget previewed as + /// "Identical". Now that the policy actually reaches sites, an unreviewed change to it is a + /// live behaviour change, so it must show up in the preview. + /// + [Fact] + public void ExternalSystem_ChangedRetryPolicy_IsReported() + { + var existing = new ExternalSystemDefinition("Mes", "https://mes", "ApiKey") + { + MaxRetries = 3, + RetryDelay = TimeSpan.FromSeconds(5), + }; + var incoming = new ExternalSystemDto("Mes", "https://mes", "ApiKey", + MaxRetries: 9, RetryDelay: TimeSpan.FromSeconds(45), Methods: [], Secrets: null); + + var item = _diff.CompareExternalSystem(incoming, existing, []); + + Assert.Equal(ConflictKind.Modified, item.Kind); + + var maxRetries = ChangeFor(item, "MaxRetries"); + Assert.Equal("3", maxRetries.GetProperty("oldValue").GetString()); + Assert.Equal("9", maxRetries.GetProperty("newValue").GetString()); + + var retryDelay = ChangeFor(item, "RetryDelay"); + Assert.Equal(TimeSpan.FromSeconds(5).ToString(), retryDelay.GetProperty("oldValue").GetString()); + Assert.Equal(TimeSpan.FromSeconds(45).ToString(), retryDelay.GetProperty("newValue").GetString()); + } + + /// An unchanged retry policy must not manufacture a spurious diff row. + [Fact] + public void ExternalSystem_UnchangedRetryPolicy_IsIdentical() + { + var existing = new ExternalSystemDefinition("Mes", "https://mes", "ApiKey") + { + MaxRetries = 3, + RetryDelay = TimeSpan.FromSeconds(5), + }; + var incoming = new ExternalSystemDto("Mes", "https://mes", "ApiKey", + MaxRetries: 3, RetryDelay: TimeSpan.FromSeconds(5), Methods: [], Secrets: null); + + var item = _diff.CompareExternalSystem(incoming, existing, []); + + Assert.Equal(ConflictKind.Identical, item.Kind); + } + [Fact] public void ApiMethod_ModifiedScript_EmitsStructuredLineDiff() {