Commit graph

241 commits

Author SHA1 Message Date
1eb5e6c199 OpenTLD 0.1: what a domain costs at a registrar, and the TLD record from IANA
A registrar price list at /.well-known/opentld.json (register, renew,
transfer, restore per TLD, promo beside the price it falls back to), the
top-level domain record a directory builds from IANA's list, root zone
database and RDAP bootstrap with a daily diff, and the RDAP availability
rule (404 is not registered, never available). nichedb.dev/tlds is the
first reader.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 16:32:35 +00:00
4ac750e65b
feat(pwa): move app.logicsrc.com from Turso/libSQL to Postgres via @profullstack/libsql-pg (#218)
Production reads DATABASE_URL (postgres://) through @profullstack/libsql-pg,
which keeps the @libsql/client surface; no query changed. A missing,
non-Postgres or leftover libsql:// URL fails at boot in production. Dev and
tests keep libSQL (file:/:memory:) as a devDependency. Postgres migrations in
src/migrations-pg/ (converted with libsql-pg convert-schema, same file names
so the copied _migrations ledger matches); migrate.mjs picks the dialect.
Tests run against Postgres with PWA_TEST_DATABASE_URL.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-25 09:28:40 -07:00
c535aaa32c
Restore .nixpacks so deploy-dev2 can build master again (#217)
PR #216 deleted .nixpacks/ and git-ignored it; both dev2 deploy workflows
build from .nixpacks/Dockerfile, so every deploy of master since has failed
before the build (lstat app/.nixpacks: no such file or directory). Restored
from f478025.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-25 08:46:26 -07:00
587c073f72
ops: deploy app.logicsrc.com to dev2 on merge (#216)
* ops: deploy app.logicsrc.com to dev2 on merge

Railway deployed this on push; dev2 does not by itself. The workflow ssh's
to the box and runs /home/anthony/www/app.logicsrc.com/deploy-app.sh, which builds
the image from this checkout and restarts the compose stack. Generated by
cli-tools dev2/dev2-site scaffold.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ops: stop committing the nixpacks build plan

`railway up` writes .nixpacks/ into the working tree: a Dockerfile, a
nixpkgs pin, and a build.sh that hard-codes the absolute path of the
worktree it ran in plus a one-off image tag. None of it is reusable and
no workflow reads it, so every branch that deploys regenerates it and
conflicts with the last one -- which is exactly how this branch ended up
CONFLICTING against master.

Drop it and ignore it.

* ops: drop the nixpacks build plan master picked up in #215

Same detritus, same stale absolute path (a worktree that no longer
exists). Now ignored, so it will not come back.

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-25 05:52:37 -07:00
f4780259fd
ops: deploy logicsrc.com to dev2 on merge (#215)
Railway deployed this on push; dev2 does not by itself. The workflow ssh's
to the box and runs /home/anthony/www/logicsrc.com/deploy-app.sh, which builds
the image from this checkout and restarts the compose stack. Generated by
cli-tools dev2/dev2-site scaffold.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-25 03:49:38 -07:00
c434a65272
OpenVehicle is now OpenCar, and scoped to match the name (#214)
Anthony asked for OpenCar twice after I argued for OpenVehicle on the
grounds that it covered boats. His name is the better one: OpenCar is
what a person reaches for, and a site that lists cars lists the
motorcycle and the pickup beside them.

So rather than keep a name that disagrees with its contents, the scope
moves to match the name. `kind` is now car, motorcycle, truck, van,
suv, bus, rv, atv, other -- road vehicles. Boats and heavy equipment are
dropped from it and belong in sibling profiles, which cost nothing to
add because the parent does not change. Keeping them here would have
meant every field being optional and meaningless for half the subjects.

`subject.type` is `car` and the key is `subject.car`, so the axis value
and the profile agree.

Renamed the day it shipped and before anything reads it: no descriptor
exists yet, and nichedb's directory has not been built. Doing it later
would have meant a migration.

50 contract tests pass, spec-discovery included.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-25 03:22:36 -07:00
32bbdeda95
OpenListing 0.1: one file a seller serves about a thing on offer (#213)
A house for sale, an apartment to rent, a car. One document per listing,
on the seller's own origin, so a directory reads the seller's file
instead of licensing somebody else's database or scraping a marketplace
that forbids it.

**The name `OpenRental` was the obvious one and it is taken.** It
already means renting OpenAgent and OpenSwarm members through CoinPay,
with a published schema, SDK, validators and fixtures. Its own document
records surviving one collision already: it was OpenFleet until
2026-09-13. Reusing the name for housing would have broken a shipped
0.1 spec, so the family is OpenListing instead.

Two axes rather than a spec per combination. A house for sale and a
house to rent are the same house; only the offer differs. A house for
sale and a car for sale are the same offer; only the subject differs.
Writing OpenHouseForSale, OpenHouseForRent, OpenCarForSale and the rest
produces one spec per cell of a grid, each repeating most of the others.
So `offer.type` carries sale/rent/lease/auction/free/wanted and
`subject.type` carries property/vehicle, and a subject profile adds only
the fields peculiar to its subject. A reader that knows OpenListing but
not a profile still reads price, location, offer and dates correctly.

`openhouse` is not used as a name: in real estate an open house is a
viewing event, not a kind of listing. An actual one is an entry in
`showings`.

The problem section is grounded rather than asserted: RESO tracks 484
separate MLSs as of August 2026, down from nearly twice that in 2015;
each does publish a RESO Web API feed, and each requires a real estate
licence, a signed per-MLS data agreement and vendor credentials. Four
hundred and eighty-four negotiations for one national view, not
redistributable. Vehicle parts are the same shape with different
letters (ACES/VCdb, TecDoc).

Decisions worth naming. Prices are decimal STRINGS, because 2450.10 is
not representable in binary floating point and a listing is a price.
Absent means unstated, never zero -- with `bedrooms: 0` called out in
the property profile as the one place where zero genuinely differs and a
studio is not an unstated bedroom count. `location.precision` lets a
seller publish a postal code rather than a street for an occupied home,
and forbids a reader from drawing a pin on a building anyway. A closed
listing SHOULD stay served for 90 days with `closed_at`, because a
directory that learns from a 404 cannot tell "sold" from "outage" --
the failure that makes every coupon site a graveyard.

The vehicle profile treats a VIN as a claim rather than a proof, points
readers at NHTSA vPIC (free, keyless, public domain) for decoding rather
than making sellers restate what the VIN encodes, and puts parts fitment
explicitly out of scope: that is ACES/VCdb or TecDoc, and a public spec
cannot restate licensed data.

Registered as one parent and two blocks under it, using the registry's
existing `parent` field the way OpenServer's blocks already do. Flat
slugs rather than nested paths because /docs/[slug] is a single dynamic
segment, not a catch-all -- a nested slug would not have routed at all.

nichedb.dev is named as the reference directory.

50 contract tests pass including spec-discovery and page-metadata.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-25 03:14:19 -07:00
699eff427a
OpenModel 0.1: what a model costs, from the provider that bills you (#212)
Some checks are pending
CI / build (push) Waiting to run
test / test (push) Waiting to run
There is no shortage of places to look up an AI model and nowhere to look
up what it costs you, from the provider that will bill you, in a form a
program can read. Every provider publishes prices as a marketing page and
every comparison site scrapes those pages. The best of them, models.dev,
is a community database: 8,179 provider offerings across 223 providers,
maintained by volunteers reading pricing pages. Good work, and still a
third party writing down what a company charges.

OpenModel is that table served by its author at
/.well-known/openmodel.json: price per million tokens in, out and cached,
context and output limits, modalities, and what each model can do.

The unit is the offering, a provider and a model together, because the
same model reaches a buyer from many providers at many prices and the
price is the question they came with.

Two rules decide what a row means, and both come from measuring the real
data. A published zero is a fact: of those 8,179 offerings, 638 cost
nothing and 424 published no price at all, so a reader that collapses the
two invents a free model or hides one. And absent is unstated, never
false, because a false travels further than a blank and comes back quoted
as a fact.

Origin is the proof, as with every catalog spec here: a file naming a
provider's web and served by somebody else is a stranger's claim about
that company's prices.

The first reader is the models collection at nichedb.dev/c/models, which
reads descriptors beside the models.dev catalogue in the same vocabulary
so one feed catches both.

Registered in the one registry, with docs/openmodel.md and a docs-only
entry (no landing page yet), and added to the spec-discovery contract so
the family page, docs index, llms feeds and sitemap are proven to carry
it.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 11:21:10 -07:00
053ac427b6
OpenIcon: the agentic styles re-render brand marks, and a style says which ground it needs (#211)
Some checks are pending
CI / build (push) Waiting to run
test / test (push) Waiting to run
Anthony, on the gallery with the Brands filter and Agentic Emissive
selected: "why doesn't brands change?" It could not. Every brand file
was byte-identical across all four colour styles — github.webp was the
same 606-byte file in hq, matte, machined and emissive — because a
colour style stopped at the drawn icons and a brand's coloured form was
just the line mark recoloured to the owner's hex.

The three agentic styles now re-render the marks in their material,
pinned to the owner's own mark: same geometry, same colour, new surface.
hq keeps the flat marks a first-release reader already has.

Spec:

- Rule 7 said "a set does not redraw them", which forbade exactly this.
  It now says what a re-rendering may and may not do: geometry, counters
  and figure/ground are fixed, only the surface changes.
- Rule 8 gains the third provenance: a re-rendered mark is `both`, the
  geometry its owner's and the material the set's.
- Rule 10 covers every colour style rather than hq alone, and says what
  a material that works by emitting light does with a mark too dark to
  emit: its own colour where it can, a lighter tint of the same hue
  where it is dark but still coloured, neutral white where there is no
  hue left. 85, 4 and 22 of the 111.
- Rule 12 says how a reader tells the two apart without comparing files:
  a flat style writes `svg` and `made_by: human`, a re-rendering style
  writes no `svg` and `made_by: both`.
- Rule 13 gains `ground`. It already observed that a near-black style is
  right on a terminal and illegible on paper, but gave a reader no way
  to know which style that was.

Gallery:

- Honours `ground: dark`, so emissive draws on the dark tiles it needs.
  Without it the 22 marks that emit neutral white — GitHub, Apple,
  OpenAI — were invisible on a white tile, which is the same class of
  bug as the one that started this.
- In the detail hero each style tile carries its own ground, so a
  dark-only style sitting beside three light ones still shows something.

The hosted set carries the new brand webp for the three agentic styles
and drops the 333 flat marks they no longer reference.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 07:24:08 -07:00
08559cacd7
OpenIcon: the gallery must hold four colour styles, not one (#210)
The detail panel's hero was a flex row that could not wrap, and the style
tabs were a segmented group that could not shrink. Both were written when
a set had one colour style; with hq plus the three agentic styles the
later tiles fell outside the panel entirely ("Agentic Machined" and
"Agentic Emissive" rendered past the right edge) and the tab group's
min-content width dragged the whole page onto a horizontal scrollbar on
a phone.

- hero: style previews move into their own auto-fit grid that reflows,
  72px -> 64px so four fit a 24rem panel, labels wrap and centre. The
  margin-left:auto that positioned the single old tile is gone.
- toolbar: the grid column is minmax(0, 1fr) rather than auto, so no one
  unbreakable control can widen the page, and the style group scrolls
  inside its own border.

Both are sized by what the set ships, so a fifth style costs nothing.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 05:45:57 -07:00
559c500ea4
OpenIcon: a set may carry several colour styles (#209)
* OpenIcon: a set may carry several colour styles

Rules 12 and 13. HQ was the first colour style and it was drawn beside
glossy 3D emoji masters, which reads as the decade it borrows from, so
a colour style cannot be one look forever: the reference set now carries
four, HQ plus three flat-material agentic styles.

Rule 12 makes more than one a map rather than a second name: `styles`
lists them, `style_info` describes each, and each icon carries a `styles`
map whose entries are shaped exactly as rule 9's `hq` block. A set with
an HQ style keeps writing the old top-level `hq` block and per-icon `hq`
key as the same objects under their older name, so a reader written
against the first HQ release keeps working.

Rule 13 makes a style say what it is for. The choice between colour
styles is a choice of surface: near-black bodies with lit accents are
right on a dark terminal and illegible on paper, and only the set knows
which is which. A family shares a prefix and the family name alone means
its default member, so `agentic` means `agentic-matte`.

The gallery follows the manifest instead of a simple/HQ switch: one tab
per style the set has actually drawn, labelled and explained from
`style_info`, and the detail pane shows every style an icon has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* openicon gallery: serve the three agentic styles

The hosted copy of the reference set gets the new styles' 64 and 128px
webp and the brand SVGs, plus the manifest that now lists all four
colour styles, so the gallery's tabs have something behind them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 05:26:41 -07:00
94013eefe9
chore(release): CLI 0.4.0 (#208)
0.3.0 has been the reported version through three merges that changed what the
vault can import: Bitwarden JSON read natively, then ids, timestamps, password
history and URI match rules preserved, then 1Password .1pux plus six more CSV
products and a --source that names a product rather than a file format.

install.sh reads the version straight out of packages/cli/package.json, so
`logicsrc --version` and install.json both said 0.3.0 on a build that already
carried all of it. Anyone checking a version to tell whether their CLI could read
a 1Password export got the wrong answer.

Only the CLI moves. @logicsrc/opencreds stays at 0.1.0 deliberately: its version
is hardcoded in three places and one of them is the GENERATOR stamp written into
every exported database, so bumping it changes a file-format field. That is worth
doing on its own terms, not as a side effect of making --version honest.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 04:39:15 -07:00
6f26e23e2e
Import from any password manager, and let a person say which (#207)
Two gaps. Format was decided inline in the import command by whether the text
started with a brace, and --source only ever reached the CSV reader -- so a JSON
or archive export could not be forced at all. If the sniff was wrong there was no
way to say "this came from 1Password". And 1Password's own export button produces
a .1pux, which nothing here could open.

A source now names a product rather than a file format. Bitwarden exports JSON
and CSV; 1Password exports .1pux and CSV. Saying --source onepassword says where
the file came from, and the container is still decided by looking at the bytes.
One router owns that decision instead of the command, and when nothing can read a
file it prints every source that can be named rather than the bare "pass
--source" it used to.

1Password's .1pux is a ZIP holding a single JSON document. Pulling in a zip
library for that would have been this package's only dependency beyond commander,
so the central directory is read here: node's zlib already does the
decompression, and the container is a few dozen lines of offsets. Deliberately
not a general ZIP implementation -- no encryption, no ZIP64, only the two
compression methods an export uses.

The .1pux reader keeps vaults as folders, TOTP secrets, custom sections, password
history and the whole of a card. 1Password item ids are 26-character base32
rather than UUIDs, so they are hashed into a v5-shaped UUID: the same export
imported twice produces the same ids, which is what makes a re-import report its
items as already present instead of duplicating the vault. Trashed items are left
behind and reported, since restoring deleted entries into a fresh vault would be
a surprise. A category we do not model -- a passport, a server, a licence --
becomes a note carrying its fields, so an import never quietly loses one.

Six more CSV products join the existing five: NordPass, Dashlane, Proton Pass,
RoboForm, Apple Passwords and Firefox. Adding Apple broke 1Password: their
columns are nearly identical and only 1Password's `type` separates them, so Apple
now requires its absence and 1Password is asked first. There is a test for that
pair, because the failure mode is silent -- every 1Password CSV had started
importing as Apple.

LastPass and KeePass needed nothing: LastPass only ever exports CSV, which was
already read, and the same is true of KeePass's CSV.

Verified end to end: the real 4,395-item Bitwarden export still reads, a .1pux
round-trips through the CLI, an unidentifiable CSV prints the source list and
then imports once told, and an unknown source name is refused by name. The zip
reader is tested against archives the system zip produced rather than ones we
wrote. 195 tests pass.

The 1Password mapping is built from the documented 1PUX schema and driven by
fixtures, not from a real 1Password export. Run it with --dry-run first.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 04:22:35 -07:00
b873c2bf41
Keep what the Bitwarden export actually says (#206)
Four things the first pass threw away, all of them recorded in the mapping notes
from the 2026-09-02 hand conversion and all of them silent.

Item ids were regenerated. Bitwarden ids are already UUIDs, so keeping them is
what makes a re-import idempotent: run the same export twice and the second run
reports its items as already present under "skip", rather than duplicating the
entire vault. createItem deliberately refuses a caller-supplied id and always
mints a fresh one, so the id is adopted after construction, and only when it
really is a UUID.

Timestamps were restamped to "now". creationDate and revisionDate are the only
record of when a password was last rotated, and overwriting them destroys that
permanently. The oldest item in a real export dates to 2018; every one of them
would have been dated today.

Password history was dropped entirely. It is carried now, newest first, mapping
lastUsedDate to changedAt and capped at MAX_HISTORY_ENTRIES. 189 items in a real
export have history, so this was not a rare case.

URI match rules were hardcoded to "domain". Bitwarden stores them as a number --
0 domain, 1 host, 2 startsWith, 3 exact, 4 regex, 5 never, with null meaning
domain -- and flattening them quietly widens a login pinned to an exact URL,
which is a security change rather than a cosmetic one.

Verified on the same 4,395-item export: all 4,395 ids carried across, stable
across two runs, 189 items with history recovered, and the oldest createdAt still
2018-02-22. That export happens to use domain matching throughout, so the match
mapping is covered by tests rather than by it.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 04:06:47 -07:00
1f9c25fcd2
Read a Bitwarden JSON export natively (#205)
Importing a Bitwarden export failed with "Not an OpenCreds database" and had to
be converted by hand first. The import command decided what a file was by
`text.trimStart().startsWith("{")`, and treated every JSON as an OpenCreds
database. A Bitwarden export starts with a brace too, so it went to parseDatabase
and was rejected there.

JSON is the format Bitwarden's own UI hands you by default, and the only one of
its formats that keeps folders, custom fields, multiple URIs per login and full
card/identity detail. Its CSV drops all of that, so "export as CSV instead" is a
lossy workaround rather than an answer.

The shape is now sniffed before deciding which reader owns the file: an `items`
array plus either a `folders` array or the `encrypted` flag. An OpenCreds
database has neither at its top level, so the two never collide, and a cheap
substring test means a large database is not parsed twice to find that out.

Everything the format carries is mapped: all four item types, Bitwarden's own
folder ids (so two folders sharing a name stay distinct), custom fields with
their hidden flag, every URI rather than only the first, TOTP secrets, and
favourites. An untitled login is still named after its host. A folderId naming no
folder is dropped rather than inventing a folder, and only folders something
actually landed in come back, so importing one item out of a big export does not
create sixteen empty folders beside it.

An encrypted export is refused outright instead of half-read. Its items are
opaque strings, so a best-effort parse would store ciphertext as if it were a
password and leave a vault full of junk that never decrypts.

Verified against a real 4,395-item export: 4,387 logins, 3 cards, 4 identities,
1 note, 16 folders, zero skipped, parsed in 56ms. Every count matches the source
file, and the command that used to fail now reports "(Bitwarden JSON)".

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 03:59:41 -07:00
ad4879b0fe
OpenEmoji catalog: the complete set, and install by network (#204)
Some checks are pending
CI / build (push) Waiting to run
test / test (push) Waiting to run
All 3,963 emoji (previews 64/128 and openemoji.json with CLDR keywords
and oe_ shortcodes), and platforms.json: 23 networks, 324 packs, steps
and limits, downloads from the openemoji release. The catalog's Install
on picker appears with it.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 01:34:12 -07:00
61870aa481
seo: give every page its own social title, not the homepage's (#203)
Reddit, Slack, X and Facebook read og:title, and every page of
logicsrc.com was serving the homepage's. The <title> tag was correct per
page, which is why it went unnoticed: Next.js shallow-merges metadata, so
a page exporting only `{ title, description, alternates }` inherits the
root layout's `openGraph` object whole. og:title, og:description and
og:url were the homepage's on all 45 spec landing pages, every doc, every
report and every SPA section.

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

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

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 01:00:25 -07:00
baa3adf716
OpenIcon gallery: the HQ style for all 370 icons (#202)
openicon.json with the hq block, 259 full-colour UI icons as WebP 64/128
and 111 brand SVGs in their owners' colours. The gallery's Simple/HQ
toggle appears on its own now that icons carry hq.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 23:31:22 -07:00
0b3a7188de
OpenEmoji catalog: 2,424 drawn glyphs, and browse/install by network (#201)
* OpenEmoji catalog: browse and install by network

Reads platforms.json from the set: a network picker grouped by kind
(custom emoji, sticker packs, images to post), and for the chosen
network its verified limits, install steps, packs with sizes, Show (in
the grid) and Download (release assets), the grid narrowed to what its
bundles hold with each emoji's name on that network, and the detail
panel saying which pack holds it. A network that names what it is for
opens on it: X opens on country flags.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* OpenEmoji catalog: previews for 2,424 drawn glyphs

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 23:05:15 -07:00
f2e4dd1ea9
OpenIcon: the optional HQ style (rules 9-11) (#200)
A set may add a full-colour HQ style beside the canonical simple one:
styles: [simple, hq], a top-level hq block (sizes, webp_sizes, provenance,
coverage) and an hq block per icon (png, webp, svg for brands, made_by,
hex, hex_source). Same silhouette as simple, falls back to simple, brand
colour must be the owner's with its source, provenance is per style.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 22:48:32 -07:00
3e8d3098ba
OpenIcon gallery: all 370 icons on /openicon, with search, filters and terminal glyphs (#199)
Replaces the 47-icon sample with the whole reference set: search by name,
alias or keyword; category chips with counts; UI/brand filter; SVG,
Nerd Font, Unicode or ASCII view (the Nerd Font view uses the official
symbols subset to the set's 349 glyphs, 38 KB woff2, MIT); size and
colour; a detail panel with all three glyphs and codepoints, trademark
notes, copy SVG / <img> / icon("key"), and downloads. Ready for the
optional HQ (full-colour) style: a Simple/HQ toggle appears once icons
carry an hq field. Filters live in the URL.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 22:43:12 -07:00
3883ee7a2c
OpenIcon 0.1: an icon set as a folder, with terminal glyphs (#198)
One openicon.json naming every icon (kebab-case keys, unique aliases,
shared keywords), 24x24 currentColor SVGs with a stated grid, and three
terminal glyphs per icon: a Nerd Font character with its codepoint and
glyph name, a Unicode symbol and a 1-4 character ASCII spelling, picked
nerd > unicode > ascii. Brand logos carry brand: true, a trademark note,
source and licence. made_by + W3C AI disclosure per set and per icon.
Mapping from Lucide, Tabler, Font Awesome, Nerd Fonts and Simple Icons.
Landing page shows 47 icons from the reference set (profullstack/openicon).

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 22:27:15 -07:00
f80488ce92
OpenEmoji catalog: every emoji in the reference set, with search and filters (#197)
* OpenEmoji catalog: every emoji in the reference set, with search and filters

/openemoji/catalog reads the set's openemoji.json (every Unicode emoji,
drawn or not) and filters by name and CLDR keyword, group, subgroup,
skin tone (keyboard-style swap), Emoji version and status, with sort,
tile size, URL-synced filters, paged rendering and a detail panel
(codepoints, tone family, copy, PNG/SVG downloads, keyword chips).
Spec: an entry without files is listed, not drawn.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* OpenEmoji catalog: reference set previews, first 206 glyphs

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 21:27:40 -07:00
6d1bcd0e05
OpenEmoji 0.1: an emoji set as a folder (#196)
One openemoji.json that states coverage against a Unicode version, the
licence, and who or what drew the set (made_by plus the W3C AI disclosure
vocabulary), and glyphs named by fully-qualified codepoint sequence.
Custom emoji get x- keys with shortcodes; skin tones name their base;
rendered emoji keep the character as alt. Mapping from Mastodon, Slack,
Discord and CLDR.

Landing page shows 19 glyphs drawn by the reference implementation,
`emoji` in profullstack/cli-tools.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 21:08:15 -07:00
3d155af970
vault + teams: filter secrets by category, export to CSV, simpler help (#195)
Every secret now has a category derived from its name (db, social, server,
api, cloud, finance, crypto, ai, email, messaging, storage, dns, analytics,
devtools, auth, config, other). One rule table in @logicsrc/opencreds serves
both vaults; services win over generic words, so STRIPE_WEBHOOK_SECRET is
finance, not auth. Checked against the 1,208 distinct key names in the
profullstack team: 74 fall to "other".

Team vaults (where the shared .env secrets live):
- teams categories [team]    the filter words, with per-category counts
- teams secrets <team> [project] [env] --category/-c --search/-s
                             names + categories, never decrypts; --format csv
- teams export  <team> [project] [env] --category -o file.csv [--yes]
                             decrypts into team,project,env,category,key,
                             value,updated_at (0600); skips vaults without a
                             grant and names them

Personal vault (OpenCreds):
- vault list --category, and the category column in list output
- vault export --format csv: one flat row per item, keeps key/account
  secrets that a Bitwarden CSV drops; --category on every export format

DX:
- examples in `logicsrc vault help` / -h / --help that start by saying which
  of the two vaults you want, plus examples on teams and each subcommand
- password prompts go to stderr, so eval "$(logicsrc vault unlock)" works
- hints name the command you actually ran (logicsrc vault init, not
  opencreds init)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 21:08:08 -07:00
156c9164a9
Add OpenSong spec: a song as plain-text blocks a generator takes (#194)
Some checks are pending
CI / build (push) Waiting to run
test / test (push) Waiting to run
TITLE / STYLE / EXCLUDE STYLES / LYRICS label blocks with [Section - direction]
tags, no Markdown (Suno reads plain text only). Kept beside the audio as the
same name with .txt, album lyrics.txt with ==== dividers, one-way mapping to
ID3/Vorbis tags. Example is track 002 of Þrøngva, When the Ravens Lied.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 17:33:18 -07:00
50eaf30beb
Honour Profullstack Power Keys: x402-gateway 0.7.0 (#193)
Some checks failed
CI / build (push) Has been cancelled
test / test (push) Has been cancelled
A Power Key bought once at profullstack.com/shop now opens this site:
the gateway verifies it offline with @profullstack/keys and treats it
as a pass, so it is never charged as a crawler or metered. The 402
body and the sales page point at the shop.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 02:58:10 -07:00
c7cecb2955
feat(specs): OpenObject and OpenSlice 0.1 (#192)
Some checks are pending
CI / build (push) Waiting to run
test / test (push) Waiting to run
OpenObject is a bucket you can mount: keyed objects as ipfile swarms placed
on OpenDisk disks under pay2seed at a stated redundancy (three replicas on
three operators in two countries by default), an ipdb index as the bucket's
clock, a repair loop, an HTTP API, an S3 mapping and a mount whose
consistency is close-to-open by seq. d1sks.com is the reference store.

OpenSlice is a container whose compute is rented from one market and whose
disk is mounted from another: a host descriptor at
/.well-known/openslice.json, one signed slice file (image by digest,
OpenCPU/OpenMemory/OpenGPU units, OpenObject mounts, OpenCreds env, ports,
placement, lifetime), and a reservation that is a paid2seed lease applied to
compute with presence proofs per epoch. slic3s.com is the reference
marketplace; c0mpute hosts take the slice role.

Both are registered under OpenServer in the catalogs family with landing
pages, rows in the OpenSwarm family table, and the spec-discovery contract.
llms.txt now cites the specification URL for child specs too, which every
block under OpenServer had been missing.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 03:05:38 -07:00
8e4ea2f997
docs: OpenStack.md 0.1, one Markdown file for what a project is built on (#191)
A new LogicSRC spec in the catalogs family. One heading, an identity block
(Kind, Web, Repo, Operator, License, Extends, Updated), one line, then eleven
layers: Languages, Runtimes, Interfaces (one ### per way in: web, api, cli,
tui, mcp, desktop, mobile, worker, extension, bot), Data, Services, Modules,
Tooling, Hosting, Auth, Conventions, Not. An item is one bullet: name,
version, an optional status word (trial, hold, leaving) and a role. Extends
inherits a parent file, sections replace, Conventions and Not accumulate.
Rule 8 is the reading rule for agents: use what is listed, prefer listed
over new, ask before adding a layer or a service, never add a Not, keep the
file true. Discovery at OpenStack.md in the repo, /.well-known/openstack.md,
rel=openstack, or a platform path. JSON is derived and never the source.

logicsrc.com serves its own at /.well-known/openstack.md: the route extracts
the worked example from docs/openstack.md, and a contract test holds the two
together and checks the file follows its own rules. rel=openstack in <head>
and in the Link header beside openprofile.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 02:21:12 -07:00
08490aecf3 fix(web): scope sidebar navigation styles away from breadcrumbs
Some checks failed
CI / build (push) Has been cancelled
test / test (push) Has been cancelled
2026-09-13 16:02:00 +00:00
60ffe1051a fix(web): keep expanded skills navigation inside mobile viewport 2026-09-13 15:57:39 +00:00
25e18f0766 Add OpenSkill descriptions for human capabilities and OpenProfile discovery 2026-09-13 15:47:58 +00:00
ba69f58243
OpenWebring and OpenWiki carry the whole AI Content Disclosure vocabulary (#190)
The W3C AI Content Disclosure community group defines four attributes,
not one: ai-disclosure, ai-model, ai-provider and ai-prompt-url. Both
specs carried only the first, as `disclosure`. They now carry all four,
as `disclosure`, `ai_model`, `ai_provider` and `ai_prompt_url`: which
model, whose, and a page describing how it is used, optional and
meaningful only beside a disclosure other than none. `mixed`, the value
the group's meta tag form allows, is accepted at the site level in
OpenWebring and the page level in OpenWiki.

A ring host copies all of them into the member entry as the member said
them. A wiki carries them on the page and per revision, and a host
rendering a page puts the same four on it as the group's meta tag and
attributes. Both specs are 0.1.1; the wire version stays 0.1, since every
field is additive and absent is still unstated.


Claude-Session: https://claude.ai/code/session_01XYae2mH3khdwiXUVzcVMDw

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 13:14:05 +00:00
0395ff12ce
Publish the OpenWiki specification: a wiki that is a folder of Markdown files (#189)
OpenWiki writes down what the [[Page]] convention already is in practice,
so a wiki can be moved, mirrored, read by a directory and edited by an
agent without reading any engine's source. One Markdown file per page
with a small optional front matter (title, aliases, tags, summary,
author, made_by, disclosure, created, updated, license, lang, redirect);
wikilinks resolved by exact name, loose name, alias, path, then wanted;
one address with three representations (rendered, .md, .md?rev=) and a
history.json per page; a descriptor at /.well-known/openwiki.json with
pages_url, changes (Atom), repo, edit, editors and accepts; editing as
PUT of the same file with If-Match on the revision; discovery; what a
directory owes a wiki. Every revision says who made it: human, ai or
both, the same word OpenWebring uses.

A folder that follows it opens unchanged in Obsidian and Logseq, and any
such folder becomes an OpenWiki by adding the two files. The first host,
goviral.wiki, is being built on it.


Claude-Session: https://claude.ai/code/session_01XYae2mH3khdwiXUVzcVMDw

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 13:00:20 +00:00
f389edb6f8
Publish OpenAgent profile specification and discovery (#188) 2026-09-13 05:37:59 -07:00
fd25e8e4ba
docs: OpenStream benchmark report for nixamp 0.24.2 (#184)
Some checks are pending
CI / build (push) Waiting to run
test / test (push) Waiting to run
Manual publish of the report attached to the nixamp v0.24.2 release
(openstream-report-0.24.2.json/.md). The release workflow's
publish-report job skipped because LOGICSRC_PUBLISH_TOKEN is not set
on profullstack/nixamp. Built-in synthetic corpus, so the id carries
the -synthetic label per docs/openstream/reports/README.md.


Claude-Session: https://claude.ai/code/session_015uheT4Gn9BBKAnSZGcabyN

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 05:16:24 -07:00
dfbf5a4bc4
docs: OpenL10n and OpenI18n, the words half and the languages half of OpenFile (#187)
OpenL10n is the record of what a file says, in any language: one
transcript per media per language, addressed by the OpenFile id (or a
url: or live: identity for what has no bytes), lines as seconds into the
media, a translation kept beside the original and marked with what it
came from, complete or heard in pieces, made once on request with 202
progress, served as JSON, SRT, VTT or text.

OpenI18n is one file a service serves at /.well-known/openi18n.json
about the languages it speaks: which, which it can turn into which, how
to ask for one, and where texts are translated, with limits, models and
the pivot said out loud.

Both are what nixamp 0.24 and 0.25 serve today, written down so anything
that hears or translates can keep the same record.


Claude-Session: https://claude.ai/code/session_015KAKuRngFbQg3kET6RS5yN

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 04:39:59 -07:00
a47dc62fec
OpenWebring: nichedb.dev/c/webrings is the first directory reading rings (#186)
The spec and its landing page now name the directory that reads every
OpenWebring host: one row per ring and one per member, with made_by as
the member said it and the status the host last verified, re-read hourly.
The first host line also names rssamplifier's curated Profullstack ring.


Claude-Session: https://claude.ai/code/session_01XYae2mH3khdwiXUVzcVMDw

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 11:11:55 +00:00
9ae7ad8962
OpenFleet reference implementation: @logicsrc/openfleet, logicsrc fleet, and Claude Code hooks (#185)
* OpenFleet reference implementation: @logicsrc/openfleet 0.1.0 and logicsrc fleet

Ship what docs/openfleet.md describes. The new workspace package holds the
record (write once, never overwrite, 0600), the ledger (append-only JSON
Lines, merged across ledger*.jsonl by at), the ceiling rules (whole fleet
ceiling, narrowed swarm keys, a merge that never widens, refusals by key),
claiming and deriving exactly as the spec's "Claiming and deriving" and
rule 13, and fold(), which turns any $OPENFLEET_HOME plus the engine
rosters into the tree the landing page shows.

logicsrc fleet open|cap|tree|stop|log are the sysop's verbs, every one with
--json. open and cap exit 4 when OPENFLEET_MEMBER is set; stop exits 4
outside the caller's subtree, ends nested swarms first, goes through each
member's own engine (claude stop, moshcode herd kill, tmux kill-pane, a
signal for claude-p) and writes one swarm.end per swarm. tree reads claude
agents --json --all and ~/.moshcode/herd/sessions.json when it can, draws
recordless sessions as roster roots of the implicit fleet, and writes
member.end lost for a recorded member its engine no longer lists.

Claude Code takes part through hooks: logicsrc fleet hooks install merges
SessionStart, UserPromptSubmit, PreToolUse, Stop and SessionEnd into
~/.claude/settings.json without clobbering it, and logicsrc fleet hook
<Event> runs each one. SessionStart claims, derives or writes a root record
and hands the member its variables through CLAUDE_ENV_FILE; UserPromptSubmit
checks the ceiling with the permission mode the engine reports and writes
member.start, or refuses the first prompt with exit 2 and ceiling.refuse;
PreToolUse denies an edit outside piece.owns; Stop and SessionEnd write
member.end. A hand-started root takes the engine's reported approvals
before member.start, since the command line only guesses them. Hooks
never fail the engine: everything is caught and logged to hooks.log.

The spec and the landing page now say what ships, keep Status 0.1, and
record the two verified Claude Code limits: a background job dispatched from
claude agents gets no launcher environment, and OPENFLEET_* exported at
SessionStart reach the member's tools but not later hooks, so hooks key on
session_id through $OPENFLEET_HOME/sessions/<session_id>.json. PRD 0008
covers the work. CLI 0.2.1 -> 0.3.0; build and build:cli chains build the
package before the CLI; README and docs/cli.md list the group.

Tests: 95 in the package (record, ledger merge, every narrower case, the
worked example's claim and derive, the folded tree, hook install
idempotence, each hook handler including the exit-2 refusal and the
PreToolUse deny, every verb with fake deps) and 4 in the CLI.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RZV4zJ2pDZLNN3kE5jFCmV

* OpenFleet fix round: rebuild the ceiling from the ledger, once-markers, rule 6 in tree, lost only for what a roster can hold

The review of the reference implementation against moshcode found the two
readers disagreeing on the same files. This round applies the shared
rulings so both sides read a ledger the same way.

Ceiling (R-A, R-B, R-C, R1, R6, R10, R15, R17): memberCeiling rebuilds the
effective ceiling from the ledger on every read. The latest fleet-target
fleet.cap (else fleet.open, else the implicit fleet's) replaces the copy in
a record, so a sysop's widening cap reaches running members; then each
swarm.spawn narrowing down the path, then swarm caps last. In the implicit
fleet a parentless record's own approvals enters at the root; a ceiling a
writer left without the key is never read as native, and startMember fills
it with the engine's word while the record is unclaimed. A fleet.open or
cap with no hosts means the host it was written on (R23).

Once-markers (R-G, R28): member.start, member.end and swarm.end each take
an exclusive create under fleets/<fleet>/marks/<event>.<id> before the
append; a lost end takes <id>.lost so a real end can still supersede it.
The hooks let a real end follow a lost line (R9).

tree (R-F, R20): run by the sysop it enforces rule 6, stopping a member
past its effective until with state timeout and the members of a swarm or
fleet at its budget with state budget, then writes swarm.end for each swarm
touched once it is complete. An agent's tree stops nothing. lost is written
only for a member its engine's roster can hold: a claude-code background job
(8-hex member or session) or a moshcode pane, never an interactive session
claude agents does not list (R-E, R3, R14). A nested swarm is drawn under
the member that spawned it and its row shows the effective ceiling (R25).

stop and cap (R-D, R-H, R22, R27): swarm.end is written only once every
member and every nested swarm has an end line that counts; an engine that
will not end a member leaves it without an end line and the verb exits
non-zero. claude stop takes the job id: the member of a background job,
else the first eight characters of a session UUID; an interactive session
with no job id cannot be stopped and the tool says so. cap on a swarm
refuses a key that would widen. A derived claude-code job is named by its
job id and carries no pid.

Also: R-I (endMember ends only the engine-minted swarm of one), R35 (a
derived record's guessed approvals corrected at UserPromptSubmit), R32
(the UserPromptSubmit hook passes only exit 2 through), R31 (package
README), R36 (rule 13 says the launcher test is unimplemented in 0.1),
docs and PRD 0008 updated for lost, rule 6 and the markers. 113 openfleet
tests, 93 CLI tests, contract green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RZV4zJ2pDZLNN3kE5jFCmV

* openfleet hooks: no member.end for a member that never started

A first prompt refused by the ceiling still lets the session wind down through Stop and SessionEnd; those handlers now write nothing when the ledger holds no member.start for the member, so a refused member is never drawn as done.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01RZV4zJ2pDZLNN3kE5jFCmV

* logicsrc-mcp test: the next free PRD id is 0009 now that PRD 0008 exists

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01RZV4zJ2pDZLNN3kE5jFCmV

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 10:58:55 +00:00
519d13c3d6
Merge pull request #183 from profullstack/feat/logicsrc-agent-pricing
Clarify implementation billing as $400 per agent-hour
2026-09-13 02:57:58 -07:00
b27b52fc06
Merge pull request #181 from profullstack/docs/openserver-reference
Promote OpenServer and label NicheDB as its reference implementation
2026-09-13 02:45:00 -07:00
a0148a34e4 Match browser pricing assertion to agent-hour units 2026-09-13 09:44:57 +00:00
d4ee3aa4dc
logicsrc.com joins the Profullstack ring (#182)
The footer carries the three OpenWebring links (previous, ring, random,
next, with ?from=https://logicsrc.com/blog, the member URL the directory
holds), and /.well-known/openwebring.json says who makes the blog: both,
ai-generated, in the W3C AI Content Disclosure vocabulary the spec adopts.


Claude-Session: https://claude.ai/code/session_01XYae2mH3khdwiXUVzcVMDw

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 09:43:25 +00:00
b5d1990c95 Lead with OpenServer and link its NicheDB reference implementation 2026-09-13 09:37:27 +00:00
f001abb539 Check agent-hour units in checkout validation 2026-09-13 09:35:14 +00:00
5727ac8d06 Clarify public implementation pricing per agent-hour 2026-09-13 09:30:24 +00:00
d8b3126782
Add OpenABTest draft experiment and accounting contracts (#180) 2026-09-13 02:17:35 -07:00
123ba2e39a Merge remote-tracking branch 'origin/master' into openfleet
# Conflicts:
#	apps/logicsrc-web/src/lib/specs.ts
2026-09-13 08:48:44 +00:00
b2c23f724b spec-discovery test: accept a landing-page link in llms.txt, still require the docs URL
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01RZV4zJ2pDZLNN3kE5jFCmV
2026-09-13 08:46:08 +00:00
68f3151699 Merge master; OpenFleet keeps its name, PR 177's rental descriptor becomes OpenRental
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
2026-09-13 08:45:33 +00:00