Partially superseded (2026-06-09) — two halves, different fates
This document merged two architectural threads. They have diverged since:
OAuth-issuer half (hub-as-issuer): remains canonical and load-bearing. Hub is the ecosystem OAuth issuer; modules accept hub-issued JWTs; the hub JWKS +
/oauth/*surface is the live, enforced contract. Nothing here has changed.Config-portal half (hub renders config dashboards / privileged writes via
PUT /.parachute/config): this is the "thick hub" pattern that seeded the architectural debt the 2026-06-09 shift retired. Hub drivingPUT /.parachute/configforms is no longer the target shape. Module-owned admin surfaces (e.g./vault/admin/) now own their config UX; hub owns the provisioning transaction, not the form rendering. See the boundary charter and the modular-UI design doc for the current model.Current ownership rules:
parachute-patterns/patterns/hub-module-boundary.mdModular-UI shift:parachute-patterns/design/2026-06-09-modular-ui-architecture.md
Date: 2026-04-20 (launch week — launch target 2026-04-23) Context: Aaron is out walking, has two related architectural thoughts that converge.
Aaron clicks the Vault card on the hub and lands on a raw API response. Not useful for humans. Notes has a real UI; Vault doesn't. He wants the hub to do more — become a configuration surface for the whole ecosystem so users don't have to use the CLI for every setting.
Originally proposed: vault as ecosystem identity provider (vault already has OAuth 2.1 + PKCE + DCR — just reuse it). Aaron pushed back: that conflates data service with identity. The hub is the front door; identity belongs at the front door. His proposal: hub-as-OAuth-issuer, vault and other services accept hub-issued tokens.
Aaron is right. Vault-as-IDP was expedient, not clean. Extracting identity from the data layer is the architecturally correct move. Services shouldn't inherit auth from a sibling; they should inherit from a shared source above them.
Hub is the ecosystem front door. It owns three things:
/.well-known/oauth-authorization-server, /oauth/authorize, /oauth/token, /oauth/register)Vault, Notes, Scribe, and future services are OAuth clients of the hub. Services accept hub-issued tokens, validate scopes.
Scopes per service: vault:read, vault:write, scribe:transcribe, hub:admin, channel:send. Clients declare what they need at authorization; hub consent UI shows "Notes wants: read your vault, write notes, transcribe audio."
Token response carries the service catalog (ecosystem extension):
{
"access_token": "...",
"scopes": ["vault:read", "vault:write"],
"services": {
"vault": { "url": "https://parachute.x.ts.net/vault/default", "version": "0.3.0" },
"scribe": { "url": "https://parachute.x.ts.net/scribe", "version": "0.2.0" }
}
}
Notes never asks the user for a vault URL. It does OAuth against the hub, gets a token + the service catalog, auto-populates everything.
Step-up permissions: Notes initially requests vault:read. User tries voice memo. Notes requests scribe:transcribe + vault:write via refresh; hub re-prompts consent for new scopes. No full re-login.
Small PR, low risk:
/.well-known/oauth-authorization-server with issuer = hub origin, endpoints advertised at /oauth/* paths on the hub./oauth/authorize, /oauth/token, /oauth/register to vault's current implementation.issuer metadata to match the hub origin (don't advertise itself as issuer anymore).Net effect: clients from launch day onward never know vault is the auth implementation. When we later extract or rewrite auth, zero client changes.
services catalog (drawn from /.well-known/parachute.json).vault:read vs vault:write), scribe (scribe:transcribe), etc.parachute-auth service, or stays in vault as an internal module. Public shape is unchanged.Orthogonal to OAuth but benefits from it:
Pre-launch (minimum): hub cards for API-only services (vault, scribe) show a detail panel in place instead of navigating. Panel shows: MCP endpoint, OAuth discovery link, "Open in Notes" deep-link, version. No writes. No OAuth needed (hub runs on loopback for local installs).
Post-launch Phase 1: each service exposes GET /.parachute/config (read-only, returns current settings as JSON schema). Hub renders a read-only config dashboard. Still no writes; no OAuth needed.
Post-launch Phase 2+: PUT /.parachute/config on each service. Now the hub is doing privileged writes — OAuth from Phase 0/1 gates this. User logs in via hub OAuth, hub's token carries hub:admin scope, services accept.
vault:read + vault:write. AI calls vault's MCP endpoint with token. Vault introspects against hub or validates JWT signature.*, no auth). Once it's hub-OAuth-gated, the CORS becomes less permissive and scribe validates hub tokens. Scope: scribe:transcribe.vault:<name>:read or similar. Decide with Phase 2.We're 3 days from launch. This is a significant architectural direction that we can't fully build pre-launch but whose public shape matters from day one. The launch Phase 0 seam is the commitment that keeps the post-launch door open without painting us into a corner.
Revisit this note when starting Phase 1 work — likely within 2 weeks of launch.