namespace DodoSSH.Domain; /// What a change did to an entity. public enum ChangeOperation { /// Not a legal value. Unspecified = 0, /// Created or modified. Upsert = 1, /// Soft-deleted, leaving a tombstone. Delete = 2, } /// The kind of item a change refers to. Mirrors the contract enum; append only. public enum ChangeEntityType { /// Not a legal value. Unspecified = 0, /// An SSH host. SshHost = 1, /// A credential. M2. Credential = 2, /// An SSH key pair. M2. SshKey = 3, /// A host group. M2. HostGroup = 4, /// A tag. M2. Tag = 5, /// A host-to-tag association. M2. HostTag = 6, /// A host-to-credential association. M2. HostCredential = 7, /// A snippet. M2. Snippet = 8, /// A port forward. M2. PortForward = 9, /// A known SSH host key. M2. KnownHostKey = 10, /// One connection that was made, and how long it lasted. M2. ConnectionLogEntry = 11, /// One create, edit or delete of a vault item. M2. ActivityLogEntry = 12, /// An S3-compatible bucket, and the credentials that reach it. M2. ObjectStore = 13, /// A Spelunk API key, and the one Spelunk address it may be sent to. SpelunkKey = 14, } /// /// One entry in a vault's change log, which is what delta sync reads. /// /// /// /// comes from a bigserial, and that carries a trap worth stating /// where the code lives: sequence values are handed out before commit. If transaction A /// takes 5 and B takes 6 but B commits first, a reader that advances its cursor to 6 permanently /// misses 5 — silent sync corruption that only appears under concurrent writes to one vault. /// /// /// Every push therefore takes a per-vault transaction-scoped advisory lock as its first /// statement, so sequence order equals commit order. See ADR 0003. /// /// public sealed class VaultChange { /// Monotonic sequence. Database-generated. public long Sequence { get; set; } /// Owning vault. Cursors are scoped per vault. public Guid VaultId { get; set; } /// Kind of item. public ChangeEntityType EntityType { get; set; } /// The item. public Guid EntityId { get; set; } /// What happened. public ChangeOperation Operation { get; set; } /// The item's version after the change. public int Revision { get; set; } /// Who made the change. public Guid ActorUserId { get; set; } /// When it was recorded. public DateTimeOffset OccurredAtUtc { get; set; } } /// /// Records that a client operation was applied, so a retry is a no-op. /// /// /// Keyed on the client-generated operation id, which makes retries exactly-once at /// operation granularity. A batch-level idempotency key alone would not: a client that /// times out mid-push and retries with a partially overlapping batch would otherwise double-apply /// the operations that did land. /// public sealed class SyncOperationReceipt { /// The client-generated operation id. Primary key. public Guid OperationId { get; set; } /// Vault the operation targeted. public Guid VaultId { get; set; } /// Change-log sequence produced, when the operation was applied. public long? AppliedChangeSequence { get; set; } /// The item's version after the operation. public int? ResultVersion { get; set; } /// Creation timestamp. public DateTimeOffset CreatedAtUtc { get; set; } }