namespace DodoSSH.Contracts;
///
/// Stable machine-readable error codes returned in the code extension of an
/// RFC 9457 ProblemDetails response.
///
///
/// These live in Contracts so the client switches on constants rather than parsing prose.
/// The values are part of the public contract: add freely, never rename or repurpose.
///
public static class ProblemCodes
{
/// The base URI that every problem type is formed under.
public const string TypeBaseUri = "https://dodossh.dev/problems/";
/// A push operation's expectedVersion did not match the stored row.
public const string VaultConflict = "vault-conflict";
/// The caller is authenticated but lacks the required permission.
public const string Forbidden = "forbidden";
/// The sync cursor was malformed, or failed its integrity tag.
public const string InvalidCursor = "invalid-cursor";
/// An Idempotency-Key was reused with a different request body.
public const string IdempotencyKeyReuse = "idempotency-key-reuse";
/// The caller has not yet enrolled a public key, so no vault is reachable.
public const string EnrollmentRequired = "enrollment-required";
/// Enrollment was attempted for a user who already holds a different current key.
public const string AlreadyEnrolled = "already-enrolled";
///
/// The enrollment request was structurally invalid: a bad key length, mismatched KDF
/// parameters, a statement that does not describe the caller, or a vault id already in use.
///
public const string InvalidEnrollment = "invalid-enrollment";
///
/// The identity-provider token did not bind the supplied keys: a bad signature, the wrong
/// subject or audience, an expired token, or a nonce that is not the statement's hash.
///
public const string IdentityBindingInvalid = "identity-binding-invalid";
///
/// A device registration was structurally invalid: a public key of the wrong length, a missing or
/// oversized wrap, or a blank name.
///
///
/// Distinct from even though the rules overlap, because the two are
/// different requests and a client showing "your enrollment was rejected" when somebody added a
/// fingerprint reader would be describing the wrong thing entirely.
///
public const string InvalidDeviceRegistration = "invalid-device-registration";
///
/// The request body could not be read at all: malformed JSON, or a property the server does not
/// know.
///
///
/// A contract mismatch rather than a rejected value. The request never reached a handler, so no
/// field-level detail is offered and none should be inferred from its absence. Compare the request
/// against the DodoSSH.Contracts assembly for the server version GET /api/v1/meta
/// reports.
///
public const string MalformedRequest = "malformed-request";
/// The relay refused the requested target. Never states why, to avoid a probe oracle.
public const string RelayTargetRejected = "relay-target-rejected";
/// The relay ticket is expired, already used, or not valid for this node.
public const string RelayTicketInvalid = "relay-ticket-invalid";
/// A per-user or per-node relay session limit was reached.
public const string RelayLimitReached = "relay-limit-reached";
/// The client is older than the server's minClientVersion.
public const string ClientTooOld = "client-too-old";
/// A push batch exceeded the operation count or payload size cap.
public const string PushBatchTooLarge = "push-batch-too-large";
///
/// A team create or membership change was structurally invalid: a blank name, a slug that is
/// not URL-safe, an unknown role, or an account that does not exist here.
///
public const string InvalidTeam = "invalid-team";
///
/// The requested slug is already in use.
///
///
/// Its own code rather than folded into , because it is the one create
/// failure the caller could not have predicted from their own input and the only one whose
/// remedy is "pick a different one" rather than "fix what you typed".
///
public const string TeamSlugTaken = "team-slug-taken";
///
/// The change would leave a team with no owner.
///
///
/// Refused rather than allowed, because a team with no owner has nobody who can appoint one —
/// and the only route back would be an operator editing the database by hand. The way past it is
/// POST /api/v1/teams/{teamId}/owner, which moves ownership and the outgoing owner's
/// demotion in one transaction; a client that gets this code can offer that.
///
public const string LastTeamOwner = "last-team-owner";
///
/// A team cannot be archived while it still owns vaults.
///
///
/// Its own code because the remedy is neither "fix what you typed" nor "pick another value": it is
/// to deal with the vaults first. Archiving anyway would hide vaults from every member including
/// the ones holding keys to them, and this product has no way to delete a vault, so the refusal is
/// the honest end of that road rather than a step on it.
///
public const string TeamNotEmpty = "team-not-empty";
///
/// An invitation was rejected: a malformed address, an unknown or ownership role, an expiry the
/// server will not issue, or an address that already has an account here.
///
///
/// Separate from because the most common cause has its own remedy that a
/// client can act on — an address that already has an account should be added through the
/// directory instead, which is the path that shows the caller the key they are about to trust.
///
public const string InvalidTeamInvitation = "invalid-team-invitation";
///
/// A vault key grant was rejected: a fingerprint or wrap of the wrong size, a generation that is
/// not the vault's current one, or a recipient who cannot reach the vault in the first place.
///
///
/// Never a statement about the wrapped key's contents. The server cannot open it, so a
/// grant containing garbage is accepted here and surfaces at the recipient as a tag failure,
/// with the signature naming who issued it. See docs/crypto.md §6.
///
public const string InvalidVaultGrant = "invalid-vault-grant";
}