feat: Substrata — hosted sites, and a research firm that proves they work - #782
Closed
catomean wants to merge 10 commits into
Closed
feat: Substrata — hosted sites, and a research firm that proves they work#782catomean wants to merge 10 commits into
catomean wants to merge 10 commits into
Conversation
…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']; |
| 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
Collaborator
Author
|
Superseded by #783, which is now merged as Every commit from this branch is in #783 also closed the six CodeQL alerts that were keeping this PR unmergeable (five dead locals, and Closing as merged-by-proxy rather than stale. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Two things at once: a general mechanism, and the customer that proves it.
The mechanism — a profile spins up a whole website.
/domainshas 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
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
Checklist
Environment note: no new environment variables. The domain search needs outbound HTTPS from the box to
rdap.organddata.iana.org— without it every lookup degrades safely to "check manually". Servingsubstrata.orangecat.chneeds a wildcard*.orangecat.chDNS record and the matching Caddy entry; the path form/sites/substrataworks today without either.Type Safety Checklist
as anycasts (unless absolutely necessary with explanation)@ts-ignoreor@ts-expect-error(unless justified with description)@/types/databaseas anyfor runtime validationVerification
npm run verifygreen: 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., andsubstrata.localhostall rewrite;orangecat.ch,notsubstrata.orangecat.chandsubstrata.orangecat.ch.evil.exampleare all correctly refused.Tests that exist for a specific reason
orangecat.ch— this project's own production domain — as available, because.chpublishes 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.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.siteForHostis pure and never touches the database; it runs in edge middleware on every request to the whole app.substrata.chandsubstrataintel.comare 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./groups/substrataneedsORANGECAT_OWNER_SEED=1 npx tsx scripts/seed-substrata.tsrun from the box; the site renders from config and does not depend on it.🤖 Generated with Claude Code
Generated by Claude Code