logicsrc/apps/logicsrc-web/src/app/openswarm/page.tsx
Anthony Ettinger 878ac53718 seo: give every page its own social title, not the homepage's
Reddit, Slack, X and Facebook read og:title, and every page of
logicsrc.com was serving the homepage's. The <title> tag was correct per
page, which is why it went unnoticed: Next.js shallow-merges metadata, so
a page exporting only `{ title, description, alternates }` inherits the
root layout's `openGraph` object whole. og:title, og:description and
og:url were the homepage's on all 45 spec landing pages, every doc, every
report and every SPA section.

Blog posts were the one route that declared openGraph, and they proved
the mechanism: their og:title is right. They also showed the other half
of it -- declaring openGraph drops the file-convention opengraph-image,
so live posts shipped with no og:image at all, and their twitter:title
was still the homepage's.

- src/lib/page-meta.ts: one builder, three entry points. specMetadata
  titles a spec landing page from its lib/specs.ts entry, pageMetadata
  titles the site's own pages from PAGE_META, contentMetadata titles a
  doc, post, skill or ontology record from its content. All three always
  emit openGraph and twitter title, description, url and image.
- Spec titles come from the registry, so they cannot drift from the way
  the sidebar, /specs and the home page describe the same spec, and a new
  spec page is titled the moment its registry entry exists. composeTitle
  trims a long subject line at a clause boundary to fit a link preview.
- Doc pages carry that subject line too, because every spec's H1 is just
  its name: "OpenStream" alone was the whole title.
- docExcerpt joins the wrapped lines of the first real paragraph and
  skips Status:/Slug: headers and list lead-ins, so descriptions stopped
  being "Status: 0.1 draft" and "Core tables:".
- contract/page-metadata.contract.test.ts fails if a sitemap route has no
  title, if a spec page has no registry entry, if two pages share a
  title, if a page declares metadata without the builder, or if a page
  loses its social image.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 07:55:58 +00:00

240 lines
12 KiB
TypeScript

import type { ReactNode } from "react";
import type { Metadata } from "next";
import { specMetadata } from "@/lib/page-meta";
import { SiteShell } from "@/components/site-shell";
import { card, mono, pre, table, td, th } from "../openontology/ui";
export const metadata: Metadata = specMetadata(
"/openswarm",
"OpenSwarm is an open specification family for paid, encrypted, peer-to-peer distribution of files and media: BitTorrent plus payment plus encryption as extension messages, with audio, video, live streams and a replicated catalogue on top, and c0mpute.com nodes that seed, relay and index for money."
);
const PROTOCOLS: Array<[name: string, line: string]> = [
["ipfile", "Paid, encrypted file swarms as BitTorrent extension messages"],
["ippay", "Passes and vouchers over x402 and CoinPay: how a leecher pays and a seeder is paid"],
["ipdb", "A replicated, append-only catalogue of manifests and metadata, discoverable over the DHT"],
["ipaudio", "Audio releases and tracks on ipfile swarms: renditions, seek tables, gapless, royalties"],
["ipvideo", "Video on demand: CMAF renditions, segment index, subtitles, thumbnails, an HLS bridge"],
["iplive", "Live streams: segment fan-out over paid relays with backpressure"],
["ipname", "How a Moshpit name or a domain resolves to a publisher key and a catalogue"],
["pay2seed", "Client protocol for paid seeding: consent at upload, seed offers with escrowed budgets, the requester's market"],
["paid2seed", "Server protocol for paid seeding: leases, storage challenges and probes over the wire, GiB-month settlement, the seeder client"],
["pay2stream", "Client protocol for paid live streams: consent for channels, relay offers by the hour, tickets, watching on any HLS player"],
["paid2stream", "Server protocol for paid live streams: relay leases, presence proofs, gateways serving standard HLS with an M3U and EPG"]
];
const ROLES: Array<[role: string, work: string, paid: string]> = [
["Storage", "Pin an ipfile swarm for a period and seed it", "Pin job plus vouchers per byte served"],
["Relay", "Fan out iplive segments to viewers", "Vouchers per byte served"],
["Transcode", "Produce ipaudio and ipvideo renditions as ipfile swarms", "Job price"],
["Index", "Replicate ipdb feeds, answer queries, serve a gateway", "Job price plus x402 per query"],
["Keeper", "Hold a content key and grant it to paying peers", "Share of each key grant"]
];
const DOCS: Array<[slug: string, title: string, blurb: string]> = [
["openswarm", "Overview", "What the family defines, how the pieces stack, and what already exists."],
["opencreds", "OpenCreds", "Where the publisher seed should live."]
];
export default function OpenSwarmPage(): ReactNode {
return (
<SiteShell active="OpenSwarm">
<div className="band">
<div className="section-head">
<p className="eyebrow">LogicSRC standards surface</p>
<h2>OpenSwarm</h2>
<p>
An open specification family for <strong>paid, encrypted, peer-to-peer distribution</strong>{" "}
of files and media. It is an add-on to BitTorrent, not a new transport. A swarm carries
ciphertext, the tracker and the DHT learn nothing about the content, every peer that
serves a verified piece gets paid for it, and the same primitives carry files, audio,
video, live streams and the catalogue that describes them.
</p>
</div>
<p style={{ color: "#41505d" }}>
It exists because BitTorrent solved distribution and never solved the two things that
keep it from being a product: nobody is paid to seed, and nothing in it is private. Every
Profullstack media property has rebuilt the same paid-access layer on top of a central
HTTP proxy because the swarm could not carry the payment.
</p>
<p style={{ color: "#5b6b7a", fontSize: "0.95rem" }}>
Status: <strong>0.1 draft</strong>. No reference implementation yet. The full documents
live in the repo under <code style={mono}>docs/openswarm/</code>.
</p>
</div>
<div className="band">
<div className="section-head">
<h2>The family</h2>
<p>Seven protocols over one core. Reuse is the default posture: a primitive is specified once.</p>
</div>
<div style={{ overflowX: "auto" }}>
<table style={table}>
<thead>
<tr>
<th style={th}>Protocol</th>
<th style={th}>One line</th>
</tr>
</thead>
<tbody>
{PROTOCOLS.map(([name, line]) => (
<tr key={name}>
<td style={{ ...td, ...mono }}>{name}</td>
<td style={td}>{line}</td>
</tr>
))}
</tbody>
</table>
</div>
<pre style={pre}>{` +---------------------------------------------------------------+
| ipaudio ipvideo iplive ipdb (catalogue) |
+---------------------------------------------------------------+
| ipfile: manifest, per-file key pair, encrypted pieces, |
| key grants, credit window, vouchers per served piece |
+-------------------------------+-------------------------------+
| ippay: passes and vouchers | ipname: Moshpit name -> key |
+-------------------------------+-------------------------------+
| BitTorrent wire (BEP 3) + extension protocol (BEP 10) |
| DHT (BEP 5, 44, 46), trackers, WebRTC peers, webseeds, |
| optional Moshpit MTP/1 tunnel |
+---------------------------------------------------------------+`}</pre>
</div>
<div className="band">
<div className="section-head">
<h2>One swarm shape, one key model, one payment loop</h2>
</div>
<div style={{ display: "grid", gap: "0.6rem" }}>
<div style={card}>
<strong>The swarm carries ciphertext.</strong>
<p style={{ margin: "0.35rem 0 0", color: "#41505d" }}>
A hybrid BEP 52 torrent whose single file is AES-256-CTR ciphertext. The v1 infohash
makes it reachable from WebTorrent in a browser, the v2 merkle tree makes every 16 KiB
block verifiable, and the plaintext root in the signed manifest makes the decrypted
result verifiable. A vanilla client can join and, if the publisher allows, download
bytes it cannot read.
</p>
</div>
<div style={card}>
<strong>Adding a file mints a key pair for it.</strong>
<p style={{ margin: "0.35rem 0 0", color: "#41505d" }}>
Derived from one publisher seed by default, so there is one thing to back up. The
public half is the file&rsquo;s identity and the BEP 46 key under which the latest
manifest is published; the private half signs the manifest and authorises key grants
and payout changes. A separate random content key encrypts the bytes and is sealed to
paying peers.
</p>
</div>
<div style={card}>
<strong>Seeders are paid per verified piece.</strong>
<p style={{ margin: "0.35rem 0 0", color: "#41505d" }}>
A leecher buys a pass over x402 in USDC. A seeder serves inside a bounded credit
window. After verifying each batch the leecher signs a cumulative voucher. The seeder
redeems the latest voucher at a hub, which splits it between publisher, seeder and
itself. Nobody pays for bytes they did not verify, and nobody serves more than the
window unpaid.
</p>
</div>
</div>
<pre style={pre}>{`leecher seeder
| hello |
|---------------------------------------->|
|<------------------------------- hello |
| pass |
|---------------------------------------->| verifies hub signature, scope, cap
|<----------------------------- unchoke |
| request / piece ... (vanilla) |
|<=======================================>| up to the credit window unpaid
|<------------------------------ credit |
| voucher (cumulative, signed) |
|---------------------------------------->| unpaid resets
| key_req |
|---------------------------------------->|
|<------------------------------- grant | content key sealed to the leecher`}</pre>
</div>
<div className="band">
<div className="section-head">
<h2>How c0mpute.com nodes take part</h2>
<p>
A node already advertises roles and takes work through a gossipsub auction. OpenSwarm
adds workload types to bid on and a per-byte income that needs no auction at all.
</p>
</div>
<div style={{ overflowX: "auto" }}>
<table style={table}>
<thead>
<tr>
<th style={th}>Role</th>
<th style={th}>Work</th>
<th style={th}>Paid by</th>
</tr>
</thead>
<tbody>
{ROLES.map(([role, work, paid]) => (
<tr key={role}>
<td style={td}>{role}</td>
<td style={td}>{work}</td>
<td style={td}>{paid}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<div className="band">
<div className="section-head">
<h2>Using it (proposed CLI)</h2>
</div>
<pre style={pre}>{`# One publisher identity, reused for every file
ip init
# Add a file: derives the file key pair, encrypts, builds the hybrid torrent,
# signs the manifest, starts seeding
ip file add ./interview.flac --per-gib 0.01 --key-price 0.50
# Publish an audio release that references it
ip audio publish ./release.json
# Fetch as a paying peer (buys a pass, gets the key, verifies, decrypts)
ip file get ed25519:0d87e09c7fea3ad6ba6c2f3e027ea47f5b245452899910948470906704c5295d --out ./interview.flac
# Ask the catalogue
ip db query --type ipaudio.track --where 'record.artist == "Ada"' --limit 20`}</pre>
</div>
<div className="band">
<div className="section-head">
<h2>What already exists</h2>
</div>
<div style={{ display: "grid", gap: "0.6rem" }}>
<div style={card}>
<strong>bittorrented.com</strong> runs the browser WebTorrent player, a hybrid seeder on
wss trackers, and a DHT crawl. It speaks the vanilla wire this family extends.
</div>
<div style={card}>
<strong>x402-gateway and CoinPay</strong> sell passes with the x402 v2 offer and verify
and settle the proof. <code style={mono}>ippay</code> reuses that exchange unchanged.
</div>
<div style={card}>
<strong>c0mpute</strong> has the blake3 chunk store, erasure coding, hardware transcode,
a gateway, and a live-stream design that already swarms segments.
</div>
<div style={card}>
<strong>moshpit-transport and the Moshpit registry</strong> provide the post-quantum
tunnel and the name pins.
</div>
</div>
<div style={{ display: "flex", flexWrap: "wrap", gap: "0.75rem", marginTop: "1rem" }}>
{DOCS.map(([slug, title, blurb]) => (
<a key={slug} href={`/docs/${slug}`} style={{ ...card, textDecoration: "none", color: "inherit", flex: "1 1 16rem" }}>
<strong>{title}</strong>
<p style={{ margin: "0.35rem 0 0", color: "#5b6b7a", fontSize: "0.9rem" }}>{blurb}</p>
</a>
))}
</div>
</div>
</SiteShell>
);
}