posting.rs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. //! Posting records and their derived lifecycle state.
  2. use crate::ids::{AccountId, AssetId, PostingId, ReservationId};
  3. use kuatia_money::Cent;
  4. use serde::{Deserialize, Serialize};
  5. /// Read filter over the derived lifecycle state of postings.
  6. ///
  7. /// A posting's state is no longer stored on the posting itself; it is derived
  8. /// from index-table membership. This filter selects which postings a read
  9. /// returns:
  10. ///
  11. /// - `Active` — spendable (present in the active index).
  12. /// - `Reserved` — claimed by an in-flight saga (present in the reserved index).
  13. /// - `Live` — `Active ∪ Reserved`; everything that still counts toward balance.
  14. /// - `All` — every posting in the immutable table, including spent ones.
  15. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
  16. pub enum PostingFilter {
  17. /// Spendable postings only.
  18. Active,
  19. /// Reserved (in-flight) postings only.
  20. Reserved,
  21. /// Active or reserved: the balance-bearing set (everything not yet Spent).
  22. Live,
  23. /// Every posting ever created, including spent ones.
  24. All,
  25. }
  26. /// The derived lifecycle state of a single [`Posting`], computed from
  27. /// index-table membership rather than stored on the posting.
  28. ///
  29. /// ```text
  30. /// Active ──reserve──▶ Reserved(rid) ──consume──▶ Spent
  31. /// ▲ ▲ │
  32. /// │ └── release ────────┘ (compensation)
  33. /// └── (id in active index)
  34. /// ```
  35. ///
  36. /// `Reserved` carries the owning [`ReservationId`] so a saga can confirm it
  37. /// still holds a posting before finalizing or releasing it. `Missing` means the
  38. /// id is not present in the immutable postings table at all.
  39. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
  40. pub enum PostingState {
  41. /// Present in the active index — spendable, counts toward balance.
  42. Active,
  43. /// Present in the reserved index, claimed by the given reservation.
  44. Reserved(ReservationId),
  45. /// Present only in the immutable table — consumed by a committed transfer.
  46. Spent,
  47. /// Not present in the immutable table.
  48. Missing,
  49. }
  50. /// A signed amount of one asset, owned by exactly one account.
  51. ///
  52. /// A positive posting is value controlled by the account; a negative posting is
  53. /// an offset position (issuance, external flow, overdraft, or system balancing).
  54. /// Negative postings are allowed on any account except one that forbids
  55. /// overdraft (carries [`AccountFlags::DEBIT_MUST_NOT_EXCEED_CREDIT`](crate::AccountFlags::DEBIT_MUST_NOT_EXCEED_CREDIT)).
  56. ///
  57. /// A `Posting` is an immutable record: once created it is never updated. Its
  58. /// lifecycle state is not a field here; it is derived from index-table
  59. /// membership (see [`PostingState`]).
  60. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  61. pub struct Posting {
  62. /// Unique identifier derived from the creating transfer.
  63. pub id: PostingId,
  64. /// The account (subaccount) that owns this posting.
  65. pub owner: AccountId,
  66. /// The asset this posting denominates.
  67. pub asset: AssetId,
  68. /// Signed: positive = value controlled by the account, negative = offset position.
  69. pub value: Cent,
  70. }
  71. impl Posting {
  72. /// Construct a posting record.
  73. pub fn new(id: PostingId, owner: AccountId, asset: AssetId, value: Cent) -> Self {
  74. Self {
  75. id,
  76. owner,
  77. asset,
  78. value,
  79. }
  80. }
  81. }
  82. /// A posting to be created — carries no id yet because the [`PostingId`] depends
  83. /// on the [`EnvelopeId`](crate::EnvelopeId), which is computed during validation.
  84. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  85. pub struct NewPosting {
  86. /// The account (subaccount) that will own the created posting.
  87. pub owner: AccountId,
  88. /// The asset this posting denominates.
  89. pub asset: AssetId,
  90. /// Signed amount: positive = value controlled by the account, negative = offset position.
  91. pub value: Cent,
  92. /// Informational provenance — who funded this posting.
  93. pub payer: Option<AccountId>,
  94. }