feat: the directory hosts webrings (OpenWebring), one ring per topic - #196
Merged
Conversation
rssamplifier.com is the first OpenWebring host (logicsrc.com/openwebring): a ring is an ordered, circular list of member sites, and every member carries its own word on who makes it, human, ai or both, read from the member's /.well-known/openwebring.json and never verified. - rings + ring_members (explicit position: a topic's feeds are re-derived every crawl, so ring order must be its own column; new members append). - /.well-known/openwebring.json (a route: rings are data), /ring index, /ring/<slug> page with the join snippet, /ring/<slug>/openwebring.json, /ring/<slug>/opml, hops /next /previous /prev /random with ?from= and every shape older rings use (?host=, ?via=, ?url=, /<member>/next, the bare Referer); unknown from is random; wrap; skip inactive; 302 no-store. - Verification in the poller (RING_* env): a bounded batch per tick, one stranger's front page at a time, any link to the ring counts, the member's descriptor read for made_by and rings[]; inactive, never removed. An on-demand /ring/<slug>/check behind the page gate. - Hops exempt from the proxy throttle (a refused hop is a broken link on someone else's site), counted like everything else. - Owner override PUT /api/rings/<slug>/members/<member>; OpenAccess scope openwebring:edit; /api/rings, /api/rings/<slug>; MCP list_rings, get_ring, ring_next; nav, sitemap, seed script. - linksBack/fetchPage moved to @rssamplifier/feed (linksTo, fetchPage) so the poller and the web app run one scan. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XYae2mH3khdwiXUVzcVMDw
ralyodio
marked this pull request as ready for review
September 13, 2026 08:50
This was referenced Sep 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The directory hosts webrings: the first OpenWebring host
Spec: OpenWebring (profullstack/logicsrc#178). A ring is an ordered, circular list of member sites; every member carries its own word on who makes it (
made_by: human, ai or both), read from the member's/.well-known/openwebring.json, never verified, absent shown as unstated. rssamplifier.com runs one ring per well-covered topic out of the feeds it already reads.Schema
packages/db/migrations/20260913081529_openwebring.sql:ringsandring_members, purely additive. Ring order is an explicitposition(a topic's feeds are re-derived on every crawl, so order cannot come from there); seeding is idempotent, new members append. The poller applies it at boot.Routes
GET /.well-known/openwebring.json(a route, not a static file: rings are data)GET /ring,GET /ring/[slug](members in order, made_by, status, the three-anchor join snippet,<link rel="openwebring">)GET /ring/[slug]/openwebring.json,GET /ring/[slug]/opmlGET /ring/[slug]/next|previous|prev|randomandGET /ring/[slug]/[member]/next|previous|prev|random.?from=canonical;?host=,?via=,?url=and the bare Referer resolve too. 302,cache-control: no-store, no cookie, no write. Unknownfromis a random member; wraps; skips members that are not active.POST /ring/[slug]/check?url=(one member, behind the page gate)GET /api/rings,GET /api/rings/[slug],PUT /api/rings/[slug]/members/[member](owner of a claimed author profile, admin, or an OpenAccess bearer withopenwebring:edit; added toopenaccess.json)list_rings,get_ring,ring_next(names added toopenmcp.json)Verification and seeding
packages/ingest/src/webring.js:checkRingMemberis pure (takes its fetch), shared by the poller pass and the on-demand check. The poller (apps/poller/src/index.js,ringTick) seeds the topRING_TOPICStopics everyRING_SEED_SECONDSand checksRING_BATCHmembers everyRING_SECONDS, oldest verdict first, one fetch at a time;RING_VERIFY=0pauses it..env.exampledocuments all of it. One-off:node --env-file=.env scripts/seed-rings.mjs.Throttle
Hops are exempt from the proxy tiers (
RING_HOPinproxy.js, read back bytest/proxy.test.js): a refused hop is a broken link on somebody else's page. Still counted. Ring loads are cached 30 s per worker.Shared code
linksBackandfetchPageinprofileAuth.jsnow delegate to@rssamplifier/feed(linksTo,fetchPage) so the web app and the poller run one scan; OpenProfile callers are unchanged.Verification
pnpm --filter @rssamplifier/web test: 460 pass, 0 fail (includestest/openwebring.test.js)pnpm --filter @rssamplifier/db test,ingest,feed(344),poller(24): all pass (a first run of db and ingest failed with SQLite disk I/O errors while the box was swapping; clean on re-run)pnpm --filter @rssamplifier/web build: compiles, every ring route listedBuilt by a subagent from a repo survey; the agent died on the org's spend cap after finishing, so this PR was reviewed and verified by the parent session.
🤖 Generated with Claude Code
https://claude.ai/code/session_01XYae2mH3khdwiXUVzcVMDw