using ZB.MOM.WW.ScadaBridge.Commons.Entities.Deployment; using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances; using ZB.MOM.WW.ScadaBridge.Commons.Types.Deployment; using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums; namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories; public interface IDeploymentManagerRepository { // DeploymentRecord /// /// Gets a deployment record by its ID. /// /// The deployment record ID. /// A cancellation token that can be used to cancel the operation. /// The deployment record, or null if not found. Task GetDeploymentRecordByIdAsync(int id, CancellationToken cancellationToken = default); /// /// Database-side paged, filtered and instance-joined deployment listing — THE /// paged read of DeploymentRecords, shared by the Central UI /// deployment-status page and the CLI/HTTP QueryDeployments command. /// /// /// It was two reads until deferred-work register row 37. The other one /// (QueryDeploymentSummariesAsync) differed in three ways that were all /// defects rather than requirements: it took a raw skip instead of a page /// number, returned no total (so a truncated page was indistinguishable from a /// complete one), skipped the Instance join, and scoped by a caller-resolved /// INSTANCE-ID LIST — which its one caller built by loading every instance in the /// system on every scoped query. Site scoping is expressed here as /// and resolved by /// the join, so no caller needs that read. /// /// /// /// Replaces a GetAllDeploymentRecords whole-table read. That method's only /// caller loaded EVERY deployment record (the table is insert-only, one row per /// deploy attempt for the retention window) plus EVERY instance, then filtered by /// site scope, sorted, counted the status tiles and sliced a 25-row page — all in /// the Blazor circuit's memory, on every render AND on every deployment-status /// push. Cost scaled with the age of the system rather than the size of the page. /// /// /// /// OFFSET paging, not keyset — deliberately. The sibling Audit Log and Site /// Calls read paths are keyset-cursored, but this one is offset-paged like the /// Notification Outbox, and for the same reason: its contract surfaces a PAGE /// NUMBER and a TOTAL COUNT (the page renders a numbered, jump-to-any-page pager), /// and a keyset cursor can express neither. The total is needed regardless, since /// the status tiles are an aggregate over the same filtered set. The deep-offset /// cost that pushes high-volume tables to keyset is bounded here by /// , which caps the table at the /// terminal-record retention window — unlike the 365-day central /// AuditLog. /// /// /// /// Ordering is DeployedAt DESC, Id DESC. The Id tie-break is not /// cosmetic: DeployedAt ties on rapid redeploys, and an unstable sort key /// makes offset paging non-deterministic — rows repeat across pages or vanish /// between them. /// /// /// Status / search / site-scope / instance constraints; all applied DB-side. /// 1-based page number; values below 1 are floored at 1. /// Rows per page; clamped to [1, DeploymentListPage.MaxPageSize]. There is no "unlimited" value. /// A cancellation token that can be used to cancel the operation. /// The requested page of rows plus the total count of the filtered set. Task QueryDeploymentListPageAsync( Types.Deployment.DeploymentListFilter filter, int pageNumber, int pageSize, CancellationToken cancellationToken = default); /// /// One grouped aggregation returning the per-status row counts of the filtered /// set — the server-side replacement for the deployment page's four /// records.Count(r => r.Status == X) tile computations over a /// client-materialized table. /// /// /// is ignored here. /// The tiles are the status BREAKDOWN of the otherwise-identically-filtered set, /// so constraining them by the active status filter would collapse three of the /// four tiles to zero the moment an operator clicked one. Every other dimension /// (search, site scope, instance) IS applied, so the tiles always describe the /// same population the list is drawn from. /// /// /// The active list filter; its Status member is deliberately not applied. /// A cancellation token that can be used to cancel the operation. /// Per-status counts, with absent statuses reported as 0. Task GetDeploymentStatusCountsAsync( Types.Deployment.DeploymentListFilter filter, CancellationToken cancellationToken = default); /// /// Deletes TERMINAL deployment records ( / /// ) that completed before /// , in one bounded batch. /// /// /// Non-terminal rows are never touched: an InProgress record is what the /// query-before-redeploy reconciliation path reads to decide whether a prior /// deploy actually landed at the site, so purging one by age would silently /// disable that idempotency guard. /// /// /// Records completing before this instant are eligible. /// Maximum rows deleted in this call. /// A cancellation token that can be used to cancel the operation. /// The number of rows deleted; fewer than means the sweep is complete. Task PurgeTerminalDeploymentRecordsAsync(DateTimeOffset cutoffUtc, int batchSize, CancellationToken cancellationToken = default); /// /// Gets all deployment records for a specific instance. /// /// The instance ID. /// A cancellation token that can be used to cancel the operation. /// A read-only list of deployment records for the instance. Task> GetDeploymentsByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default); /// /// Gets the current deployment status for an instance. /// /// The instance ID. /// A cancellation token that can be used to cancel the operation. /// The current deployment record, or null if no deployment exists. Task GetCurrentDeploymentStatusAsync(int instanceId, CancellationToken cancellationToken = default); /// /// Gets a deployment record by deployment ID. /// /// The deployment ID. /// A cancellation token that can be used to cancel the operation. /// The deployment record, or null if not found. Task GetDeploymentByDeploymentIdAsync(string deploymentId, CancellationToken cancellationToken = default); /// /// Adds a new deployment record. /// /// The deployment record to add. /// A cancellation token that can be used to cancel the operation. /// A task representing the asynchronous operation. Task AddDeploymentRecordAsync(DeploymentRecord record, CancellationToken cancellationToken = default); /// /// Updates an existing deployment record. /// /// The deployment record to update. /// A cancellation token that can be used to cancel the operation. /// A task representing the asynchronous operation. Task UpdateDeploymentRecordAsync(DeploymentRecord record, CancellationToken cancellationToken = default); /// /// Deletes a deployment record by ID, enforcing optimistic concurrency against the /// supplied . The caller MUST pass the /// RowVersion it last observed on the record so EF emits /// DELETE ... WHERE Id = @id AND RowVersion = @prior. A concurrent edit /// surfaces as /// on , matching the documented /// "Optimistic concurrency is used on deployment status records" design rule. /// /// The deployment record ID to delete. /// The RowVersion the caller observed; used as the optimistic-concurrency token. /// A cancellation token that can be used to cancel the operation. /// A task representing the asynchronous operation. Task DeleteDeploymentRecordAsync(int id, byte[] expectedRowVersion, CancellationToken cancellationToken = default); // SystemArtifactDeploymentRecord /// /// Gets a system artifact deployment record by ID. /// /// The system artifact deployment record ID. /// A cancellation token that can be used to cancel the operation. /// The system artifact deployment record, or null if not found. Task GetSystemArtifactDeploymentByIdAsync(int id, CancellationToken cancellationToken = default); /// /// Gets all system artifact deployment records. /// /// A cancellation token that can be used to cancel the operation. /// A read-only list of all system artifact deployment records. Task> GetAllSystemArtifactDeploymentsAsync(CancellationToken cancellationToken = default); /// /// Adds a new system artifact deployment record. /// /// The system artifact deployment record to add. /// A cancellation token that can be used to cancel the operation. /// A task representing the asynchronous operation. Task AddSystemArtifactDeploymentAsync(SystemArtifactDeploymentRecord record, CancellationToken cancellationToken = default); /// /// Updates an existing system artifact deployment record. /// /// The system artifact deployment record to update. /// A cancellation token that can be used to cancel the operation. /// A task representing the asynchronous operation. Task UpdateSystemArtifactDeploymentAsync(SystemArtifactDeploymentRecord record, CancellationToken cancellationToken = default); /// /// Deletes a system artifact deployment record by ID. /// /// The system artifact deployment record ID to delete. /// A cancellation token that can be used to cancel the operation. /// A task representing the asynchronous operation. Task DeleteSystemArtifactDeploymentAsync(int id, CancellationToken cancellationToken = default); /// /// Deletes system artifact deployment records deployed before , /// in one bounded batch. One row is written per Deploy Artifacts run and nothing /// reads them back, so they share the terminal deployment-record retention window /// instead of growing without bound. /// /// Records deployed before this instant are eligible. /// Maximum rows deleted in this call. /// A cancellation token that can be used to cancel the operation. /// The number of rows deleted; fewer than means the sweep is complete. Task PurgeSystemArtifactDeploymentRecordsAsync(DateTimeOffset cutoffUtc, int batchSize, CancellationToken cancellationToken = default); // DeployedConfigSnapshot /// /// Gets the deployed config snapshot for an instance. /// /// The instance ID. /// A cancellation token that can be used to cancel the operation. /// The deployed config snapshot, or null if not found. Task GetDeployedSnapshotByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default); /// /// InstanceId → RevisionHash for every DeployedConfigSnapshot. A projection read — never /// materialises ConfigurationJson, so it is safe to call for the whole fleet (the /// pending-updates aggregate does, once per cache TTL). /// /// /// Deliberately UNFILTERED: it always returns the whole fleet's two-column projection, with no /// site, state or scope narrowing. A site-scoped or otherwise partial caller must filter the /// returned map itself rather than assume the read was narrowed for it. /// /// /// A cancellation token that can be used to cancel the operation. /// A dictionary keyed by instance ID; instances with no snapshot are absent. Task> GetDeployedRevisionHashesAsync(CancellationToken cancellationToken = default); /// /// Adds a new deployed config snapshot. /// /// The deployed config snapshot to add. /// A cancellation token that can be used to cancel the operation. /// A task representing the asynchronous operation. Task AddDeployedSnapshotAsync(DeployedConfigSnapshot snapshot, CancellationToken cancellationToken = default); /// /// Updates an existing deployed config snapshot. /// /// The deployed config snapshot to update. /// A cancellation token that can be used to cancel the operation. /// A task representing the asynchronous operation. Task UpdateDeployedSnapshotAsync(DeployedConfigSnapshot snapshot, CancellationToken cancellationToken = default); /// /// Deletes the deployed config snapshot for an instance. /// /// The instance ID. /// A cancellation token that can be used to cancel the operation. /// A task representing the asynchronous operation. Task DeleteDeployedSnapshotAsync(int instanceId, CancellationToken cancellationToken = default); // Notify-and-fetch: PendingDeployment staging store /// /// Stages a flattened instance config for an in-flight deployment. Supersession: /// any prior pending row for the same InstanceId is removed first, so at most /// one pending row exists per instance (safe because the per-instance operation lock /// serializes same-instance deploys). Does NOT call — /// the caller commits, mirroring the other Add/Delete repository methods. /// /// The pending deployment to stage. /// A cancellation token that can be used to cancel the operation. /// A task representing the asynchronous operation. Task AddPendingDeploymentAsync(PendingDeployment pending, CancellationToken cancellationToken = default); /// /// Gets a pending deployment by its deployment ID (the fetch key). /// /// The deployment ID. /// A cancellation token that can be used to cancel the operation. /// The pending deployment, or null if not found. Task GetPendingDeploymentByIdAsync(string deploymentId, CancellationToken cancellationToken = default); /// /// Gets the pending deployment staged for a given instance (there is at most one per instance /// by design — supersedes and /// inserts only if absent). Used by startup reconcile: /// when reports a row already exists (a concurrent /// reconcile from the other node, or an in-flight deploy), the handler reads that existing row /// so a second concurrently-missing node can fetch the same pending config with its (multi-use, /// TTL-bound) token instead of being omitted from the gap. Defensive against >1 row: returns /// the most recently created one. When is supplied, EXPIRED rows /// (ExpiresAtUtc <= nowUtc) are filtered out so the caller never receives a token the /// config-fetch endpoint would 404. /// /// The instance whose pending deployment to read. /// When supplied, only rows with ExpiresAtUtc > nowUtc are considered (expired rows excluded). /// A cancellation token that can be used to cancel the operation. /// The pending deployment for the instance, or null if none is staged. Task GetPendingDeploymentByInstanceIdAsync(int instanceId, DateTimeOffset? nowUtc = null, CancellationToken cancellationToken = default); /// /// Deletes a pending deployment by its deployment ID. No-op if not found. Does NOT /// call — the caller commits, mirroring the other /// Add/Delete repository methods. /// /// The deployment ID to delete. /// A cancellation token that can be used to cancel the operation. /// A task representing the asynchronous operation. Task DeletePendingDeploymentByIdAsync(string deploymentId, CancellationToken cancellationToken = default); /// /// Purges all pending deployments whose ExpiresAtUtc is at or before /// (TTL maintenance). This is a self-contained maintenance /// operation: it commits its own delete and returns the number of rows removed. /// /// The current UTC time; rows with ExpiresAtUtc <= nowUtc are purged. /// A cancellation token that can be used to cancel the operation. /// The number of pending deployments purged. Task PurgeExpiredPendingDeploymentsAsync(DateTimeOffset nowUtc, CancellationToken cancellationToken = default); // Startup reconciliation: expected-set query and insert-if-absent staging /// /// Returns the set of instances that central considers deployed for the given site — the join of /// DeployedConfigSnapshot (by InstanceId) with Instance (where SiteId == siteId). /// Instances without a snapshot are excluded. No ConfigurationJson is loaded; the full /// config is fetched on demand via the HTTP endpoint using a freshly-minted token from /// . /// /// The site primary key to filter on. /// A cancellation token that can be used to cancel the operation. /// All deployed instances for the site, as lightweight projections. Task> GetExpectedDeploymentsForSiteAsync(int siteId, CancellationToken cancellationToken = default); /// /// Inserts a for the instance ONLY IF no pending row already /// exists for that InstanceId. An existing pending row signals an in-flight deploy that /// is already delivering to the node — do NOT supersede it (contrast with /// , which supersedes). Returns true if the row /// was staged, false if an existing row was found and left untouched. /// This method is self-contained: it commits its own save and returns a meaningful result, /// matching the convention of . /// /// The instance to stage the pending deployment for. /// The deployment ID (fetch key). /// Revision hash of the flattened configuration. /// JSON-serialized flattened configuration. /// Short-TTL fetch token the site node will present to the HTTP endpoint. /// UTC timestamp for this pending row. /// UTC expiry after which the token is no longer valid. /// A cancellation token that can be used to cancel the operation. /// true if staged; false if a pending row already existed and was left unchanged. Task StagePendingIfAbsentAsync(int instanceId, string deploymentId, string revisionHash, string configurationJson, string token, DateTimeOffset createdAtUtc, DateTimeOffset expiresAtUtc, CancellationToken cancellationToken = default); // Instance lookups for deployment pipeline /// /// Gets an instance by ID. /// /// The instance ID. /// A cancellation token that can be used to cancel the operation. /// The instance, or null if not found. Task GetInstanceByIdAsync(int instanceId, CancellationToken cancellationToken = default); /// /// Gets an instance by unique name. /// /// The unique instance name. /// A cancellation token that can be used to cancel the operation. /// The instance, or null if not found. Task GetInstanceByUniqueNameAsync(string uniqueName, CancellationToken cancellationToken = default); /// /// Updates an instance. /// /// The instance to update. /// A cancellation token that can be used to cancel the operation. /// A task representing the asynchronous operation. Task UpdateInstanceAsync(Instance instance, CancellationToken cancellationToken = default); /// /// Removes an instance and everything that depends on it: deployment /// records, deployed config snapshot, attribute/alarm overrides, and /// connection bindings. /// /// The instance ID to delete. /// A cancellation token that can be used to cancel the operation. /// A task representing the asynchronous operation. Task DeleteInstanceAsync(int instanceId, CancellationToken cancellationToken = default); /// /// Saves all pending changes to the database. /// /// A cancellation token that can be used to cancel the operation. /// A task representing the number of entities saved. Task SaveChangesAsync(CancellationToken cancellationToken = default); /// /// Abandons every pending (added / modified / deleted) change on the unit of /// work, leaving the underlying store untouched, and returns how many entries /// were dropped. /// /// /// Recovery only. A failure does NOT /// untrack the entity that caused it: it stays in the change set and is /// re-attempted by the NEXT save, so one unsavable entity poisons every /// later save made through the same unit of work. That matters most on the /// bulk site-deploy path, where all three phases share ONE scoped context /// across every instance in the batch — a persistent failure on instance 1 /// would otherwise fail instance 2..N's status writes as well, including the /// cleanup write that exists precisely to stop records being stranded /// InProgress. Call this only after a save has already failed and its /// pending work has been given up on; anything not yet committed is lost by /// design. /// /// /// The number of tracked entries detached (0 when nothing was pending). int DetachPendingChanges(); }