From 0042de084d7d71db61501923d27693a2c5253ebc Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Fri, 31 Jul 2026 04:20:22 +0000 Subject: [PATCH] feat(resolve): mosh. opens the Pit to register names under that TLD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Typing `mosh.eggs` should take you to the console where `.eggs` is claimed and names under it are registered, rather than trying to resolve `mosh.eggs` as a name. - consoleUrlFor() maps a `mosh.` hostname to app.moshcode.sh/pit?tld=. The TLD rides in a query param, not a path segment: a console that doesn't understand `?tld=` still lands on a working page, where an unknown path segment would 404. - decideResolution() gains a third outcome, 'register', carrying that URL. The console label is reserved, so it beats a registered name — otherwise whoever claims `.eggs` could register `mosh.eggs` and own the page people use to check who owns `.eggs`. It obeys the SAME clearnet precedence as any other Moshpit answer instead of taking an exemption: `mosh.org` and `mosh.com` are real clearnet domains, and swallowing them to show a registry page is exactly the hijack the default mode exists to prevent. So in clearnet mode it only fires where DNS came up empty; in moshpit mode the user has opted into the override. Pure policy + tests, matching the rest of the module — nothing intercepts navigation yet, so this changes no browsing behaviour on its own. Co-Authored-By: Claude Opus 5 (1M context) --- apps/desktop/src/moshpit-resolve.test.ts | 76 ++++++++++++++++++++++++ apps/desktop/src/moshpit-resolve.ts | 59 +++++++++++++++++- 2 files changed, 133 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/moshpit-resolve.test.ts b/apps/desktop/src/moshpit-resolve.test.ts index 4fd34fc..3e3acca 100644 --- a/apps/desktop/src/moshpit-resolve.test.ts +++ b/apps/desktop/src/moshpit-resolve.test.ts @@ -7,6 +7,7 @@ import { lookupMoshpit, parseRegistryName, type MoshpitLookup, + consoleUrlFor, } from './moshpit-resolve'; const registered = (resolved: string): MoshpitLookup => ({ registered: true, resolved }); @@ -158,3 +159,78 @@ describe('gatewayUrlFor', () => { expect(gatewayUrlFor('fuck.yeah', 'https://my.pit/')).toBe('https://my.pit/n/fuck.yeah'); }); }); + +describe('consoleUrlFor', () => { + it('points mosh. at the Pit for that TLD', () => { + expect(consoleUrlFor('mosh.eggs')).toBe('https://app.moshcode.sh/pit?tld=eggs'); + expect(consoleUrlFor('MOSH.Whatever')).toBe('https://app.moshcode.sh/pit?tld=whatever'); + }); + + it('tolerates a trailing slash on an overridden console', () => { + expect(consoleUrlFor('mosh.eggs', 'https://my.console/')).toBe( + 'https://my.console/pit?tld=eggs', + ); + }); + + it('is null for anything that is not a mosh. name', () => { + expect(consoleUrlFor('eggs.mosh')).toBeNull(); // the other way round + expect(consoleUrlFor('moshy.eggs')).toBeNull(); // not the reserved label + expect(consoleUrlFor('a.mosh.eggs')).toBeNull(); // too many labels + expect(consoleUrlFor('mosh')).toBeNull(); // no TLD + }); +}); + +describe('decideResolution — the registration console', () => { + it('sends mosh. to the Pit when clearnet has no answer', () => { + const d = decideResolution({ + hostname: 'mosh.eggs', + mode: 'clearnet', + clearnetResolves: false, + moshpit: null, + }); + expect(d.use).toBe('register'); + expect(d.url).toBe('https://app.moshcode.sh/pit?tld=eggs'); + }); + + it('never swallows a real clearnet domain like mosh.org in the default mode', () => { + const d = decideResolution({ + hostname: 'mosh.org', + mode: 'clearnet', + clearnetResolves: true, + moshpit: null, + }); + expect(d.use).toBe('clearnet'); + }); + + it('takes the console over clearnet once the user opts into moshpit mode', () => { + const d = decideResolution({ + hostname: 'mosh.org', + mode: 'moshpit', + clearnetResolves: true, + moshpit: null, + }); + expect(d.use).toBe('register'); + expect(d.url).toBe('https://app.moshcode.sh/pit?tld=org'); + }); + + it('beats a registered name — the console label is reserved, not claimable', () => { + const d = decideResolution({ + hostname: 'mosh.eggs', + mode: 'moshpit', + clearnetResolves: false, + moshpit: registered('someone-elses-squat.eggs'), + }); + expect(d.use).toBe('register'); + }); + + it('honours an overridden console base', () => { + const d = decideResolution({ + hostname: 'mosh.eggs', + mode: 'clearnet', + clearnetResolves: false, + moshpit: null, + consoleBase: 'https://my.console', + }); + expect(d.url).toBe('https://my.console/pit?tld=eggs'); + }); +}); diff --git a/apps/desktop/src/moshpit-resolve.ts b/apps/desktop/src/moshpit-resolve.ts index 804c501..89232ce 100644 --- a/apps/desktop/src/moshpit-resolve.ts +++ b/apps/desktop/src/moshpit-resolve.ts @@ -33,6 +33,22 @@ export const DEFAULT_RESOLVE_MODE: ResolveMode = 'clearnet'; /** The public registry. Overridable so a self-hosted pit can be pointed at. */ export const DEFAULT_REGISTRY_BASE = 'https://pit.moshcode.sh'; +/** + * The Pit — the human-facing console where a TLD is claimed and the names under + * it are registered. Distinct from DEFAULT_REGISTRY_BASE, which is the resolver + * API the browser talks to. + */ +export const DEFAULT_CONSOLE_BASE = 'https://app.moshcode.sh'; + +/** + * The one label that means "manage this namespace" instead of "visit a name": + * `mosh.eggs` opens the Pit for `.eggs` rather than resolving as a name. + * + * It has to be reserved rather than resolvable, or whoever claims `.eggs` could + * register `mosh.eggs` and own the page people use to check who owns `.eggs`. + */ +export const CONSOLE_LABEL = 'mosh'; + export interface MoshpitLookup { /** The registry holds this name. */ registered: boolean; @@ -47,15 +63,36 @@ export interface ResolveInputs { clearnetResolves: boolean; /** Registry answer, or null when it was not consulted / was unreachable. */ moshpit: MoshpitLookup | null; + /** The Pit console. Overridable alongside a self-hosted registry. */ + consoleBase?: string; } export interface ResolveDecision { /** Which namespace serves this navigation. */ - use: 'clearnet' | 'moshpit'; + use: 'clearnet' | 'moshpit' | 'register'; /** Why — surfaced in the UI so an override never looks like a glitch. */ reason: string; /** The name to fetch through the gateway. Only set when `use` is 'moshpit'. */ resolved?: string; + /** Where to send the browser. Only set when `use` is 'register'. */ + url?: string; +} + +/** + * The Pit URL for the TLD a `mosh.` hostname refers to, or null when the + * hostname isn't one. + * + * The TLD rides in a query parameter rather than a path segment deliberately: + * a console that doesn't (yet) understand `?tld=` still lands the user on a + * working registry page, whereas an unknown path segment would 404. + */ +export function consoleUrlFor( + hostname: string, + consoleBase: string = DEFAULT_CONSOLE_BASE, +): string | null { + const parsed = parseRegistryName(hostname); + if (!parsed || parsed.label !== CONSOLE_LABEL) return null; + return `${consoleBase.replace(/\/+$/, '')}/pit?tld=${encodeURIComponent(parsed.tld)}`; } /** @@ -66,7 +103,25 @@ export interface ResolveDecision { * the whole policy is testable without a network or a browser. */ export function decideResolution(inputs: ResolveInputs): ResolveDecision { - const { mode, clearnetResolves, moshpit } = inputs; + const { hostname, mode, clearnetResolves, moshpit, consoleBase } = inputs; + + // `mosh.` is the registration console for `.`, not a name to fetch. + // It obeys the SAME precedence as any other Moshpit answer rather than + // getting a special exemption — `mosh.org` and `mosh.com` are real clearnet + // domains, and quietly swallowing them to show a registry page would be the + // hijack this module's default mode exists to prevent. + const consoleUrl = consoleUrlFor(hostname, consoleBase); + if (consoleUrl) { + if (mode === 'clearnet' && clearnetResolves) { + return { use: 'clearnet', reason: 'clearnet answers for this name (Moshpit set to backfill only)' }; + } + const tld = hostname.trim().toLowerCase().replace(/\.$/, '').split('.')[1]; + return { + use: 'register', + reason: `${CONSOLE_LABEL}.${tld} is the registration console for .${tld}`, + url: consoleUrl, + }; + } // The registry could not be reached, or was never asked. Falling back to // clearnet is the only safe move: a registry outage must not take the