namespace ZB.MOM.WW.ScadaBridge.SiteRuntime;
///
/// Configuration options for the Site Runtime component.
/// Bound from ScadaBridge:SiteRuntime configuration section.
///
public class SiteRuntimeOptions
{
///
/// Number of Instance Actors to create per batch during staggered startup.
/// Default: 20.
///
public int StartupBatchSize { get; set; } = 20;
///
/// Delay in milliseconds between startup batches to prevent reconnection storms.
/// Default: 100ms.
///
public int StartupBatchDelayMs { get; set; } = 100;
///
/// Maximum call depth for recursive script calls (CallScript/CallShared).
/// Default: 10.
///
public int MaxScriptCallDepth { get; set; } = 10;
///
/// Default script execution timeout in seconds.
/// Default: 30 seconds.
///
public int ScriptExecutionTimeoutSeconds { get; set; } = 30;
///
/// Per-subscriber buffer size for the site-wide Akka stream.
/// Slow subscribers drop oldest messages when buffer is full.
/// Default: 1000.
///
public int StreamBufferSize { get; set; } = 1000;
///
/// FLOOR for the number of dedicated threads in the script-execution scheduler.
/// Script and alarm on-trigger bodies run on these threads instead of the shared
/// .NET thread pool, so blocking script I/O cannot starve the global pool.
///
/// WP3.1: this was a fixed size and is now the lower bound of a grow-only,
/// instance-scaled pool — see
/// . Existing
/// configurations keep exactly the previous behaviour at or below
/// ScriptExecutionThreadCount * InstancesPerScriptThread deployed instances.
///
/// Default: 8.
///
public int ScriptExecutionThreadCount { get; set; } = 8;
///
/// WP3.1: CEILING for the instance-scaled script-execution pool. The pool grows
/// towards ceil(enabledInstances / 8) threads but never past this value; beyond
/// it, is the real regulator. A 1 MB-stack
/// dedicated thread is cheap, so 32 (≈ 256 instances at the /8 ratio) is a generous
/// default. Must be greater than or equal to .
/// Default: 32.
///
public int ScriptExecutionMaxThreadCount { get; set; } = 32;
///
/// WP3.1 (finding #4): maximum number of trigger-expression evaluations allowed to run
/// concurrently across the whole process. Trigger expressions are non-blocking by
/// construction ( exposes only reads over an
/// in-memory snapshot, and the script trust gate has already denied I/O, network, and
/// threading), so they run as plain async work on the shared .NET thread pool behind this
/// gate rather than on the blocking script-execution pool. That is what keeps an alarm's
/// Expression trigger from queueing behind blocking script bodies.
/// Default: max(2, Environment.ProcessorCount).
///
public int TriggerEvalMaxConcurrency { get; set; } = Math.Max(2, Environment.ProcessorCount);
///
/// WP3.1: timeout (seconds) for a single trigger-expression evaluation. Previously
/// hardcoded at 2 s in both ScriptActor and AlarmActor. The deadline is now
/// armed when the evaluation is ENQUEUED, not when it is dequeued, so time spent waiting
/// on burns the same budget — a saturated gate
/// produces a timely "false" instead of an unbounded stall.
/// Default: 2.
///
public int TriggerEvalTimeoutSeconds { get; set; } = 2;
///
/// WP3.1: maximum number of concurrent runs (queued or executing) for any single script
/// or alarm on-trigger script. A trigger that arrives while the cap is reached is SHED —
/// the newest run is refused, the four already in flight (which are closest to their own
/// deadlines) are kept, and no reordering occurs. A shed increments
/// ISiteHealthCollector.IncrementScriptRunShed, emits a rate-limited Warning site
/// event, and — for an Ask-based CallScript — replies with an explicit error rather
/// than letting the caller hang to its Ask timeout.
/// Default: 4.
///
public int MaxConcurrentRunsPerScript { get; set; } = 4;
///
/// Max mirrored native alarms retained per source binding before older entries are dropped (logged).
/// Default: 1000.
///
public int MirroredAlarmCapPerSource { get; set; } = 1000;
///
/// Interval in milliseconds to retry a failed native alarm subscription.
/// Default: 5000ms.
///
public int NativeAlarmRetryIntervalMs { get; set; } = 5000;
/// HTTP timeout (seconds) for fetching a deployment config from central (notify-and-fetch).
public int ConfigFetchTimeoutSeconds { get; set; } = 30;
///
/// Fixed interval (ms) at which an Instance Actor re-sends a tag-subscribe
/// request that either failed or whose response was lost (S4/UA6). The retry is
/// armed on every send and cancelled on the first Success reply, so it closes
/// both the failed-response and the lost-response gaps. Default: 5000ms.
///
public int TagSubscribeRetryIntervalMs { get; set; } = 5000;
///
/// Grace period (ms) after a script's execution timeout elapses before the
/// stuck-script watchdog declares the script's dedicated thread blocked and
/// emits a loud site event naming it (S2). The CTS timeout only requests
/// cooperative cancellation; a script blocked in synchronous I/O never
/// observes it, so this watchdog is the only operator signal that one of the
/// bounded script-execution threads is gone. Default: 30000ms.
///
public int StuckScriptGraceMs { get; set; } = 30000;
///
/// WP2.6d (arch-review misc — site stream alarm-vs-attribute shared buffer):
/// capacity of the bounded hand-off queue feeding 's
/// dedicated alarm publish path. Alarm state changes are staged through this
/// queue into their OWN broadcast source — separate from the (far higher-volume)
/// attribute publish source — so an attribute storm cannot evict a pending alarm
/// transition. DropOldest overflow; drops are counted (surfaced on the site health
/// report) and logged. Default: 2000 (alarm volume is inherently far lower than
/// attribute volume, so this rarely needs to be anywhere near
/// ).
///
public int AlarmPublishQueueCapacity { get; set; } = 2000;
///
/// WP3.2 (site_events volume policy — docs/plans/2026-08-15-site-events-policy-design.md):
/// global switch restoring the legacy per-run Started/Completed Info site events for
/// EVERY instance script run. Off by default: those two rows per run were the dominant
/// site_events writer (~12 physical row-writes across a replicated pair per run,
/// counting CDC oplog + row-version rows and their eventual purge tombstones), for
/// content nobody looks at unless something went wrong. With this off, an interval
/// aggregate () replaces the per-run rows
/// and script Error events (timeout/failure/stuck-watchdog) are unaffected — they are
/// unconditional regardless of this switch. Read via
/// at the top of
/// every run, so it is hot-togglable (no restart, no redeploy). Default: false.
///
public bool PerRunScriptEvents { get; set; }
///
/// WP3.2: per-script opt-in restoring the legacy per-run Started/Completed Info site
/// events for NAMED scripts only, for live debugging without flipping the global
/// switch (and therefore without paying its volume
/// cost for every other script). Effective only while the global switch is off.
/// Entries are "{InstanceName}/{ScriptName}" for an exact match, or
/// "{InstanceName}/*" to opt in every script on that instance. Matched by
/// , read from
/// per run — hot
/// -togglable like . Default: empty (no opt-ins).
///
public List PerRunScriptEventScripts { get; set; } = new();
///
/// WP3.2: flush interval (seconds) for 's
/// interval aggregate — one "script" Info site event per interval summarizing every
/// instance script run since the last flush (headline totals + a top-50-scripts
/// breakdown, capped so a pathological deployment cannot mint an oversized row). An
/// interval with zero activity emits no row. 0 disables the summary flush
/// entirely (the recorder still accumulates counters; they are just never emitted or
/// reset). Default: 300 (5 minutes) — at most 288 summary rows/day, versus 2 rows per
/// script run under the legacy per-run behaviour.
///
public int ScriptRunSummaryIntervalSeconds { get; set; } = 300;
///
/// Interval (seconds) of the Deployment Manager singleton's instance reconcile sweep
/// (deferred register rows 78 + 72). Every tick the singleton re-reads the durable
/// deployed_configurations rows and repairs two silent divergences between the
/// stored configs and the running Instance Actors:
///
/// - an ENABLED instance with a stored config and NO running actor is started
/// (row 78 — configs that arrive by LocalDb CDC replication are written straight into the
/// file by the sync applier, so they are invisible to both the boot-time load, which has
/// already run, and the startup reconcile, which reports no gap because central's view is
/// correct);
/// - a running instance whose stored revision hash no longer matches the revision it
/// was started from is re-applied through the ordinary redeploy path (row 72 — the startup
/// reconcile refreshes a stale config row but leaves the running actor serving the config
/// central believes it replaced).
///
/// The sweep is silent when there is nothing to repair. 0 disables it entirely
/// (the row-67 EnsureInstanceStartedCommand path is unaffected). Default: 60.
///
public int InstanceSweepIntervalSeconds { get; set; } = 60;
}