namespace DodoSSH.Contracts; /// /// Plaintext columns that the server needs in order to function, supplied alongside a payload. /// /// /// /// Kept deliberately minimal. There is no plaintext label or name: ACL administration happens /// in the client, which can decrypt, so the server never needs a searchable title. /// /// /// and may only be set when /// is true, and the database enforces that with a CHECK constraint. The relay must resolve its /// target server-side or it becomes an authenticated open TCP proxy into the operator's own /// network; see ADR 0004. Everything else about a host — username, notes, jump chain, options — /// stays inside the encrypted payload. /// /// /// Whether this host may be dialled through the server relay. /// Target hostname. Permitted only when relay is enabled. /// Target port. Permitted only when relay is enabled. /// Owning group, for tree placement. /// Parent entity, for associations and nested groups. /// The second side of an association row. /// Discriminator for entity types that have one, such as credential kind. /// /// Fingerprint of a public key. Deliberately plaintext: public keys are not secret, and this /// enables "which hosts trust this key" without weakening the threat model. /// public sealed record SyncPlaintextFields( bool RelayEnabled = false, string? Hostname = null, int? Port = null, Guid? GroupId = null, Guid? ParentId = null, Guid? RelatedId = null, int? Kind = null, string? PublicKeyFingerprint = null); /// /// One change a client wants to apply. /// /// /// Client-generated idempotency key for this single operation. A retried push with the same id /// returns rather than applying twice. /// /// Kind of item. /// /// Identifier, generated by the client with UUIDv7 so items can be created offline. /// /// Upsert or delete. /// /// The version the client believes the server holds; means create. A /// mismatch yields — never last-writer-wins. /// /// Ciphertext. Required for an upsert, omitted for a delete. /// Plaintext columns the server needs. public sealed record SyncPushOperation( Guid OperationId, SyncEntityType EntityType, Guid EntityId, SyncOperation Operation, int? ExpectedVersion, EncryptedPayload? Payload, SyncPlaintextFields? PlaintextFields); /// A batch of changes to apply to one vault. /// /// The batch. Capped by the server; see . /// public sealed record SyncPushRequest(IReadOnlyList Operations); /// Outcome of one operation in a push. /// Echoes the request's operation id. /// What happened. /// The stored version after a successful apply. /// The change-log sequence assigned, for cursor comparison. /// /// The server's current state, present only on so /// the client can perform a three-way merge and re-push. /// /// Human-readable explanation for an invalid operation. Never a secret. public sealed record SyncPushResult( Guid OperationId, SyncOperationStatus Status, int? Version, long? ChangeSequence, SyncChange? ServerEntity, string? Detail); /// Per-operation outcomes for a push. /// One entry per submitted operation, in request order. /// /// A cursor positioned after every change this push produced. /// /// Adopting this is only safe if the client had already pulled to the log head. The cursor is /// a sequence position, so if another client committed at sequence 10 while this push took 11, /// jumping to 11 skips 10 permanently. The per-vault advisory lock guarantees that sequence order /// matches commit order; it cannot tell this client about a write it never read. A client that /// keeps its own cursor and re-reads its own writes — which is idempotent, since applying a change /// is a blind overwrite of a local mirror — is strictly safer, and that is what /// DodoSSH.Client.Sync does. /// /// public sealed record SyncPushResponse( IReadOnlyList Results, string Cursor); /// A request for changes since a cursor. /// /// An opaque, integrity-tagged cursor from a previous response, or to /// start from the beginning. Clients must never construct or modify one. /// /// Maximum changes to return. The server clamps this. /// Optional filter. Empty or null means all types. public sealed record SyncPullRequest( string? Cursor, int? Limit, IReadOnlyList? EntityTypes); /// One change as returned by a pull. /// Kind of item. /// Identifier. /// Upsert or delete. A delete carries no payload. /// Version after the change. /// Position in the vault's change log. /// Ciphertext, absent for a delete. /// Plaintext columns, absent for a delete. /// When the change was recorded. public sealed record SyncChange( SyncEntityType EntityType, Guid EntityId, SyncOperation Operation, int Version, long ChangeSequence, EncryptedPayload? Payload, SyncPlaintextFields? PlaintextFields, DateTimeOffset UpdatedAt); /// A page of changes. /// Changes in ascending change-sequence order. /// Cursor to pass to the following pull. /// Whether more changes are immediately available. /// /// The server's clock, so a client can detect its own skew rather than mis-ordering local edits. /// /// /// The vault's current key generation. A client seeing a generation ahead of its own knows a /// rekey happened and that it must fetch new grants. /// public sealed record SyncPullResponse( IReadOnlyList Changes, string NextCursor, bool HasMore, DateTimeOffset ServerTime, uint CurrentKeyGeneration);