OpenPay (1 crate, ~2940 LOC, 40 tests)
op-statements Statement generation + reconciliation + fee accounting.
Render targets: PDF (template), CSV, JSON, 94-char
NACHA fixed-width. ISO 20022 camt.053 with full
BkToCstmrStmt envelope (GrpHdr/Stmt/Bal/Ntry). BAI2
with 01/02/03/16/49/98/99 records. MT940/MT942
SWIFT with :20:/:25:/:28C:/:60F:/:61:/:86:/:62F:
+ :90C:/:90D: counters. FeeBucket (interchange/scheme/
acquirer/fx/settlement-network/bnpl) + FeeSchedule
with bps + flat rules. Cadence (daily/weekly/monthly/
custom). Two-tier Reconciler (external_id → window
heuristic).
Smart Byte (1 crate, 3032 LOC, 39 tests)
smart-byte-activitypub ActivityPub W3C Rec (2018). AS2 vocab + Public
addressing; Actor (Person/Application/Group/
Organization/Service) with publicKey + shared-inbox;
Object (Note/Article/Image/Video/Tombstone) with
Mastodon extensions (sensitive, CW summary,
conversation, blurhash); 12 Activity types
(Create/Update/Delete/Follow/Accept/Reject/Announce/
Like/Undo/Move/Block/Flag) with side-effect rules;
OrderedCollection + Page; Webfinger (RFC 7033);
HTTP Signatures (cavage draft-12 + RFC 9421);
SHA-256 Digest; Ed25519 sign/verify.
EOC (1 crate, 2759 LOC, 20 tests)
eoc-observability OpenTelemetry-compatible tracing + metrics + logs.
W3C tracecontext + baggage. Span / SpanKind / Status.
Samplers: AlwaysOn/Off/RatioBased/ParentBased. Sync
BatchSpanProcessor + Exporter trait. Counter / Gauge /
Histogram. Prometheus text exposition (HELP/TYPE/
_bucket/_sum/_count/+Inf). StatsD / DogStatsD lines.
GenAI semconv (gen_ai.{operation,system,request.*,
response.*,usage.*}) + EOC extensions (eoc.joules.*,
eoc.co2e_grams, eoc.cost.usd). USD + gCO2e attribution
with PUE. LangSmith + LangFuse export adapters.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
89 lines
2.9 KiB
Rust
89 lines
2.9 KiB
Rust
//! Publish a Note via the Outbox, resolve its audience, and replay it
|
|
//! through an Inbox on the other side.
|
|
|
|
use smart_byte_activitypub::{
|
|
Activity, InboxEffect, InboxState, Object, Outbox, Result, StaticResolver, PUBLIC,
|
|
};
|
|
|
|
#[test]
|
|
fn create_note_round_trip() -> Result<()> {
|
|
// Alice composes a public Note that ccs her followers.
|
|
let note = Object::note(
|
|
"https://a.test/notes/1",
|
|
"https://a.test/users/alice",
|
|
"<p>hello fediverse</p>",
|
|
)
|
|
.cc("https://a.test/users/alice/followers");
|
|
|
|
let create = Activity::create(
|
|
"https://a.test/activities/c-1",
|
|
"https://a.test/users/alice",
|
|
note.clone(),
|
|
);
|
|
|
|
// Alice's followers collection contains Bob and Carol.
|
|
let mut resolver = StaticResolver::new();
|
|
resolver.insert(
|
|
"https://a.test/users/alice/followers",
|
|
vec![
|
|
"https://b.test/users/bob".to_string(),
|
|
"https://c.test/users/carol".to_string(),
|
|
],
|
|
);
|
|
|
|
// Publish via the outbox.
|
|
let mut outbox = Outbox::new();
|
|
let audience = outbox.publish(create.clone(), &resolver, &|actor| match actor {
|
|
"https://b.test/users/bob" => Some("https://b.test/users/bob/inbox".into()),
|
|
"https://c.test/users/carol" => Some("https://c.test/users/carol/inbox".into()),
|
|
_ => None,
|
|
})?;
|
|
assert!(audience.is_public);
|
|
assert_eq!(audience.actors.len(), 2);
|
|
assert_eq!(outbox.total_items(), 1);
|
|
|
|
let delivery = outbox.next().expect("one pending delivery");
|
|
assert_eq!(delivery.inboxes.len(), 2);
|
|
assert!(delivery.body.to.iter().any(|t| t == PUBLIC));
|
|
|
|
// Bob's inbox applies the Create.
|
|
let mut bob = InboxState::new();
|
|
let effect = bob.handle(&delivery.body)?;
|
|
assert_eq!(effect, InboxEffect::ObjectStored("https://a.test/notes/1".into()));
|
|
let stored = bob
|
|
.objects
|
|
.get("https://a.test/notes/1")
|
|
.expect("note stored");
|
|
assert_eq!(stored.content.as_deref(), Some("<p>hello fediverse</p>"));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn delete_replaces_with_tombstone() -> Result<()> {
|
|
let mut state = InboxState::new();
|
|
let note = Object::note(
|
|
"https://a.test/notes/9",
|
|
"https://a.test/users/alice",
|
|
"to be removed",
|
|
);
|
|
let create = Activity::create(
|
|
"https://a.test/activities/c-9",
|
|
"https://a.test/users/alice",
|
|
note,
|
|
);
|
|
state.handle(&create)?;
|
|
let delete = Activity::delete(
|
|
"https://a.test/activities/d-9",
|
|
"https://a.test/users/alice",
|
|
"https://a.test/notes/9",
|
|
);
|
|
let effect = state.handle(&delete)?;
|
|
assert_eq!(effect, InboxEffect::ObjectDeleted("https://a.test/notes/9".into()));
|
|
let after = state
|
|
.objects
|
|
.get("https://a.test/notes/9")
|
|
.expect("tombstone present");
|
|
assert_eq!(after.type_field, "Tombstone");
|
|
assert_eq!(after.former_type.as_deref(), Some("Note"));
|
|
Ok(())
|
|
}
|