New :relayBench module that boots relay implementations as real external
processes under equivalent setups (persistent storage, sig verification on,
no auth) and compares them on the same corpus:
- Ingest: receipt->queryable-by-REQ latency (publish on one connection,
hammer-poll REQ{ids} on another), OK-ack latency, and pipelined corpus
replay throughput over N connections.
- Queries: client-realistic filters derived from the corpus (home feed,
thread, notifications, profiles, hashtag, by-ids, ...), time-to-EOSE
percentiles plus aggregate events/sec under concurrency; result-set
counts are cross-checked between relays and mismatches flagged.
- NIP-77 negentropy sync between every relay pair: 80%/80% slices with 60%
overlap, reconcile timing/rounds/wire-bytes per server side, delta
transfer, steady-state identical-set reconcile, convergence verified.
- Storage footprint after full ingest.
Corpora (all cached as NDJSON + manifest with a sha256 id fingerprint so
results are comparable across runs and machines):
- synthetic (default): deterministic to the byte — seeded keys, fixed
timestamps, seed-derived BIP-340 aux nonces — with a realistic social
shape (zipf authors, threads, reactions, reposts, zap request/receipt
pairs, hashtags);
- the checked-in real dump (quartz test fixture, ~31k unique 2024 events);
- external dumps (NDJSON or JSON array, gzip sniffed by magic bytes),
e.g. the 2.1M contact-list archive, with --max-event-bytes/--max-tags
raising both the corpus filter and the strfry config together;
- live download from public relays.
Every source runs through the same preparation: dedup, drop unsigned/
ephemeral/kind-5, enforce relay ingest caps, parallel Schnorr verify,
chronological sort.
relayBench/run.sh is the one-command entry point: builds geode + harness,
resolves strfry (STRFRY_BIN, PATH, or source build into .cache), runs the
suite and renders an ANSI report with per-metric bars and winners, plus
report.md and results.json under relayBench/results/<timestamp>/.
The harness client disables Nagle (TCP_NODELAY): with the JDK default, a
REQ following the previous round's CLOSE stalls a full delayed-ACK
interval and every latency floors at ~44 ms against both geode and strfry
(verified: ~0.3 ms with it off).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeoCvXnTxsKzqurkmjdC46
The `:napplet` sandbox runtime (NappletHostActivity, NappletContentServer,
NappletIpc, NappletKeyActions) now lives in a new :nappletHost Android library
that depends only on :commons + :quartz — NEVER :amethyst. So the sandbox code
is compile-time incapable of importing Amethyst.instance / LocalCache / Account,
turning the "two-process, no secrets in the sandbox" rule from a convention into
a build-graph guarantee.
- New module + NappletHostContract (Intent-extra keys + broker service FQN), so
the launcher (amethyst) and activity (module) share the launch contract with no
dependency cycle. The activity binds the broker by class name.
- Capability labels for the "what it can access" sheet are resolved by the
launcher (which has app resources) and passed in, so the module needs no
capability string resources. Host-only strings moved into the module.
- amethyst depends on :nappletHost; the broker-side (NappletBrokerService,
gateways, NappletLaunchRegistry) stays in :amethyst. Manifest declares the
activity by FQN (keeps @style/Theme.Amethyst resolvable).
- Docs updated (CLAUDE.md + security plan).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
Converts the last four Groovy build scripts to Kotlin DSL so the entire
build is consistent with the rest of the modules (commons, quartz, etc.).
- settings.gradle -> settings.gradle.kts
- build.gradle (root) -> build.gradle.kts
- amethyst/build.gradle -> amethyst/build.gradle.kts
- benchmark/build.gradle -> benchmark/build.gradle.kts
Notes:
- Switched the per-subproject spotless format from groovyGradle to
kotlinGradle now that the only gradle scripts are .gradle.kts.
- benchmark module: moved targetSdk from defaultConfig to testOptions —
AGP 9 removed targetSdk from library defaultConfig.
- Reimplemented getCurrentBranch() with ProcessBuilder (Groovy's
String.execute() isn't available in Kotlin DSL).