M1: the interaction ledger
InteractionEvent records one interaction with a remote server: its channel (recv, in, out, http, preview, crawl), activity and object type, outcome and reason, status, latency, wait, bytes, attempt, audience, local actor kind, inbox, signature, features and the object's age. IInteractionLedger.Record never blocks and never throws: events go into a bounded channel of 10k, a full channel drops and counts, and a hosted service writes batches of up to 1000 every two seconds. Privacy, as decided by the owner: - no persona, root, group or activity id, inbox URL, actor URI or sender IP is stored; - distinct accounts are counted with an HMAC keyed by a per-day salt (InteractionSalt, upserted so restarts agree, never created for a past day); - the local actor kind survives only on public and unlisted traffic; - a host claimed by an unverified sender is kept only if it is already known. Traffic caused by reading is only counted per day (InstanceDay.Reads, ServerDay). Indexes: a 90-day TTL on events, unique day rows, and a TTL safety net on salts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ELjqpznMFMNrJoJUj6K5p2
This commit is contained in:
1 parent
125a49a1a0
commit
15cd034b29
16 files changed
+735
-1
No files matched your search
@@ -0,0 +1,18 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
namespace PrivaPub.Models.Statistics
|
||||
{
|
||||
public class InstanceDay : Entity
|
||||
{
|
||||
public DateTime Day { get; set; }
|
||||
public string Host { get; set; }
|
||||
public Dictionary<string, long> Counters { get; set; } = new();//admin: every key
|
||||
public Dictionary<string, long> PublicCounters { get; set; } = new();//all a public page may ever read
|
||||
public Dictionary<string, long> Latency { get; set; } = new();
|
||||
public Dictionary<string, long> Waits { get; set; } = new();
|
||||
public Dictionary<string, long> Bytes { get; set; } = new();
|
||||
public Dictionary<string, long> Reads { get; set; } = new();//caused by reading (media proxy, lookups): counted live, never by the rollup
|
||||
public int Accounts { get; set; }//distinct remote accounts that day
|
||||
public DateTime? RolledUpAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
namespace PrivaPub.Models.Statistics
|
||||
{
|
||||
public class InteractionEvent : Entity
|
||||
{
|
||||
public static readonly TimeSpan Retention = TimeSpan.FromDays(90);
|
||||
|
||||
public DateTime At { get; set; } = DateTime.UtcNow;
|
||||
public string Host { get; set; }//the remote server, lowercase; "-" when unverified and unknown
|
||||
public string Channel { get; set; }//recv | in | out | http | preview | crawl
|
||||
public string Activity { get; set; }//Create, Follow, ...; "other" when it is no plain type name
|
||||
public string Object { get; set; }//Note, Article, Person, ...
|
||||
public string Purpose { get; set; }//http and crawl: actor, key, object, context, webfinger, nodeinfo, ...
|
||||
public string Trigger { get; set; }//http: what caused the request (inbox, deliver, describe, request, ...)
|
||||
public string Outcome { get; set; }//queued, refused, accepted, dropped, ok, failed, deferred, retry, dead
|
||||
public string Reason { get; set; }
|
||||
public int? Status { get; set; }
|
||||
public int? LatencyMs { get; set; }
|
||||
public int? WaitMs { get; set; }//in: since it reached the inbox; out: since it was queued
|
||||
public long? Bytes { get; set; }
|
||||
public int? Attempt { get; set; }
|
||||
public int? Redirects { get; set; }
|
||||
public string Audience { get; set; }//public | unlisted | private | none
|
||||
public string LocalKind { get; set; }//person | group | application, only on public and unlisted traffic
|
||||
public string Inbox { get; set; }//shared | personal
|
||||
public string Signature { get; set; }//"cavage:rsa-sha256", "none"
|
||||
public string ActorHash { get; set; }//keyed with that day's salt, never reversible, never linkable across days
|
||||
public List<string> Features { get; set; }
|
||||
public long? AgeSeconds { get; set; }//Update and Delete: how old the object was
|
||||
public bool Crawl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
namespace PrivaPub.Models.Statistics
|
||||
{
|
||||
public class InteractionSalt : Entity
|
||||
{
|
||||
public DateTime Day { get; set; }
|
||||
public string Key { get; set; }
|
||||
public DateTime ExpiresAt { get; set; }//a safety net: the day's rollup deletes it
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
namespace PrivaPub.Models.Statistics
|
||||
{
|
||||
public class ServerDay : Entity
|
||||
{
|
||||
public DateTime Day { get; set; }
|
||||
public Dictionary<string, long> Client { get; set; } = new();//client API per endpoint group, method and status class
|
||||
public Dictionary<string, long> ClientLatency { get; set; } = new();
|
||||
public Dictionary<string, long> Served { get; set; } = new();//our own documents fetched, never per server
|
||||
public long LedgerWritten { get; set; }
|
||||
public long LedgerDropped { get; set; }
|
||||
public long LedgerFailed { get; set; }
|
||||
public int Accounts { get; set; }
|
||||
public int Hosts { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user