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);