Skip to content

feat: Substrata — hosted sites, and a research firm that proves they work - #782

Closed
catomean wants to merge 10 commits into
mainfrom
claude/singularity-materials-profile-gzunby
Closed

feat: Substrata — hosted sites, and a research firm that proves they work#782
catomean wants to merge 10 commits into
mainfrom
claude/singularity-materials-profile-gzunby

Conversation

@catomean

Copy link
Copy Markdown
Collaborator

Description

Two things at once: a general mechanism, and the customer that proves it.

The mechanism — a profile spins up a whole website. /domains has been selling the sentence "a working site, hosted and managed at yourname.orangecat.ch" with nothing behind it. This is the machinery. A request arriving on a hosted site's host is rewritten onto /sites/<slug> and answered by a standalone site built from that profile's own structured data. Rewrite, not redirect — the URL bar keeps saying the customer's domain, which is the whole of what is being sold.

The customer — Substrata, an open-source research firm covering the physical chokepoints between here and a technological singularity. Its site is not separately authored: the mandate, the coverage universe, the thesis and the directory already exist as config because the profile needs them, and the eight pages are those same objects rendered for a different audience.

Also: a domain availability search on /domains, exposed as a public v1 endpoint so FleetCrown consumes the same one.

What's in the research

  • The map — 92 producer rows across 85 companies, for 15 chokepoint materials
  • Chokepoints — 14 constraints that are not materials at all: EUV scanners, packaging capacity, transformer slots, interconnection queues, process engineers
  • Participants — 102 organisations across 10 chain layers, ore to buyer, each graded chokepoint / concentrated / competitive
  • Thesis — 6 claims, each with a falsifier
  • Acting on it — how anyone acts in these markets, and a 9-item readiness ledger at 1 done

Every row starts unsourced and renders as "unverified lead", never as a finding. There is no trading desk and nothing on the site implies one.

Type of Change

  • New feature (non-breaking change which adds functionality)

Checklist

  • I have tested these changes locally
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the documentation accordingly
  • I have followed the project's coding standards
  • I have added necessary environment variables to the deployment configuration

Environment note: no new environment variables. The domain search needs outbound HTTPS from the box to rdap.org and data.iana.org — without it every lookup degrades safely to "check manually". Serving substrata.orangecat.ch needs a wildcard *.orangecat.ch DNS record and the matching Caddy entry; the path form /sites/substrata works today without either.

Type Safety Checklist

  • No new as any casts (unless absolutely necessary with explanation)
  • No @ts-ignore or @ts-expect-error (unless justified with description)
  • All new functions have proper return types
  • All parameters are properly typed
  • Database queries use generated types from @/types/database
  • New code passes type-check
  • Type guards used instead of as any for runtime validation

Verification

npm run verify green: 247 suites, 2463 tests, plus type-check, lint, route audit, duplication, dead-fields, schema-columns, currency-units, RPC-exists and MDX gates. Verified against a running server at 1280px and 390px.

Host resolution was checked live: substrata.orangecat.ch, www., and substrata.localhost all rewrite; orangecat.ch, notsubstrata.orangecat.ch and substrata.orangecat.ch.evil.example are all correctly refused.

Tests that exist for a specific reason

  • Domain search is gated on the IANA RDAP bootstrap, not on "404 means free". The naive version reports orangecat.ch — this project's own production domain — as available, because .ch publishes no RDAP and a redirector cannot tell "no such domain" from "no such registry". A test per no-RDAP TLD fails if one is ever reported free.
  • A material may carry no price, unit or lot-size field, and the config and site builder are grepped for dealing language. There is no desk; a config that quietly regrows a price field puts an offer in front of a reader.
  • The scarcity grades must all be in use, and chokepoints must stay a minority. A directory on which everything binds has graded nothing.
  • Coverage follows the universe both ways, and the meter and the tables must carry the same two numbers, so the picture cannot flatter the work.

Screenshots

Screenshots of all eight pages at desktop and mobile were shared in the session that produced this branch.

Additional Notes

  • 'site' is a fourth route surface, not a chrome override — a hosted site can never accidentally grow OrangeCat's header on someone else's domain.
  • siteForHost is pure and never touches the database; it runs in edge middleware on every request to the whole app.
  • substrata.ch and substrataintel.com are pre-wired as aliases. A host only reaches that check if DNS already points here, so listing them before purchase costs nothing and means the site works the hour one is bought, with no deploy.
  • The profile at /groups/substrata needs ORANGECAT_OWNER_SEED=1 npx tsx scripts/seed-substrata.ts run from the box; the site renders from config and does not depend on it.

🤖 Generated with Claude Code


Generated by Claude Code

claude added 9 commits August 26, 2026 15:49
…ials

Substrate Materials is a company profile with exactly one focus: the
physical inputs a technological singularity actually consumes. Software
is not the constraint — purified tin, neon, polysilicon, ruthenium,
transformer steel and rare-earth metal are, and each has a chokepoint, a
lead time and a counterparty.

The profile is a `group` (label 'company', public so a counterparty can
read the book before sending an RFQ), its own actor_type 'group' actor,
and a 15-line catalogue of `user_products` owned by that actor. Follows
the Revive My Old Ride convention: copy defined once in config, a
separate owner-gated seed writes it to the live DB.

What makes "solely focused" a rule rather than a slogan is the inclusion
test. A material is listed only if it moves one of three curves —
compute per joule, joules delivered, or actuation. Everything else is
declined, and the test is enforced: every listing must sit on a desk,
every desk on a curve, and no desk may sit empty.

- src/config/singularity-materials.ts — SSOT: mandate, exclusion rule,
  five desks, the catalogue, compliance stance, group + product payloads
- scripts/seed-singularity-materials.ts — idempotent, owner-gated seed
  (group by slug, actor by group_id, listings by (actor_id, title))
- test gating the payloads against the live CHECK constraints, the label
  / feature / governance registries, and the mandate itself

Treasury is deliberately not enabled: GROUP_FEATURES.treasury requires a
bitcoin_address the company does not yet have. Prices are indicative
reference levels, and every listing says so — a budgeting number, not a
quote. Export-controlled lines carry the licence and end-use conditions.

The seed has not been run; it needs the box's service-role key.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF9GDWaBWYHCZ9iNwmfZ41
…h firm

Reframes the trading company into what it should have been from the start:
an open-source research firm covering the chokepoints between here and a
technological singularity, with a trading desk on the materials it knows
best. The asset is the map, not the book — trading a commodity and
researching a robotics company are the same work, so the map is the
product and the desk is one way to monetise it.

Two tests now gate the universe instead of one. A node must move a curve
(compute per joule, joules delivered, actuation) AND actually gate it, on
concentration, substitutability, lead time and demand inelasticity. The
unit of coverage is a chokepoint node, not an asset class — a node can be
a material, company, person, machine or process. That is what lets the
firm reach robotics, AI hardware and additive manufacturing without
becoming "everything": you arrive at them by tracing a chain you were
already mapping, so the graph grows by traversal rather than ambition.

Phase 1 is the producers of the fifteen: 92 producer leads across 85
distinct companies, for every material on the desk. Each row asserts a
name, a jurisdiction and a step in the chain, and nothing else. There is
no field for capacity, share, revenue or quality — those are the claims
that go stale in a quarter and move markets when wrong, and the research
phase is what adds them with a citation. Every row therefore starts at
source: null, which reads as an unverified lead; coverage is measured by
how many rows have a source, not by how many rows exist.

Disclosure is written now, before any position exists, because a firm
that publishes on what it trades and intends to own cannot install that
rule credibly afterwards.

- src/config/substrate.ts — firm: curves, chokepoint screen, node types,
  four phases, desks, catalogue, compliance, disclosure, group payload
- src/config/substrate-coverage.ts — Phase 1 universe + coverageProgress()
- tests: the schema gates, plus coverage follows the book both ways and
  no producer row can carry an unsourced claim it has no field for

Renamed from singularity-materials — the firm is no longer materials-only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF9GDWaBWYHCZ9iNwmfZ41
/domains has been selling this sentence — "a working site, hosted and
managed at yourname.orangecat.ch" — with no mechanism behind it. This is
the mechanism, proved end to end on Substrate.

A request arriving on a hosted site's host is rewritten onto
/sites/<slug> and answered by a standalone website built from that
profile's own structured data. Rewrite, not redirect: the visitor's URL
bar keeps saying substrate.orangecat.ch, which is the entire point of
what is being sold. The path form keeps working on any host, so a site
is previewable before its DNS exists.

The website is not separately authored. Substrate's mandate, desks,
catalogue and coverage universe already existed as config because the
profile needed them; the five pages are those same objects rendered for
a different audience. Change the profile and the site changes with it —
and the map page cannot quietly drop the "unverified lead" caveat,
because the only data it has is the data the tests hold to source: null.

Adding a site is an entry in HOSTED_SITES plus a builder returning
SitePage[]. Pages are data, sections come from one closed set of shapes,
and one renderer serves every hosted site — which is what stops fifty
customer sites becoming fifty stylesheets.

- src/config/sites.ts — host → profile, pure and DB-free (edge hot path)
- src/config/site-content.ts — page/section model + per-site dispatch
- src/config/site-substrate.ts — Substrate's five pages, from its config
- src/middleware.ts — the host rewrite
- routes.ts + AppShell — 'site' is a fourth surface, not a chrome
  override, so a hosted site can never grow OrangeCat's header on
  somebody else's domain
- metadata uses title.absolute, so the tab says "Substrate", not
  "Substrate | OrangeCat"
- formatChf so a ruthenium quote reads CHF 15’000, in the listing the
  seed writes and on the site alike

Verified against a running server: the host rewrite resolves for
substrate.orangecat.ch, www., and substrate.localhost; orangecat.ch
itself is untouched; an unknown path under a site host 404s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF9GDWaBWYHCZ9iNwmfZ41
…eetCrown

Finding a name is the rung before the two services /domains already
sells — somebody with no domain cannot buy hosting for one. This adds
the check, as a public keyless v1 endpoint both products call rather
than a widget one of them owns.

THE RULE THIS IS BUILT AROUND

The obvious implementation asks a redirector for a domain and treats 404
as available. That implementation reports ORANGECAT.CH — this project's
own production domain — as free, because .ch operates no public RDAP
service and a redirector cannot distinguish "no such domain" from "no
such registry". The same false positive hits .io and .co.

So a TLD earns a definitive answer only by appearing in IANA's RDAP
bootstrap. Everything else — unsupported TLD, timeout, transport error,
odd status, bootstrap unreachable — is `unknown`, surfaced as "check
manually" with the reason. `unregistered` is reachable by exactly one
path: a bootstrapped registry that answered 404. Even then the copy says
premium pricing, registry reservations and trademark conflicts are not
visible here. A search tool that guesses is worse than one that admits
the gap, because the guess is what someone acts on.

- src/config/domain-search.ts — SSOT: bootstrap URL, TLDs, patterns,
  timeouts, and the status copy
- src/services/domains/availability.ts — bootstrap-gated RDAP lookup,
  cached, batched at a polite concurrency
- src/services/domains/suggest.ts — bare name across every TLD first,
  variants after; a caller may narrow the TLD list
- GET /api/v1/domains — registered in PUBLIC_API_INTEGRATION_ENDPOINTS
  beside search and demand, so FleetCrown gets it as a contract
- /domains grows a search box above the offer it feeds

19 tests, none touching the network, including one per no-RDAP TLD that
fails if the feature ever reports one of them as free.

Verified live: substrataintel.com and .ai unregistered; orangecat.ai and
orangecatlabs.com registered; orangecat.ch correctly unresolved rather
than free.

Note for deploy: the box needs outbound HTTPS to rdap.org and
data.iana.org, or every lookup degrades to "check manually".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF9GDWaBWYHCZ9iNwmfZ41
… properly

Renames the firm to Substrata Intel (files and slug follow, so a reader
never finds substrate.ts exporting a company called something else) and
gives the hosted site a design rather than a stylesheet.

TWO CORRECTIONS TO THE BRIEF, BOTH CHECKED

No domain is bought. The site runs on the free subdomain the platform
already offers. And it is orangecat.CH, not .com: orangecat.com is
registered to someone and serves nothing, while every part of this
platform — CD, Caddy, the /domains copy, SITES_BASE_DOMAIN — is .ch.

THE DESIGN

Editorial, not marketing. Space Grotesk for display, IBM Plex Mono for
anything a reader compares down a column, Inter for prose at measure.
Monochrome surfaces; the warm accent appears exactly twice, on the
current nav item and the coverage bar; status colour only on status.

- Hero replaces the generic title block on the home page: eyebrow, one
  display statement, and a lead cut to two paragraphs. The two that were
  dropped are said better by the phase block and the desk page, and a
  lead nobody finishes is not a lead.
- Coverage meter. The single most important number on the site now has a
  picture, and at 0 of 92 that picture is an empty bar. Drawn from the
  same data as the map, so it cannot flatter the work.
- The map gets a jump index — fifteen tables without one is a scroll,
  not a document — and every anchor is tested to land on a real table.
- Tables are table-fixed with declared widths. Auto layout sized each of
  the fifteen to its own longest cell, so one long company name shifted
  every column out of line with the table above it.
- Status cells carry a dot. "Unverified lead" and "Sourced" are the two
  words on this site a reader must never skim past.
- Sections are numbered, headings sit on a hanging number, cards are
  top-ruled rather than boxed.
- Masthead is one row at every width; the nav scrolls sideways on a
  phone instead of wrapping, because a two-row sticky header ate a third
  of a 390px screen.

Renderer split into focused section components under components/sites/
sections/, dispatched from one place. Three new section kinds — hero,
meter, index — available to every future hosted site, not just this one.

Verified against a running server at 1280 and 390: the host rewrite
answers on substrataintel.orangecat.ch, the tab reads "Substrata Intel",
and orangecat.ch itself is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF9GDWaBWYHCZ9iNwmfZ41
"Intel" was only ever a workaround for substrata.com being taken. On our
own subdomain there is no scarcity to work around, so the name is just
Substrata — config files, slug, subdomain and site all follow, and the
wordmark is one word again.

ORANGECAT.COM IS NOT OURS — DNS, not opinion:

  orangecat.ch   -> 167.233.22.31   (bitbaum, this platform)
  orangecat.com  -> 222.122.39.84   (KRNIC space, unrelated party)

So substrata.orangecat.com cannot be built: it would point a customer
site at somebody else's domain. The site is on .ch, where the CD, Caddy
and every line of the /domains copy already live. No multi-base-domain
plumbing was added for a .com we do not own — that is a constant to
change on the day it becomes true, not an abstraction to carry until then.

Also: the domain-search placeholder was one customer's name. It is
"yourname" now, matching the /domains copy.

Verified against a running server — substrata.orangecat.ch and
substrata.localhost both rewrite, the tab reads "Substrata", the retired
substrataintel host correctly no longer resolves as a site, and
orangecat.ch itself is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF9GDWaBWYHCZ9iNwmfZ41
Two corrections, both of which the site was getting wrong.

THE DESK DOES NOT EXIST

Standing a regulated commodities book up is a long road through
licensing, and none of it has been walked. Until then, prices, units,
lot sizes and invitations to deal are advertising a capability the firm
does not have — which is the single biggest untruth a page can carry,
and the one everything else here was built to prevent.

So the desk page is gone, along with every price, every unit, and the
product listings the seed wrote. The fifteen materials remain as
research subjects, keeping the part that was always research — why each
gates a curve, and which grade actually ships — folded into the map
where it belongs. The group advertises no marketplace, because there is
nothing to sell. `SCOPE.today` says plainly: we publish research, we do
not trade, broker, quote or arrange movement, and nothing here is an
offer or advice.

Disclosure keeps its rules even though there is nothing to declare —
a disclosure policy is only credible before it is needed — and now
opens by saying so. A desk survives only as a phase marked not-started,
described as an intention rather than a service.

Two tests keep it that way: one asserts a material carries no price,
unit or lot-size field, and one greps the config and the site builder
for dealing language. A config that quietly regrows a price field is a
config that puts an offer in front of a reader nobody may sell to.

MATERIALS ARE NOT THE ONLY CHOKEPOINT

The node taxonomy admitted machines, processes, companies and people
from the start, and then the universe contained nothing but materials.
Fourteen non-material chokepoints now sit alongside them: EUV scanners
with one supplier, projection optics that are a chokepoint inside a
chokepoint, packaging capacity allocated years ahead, HBM stacking
yield, transformer slots, interconnection queues, turbine order books,
magnet sintering, precision drives — and process engineers, the
constraint nobody can buy.

They enter on the same two tests and carry the same claim limits: what
the node is and why it gates, nothing about capacity, share or price,
every row unverified until sourced. A test now fails if the universe
ever collapses back to one kind of node, so "a node can be anything"
stays a fact about the coverage rather than a line in the config.

Rendered as cards, not a table: a grid of names and country codes scans
nicely and says nothing, and the claim IS the reason it gates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF9GDWaBWYHCZ9iNwmfZ41
… the fund

Readers will want to do something with this research. Two new pages serve
that without crossing a line the firm has no licence to cross.

THE LINE, AND WHERE IT ACTUALLY IS

Publishing an impersonal, generally-circulated view is research.
Telling a particular person what to buy is advice, and advice is
licensed nearly everywhere. Less obviously: taking a fee for an
introduction is what converts "here is who the brokers are" into
regulated intermediation — introducing broker, tied agent, finder — in
Switzerland, the EU and the US alike. Being unpaid is not a detail of
that arrangement; it is the whole of what keeps it lawful.

So there is no referral list. `PARTNERS` is empty behind a flag, and the
gate is documented: a name appears only with an executed agreement AND
confirmation that the introduction itself needs no licence. The page
says so rather than leaving a blank space, because volunteering
somebody's name as an endorsement they never gave is the easier thing
to do and the wrong one.

WHAT THE PAGES DO INSTEAD

Six routes by which anyone acts in these markets, described as market
structure and not endorsement, each carrying what it does NOT give you
— the real failure mode here is misleading by omission, and a reader
who takes a commodity ETF as exposure to seven-nines tin has been
misled by what nobody said. One route is not financial at all:
for an industrial reader the highest-return action is usually
procurement, and the map is a supplier list as much as a research
product.

A thesis, six claims, each with a falsifier. A thesis without one is a
slogan, and a slogan cannot be scored — which also makes the track
record possible later.

A readiness ledger: nine requirements to manage money rather than only
publish, at 1 done and 2 in progress. Same discipline as the coverage
meter, and the licence line still reads not started, which is the whole
reason the other two pages are written the way they are.

Tests hold the line rather than good intentions: no partner may appear
while the gate is shut; every route must state its limitation; and the
whole site is grepped for directive language ("we recommend", "you
should buy", "guaranteed", "risk-free"). These erode quietly, one
helpful sentence at a time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF9GDWaBWYHCZ9iNwmfZ41
The ask was everyone in the singularity chain, seen through scarcity.
102 participants across ten layers, ore to buyer, each graded.

THE GRADE IS THE PRODUCT

A directory of everyone in a supply chain is a phone book. What makes
this research is the last column: chokepoint, concentrated, or
competitive. Grading participants "competitive" is not filler — it is
what makes "chokepoint" mean anything, and several well-known names are
in the list precisely because they are not constraints. A map where
every node is critical is a map nobody has read, so a test fails if all
three grades are not in use, and another fails if chokepoints ever
exceed half the list.

On the demand side the grade reads the other way: when a handful of
firms account for most of the world's orders, that concentration is a
scarcity fact about the chain too, pointing upstream instead of down.

Same claim limits as everywhere: name, layer, jurisdiction, role and a
scarcity judgement. Nothing about revenue, capacity or share, and every
row unsourced until an analyst attaches a source.

The page leads with the 24 that actually bind, as cards carrying the
reasoning, then the full directory as per-layer tables behind a jump
index.

ALSO

- Dropped the defensive framing from Acting. The limits are three
  plain lines now, and the "why there is no referral list" section is
  gone: a directory nobody paid to be in is the point, not a caveat.
- Domain aliases wired ahead of ownership. substrata.ch (intended home,
  appears free — .ch publishes no RDAP so a registrar has to confirm)
  and substrataintel.com (the .com fallback; substrata.com is taken).
  A host only reaches the check if DNS already points here, so listing
  them early costs nothing and means the site works the hour one is
  bought, with no deploy.
- The readiness ledger became definitions instead of a table. Three
  columns where one holds a paragraph is a table that scrolls sideways
  on a phone and is read by nobody.
- The nav is a client component for one reason: at eight sections the
  active item sat off-screen on a phone, so a visitor on a deep page saw
  no indication of where they were. It scrolls itself into view now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF9GDWaBWYHCZ9iNwmfZ41
describe('availability — batches', () => {
it('checks every candidate and preserves order', async () => {
mockFetch(url =>
url.includes('data.iana.org')
import { isReservedUsername } from '@/config/usernames';

// Mirrors of the live CHECK constraints on public.user_products / public.groups.
const PRODUCT_CURRENCIES = ['USD', 'EUR', 'CHF', 'BTC', 'GBP'];

// Mirrors of the live CHECK constraints on public.user_products / public.groups.
const PRODUCT_CURRENCIES = ['USD', 'EUR', 'CHF', 'BTC', 'GBP'];
const PRODUCT_TYPES = ['physical', 'digital', 'service'];
// Mirrors of the live CHECK constraints on public.user_products / public.groups.
const PRODUCT_CURRENCIES = ['USD', 'EUR', 'CHF', 'BTC', 'GBP'];
const PRODUCT_TYPES = ['physical', 'digital', 'service'];
const FULFILLMENT_TYPES = ['manual', 'automatic', 'digital'];
const PRODUCT_CURRENCIES = ['USD', 'EUR', 'CHF', 'BTC', 'GBP'];
const PRODUCT_TYPES = ['physical', 'digital', 'service'];
const FULFILLMENT_TYPES = ['manual', 'automatic', 'digital'];
const PRODUCT_STATUSES = ['draft', 'active', 'paused', 'sold_out'];
Comment thread scripts/seed-substrata.ts
console.log(`founder actor '${FOUNDER_ACTOR_SLUG}' = ${founder.id}`);

const groupId = await upsertGroup(founder);
const groupActorId = await upsertGroupActor(groupId);
It said "Substrate", promised a trading desk, and claimed the script had
to run on the box. All three were stale: the firm is Substrata, there is
no desk, and the seed is PostgREST calls rather than psql — so it runs
from any machine that can reach supabase.orangecat.ch over HTTPS, no SSH
and no tunnel needed. That last one matters, because believing otherwise
is what makes a two-minute task look like it needs a maintenance window.

Also notes that the service-role key bypasses RLS, so the machine running
it is privileged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF9GDWaBWYHCZ9iNwmfZ41
@catomean

Copy link
Copy Markdown
Collaborator Author

Superseded by #783, which is now merged as 370ccce9.

Every commit from this branch is in main#783 branched from here, so the squash carried all of it. Verified: all five src/config/substrata* files are on main, and https://orangecat.ch/sites/substrata serves <title>Substrata</title>.

#783 also closed the six CodeQL alerts that were keeping this PR unmergeable (five dead locals, and url.includes('data.iana.org') in a test, which also matches data.iana.org.evil.example), and four defects an architecture audit found — the largest being 60.1 KB of the site's own data serialised into the RSC payload of every page to render eight nav links.

Closing as merged-by-proxy rather than stale.

@catomean catomean closed this Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants