PR 177 shipped a second OpenFleet under the same slug: a published listing of OpenAgent profiles and ipfile swarms with CoinPay rental offers. Anthony ruled that OpenFleet means the human-controlled fleet and the record of who spawned whom, so the rental contract is renamed OpenRental (slug openrental, catalogs family, group noun listing): docs/openrental.md, logicsrc-openrental.schema.json with type logicsrc.openrental and scope kind listing, fixtures/openrental, createOpenRental and OpenRental* types in the SDK, the openrental validator kind. Minor bumps because an export moved: @logicsrc/schemas 0.2.0, @logicsrc/validators 0.2.0, @logicsrc/sdk 0.2.0. The discovery contract test now covers openfleet, openrental and openwall, one per family, and accepts a landing-page link in llms.txt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RZV4zJ2pDZLNN3kE5jFCmV
One file a ring serves about its members, one file a member may serve
about itself, plain links between them, and a made_by declaration on
every member (human, ai, both) so a reader can follow the human web or
the machine web on purpose. Six hop rules that accept every addressing
shape rings already use (?from=, ?host=, ?via=, ?url=, a slug in the
path, the bare Referer), so a member of any existing ring joins with no
change; verification the IndieWeb way (mark inactive, never delete); an
OPML twin of every ring; no script, no tracking, no central registry.
Name: OpenRing is Drew DeVault's tool in this exact niche and every
openring domain is taken; OpenWebring has only a dead 2020 predecessor
and free domains. Registered in the catalogs family.
First host: rssamplifier.com/ring (one ring per topic), in flight.
Claude-Session: https://claude.ai/code/session_01XYae2mH3khdwiXUVzcVMDw
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
A new spec at /openfleet and /docs/openfleet replacing the AgentSwarm placeholder. A fleet is every agent session one human answers for; a swarm is the members one spawner starts inside it for one task. One record per session (claimed by the first session that writes its member.start, derived when inherited), one append-only ledger per fleet per host with eight events, five sysop verbs, fifteen rules, and what Claude Code and moshcode would each add. Written from job 172ffd83, which had to reconstruct its own parentage by hand.
openswarm keeps its slug (it is the peer-to-peer media family) and gains a one-line pointer. /agent-swarm redirects permanently to /openfleet; the registry entry, home band, catch-all route and scroll hook for the placeholder are replaced or removed. Contract test covers the redirect.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RZV4zJ2pDZLNN3kE5jFCmV
* feat: add OpenFleet membership and CoinPay rental contracts
* Propose OpenWall broadcasts and direct messaging contracts
* release: prepare OpenFleet and OpenWall public contracts
* fix: use tested npm for compatible CLI installs
Every consumer of a link reads a page's card tags its own way, caches the
first reading for days, and publishes nothing. OpenSite writes the reading
down: a record per URL (title, description, image, kind, canonical, author,
feeds, the card tags verbatim, JSON-LD as parsed), a descriptor a site
serves at /.well-known/opensite.json, the order a reader takes each field
from a page, the four calls an index offers, and a table of what each
consumer reads and caches.
docs/opensite.md, one entry in the specs registry (catalogs family), and a
landing page at /opensite. The first index is nichedb.dev/c/sites; the
first publisher is nixamp's share links.
Claude-Session: https://claude.ai/code/session_01MwAoNvWzezmBHeT7oDHo3C
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Subscribing takes one click and cancelling takes a support ticket, and no
reader can check that the two are as easy as each other because the way
out is not written down anywhere a reader can fetch. The service already
has a plan table, a cancel endpoint and a refund rule. OpenSaaS is those
at /.well-known/opensaas.json: plans, and eight actions (subscribe,
cancel, pause, resume, change_plan, unsubscribe, export, delete), each
as the page a person opens and the endpoint an agent calls with an
OpenAccess scope, with steps and confirm as the exit measure a directory
shows beside the entrance. Doc, landing page, registry entry under
catalogs. nichedb.dev is named as the first directory and the first
service.
Claude-Session: https://claude.ai/code/session_01S7yeJUHGxA4P5N74xnsRPQ
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
The SimpleFIN door written down for anything a bridge holds: a person gets
a single-use setup token from the bridge, pastes it into an app, the app
claims it once for an access URL and a bearer the bridge can revoke. No
client registration, no redirect, no key for the app to keep, which is what
a browser extension or a script needs. Bearer instead of SimpleFIN's Basic
credentials in the URL, because a browser's fetch refuses those. Eight
rules, the social profile (accounts, analyze, write, suggest, activity,
posts only when declared) and the finance profile (SimpleFIN, unchanged).
First bridge: mynaposter.com (/connect). First app: DefPromo. Registered
as one entry in the specs registry under Access and credentials, beside
OpenAccess, which is the registered door with the same scope vocabulary.
Claude-Session: https://claude.ai/code/session_01XYae2mH3khdwiXUVzcVMDw
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
A directory that lists a host's plans is asked the next question at once:
how do I drive this thing from a terminal? Every provider answers it on a
page of its own, in its own words, and a reader that wants the install line
has to find and read that page for each host. nichedb.dev's hosting
collection just did exactly that for 46 providers, copying the commands
off each vendor's guide, and the exercise is the argument for putting the
facts in the descriptor.
`provider.developer` names the official CLI, its install commands keyed by
package manager and copied as the guide prints them, the install guide and
source, the API docs, the Terraform provider and the GitHub organisation. A
provider with no CLI says `"cli": null`, which is a fact, while a missing
block means unknown. Commands are copied, never composed: a reader that
invents `brew install <name>` sends a buyer to a formula that may not exist.
c0mpute.md gains the same block for the compute market, with the one
install line c0mpute.com prints, and the landing page says what 0.2 adds.
Claude-Session: https://claude.ai/code/session_01Khk1C6Ese6xjdHAWLVstca
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Anthony: "this needs a cookie crumb navbar, all pages need this with the
new IA" and "broad and drill down, I'm not seeing that in the sidebar".
lib/crumbs.ts derives the trail from the path and the spec registry, so
no page declares it: /openthreat is Home > Specs > Catalogs a site serves
about itself > OpenThreat, /opencpu adds OpenServer before OpenCPU,
/docs/openthreat ends in Specification, /docs/cli is Home > Docs > CLI,
a blog post passes its title as the leaf. components/breadcrumbs.tsx
renders it (server-rendered, with a BreadcrumbList JSON-LD) at the top
of every SiteShell page; the SPA routes rendered by page-markup.ts get
the same trail as a string.
components/side-nav.tsx replaces the flat sidebar: the four groups stay,
and under Specs the family the current page belongs to unfolds to its
specs, and the spec to its blocks, marked active. The home page string
marks the active entry for the SPA routes too.
Claude-Session: https://claude.ai/code/session_014cmNRtR2vL1p89dbVQ7FZJ
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Anthony: "that site needs better information architecture, it's impossible
to find anything", "start broad in sidebar and drill down with dedicated
pages, not all one page", and "I see none of our specs" on the home page.
One registry, lib/specs.ts, now lists every specification in four
families (people and agents; access and credentials; catalogs a site
serves about itself; agents and process), with a landing path, a
specification path and, for OpenServer's blocks, a parent. Everything
that lists specs reads it: the sidebar (lib/nav.ts, four groups: Start,
Specs, Tools, Company, rendered by SiteShell and by the home page string
from the same array), /specs and /specs/<family>, the home page's
Standards Surface grid (families with their specs, replacing the five
abstract primitives), /docs (grouped by family, then guides), the sitemap
and llms.txt. DOC_SLUGS is derived from the registry. Adding a spec is
one entry plus its files; the four hand-kept lists are gone.
Claude-Session: https://claude.ai/code/session_014cmNRtR2vL1p89dbVQ7FZJ
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* docs: OpenBroadcast and OpenGuest, the broadcaster-and-guest framework as OpenProfile.md sections
Anthony: "broadcasters and guests is the usual framework for live audio
shows, radio, podcasts" and OpenProfile.md should carry it so a platform
(anyfans) can match hosts with guests from two files rather than two
forms. OpenExpert folds into OpenGuest: an expert is a guest with
Expertise and Credentials.
OpenBroadcast is the `## Broadcast` section: Show, Kind, Format, Live,
Cadence, Length, Language, Audience (host's own unit), Feed, Topics,
Seeking, Not, Slots, Remote, Book, and Pays / Charges (unstated by
default, because pay-to-play is the thing a guest is most often not
told). OpenGuest is the `## Guest` section: Available, Expertise,
Credentials, Pitch, Formats, Live, Languages, Availability, Lead time,
Remote, Rate, Pays, Appeared on, Press, Book, Not. Matching scores
Topics/Seeking against Expertise/Topics, Slots against Availability,
Pays/Charges against Rate/Pays; both Not keys are absolute; a platform
never fills a key the person did not write. Both landing pages share
profile-section-page.tsx. OpenProfile.md names the two sections in rule
4 and in Related standards. Registered in the four places.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014cmNRtR2vL1p89dbVQ7FZJ
* ci: trigger workflows
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* docs: OpenThreat, one file a security tool serves about what it found in the open
Twelve rules that degrade and two that do not: a subject is public or it
is not in the file (no private repos, no customer servers, no paid users'
scans), and a secret is never located while it is open (rule, severity,
subject, status only; no location, message or excerpt). Four kinds:
finding, attack, indicator, advisory. Status open, fixed, mitigated,
blocked, withdrawn; a withdrawn threat stays a while so directories
retract it. Announcing is on by default with a one-switch opt-out in the
tool's own settings. Discovery at /.well-known/openthreat.json,
rel="openthreat", or a handed URL; origin is the verification. Mapped
against SARIF, STIX 2.1 and CSAF rather than replacing them.
First reporter: threatcrush.com/discovery (its own PR). First directory:
nichedb.dev/c/threats (its own PR). Registered in DOC_SLUGS, NAV,
STATIC_ROUTES and llms.txt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014cmNRtR2vL1p89dbVQ7FZJ
* ci: trigger workflows
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
The descriptor at /.well-known/openaffiliate.json (programs with pays,
link, window, attribution, hold_days, payout, approval, self), the four
calls (join with an OpenProfile.md, link with ?oa=code, read the ledger,
get paid to your own address), webhooks, discovery, what a directory
owes a merchant, and what is deliberately absent: no network in the
money, no tracking host, no application form, no exclusivity, no
impression payments. Landing page at /openaffiliate, registered in
DOC_SLUGS, NAV, sitemap and llms.txt. Reference implementation is
crawlproof.com/affiliate.
Claude-Session: https://claude.ai/code/session_01CDEiDss9RWYibtmxSk5Gr2
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Anthony: every top-level nichedb.dev niche may need its own open<niche>
spec so the serve-your-own-file pattern scales across industries. These
are the two he named first.
OpenCoupon: one JSON file a merchant serves at
/.well-known/opencoupon.json about what is on offer right now: every
code, sale and shipping threshold with kind (percent, amount, shipping,
bogo, gift, other), value, scope, min_order, dates, status, per-customer
and region limits. Expired coupons stay in the file so a directory
learns a code died from the one party that knows. No affiliate links,
no redemption, no votes. First reader: nichedb.dev/c/deals.
OpenRecipe.md: one Markdown file that is a recipe, in the OpenProfile.md
and OpenResume.md style: a summary block (Serves, Prep, Cook, Cuisine,
Course, Diet, Author, Source, Image), a description line, Ingredients
and Steps as written, Notes, Nutrition per serving. Served next to the
page, linked with rel="openrecipe", or indexed at
/.well-known/openrecipe.md. A one-way mapping to schema.org/Recipe:
the JSON-LD is generated from the Markdown, never the reverse.
Both registered in DOC_SLUGS, NAV, STATIC_ROUTES and llms.txt.
Claude-Session: https://claude.ai/code/session_014cmNRtR2vL1p89dbVQ7FZJ
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
OpenSwarm says how a file lives on a swarm and how a seeder is paid to hold
it, and nothing in it gives a person with a browser, a search engine or a
directory a URL to start from. A manifest is on the DHT under a key, the
catalogue is a feed found through the DHT, the seed market is one-sided:
offers are listed and seeders poll them. A requester who wants a box in
Germany with two terabytes free and a year of clean proofs has no file to
read, and a seeder with those things has no file to serve.
OpenFile is /.well-known/openfile.json on a publisher's origin: each file
by its plaintext content hash (the ipfile plainRoot, so the id a reader
gets is the root the decrypted file verifies against), every way to fetch
it (an ipfile magnet, a webseed, plain HTTP by range, HLS for a player
with nothing installed), the pay2seed attestation and README, the price
as an x402 offer URL, and a holders list of who has the bytes now with
the age of each seeder's last proof. Encryption is ipfile unless the
publisher says none as an explicit act.
OpenDisk is /.well-known/opendisk.json on a machine that rents disk: free
GiB, price per GiB-month in the unit pay2seed already prices in, the
operator's accept policy stated up front so nobody posts an offer the disk
would never take, proof cadence, the seeder key and the hubs it takes
leases at, and a record block whose source is the hub's own seeder page,
because a marketplace reads standing from the hub and never from the
file. d1sks.com is the reference marketplace. A disk is also an OpenServer
offer of kind storage, and the mapping is a table, so the nichedb hosting
collection lists every disk without a second parser.
Neither restates a record that already has a name: the swarm is ipfile,
the consent is pay2seed, the leases and proofs are paid2seed, the payee
and the pass are ippay, the feed is ipdb. Both are registered in the four
places a LogicSRC spec needs and added to the OpenSwarm family table.
OpenFile has no product domain yet and says so.
Claude-Session: https://claude.ai/code/session_01Khk1C6Ese6xjdHAWLVstca
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Four resource specifications under OpenServer, one per thing that is
negotiable when a server is bought. Each is the block of an OpenServer
offer (compute, memory, gpu, network) written down on its own, with the
units OpenServer 0.1 already uses (vcpu, cores, ram_mb, vram_mb,
bandwidth_mbps, transfer_gb, ipv4, ipv6) and one new shape shared by all
four: `range`, the field a buyer can dial at checkout, its bounds, the
step and what a step costs on top of the base price.
- OpenCPU: threads against cores, the processor by its vendor name,
dedicated, shared or burstable allocation.
- OpenMemory: mebibytes, DDR generation, ECC as three states, reserved,
balloonable or shared; wins over compute.ram_mb when both are present.
- OpenGPU: the card by its vendor name, count and VRAM per device,
interconnect, passthrough, MIG, vGPU or shared access.
- OpenBandwidth: port, four meters (transfer, unmetered, percentile,
flat), overage, IPv4 and IPv6 addresses as a priced resource.
A provider that sells only one resource lists it as an OpenServer offer
and may serve the same document at /.well-known/<slug>.json. Landing
pages share one component (resource-spec-page.tsx). Registered in
DOC_SLUGS, NAV, STATIC_ROUTES and llms.txt. OpenServer, OpenFile and
OpenDisk arrive in sibling PRs.
Claude-Session: https://claude.ai/code/session_014cmNRtR2vL1p89dbVQ7FZJ
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
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 <noreply@anthropic.com>
* 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 <noreply@anthropic.com>
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/<slug> 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Khk1C6Ese6xjdHAWLVstca
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Lists LogicSRC on OpenAccess hubs (openaccess.logicsrc.com) so people can
link it with OAuth 2.1 + PKCE and it honours the shared
profullstack.com/all-access entitlement. The Ed25519 public key here is
the app's credential for reporting sales; the private half is in the
logicsrc teams vault openaccess-app-keys--prod. Scopes are empty for now:
the reserved openid, email and entitlements scopes need no listing.
Spec: https://logicsrc.com/openaccess
Claude-Session: https://claude.ai/code/session_01SWRffW4ifQPUrGXJtgYWMd
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
A new LogicSRC spec at /openaccess and /docs/openaccess. A person, an
agent or an organisation keeps one account at a hub; every app keeps its
own users and links each to that account once. Grants delegate narrower
to agents, and a subscription bought in one app is honoured by every app
that honours the product. The app descriptor, the hub metadata, the four
flows, the token, signed webhooks and the hub's own doors.
Registered in DOC_SLUGS, NAV, STATIC_ROUTES and llms.txt.
Reference implementation: github.com/logicsrc/openaccess, hub at
openaccess.logicsrc.com.
Claude-Session: https://claude.ai/code/session_01SWRffW4ifQPUrGXJtgYWMd
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
OpenContext, OpenCreds, OpenOntology and OpenPRD were already registered
under the umbrella. Three were not:
- `logicsrc openmcp …`: OpenMCP lives in its own repository, so it is a
dependency (@logicsrc/openmcp ^0.3.1, which exports ./cli for this) and
every argument goes untouched to the same main the standalone binary runs.
Imported on first use, because the catalog is node:sqlite (Node 24) while
the rest of the CLI runs on 18; below the floor that one word says so and
offers the standalone installer, which brings its own Node.
- `logicsrc openspec <anything else>`: import, export and change stay ours;
any other word (init, list, validate, archive, show) runs OpenSpec.dev's
own CLI (@fission-ai/openspec) as the group's default subcommand, flags
intact. One command for a repo in compatibility mode, and the OpenSpec.dev
half is upstream itself rather than a copy that would drift.
- `logicsrc mcp`: the LogicSRC MCP server (@profullstack/logicsrc-mcp) over
stdio, spawned as a child because it owns the process's stdio. build:cli
now builds that workspace.
README gets a table of every word and the standalone name it mirrors; the
OpenMCP spec page and doc mention the umbrella form.
Claude-Session: https://claude.ai/code/session_01Qh2dieNyPZ4Hx5g3XNJ1Eo
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
logicsrc.com/.well-known/openprofile.md was a 404 while every relay in the
OpenMCP catalog, Obscura included, named it as its operator. The site that
publishes the spec now serves its own file: Kind organization, accounts
(GitHub, blog, the catalog, the Obscura relay so the trust chain closes both
ways), topics, projects and contact. The root layout carries
rel="openprofile" and every response carries the same relation as a Link
header, per the spec's discovery rules. skill.md and llms.txt point at it.
The OpenMCP spec page and docs/openmcp.md now lead with
curl -fsSL https://openmcp.logicsrc.com/install.sh | sh instead of npx, and
the descriptor examples name logicsrc.com's real profile rather than one
profullstack.com never served.
Claude-Session: https://claude.ai/code/session_01Qh2dieNyPZ4Hx5g3XNJ1Eo
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
The spec page pointed only at the GitHub reference implementation. The
catalog is running now, so the intro and the "where everything lives" list
link to openmcp.logicsrc.com to browse or point a client at, and to the
Obscura relay it hosts.
Claude-Session: https://claude.ai/code/session_018dwULHaBKAh7nbWTCqUX7D
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
A relay serves /.well-known/openmcp.json; a catalog probes it (the
descriptor from the relay's own origin, then initialize and tools/list)
and lists only what it found; a client reaches every relay through the
catalog's REST, its own MCP endpoint, or signed webhooks. Landing page at
/openmcp, the document at /docs/openmcp, registered in the same four
places as the other specs. Reference implementation at
github.com/logicsrc/openmcp.
Claude-Session: https://claude.ai/code/session_01FMT2v1YxmgcDuionrfT719
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
A new LogicSRC spec at /openprofile with the document at /docs/openprofile.
Eight degrading rules (name, identity block, headline, sections, accounts,
topics, reshare terms, operator), three discovery locations
(/.well-known/openprofile.md, rel="openprofile", a platform path) and
verification by linking back. Registered in DOC_SLUGS, NAV, STATIC_ROUTES
and llms.txt, the same four places as ASDLC.
myna writes one from its accounts and publishes the Reshare section to the
myna reshare network; agenticjobs serves one per public candidate.
Claude-Session: https://claude.ai/code/session_01FMT2v1YxmgcDuionrfT719
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* blog: honor canonical_url so guest posts point at their source
A post syndicated from another blog carries the original URL in
blog_posts.canonical_url (the autoblog webhook already stores it). The post
page ignored it and always self-canonicalized, so a guest post would
compete with its source for the same words. Now the page selects
canonical_url and author, sets rel=canonical to the original when present
(self otherwise), points JSON-LD mainEntityOfPage at it, and shows readers
an "Originally published on <host>" line with the author byline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxNif5tsYq4LczgG7aE8Jp
* blog: author is jsonb, render a name not [object Object]
The blog_posts.author column is jsonb (e.g. { name, url }), not text.
Extract a display name for the byline and JSON-LD instead of rendering the
object directly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxNif5tsYq4LczgG7aE8Jp
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Adds a reports section to the OpenStream spec so its claims rest on a
reproducible measurement rather than an assertion. Each report is a run of
the envelope over a defined corpus on real hardware: proof that
decompression restores every byte, that an incompressible input costs only
the framing overhead, that a compressible one saves what it claims against
the complete wire size, and how long each codec takes.
- docs/openstream/reports/ holds a machine-readable <id>.json (canonical,
with a versioned schema) and a rendered <id>.md per report, plus a README
on the shape and on submitting one. The seed report is nixamp 0.17.1 over
the synthetic corpus, labelled synthetic so no one reads a padded-fixture
number as production.
- The site renders them at /docs/openstream/reports (index) and
/docs/openstream/reports/<id> (one report), under the dynamic /docs/[slug]
tree so the reports routes never shadow a spec's own doc page. A small
lib/reports.ts reads the JSON at build time; REPORTED_SPECS keeps the
route surface explicit. sitemap includes the index and every report.
- The spec doc gains a Benchmark reports section linking there, and repeats
the honest caveats: OpenStream frames Zstandard and gzip rather than being
a new algorithm, synthetic padding flatters a codec, an efficient real
feed saves little, and round-trip exactness is the one pass/fail.
The report format is produced by `nixamp compression benchmark` (in the
nixamp repo); a release runs it and commits the two files here.
Claude-Session: https://claude.ai/code/session_01MxNif5tsYq4LczgG7aE8Jp
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
OpenStream is the wire format nixamp uses to relay a channel or a file
between two servers: a framed stream of blocks, each compressed with an
established codec or stored verbatim, each carrying the length and SHA-256
of the bytes it stands for, ending in a marker that says the stream
finished rather than dropped. It is a framing envelope, not a compression
algorithm, and it is deliberately product-neutral: nixamp is the reference
implementation, the format carries any byte stream.
The doc gives the byte layout (16-byte stream header, 48-byte frames, both
big-endian), the mode set, the validation order, cross-language test
vectors, the negotiation, the source/channel boundary, recovery semantics,
and a conformance checklist. Published at /docs/openstream via the same
DOC_SLUGS path as every other spec; no README change, matching how
OpenJob/OpenResume (#148) landed.
Claude-Session: https://claude.ai/code/session_01MxNif5tsYq4LczgG7aE8Jp
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Two conventions for the hiring end of the agentic stack, published here beside
the other Open* specs.
OpenResume.md says a resume is a Markdown file: a document a person can read,
diff and keep, and one an agent can write without being taught a schema first.
Six conventions, every one of which degrades rather than fails, because a
resume that does not parse still has to be a usable resume.
OpenJob extends schema.org JobPosting with the three things it has no
vocabulary for: whether the employer accepts applications written with an agent
(stated, rather than discovered by silent rejection), the application form as
data so applying does not require rendering a page, and a description in
Markdown.
Both are implemented by profullstack/agenticjobs, and neither requires it.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
OpenPRD 0.2 fixed eight body sections, none of which asked what the thing is
built on or how it earns. The stack got chosen in the first implementation PR
instead of at review, and a PRD could be filled out completely without anyone
writing down who pays. PRD 0006 had already grown a hand-rolled
`## Business model` section, which is the gap showing.
0.3 adds two required sections between `UX Notes` and `Success Metrics`:
- Tech Stack — languages, frameworks, datastores, third-party services, and
anything the work must not depend on. It makes the requirements costable.
- Monetization — the revenue model: who pays, for what, how much, and when.
`_None._` stays a valid answer, but it now has to be said out loud.
Adding required sections would normally invalidate every document already
written, so a document is now held to the section list its own `openprd:` key
fixes. A 0.2 document keeps conforming with eight sections, forever; a 0.3
document needs ten. Adoption is per document, and `logicsrc prd validate
--expect-version 0.3` (new flag, wiring up the validator option that already
existed) reports the stragglers as OP-L-VERSION.
The front-matter schema is untouched — both additions are body sections.
Conformance bundle proves both directions: invalid/missing-monetization.md
fails with OP-C-SECTION-MISSING, and valid/legacy-0-2.md passes unedited.
This repo's own PRDs 0001-0006 stay at 0.2 as standing evidence that the
compatibility rule holds. PRD 0007 records the decision at 0.3.
Claude-Session: https://claude.ai/code/session_017XRNNm6pK6nPi7rJ6bJNHu
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
ASDLC 0.1 describes how software gets built when most of the work is done
by agents running in parallel and CI/CD is the only gate. It is a
description of a practice already in production, not a proposal.
The traditional SDLC assumes the scarce resource is engineering time, so
it spends process on deciding whether each change is worth building. When
agents write the code, engineering time stops being scarce and two other
things become scarce: human attention, and trunk stability.
Nine phases: frame, fan out, gate locally, merge, release, verify live,
correct, ratchet, promote. Correct returns to fan out, so the loop is the
lifecycle.
The load-bearing phase is the ratchet. Testing in production is only
defensible if production failures are one-time events, so every escape
becomes a permanent automated check before the incident is closed, and
that check has to be confirmed to fail when the bug is reintroduced. A fix
without a ratchet is how the same class of bug ships three times.
Four conformance levels, of which only level 3 requires evidence rather
than intent. The worked example is DiskPush on 2026-09-06: eight agent
worktrees on one checkout, four releases between 08:53 and 14:56 UTC, and
a three-release desktop bug whose first layer no local harness could have
caught, because a static server resolves absolute paths correctly by
construction and the bug only existed under file://.
Published at /asdlc with the spec at /docs/asdlc, listed in the nav,
sitemap and llms.txt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Q2bt449mEJSHoEZzaemCn
* Add pay2seed to the OpenSwarm family: consent at upload and a paid seed market
OpenSwarm pays a seeder per verified piece served, and nothing pays anyone
to stay. An archive, a backup, a dataset waiting for its buyer or a
podcast's back catalogue earns nothing the month nobody downloads it, so
it dies the way every swarm always has. And nothing in BitTorrent says who
put a swarm there or whether they were allowed to, which is why a seeder
is presumed to be doing something wrong.
pay2seed is the member document for both halves. An attestation, signed
at upload with a fixed basis (own, licensed, open-license, public-domain,
personal) and a notice endpoint, is what a hub requires before it will
list anything; public claims get a claim window and a standing, and a
notice voids them. An offer escrows a budget at an ippay hub for a swarm,
public or private, to be held by M seeders for N days at a price per
GiB-month, bought over x402 exactly as a pass is. Seeders take leases,
prove each period by storage challenge or by a probe over the ordinary
wire, and are paid through the payee they already have. Public feeds ride
on ipdb; ipfile.pin on c0mpute is the same offer on the auction.
Also: the family table, stack diagram and registry rows; the ip seed
command group; PRD 0006; the protocol row on /openswarm.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKAohrRkqLKVQL2cGCAkR5
* Split pay2seed into client and server halves, and add pay2stream and paid2stream
The rule is now in the names. pay2* is the client protocol: the side that
pays, over HTTPS, and plays. paid2* is the server protocol: the BitTorrent
side that earns. One hub implements both halves of a pair; a requester or
viewer implements only pay2*; a seeder, relay or gateway only paid2*.
pay2seed keeps consent, offers, the requester's market and notices.
paid2seed takes leases, storage challenges and probes over the wire,
GiB-month accrual and receipts, the seeder client, and the ipfile.pin
mapping.
pay2stream and paid2stream do the same for a live channel over iplive.
A broadcaster attests the channel (with the two rules that separate a
licensed rebroadcast from a stolen feed), buys relays by the hour, and
publishes listings; viewers buy tickets. Relays take leases and are
proven present by a verifier that pulls segments as a peer; a gateway is
a relay that also serves standard HLS, clear or sealed, with the M3U and
XMLTV pair every IPTV app asks for, so VLC, TiviMate, Kodi and a
television play a paid swarm with nothing installed. Ace Stream showed
BitTorrent can carry live TV to millions; this is that with consent,
payment and an open spec.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKAohrRkqLKVQL2cGCAkR5
* pay2seed: encrypted by default, access as the product, a README in every swarm
The client encrypts by default and the hub never does: what the hub
manages is who may decrypt. A team is a named set of member keys with a
scope over the owner's swarms; the hub, as keeper, issues grants to
members when the owner is offline, with invitations that expire, roles,
an audit trail, and re-encryption on removal so the next version is
closed to whoever left. A few seats are free; above that the hub charges
per seat and per organisation, settled through the same pay plugins as
everything else. Seeding is priced at disk; access is where a hub earns,
and both sides earn: seeders rent disk, requesters sell access.
Public is not a fallback. Encryption off is an explicit act, and a
public swarm is attested, listed, kept and rendered exactly as a private
one is; the only difference is who can read it.
Every swarm on the market carries a README.md at its root, no
exceptions, and the attestation carries its Markdown and the hash of
the copy inside the swarm, so the hub renders it as the swarm's page
without a key. Relative links resolve into the swarm and are gated the
way the files are.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKAohrRkqLKVQL2cGCAkR5
* pay2seed: 1 percent, ads on the free tier, and agents as sellers
Three things the specs did not say. The reference hub takes 1 percent of
any payment that crosses it, charged to whoever is paying and never
deducted from a seeder or a relay, so a quoted price is what the
publisher gets and a promised floor is what the seeder is paid.
Public swarms are free to fetch and free to list, and an advertisement on
the swarm README page is what pays for that. The ad is on the hub page
and nowhere else: never inside a swarm, never injected into a file, a
segment or a playlist, and never in the catalogue or the market API. A
requester who wants no ad buys a seat instead. A free-to-watch channel
works the same way.
And a requester is a key, not a person. An agent can attest what it made,
price access, sell tickets, take payment through its own payee and spend
what it earns keeping its own work online. The consent rules do not
soften because a machine signed them, and the reference hub asks an
agent public attestation to name a responsible operator key so somebody
is reachable when a notice arrives.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKAohrRkqLKVQL2cGCAkR5
* Fix CI: number the PRD requirements and advance the next-id assertion
Two checks the new PRD tripped, both by existing rather than by being
wrong.
The collection validator wants requirements as numbered R# entries and
0006 used a plain ordered list, so it reported OP-L-NO-REQUIREMENTS.
Rewritten as R1 to R7 with priorities, one capability per entry, and the
implementation tracking moved to a paragraph under them where it is not
pretending to be a requirement.
The MCP standards test asserts what the next free PRD id is, and its own
comment says that advances with every PRD added. Adding 0006 makes it
0007.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKAohrRkqLKVQL2cGCAkR5
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
BitTorrent distributes bytes better than any CDN and has never been a
product: nobody is paid to seed, and nothing in it is private. Every
Profullstack media property answers that with a central HTTP proxy and a
pass system bolted on, and pays for every byte it serves.
OpenSwarm is an add-on to BitTorrent, carried as BEP 10 extension
messages, that fixes both. The swarm carries AES-256-CTR ciphertext whose
integrity is pinned by two SHA-256 merkle roots in a signed manifest, so
a tracker or DHT node learns an infohash and a size. A leecher buys a
pass over x402 in USDC (the same exchange x402-gateway runs for crawl
passes, settled by CoinPay), a seeder serves inside a bounded credit
window, and the leecher signs a cumulative voucher for every verified
batch. Whoever seeds gets paid. Vanilla clients remain valid members and
browsers remain first-class peers.
Adding a file mints a key pair for it, derived from one publisher seed by
default so there is one thing to back up: the public half is the file's
identity and its BEP 46 key, the private half signs the manifest and
authorises grants and payout changes, and a separate content key
encrypts the bytes and is sealed to paying peers.
The family: a core (records, keys, hashing, transports, discovery,
events), ipfile, ippay, ipdb (a signed hash-chained catalogue with heads
on the DHT), ipaudio, ipvideo, iplive (paid relays with backpressure)
and ipname (Moshpit pins and DNS TXT). Plus the c0mpute.com integration
with seven workload types and thirteen use cases, a proposed ip CLI,
conformance profiles, a security model and an FAQ.
Registered on the site the way OpenCreds is: nav entry, docs registry,
sitemap, a /openswarm landing page, PRD 0005, and the MCP prd_next_id
expectation moved to 0006.
Specs only. No code, no schemas, no reference implementation.
Claude-Session: https://claude.ai/code/session_01YafYxayh7Gqe5MWNNQMev2
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* Charge AI training crawlers for access (@profullstack/x402-gateway)
Training crawlers (GPTBot, ClaudeBot, CCBot, meta-externalagent, Bytespider,
Applebot-Extended) get 402 Payment Required with an x402 offer, or the sales
page at /crawl, and a paid pass opens the site for a day. People, search
engines and retrieval crawlers pass through untouched. robots.txt is now
generated from the same lists.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YafYxayh7Gqe5MWNNQMev2
* Type the middleware as returning Response | NextResponse
The crawl gateway answers with a plain Fetch Response.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YafYxayh7Gqe5MWNNQMev2
* Contract test awaits the now-async proxy
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YafYxayh7Gqe5MWNNQMev2
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* Add the LogicSRC OpenCreds specification
Leaving a password manager means writing every secret you own to disk in
the clear, and losing whatever the spreadsheet had no column for. A CSV
is plaintext by construction, lossy by omission, and carries no
integrity: nothing in it says which rows were meant to be there, so a
truncated import looks exactly like a complete one.
The same gap showed up inside LogicSRC. `logicsrc credentials` moves
.env secrets and SSH keys through end-to-end-encrypted team vaults, but
it can only model a key/value pair. A card, a passport, a login with a
TOTP seed, or an OAuth account with a refresh token are all things
people already keep in a vault, and none of them are a key/value pair.
OpenCreds defines three things: the item, the vault, and the database.
- Six item types (login, card, identity, note, key, account) as one
record with a type and a named field group, so everything the user
typed lives in a single encrypted blob. Codes 1-4 match MarkSyncr's
deployed vault and are not renumbered; compatibility is cheaper than
elegance.
- AES-256-GCM over that record with the item id bound as AAD. Without
it, anyone with storage write access could move a low-value login's
ciphertext into a high-value row and watch what the user does next.
- A key hierarchy where the user key is random, not derived, so a
password change re-wraps 32 bytes rather than re-encrypting a vault.
The auth hash comes out of a different HKDF label than the wrap key,
which is what lets it reach a server at all.
- A portable .opencreds file, encrypted by default, whose header is the
AAD over the payload -- so the manifest is authenticated by the same
tag as the data and a truncated import fails rather than reporting
success. The plaintext form exists because people move to products
that read nothing else; it is opt-in, confirmed, 0600, and labelled
"protected": false in its own header.
Namespaces are carried as data, not fixed by the spec: labels are
compiled into every ciphertext a vault has written, so editing one does
not migrate a vault, it makes it undecryptable. MarkSyncr's deployed
vault is conformant by declaring `marksyncr`.
Ships: prd/0004, nine spec pages under docs/opencreds/, six JSON
Schemas, the @logicsrc/opencreds reference implementation with CSV
importers for five products, `logicsrc vault` and the standalone
`opencreds` binary, and the spec page at logicsrc.com/opencreds.
`vault` rather than `creds` because `creds` is already an alias of
`logicsrc credentials`, and the two are different: one moves a pair
between providers, the other stores a record.
@logicsrc/validators now registers every schema by $id before
compiling, so the database schema can $ref the item and manifest
schemas rather than restating them.
120 tests, including CLI end-to-end coverage of the masking rules,
exit codes, and the manifest-mismatch path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRQrfuwuYKKV5UB9kLHuX5
* Make the OpenCreds conformance claim executable
The conformance page described a fixture suite and an `opencreds
conformance` command that did not exist. A specification that documents
a conformance surface it cannot run is a specification nobody can hold
to, including us.
`opencreds conformance` now runs the requirement list as code -- one
check per C-number, carrying its own id and level -- and emits the
report shape the spec publishes. It exits 2 when a MUST does not pass,
so it can gate CI directly. The reference implementation reports 29
passed, 0 failed, 1 skipped; the skip is C19, because key management for
the team profile lives in @logicsrc/plugin-credential-sharing rather
than in this package, and a skipped MAY does not affect conformance.
Fixtures are generated (`--emit-fixtures <dir>`) rather than
hand-written. A vector produced by an implementation and then verified
by it is worth more than a JSON file someone typed: the typed file
drifts silently when the format moves, and the generated one cannot.
Fourteen files, including an invalid/ set every conforming reader must
reject -- a wrong field group, a weak KDF, an unregistered namespace, a
short payload and a tampered manifest.
The CLI requirements stay with the end-to-end tests that drive the real
binary through a child process; a command cannot meaningfully check its
own exit codes, and a masked value that is only masked in the library is
not masked.
conformance.md and cli.md now describe what ships.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRQrfuwuYKKV5UB9kLHuX5
* Add @logicsrc/opencreds to the lockfile
`npm ci` refuses a lockfile that does not match package.json, and the
new workspace package plus the CLI's dependency on it were never
recorded: the worktree was bootstrapped by hardlinking node_modules
rather than installing, so npm was never asked to update the lock.
Adds the workspace link and the package entry. No dependency versions
move.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRQrfuwuYKKV5UB9kLHuX5
* Register PRD 0004, and stop the fixtures looking like real secrets
Two CI failures, both mine.
`prd/README.md` is generated by `logicsrc prd index --write` and the
scaffold test asserts it is current, so adding a PRD without
regenerating it leaves the repo's own conformance check failing.
Regenerated. The MCP test asserts the next free PRD id against the live
prd/ directory — its comment says it advances with every PRD added — so
it moves to 0005.
ThreatCrush flagged three of the example strings: a PEM header in the
item-model docs and in the conformance fixture, and an `sk_live_`
prefixed token. All placeholders, none real, but the finding is the
scanner working. A fixture only has to exercise the field, and a
real-looking private key header or live-key prefix sitting in the tree
trains both the scanner and the people reading its output to shrug at
exactly the shape that matters. Replaced with obvious placeholders
rather than suppressing the rule.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRQrfuwuYKKV5UB9kLHuX5
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add SSH keys and config to credential sharing
Private keys have lived as plaintext-on-disk files guarded only by a
passphrase. This puts them in the same end-to-end-encrypted vaults as
.env secrets, and adds an agent path so a machine can use a key without
ever writing one to its disk.
- `ssh` provider: ~/.ssh as a value bag. Files are picked by sniffing
contents (PRIVATE KEY blocks, ssh-*/ecdsa-*/sk-* public keys) plus
config, config.d/* and allowed_signers. known_hosts and
authorized_keys are host-specific and access-granting, so they need
an explicit --include.
- Each file is one secret carrying a JSON envelope of path, mode and
body. The engine only hands write() the secrets that CHANGED, so a
separate manifest secret would be absent whenever a key's contents
change but the file list doesn't — self-describing values keep every
restore total.
- `logicsrc secrets ssh push|pull|list|agent`, addressed by PERSON not
project: the vault is ssh--<username>, which teams vaults reads as
project ssh, env <username>. One teammate's keys never land in
another's restore; sharing stays a deliberate teams grant.
- Both directions hold back anything that would overwrite a file that
already differs, and say what they skipped. --force opts in. A
restore onto a machine with its own keys is otherwise a way to lose
them.
- Restores chmod each file back to its recorded mode; writeFileSync's
mode applies only on create, so an existing world-readable key would
otherwise stay world-readable. The adapter declares delete:false.
- push warns about passphrase-less private keys before they go up.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add worked examples to secrets and secrets ssh help
Commander's usage line shows only the first alias, so `logicsrc secrets`
— the spelling people actually type — was invisible in its own help.
The examples carry it, alongside the flows worth copying: link/up/down,
the ssh backup round trip, and a plan → dry-run → approve sync.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Advertise the ssh provider on the marketing page
The marketing-drift contract failed the build because `ssh` shipped in the
provider registry with no entry in MARKETING_PROOF -- which is the test
working: it exists so a provider cannot ship while the pages people
actually land on still describe the tool without it.
The proof regex is `/~\/\.ssh|SSH key/` rather than a bare `/SSH/` on
purpose. The provider grid renders every registry `name`, and this one is
"Local SSH directory", so `/SSH/` would already be satisfied by the
generated grid and the provider could ship with no copy written about it
at all -- passing the test while failing its intent. Requiring the path or
the phrase means a human wrote a sentence.
That sentence is the new block in the credential-sharing band: ~/.ssh is a
directory of files whose permission bits are load-bearing, not a set of
KEY=VALUE lines, which is the part that makes this provider different from
the other six. README already named ~/.ssh keys, so it needed no change.
apps/logicsrc-web: 75/75 contract tests pass (was 74 passed, 1 failed).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Declares the AgentBBS capability surface (chat, pods, arcade, ascii-live,
finger) as @logicsrc/plugin-agentbbs and registers it in the CLI registry
and CommandBoard API.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
GET /api/credshare/teams/:slug/vaults built its response in a loop, asking
the database for a grant row and a secret count once per vault. libSQL is
remote, so each of those is a network round trip, and the endpoint cost
2N+1 of them.
On a team with 176 vaults that is 353 round trips and ~10.6s of server
time. `logicsrc teams pull` resolves the vault id twice -- once planning
the sync, once reading values -- so a pull of a single ten-key vault took
~24s, nearly all of it spent listing vaults the command does not want.
Replaced with one SELECT carrying two correlated subqueries. Both are
covered by existing primary keys (credshare_secrets is keyed
(vault_id, name), credshare_vault_grants (vault_id, user_id)), so the
per-vault work becomes an index probe inside the database instead of a
round trip across the network. No schema or index change.
Measured on a local libSQL seeded to match that team -- 176 vaults, 17
secrets each -- the endpoint goes from 355 round trips to 3, and returns
identical rows.
The response shape is unchanged: hasAccess is still a real boolean rather
than the 0/1 SQLite hands back, and secretCount is still a number.
Tests pin behaviour and cost separately. The behavioural cases pass
against both the old loop and the new query, which is the point -- only
the round-trip count changed. The regression guard asserts the query
count for 3 vaults EQUALS the count for 30 rather than matching a magic
number, so any future rewrite that reintroduces per-vault I/O fails no
matter what the constant part costs. Against the old loop it reports
9 vs 63.
Two sibling endpoints have the same shape -- /teams/:slug/members and
/vaults/:id/grants both call publicKeyFor() per member. Neither is on the
pull path and both scale with member count rather than vault count, so
they are left alone here.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The last three PRs all fixed the same class of bug. /credential-sharing
and README.md are hand-written copy; the providers they advertise are a
real registry in @logicsrc/plugin-credential-sharing. Nothing connected
the two, so the `team` provider shipped on 2026-07-13 and three weeks
later both surfaces still described a five-provider tool with no mention
of teams. The docs were right the whole time -- only the pages people
actually land on had gone stale, which is worse, because it reads as
"the product cannot do this" rather than as a documentation gap.
Assert it instead. For every provider in the registry, the Credential
Sharing section and the README must say something that counts as
advertising it. The registry's own `name` cannot be the proof -- `env`
is "Local .env file" and `team` is "LogicSRC Team Vault", neither of
which is how the copy reads -- so each provider declares its own
pattern, and a provider with no declaration fails too. That way adding
a provider forces a deliberate answer about the customer-facing copy.
Verified against the bug it is meant to catch: reverting the team copy
reproduces "These providers ship but /credential-sharing never mentions
them: team", and reverting the README line reproduces the same for
sh1pt and team.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
End-to-end-encrypted team vaults shipped on 2026-07-13, and
/docs/credential-sharing documents them in full. The marketing route
/credential-sharing is a separate hand-written page, and its copy was
never updated -- it listed five providers, omitted the `team` endpoint
type, and said nothing about sharing with teammates at all. Anyone
evaluating the product from that page concluded teams were unsupported.
Add a Team vaults provider card and a team-sharing block covering the
trust model (zero-knowledge relay, X25519-sealed DEKs, rotation on
departure) with the real `logicsrc teams` commands. The route metadata
and llms.txt entry had drifted the same way and also omitted sh1pt.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
A failed install left the machine with no CLI at all. do_install ran
`rm -rf "$SRC_DIR"` and only then built; if the build failed, or the run
was interrupted, what remained was an unbuilt tree, a wrapper still
pointing at the dist/ that was never produced, and the previous run's
install.json still claiming success. Every later `logicsrc` invocation
died with MODULE_NOT_FOUND, and nothing said why.
That is what happened here: install.json dated 01:57, src/ replaced at
02:59 by a second run that did not finish.
Now the download, npm install and build all happen in a staging
directory, and $SRC_DIR is only touched once packages/cli/dist/index.js
actually exists -- the file the wrapper execs, so its absence is exactly
the failure the user would otherwise hit on their next command. Staging
sits inside $LOGICSRC_HOME so the swap is a rename on one filesystem
rather than a cross-device copy of node_modules, and the previous tree
is kept until the swap succeeds so a failed move can be undone.
Build output was going to /dev/null, so "build failed" carried no reason
at all. It is captured now, with the last 25 lines printed on failure and
the full log left on disk.
Also validates the commit id from the GitHub API before recording it:
anything that is not 40 hex characters is dropped rather than written
into install.json, which `logicsrc update` compares against.
Verified against a stubbed npm/curl in all three paths: a failing build
leaves the existing install running, a build that produces no artifact is
caught, and a clean install still swaps in and writes a correct manifest.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Two additions to Credential Sharing.
`logicsrc credentials rotate` (alias `logicsrc secrets rotate`) re-keys a
team vault: fresh DEK, re-sealed to the members who keep access, every
secret re-encrypted under it. Values do not change, so nothing that
consumes them breaks; what changes is that every wrapped key issued
before the rotation is dead. --active (the default) keeps only active
members and revokes the rest -- the "someone left" rotation. --all keeps
everyone who holds access, for plain hygiene. Dry run by default, like
`sync`.
The DEK is recoverable ONLY through the grants, so a half-applied
rotation makes a vault permanently unreadable by everyone. The whole next
state therefore goes to the server in one request and commits in one
transaction (new db.batch helper). The server also requires every
submitted fingerprint to equal the stored one: it cannot see values, but
it can prove a re-key did not swap any. Rotations that would leave the
caller ungranted, grant nobody, or cover the wrong secret count are
rejected before anything is written. GET /vaults/:id/grants now returns
publicKey and status so a client can re-seal in one pass instead of N+1
user lookups, and revocation finally deletes the grant row rather than
leaving one that reports access it no longer confers.
The sh1pt adapter is the fifth provider. It is the only one driven
through a CLI rather than HTTP, because sh1pt publishes
`sh1pt secret set|get|list|rm` as the interface to its vault and
documents no REST endpoint. Values go over the child's stdin, never argv
-- a secret in argv is readable by any user on the host via ps. Since
`sh1pt secret get` needs interactive confirmation it cannot be scripted,
so the adapter is write-only for values like github-secrets: a sync
target, never a source, no value-restoring rollback.
Tests drive a real fake sh1pt binary rather than a mocked execFile, which
is how the hang surfaced: with nothing to pipe, stdin was left open and
any subcommand that reads it would wait forever. It is now always closed.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The "Top-Level Pages" band advertises eight stable routes, but the cards
were plain <h3> text with no anchors -- nothing on that band was
clickable. Wrap each card title in a link to its route.
/privacy was the worst of the eight. It had no page and no homepage
section, so it fell through to [[...slug]], which served the entire
homepage (82KB, byte-identical to /openspec, /credential-sharing, and
/hire-us) and then scrolled to the card that merely described the page
that did not exist. Give it a real page covering what the site actually
does: CrawlProof analytics, the Hire Us form, the CoinPay OAuth session
cookie, and the credshare boundary -- ciphertext and salted-hash
fingerprints are stored, secret values never reach the server.
The cards also reused the ids openspec, credential-sharing, and hire-us,
which already name sections further up the same document. Duplicate ids
made those scroll targets ambiguous, so the cards are now page-<route>.
With that, the scroll list in home-interactivity only needs the three
routes [[...slug]] still serves; docs, blog, about, terms, and privacy
are real routes and were only ever aiming scrollIntoView at a card.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The CLI is the product, and the way you get it was nowhere on the site. You
had to already know the URL of a script served out of public/.
Two placements, one command:
- The homepage hero gets the loud version, directly under the lede and above
the fold -- a bordered dark panel, the command at full size, Copy alongside.
- Every page carries a compact version in the rail, between the brand and the
nav. Present on arrival, never competing with navigation.
Both come from renderInstallCommand() in one module. The homepage builds its
HTML as a string and the rest of the site is JSX, which is precisely the shape
that lets one copy of a command drift while the other stays right -- so there
is one definition and SiteShell renders it rather than restating it.
The command keeps its flags: `curl -fsSL`. Without -f, curl prints an HTTP
error body and still exits 0, so a 404 gets piped into sh; without -L the
install breaks the first time the URL redirects. This is the form install.sh
already documents in its own header.
Copy is one delegated listener on document for any [data-copy] button, mounted
site-wide in the layout. Delegation because the two placements arrive by
different rendering paths and a document listener does not care which; it also
means the next copy button needs the attribute and no wiring. It falls back to
a throwaway textarea + execCommand outside a secure context, where
navigator.clipboard is simply undefined, so the button never no-ops silently.
The contract tests pin the command, both placements, and that the clipboard
payload equals the visible text -- a Copy button that hands over something
other than what is on screen is worse than no button. They also read
public/install.sh and assert it is #!/bin/sh and documents this exact command,
so `| sh` cannot quietly become a lie.
apps/logicsrc-web: 13 new tests pass, 46 total. The ontology-api contract file
fails to resolve @logicsrc/validators, which it also does on a pristine
origin/master -- unbuilt workspace package, unrelated to this change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The .btn skin is built for the light page ground (white fill, ink text).
The topnav rail is #101418, so those buttons landed there as stray
light-mode chips -- and ".bar a{color:var(--rail-text)}" outranks ".btn"
on color (0,1,1 vs 0,1,0), so <a class="btn">Settings</a> painted
rail-text on a white fill: 1.08:1, effectively invisible.
Scope a rail variant to .bar: transparent fill, rail-text label, and a
border at 38% rail-text (3.40:1, clearing the 3:1 non-text minimum --
--rail-line is only 1.4:1 and vanishes). Sign in keeps the green accent,
the focus ring moves green -> mint (3.60:1 -> 9.03:1), and .faint/.dim
in the bar resolve to --rail-dim.
Settings and Sign out go 1.08:1 and light-chip-on-dark to 17.20:1.
Body buttons are untouched -- every rule is .bar-scoped.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>