Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[6.6.9] - 2026-04-08¶
Fixed¶
- Restore jemalloc allocator: local benchmarking confirmed
shutdown_client()releases Rust-side memory correctly (zero growth on macOS). However, Linux glibc's per-thread arenas never return freed pages to the OS —shutdown_clientfrees the memory, but glibc retains the pages. jemalloc is required on Linux to return freed pages viamadvise(MADV_DONTNEED)
Changed¶
- Remove
gc.collect()from synchronizer: PyO3 objects do not form reference cycles; CPython's reference counting frees them immediately ondel
[6.6.8] - 2026-04-08¶
Changed¶
- Remove jemalloc: incorrectly removed — see v6.6.9
[6.6.7] - 2026-04-08¶
Fixed¶
- Complete
shutdown_client()adoption: DVM__aexit__now usesshutdown_client()instead of directclient.shutdown(). Synchronizer worker removes redundantclient.disconnect()beforeshutdown_client()—force_remove_all_relays()already disconnects
[6.6.6] - 2026-04-08¶
Fixed¶
- nostr-sdk Client memory leak:
Client.shutdown()does not release the internal event database, active subscriptions, or relay connection state on the Rust side. Newshutdown_client()helper performsunsubscribe_all()+force_remove_all_relays()+database().wipe()beforeshutdown(). Replaces all 7 directshutdown()calls across protocol, synchronizer, monitor client pool, and NIP-66 RTT. Local benchmark confirms RSS stabilizes with zero growth over 100 iterations
[6.6.5] - 2026-04-08¶
Fixed¶
- Rust FFI memory fragmentation: glibc malloc retains per-thread arenas from nostr-sdk Tokio threads and never returns pages to the OS, causing monotonic RSS growth (15 GB in 2 hours). Replaced glibc malloc with jemalloc via
LD_PRELOADin the Docker image — jemalloc actively returns freed pages viamadvise(MADV_DONTNEED)
[6.6.4] - 2026-04-08¶
Changed¶
- Schema-agnostic monitoring queries: all postgres-exporter queries now auto-discover tables, materialized views, and partitioned parents from
pg_cataloginstead of hardcoding names. Adding tables, views, or partitions requires zero monitoring changes
[6.6.3] - 2026-04-08¶
Fixed¶
- Monitoring row estimates always 0 for partitioned tables:
overviewandrow_estimatesqueries readpg_class.reltuplesfrom partitioned parent tables (event,event_relay), which PostgreSQL never auto-analyzes. Now aggregatesSUM(child.reltuples)from partitions viapg_inherits, same pattern already used bytable_sizessince v6.6.0 - Monitoring index usage and dead tuples showed individual partitions:
index_usagereported 32 separate partition entries instead of aggregated parent tables. Newdead_tuplescustom query replaces built-inpg_stat_user_tables_n_dead_tupwith partition-aggregated version. Both useCOALESCE(parent.relname, s.relname)+GROUP BYto merge partition stats under the parent name
[6.6.2] - 2026-04-08¶
Fixed¶
- Event model FFI memory leak: the
Eventdataclass retained a reference tonostr_sdk.Event(Rust FFI / PyO3) for the lifetime of each instance, invisible to Python's garbage collector. With thousands of events flowing through the synchronizer pipeline, Rust-side memory accumulated to 54 GB RSS in production within 11 hours. The v6.6.1 fix addressedClientandEventStreamcleanup but missedNostrEventreferences insideEventwrappers. The model now usesInitVarto consume the FFI object during construction and extracts all fields into Python-native types — the Rust reference is never stored on the instance - Event model
__getattr__delegation removed: replaced magic delegation to the FFI object with explicit domain fields (id,pubkey,created_at,kind,tags,content,sig), consistent withRelayandMetadatamodels._compute_db_params(self)now reads fromselffields instead of the FFI object
[6.6.1] - 2026-04-07¶
Fixed¶
- nostr-sdk Rust memory leak: Client and EventStream FFI objects allocated memory on the Rust side invisible to Python's garbage collector, causing the Synchronizer to reach 27 GB RSS. EventStream references are now explicitly deleted after consumption, clients are shut down (not just disconnected) when discarded, and
gc.collect()is called after each relay in the Synchronizer to trigger PyO3 destructors - Inconsistent client cleanup across services:
connect_relay()error paths andis_nostr_relay()useddisconnect()(closes WebSocket only) instead ofshutdown()(releases entire Rust Client). NIP-66 RTT_cleanup()had the same issue. All now useshutdown()when the client is being discarded
[6.6.0] - 2026-03-31¶
Added¶
- HASH partitioning on
eventandevent_relay: 16 partitions each, keyed byidandevent_idrespectively. Same hash key enables partition-wise joins. Configurable via Jinja2 template variable (partitions, default 16). Zero changes to CRUD functions, indexes, matviews, or Python code - LZ4 compression:
event.content,event.tags(bigbrotr), andmetadata.data(both deployments) use LZ4 instead of the defaultpglzfor faster compression at high insert rates - Partition monitoring: new
partition_distributionPrometheus metric exposing per-partition row estimates; two Grafana dashboard panels showing distribution as percentage - 7 integration tests: partition structure, distribution across partitions, event/event_relay co-location
Fixed¶
- Missing matview ownership grants:
events_replaceable_latestandevents_addressable_latestwere not granted to therefresherrole in98_grants.sh, preventingREFRESH MATERIALIZED VIEW CONCURRENTLYon fresh deployments - Table sizes metric showed 0 for partitioned tables:
pg_total_relation_sizeon a partitioned parent returns 0; now aggregates child partition sizes - LilBrotr verify script: corrected matview count (11 to 13) and refresh function count (12 to 14)
Changed¶
- postgresql.conf:
enable_partitionwise_joinandenable_partitionwise_aggregateenabled (both deployments) - Monitoring queries:
table_sizesexcludes individual partitions and aggregates their sizes under the parent;row_estimatesincludes partitioned parent tables (relkind = 'p') - Autovacuum tuning: aggressive settings (
scale_factor = 0.02,threshold = 10000) applied per leaf partition onevent_relay(cannot be set on partitioned parent)
[6.5.5] - 2026-03-31¶
Fixed¶
- Hostname validation too strict: underscores in DNS labels now accepted per RFC 2181 §11, fixing rejection of real-world relay hostnames (e.g. Coracle room subdomains like
https_2140_wtf.spaces.coracle.social) - Tor subdomains rejected:
.onionvalidation now supports virtual subdomains (RFC 7686 §2), validating the onion hash on the rightmost label and standard hostname rules on subdomain labels (e.g.relay.hash.onion) - IDN hostnames rejected: internationalized domain names are now converted to punycode via stdlib IDNA 2003 codec before RFC 3986 parsing (e.g.
wss://cafe.com->wss://xn--caf-dma.com) - Trailing-dot hostnames rejected: FQDN root labels are now stripped during normalization (e.g.
relay.com.->relay.com) - IPv6 not normalized: equivalent IPv6 representations now produce the same canonical URL via RFC 5952 compression
- Port range not validated: ports outside 1-65535 are now rejected
Changed¶
- PostgreSQL upgraded:
16-alpine→18-alpine - Tor proxy upgraded:
osminogin/tor-simple0.4.8.10→0.4.8.16(fixes SIGSEGV crashes) - postgres-exporter upgraded:
v0.16.0→v0.17.0(fixes goroutine panic during metric collection) - Docker image pinning: all 7 infrastructure images pinned with SHA256 digests for supply chain security
- Relay hostname validation: extracted
_is_valid_hostnameand_is_valid_overlay_hostnamewith strict per-network hash validation (Tor v3/v2 base32, I2P b32, Loki), replacing the previous permissive label-only check
[6.5.4] - 2026-03-28¶
Added¶
- Configurable
max_event_size: synchronizer can now drop events exceeding a configurable JSON size limit viaprocessing.max_event_size(default:None, no limit). Applied before domain model construction instream_events()
Fixed¶
- Synchronizer crash on oversized tag values: events with tag values exceeding 2048 characters caused
index row size exceeds maximum for index "idx_event_tagvalues". Now rejected atEventconstruction, consistent withRelayURL length validation
[6.5.3] - 2026-03-28¶
Added¶
- Geohash in announcement: Kind 10166 now supports an optional
gtag declaring the monitor's geographic location (NIP-52 geohash). Configurable viaAnnouncementConfig.geohash, defaults toNone(omitted)
Fixed¶
- Missing timeout tags for Geo/Net in announcement: Kind 10166 did not declare
["timeout", "geo", ...]and["timeout", "net", ...]despite both checks now accepting timeout parameters. Refactored tag generation to a data-driven loop
[6.5.2] - 2026-03-28¶
Fixed¶
- Announcement included LOCAL/UNKNOWN networks:
publish_announcement()iterated over allNetworkTypeenum members; LOCAL and UNKNOWN fell back to clearnet config, causing the Kind 10166 event to declare monitoring of invalid networks. Now usesget_enabled_networks()which only returns configured networks (clearnet, tor, i2p, loki) - Profile publishing disabled by default:
ProfileConfig.enabledwasFalsewhile announcement and relay_list wereTrue. All three publishing features now default to enabled
[6.5.1] - 2026-03-28¶
Monitor hardening, announcement frequency fix, and dependency cleanup.
Fixed¶
- NIP-66 Geo/Net timeout protection:
Nip66GeoMetadata.execute()andNip66NetMetadata.execute()had no timeout —resolve_host()andasyncio.to_thread()could hang indefinitely. Now wrapped inasyncio.wait_for()withDEFAULT_TIMEOUT(10s) fallback, and monitor passes network timeout to both checks (#402) - Announcement frequency tag: Kind 10166
["frequency"]tag was using the cycle interval (BaseServiceConfig.interval) instead of the per-relay monitoring frequency (DiscoveryConfig.interval) build_relay_list_eventmissing from__all__: Kind 10002 builder was defined and used but not exported inevent_builders.__all__
Changed¶
cryptographyfloor raised to >=46.0.6: prevents installation of 46.0.5 which has GHSA-m959-cc7f-wv43nostr-sdkfloor raised to >=0.44.0: aligns minimum with actual deployed version- Removed stale mypy ignores:
fastapiandstarletteno longer needignore_missing_imports(native stubs available)
[6.5.0] - 2026-03-27¶
Relay URL validation hardening, parse_relay_url factory, and dependency updates.
Added¶
parse_relay_url()factory function: centralizes URL-to-Relay conversion with logging for invalid URLs, wired into finder, monitor, DVM, and common utils (#400)
Changed¶
- Relay URL sanitization separated from construction: path validation (character whitelist, segment structure, length limit) extracted into
sanitize_relay_path()inutils/parsing.py, keepingRelay.__post_init__focused on structural validation - URL length limit enforced: relay URLs exceeding 2048 characters are rejected at construction, preventing PostgreSQL B-tree index overflow (
index row requires N bytes, maximum size is 8191) - CI actions pinned to commit SHAs:
actions/checkout,actions/setup-python,actions/upload-pages-artifact,actions/deploy-pages,actions/upload-artifact, andgithub/codeql-actionnow use full SHA pins instead of version tags requestsupgraded to 2.33.0: security update; unfixed Pygments CVE added totool.pip-audit.ignorewith tracking link
Fixed¶
- Synchronizer crash on oversized relay URLs: URLs with long percent-encoded paths (e.g. 29KB) caused
index row requires 29656 bytes, maximum size is 8191after 5 consecutive failures, stopping the service (#400)
[6.4.0] - 2026-03-20¶
Replaceable/addressable event matviews, synchronizer idle timeout fix, and configuration updates.
Added¶
events_replaceable_latestandevents_addressable_latestmaterialized views: track latest replaceable (Kind 0, 3, 10000–19999) and addressable (Kind 30000–39999) events per author, enabling efficient lookups for profile metadata, contact lists, and parameterized replaceable content (#395)
Changed¶
- Default concurrency limits updated: tuned
max_tasksdefaults across network configurations (#394) - Monitor discovery interval: increased from 1 hour to 4 hours to better match relay update cadence
Fixed¶
- Synchronizer idle timeout: replaced per-relay deadline with progress-based idle timeout — relays that continuously receive events no longer get prematurely disconnected (#396)
[6.3.0] - 2026-03-16¶
NIP-65 relay list publishing, default relay updates, Grafana improvements, and test cleanup.
Added¶
- Kind 10002 relay list publishing (NIP-65): Monitor publishes its relay list so clients know where to find its events. Configurable via
relay_listconfig with interval and relay override (#386) build_relay_list_event(): event builder for Kind 10002 with["r", url, "write"]tagsrelay.primal.net: added to default publishing relays for both Monitor and DVM
Changed¶
- DVM default relays:
relay.nostr.bandreplaced withrelay.mostr.pub - Grafana "Event Scan" card renamed to "Scanned": shows both rows and relays scanned
- Grafana "Total Candidates" card removed: Database Size restored to full width in overview
- Grafana "Total Candidates" renamed in Validator section: clarifies candidate vs relay counts
- Grafana "Total Relays" renamed in Monitor section: clarifies monitored relay count
Fixed¶
- RuntimeWarning in cli test: replaced async
main()with sync lambda to prevent orphan coroutine whenasyncio.runis mocked
[6.2.0] - 2026-03-16¶
Metrics overhaul, IPv4 DNS fix for Docker containers, and deployment improvements. Service gauges refactored with new inc_gauge/dec_gauge API, finder and synchronizer metrics redesigned for better observability, and Grafana dashboards updated with new panels.
Added¶
inc_gauge()anddec_gauge()methods on BaseService: native Prometheus gauge increment/decrement without intermediate variablesrelays_connectedgauge in synchronizer: tracks relays that connected successfully via WebSocket, regardless of events (later unified intorelays_seen)candidates_found_from_apiandcandidates_found_from_eventsgauges in finder: split candidate tracking by discovery source- Total Candidates card in Grafana overview: shows validator candidate queue size from postgres-exporter
- Relays/s in Processing Rate panels: both finder and synchronizer now show relay processing rate alongside rows/events rate
gai.conffor IPv4 DNS precedence: mounted in all 8 service containers to force IPv4 over IPv6 (glibc RFC 6724)backup.shin deployment folders: included in both bigbrotr and lilbrotr deployments with per-deployment database namesdata/anddumps/directories: with.gitkeepfor deployment folder completeness
Changed¶
- Synchronizer
relays_seengauge: now incremented in workerfinallyblock after WebSocket session completes, counting all visited relays (not just those with events) - Finder
relays_seengauge: moved to workerfinallyblock, counts relays scanned during event discovery - Finder dedup removed:
find_from_apino longer deduplicates in-memory;ON CONFLICT DO NOTHINGininsert_relays_as_candidateshandles it - Finder
total_relaysgauge removed: redundant withrelays_seen - Validator "Total" card renamed to "Total Candidates" in Grafana
- Monitor "Total" card renamed to "Total Relays" in Grafana
- Services use
inc_gaugeinstead ofset_gaugewith counter variables: synchronizer, finder, validator, and monitor refactored for cleaner metric tracking - Default publishing relays: replaced
relay.nostr.bandwithrelay.mostr.pub - Self-hosting guide rewritten: download deployment folder instead of cloning repo, Docker Hub images, per-deployment storage layout, multi-deployment support (Appendix C)
Fixed¶
- IPv4 DNS precedence in containers: full
gai.confwith labels + precedence table for glibc compatibility; single-line version was silently ignored backup.shPGPASSWORD: usedocker exec -eto inject password into container instead of shell-level env var
[6.1.0] - 2026-03-16¶
Performance optimizations, infrastructure fixes, and CI/CD improvements. Materialized views optimized for significantly faster refresh, Docker networking fixed for IPv6-less hosts, and image publishing migrated from GHCR to Docker Hub.
Changed¶
- Materialized view
relay_statsoptimized: replaced 2 LATERAL subqueries with CTE ROW_NUMBER() for RTT averages and JOIN onrelay_metadata_latestfor NIP-11 info;COUNT(DISTINCT)replaced withCOUNT(*);LEFT JOINreplaced withINNER JOINon event table (~4-8x speedup) (#378) - Materialized view
network_statsoptimized: relay count computed in separate CTE on relay table; events deduplicated per network before joining event table to avoid processing duplicates (~1.5-3x speedup) (#378) - Materialized view
event_daily_countsoptimized: replacedto_timestamp()+ timezone conversion with integer arithmetic for faster date grouping (~1.1-1.3x speedup) (#378) - Docker image publishing migrated from GHCR to Docker Hub: GHCR packages defaulted to private under org settings; Docker Hub provides public images with anonymous pull access (#382)
- Dependabot PRs target
develop: all 3 ecosystems (uv, docker, github-actions) now create PRs againstdevelopinstead ofmain(#381) - Streaming verify logs downgraded:
inconsistent_relay_empty_verifyandinconsistent_relay_verify_maxchanged fromwarningtodebug— expected during normal operation (#379)
Fixed¶
- Docker IPv6 networking: disabled IPv6 on Docker bridge networks; containers preferred IPv6 (RFC 6724) but hosts without IPv6 connectivity failed with "Network is unreachable" for relays behind dual-stack CDNs (#380)
event_daily_countsdate arithmetic: added explicit::INTEGERcast forBIGINT / 86400— PostgreSQL has noDATE + BIGINToperator (#381)
Security¶
- Docker base image hardened: added
apt-get upgradein both Dockerfile stages to patch OS-level vulnerabilities (CVE-2026-0861, glibc heap corruption)
Docs¶
- Self-hosting guide: added
docs/guides/self-hosting.md(#379)
Dependencies¶
- ruff 0.15.5 → 0.15.6 (#381)
- mkdocs-material 9.7.4 → 9.7.5 (#381)
- astral-sh/setup-uv v7.3.1 → v7.5.0, docker/setup-buildx-action v3.12.0 → v4.0.0, docker/build-push-action v6.19.2 → v7.0.0, docker/setup-qemu-action v3.7.0 → v4.0.0, docker/login-action v3.7.0 → v4.0.0, docker/metadata-action v5.10.0 → v6.0.0, aquasecurity/trivy-action v0.34.1 → v0.35.0, github/codeql-action v4.32.4 → v4.32.6, actions/download-artifact v8.0.0 → v8.0.1 (#381)
[6.0.0] - 2026-03-15¶
Service architecture refinements, database index overhaul, and monitoring stack rewrite. Finder service fully restructured with cursor-based pagination, database indexes redesigned for actual query patterns, and Grafana dashboards rewritten from scratch.
Changed¶
- Finder service restructured: aligned with synchronizer patterns — cursor-based event scanning with
(seen_at, event_id)composite pagination, streaming relay extraction, and per-relay deadline enforcement (#366, #367) - Finder configs tightened:
scan_sizereplacesbatch_sizefor DB queries,max_relay_timeraised to 900s,max_durationto 7200s, API sourceexpressionfield no longer has a default (#366, #367) - Monitor simplified: removed
count_relays_to_monitorquery — service now fetches all due relays once and slices in-memory;_monitoring_workerrenamed to_monitor_worker(#372) - Refresher interval: default changed from 1 hour to 24 hours; per-view duration gauge and
time.monotonic()timing added; counter metrics removed in favor of gauges (#371) - Synchronizer configs:
batch_sizeminimum raised from 1 to 100, maximum lowered from 100k to 10k;limitminimum raised from 1 to 10;_sync_workerrenamed to_synchronize_worker(#366) - Checkpoint defaults:
Checkpoint.timestampdefaults to 0;CandidateCheckpointis nowkw_only;Cursorfields default to sentinel values instead of None (#366) - Batched operations: Synchronizer and Monitor queries use shared
batched_insertandupsert_service_stateshelpers fromservices/common/queries(#366) - Grafana dashboards: complete rewrite for both bigbrotr and lilbrotr deployments with aligned service metrics and panel layout (#372)
- Postgres-exporter queries: rewritten to match new metric names and dashboard panels (#372)
Fixed¶
- Database indexes redesigned: replaced 6 redundant or misaligned indexes with 4 optimized composite indexes —
idx_event_created_at_id(DESC for timeline),idx_event_relay_relay_url_seen_at_event_id(3-column covering index for cursor pagination), removed standaloneidx_event_kind,idx_event_relay_relay_url,idx_service_state_service_name,idx_service_state_service_name_state_type; fixed candidate network partial index filter to usestate_type = 'checkpoint'(#370) - Materialized view indexes: added
idx_relay_metadata_latest_relay_urlfor relay-level lookups; addedidx_kind_counts_by_relay_relay_urlandidx_pubkey_counts_by_relay_relay_url; removed redundantidx_kind_counts_by_relay_kind(#370)
Docs¶
- README badges: improved layout and styling (#374)
- Documentation drift: comprehensive fix across configuration, services, architecture, and database pages (#366, #369)
[5.10.0] - 2026-03-13¶
API ergonomics and service architecture refinements: simplified public API for library consumers, unified async generator workers, and NIP protocol enhancements.
Added¶
- NIP-11
attributesfield: relay self-describing PascalCase attributes, emitted asWtags in Kind 30166 discovery events (#361) - NIP-66 discovery tags:
add_dns_tags,add_http_tags,add_attributes_tags,add_requirement_and_type_tagsfor richer Kind 30166 events (#361) - NIP-32 label tags: geo (
ISO-3166-1,nip66.label.city,IANA-tz) and net (IANA-asn,IANA-asnOrg) labels in discovery events (#361)
Changed¶
build_relay_discoverysimplified: from 11 primitive parameters to 3 domain objects (Relay,Nip11,Nip66) (#363)Nip66Dependenciesdefaults:keys,event_builder, andread_filterauto-populated — RTT tests work out-of-the-box (#363)- Async generator workers: Finder, Validator, Monitor, Synchronizer unified with
AsyncGeneratorworkers and streaming flush (#362) - Semaphore at worker level: finer-grained concurrency control (#362)
- Synchronizer configs: inlined sync worker, simplified configuration (#362)
extract_relays_from_response: removed default parameter, expression always explicit (#364)metadata.typecolumn: renamed frommetadata_type(#360)
Fixed¶
relay_metadata.metadata_typecolumn: restored correct column name in SQL template (#360)
5.9.0 - 2026-03-09¶
Comprehensive service review, test rewrite, and documentation overhaul. Services restructured for consistency, test suite rewritten for comprehensive coverage, and all documentation audited against current codebase.
Added¶
allow_insecureconfig: Validator, Synchronizer, and Dvm services now supportallow_insecureoption for connecting to relays with invalid TLS certificates (#353, #357)- CORS and request timeout: Api service gains configurable CORS middleware and per-request timeout (#357)
- DVM utilities module: Extracted DVM utility functions into dedicated
dvm/utils.pyfor clearer separation of concerns (#357)
Fixed¶
service_state_upsertreturn value: Stored procedure now returns affected row count instead of VOID, enabling callers to verify upsert success (#353)- Integration test failures: Corrected fixture setup and teardown for reliable test execution (#356)
Changed¶
- Service-wide refactoring: DVM service restructured with extracted utilities; Monitor configs, queries, and utils cleaned up; Validator metrics simplified to three gauges; delete-count queries standardized to CTE + fetchval pattern (#353, #357)
- Client lifecycle:
PublishClientsrenamed toClientswith simplified publish methods; client lifecycle moved to protocol module; dead protocol helpers removed (#353) - Monitor alignment:
monitor()method aligned tovalidate()pagination pattern; helpers extracted and inlined for clarity (#353) - Synchronizer simplification: Config simplified, streaming module renamed, sync algorithm extracted to utils (#352)
- Candidate queries: Insertion queries moved to common module for reuse across services (#352)
- Pydantic config models: Added Field descriptions across all config models (#353)
- Integration fixtures: Optimized with TRUNCATE instead of DROP/CREATE for faster test cycles (#356)
Removed¶
- Redundant code:
or 0guard ondelete_promoted_candidates(already returns int), dead protocol helpers in Clients class (#353)
Tests¶
- Complete test rewrite: Unit tests (~2,739) and integration tests (~216) rewritten for comprehensive coverage with consistent patterns (#355, #356)
- Integration consolidation: BigBrotr-specific integration tests merged into base test suite (#356)
Docs¶
- PROJECT_SPECIFICATION.md: Complete rewrite (984→1464 lines) based on deep codebase audit covering all 16 sections (#358)
- README.md: Full refresh with accurate architecture diagram, service table, and project data (#358)
- Documentation site: Fixed broken Material Design icons, Mermaid diagram syntax, CSS styling; corrected inaccuracies across user guide, how-to, and development docs (#358)
- Unified descriptions: Project description aligned across
pyproject.toml,__init__.py,mkdocs.yml,Dockerfile, and landing page (#358) - Stale cross-references: Fixed broken docstring references after query module relocation (#352)
5.8.0 - 2026-03-06¶
Service quality and correctness release: concurrent processing extracted into shared mixin, error handling normalized across all services, and two production bugs fixed.
Added¶
ConcurrentStreamMixin: shared mixin extracted from Finder/Validator/Monitor/Synchronizer for concurrent item processing via TaskGroup + Queue streaming (#349)
Fixed¶
- Reason validation:
BaseLogsandNip66RttMultiPhaseLogsused truthiness check (not self.reason) instead ofself.reason is None, rejecting empty string reasons withValidationError. Fixed in 4 locations (#350) - NULL tagvalue handling:
extract_relays_from_tagvalues()crashed onNoneelements in PostgreSQLtext[]arrays (None.partition(":")→AttributeError). Added type guard (#350)
Changed¶
- Worker exception boundary in Validator upgraded from
warningtoerrorlog level, witherror_typediagnostic field (#350) - Boundary comments added to all worker
except Exceptionblocks (Finder, Monitor, Synchronizer) documenting TaskGroup protection (#350) - Module-level log messages normalized to
snake_case_event: contextformat across common/utils, finder/queries, validator/queries (#350)
Removed¶
- Dead
validator/utils.pymodule (empty, no imports) (#350) - Unnecessary
.copy()in Synchronizer cursor flush (already under lock) (#350)
5.7.0 - 2026-03-03¶
Major service refactoring release: all 8 services restructured with dedicated query modules, typed domain objects, and cleanup lifecycle. Synchronizer gains forward-progression binary split algorithm for complete event synchronization.
DEPLOYMENT CHANGES¶
- ServiceState
updated_atcolumn removed: Theservice_statetable no longer has anupdated_atcolumn. SQL init scripts regenerated accordingly. Existing deployments should drop the column if present - ServiceState type consolidation:
CANDIDATEstate type replaced withCHECKPOINT;MONITORINGandPUBLICATIONtypes merged back intoCHECKPOINT. Existing rows must be migrated - API route prefix configurable: Api service now supports
route_prefixin config YAML (default/api/v1)
Added¶
- Forward-progression binary split sync algorithm (#346):
iter_relay_events()async generator in Synchronizer guarantees all events are fetched from a relay by splitting time windows when the response hits the limit. Events are yielded in ascending time order for cursor-based resumption cleanup()lifecycle hook: Abstract method onBaseServicewith implementations across all 8 services for stale state removal (orphaned cursors, exhausted candidates, obsolete checkpoints)cleanup_stale()shared query: Common query for service state cleanup by composite key- Grafana dashboards: Dedicated panels (4 stat + 1 timeseries) for Finder, Monitor, Synchronizer services
- Postgres-exporter queries:
relay_metadataandevent_relayapproximate row counts
Changed¶
- Service-specific query modules: Extracted queries from
common/queries.pyinto per-servicequeries.pymodules (finder, monitor, synchronizer, validator), decoupling query functions from service instances (#345) - Finder restructured:
find_from_events()rewritten as async generator with per-relay streaming metrics; API cooldown moved from per-source interval to sharedapi.cooldownconfig; defaultbatch_sizelowered from 1000 to 100 - Monitor simplified: Inlined
_fetch_nip11_info()closure, mergedcheck_chunks()/_check_chunk()into single method, removed redundant gauge updates - Synchronizer restructured: Client lifecycle extracted into
_fetch_and_insert()method consuming theiter_relay_events()generator; cursor tracking returns value to caller for testability; removedSyncContextin favor of direct parameters - Seeder simplified: Publicized
batched_insert, consolidated test structure - Validator queries: Use
batched_insertpattern, consolidate test file BaseService.run_forever()lifecycle logging: Centralized start/stop/error logging in base class- Checkpoint/Cursor types: Added
ApiCheckpoint,EventRelayCursortyped domain objects replacing raw dicts parse_relay_urlrenamed toparse_relay: Addedparse_relay_rowfor database row parsing
Fixed¶
- SQL template drift: Removed stale
updated_atcolumn and parameter from SQL generation templates - Docstring cross-references: Updated 6 broken references for query functions moved to service-specific modules
- Ruff lint errors: Fixed TC003, PIE804, RUF003, ERA001 across test files
- Monitor
_check_chunk: Properly re-raiseBaseException(includingCancelledError) - DVM
default_page_size: Added missing config field and fixed FFI comment
Tests¶
- Consolidated test structure: All service tests moved from subdirectories (
tests/unit/services/<service>/) to flat files (tests/unit/services/test_<service>.py) with configs, queries, utils, and service tests in a single file per service (#346) TestIterRelayEvents: 9 tests covering empty relay, single batch, binary split, nested splits, degenerate single-second window, exception propagation, partial completion on error- Updated service-level tests: Synchronizer tests mock
_fetch_and_insertinstead of oldsync_relay_events
Docs¶
- README: Removed Resources column from Container Stack table, updated NIP references
- Docstrings: Updated cross-references for cleanup lifecycle, service review, and moved query functions
DEFAULT_VIEWSreference: Replaced unresolvable mkdocs cross-reference with code literal
5.6.0 - 2026-03-01¶
Observability, code quality, and deployment alignment release.
DEPLOYMENT CHANGES¶
- Renamed
PRIVATE_KEYtoNOSTR_PRIVATE_KEYacross all services, Docker Compose files, and.env.exampletemplates. Update your.envfiles accordingly (#337) - Rewrote
.env.examplefor both bigbrotr and lilbrotr with cleaner format and sections - Aligned lilbrotr deployment with bigbrotr: unified SQL init scripts, config structure, docker-compose topology, and PGBouncer setup (#337)
Added¶
- Prometheus counters for all 8 services: relay-level counters (Finder), total_promoted (Validator), check/metadata/publishing counters (Monitor), relay sync/fail counters (Synchronizer), view counters and cycle duration gauge (Refresher), total_jobs_received (Dvm) (#331)
- Grafana dashboard rows for Refresher metrics and candidate queue gauge (#334)
Fixed¶
- Resolve ambiguous alias in
filter_new_relaysquery (#333)
Changed¶
- Extract
CatalogAccessMixinfrom Api and Dvm services to share catalog initialization logic (#335) - Extract
ApiConfigandDvmConfiginto dedicated config modules (#335) - Move
_emit_progress_gaugesintoChunkProgressMixin(#335)
Tests¶
- Restructure all service tests into uniform 1:1 directory layout (api, dvm, refresher, seeder, validator, finder, synchronizer) (#332)
- Add tests for common types and utils modules (#332)
- Add deterministic time patching to publishing tests, fix mock targets (#332)
- Add Prometheus counter tests for all services (#331)
Docs¶
- Comprehensive documentation update for all eight services (#336)
- Add CatalogAccessMixin and catalog.py to architecture docs (#336)
- Clean up ASCII diagrams in README and PROJECT_SPECIFICATION (#338)
- Fix mermaid connection pooling chart in architecture docs (#338)
5.5.0 - 2026-03-01¶
Hardening release: security fixes, configuration constraints, code quality improvements, and deployment changes across all services.
DEPLOYMENT CHANGES¶
- New
refresherdatabase role with least-privilege access (SELECT + matview ownership only), replacing the writer role previously used by the Refresher service. Requires newDB_REFRESHER_PASSWORDenv variable and updated PGBouncer/Docker Compose config (#323) - Default-closed table access: tables now default to
enabled: falseinTableConfig— only tables explicitly listed withenabled: truein service YAML configs are served by API and DVM. Existing deployments must add explicitenabled: trueentries for each exposed table (#327)
Fixed¶
- Fix 12 JSONB path errors in materialized views (
relay_stats,relay_software_counts,supported_nip_counts) that referenced top-level keys instead of the nesteddataenvelope, producing NULL columns (#323) - Prevent stack-trace exposure in API error responses:
ValueErrorexceptions inget_rowhandler now return generic message instead ofstr(e)(#329)
Changed¶
- Config constraints hardened across all 8 services:
min_length=1on non-empty strings,ge=0.1on timeouts, cross-field validators (max_delay >= initial_delay,connect_timeout <= timeout), relay URL validation on DVM,le=100upper bound onmax_consecutive_failures(#319) - Fixed-schedule cycling:
run_forever()now starts the next cycleintervalseconds after the previous started (subtracting elapsed time), preventing drift accumulation (#319) - Core defaults tuned: pool
min_size=1/max_size=5for lighter footprint; refresher interval raised to 3600s (#319) - Unified
TablePolicyandDvmTablePolicyinto singleTableConfigmodel withpricesupport for both services (#327) - Regex validation on refresher view names (
^[a-z_][a-z0-9_]*$) to prevent SQL injection (#328) - Upper bounds on monitor config fields: publishing timeout
le=300, discovery/announcement/profile intervalsle=604800(#328) - Explicit column list in
scan_eventquery replacingSELECT *(#328) - Aligned
insert_metadatato_transpose_to_columnspattern used by all other insert methods (#328) - Added
asyncio.wait_fortimeout on DNSasyncio.to_threadcall (#328) - Replaced all
contextlib.suppress(Exception)withtry/except+ log DEBUG across cleanup paths (dvm, protocol, transport, rtt) (#328) - Standardised
logs["reason"]assignment and log output across NIP-66 modules (ssl, http, geo, dns) (#328) - Added
CatalogError.client_messageattribute for controlled access to client-safe error strings (#329)
5.4.0 - 2026-02-28¶
Minor release: two new services (Api and Dvm) bring read-only HTTP and Nostr interfaces to the database, backed by a shared schema-driven Catalog query builder. Major refactoring of service queries and state types consolidates raw-dict handling into typed domain objects.
DEPLOYMENT CHANGES¶
- New services: Api (port 8080) and Dvm containers added to both
bigbrotrandlilbrotrdocker-compose stacks. Requires updated.envwithNOSTR_NSECfor Dvm - New dependencies:
fastapi>=0.115,uvicorn>=0.34,nostr-sdk>=0.39added topyproject.toml - Service configs: New
config/services/api.yamlandconfig/services/dvm.yamlin both deployments - Prometheus: Scrape targets added for Api (
:8090/metrics) and Dvm (:8091/metrics) - State type migration:
CHECKPOINTstate type split intoMONITORINGandPUBLICATION. Existingservice_staterows withstate_type = 'CHECKPOINT'must be migrated or deleted before deploying
Added¶
- Api REST service (#315): FastAPI-based read-only HTTP server with auto-generated paginated endpoints for all tables, views, and materialized views. Per-table access control via
TableConfig, configurable CORS, request statistics logging, and Prometheus metrics - Dvm NIP-90 service (#315): Data Vending Machine listening for kind 5050 job requests on configured Nostr relays, executing read-only queries via the Catalog, and publishing kind 6050 result events. Per-table pricing via
TableConfigwith bid/payment-required mechanism - Catalog query builder (#315): Schema introspection engine discovering tables, views, and materialized views at runtime. Builds parameterized queries with whitelist-by-construction validation. Shared by Api and Dvm services
CatalogError(#315): Client-safe exception preventing internal database error details from leaking to API/DVM consumersCandidatedataclass (#314): Typed domain object inservices/common/types.pyreplacing raw dicts in the validation pipelineEventRelayCursorandEventCursor(#314): Cursor dataclasses with invariant enforcement for deterministic pagination in Finder and Synchronizerscan_event_relayandscan_eventqueries (#314): Cursor-typed query functions replacingfetch_event_tagvalues- Batch-safe query wrappers (#314):
insert_relay_metadata,insert_event_relays,upsert_service_stateswith automatic chunk splitting
Refactored¶
- Service state types (#314): Split
CHECKPOINTintoMONITORING(health check markers) andPUBLICATION(event publish markers) for explicit, independently queryable semantics - Query functions return domain objects (#314):
fetch_all_relays,fetch_candidates,fetch_relays_to_monitornow return typed objects (Relay,Candidate,ServiceState) instead of raw dicts - Removed
from_db_params(#314): Eliminated from all 6 models (Relay, Event, Metadata, ServiceState, EventRelay, RelayMetadata). Consolidatedmodels_from_db_params+models_from_dictintosafe_parseutility - Centralized batch splitting (#314): Services pass arbitrarily large lists to query wrappers; chunk management moved into
queries.pyvia_batched_inserthelper - Finder cursor (#314): Replaced single-field
seen_atcursor with composite(seen_at, event_id)for deterministic pagination when multiple rows share the same timestamp - Query renames (#314):
get_all_relays->fetch_all_relays,filter_new_relay_urls->filter_new_relays,fetch_candidate_chunk->fetch_candidates,insert_candidates->insert_relays_as_candidates,cleanup_stale_state->cleanup_service_state get_enabled_networks()(#314): ReturnsNetworkTypedirectly, eliminating manual casts at 4 call sites
Fixed¶
- Api
get_rowerror handling: CatchCatalogErroralongsideValueErrorin the single-row lookup handler. Previously, invalid primary key values returned 500 instead of 400 - Catalog error sanitization (#315): asyncpg error messages sanitized at the Catalog layer;
CatalogErrorwraps internal exceptions to prevent leaking database internals promote_candidatesdocstring (#315): Corrected parameter documentation and added error logging_publish_if_duedead timeout (#315): Removed unused default timeout parameter- Synchronizer cursor validation (#315):
NostrSdkErrorcaught and cursor parsing hardened against corrupt data - Brotr facade access (#315): Replaced direct pool access with facade method
- LilBrotr postgres-exporter (#315): Added missing
LABELdefinitions - Monitor retry YAML key (#315): Renamed to match Pydantic field name
scan_event_relaycolumns (#315): Explicit column enumeration replacingSELECT *conftestmock fixture: Corrected staleprocedurekey tocleanupinmock_brotrTimeoutsConfig- Seeder error handling (#314): Added try/except around DB calls that previously had no graceful error handling
- Synchronizer silent failures (#314): Replaced
contextlib.suppress(Exception)with explicit try/except that logs at DEBUG level - Monitor log field (#314): Renamed
attempts->total_attemptsfor consistency with retry-related log conventions delete_orphan_cursors(#314): Changed fromNOT INtoNOT EXISTSfor better PostgreSQL performance with large result sets
Documentation¶
- 8-service architecture: Updated all service counts (6 -> 8), architecture diagrams, service tables, and interaction maps across README, PROJECT_SPECIFICATION, CLAUDE.md, guides, and MkDocs
- Source docstrings: Fixed cross-references (
utils.transport->utils.protocol), exception types (ValueError->CatalogError), field references (log_level->metrics), consumer lists, and column names across 11 source files - Deployment configs: LilBrotr composite index upgrade, missing metrics port variables, singular table names in README, proxy container clarification, postgres-exporter network enum cleanup, bigbrotr role verification in
99_verify.sql - Removed
aiomultiprocess: Dead runtime dependency never imported in source code
5.3.1 - 2026-02-26¶
Patch release: one bug fix and comprehensive documentation overhaul. Architecture terminology corrected across all documentation, README rewritten, project specification added.
Fixed¶
_transpose_to_columns(#311): Usestrict=Trueinzip()to catch row length mismatches that bypass validation. Closes #242
Documentation¶
- Architecture terminology (#312): Replaced "pipeline" terminology with "independent services" across all documentation, source docstrings, and deployment configs. Renamed
pipeline.mdtoservices.md. Fixed service count (5 -> 6), architecture tiers (5 -> 4), test counts, query function count (14 -> 15), Prometheus ports, alert rules (4 -> 6), and DAG diagram edges - README: Rewritten with comprehensive project overview reflecting current architecture
- PROJECT_SPECIFICATION.md: Added full system specification with database schema, service descriptions, deployment variants, and architecture diagrams
- Removed stale REVIEW.md: Cleanup of previous review artifact
5.3.0 - 2026-02-25¶
Comprehensive codebase hardening: 46 commits across 44 PRs. Finder refactored from kind-specific event parsing to kind-agnostic tagvalues scanning with JMESPath-based configurable API extraction. Extensive bug fixes across all layers (models, core, NIPs, services, deployments), dead code removal, and improved error handling.
DEPLOYMENT CHANGES¶
- SQL template sync: LilBrotr deployment SQL files updated for batched orphan_event_delete, network_stats direct-join rewrite, supported_nip_counts cast guard, Synchronizer indexes, and corrected file headers. Requires fresh
initdbor manual execution of updated SQL files (04, 05, 06, 07, 08) - Grafana depends_on: Added
service_healthycondition to Grafana container's Prometheus dependency
Added¶
- JMESPath API extraction (#309):
ApiSourceConfig.jmespathfield enables configurable relay URL extraction from diverse JSON API response formats ([*],data.relays,[*].url,keys(@)) jmespathdependency:jmespath>=1.0.0(runtime) andtypes-jmespath>=1.0.0(dev type stubs)cleanup_stale_statequery: Removes state records for relays no longer in the database, used by Finder, Synchronizer, and Monitor (#309)- Finder metrics:
relays_failedgauge andtotal_api_relays_foundcounter (#309) - Synchronizer network filtering:
fetch_relays()now filters by enabled networks, avoiding unnecessary relay loading (#309) - NIP-11 shared session:
Nip11.fetch_info()accepts an optional sharedaiohttp.ClientSessionfor connection pooling (#307) - Event.from_db_params() roundtrip test (#295)
Refactored¶
- Finder event scanning (#309): Replaced kind-specific parsing (r-tags, kind 2 content, kind 3 JSON) with kind-agnostic
tagvaluesscanning viaparse_relay_url. RemovedEventsConfig.kindsfield. Renamedget_events_with_relay_urls→fetch_event_tagvalues(simpler query selecting onlytagvalues+seen_at) - Logger truncation (#300): Consolidated duplicate value truncation logic to single point in
format_kv_pairs() - Dead code removal: Removed unused
fetch_relay_eventsfunction (#280), unused dict-basedCertificateExtractormethods (#306), hardcodedskipped_eventsfrom Synchronizer (#297), unusedstagger_delayconfig (#272), unnecessarygetattrfallbacks inrun_forever()(#275)
Fixed¶
- Relay equality (#287, #289): Excluded
raw_urlfromRelay.__eq__comparison, preventing false negatives when the same URL is stored with different original casing - SSL validation (#268): Made conditional on successful certificate extraction, preventing false validation errors
_persist_scan_chunk(#309): No longer re-raises non-fatal cursor update errorsupsert_service_state(#305): Returns confirmed count from database instead of input count_get_publish_relays(#285): Usesis not Noneinstead of truthiness check_publish_if_due(#279): Usesinttimestamp consistentlydeep_freeze(#284): Returnstupleinstead oflistfor true immutabilityparse_seed_file(#292): Widened exception handling to catch all I/O errorsmodels_from_dict(#293): CatchesKeyErrorfor missing keysSyncCycleCounters(#298, #308): Removed duplicate initialization and staleskipped_eventsreference- NIP-11
supported_nips(#291): Deduplicates NIP values during parsing - NIP-66 log classes (#270): Unified reason validation to falsy check
- NIP-66 DNS (#269): Catches
tldextractexceptions - NIP-66 GeoIP (#278): Removed dead exception catch in
_geo()method _NostrSdkStderrFilter(#299): Added max line safety valveNetworksConfig(#294): Logs warning on fallback to clearnetparse_fields(#277): Cached dispatch dict withlru_cache- One-shot mode (#276): Wrapped in service context manager
- Refresher (#271): Narrowed exception catch to database errors only
network_stats(#274): Computes unique counts at network level with direct joinsupported_nip_counts(#273): Guards against non-integer NIP valuesorphan_event_delete(#283): Added batching for large deletions- mypy (#302, #304): Removed blanket
ignore_errorsfrom__main__andasyncpgfrom ignore list - Pre-commit mypy hook (#303): Added missing runtime dependencies
- LilBrotr SQL (#282, #286): Fixed file headers and added missing Synchronizer indexes
- Grafana (#281): Added health check condition to
depends_on - SQL templates: Synced with all deployment fixes (batching, joins, cast guards, headers)
Tests¶
- Relaxed exact mock call count assertions in Event tests (#296)
- Added 170+ new tests for Finder (JMESPath, stale cursors, metrics), Synchronizer (network filter, stale cursors), queries (
cleanup_stale_state,fetch_event_tagvalues), and transport
Documentation¶
- Removed incorrect case-insensitive claim from procedure name docstring (#288)
- Documented
_StderrSuppressorglobal scope trade-off (#301) - Updated service count from five to six in common package docstring (#290)
5.2.0 - 2026-02-24¶
Refresher service, rich analytics materialized views, shared deployment SQL, concurrent Finder event scanning, and legacy brotr deployment removal. 8 commits across 6 PRs.
DEPLOYMENT CHANGES¶
These changes require deployment updates (Docker Compose, SQL schema, monitoring). No Python API breaking changes.
- Refresher service container: New
refreshercontainer added to bothdocker-compose.yamlfiles. RequiresDB_WRITER_PASSWORDenvironment variable andconfig/services/refresher.yamlconfig file deployments/brotr/removed: The legacy monolithic deployment has been deleted. Usebigbrotrorlilbrotrinstead- Materialized views shared: All 11 materialized views, 12 refresh functions, and matview indexes are now present in both bigbrotr and lilbrotr (previously only bigbrotr had statistical views). LilBrotr deployments require fresh
initdbor manual06_materialized_views.sql+07_functions_refresh.sql+08_indexes.sqlexecution - Config class renames:
PoolLimitsConfig→LimitsConfig,PoolTimeoutsConfig→TimeoutsConfig,PoolRetryConfig→RetryConfig,BrotrTimeoutsConfig→TimeoutsConfig. Old names are removed (no backward compatibility shim)
Added¶
- Refresher service (
services/refresher/): Periodically refreshes all 11 materialized views in 3-level dependency order (relay_metadata_latest → independent stats → dependent stats). Per-view logging, timing, error isolation, and Prometheus gauges (views_refreshed,views_failed) (#207, #208) - 4 new materialized views:
network_stats(per-network relay/event/pubkey/kind counts),relay_software_counts(NIP-11 software distribution),supported_nip_counts(NIP support frequency),event_daily_counts(daily event volume time series) (#206) - Concurrent Finder event scanning:
find_from_events()usesasyncio.TaskGroup+asyncio.Semaphorefor bounded concurrent relay scanning, replacing the sequentialforloop. New config fieldConcurrencyConfig.max_parallel_events(default: 10) (#203) - Refresher Docker containers: Service definitions and Prometheus scrape jobs in both bigbrotr and lilbrotr
docker-compose.yamlandprometheus.yaml(#208) - 389 Refresher unit tests: Full coverage of service lifecycle, dependency ordering, error handling, metrics, and configuration validation (#207)
- Comprehensive codebase review (
REVIEW.md): Full project audit covering all source files, tests, deployment configs, SQL, and documentation. 58 findings documented with exact locations and solutions (#208)
Refactored¶
- Materialized views redesigned (#206): 6 existing BigBrotr stat matviews enriched with
events_per_day,unique_kinds, NIP-11 info columns, NIP-01 category labels, andHAVING >= 2anti-explosion filter.all_statistics_refresh()updated with 3-level dependency ordering - Deployment SQL shared (#205, #207): Moved matview definitions, refresh functions, and indexes from bigbrotr-specific Jinja2 overrides to shared base templates. Both bigbrotr and lilbrotr now generate identical matview SQL from the same base blocks (
extra_materialized_views,extra_refresh_functions,extra_matview_indexes) - Deployment base restructured (#205):
_templaterenamed tobrotras the reference implementation. Integration tests reorganized by deployment:base/(61 tests),bigbrotr/(25 tests),lilbrotr/(8 tests) - Config naming cleaned up (#204): Removed redundant prefixes from 4 config classes. Slimmed down
__init__.pyexports to public API only, removing 45+ dead re-exports across 6 packages. Removed ~110 decorative comments while preserving ~65 meaningful comments deployments/brotr/removed (#207): 35 files deleted after matview consolidation made brotr and bigbrotr generate identical SQL.generate_sql.pyupdated to produce 20 files instead of 30
Fixed¶
BaseService.from_yaml()return type (#203): Factory methodsfrom_yaml(),from_dict(), and__aenter__()now returnSelfinstead of"BaseService[ConfigT]", giving type checkers the correct subclass return typeConcurrencyConfig.max_parallelrenamed (#203): Renamed tomax_parallel_apifor clarity, distinguishing API-based from event-based concurrency
Documentation¶
- MkDocs cross-references fixed for removed re-exports (#204)
- README, database.md, architecture.md, configuration.md, custom-deployment.md, sql-templates.md, and new-service.md updated for Refresher service, shared matviews, and deployment changes
- PostgreSQL guide updated for 25 stored functions (was 21)
5.1.0 - 2026-02-23¶
Major infrastructure and architecture release: services restructured into packages with clear public APIs, PostgreSQL role isolation with PgBouncer dual-pool routing, full monitoring stack (postgres-exporter + Grafana dashboards), asyncpg/PgBouncer compatibility hardening, and comprehensive audit remediation across all layers. 152 commits across 22 PRs.
DEPLOYMENT CHANGES¶
These changes require deployment updates (env vars, Docker Compose, PostgreSQL schema). No Python API breaking changes.
DB_PASSWORDrenamed toDB_ADMIN_PASSWORD: Update.envfiles and Docker Compose environment sections- PostgreSQL role isolation: New
*_writerand*_readerroles replace single-role access. Requires freshinitdbor manual98_grants.shexecution - PgBouncer dual-pool: Separate
[bigbrotr_writer]and[bigbrotr_reader]pool sections. Updatepgbouncer.inianduserlist.txt metadata.metadata_typecolumn renamed totype: SQL schema change across all deployments. Requires freshinitdbor manual migrationpg_stat_statementsextension: Now enabled in all deployments. Requiresshared_preload_librariesinpostgresql.conf
Added¶
- PostgreSQL role isolation (
98_grants.sh): Separate writer (DML + EXECUTE) and reader (SELECT + EXECUTE +pg_monitor) roles with principle of least privilege (#197) - PgBouncer dual-pool routing (
pgbouncer.ini): Writer and reader pools with independent connection limits, routed by PostgreSQL role (#197) - Per-service pool overrides (
BaseServiceConfig): Services can overridemin_size,max_size, and timeouts in their YAML config (#197) - Postgres-exporter (
monitoring/postgres-exporter/): Custom SQL queries for materialized view age, event ingestion rates, relay counts, service state health (#196) - Grafana dashboard panels: 35+ panels covering PostgreSQL internals, relay statistics, event pipeline, service health across all deployments (#196, #198)
- Prometheus metrics for Finder and Synchronizer: Relay discovery counts, event fetch counters, cursor synchronization progress (#198)
pg_stat_statements: Enabled across all deployments (template, bigbrotr, lilbrotr) for query performance analysis (#199)- Template schema completeness: Views, materialized views, refresh functions, and full indexes now included in
_templatedeployment (#199) - BaseNip abstract hierarchy (
nips/base.py): UniformBaseNip→BaseNipMetadata→BaseNipDependenciesclass hierarchy for all NIP implementations (#174) - Lazy imports (
bigbrotr/__init__.py): Deferred import system for faster CLI startup (#162) - Integration tests: Full stored procedure coverage with testcontainers PostgreSQL (#195)
- SQL generation tooling: Jinja2 templates (
tools/templates/sql/) with CI drift check viagenerate_sql.py --check(#162) - Bounded file download (
utils/http.py):download_file()with configurable size cap for GeoLite2 and NIP-11 responses (#174) - PgBouncer
query_timeout: 300s server-side safety net for abandoned queries (#199)
Refactored¶
- Services package restructure (#194): All 5 services converted from single modules to packages with explicit public APIs:
- Each service now exposes granular methods (
seed(),find_from_events(),validate(),fetch_relays(), etc.) - Extracted
GeoReaders,NetworkSemaphores,ChunkProgressas standalone classes - Split
transport.pyintotransport.py(low-level WebSocket) +protocol.py(Nostr protocol) - Extracted
event_builders.pyfrom Monitor to NIP layer - Standardized sub-config naming and field names across all services
- NIP layer hardening (#174):
BaseNipMetadatanaming consistency, NIP-66execute()methods return graceful failures instead of raising, response size limits on Finder API and NIP-11 info fetch - Brotr simplification (#192): Removed unused Pool/Brotr methods, aligned
ServiceStatedb_params pattern, cleaned up candidate lifecycle - Model field alignment (#163):
ServiceStatepromoted to DbParams pattern, SQL columns and stored procedure parameters aligned with Python models - Schema cleanup (#175):
metadata.metadata_typecolumn renamed totype, PgBouncer config improvements - Build system (#170): Migrated from pip/setuptools to uv with
uv.lock - Makefile (#169): Redesigned for consistency with
pyproject.tomland CI workflows - Documentation (#164, #165): Consolidated CONTRIBUTING.md, fixed stale docstring references, restructured docs/ into mkdocs-material sections
Fixed¶
- asyncpg prepared statement caching (#199): Disabled (
statement_cache_size=0) for PgBouncer transaction mode compatibility — previously caused silentprepared statement does not existerrors statement_timeoutineffective (#199): Default changed to 0 because PgBouncer'signore_startup_parametersstrips it before it reaches PostgreSQL- PgBouncer
userlist.txtpermissions (#199):chmod 600after creation to prevent credential exposure - Health check
start_period(#199): PostgreSQL 10s→30s, PgBouncer 15s→20s to accommodate init scripts - WAL metrics (#199):
GRANT pg_monitorto reader role replaces--no-collector.walworkaround, re-enabling full WAL collector - Reader role permissions (#199):
GRANT EXECUTEon all functions +ALTER DEFAULT PRIVILEGESfor future functions - Chunked transfer-encoding (#196): HTTP response handling in NIP-11 info fetch
- Monitor completion percentage (#196): Correct handling on empty batches (division by zero)
- NIP-42 detection (#192): Standardized
auth-requiredprefix per NIP-01 - Publisher state type (#168): Use
CHECKPOINTstate type for publisher timestamps inMonitorPublisherMixin - Config-driven timeouts (#167): Graceful shutdown waits and per-network semaphores now configurable
- Docker image size (#171, #172): Removed system Python packages and
site-packagesto resolve Trivy findings - Shell injection (#162): Hardened
release.ymlagainst untrusted input in shell commands - SQL hardening (#177, #178, #191): Cleanup functions batched, views improved, redundant indexes removed, SSL validation tightened
- Models and core validation (#191): Empty reason string rejection, NIP-11 parsing deduplication, fail-fast validation improvements
- Dockerfile HEALTHCHECK (#162): Corrected port and switched to
console_scriptsentrypoint
Documentation¶
- MkDocs Material site restructured with auto-generated API reference
- Database and architecture docs updated for role isolation and schema changes
- Cross-references and broken links fixed after services restructure
- All deployment README and CI workflow documentation updated
5.0.1 - 2026-02-10¶
CI/CD infrastructure hardening, automated documentation site, and dependency maintenance.
Added¶
- MkDocs Material documentation site (
mkdocs.yml,docs/reference/): Auto-generated API reference via mkdocstrings, deployed to GitHub Pages on push to main - Release pipeline (
.github/workflows/release.yml): 6-job DAG -- validate, build-python, build-docker, publish-pypi (OIDC), publish-ghcr (semver tags), release (GitHub Release with SBOM artifacts) - Documentation workflow (
.github/workflows/docs.yml): Automatic rebuild on docs/source/config/changelog changes - CODEOWNERS (
.github/CODEOWNERS):@BigBrotr/maintainersfor all paths
Changed¶
- CI pipeline overhauled (
.github/workflows/ci.yml): Renamedtest→unit-test, addedintegration-testjob,timeout-minuteson all jobs,buildadded toci-successgate with skipped-allowed logic, Docker cache scoped per deployment - Dependabot grouping (
.github/dependabot.yml):github-actions-allgroup for major/minor/patch updates - Makefile: Renamed
test→test-unit, addedtest-integration,docs,docs-serve,buildtargets - GitHub Actions pinned by SHA with
# vX.Y.Zcomments for Dependabot compatibility - Dependencies updated: upload-artifact v4→v6, download-artifact v4→v7, codeql-action and codecov-action SHA updates
Fixed¶
- Codecov upload on Dependabot PRs: Added
github.actor != 'dependabot[bot]'condition to skip upload when secrets are unavailable, unblocking all automated dependency PRs - Docker GHA cache collision: Added
scope=${{ matrix.deployment }}to prevent cache eviction between bigbrotr/lilbrotr matrix jobs - docs.yml missing CHANGELOG.md trigger: Root
CHANGELOG.mdincluded via pymdownx.snippets but wasn't in the paths filter - release.yml coverage overhead: Removed unused
--covflags from validate job
Documentation¶
- MkDocs site: Home page, 5 user guide sections (Architecture, Configuration, Database, Deployment, Development), Changelog, and 5 API reference modules (Core, Models, NIPs, Utils, Services)
- README.md: Updated CI/CD pipeline table and make targets
- docs/DEVELOPMENT.md: Updated make targets and test commands
- CONTRIBUTING.md: Migrated all commands to
maketargets, added docs section - PULL_REQUEST_TEMPLATE.md: Added integration test checkbox
5.0.0 - 2026-02-09¶
Major quality and operational hardening release: exception hierarchy replaces all bare catches, Monitor split into 3 modules, DAG violation fixed, Docker infrastructure hardened with real healthchecks and network segmentation, CI/CD expanded with security scanning, 4 Prometheus alerting rules, and complete documentation rewrite.
BREAKING CHANGES¶
- Exception hierarchy: All
except Exceptionblocks replaced with specific catches frombigbrotr.core.exceptions(BigBrotrError,ConfigurationError,DatabaseError,ConnectionPoolError,QueryError,ConnectivityError,RelayTimeoutError,RelaySSLError,ProtocolError,PublishingError) - Monitor split into 3 modules:
monitor.py(~1,000 lines orchestration) +monitor_publisher.py(~230 lines Nostr broadcasting) +monitor_tags.py(~280 lines NIP-66 tag building). ImportMonitorPublisherMixinandMonitorTagsMixinseparately. - ServiceState moved:
ServiceState,ServiceStateKey,StateType,EventKindmoved fromservices/common/constantstomodels/service_state.py(re-exported from constants for backward compatibility) - Per-deployment Dockerfiles deleted: Single parametric
deployments/DockerfilewithARG DEPLOYMENTreplaces 3 separate Dockerfiles - Docker networks: Flat bridge network replaced with
data-network+monitoring-networksegmentation - SQL functions: All 22 stored functions now require
SECURITY INVOKER
Added¶
- Exception hierarchy (
core/exceptions.py): 10-class typed exception tree replacing bareexcept Exceptionacross 15 files. Transient errors (ConnectionPoolError) distinguished from permanent (QueryError) for retry logic - Prometheus alerting rules (
deployments/bigbrotr/monitoring/prometheus/rules/alerts.yml): 4 alerts -- ServiceDown (critical, 5m), HighFailureRate (warning, 0.1/s over 5m), PoolExhausted (critical, 2m), DatabaseSlow (warning, p99 > 5s) - Makefile: 11 targets --
lint,format,typecheck,test,test-fast,coverage,ci,docker-build,docker-up,docker-down,clean. Parametric Docker targets viaDEPLOYMENT=variable - CI security scanning:
pip-audit --strictfor dependency vulnerabilities, Trivy image scanning (CRITICAL/HIGH severity), CodeQL static analysis (.github/workflows/codeql.yml), Dependabot for pip/docker/github-actions (.github/dependabot.yml) - Shared test fixtures (
tests/fixtures/relays.py): Canonical relay fixtures (relay_clearnet,relay_tor,relay_i2p,relay_loki,relay_ipv6,relay_clearnet_with_port,relay_clearnet_ws) registered as pytest plugin viapytest_plugins - Pre-commit hooks: Added
hadolint(Dockerfile linting),markdownlint(with--fix),sqlfluff-fix(PostgreSQL SQL formatting) - Global test timeout:
--timeout=120in pytest addopts prevents hanging tests
Refactored¶
- Monitor service split: Single 1,400+ line
monitor.pydecomposed into 3 modules using mixin pattern --MonitorPublisherMixin(event broadcasting) andMonitorTagsMixin(NIP-66 tag building) mixed intoMonitorclass - ServiceState extraction:
ServiceState,ServiceStateKey,StateType,EventKindmoved fromservices/common/constants.pytomodels/service_state.py, fixing DAG violation wherecore/brotr.pyhad aTYPE_CHECKINGimport from services layer - Single parametric Dockerfile:
deployments/DockerfilewithARG DEPLOYMENT=bigbrotrreplaces 3 per-deployment Dockerfiles. Multi-stage build (builder -> production), non-root execution (UID 1000),tinias PID 1 for proper signal handling - Docker healthchecks: Fake
/proc/1/cmdlinechecks replaced with real service probes (pg_isreadyfor PostgreSQL/PGBouncer,curl http://localhost:<port>/metricsfor application services) - Docker network segmentation: Single flat bridge split into
data-network(postgres, pgbouncer, tor, services) andmonitoring-network(prometheus, grafana, services) - Docker resource limits: CPU and memory limits on all containers (postgres 2 CPU/2 GB, services 1 CPU/512 MB, pgbouncer 0.5 CPU/256 MB)
- SQL hardening:
SECURITY INVOKERon all 22 stored functions,DISTINCT ONqueries paired withORDER BYfor deterministic results, batched cleanup operations
Changed¶
- pyproject.toml: Version
4.0.0->5.0.0; coveragefail_under = 80(branch coverage);--timeout=120in pytest addopts;pytest-timeoutadded to dev dependencies - Logger JSON format:
_format_json()now emitstimestamp(ISO 8601),level,servicefields for cloud log aggregation compatibility - Metrics config:
MetricsConfigwithenabled,port,host,pathfields;hostdefaults to"127.0.0.1"(use"0.0.0.0"in containers) - Docker Compose:
stop_grace_period: 60sandSTOPSIGNAL SIGTERMfor graceful shutdown; JSON-file logging driver with size rotation - CI pipeline: Single coverage run for all Python versions (removed duplicate non-coverage step for 3.11); Trivy scan on both BigBrotr and LilBrotr images; Python 3.14 with
allow-prereleases: true
Fixed¶
- DAG violation: Removed
TYPE_CHECKINGimport ofServiceStatefrom services layer incore/brotr.py - Metadata column naming:
MetadataDbParamsconsistently usespayloadfield matching SQL columnmetadata.payload - Grafana dashboards: Set
editable: falseon provisioned dashboards to prevent drift
Documentation¶
- Complete docs/ rewrite: All 6 documentation files rewritten from scratch for v5.0.0 accuracy:
docs/ARCHITECTURE.md(~970 lines): Diamond DAG, all 5 layers, every service flow, data architecture, design patternsdocs/CONFIGURATION.md(~760 lines): Complete YAML reference for all services with Pydantic models, CLI args, env varsdocs/DATABASE.md(~620 lines): All 6 tables, 22 stored functions, 7 materialized views, complete index referencedocs/DEPLOYMENT.md(~515 lines): Docker Compose and manual deployment, monitoring stack, backup/recoverydocs/DEVELOPMENT.md(~460 lines): Setup, testing, code quality, CI/CD pipeline, contribution guidedocs/README.md(~33 lines): Documentation index with quick links- README.md (~460 lines): Complete project overview rewritten with verified data from codebase
- Removed obsolete docs:
OVERVIEW.md(redundant with README),TECHNICAL.md(redundant with ARCHITECTURE),V5_PLAN.md(internal planning) - CLAUDE.md: Updated for v5.0.0 architecture, exception hierarchy, monitor split, ServiceState location
4.0.0 - 2026-02-09¶
Major architectural restructuring: all code moved under bigbrotr namespace package with diamond DAG dependency graph. Nine design problems resolved. No functional or behavioral changes — pure structural refactor.
BREAKING CHANGES¶
- All imports changed:
from core.X/from models.X/from services.X/from utils.X→from bigbrotr.core.X/from bigbrotr.models.X/from bigbrotr.services.X/from bigbrotr.utils.X - CLI entry point changed:
python -m services <name>→python -m bigbrotr <name>(orbigbrotr <name>via console script) - Deployment directories renamed:
implementations/→deployments/ - Config directories renamed:
yaml/core/brotr.yaml→config/brotr.yaml(flattened);yaml/services/→config/services/ - NIP models extracted:
from models.nips.nip11 import Nip11→from bigbrotr.nips.nip11 import Nip11 - YAML loader moved:
from utils.yaml import load_yaml→from bigbrotr.core.yaml import load_yaml - Dependency files removed:
requirements.txt/requirements-dev.txtdeleted; usepip install -e .orpip install -e ".[dev]"
Refactored¶
- Namespace package: All source code moved under
src/bigbrotr/to eliminate pip namespace collisions from generic top-level names (core,models,services,utils) - Diamond DAG architecture: Five-layer dependency graph (
services → {core, nips, utils} → models) replacing the previous linear four-layer stack - NIP extraction:
models/nips/(18 files with I/O logic: HTTP, DNS, SSL, WebSocket, GeoIP) extracted tobigbrotr/nips/as a separate package, restoring models layer purity - YAML loader:
utils/yaml.pymoved tocore/yaml.py(resolving upward layer dependency — only consumers were in core) - CLI decoupled:
services/__main__.pymoved tobigbrotr/__main__.pywith synccli()wrapper for console_scripts entry point - Monitoring directories merged:
grafana/+prometheus/→monitoring/grafana/+monitoring/prometheus/in each deployment - Root cleanup: Deleted
alembic.ini,migrations/,requirements*.txt,requirements*.in; movedgenerate_sql.pyandtemplates/totools/ - Deleted
src/__init__.py: Removed the 107-line file with 36 re-exports that violated the src-layout pattern
Changed¶
- pyproject.toml: Version
3.0.4→4.0.0;known-first-party = ["bigbrotr"];include = ["bigbrotr*"];files = ["src/bigbrotr"](mypy);source = ["src/bigbrotr"](coverage); added[project.scripts] bigbrotr = "bigbrotr.__main__:cli" - 100+ source files: Moved under
src/bigbrotr/with updated imports - 40+ test files: Updated with
bigbrotr-prefixed imports and ~100 mock patch targets rewritten - 3 Dockerfiles + 3 docker-compose files: Updated paths, commands, and volume mounts
- CI workflow: Updated for new deployment and source paths
- 12 deployment YAML configs: Updated
_template/yaml/→_template/config/in comments - 6 service module docstrings: Updated example paths from
yaml/toconfig/
Fixed¶
- Stale NIP class references (pre-existing, exposed by restructuring):
Nip11FetchMetadata→Nip11InfoMetadata(renamed in v3.1.0 but__init__.pynot updated; fully completed in v5.1.0)Nip66RttLogs→Nip66RttMultiPhaseLogsRttDependencies→Nip66RttDependenciesNip66TestFlags→Nip66Selection+Nip66Options
Added¶
- Console script:
bigbrotrcommand via[project.scripts]in pyproject.toml - Integration test infrastructure:
tests/integration/conftest.pywith testcontainers-based ephemeral PostgreSQL;tests/integration/test_database_roundtrip.py - SQL generation tooling:
tools/generate_sql.py+tools/templates/sql/(Jinja2 templates for deployment SQL files)
Documentation¶
- README.md: Version badge, five-layer diamond DAG architecture diagram, updated all paths/commands/project structure tree, test count → 1896
- All docs/*.md: Updated for new paths, imports, and architecture (ARCHITECTURE, CONFIGURATION, DATABASE, DEPLOYMENT, DEVELOPMENT, OVERVIEW, TECHNICAL)
- CLAUDE.md: Rewritten for bigbrotr namespace and diamond DAG architecture
- CONTRIBUTING.md: Updated paths and install commands
- Agent knowledge base: All 7
.claude/agents/bigbrotr-expert/files updated
3.0.4 - 2026-02-07¶
Architecture refinement release: domain logic extracted from core to services/common/, three-tier architecture formalized, and comprehensive test and documentation alignment.
Refactored¶
services/common/package: Extracted domain queries, constants, and mixins fromcore/into a new shared service infrastructure package with three stable modules:constants.py:ServiceNameandDataTypeStrEnum classes replacing all hardcoded service/data-type stringsmixins.py:BatchProgressMixinandNetworkSemaphoreMixin(moved fromcore/service.pyandutils/progress.py)queries.py: 13 domain SQL query functions parameterized with enum values (moved fromcore/queries.py)- Core layer purified:
core/is now a generic infrastructure facade with zero domain logic - Renamed
core/service.pytocore/base_service.py(contains onlyBaseServiceandBaseServiceConfig) - Removed
core/queries.py(absorbed intoservices/common/queries.py) - Removed
BatchProgress,NetworkSemaphoreMixinfrom core - Brotr API simplified:
- Removed
retryparameter from facade methods (retry always handled internally by Pool) - Removed
connparameter from_call_procedure(usetransaction()instead) - Removed
default_query_limitandmaterialized_viewsfromBrotrConfig - Simplified
refresh_matview()injection prevention (regex guard only) - Fixed
result or 0toresult if result is not None else 0for correct falsy handling - Model layer decoupled: NIP models (
nip11/fetch,nip66/*) now use stdliblogginginstead ofcore.logger, maintaining zero core dependencies - Model caching: All frozen dataclasses (
Relay,EventRelay,Metadata,RelayMetadata) now cacheto_db_params()in__post_init__ - Service registry: Uses
ServiceEntryNamedTuple withServiceNameenum keys instead of raw tuples utils/transport.py: Decoupled fromcore.logger(stdlibloggingonly)utils/progress.py: Deleted (functionality moved toservices/common/mixins.py)
Changed¶
- Monitor service: Aligned
MetadataFlagsandCheckResultwithMetadataTypeenum values (nip11->nip11_info); removed unusednip66_probefield - Infrastructure: Removed
CHECKconstraints fromrelay_metadata.metadata_typeacross all implementations; validation handled in Python enum layer - Implementation configs: Standardized
.env.example,docker-compose.yaml, and04_functions_cleanup.sqlacross template, bigbrotr, and lilbrotr
Added¶
- 69 new unit tests for
services/common/: test_constants.py(15 tests):ServiceNameandDataTypeStrEnum value and behavior coveragetest_mixins.py(15 tests):BatchProgressMixinandNetworkSemaphoreMixininitialization, composition, and edge casestest_queries.py(39 tests): All 13 domain SQL query functions with mocked Brotr, SQL fragment verification, and edge cases- Total test count: 1854 (up from 1776)
Documentation¶
- Three-tier architecture: Reframed documentation around Foundation (core + models), Active (services + utils), and Implementation tiers
- All docs updated:
ARCHITECTURE.md,DEVELOPMENT.md,TECHNICAL.md,README.md,CLAUDE.mdreflect renamed files andservices/common/ - Agent knowledge base updated:
AGENT.md,core-reference.md,architecture-index.mdaligned with new structure - YAML template comments: Fixed
BaseServiceConfigfile path references in all 4 service templates - Removed deprecated
test_nip11_nip66.ipynbnotebook
Chore¶
- Bumped version to 3.0.4
- Updated secrets baseline line numbers
- Added
AUDIT_REPORT.*pattern to.gitignore - Removed stale
RESTRUCTURING_PLAN.md
3.0.3 - 2026-02-06¶
Documentation-focused release with comprehensive docstring rewrites, standardized file headers, and cleaned up project documentation.
Documentation¶
- Core layer: Rewrote docstrings for all core modules (pool, brotr, service, metrics, logger)
- Models layer: Rewrote docstrings for all data model modules
- Services layer: Rewrote docstrings for services and utilities
- SQL: Rewrote SQL file headers and function documentation
- YAML: Standardized YAML configuration file headers
- Tests: Cleaned up test documentation and removed redundant comments
- Project docs: Rewrote project documentation and cleaned up markdown files
- Agents: Fixed outdated references and cleaned up agent knowledge base
Chore¶
- Updated secrets baseline line numbers
3.0.2 - 2026-02-05¶
Code quality and maintainability release with FieldSpec pattern, module reorganization, and comprehensive test restructuring.
Changed¶
- FieldSpec pattern: Consolidated field parsing with
FieldSpecdataclass for consistent validation and transformation across NIP models - Module reorganization:
- Logger now at
src/core/logger.py, imported asfrom core.logger import Logger - Renamed
base_servicetoserviceand consolidated mixins - Added
NetworkSemaphoreMixinfor simplified service code - NIP-11 refactoring:
- Migrated to FieldSpec pattern for improved type safety
- Simplified structure with keyword-only arguments in
createmethod - NIP-66 refactoring:
- Migrated to FieldSpec pattern for improved code quality
- Extracted
GeoExtractorhelper class for geolocation logic - Extracted
CertificateExtractorhelper class for SSL certificate parsing - Decomposed RTT method into focused phase methods
- Added keyword-only arguments in
createmethod - Models: Added fail-fast validation and unified
from_db_paramsAPI across all models - Core: Improved type safety and simplified database operations
- Services: Updated imports for module renames, simplified code structure
- Utils: Moved
NetworkTypeenum for better organization, improved configuration flexibility
Refactored¶
- Monitor service: Updated NIP-11 API usage and decomposed tag building logic
- Test structure:
- Renamed
test_cli.pytotest_main.py - Renamed
test_base_service.pytotest_service.py - Moved
test_logger.pytotests/unit/core/to matchsrc/core/logger.pylocation - Restructured NIP-11 tests into focused modules (
test_nip11.py,test_data.py,test_logs.py,test_fetch.py) - Restructured NIP-66 tests into focused modules (
test_nip66.py,test_rtt.py,test_ssl.py,test_geo.py,test_net.py,test_dns.py,test_http.py,test_logs.py) - Added comprehensive tests for
base.pyandparsing.py - Updated tests for fail-fast validation and simplified return types
Style¶
- Reordered imports per isort conventions
- Combined nested if statements per ruff SIM102
Chore¶
- Added EditorConfig for consistent coding styles
- Cleaned up project configuration
- Removed versioned release notes from repository
- Removed auto-generated footer from agents README
3.0.1 - 2026-02-04¶
Major refactoring release with new NIP models architecture, Python-side hash computation, and comprehensive documentation alignment.
Added¶
- NIP-11 subpackage (
src/models/nips/nip11/): Nip11main class with database serializationNip11InfoDatawith relay info document structure (originallyNip11FetchData)Nip11InfoLogsfor info retrieval status tracking (originallyNip11FetchLogs)- HTTP fetch implementation with SSL fallback
- NIP-66 subpackage (
src/models/nips/nip66/): Nip66aggregate class with database serializationNip66RttMetadatawith WebSocket probe testingNip66SslMetadatawith certificate validationNip66GeoMetadatawith MaxMind GeoLite2 lookupNip66NetMetadatawith ASN lookupNip66DnsMetadatawith comprehensive record lookupNip66HttpMetadatafrom WebSocket handshake- Data and logs models for all metadata types
- NIP base classes (
src/models/nips/base.py) for content-addressed storage - Async DNS utility (
src/utils/dns.py) with IPv4/IPv6 support - Retry configuration for all metadata types in Monitor
py.typedmarkers for nips subpackages
Changed¶
- Hash computation moved to Python: SHA-256 hashing now performed in Python instead of PostgreSQL for better portability
- SQL schema updated: All implementations (bigbrotr, lilbrotr, template) updated for BYTEA metadata id
- Monitor service refactored to use new nips metadata classes
- Brotr updated for Python-side metadata hash computation
- Logging standardized across all models, utils, and services
- Default max_batch_size reduced from 10000 to 1000
- Network config classes separated to fix partial YAML override inheritance
- Metrics endpoint secured with standardized ports
Fixed¶
- Runtime imports for Pydantic models restored
- Column name in
relay_metadata_latestmaterialized view corrected - Null byte validation added to Event content
- Logger-related issues resolved (#124, #78, #99, #141, #92)
- Documentation aligned with actual codebase:
- BigBrotr Expert reference files updated for NIP subpackages
- Database column name corrected (
metadata.data→metadata.metadata) - BaseService constructor signature documented (config is optional)
- Version references aligned across all files
3.0.0 - 2026-01-26¶
Major release with four-layer architecture, expanded NIP-66 compliance, and comprehensive AI-assisted development tooling.
Breaking Changes¶
- Service
initializerrenamed toseeder - Service config classes now extend
BaseServiceConfiginstead ofBaseModel - Constructor signature changed:
__init__(brotr, config)instead of__init__(config, brotr) - MetadataType values changed:
nip66_rttsplit into granular types
Added¶
- Four-layer architecture: Added Utils layer between Core and Services
- New Utils module (
src/utils/): NetworkConfig- Multi-network configuration (clearnet, tor, i2p, loki)KeysConfig- Nostr keypair configuration from environmentBatchProgress- Batch processing progress tracking dataclasstransport.py- Multi-network transport factory (aiohttp/aiohttp-socks)yaml.py- YAML configuration loading utilitiesparsing.py- URL and data parsing utilities- Prometheus metrics (
src/core/metrics.py): SERVICE_INFO- Static service metadataSERVICE_GAUGE- Point-in-time values with labelsSERVICE_COUNTER- Cumulative counters with labelsCYCLE_DURATION_SECONDS- Histogram for cycle duration percentiles- MetadataType expanded from 4 to 7 types:
nip11_info- NIP-11 relay information documentnip66_rtt- Round-trip time measurementsnip66_ssl- SSL certificate informationnip66_geo- Geolocation datanip66_net- Network information (ASN, ISP)nip66_dns- DNS resolution datanip66_http- HTTP header analysis- Validator service - Streaming relay validation with multi-network support
- NIP-42 authentication support
- Probabilistic candidate selection (Efraimidis-Spirakis algorithm)
- Automatic cleanup of failed candidates (configurable threshold)
- Full multi-network support in all services:
- Clearnet (wss://, ws://)
- Tor (.onion via SOCKS5 proxy)
- I2P (.i2p via SOCKS5 proxy)
- Lokinet (.loki via SOCKS5 proxy)
- Monitor service restructured:
BatchProgressfor tracking check progressCheckResultfor individual relay check resultsNip66RelayMetadatafor NIP-66 compliant output- 31 AI agents for development assistance:
- 29 generic agents (python-pro, security-auditor, etc.)
- 2 specialized agents (nostr-expert, bigbrotr-expert)
- 3 audit commands (
/audit-quick,/audit-core,/audit-full) - NIP-42 authentication support in Validator, Monitor, and Synchronizer
- Comprehensive docstrings across all models and services
- Keys model for loading Nostr keypairs from environment variables
Changed¶
- Architecture: Three-layer → Four-layer (Core, Utils, Services, Implementation)
- Test structure reorganized to
tests/unit/{core,models,services,utils}/ - Config inheritance: All service configs now extend
BaseServiceConfig - Constructor order:
(brotr, config)instead of(config, brotr)for consistency - Finder now stores candidates in
service_datatable (Validator picks them up) - Monitor checks use
service_datacheckpoints for efficient scheduling - Synchronizer uses
relay_metadata_latestview for faster relay selection - Improved error handling and logging across all services
- Enhanced test coverage with 411+ unit tests
Fixed¶
- Race conditions in Monitor metrics collection (added
asyncio.Lock) - Resource leaks in Monitor client shutdown (added
try/finally) - Memory optimization in Monitor with chunked relay processing
Migration Guide¶
1. Update service imports:
# Before (v2.x)
from pydantic import BaseModel
class MyServiceConfig(BaseModel):
interval: float = 300.0
# After (v3.0.0)
from core import BaseServiceConfig
class MyServiceConfig(BaseServiceConfig):
# interval is inherited from BaseServiceConfig
pass
2. Update constructor signatures:
# Before (v2.x)
def __init__(self, config: MyConfig, brotr: Brotr):
self._config = config
self._brotr = brotr
# After (v3.0.0)
def __init__(self, brotr: Brotr, config: MyConfig | None = None):
super().__init__(brotr=brotr, config=config or MyConfig())
3. Update MetadataType references:
# Before (v2.x)
type = MetadataType.NIP66_RTT # Was used for all NIP-66 data
# After (v3.0.0)
type = MetadataType.NIP66_RTT # Only for RTT measurements
type = MetadataType.NIP66_PROBE # For connectivity checks
type = MetadataType.NIP66_SSL # For SSL certificate data
type = MetadataType.NIP66_GEO # For geolocation
type = MetadataType.NIP66_NET # For network info
type = MetadataType.NIP66_DNS # For DNS data
type = MetadataType.NIP66_HTTP # For HTTP headers
2.0.0 - 2025-12¶
Complete architectural rewrite from monolithic prototype to modular, enterprise-ready system.
Added¶
- Three-layer architecture (Core, Service, Implementation)
- Multiple implementations: BigBrotr (full) and LilBrotr (lightweight)
- Core components: Pool, Brotr, BaseService, Logger
- Services: Seeder, Finder, Monitor, Synchronizer
- Async database driver (asyncpg) with connection pooling
- PGBouncer for connection management
- BYTEA storage for 50% space savings
- Pydantic configuration validation
- YAML-driven configuration
- Service state persistence
- Graceful shutdown handling
- NIP-11 and NIP-66 content deduplication
- 174 unit tests with pytest
- Pre-commit hooks (ruff, mypy)
- Comprehensive documentation (ARCHITECTURE, CONFIGURATION, DATABASE, DEVELOPMENT, DEPLOYMENT)
- GitHub Actions CI pipeline (lint, typecheck, test matrix Python 3.11-3.14, Docker build)
- Issue templates (bug report, feature request)
- Pull request template
- CHANGELOG.md (Keep a Changelog format)
- CONTRIBUTING.md (contribution guidelines)
- SECURITY.md (security policy)
- CODE_OF_CONDUCT.md (Contributor Covenant)
Changed¶
- Architecture: Monolithic → Three-layer modular design
- Configuration: Environment variables → YAML + Pydantic
- Database driver: psycopg2 (sync) → asyncpg (async)
- Storage format: CHAR (hex) → BYTEA (binary)
- Service name: syncronizer → synchronizer (fixed typo)
- Multicore: multiprocessing.Pool → aiomultiprocess
Removed¶
- pgAdmin (use external tools instead)
- pandas dependency
- secp256k1/bech32 dependencies (using nostr-sdk)
Fixed¶
- Connection pooling (was creating new connections per operation)
- State persistence (services now resume from last state)
- Configuration validation (now validates at startup)
- Graceful shutdown (services handle SIGTERM properly)
1.0.0 - 2025-06¶
Initial prototype release.
Added¶
- Full event archiving from Nostr relays
- Relay monitoring with NIP-11 support
- Connectivity testing (openable, readable, writable)
- RTT measurement for all operations
- Tor support for .onion relays
- Multicore processing with multiprocessing.Pool
- Time-window stack algorithm for large event volumes
- Docker Compose deployment
- PostgreSQL database with stored functions
- 8,865 seed relay URLs
Known Issues¶
- No async database (synchronous psycopg2)
- No connection pooling
- Finder service not implemented (stub only)
- No unit tests
- No configuration validation
- No graceful shutdown
- No state persistence
- Typo in service name ("syncronizer")