diff --git a/apps/logicsrc-web/src/app/llms.txt/route.ts b/apps/logicsrc-web/src/app/llms.txt/route.ts index 07912a7..c64a787 100644 --- a/apps/logicsrc-web/src/app/llms.txt/route.ts +++ b/apps/logicsrc-web/src/app/llms.txt/route.ts @@ -28,6 +28,8 @@ export function GET(): Response { - [OpenMemory](${SITE_URL}/openmemory): The memory block of an OpenServer offer: RAM in mebibytes, DDR generation, ECC as three states, reserved or balloonable allocation, and a range priced per step. - [OpenGPU](${SITE_URL}/opengpu): The gpu block of an OpenServer offer: the card by its vendor name, count and VRAM per device, interconnect, passthrough, MIG, vGPU or shared access, and a range over count. - [OpenBandwidth](${SITE_URL}/openbandwidth): The network block of an OpenServer offer: port speed, transfer, unmetered, 95th percentile or flat metering, overage, IPv4 and IPv6 addresses as a priced resource, DDoS scrubbing, and a range. +- [OpenFile](${SITE_URL}/openfile): One file a publisher serves about the files it has published: content hash, swarm and HTTP fetch routes, verification, consent basis, price, and who holds it now, discovered at /.well-known/openfile.json. The web door onto an OpenSwarm ipfile swarm. +- [OpenDisk](${SITE_URL}/opendisk): One file a machine serves about the disk it will rent: free GiB, price per GiB-month, location, policy, proof cadence and hub standing, discovered at /.well-known/opendisk.json. What a peer-to-peer storage market is made of; reference marketplace d1sks.com. - [AgentSwarm](${SITE_URL}/agent-swarm): Provider-neutral agent orchestration, model routing, and cost controls. - [AgentByte](${SITE_URL}/agentbyte): Agent screening sessions, policy events, and APIs. - [Credential Sharing](${SITE_URL}/credential-sharing): End-to-end-encrypted team vaults, plus source/target credential diffs, approval, sync, rollback, and audit. diff --git a/apps/logicsrc-web/src/app/opendisk/page.tsx b/apps/logicsrc-web/src/app/opendisk/page.tsx new file mode 100644 index 0000000..7cf8230 --- /dev/null +++ b/apps/logicsrc-web/src/app/opendisk/page.tsx @@ -0,0 +1,195 @@ +import Link from "next/link"; +import type { ReactNode } from "react"; +import type { Metadata } from "next"; +import { SiteShell } from "@/components/site-shell"; +import { mono, pre, table, td, th } from "../openontology/ui"; + +export const metadata: Metadata = { + title: "OpenDisk · LogicSRC", + description: + "OpenDisk is one file a machine serves about the disk it will rent: free GiB, price per GiB-month, location, policy, proof cadence and hub standing, at /.well-known/opendisk.json. What a peer-to-peer storage market is made of. Reference marketplace d1sks.com.", + alternates: { canonical: "/opendisk" } +}; + +const DESCRIPTOR = `{ + "name": "seeder-a41e, Falkenstein", + "operator": "https://seeder-a41e.example/.well-known/openprofile.md", + "key": "ed25519:a41e…7b0a", + "hubs": ["https://bittorrented.com/api/openswarm", "https://d1sks.com/api/openswarm"], + "capacity": { "total_gib": 3726, "free_gib": 2210 }, + "price": { "currency": "USD", "per_gib_month": 0.12, "per_gib_transfer": 0.004, "max_gib": 2000, "max_days": 365 }, + "location": { "regions": ["fsn1"], "countries": ["DE"] }, + "network": { "bandwidth_mbps": 1000, "transfer_gb": 20000 }, + "accepts": { "visibility": ["private", "public"], "encrypted_only": false, "max_file_gib": 500 }, + "proof": { "kinds": ["challenge", "probe"], "every_hours_min": 6 }, + "record": { "source": "https://bittorrented.com/api/openswarm/pay2seed/seeders/ed25519:a41e…7b0a", "standing": 412 }, + "payout": { "payee": "ed25519:a41e…7b0a", "network": "eip155:8453" } +}`; + +const MINIMAL = `{ "name": "spare drive, Lisbon", "capacity": { "free_gib": 900 }, + "price": { "currency": "USD", "per_gib_month": 0.08 } }`; + +const RENT: Array<[string, string]> = [ + ["1. Read", "The descriptor for policy and price, and record.source at the hub for standing."], + ["2. Attest", "The consent record pay2seed requires, with README.md at the root of the swarm."], + ["3. Offer", "A pay2seed offer at one of the disk's hubs, at or above per_gib_month, with the budget escrowed."], + ["4. Lease", "The disk's seeder client takes it on its next poll, or at once when the hub pushes to proof.webhook."], + ["5. Prove", "Challenges and probes every period, receipts per proven period, payout to the payee. All paid2seed, unchanged."] +]; + +const MAPPING: Array<[string, string]> = [ + ["offers[].kind", "storage"], + ["offers[].model", "p2p for a peer, centralized for a provider; the disk may say"], + ["offers[].price", "{ amount: per_gib_month, currency, interval: month, unit: gib }"], + ["offers[].stock", "in_stock while free_gib covers min_gib"], + ["location, network, storage", "the same keys, the same units"] +]; + +const ABSENT: Array<[string, string]> = [ + ["No lease in this file", "The descriptor is the ask, the offer is the bid, the lease is the match, made at the hub as paid2seed says."], + ["No escrow at the disk", "Money sits at a hub. A disk never holds a requester's funds."], + ["No reputation of its own", "record is a copy of what a hub signed. A marketplace reads the hub, and lists the hub's number."], + ["No plaintext", "By default a disk holds ipfile ciphertext it cannot read, and a personal swarm's existence is not in its public list."], + ["No central registry", "Anyone may serve a descriptor and anyone may read it. Two marketplaces reading the same file list the same disk."] +]; + +export default function OpenDiskPage(): ReactNode { + return ( + +
+
+

LogicSRC standards surface

+

OpenDisk

+

+ One file a machine serves about the disk it will rent, so a disk can be found instead of + waited for, and a peer with a spare terabyte is on the market by putting a file at a URL. +

+
+

+ OpenSwarm already says how a seeder is paid to hold a swarm: + an offer with money escrowed, a lease, a storage proof every period, a payout per + GiB-month. What it does not say is how a requester finds a seeder before posting. The + market is one-sided: offers are listed and seeders poll them. OpenDisk is the other side. + A disk puts its free space, price, location, policy, proof cadence and hub standing at{" "} + /.well-known/opendisk.json, and a requester reads the disk + before it posts. +

+

+ Status: 0.1. d1sks.com is the reference marketplace: every + descriptor it has read, standing pulled from the hubs each disk names, and an offer form that + posts to the disk's hub. Every disk is also a hosting offer in the{" "} + OpenServer sense, listed as one at nichedb.dev. +

+
+ +
+
+

The descriptor

+

+ Served at /.well-known/opendisk.json. Only a name, the free + space and a price are required. +

+
+
{DESCRIPTOR}
+

+ key is the seeder identity that signs proofs and is paid;{" "} + hubs is where it takes leases. accepts{" "} + is the operator's policy stated up front, so nobody posts an offer this disk would never + take. record.source is the hub's own page for this seeder, + and a marketplace reads the hub, not the file, for anything it ranks on. The smallest valid + descriptor: +

+
{MINIMAL}
+
+ +
+
+

Renting a disk

+

Nothing on the swarm side changes. The descriptor only moves the first step to a URL.

+
+ + + {RENT.map(([step, what]) => ( + + + + + ))} + +
+ {step} + {what}
+
+ +
+
+

As an OpenServer offer

+

A disk is a hosting offer, and a directory that reads OpenServer lists it without a second parser.

+
+ + + + + + + + + {MAPPING.map(([key, from]) => ( + + + + + ))} + +
OpenServerfrom OpenDisk
+ {key} + {from}
+

+ A provider that sells storage plans and rents disk to swarms serves both files. A peer with + one drive serves only this one, and is listed in both places. +

+
+ +
+
+

What is deliberately absent

+
+ + + {ABSENT.map(([what, why]) => ( + + + + + ))} + +
+ {what} + {why}
+
+ +
+
+

Where everything lives

+
+ +
+
+ ); +} diff --git a/apps/logicsrc-web/src/app/openfile/page.tsx b/apps/logicsrc-web/src/app/openfile/page.tsx new file mode 100644 index 0000000..51c4815 --- /dev/null +++ b/apps/logicsrc-web/src/app/openfile/page.tsx @@ -0,0 +1,220 @@ +import Link from "next/link"; +import type { ReactNode } from "react"; +import type { Metadata } from "next"; +import { SiteShell } from "@/components/site-shell"; +import { mono, pre, table, td, th } from "../openontology/ui"; + +export const metadata: Metadata = { + title: "OpenFile · LogicSRC", + description: + "OpenFile is one file a publisher serves about the files it has published: content hash, swarm and HTTP fetch routes, verification, consent basis, price, and who holds it now, at /.well-known/openfile.json. The web door onto an OpenSwarm ipfile swarm.", + alternates: { canonical: "/openfile" } +}; + +const DESCRIPTOR = `{ + "publisher": { + "name": "Dartmoor Field Recordings", + "operator": "https://dartmoor.example/.well-known/openprofile.md", + "key": "ed25519:5d29…d59e", + "hubs": ["https://bittorrented.com/api/openswarm"] + }, + "files": [{ + "id": "sha256:d6c3…93ff", + "name": "interview-2026-09-05.flac", + "size": 734003200, + "swarm": { "file": "ed25519:0d87…295d", "infohashV2": "sha256:4b74…a342" }, + "encryption": "ipfile", + "fetch": [ + { "kind": "ipfile", "url": "magnet:?xs=urn:btpk:0d87…295d&s=ipfile" }, + { "kind": "http", "url": "https://gw.c0mpute.com/file/d6c3…93ff" }, + { "kind": "hls", "url": "https://gw.c0mpute.com/file/d6c3…93ff/index.m3u8" } + ], + "attestation": { "basis": "own", "license": "CC-BY-4.0" }, + "price": { "amount": 0.5, "currency": "USD", "per": "key", "offer": "https://keys.dartmoor.example/…" }, + "holders": "https://bittorrented.com/api/openswarm/files/d6c3…93ff/holders" + }] +}`; + +const MINIMAL = `{ "publisher": { "name": "Dartmoor Field Recordings" }, + "files": [{ "id": "sha256:d6c3…93ff", "name": "interview-2026-09-05.flac" }] }`; + +const FETCH: Array<[string, string, string]> = [ + ["ipfile", "a magnet for an OpenSwarm client", "encrypted pieces, paid per piece, the key on a pass"], + ["magnet", "a vanilla magnet", "the ciphertext, for a client that cannot pay"], + ["webseed", "BEP 19, ciphertext by range", "a gateway standing in for peers"], + ["http", "the plaintext by range from a gateway", "a browser with nothing installed, behind a pass when there is a price"], + ["hls", "a media file as a standard playlist", "any player, sealed when there is a price"] +]; + +const REUSED: Array<[string, string]> = [ + ["The swarm and the manifest", "ipfile: the file key, both infohashes, plainRoot as the content hash, the plaintext piece layer"], + ["Consent and the README", "pay2seed: the attestation record, its basis and licence, the notice endpoint, README.md at the root"], + ["Who holds it", "paid2seed: leases and the proofs behind provenAt"], + ["Payment", "ippay: a pass bought over x402, presented as a bearer token on the gateway"], + ["The feed", "ipdb: the same catalogue, replicated between peers, for a reader that speaks the swarm"] +]; + +const ABSENT: Array<[string, string]> = [ + ["No new swarm format", "The swarm is ipfile, the consent is pay2seed's, the payment is ippay's. This is a JSON door onto records that already exist."], + ["No search", "A descriptor lists one publisher's files. Finding a file across publishers is a directory's job."], + ["No trust score", "verified says where the file came from and who signed the manifest. basis is what the publisher claimed. The rest is the reader's judgement."], + ["No DRM", "A pass holder gets the key and the bytes, as ipfile says."] +]; + +export default function OpenFilePage(): ReactNode { + return ( + +
+
+

LogicSRC standards surface

+

OpenFile

+

+ One file a publisher serves about the files it has published, so a file can be found + instead of crawled, and fetched by a browser that has never heard of a swarm. +

+
+

+ A file on a swarm is findable by its infohash and by nothing else. OpenSwarm{" "} + fixed the inside of the swarm: a signed manifest, encrypted pieces, consent at upload, a + replicated catalogue. What none of that gives a person with a browser, a search engine or a + directory is a URL to start from. OpenFile puts a publisher's catalogue at{" "} + /.well-known/openfile.json, with a content hash to verify + against, every way to fetch the bytes, the basis it was published on, the price, and who is + holding it right now. +

+

+ Status: 0.1. The reference reader is bittorrented.com, which lists consented swarms beside + the bare infohashes of its DHT crawl, with the README as the page. A product for it is + planned under a name not yet chosen. +

+
+ +
+
+

The descriptor

+

+ Served at /.well-known/openfile.json. Only the publisher's + name and each file's content hash and name are required. +

+
+
{DESCRIPTOR}
+

+ id is the SHA-256 of the plaintext, which for an ipfile swarm is + the manifest's plainRoot: the same root the decrypted file + verifies against. Two publishers serving the same bytes list the same id, and a directory + has one file with two listings. encryption is ipfile unless the + publisher says none as an explicit act. The smallest valid + descriptor: +

+
{MINIMAL}
+
+ +
+
+

Five ways to fetch

+

Listed in the publisher's order of preference. A reader picks the first kind it speaks.

+
+ + + + + + + + + + {FETCH.map(([kind, what, who]) => ( + + + + + + ))} + +
kindWhat it isWho uses it
+ {kind} + {what}{who}
+

+ Over any of them the bytes verify the same way: each piece against the plaintext piece + layer, or the whole against id. A reader refuses bytes that fail + and says which holder served them. +

+
+ +
+
+

Holders

+

A file is only as available as the machines holding it.

+
+

+ holders answers with who has the bytes now: a{" "} + seeder with a lease and the time it last passed a proof, a{" "} + gateway serving it over HTTP, a peer{" "} + merely seen. Each carries its age, and a seeder's lease can be checked at the hub. + A holder that rents disk links its own OpenDisk descriptor, so a + publisher that likes a holder can buy more of it. +

+
+ +
+
+

What is reused

+

Every record OpenFile points at already has a name in the OpenSwarm family.

+
+ + + {REUSED.map(([what, from]) => ( + + + + + ))} + +
+ {what} + {from}
+
+ +
+
+

What is deliberately absent

+
+ + + {ABSENT.map(([what, why]) => ( + + + + + ))} + +
+ {what} + {why}
+
+ +
+
+

Where everything lives

+
+ +
+
+ ); +} diff --git a/apps/logicsrc-web/src/app/sitemap.ts b/apps/logicsrc-web/src/app/sitemap.ts index d8d7299..a42eeb8 100644 --- a/apps/logicsrc-web/src/app/sitemap.ts +++ b/apps/logicsrc-web/src/app/sitemap.ts @@ -30,6 +30,8 @@ const STATIC_ROUTES: Array<{ { path: "/openmemory", changeFrequency: "weekly", priority: 0.9 }, { path: "/opengpu", changeFrequency: "weekly", priority: 0.9 }, { path: "/openbandwidth", changeFrequency: "weekly", priority: 0.9 }, + { path: "/openfile", changeFrequency: "weekly", priority: 0.9 }, + { path: "/opendisk", changeFrequency: "weekly", priority: 0.9 }, { path: "/openontology/explore", changeFrequency: "daily", priority: 0.7 }, { path: "/openspec", changeFrequency: "weekly", priority: 0.8 }, { path: "/agent-swarm", changeFrequency: "weekly", priority: 0.8 }, diff --git a/apps/logicsrc-web/src/components/site-shell.tsx b/apps/logicsrc-web/src/components/site-shell.tsx index e565ea5..4699247 100644 --- a/apps/logicsrc-web/src/components/site-shell.tsx +++ b/apps/logicsrc-web/src/components/site-shell.tsx @@ -22,6 +22,8 @@ const NAV: Array<{ href: string; label: string; external?: boolean }> = [ { href: "/openmemory", label: "OpenMemory" }, { href: "/opengpu", label: "OpenGPU" }, { href: "/openbandwidth", label: "OpenBandwidth" }, + { href: "/openfile", label: "OpenFile" }, + { href: "/opendisk", label: "OpenDisk" }, { href: "/#cli", label: "CLI" }, { href: "/docs", label: "Docs" }, { href: "/blog", label: "Blog" }, diff --git a/apps/logicsrc-web/src/lib/docs.ts b/apps/logicsrc-web/src/lib/docs.ts index cd2afff..009153c 100644 --- a/apps/logicsrc-web/src/lib/docs.ts +++ b/apps/logicsrc-web/src/lib/docs.ts @@ -21,6 +21,8 @@ export const DOC_SLUGS = [ "openmcp", "openaccess", "openserver", + "openfile", + "opendisk", "openstream", "opencpu", "openmemory", diff --git a/docs/opendisk.md b/docs/opendisk.md new file mode 100644 index 0000000..656cb5e --- /dev/null +++ b/docs/opendisk.md @@ -0,0 +1,184 @@ +# OpenDisk + +OpenDisk is one file a machine serves about the disk it will rent: how much is free, what a GiB-month costs, where the box is, what it will hold, how it proves it is still holding it, and where it is paid. A requester reads the disk's own file instead of a marketplace's listing, a directory lists every disk that serves one, and a peer with a spare terabyte is on the market by putting a file at a URL. It is the web-facing door onto an [OpenSwarm](/openswarm) `paid2seed` seeder, and it is what a peer-to-peer storage market is made of. It is maintained by Profullstack, Inc. as part of the LogicSRC open-standards surface, with [d1sks.com](https://d1sks.com) as the reference marketplace. + +Status: **0.1**. A description of a file a marketplace will read, published so a peer can serve one and any directory can read it. + +Slug: `opendisk` + +## The problem + +OpenSwarm already says how a seeder is paid to hold a swarm: a requester posts a `pay2seed` offer at a hub with money escrowed, a seeder takes a `paid2seed` lease, proves it holds the pieces every period, and is paid per GiB-month. What it does not say is how a requester finds a seeder before posting, or how a seeder is found at all. The market in `paid2seed` is one-sided: offers are listed and seeders poll them. A requester who wants a box in Germany with 2 TB free and a year of clean proofs has no file to read, and a seeder with those things has no file to serve. + +Every other rentable thing has a listing. `/.well-known/` is where a host says things about itself. What is missing is the one file that puts a seeder's capacity, price and record where a requester can fetch it, in a shape every reader agrees on, so a disk can be found instead of waited for. + +## Terms + +- A **disk** is capacity somebody will rent: a peer's spare drive, a seedbox, a storage node, a provider's storage plan. Its **descriptor** is the file it serves about itself. +- The **seeder** is the OpenSwarm identity that takes leases for the disk and is paid. One disk, one seeder key. +- A **requester** is whoever wants bytes kept, as `pay2seed` defines it: a publisher, a person with a backup, an agent. +- A **marketplace** is a directory of disks that also fronts a hub, so a requester can read a disk and post the offer in one place. [d1sks.com](https://d1sks.com) is the reference. +- A **reader** is anything that reads a descriptor: a marketplace, a requester's client, a person's terminal, an agent. + +## The descriptor + +A disk serves a JSON document at `/.well-known/opendisk.json` on its own origin. + +```json +{ + "name": "seeder-a41e, Falkenstein", + "web": "https://seeder-a41e.example", + "operator": "https://seeder-a41e.example/.well-known/openprofile.md", + "key": "ed25519:a41e7f0c2d9b8e5a6f3c1d4e7b0a9f8c5d2e1b4a7c0f3e6d9b2a5c8e1f4d7b0a", + "hubs": ["https://bittorrented.com/api/openswarm", "https://d1sks.com/api/openswarm"], + "updated": "2026-09-13T06:00:00Z", + "capacity": { "total_gib": 3726, "free_gib": 2210, "reserved_gib": 200 }, + "price": { + "currency": "USD", + "per_gib_month": 0.12, + "per_gib_transfer": 0.004, + "min_gib": 1, + "max_gib": 2000, + "min_days": 7, + "max_days": 365 + }, + "location": { "regions": ["fsn1"], "countries": ["DE"] }, + "network": { "bandwidth_mbps": 1000, "transfer_gb": 20000, "ipv4": 1, "ipv6": true }, + "storage": [{ "type": "hdd", "size_gb": 4000 }], + "accepts": { + "visibility": ["private", "public"], + "bases": ["own", "licensed", "open-license", "public-domain", "personal"], + "encrypted_only": false, + "max_file_gib": 500, + "feeds": true + }, + "proof": { "kinds": ["challenge", "probe"], "every_hours_min": 6, "webhook": "https://seeder-a41e.example/openswarm/hooks" }, + "record": { + "source": "https://bittorrented.com/api/openswarm/pay2seed/seeders/ed25519:a41e7f0c2d9b8e5a6f3c1d4e7b0a9f8c5d2e1b4a7c0f3e6d9b2a5c8e1f4d7b0a", + "standing": 412, + "proven": 418, + "failed": 3, + "abandoned": 0, + "since": "2025-11-02T00:00:00Z" + }, + "payout": { "payee": "ed25519:a41e7f0c2d9b8e5a6f3c1d4e7b0a9f8c5d2e1b4a7c0f3e6d9b2a5c8e1f4d7b0a", "network": "eip155:8453" }, + "holding": "https://seeder-a41e.example/openswarm/holding" +} +``` + +The smallest valid descriptor is a name, the free space and a price: + +```json +{ "name": "spare drive, Lisbon", "capacity": { "free_gib": 900 }, "price": { "currency": "USD", "per_gib_month": 0.08 } } +``` + +The rules, and every one degrades: + +1. **`name`, `capacity.free_gib` and `price.per_gib_month` are the only required keys.** A descriptor with those alone is valid: it is a disk, it has room, it has a price. A reader lists what it was given and reports the rest as unstated rather than assumed. +2. **`operator`** is the person or organisation answerable, as an [OpenProfile.md](/openprofile) URL. `web` is the disk's site, if it has one beyond the descriptor. A disk with no operator is listed as such, and a marketplace may decline to list it. +3. **`key`** is the seeder's OpenSwarm identity, the key that signs `paid2seed` proofs and is registered as an `ippay` payee. **`hubs`** are the hubs it takes leases at. A requester posts its offer at one of them; a disk with no `hubs` is rented some other way, and `web` says how. +4. **`updated`** is when anything in the file last changed. `capacity.free_gib` changes with every lease, so a disk that is busy updates often and a reader with `updated` unchanged since its last fetch may skip the rest. +5. **`capacity`** is in GiB: `total_gib` the disk, `free_gib` what a new lease can have now, `reserved_gib` what the operator keeps back. `free_gib` is the number that matters and the only one required. +6. **`price`** is one price list. `per_gib_month` is what one GiB held for thirty days costs, pro rata, the same unit `pay2seed` offers are priced in. `per_gib_transfer` is what serving one GiB costs on top, or absent when serving is included; a disk that prices transfer in more detail does so as [OpenBandwidth](/docs/openbandwidth) describes and keeps this one number as the summary. `min_gib`, `max_gib`, `min_days`, `max_days` bound a lease. `currency` is ISO 4217. +7. **`location`, `network`, `storage`** are as [OpenServer](/openserver) defines them, the same keys and the same units, so a directory that already reads an OpenServer storage block reads this one: the provider's own region names and ISO country codes, megabits and gigabytes, one `storage` entry per drive with `type` `nvme`, `ssd` or `hdd`. A requester choosing a disk for a backup wants the country; one choosing a seed for a release wants the bandwidth. +8. **`accepts`** is the operator's policy, the same policy a `paid2seed` seeder client applies when it polls a market, stated up front so a requester does not post an offer this disk would never take. `visibility` and `bases` are `pay2seed`'s lists. `encrypted_only: true` means the disk holds `ipfile` ciphertext and nothing else. `max_file_gib` caps one swarm. `feeds` says whether it follows `ipdb` feeds and keeps their segments. +9. **`proof`** is how the disk expects to be checked: `kinds` any of `challenge` and `probe` as `paid2seed` §4 defines them, `every_hours_min` the shortest period it will accept, `webhook` where a hub pushes challenges so the disk need not poll. +10. **`record`** is the disk's history, and its `source` is the hub's own `GET /seeders/` for this seeder. The numbers are copied from there for a reader's convenience; the hub's answer wins, and a marketplace reads the hub rather than the file for anything it ranks on. +11. **`payout`** names the `ippay` payee and the network it is paid on. The address itself lives at the hub, registered by the seeder key, and this file never carries it. +12. **`holding`** is a URL that answers with what the disk holds right now, or the same list inline. The shape is below. +13. **Unknown keys are kept.** A disk says more than this document names, and a reader passes it through under the disk's own key. + +Serve it as `application/json`. The descriptor is a claim; that it came from the disk's own origin is one verification, and the hub's record under `key` is the other. + +## Holding + +The other half of a `holders` list in [OpenFile](/openfile): what one disk is holding, from the disk's side. + +```json +{ + "updated": "2026-09-13T06:14:02Z", + "holding": [ + { "id": "sha256:d6c3f828…c493ff", "swarm": "sha256:4b74eb43…c7a342", "lease": "sha256:5c02…", "gib": 0.68, "since": "2026-09-05T18:30:00Z", "until": "2026-10-05T18:00:00Z", "provenAt": "2026-09-13T06:00:05Z" }, + { "id": "sha256:1f9e…", "swarm": "sha256:88c0…", "lease": "sha256:0e4d…", "gib": 122.4, "since": "2026-08-01T00:00:00Z", "until": "2027-08-01T00:00:00Z", "provenAt": "2026-09-13T00:00:02Z" } + ] +} +``` + +`id` is the OpenFile content hash when the disk knows it, `swarm` the `infohashV2`, `lease` the `paid2seed` lease. A private swarm's `id` is its `plainRoot` from the manifest, which reveals nothing about the bytes; a disk holding `personal` swarms omits them from the public list, because a backup's existence is the requester's to announce. + +## Renting a disk + +A requester that has read a descriptor and wants the disk: + +1. Checks `accepts` against what it wants kept, and `record.source` at the hub for the disk's standing. +2. Makes its attestation, as `pay2seed` §3 requires, and posts a `pay2seed.offer` at one of the disk's `hubs` with `priceUsdPerGibMonth` at or above `price.per_gib_month` and `seeders.min` of one. +3. Waits for the disk's seeder client to take the lease, which it does on its next poll or at once if the hub pushes to `proof.webhook`. +4. Watches the lease as `pay2seed` §7 says: proven periods, spend against the budget, the next challenge due. + +A marketplace collapses those into one act: read the disk, post the offer, and show the lease when it lands. A requester that wants several disks posts one offer with `seeders.min` above one and lets the hub fill it, or reads several descriptors and posts to each. Nothing in the swarm side changes: leases, challenges, probes, receipts and payout are `paid2seed` exactly as written. + +## Marketplaces + +A marketplace reading descriptors: + +1. **Fetches on a schedule, hourly at least**, because `free_gib` moves. A disk that has told the marketplace its `updated` has not changed is skipped. +2. **Dedupes on `key`.** One seeder identity is one disk, whatever URL its descriptor was found at. A descriptor with no `key` dedupes on its origin. +3. **Reads standing from the hub**, never from the file, for anything it sorts or filters by. The file says where; the hub says how much. +4. **Lists a disk's policy beside its price**, so a requester sees that a cheap disk takes public swarms only, or ciphertext only, before it posts. +5. **Marks a disk gone, not deleted**, when its descriptor stops answering, and shows the leases it still holds until they end. +6. **Reports absence as absence.** An unstated operator is unstated. An unstated `accepts` means the disk will say when the offer arrives. + +[d1sks.com](https://d1sks.com) is the reference marketplace: a directory of every OpenDisk descriptor it has read, standing pulled from the hubs each disk names, and an offer form that posts to the disk's hub. [nichedb.dev](https://nichedb.dev) lists every disk in its hosting collection as an OpenServer offer, by the mapping below. + +## As an OpenServer offer + +A disk is a hosting offer, and a directory that reads [OpenServer](/openserver) descriptors lists it as one without a second parser: + +| OpenServer | from OpenDisk | +|---|---| +| `provider.name`, `provider.web`, `provider.operator`, `provider.country` | `name`, `web`, `operator`, `location.countries[0]` | +| `offers[].id` | `key`, or the descriptor's origin when there is no key | +| `offers[].name` | `name` | +| `offers[].url` | the descriptor's URL | +| `offers[].kind` | `storage` | +| `offers[].premises`, `management`, `tenancy` | `off-prem`, `unmanaged`, `shared` | +| `offers[].model` | `p2p` when the operator is a person or a peer, `centralized` when it is a provider; the disk may state `model` itself and that wins | +| `offers[].location`, `network`, `storage` | the same keys, unchanged | +| `offers[].price` | `{ "amount": price.per_gib_month, "currency": price.currency, "interval": "month", "unit": "gib" }`, `unit` kept as the provider's own key | +| `offers[].stock` | `in_stock` when `free_gib` is at least `price.min_gib`, else `out_of_stock` | +| `offers[].updated` | `updated` | + +A provider that sells storage plans and rents disk to swarms serves both files: an OpenServer descriptor for its plans and an OpenDisk descriptor for what a seeder can lease. A peer with one drive serves only this one, and is listed in both places. + +## What is deliberately absent + +**No lease in this file.** Leases, proofs, receipts and payout are `paid2seed`, and this document does not restate them. The descriptor is the ask; the offer is the bid; the lease is the match, and it is made at the hub. + +**No escrow at the disk.** Money sits at a hub, as `pay2seed` says. A disk never holds a requester's funds and never has to be trusted with them. + +**No reputation of its own.** `record` is a copy of what a hub signed, and a marketplace reads the hub. A disk that claims a standing its hub does not confirm is listed with the hub's number. + +**No plaintext.** A disk holds what `accepts` says, and by default that is `ipfile` ciphertext it cannot read. A `personal` swarm's existence is not in the public `holding` list. + +**No central registry.** Anyone may serve a descriptor and anyone may read it. d1sks.com is one marketplace among any number, and two marketplaces reading the same file list the same disk. + +## Serving one + +By hand, or by the seeder client. `torlnk serve` and a c0mpute node with `--openswarm` have every number this file needs: free space is the filesystem, the price and policy are the operator's configuration, the record is the hub's. Write it to the web root next to `robots.txt` and rewrite it when a lease starts or ends. + +## Related standards + +- [OpenSwarm](/openswarm): `pay2seed` for the offer and the attestation, `paid2seed` for leases, proofs, receipts and standing, `ippay` for the payee, `ipfile` for what is held. +- [OpenFile](/openfile): the publisher's side, and the `holders` list a disk appears in. +- [OpenServer](/openserver): the hosting offer a disk maps onto, and the units this document borrows. +- [OpenProfile.md](/openprofile): the `operator` behind a disk. + +## Version history + +| Version | Date | Change | +|---|---|---| +| 0.1 | 2026-09-13 | First publication: the descriptor, holding, renting a disk, marketplaces, the OpenServer mapping. | + +## License + +The specification text is CC BY 4.0. Serve it, copy it, extend it. diff --git a/docs/openfile.md b/docs/openfile.md new file mode 100644 index 0000000..a9d8fe4 --- /dev/null +++ b/docs/openfile.md @@ -0,0 +1,192 @@ +# OpenFile + +OpenFile is one file a publisher serves about the files it has published: what each one is, how big, how to fetch it over a swarm or over plain HTTP, how to verify the bytes, on what basis it may be distributed, what it costs, and who is holding it right now. A directory reads the publisher's own file instead of crawling the DHT for bare infohashes, a browser with no torrent client still gets the bytes, and the publisher stays the author of its own listing. It is the web-facing door onto an [OpenSwarm](/openswarm) `ipfile` swarm, and it works for a plain HTTP download too. It is maintained by Profullstack, Inc. as part of the LogicSRC open-standards surface. + +Status: **0.1**. A description of a file a directory will read, published so a publisher can serve one and any reader can read it. + +Slug: `openfile` + +## The problem + +A file on a swarm is findable by its infohash and by nothing else. A DHT crawl sees forty million of them and can say what none of them are. OpenSwarm fixed the inside of the swarm: an `ipfile` manifest names the file, signs the price and the payout, and encrypts the pieces; `pay2seed` attaches consent and a README; `ipdb` replicates the catalogue between peers. What none of that gives a person with a browser, a search engine, or a directory is a URL to start from. The manifest lives on the DHT under a key, the catalogue is a feed found through the DHT, and a reader that speaks only HTTP is outside looking in. + +The pieces exist. `/.well-known/` is where a host says things about itself. Gateways already serve swarm bytes as webseeds. What is missing is the one file that puts a publisher's catalogue where an HTTP reader can fetch it, in a shape every reader agrees on, so a file can be found instead of crawled. + +## Terms + +- A **publisher** is whoever put a file up and signed for it: a person, an organisation, an agent. Its **descriptor** is the file it serves about its files. +- A **file** is one published thing: a document, a recording, a dataset, a release, a bundle. On a swarm it is one `ipfile` manifest; over HTTP it is one URL. +- A **holder** is anything that currently has the bytes and will serve them: a seeder with a lease, a gateway, a peer. +- A **directory** is anything that reads descriptors and lists files across publishers: a search index, a database, a media site, an agent's own cache. +- A **reader** is anything that reads a descriptor: a directory, a person's terminal, a program, an agent. + +## The descriptor + +A publisher serves a JSON document at `/.well-known/openfile.json` on its own origin. + +```json +{ + "publisher": { + "name": "Dartmoor Field Recordings", + "web": "https://dartmoor.example", + "operator": "https://dartmoor.example/.well-known/openprofile.md", + "key": "ed25519:5d292428e8a68946e5225136c8b10e8f33ab78a45e1663d0730996dd2b63d59e", + "feed": "ed25519:5d292428e8a68946e5225136c8b10e8f33ab78a45e1663d0730996dd2b63d59e/default", + "hubs": ["https://bittorrented.com/api/openswarm"] + }, + "updated": "2026-09-13T06:00:00Z", + "files": [ + { + "id": "sha256:d6c3f8285b7871d6a400cba14408288a9acde679f12e1e7dc276f29ca7c493ff", + "name": "interview-2026-09-05.flac", + "url": "https://dartmoor.example/recordings/interview-2026-09-05", + "descriptor": "https://dartmoor.example/recordings/interview-2026-09-05.openfile.json", + "size": 734003200, + "contentType": "audio/flac", + "pieces": { "length": 1048576, "count": 700, "layer": "https://gw.c0mpute.com/swarm/4b74eb43677e4d03af5fb0856333f9aa21d9a5a3bbf944b13aa7eef379c7a342/layer" }, + "swarm": { + "file": "ed25519:0d87e09c7fea3ad6ba6c2f3e027ea47f5b245452899910948470906704c5295d", + "infohashV1": "sha1:a3ce2180413415d7cf4268fb892b8ffd539e8459", + "infohashV2": "sha256:4b74eb43677e4d03af5fb0856333f9aa21d9a5a3bbf944b13aa7eef379c7a342", + "manifest": "https://gw.c0mpute.com/swarm/4b74eb43677e4d03af5fb0856333f9aa21d9a5a3bbf944b13aa7eef379c7a342/manifest", + "trackers": ["wss://tracker.openwebtorrent.com", "udp://tracker.opentrackr.org:1337/announce"], + "private": false + }, + "encryption": "ipfile", + "fetch": [ + { "kind": "ipfile", "url": "magnet:?xs=urn:btpk:0d87e09c7fea3ad6ba6c2f3e027ea47f5b245452899910948470906704c5295d&s=ipfile" }, + { "kind": "webseed", "url": "https://gw.c0mpute.com/swarm/4b74eb43677e4d03af5fb0856333f9aa21d9a5a3bbf944b13aa7eef379c7a342/data" }, + { "kind": "http", "url": "https://gw.c0mpute.com/file/d6c3f8285b7871d6a400cba14408288a9acde679f12e1e7dc276f29ca7c493ff" }, + { "kind": "hls", "url": "https://gw.c0mpute.com/file/d6c3f8285b7871d6a400cba14408288a9acde679f12e1e7dc276f29ca7c493ff/index.m3u8" } + ], + "attestation": { + "record": "sha256:8e1c4a0b9d2f7e6c5b4a3d2e1f0c9b8a7d6e5f4c3b2a1d0e9f8c7b6a5d4e3f2c", + "basis": "own", + "license": "CC-BY-4.0", + "notice": "https://dartmoor.example/.well-known/pay2seed-notice" + }, + "readme": "https://dartmoor.example/recordings/interview-2026-09-05/README.md", + "price": { "amount": 0.5, "currency": "USD", "per": "key", "offer": "https://keys.dartmoor.example/openswarm/grant?file=0d87e09c" }, + "holders": "https://bittorrented.com/api/openswarm/files/d6c3f8285b7871d6a400cba14408288a9acde679f12e1e7dc276f29ca7c493ff/holders", + "updated": "2026-09-13T06:00:00Z" + } + ] +} +``` + +The smallest valid descriptor is a publisher with a name and a file with a content hash and a name: + +```json +{ "publisher": { "name": "Dartmoor Field Recordings" }, "files": [{ "id": "sha256:d6c3f828…c493ff", "name": "interview-2026-09-05.flac" }] } +``` + +The rules, and every one degrades: + +1. **`publisher.name`, `files[].id` and `files[].name` are the only required keys.** A descriptor with those alone is valid. A reader lists what it was given and reports the rest as unstated rather than assumed. +2. **`files[].id` is the content hash of the plaintext**, `sha256:` and hex. For an `ipfile` swarm it is the manifest's `plainRoot`, so the id a reader gets here is the root the decrypted file verifies against. For a plain HTTP file it is the SHA-256 of the bytes. It is the dedupe key: two publishers serving the same bytes list the same id, and a directory that meets the same id twice has one file with two publishers, not two files. +3. **`publisher`** is who put it up. `web` is the site, `operator` the person or organisation answerable as an [OpenProfile.md](/openprofile) URL, `key` the OpenSwarm publisher key that signed the manifests, `feed` the `ipdb` feed a swarm reader can follow instead of polling this file, `hubs` the `ippay` hubs whose passes the publisher accepts. A publisher with no swarm has none of the last three. +4. **`updated`** on the descriptor is when anything in it last changed; **`updated`** on a file is when that file last changed and wins for that file. A reader with the descriptor's `updated` unchanged since its last fetch may skip the rest. +5. **`descriptor`** is the URL of the same file object served on its own, next to the file: `.openfile.json`, or wherever the publisher puts it. A reader handed a single file's descriptor by that route has everything below without the listing. +6. **`size`, `contentType`, `pieces`** describe the bytes. `size` is the plaintext length in bytes. `pieces.length` is the piece length, a power of two; `pieces.count` follows from it; `pieces.layer` is where to fetch the plaintext piece layer, so a reader can verify each piece as it arrives rather than the whole at the end. +7. **`swarm`** is how a torrent client reaches it: the `ipfile` file key, both infohashes, where the signed manifest can be fetched over HTTP, the trackers, and whether the swarm is private. Absent means there is no swarm and `fetch` is the whole story. +8. **`encryption`** is `ipfile` or `none`. Absent means `ipfile`: a file on a swarm is ciphertext by default, and a publisher that wants anyone to read the bytes off the wire says `none` as an explicit act. It says nothing about HTTP fetches, which a gateway serves decrypted to a pass holder. +9. **`fetch`** is the list of ways to get the bytes, each `{kind, url}`, in the publisher's order of preference. `kind` is `ipfile` (a magnet for an OpenSwarm client), `magnet` (a vanilla magnet), `webseed` (BEP 19 ciphertext by range), `http` (the plaintext by range from a gateway, behind a pass when there is a price), `hls` (a media file as a standard playlist from a gateway, sealed when there is a price). A reader picks the first kind it speaks. A browser with nothing installed speaks `http` and `hls`, which is the point. +10. **`attestation`** is the consent the file was published under, as `pay2seed` defines it: the record id, the `basis` (`own`, `licensed`, `open-license`, `public-domain`, `personal`), the SPDX `license` when the basis is a licence, and the `notice` endpoint. A file with no attestation is listed as unattested, and a directory that requires consent does not list it. +11. **`readme`** is the URL of the swarm's `README.md`, the one `pay2seed` requires at the root of every listed swarm. A directory renders it as the file's page. +12. **`price`** is what the bytes or the key cost, or absent for free. `amount` and `currency` as ISO 4217; `per` is `key` (one grant, the `ipfile` `keyUsd`), `gib` (per GiB served, the `ipfile` `perGib`), or `fetch` (a flat price for an HTTP download). `offer` is a URL that answers `402` with an x402 offer, exactly as `ippay` sells a pass, so a reader with a wallet and no hub account can pay where it stands. +13. **`holders`** is a URL that answers with who has the bytes right now, or the same list inline. The shape is below. +14. **Unknown keys are kept.** A publisher says more than this document names, and a reader passes it through under the publisher's own key. `ipaudio`, `ipvideo` and any later member of the family put their record here. + +Serve it as `application/json`. The descriptor is a claim; that it came from the publisher's own origin is one verification, and the manifest's signature by `publisher.key` is the other. + +## Holders + +A file is only as available as the machines holding it. `holders` answers the question a reader asks before it commits to a fetch: + +```json +{ + "id": "sha256:d6c3f8285b7871d6a400cba14408288a9acde679f12e1e7dc276f29ca7c493ff", + "updated": "2026-09-13T06:14:02Z", + "holders": [ + { "kind": "seeder", "id": "ed25519:a41e…", "lease": "sha256:5c02…", "provenAt": "2026-09-13T06:00:05Z", "countries": ["DE"], "disk": "https://seeder-a41e.example/.well-known/opendisk.json" }, + { "kind": "gateway", "url": "https://gw.c0mpute.com", "seenAt": "2026-09-13T06:13:40Z", "countries": ["US"] }, + { "kind": "peer", "id": "ed25519:77aa…", "seenAt": "2026-09-13T05:58:11Z" } + ] +} +``` + +- **`kind`** is `seeder` (holds a `paid2seed` lease and passed its last proof), `gateway` (serves the file over HTTP), or `peer` (was seen in the swarm; nothing is promised). +- **`provenAt`** is when a seeder last answered a storage challenge or probe; **`seenAt`** is when a gateway or peer was last reached. A reader shows the age and treats a holder older than one proof period as unknown. +- **`lease`** is the `paid2seed` lease record, so a reader can check the seeder's standing at the hub rather than take the list's word. +- **`disk`** is the holder's own [OpenDisk](/opendisk) descriptor when it serves one, so a reader that likes a holder can rent more of it. + +The list is usually served by a hub, because the hub is what holds the leases and runs the proofs; `bittorrented.com` serves it for every swarm it lists. A publisher may inline a snapshot for a file it seeds itself. Either way the list is a claim about the moment it was made, and a reader that needs certainty fetches. + +## Discovery + +A reader finds a descriptor four ways, in this order: + +1. `/.well-known/openfile.json` on the publisher's origin. +2. `` in the HTML of a page about a file, or a `Link: <...>; rel="openfile"` header on the file itself or its page. +3. `.openfile.json` next to the file, for a directory listing or a static site. +4. A URL handed to the reader directly. + +A descriptor is **verified** when it was fetched from the same origin as `publisher.web`, or from `/.well-known/` on the origin the reader was pointed at. A file inside it is verified a second way when its `swarm.manifest` fetches, its first signature is by `swarm.file`, its second is by `publisher.key`, and its `plainRoot` equals `id`. A directory shows both facts. One found by the fourth route on some other host is a claim about the publisher by whoever hosts it, and a directory marks it so. + +## Fetching and verifying + +A conforming reader that wants the bytes: + +1. Picks the first `fetch` entry whose `kind` it speaks. +2. Pays if there is a `price`: a swarm client buys a pass at one of `publisher.hubs`; an HTTP client requests `price.offer`, gets a `402` and an x402 offer, pays, and presents the pass as `Authorization: Bearer` on the gateway, as `ippay` §7 defines. +3. Fetches. Over `ipfile` it verifies each piece against the plaintext piece layer as `ipfile` §5.6 says. Over `http` it fetches the layer from `pieces.layer` and verifies each piece the same way, or hashes the whole and compares with `id` at the end when there is no layer. +4. Refuses bytes that fail either check, and says which holder served them. + +Nothing in this is new. It is `ipfile` §10 written for a client that started from a URL instead of a key. + +## Directories + +A directory reading descriptors: + +1. **Fetches on a schedule, daily at least**, and follows `publisher.feed` on the swarm when it can, because the feed is the same catalogue with a head a DHT `get` finds. +2. **Dedupes on `id`.** The same content hash from two publishers is one file with two listings, and a reader sees both names and both attestations. +3. **Keeps the publisher's words.** The `name`, the README, the extra keys. A directory normalises for search and displays what the publisher wrote. +4. **Lists nothing it could not verify from the origin or the manifest**, and marks which of the two it has. +5. **Shows the basis** beside every file it lists, as `paid2seed` §6.2 makes a seeder client show it. A reader knows whether it is looking at somebody's own work, an open licence, or a claim. +6. **Reports absence as absence.** An unstated price is free; an unstated attestation is unattested, not consented; an unstated holder list is unknown, not empty. + +The first directory reading OpenFile is `bittorrented.com`, which today lists bare infohashes from a DHT crawl and will list consented swarms beside them with their README as the page. A file's `holders` there come from its own leases and probes. [nichedb.dev](https://nichedb.dev) lists holders that serve an OpenDisk descriptor in its hosting collection. + +## What is deliberately absent + +**No new swarm format.** The swarm is `ipfile`, the manifest is `ipfile`'s, the consent is `pay2seed`'s, the payment is `ippay`'s. This document is a JSON door onto records that already exist, so a publisher that already serves a swarm writes the file from what it has. + +**No search.** A descriptor lists one publisher's files. Finding a file across publishers is a directory's job, and two directories reading the same descriptors list the same files. + +**No trust score.** `verified` is a fact about where the file came from and who signed the manifest. `basis` is what the publisher claimed. Whether either is true is the reader's judgement, with the operator's profile and the notice endpoint as the place to start. + +**No DRM.** A pass holder gets the key and the bytes, as `ipfile` says. A publisher that wants to control a device after delivery is reading the wrong specification. + +**No product domain yet.** A directory and a marketplace for OpenFile is planned under a name not yet chosen. `bittorrented.com` is the reference reader until then. + +## Serving one + +By hand, from the manifests a publisher already signed. `ip file add` writes the swarm; the descriptor is the same fields, exported, at a fixed URL. A static site commits `openfile.json` next to `robots.txt` and one `.openfile.json` beside each file. + +## Related standards + +- [OpenSwarm](/openswarm): `ipfile` for the swarm and manifest, `pay2seed` for the attestation and the README, `paid2seed` for leases and proofs, `ippay` for passes and the x402 offer, `ipdb` for the feed. +- [OpenDisk](/opendisk): what a holder serves about the disk it rents, and how a publisher buys more holders. +- [OpenServer](/openserver): how a gateway or a seeder is listed as a hosting offer. +- [OpenProfile.md](/openprofile): the `operator` behind a publisher. +- [OpenMCP](/openmcp): a directory that also serves its rows over MCP describes that door with an OpenMCP descriptor. + +## Version history + +| Version | Date | Change | +|---|---|---| +| 0.1 | 2026-09-13 | First publication: the descriptor, the per-file descriptor, holders, discovery, fetching and verifying, what a directory owes a publisher. | + +## License + +The specification text is CC BY 4.0. Serve it, copy it, extend it. diff --git a/docs/openswarm.md b/docs/openswarm.md index aedd2ae..e314125 100644 --- a/docs/openswarm.md +++ b/docs/openswarm.md @@ -38,6 +38,8 @@ a different product. The member protocols keep their `ip` names. | `paid2seed` | Server protocol for paid seeding: leases, storage challenges and probes over the wire, GiB-month settlement, the seeder client | [`paid2seed.md`](./openswarm/paid2seed.md) | | `pay2stream` | Client protocol for paid live streams: consent for channels, relay offers, tickets and listings, watching as a peer or on any HLS player | [`pay2stream.md`](./openswarm/pay2stream.md) | | `paid2stream` | Server protocol for paid live streams: relay leases per hour, presence proofs, gateways serving standard HLS, M3U and EPG | [`paid2stream.md`](./openswarm/paid2stream.md) | +| OpenFile | The web door onto an `ipfile` swarm: `/.well-known/openfile.json` lists a publisher's files with fetch routes, verification, consent, price and holders | [`openfile.md`](./openfile.md) | +| OpenDisk | The web door onto a `paid2seed` seeder: `/.well-known/opendisk.json` lists free GiB, price, policy, proof cadence and hub standing, so a disk is found instead of waited for | [`opendisk.md`](./opendisk.md) | Supporting documents: