From 6a8ba195891c998d509a89a5a1785ee9d94aacc4 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sat, 12 Sep 2026 18:35:43 -0700 Subject: [PATCH 1/2] OpenServer 0.1: one file a hosting provider serves about what it sells (#161) * OpenServer 0.1: one file a hosting provider serves about what it sells A new LogicSRC spec at /openserver and /docs/openserver. A provider puts the table its order form already reads at /.well-known/openserver.json: every offer with a kind, four axes, specs in fixed units, one price, location and stock. A directory reads the provider instead of scraping an aggregator whose terms forbid it, and the provider stays the author of its own catalog. Only provider.name and each offer's name are required; every other rule degrades. Fifteen kinds cover what Anthony listed and the rest of the market: cloud, vps, dedicated, bare-metal, colocation, on-prem, shared, managed, paas, serverless, storage, gpu, edge, p2p and hybrid. Premises, management, tenancy and model are their own keys rather than inferred from the kind, because a managed VPS and an unmanaged one are the same kind and different offers. A peer-to-peer market publishes one descriptor whose offers are its current asks, with the operator pointing at the market and not the peer; c0mpute is the compute case, OpenDisk the storage case, OpenSwarm the settlement layer under both. The first reader is nichedb.dev's hosting collection, being built alongside this. findhost.app is named as the curated sibling. Registered in DOC_SLUGS, NAV, STATIC_ROUTES and llms.txt, one line each. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Khk1C6Ese6xjdHAWLVstca * OpenServer: name the resource blocks an offer may carry OpenCPU, OpenMemory, OpenGPU and OpenBandwidth are being written as the blocks that nest inside an offer's compute, compute.gpu and network, and that stand alone as offers. Related standards now says so, with links at /docs/ where those specs will land. No subdomain is named anywhere in this spec: every LogicSRC spec lives on logicsrc.com only. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Khk1C6Ese6xjdHAWLVstca --------- Co-authored-by: Claude Fable 5.1 --- apps/logicsrc-web/src/app/llms.txt/route.ts | 1 + apps/logicsrc-web/src/app/openserver/page.tsx | 274 ++++++++++++++++++ apps/logicsrc-web/src/app/sitemap.ts | 1 + .../src/components/site-shell.tsx | 1 + apps/logicsrc-web/src/lib/docs.ts | 1 + docs/openserver.md | 220 ++++++++++++++ 6 files changed, 498 insertions(+) create mode 100644 apps/logicsrc-web/src/app/openserver/page.tsx create mode 100644 docs/openserver.md diff --git a/apps/logicsrc-web/src/app/llms.txt/route.ts b/apps/logicsrc-web/src/app/llms.txt/route.ts index 563f3fd..2659193 100644 --- a/apps/logicsrc-web/src/app/llms.txt/route.ts +++ b/apps/logicsrc-web/src/app/llms.txt/route.ts @@ -23,6 +23,7 @@ export function GET(): Response { - [OpenProfile.md](${SITE_URL}/openprofile): One Markdown file that says who you are and where you are, for people and agents alike: identity block, accounts, topics, reshare terms and operator, discovered at /.well-known/openprofile.md or through rel="openprofile". - [OpenMCP](${SITE_URL}/openmcp): An open catalog of MCP relays: a relay serves /.well-known/openmcp.json, a catalog probes it and lists only what it found, and clients reach every relay through the catalog's REST, its own MCP endpoint, or signed webhooks. - [OpenAccess](${SITE_URL}/openaccess): OAuth 2.1 with a grant you can carry: one hub account per person or agent, apps keep their own users and link them once, grants delegate narrower to agents, and a subscription bought in one app is honoured by every app that honours the product. Reference hub at openaccess.logicsrc.com. +- [OpenServer](${SITE_URL}/openserver): One file a hosting provider serves about what it sells, at /.well-known/openserver.json: every offer with kind (cloud, vps, dedicated, bare-metal, colocation, on-prem, shared, managed, paas, serverless, storage, gpu, edge, p2p, hybrid), the premises, management, tenancy and model axes, specs, one price, location and stock. Directories read the provider instead of scraping; first reader is nichedb.dev/c/hosting. - [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/openserver/page.tsx b/apps/logicsrc-web/src/app/openserver/page.tsx new file mode 100644 index 0000000..c474478 --- /dev/null +++ b/apps/logicsrc-web/src/app/openserver/page.tsx @@ -0,0 +1,274 @@ +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: "OpenServer ยท LogicSRC", + description: + "OpenServer is one file a hosting provider serves about what it sells: every server, instance, rack, function and peer-market listing, with specs, price, location and stock, at /.well-known/openserver.json. Cloud, VPS, dedicated, bare metal, colocation, on-prem, managed, unmanaged, PaaS, serverless, storage, GPU, edge and p2p.", + alternates: { canonical: "/openserver" } +}; + +const DESCRIPTOR = `{ + "provider": { + "name": "Northwind Hosting", + "web": "https://northwind.example", + "operator": "https://northwind.example/.well-known/openprofile.md", + "country": "NL", + "status": "https://status.northwind.example" + }, + "updated": "2026-09-13T06:00:00Z", + "offers": [ + { + "id": "vps-arm-4", + "name": "ARM 4", + "url": "https://northwind.example/vps/arm-4", + "kind": "vps", + "premises": "off-prem", + "management": "unmanaged", + "tenancy": "shared", + "model": "centralized", + "location": { "regions": ["ams1", "fra1"], "countries": ["NL", "DE"] }, + "compute": { "vcpu": 4, "ram_mb": 8192, "arch": "arm64" }, + "storage": [{ "type": "nvme", "size_gb": 80 }], + "network": { "bandwidth_mbps": 1000, "transfer_gb": 4000, "ipv4": 1, "ipv6": true }, + "price": { "amount": 7.5, "currency": "EUR", "interval": "month" }, + "stock": "in_stock" + } + ] +}`; + +const MINIMAL = `{ "provider": { "name": "Northwind Hosting" }, "offers": [{ "name": "ARM 4" }] }`; + +const KINDS: Array<[string, string]> = [ + ["cloud", "a virtual machine on a cloud platform, billed by the hour or the second, with an API"], + ["vps", "a virtual private server, billed by the month"], + ["dedicated", "a whole physical server rented from the provider's rack"], + ["bare-metal", "a whole physical server with cloud-style provisioning and hourly billing"], + ["colocation", "rack space, power and network for a server the buyer owns"], + ["on-prem", "hardware or an appliance sold or leased to run on the buyer's premises"], + ["shared", "a slice of a server the provider administers, typically web hosting with a control panel"], + ["managed", "a server the provider runs for the buyer, sold as the service rather than the box"], + ["paas", "a platform that takes code and runs it, with no server the buyer sees"], + ["serverless", "functions or containers billed by invocation or by the second of use"], + ["storage", "object, block or file storage sold on its own"], + ["gpu", "compute sold for the accelerator, whatever runs beside it"], + ["edge", "compute placed near users at many small points of presence"], + ["p2p", "a listing on a decentralised marketplace where the seller is a peer, not the operator"], + ["hybrid", "a bundle that spans premises, such as an appliance with a cloud control plane"] +]; + +const AXES: Array<[string, string, string]> = [ + ["premises", "on-prem, off-prem, hybrid", "where the hardware physically is"], + ["management", "managed, unmanaged, co-managed", "who administers the operating system and what runs on it"], + ["tenancy", "shared, dedicated", "whether the hardware is shared with other customers"], + ["model", "centralized, p2p", "whether one operator runs the hardware, or peers do"] +]; + +const DIRECTORY: Array<[string, string]> = [ + ["Fetch daily at least", "Offers change price and stock. A descriptor read once is a snapshot, not a catalog. The descriptor's updated says whether the rest can be skipped."], + ["Dedupe on origin + id", "A re-read updates the row and never adds a second. An offer that leaves the file is marked gone, not deleted."], + ["Keep the provider's words", "The name, the region names, the extra keys. Normalise for search, display what the provider wrote."], + ["Attribute the provider", "Every offer links to its url, and the directory says where the file came from and when it was read."], + ["Report absence as absence", "An unstated stock is unknown, not in stock. An unstated axis is unstated."] +]; + +const ABSENT: Array<[string, string]> = [ + ["No ordering", "The file says what is for sale, not how to buy it. Every provider has an order form or a provisioning API, and url on each offer leads to it."], + ["No reviews, no trust score", "verified means the file came from the provider's own origin. Whether a provider is good is the reader's judgement."], + ["No benchmarks", "A descriptor says what an offer is specified as, in the provider's words. What it measures at is another document's business."], + ["No central registry", "Anyone may read any provider's file. A directory is one reader among many, and two directories reading the same file list the same offers."] +]; + +export default function OpenServerPage(): ReactNode { + return ( + +
+
+

LogicSRC standards surface

+

OpenServer

+

+ One file a hosting provider serves about what it sells. A directory reads the + provider instead of scraping a page, and the provider stays the author of its own + catalog. +

+
+

+ Every provider publishes its catalog as a web page, every comparison site scrapes those + pages, and the comparison site's terms then forbid anyone from scraping the scrape. + The provider, who wanted its offers seen, has no say in how they appear. A buyer's + agent that wants a 4 vCPU ARM box in Europe under 10 a month, in stock, reads twenty + pricing pages twenty different ways. OpenServer puts the table the order form already + reads at /.well-known/openserver.json, in one shape every + reader agrees on, so an offer can be found instead of scraped. +

+

+ Status: 0.1. It covers cloud, VPS, dedicated and bare-metal servers, colocation, + hardware for your own premises, shared and managed hosting, platforms, functions, + storage, GPU, edge and peer-to-peer markets. The first directory reading it is the hosting + collection at nichedb.dev. +

+
+ +
+
+

The descriptor

+

+ Served at /.well-known/openserver.json. Only{" "} + provider.name and each offer's{" "} + name are required. +

+
+
{DESCRIPTOR}
+

+ id is the dedupe key: the same origin and id tomorrow is the + same row. compute, storage and{" "} + network use fixed units, mebibytes for memory, gigabytes for + disks, megabits for bandwidth. price is one amount, an ISO + currency and an interval of hour, month, year or once. stock is + in_stock, out_of_stock, preorder or unknown. operator is the + person answerable, as an OpenProfile.md. Unknown keys are + kept. The smallest valid file is one line: +

+
{MINIMAL}
+
+ +
+
+

Fifteen kinds, four axes

+

+ kind says what is sold. The axes cut across every kind and are + stated, not inferred, because a managed VPS and an unmanaged one are the same kind and + different offers. +

+
+ + + + + + + + + {KINDS.map(([kind, what]) => ( + + + + + ))} + +
kindwhat is sold
+ {kind} + {what}
+ + + + + + + + + + {AXES.map(([axis, values, meaning]) => ( + + + + + + ))} + +
axisvaluesmeaning
+ {axis} + + {values} + {meaning}
+

+ A peer-to-peer marketplace such as Akash, Flux, Golem, Salad or Vast.ai publishes one + descriptor whose offers are the market's current asks, one per listing with{" "} + kind: p2p and model: p2p, stock and + price updated as often as the market moves, and operator{" "} + pointing at the marketplace, not the peer. In this family,{" "} + c0mpute{" "} + is the compute marketplace, OpenDisk the disk-for-rent + peer, listable as an offer with kind: storage, and{" "} + OpenSwarm the settlement and proof layer under both. +

+
+ +
+
+

What a directory owes a provider

+
+ + + {DIRECTORY.map(([what, how]) => ( + + + + + ))} + +
+ {what} + {how}
+

+ A descriptor is verified when it was fetched from the provider's own origin. Found + through rel="openserver" or a URL handed to the + reader on some other host, it is a claim about the provider by whoever hosts it, and a + directory marks it so. +

+
+ +
+
+

What is deliberately absent

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

Where everything lives

+
+
    +
  • + Specification: the descriptor, fifteen kinds, four + axes, peer-to-peer markets, discovery, what a directory owes a provider +
  • +
  • + nichedb.dev/c/hosting: the first directory + reading it, with providers and offers as feeds over RSS, JSON, an API and MCP +
  • +
  • + findhost.app: a curated register of web hosts under + CC BY 4.0, the sibling from the other direction +
  • +
  • + OpenProfile.md, the operator behind a provider;{" "} + OpenMCP, how a directory describes its own MCP door +
  • +
  • + OpenCPU, OpenMemory,{" "} + OpenGPU and{" "} + OpenBandwidth: the resource blocks an offer's{" "} + compute, compute.gpu and{" "} + network may carry, each also an offer on its own +
  • +
+
+
+ ); +} diff --git a/apps/logicsrc-web/src/app/sitemap.ts b/apps/logicsrc-web/src/app/sitemap.ts index 01c56c6..53e72f8 100644 --- a/apps/logicsrc-web/src/app/sitemap.ts +++ b/apps/logicsrc-web/src/app/sitemap.ts @@ -25,6 +25,7 @@ const STATIC_ROUTES: Array<{ { path: "/openprofile", changeFrequency: "weekly", priority: 0.9 }, { path: "/openmcp", changeFrequency: "weekly", priority: 0.9 }, { path: "/openaccess", changeFrequency: "weekly", priority: 0.9 }, + { path: "/openserver", 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 0a32a47..7048287 100644 --- a/apps/logicsrc-web/src/components/site-shell.tsx +++ b/apps/logicsrc-web/src/components/site-shell.tsx @@ -17,6 +17,7 @@ const NAV: Array<{ href: string; label: string; external?: boolean }> = [ { href: "/openprofile", label: "OpenProfile" }, { href: "/openmcp", label: "OpenMCP" }, { href: "/openaccess", label: "OpenAccess" }, + { href: "/openserver", label: "OpenServer" }, { 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 b5e40b0..1800a65 100644 --- a/apps/logicsrc-web/src/lib/docs.ts +++ b/apps/logicsrc-web/src/lib/docs.ts @@ -20,6 +20,7 @@ export const DOC_SLUGS = [ "openprofile", "openmcp", "openaccess", + "openserver", "openstream", "openspec-comparison", "data-model", diff --git a/docs/openserver.md b/docs/openserver.md new file mode 100644 index 0000000..73d40ad --- /dev/null +++ b/docs/openserver.md @@ -0,0 +1,220 @@ +# OpenServer + +OpenServer is one file a hosting provider serves about what it sells: every server, instance, box, rack, function and peer-market listing it offers, with the specs, the price, where it runs and whether it is in stock. A directory reads the provider's own file instead of scraping an aggregator, a buyer's agent reads it instead of a pricing page, and the provider stays the author of its own words. It covers cloud, VPS, dedicated and bare-metal servers, colocation, hardware sold to run on your own premises, shared and managed hosting, platforms, functions, storage, GPU, edge, peer-to-peer markets and the hybrids in between. It is maintained by Profullstack, Inc. as part of the LogicSRC open-standards surface. + +Status: **0.1**. A description of a file a directory already reads, published so a provider can serve one and any directory can read it. + +Slug: `openserver` + +## The problem + +Every hosting provider publishes its catalog as a web page, and every comparison site scrapes those pages. The scrape breaks when the page changes, the comparison site's terms then forbid anyone else from scraping the scrape, and the provider, who wanted its offers seen, is the one party with no say in how they appear. A buyer's agent that wants "a 4 vCPU ARM box in Europe under 10 a month, in stock" reads twenty pricing pages twenty different ways, and the peer-to-peer markets, where the seller is a stranger and the price moved a minute ago, cannot be read by a page scraper at all. + +The pieces exist. Providers already keep this data in a database, because their order form reads it. `/.well-known/` is where a host says things about itself. What is missing is the one file that puts a provider's catalog where a reader can fetch it, in a shape every reader agrees on, so an offer can be found instead of scraped. + +## Terms + +- A **provider** is anyone who sells compute, storage or the space to run it: a cloud, a VPS host, a dedicated-server company, a colocation facility, a hardware vendor, a platform, a peer-to-peer marketplace. Its **descriptor** is the file it serves about itself. +- An **offer** is one thing a provider sells at a price: a plan, an instance type, a server configuration, a rack unit, an appliance, a market listing. +- A **directory** is anything that reads descriptors and lists offers across providers: a comparison site, a database, a search index, 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 provider serves a JSON document at `/.well-known/openserver.json` on its own origin. + +```json +{ + "provider": { + "name": "Northwind Hosting", + "web": "https://northwind.example", + "operator": "https://northwind.example/.well-known/openprofile.md", + "country": "NL", + "support": "https://northwind.example/support", + "status": "https://status.northwind.example", + "legal": "https://northwind.example/terms" + }, + "updated": "2026-09-13T06:00:00Z", + "offers": [ + { + "id": "vps-arm-4", + "name": "ARM 4", + "url": "https://northwind.example/vps/arm-4", + "kind": "vps", + "premises": "off-prem", + "management": "unmanaged", + "tenancy": "shared", + "model": "centralized", + "location": { "regions": ["ams1", "fra1"], "countries": ["NL", "DE"] }, + "compute": { "vcpu": 4, "ram_mb": 8192, "arch": "arm64" }, + "storage": [{ "type": "nvme", "size_gb": 80 }], + "network": { "bandwidth_mbps": 1000, "transfer_gb": 4000, "ipv4": 1, "ipv6": true }, + "price": { "amount": 7.5, "currency": "EUR", "interval": "month" }, + "stock": "in_stock", + "updated": "2026-09-13T06:00:00Z" + }, + { + "id": "dedi-epyc-16", + "name": "EPYC 16", + "url": "https://northwind.example/dedicated/epyc-16", + "kind": "dedicated", + "premises": "off-prem", + "management": "unmanaged", + "tenancy": "dedicated", + "model": "centralized", + "location": { "regions": ["ams1"], "countries": ["NL"] }, + "compute": { "cores": 16, "ram_mb": 131072, "arch": "x86_64" }, + "storage": [{ "type": "nvme", "size_gb": 1920 }, { "type": "nvme", "size_gb": 1920 }], + "network": { "bandwidth_mbps": 10000, "transfer_gb": 50000, "ipv4": 1, "ipv6": true }, + "price": { "amount": 129, "currency": "EUR", "interval": "month", "setup": 49, "commitment": "1 month" }, + "stock": "preorder" + }, + { + "id": "gpu-l40s-1", + "name": "L40S x1", + "url": "https://northwind.example/gpu/l40s", + "kind": "gpu", + "premises": "off-prem", + "management": "unmanaged", + "tenancy": "dedicated", + "model": "centralized", + "location": { "regions": ["fra1"], "countries": ["DE"] }, + "compute": { "vcpu": 16, "ram_mb": 65536, "arch": "x86_64", "gpu": { "model": "NVIDIA L40S", "count": 1, "vram_mb": 49152 } }, + "storage": [{ "type": "nvme", "size_gb": 500 }], + "price": { "amount": 1.4, "currency": "EUR", "interval": "hour" }, + "stock": "out_of_stock" + }, + { + "id": "market-ask-8c7f", + "name": "8 vCPU, 32 GB, RTX 4090 (peer 8c7f)", + "url": "https://market.northwind.example/asks/8c7f", + "kind": "p2p", + "premises": "off-prem", + "management": "unmanaged", + "tenancy": "dedicated", + "model": "p2p", + "location": { "countries": ["US"] }, + "compute": { "vcpu": 8, "ram_mb": 32768, "arch": "x86_64", "gpu": { "model": "NVIDIA RTX 4090", "count": 1, "vram_mb": 24576 } }, + "price": { "amount": 0.42, "currency": "USD", "interval": "hour" }, + "stock": "in_stock", + "updated": "2026-09-13T06:14:02Z" + } + ] +} +``` + +The smallest valid descriptor is a provider with a name and an offer with a name: + +```json +{ "provider": { "name": "Northwind Hosting" }, "offers": [{ "name": "ARM 4" }] } +``` + +The rules, and every one degrades: + +1. **`provider.name` and `offers[].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. **`provider`** is who sells. `web` is the site, `country` an ISO 3166-1 alpha-2 code for where the company is, `support` where a customer gets help, `status` the status page, `legal` the terms a purchase is under. `operator` is the person or organisation answerable, as an [OpenProfile.md](/openprofile) URL. +3. **`updated`** on the descriptor is when anything in it last changed. **`updated`** on an offer is when that offer last changed, and wins over the descriptor's for that offer. Both are ISO 8601. A reader with the descriptor's `updated` unchanged since its last fetch may skip the rest. +4. **`offers[].id`** is stable for as long as the offer is the same thing. It is the dedupe key: a reader that sees the same provider origin and `id` tomorrow updates its row rather than adding one. Absent, the reader derives one from `name`, and a renamed offer becomes a new one, which is the cost of not stating it. +5. **`kind`** is what is sold, one word from the list below. **`premises`**, **`management`**, **`tenancy`** and **`model`** are four axes that cut across every kind, each its own key so a reader filters on them without guessing from the kind. A `dedicated` offer is usually `tenancy: dedicated`; a `managed` one is usually `management: managed`; but the axes are stated, not inferred, because a managed VPS and an unmanaged one are the same kind and different offers. +6. **`location`** is where the offer runs. `regions` are the provider's own region names, unchanged, so a buyer can use them at the order form. `countries` are ISO codes, so a directory can group across providers. An on-prem offer has no location, because it runs wherever the buyer puts it. +7. **`compute`, `storage`, `network`** describe the thing. Units are fixed: `ram_mb` and `vram_mb` in mebibytes, `size_gb` in gigabytes, `bandwidth_mbps` in megabits per second, `transfer_gb` per interval. `vcpu` is threads sold, `cores` is physical cores; a dedicated box states `cores`, a virtual one states `vcpu`, and one may state both. `arch` is `x86_64`, `arm64`, `riscv64` or the provider's own word. `storage` is a list, one entry per volume, so two drives are two entries. `ipv4` is a count, `ipv6` a boolean. +8. **`price`** is one price. `amount` is a number, `currency` an ISO 4217 code, `interval` is `hour`, `month`, `year` or `once`. `setup` is a one-time amount on top. `commitment` is the shortest term a buyer signs for, in words the provider uses. An offer sold at several intervals is several offers with a shared prefix in `id`, or one offer at the interval the provider quotes first, and a reader shows what it was given. +9. **`stock`** is `in_stock`, `out_of_stock`, `preorder` or `unknown`. Absent means `unknown`. A directory that shows stock shows when it was read. +10. **Unknown keys are kept.** A provider says more than this document names, and a reader passes it through under the provider's own key. + +Serve it as `application/json`. The descriptor is a claim; that it came from the provider's own origin is the verification. + +## Kinds + +One word each. A provider picks the closest; the four axes say the rest. + +| kind | what is sold | +|---|---| +| `cloud` | a virtual machine on a cloud platform, billed by the hour or the second, with an API | +| `vps` | a virtual private server, billed by the month | +| `dedicated` | a whole physical server rented from the provider's rack | +| `bare-metal` | a whole physical server with cloud-style provisioning and hourly billing | +| `colocation` | rack space, power and network for a server the buyer owns | +| `on-prem` | hardware or an appliance sold or leased to run on the buyer's premises | +| `shared` | a slice of a server the provider administers, typically web hosting with a control panel | +| `managed` | a server the provider runs for the buyer, patches, backups and all, sold as the service rather than the box | +| `paas` | a platform that takes code and runs it, with no server the buyer sees | +| `serverless` | functions or containers billed by invocation or by the second of use | +| `storage` | object, block or file storage sold on its own | +| `gpu` | compute sold for the accelerator, whatever runs beside it | +| `edge` | compute placed near users at many small points of presence | +| `p2p` | a listing on a decentralised marketplace where the seller is a peer, not the operator | +| `hybrid` | a bundle that spans premises, such as an appliance with a cloud control plane | + +The axes: + +| key | values | meaning | +|---|---|---| +| `premises` | `on-prem`, `off-prem`, `hybrid` | where the hardware physically is: the buyer's site, the provider's, or both | +| `management` | `managed`, `unmanaged`, `co-managed` | who administers the operating system and what runs on it | +| `tenancy` | `shared`, `dedicated` | whether the hardware is shared with other customers | +| `model` | `centralized`, `p2p` | whether one operator runs the hardware, or peers do | + +Absent axes are unstated, and a reader says so rather than filling them in. + +## Peer-to-peer markets + +A decentralised marketplace, such as Akash, Flux, Golem, Salad or Vast.ai, publishes one descriptor at its own origin. Its `provider` is the marketplace, and its `operator` points at the marketplace, not at any peer. Its `offers` are the market's current asks: one offer per listing, `kind: p2p`, `model: p2p`, with `stock` and `price` updated as often as the market moves and `updated` on each offer saying when. A peer that also wants to be found on its own serves its own descriptor at its own origin, with `model: p2p` on the offers it lists there, and a directory that meets the same peer both ways keeps both rows, because the market's price and the peer's price are two facts. + +A market with thousands of asks may serve the current top of book rather than every ask, and say so in a key of its own. What it serves is what a reader lists. + +The compute case in this family is [c0mpute](https://github.com/profullstack/logicsrc/blob/master/docs/openswarm/c0mpute.md), the house peer-to-peer compute marketplace: its nodes are peers, its market is the descriptor's `offers`, and settlement and proof of work done are [OpenSwarm](/openswarm)'s business, not this document's. The storage case is [OpenDisk](/docs/opendisk), a peer publishing disk capacity for rent; an OpenDisk descriptor maps onto an OpenServer offer with `kind: storage` and `model: p2p`, and may be listed as one. + +## Discovery + +A reader finds a descriptor three ways, in this order: + +1. `/.well-known/openserver.json` on the provider's origin. +2. `` in the HTML of the provider's home page, or a `Link: <...>; rel="openserver"` header on it, when the file lives somewhere else. +3. A URL handed to the reader directly. + +A descriptor is **verified** when it was fetched from the same origin as `provider.web`, or from `/.well-known/` on the origin the reader was pointed at. One found by the third route on some other host is a claim about the provider by whoever hosts it, and a directory marks it so. + +## Directories + +A directory reading descriptors: + +1. **Fetches on a schedule, daily at least**, and whenever it is told the file changed. Offers change price and stock; a directory that read a descriptor once has a snapshot, not a catalog. +2. **Dedupes on the provider's origin and the offer's `id`.** A re-read updates the row; it never adds a second. An offer that leaves the descriptor is marked gone, not deleted, so a reader can see it was sold once. +3. **Keeps the provider's words.** The offer's `name`, the region names, the extra keys. A directory normalises for search and displays what the provider wrote. +4. **Attributes the provider.** Every listed offer links to its `url`, and the directory says where the descriptor came from and when it was read. +5. **Reports absence as absence.** An unstated `stock` is unknown, not in stock. An unstated axis is unstated. + +The first directory reading OpenServer is the hosting collection at [nichedb.dev](https://nichedb.dev/c/hosting), which lists providers and their offers as feeds, with RSS, JSON, an API and MCP over the same rows. [findhost.app](https://www.findhost.app), a curated register of web hosts published under CC BY 4.0, is the sibling from the other direction: it describes providers by hand, one attribute at a time, and could emit a descriptor per provider from what it already holds. A directory that reads both has the provider's own catalog and a curator's view of the provider, and shows which is which. + +## What is deliberately absent + +**No ordering.** The descriptor says what is for sale, not how to buy it. Every provider already has an order form or a provisioning API, and `url` on each offer leads to it. + +**No reviews, no trust score.** `verified` means the file came from the provider's own origin. Whether a provider is good is the reader's judgement, with the operator's profile and the status page as the place to start. + +**No benchmarks.** A descriptor says what an offer is specified as, in the provider's words. What it measures at is another document's business. + +**No central registry.** Anyone may read any provider's file. A directory is one reader among many, and two directories reading the same file list the same offers. + +## Serving one + +By hand, from the same table the order form reads. A provider with a database has everything the file needs; the file is that table, exported, at a fixed URL. A static site can commit the file next to `robots.txt`. + +## Related standards + +- [OpenSwarm](/openswarm): the settlement and proof layer under a peer-to-peer offer; [c0mpute](https://github.com/profullstack/logicsrc/blob/master/docs/openswarm/c0mpute.md) is its compute marketplace and [OpenDisk](/docs/opendisk) its disk-for-rent peer, each listable here as an offer. +- [OpenCPU](/docs/opencpu), [OpenMemory](/docs/openmemory), [OpenGPU](/docs/opengpu), [OpenBandwidth](/docs/openbandwidth): the resource blocks. An offer's `compute` (cpu and memory), `compute.gpu` and `network` may carry those specs' fields when the provider has them, and each can stand alone as an offer of its own. +- [OpenProfile.md](/openprofile): the `operator` behind a provider. +- [OpenMCP](/openmcp): a directory that also serves its rows over MCP describes that door with an OpenMCP descriptor. +- [OpenAccess](/openaccess): how a buyer's agent carries the credential it needs at the provider's order form, if the provider honours one. + +## Version history + +| Version | Date | Change | +|---|---|---| +| 0.1 | 2026-09-13 | First publication: the descriptor, fifteen kinds, four axes, peer-to-peer markets, discovery, what a directory owes a provider. | + +## License + +The specification text is CC BY 4.0. Serve it, copy it, extend it. From 41c362ddd88ac3e77316298e8e17327b481c7524 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sat, 12 Sep 2026 18:57:53 -0700 Subject: [PATCH 2/2] OpenProfile.md 0.2: a Match section for dating sites and anything else that pairs people (#164) Rule 9, `## Match` (Dating, Matching, Partner and Looking for normalise to it): the keys a matching platform needs, about you (Born, Gender, Orientation, Status, Monogamy, Height, Body, Children, Wants children, Smoking, Drinking, Cannabis, Drugs, Religion, Politics, Ethnicity, Education, Work, Diet, Pets, Exercise, Zodiac) and about who you seek (Seeking, For, Ages, Distance, Not). Values are kept as written and matched loosely like Topics; unknown keys are kept; absence is unstated. Two rules that do not degrade: Born is the one key a matching platform must have, and a computed age under 18 keeps the profile out of any matching context; and the section is public by nature, so a platform stores only what the person confirmed with it and drops it when the file does. A `## Photos` section carries image URLs, first is the lead. "No inference" joins the deliberately-absent list. Claude-Session: https://claude.ai/code/session_014cmNRtR2vL1p89dbVQ7FZJ Co-authored-by: Claude Fable 5.1 --- .../logicsrc-web/src/app/openprofile/page.tsx | 29 +++++++- docs/openprofile.md | 66 +++++++++++++++++-- 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/apps/logicsrc-web/src/app/openprofile/page.tsx b/apps/logicsrc-web/src/app/openprofile/page.tsx index ff46e0d..1026c4d 100644 --- a/apps/logicsrc-web/src/app/openprofile/page.tsx +++ b/apps/logicsrc-web/src/app/openprofile/page.tsx @@ -50,6 +50,26 @@ Ships small fixes to open source projects, nightly. - **Name**: Ada Lovelace - **Profile**: https://ada.example/.well-known/openprofile.md`; +const MATCH = `## Match + +- **Born**: 1990-05-12 +- **Gender**: woman +- **Orientation**: bisexual +- **Status**: single +- **Height**: 168 cm +- **Children**: none +- **Wants children**: open +- **Smoking**: never +- **Seeking**: everyone +- **For**: long-term +- **Ages**: 30-45 +- **Distance**: 50 km +- **Not**: smokers, long-distance + +## Photos + +- https://ada.example/photos/garden.jpg`; + const RULES: Array<[string, string]> = [ ["One # heading", "It is the name. More than one and the first wins; none and the reader says it has no name."], ["The identity block", "The bullet list under the name. Kind, Handle, Web, Email, Avatar, Pay, Resume are understood; unknown keys are kept as written."], @@ -58,7 +78,8 @@ const RULES: Array<[string, string]> = [ ["Accounts", "One bullet per account and the URL is the identity. The network is derived from the host. An account is a claim until the page links back."], ["Topics", "The words you would use to find yourself. Readers lowercase, strip #, and match loosely. No taxonomy at write time."], ["Reshare", "What you will amplify for others and what it costs: Networks, Topics, Not, Rate, Limit. No section means no offer."], - ["Operator", "For an agent: the person answerable for it, by Name and Profile or Email. Chains are followed a few hops and reported."] + ["Operator", "For an agent: the person answerable for it, by Name and Profile or Email. Chains are followed a few hops and reported."], + ["Match", "What a dating site or any matching platform needs, about you (Born, Gender, Orientation, Status, Height, Children, Smoking, Religion, ...) and who you seek (Seeking, For, Ages, Distance, Not). Born is required for matching and must give 18 or over; nothing is inferred; a Photos section carries the pictures."] ]; const DISCOVERY: Array<[string, string, string]> = [ @@ -71,7 +92,8 @@ const ABSENT: Array<[string, string]> = [ ["No required fields", "A name and one line of prose is a valid file."], ["No schema version", "Readers ignore what they do not recognise, so a file written today reads in five years."], ["No signatures", "Verification is bidirectional linking, which every platform already supports in some form."], - ["No JSON", "A reader may derive a structured view and must regenerate it from the Markdown on every read. The Markdown is the canonical copy."] + ["No JSON", "A reader may derive a structured view and must regenerate it from the Markdown on every read. The Markdown is the canonical copy."], + ["No inference", "A Match key is never filled from a photo, a name, a handle or another site. What is not written is unstated, and a platform that wants it asks the person."] ]; export default function OpenProfilePage(): ReactNode { @@ -111,11 +133,12 @@ export default function OpenProfilePage(): ReactNode {
{EXAMPLE}
{AGENT}
+
{MATCH}
-

The eight rules

+

The nine rules

Every one of them degrades rather than fails.

diff --git a/docs/openprofile.md b/docs/openprofile.md index 72e7635..e1a1ee3 100644 --- a/docs/openprofile.md +++ b/docs/openprofile.md @@ -2,7 +2,7 @@ OpenProfile.md is one Markdown file that says who you are and where you are, for people and agents alike. It is the profile equivalent of meta tags: a small, plain document any site can serve, any platform can link to, and any reader (a person, a crawler, an agent, a job board, a resharing network) can read without being taught a schema first. It is maintained by Profullstack, Inc. as part of the LogicSRC open-standards surface. -Status: **0.1**. This is a description of a convention already in use by [myna](https://mynaposter.com) and [agenticjobs](https://agenticjobs.work), published so others can serve and read the same file. +Status: **0.2**. This is a description of a convention already in use by [myna](https://mynaposter.com) and [agenticjobs](https://agenticjobs.work), published so others can serve and read the same file. Slug: `openprofile` @@ -65,9 +65,38 @@ Ships small fixes to open source projects, nightly. - **Email**: ada@example.com ``` +A person who wants to be matched, on a dating site or anywhere else that pairs people, adds two more: + +```markdown +## Match + +- **Born**: 1990-05-12 +- **Gender**: woman +- **Orientation**: bisexual +- **Status**: single +- **Monogamy**: monogamous +- **Height**: 168 cm +- **Children**: none +- **Wants children**: open +- **Smoking**: never +- **Drinking**: socially +- **Religion**: none +- **Politics**: left +- **Seeking**: everyone +- **For**: long-term +- **Ages**: 30-45 +- **Distance**: 50 km +- **Not**: smokers, long-distance + +## Photos + +- https://ada.example/photos/garden.jpg +- https://ada.example/photos/engine.jpg +``` + ## The rules -There are eight, and every one of them degrades rather than fails. +There are nine, and every one of them degrades rather than fails. **1. One `#` heading, and it is the name.** A document with more than one is read using the first; a document with none still parses, and a reader that wants a name can say it does not have one. @@ -86,7 +115,7 @@ Values that look like an email address or a URL become links; anything else stay **3. A single prose line between the identity block and the first `##` is the headline.** One line. It is the bio a directory shows next to your name. More than one line, and only the first is treated that way; the rest is kept as prose. -**4. `##` opens a section.** The text is kept verbatim, and separately normalised for matching, so `Accounts`, `Profiles`, `Elsewhere` and `Find me` are one thing to a reader and four different words on the page. The normalised names in common use are `accounts`, `topics`, `reshare`, `operator`, `links`, `about`, `projects`, `services` and `contact`. A section whose name matches none of them keeps its own name and is not dropped. +**4. `##` opens a section.** The text is kept verbatim, and separately normalised for matching, so `Accounts`, `Profiles`, `Elsewhere` and `Find me` are one thing to a reader and four different words on the page. The normalised names in common use are `accounts`, `topics`, `reshare`, `operator`, `match`, `photos`, `links`, `about`, `projects`, `services` and `contact`. `Dating`, `Matching`, `Partner` and `Looking for` normalise to `match`. A section whose name matches none of them keeps its own name and is not dropped. **5. Every bullet under Accounts is one account, and the URL is the identity.** `[Bluesky](https://bsky.app/profile/ada.example)` names a platform and a page; the page is what matters, and the label is only what to call it. `bluesky: ada.example` and `https://bsky.app/profile/ada.example` on a line of their own are accepted too. A reader derives the network from the host when it knows the host, and from the label when it does not. An account is a **claim** until it is verified (see Verification), and a reader should show the difference. @@ -104,6 +133,31 @@ No Reshare section means you are not offering to reshare. Nothing here obliges a **8. Operator names the person answerable for an agent.** An agent's profile carries it; a person's does not. `Name` and either `Profile` (the operator's own OpenProfile.md, which is the strong form) or `Email`, and optionally `DID`, the operator's identifier, which a reader can match against the `DID` in the operator's own file. A reader that meets an agent without an Operator section should say the operator is unstated. Operators can chain: an agent run by an agent names that agent, whose profile names a person. A reader following the chain stops after a few hops and reports what it found. +**9. Match says what a matching platform needs, and only what you chose to publish.** A dating site, a co-founder board and a roommate finder match on the same few facts, and today each holds them in its own form behind its own login. The section has two kinds of key, about you and about who you are looking for, and every one is optional. + +About you: + +- `Born`: an ISO date (`1990-05-12`), a year, or an age. A date wins over a year, a year over an age. A reader computes age at read time and shows the age, not the date; a platform stores the date only if the person entered it on that platform. +- `Gender`, `Orientation`, `Pronouns` (which may also sit in the identity block): kept as written. `woman`, `man`, `non-binary`, `straight`, `gay`, `bisexual`, `pansexual`, `asexual`, `queer` are the words in common use, and any other word is kept too. +- `Status`: `single`, `divorced`, `widowed`, `separated`, `partnered`, `married`. `Monogamy`: `monogamous`, `non-monogamous`, `open`. +- `Height`: `168 cm` or `5'6"`. `Body`: kept as written. +- `Children`: `none`, a count, or a count with a word (`2, grown`). `Wants children`: `yes`, `no`, `open`, `undecided`. +- `Smoking`, `Drinking`, `Cannabis`, `Drugs`: `never`, `socially`, `often`, `quit`. +- `Religion`, `Politics`, `Ethnicity`, `Education`, `Work`, `Diet`, `Pets`, `Exercise`, `Zodiac`: kept as written. `Work` here is one line; the `Resume` link in the identity block is where the detail lives. A reader may compute `Zodiac` from `Born` when it is absent. + +About who you seek: + +- `Seeking`: the genders you want to be matched with: `women`, `men`, `everyone`, or a list. +- `For`: `long-term`, `short-term`, `marriage`, `casual`, `friends`, `open to either`, or a list. +- `Ages`: a range, `30-45`. `Distance`: a radius from `Location`, `50 km`, `30 mi`, or `anywhere`. +- `Not`: dealbreakers, matched loosely against the other profile's Match values and Topics the way Reshare's `Not` is matched. A hit here wins over everything else. + +Values are matched loosely, as Topics are. Unknown keys are kept, so a platform that matches on something this list lacks adds its own key and loses nothing. Absence is unstated: a platform shows unstated, never a default, and never a guess made from the avatar, the name or anything else in the file. + +Two rules a matching platform does not degrade on. **Born is the one key it must have**: a reader that finds no `Born`, or computes an age under 18 from it, does not list the profile in a matching context at all. And **the section is public by nature**: orientation, religion, politics, ethnicity and health-adjacent keys are the categories of personal data most laws protect, so a person puts here what they would put on a public profile page and nothing a platform would have to hold under a lock. A platform that imports a Match section stores no more of it than the person confirmed on that platform, shows where it came from, and drops it when the file drops it. + +`## Photos` goes with it: one image URL per bullet, the first is the lead, and `Avatar` in the identity block stays the small square picture a directory shows next to the name. + ## Discovery The file is served, not registered. There are three ways to find it, and a reader should try all three. @@ -145,7 +199,9 @@ Two profiles that link to each other through Operator and through an account are **No signatures.** A signed profile is a good idea and a different specification. Verification here is bidirectional linking, which every platform already supports in some form, and which is what `rel="me"` has used for twenty years. -**No structured topic taxonomy.** Topics are the words people wrote. +**No structured topic taxonomy.** Topics are the words people wrote, and so are Match values. + +**No inference.** A reader never fills a Match key from a photo, a name, a handle or another site. What is not written is unstated, and a platform that wants it asks the person. **No JSON.** A reader may compute a structured view (name, kind, identity pairs, accounts with derived networks, topics, reshare terms, operator) and use it for matching and search. That view is derived, and it is regenerated from the Markdown on every read. **The Markdown is the canonical copy.** A product that stores the parse and treats the Markdown as an export has implemented a form with a Markdown skin, and the person no longer owns their profile. @@ -158,6 +214,7 @@ A conforming reader: 3. Reports absence as absence: an unstated `Kind`, an unstated operator, an unverified account. 4. Matches topics loosely and lets `Not` win. 5. Never moves money on the strength of `Rate` alone. `Pay` says where; the reader's own agreement with the person says whether. +6. Lists a profile for matching only when `Born` is present and gives an age of 18 or more, and stores no more of Match than the person confirmed with it. ## Writing one @@ -179,6 +236,7 @@ By hand, in any editor, in five minutes. Or: |---|---|---| | 0.1 | 2026-09-12 | First publication: eight rules, three discovery locations, bidirectional verification, Reshare and Operator sections. | | 0.1.1 | 2026-09-12 | `DID` in the identity block and in Operator: did:key, did:web and AT Protocol did:plc accepted verbatim. | +| 0.2 | 2026-09-13 | Rule 9, Match: the keys a dating site or any matching platform needs, about you and about who you seek; `Born` required for matching and 18 or over; no inference; `Photos` section. | ## License