From ac134deaca35a8dbeab85718c2462ccae6047a14 Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Thu, 2 Jul 2026 19:37:19 -0700 Subject: [PATCH] Ship a built-in default remote for the central server (LLP 0062) Reaching the Hyparam-hosted central server took a `hyp remote add ` on every machine, and `query.default_remote` was validated but never consumed. - Ship a built-in `hyparam` target (https://hypaware.hyperparam.app), layered under the user's `query.remotes` so a same-named entry overrides it. - Make `--remote` optionally-valued: bare `--remote` resolves the default target (explicit `default_remote`, else the built-in); named `--remote ` is unchanged. Local-first stays: plain `hyp ` still runs locally. - `hyp remote login` with no target resolves the same default. - Accept a built-in name for `query.default_remote` validation. - Record the design as LLP 0062, extending LLP 0033. --- llp/0033-remote-query-attach.spec.md | 5 ++ llp/0062-builtin-default-remote.decision.md | 62 +++++++++++++++++++++ src/core/cli/remote_commands.js | 20 ++++--- src/core/cli/verb_codec.js | 9 ++- src/core/cli/verb_command.js | 9 ++- src/core/config/schema.js | 17 ++++-- src/core/mcp/remote_verb.js | 5 +- src/core/remote/builtin_remotes.js | 51 +++++++++++++++++ test/core/remote-login-command.test.js | 23 +++++--- test/core/verb-codec.test.js | 9 +++ test/core/verb-remote.test.js | 28 ++++++++++ 11 files changed, 210 insertions(+), 28 deletions(-) create mode 100644 llp/0062-builtin-default-remote.decision.md create mode 100644 src/core/remote/builtin_remotes.js diff --git a/llp/0033-remote-query-attach.spec.md b/llp/0033-remote-query-attach.spec.md index 25bb40e5..ff28f0b0 100644 --- a/llp/0033-remote-query-attach.spec.md +++ b/llp/0033-remote-query-attach.spec.md @@ -83,6 +83,11 @@ client now core (LLP 0034), "which server a verb talks to" is a core concern, an the **central layer can never inject a remote target** ([LLP 0031](./0031-layered-config.decision.md#query-is-local-only)) — a free invariant. The URL is non-secret and committable; the token is not config. +> **Extended-by: [LLP 0062](./0062-builtin-default-remote.decision.md).** The +> client now ships a built-in `hyparam` target and wires `default_remote`, so +> bare `hyp --remote` and bare `hyp remote login` resolve the central +> server with no `remote add`. User `query.remotes` still layers on top. + ## Credentials The query-scoped token is **never in config** diff --git a/llp/0062-builtin-default-remote.decision.md b/llp/0062-builtin-default-remote.decision.md new file mode 100644 index 00000000..0a36201b --- /dev/null +++ b/llp/0062-builtin-default-remote.decision.md @@ -0,0 +1,62 @@ +# LLP 0062: Ship a built-in default remote so the central server needs no `remote add` + +**Type:** Decision +**Status:** Accepted +**Systems:** CLI, Query, MCP +**Author:** Kenny / Claude +**Date:** 2026-07-02 +**Related:** LLP 0033, LLP 0031 + +> LLP 0033 gave every install a target registry (`query.remotes`) and a +> `default_remote`, but reaching the Hyparam-hosted central server still took a +> `hyp remote add ` on each machine, and the `default_remote` field +> was validated yet never consumed. This decision ships the central server as a +> built-in target and wires the default so `hyp --remote` (no name) and +> `hyp remote login` (no name) both resolve it. It extends LLP 0033 §targets; it +> supersedes nothing. + +## Decision + +### D1 — A built-in target, shipped in the client + +The client ships a constant registry of built-in targets +(`BUILTIN_REMOTES`), currently the single entry `hyparam → +https://hypaware.hyperparam.app`, plus `BUILTIN_DEFAULT_REMOTE = 'hyparam'`. +Target resolution reads the **effective** registry: built-ins with the user's +`query.remotes` layered on top, so a user entry of the same name repoints or +shadows a built-in (`effectiveRemotes`). The URL is non-secret and committable, +exactly as a `hyp remote add` URL is (LLP 0033 §targets); shipping it in the +public client is therefore consistent with that section, not a secrets leak. + +This keeps the local-first default intact: a bare `hyp ` still runs +locally. The built-in only changes what a target *name* resolves to, never +whether a plain command goes remote. + +### D2 — Bare `--remote` and bare `remote login` resolve the default + +`--remote` becomes optionally-valued. A bare `--remote` +parses to an empty-string sentinel (distinct from `undefined`, which stays +"local"); the command path resolves it to `effectiveDefaultRemote(config)` — an +explicit `query.default_remote` if set, else `BUILTIN_DEFAULT_REMOTE`. A named +`--remote ` is unchanged. Symmetrically, `hyp remote login` with no +positional target resolves the same default, the companion of bare `--remote` +so the one-time sign-in needs no name either. + +The resolver is never empty, so bare `--remote` always resolves to a target; +this is the behavior LLP 0033's schema comment already anticipated ("`--remote` +with no arg never silently resolves to nothing"). + +### D3 — `default_remote` may name a built-in + +Config validation for `query.default_remote` accepts a +name defined in the user's `remotes` **or** in `BUILTIN_REMOTES`, so a config may +default to the central server without restating its URL. + +## Consequences + +- Onboarding drops to `hyp remote login` then `hyp --remote`; no + `remote add`, no URL to copy. +- Credentials are still per-target (`HYP_REMOTE_TOKEN_HYPARAM`, or the stored + `0600` session under the `hyparam` key), unchanged from LLP 0033 §credentials. +- Changing the central URL is a client release (it is compiled in). A user who + must override it before then adds a `query.remotes.hyparam` entry, which wins. diff --git a/src/core/cli/remote_commands.js b/src/core/cli/remote_commands.js index 172ab1a0..c5fee763 100644 --- a/src/core/cli/remote_commands.js +++ b/src/core/cli/remote_commands.js @@ -7,6 +7,7 @@ import process from 'node:process' import { defaultConfigPath } from '../config/schema.js' import { readObservabilityEnv } from '../observability/env.js' +import { BUILTIN_REMOTES, effectiveDefaultRemote } from '../remote/builtin_remotes.js' import { deriveIdentityBase, readCredentials, @@ -122,11 +123,10 @@ export async function runRemoteLogin(argv, ctx, deps = {}) { // The target name is the first positional. Skip the VALUE slot of a // value-taking flag so e.g. `login --org acme` (name omitted) is not misread // as the target 'acme'. - const name = positionals(argv, new Set(['--token-file', '--org', '--host']))[0] - if (!name) { - ctx.stderr.write('usage: hyp remote login [--token-file ] [--org ] [--host