diff --git a/docs/guides/getting-started/README.md b/docs/guides/getting-started/README.md index e7f71a0af7..e331e3e9bf 100644 --- a/docs/guides/getting-started/README.md +++ b/docs/guides/getting-started/README.md @@ -7,20 +7,13 @@ sidebar_position: 1 This section contains easy and to the point guides to help you set up Fullsend. These are intended to be read in a certain order: -1. **Mint enrollment** — before configuring anything, your org or repo - must be enrolled in a fullsend token mint service so the mint - accepts token requests from your GitHub Actions workflows. - The CLI defaults to the hosted mint. To enroll, contact the - fullsend team in the internal Slack channel with your GitHub org - name (for org mode) or `owner/repo` (for per-repo mode). To deploy - and manage your own mint instead, see the +1. **Token mint** — the CLI defaults to the hosted community mint at + `https://mint.fullsend.sh`. Install the shared public GitHub Apps + for your organization and set (or rely on the CLI default) + `FULLSEND_MINT_URL` — no separate enrollment step is required. + To deploy and manage your own self-hosted mint instead, see the [Mint administration](../infrastructure/mint-administration.md) guide. - > **Note:** Self-service enrollment is not yet available. - > [Public mint mode](https://github.com/fullsend-ai/fullsend/pull/1580) - > will remove the need for per-org/repo enrollment, but is still - > in progress. - 2. [Getting Inference](getting-inference.md) 3. [Configuring GitHub](configuring-github.md) 4. [Organization Mode](org-mode.md) diff --git a/docs/guides/getting-started/operations.md b/docs/guides/getting-started/operations.md index c3647e445d..e79f47547c 100644 --- a/docs/guides/getting-started/operations.md +++ b/docs/guides/getting-started/operations.md @@ -74,7 +74,7 @@ To remove fullsend from a single repository: 1. Delete `.github/workflows/fullsend.yaml` and repo-level secrets/variables 2. Run `fullsend inference deprovision "$OWNER/$REPO"` to remove WIF access -3. Contact the fullsend team to unenroll the repo from the hosted mint +3. Remove the `FULLSEND_MINT_URL` repository variable (if set) — no separate unenrollment is needed for the hosted community mint **GitLab repos:** @@ -84,7 +84,7 @@ To remove fullsend from a single repository: 4. Delete fullsend pipeline schedules (`fullsend slash poll` and `fullsend event poll`) 5. For WIF-mode repos: delete the bot token Secret Manager secret (named `fullsend-bot-token---`) from the GCP project -If you manage your own self-hosted mint, run `fullsend mint unenroll "$OWNER/$REPO"` instead of GitHub step 3. See the [standalone commands](#standalone-commands) table for details. +If you manage your own self-hosted mint, run `fullsend mint unenroll "$OWNER/$REPO"` to remove the repo from the mint's allowlist. See the [standalone commands](#standalone-commands) table for details. ## Standalone commands @@ -121,7 +121,7 @@ For organizations that separate GCP and GitHub responsibilities across teams, fu | Developer | `fullsend agent update [sha]` | Re-pin a URL agent to a new commit SHA | | Developer | `fullsend agent remove ` | Unregister an agent from config | -The typical handoff: a GCP admin runs `mint deploy` + `mint enroll` + `inference provision`, then passes the mint URL and WIF provider resource name to a GitHub maintainer who runs `github setup --mint-url=... --inference-wif-provider=...`. +The typical handoff for self-managed mints: a GCP admin runs `mint deploy` + `mint enroll` + `inference provision`, then passes the mint URL and WIF provider resource name to a GitHub maintainer who runs `github setup --mint-url=... --inference-wif-provider=...`. For the hosted community mint, enrollment is automatic — install the shared Apps and use the CLI defaults. ### Per-command IAM role breakdown diff --git a/docs/guides/getting-started/repo-management.md b/docs/guides/getting-started/repo-management.md index 8364ea670c..e18d0233f8 100644 --- a/docs/guides/getting-started/repo-management.md +++ b/docs/guides/getting-started/repo-management.md @@ -19,7 +19,7 @@ fullsend across an organization. Individual repo owners should use - **fullsend CLI** installed (see [releases](https://github.com/fullsend-ai/fullsend/releases)) - **GitHub access** — admin or write access to the target repositories - **`gh` CLI** authenticated with the required OAuth scopes (see [OAuth scope reference](../infrastructure/advanced-setup.md#oauth-scope-reference)) -- **GCP prerequisites** (WIF mode only) — GCP WIF provisioning (`fullsend inference provision`) and mint enrollment (`fullsend mint enroll`) must be completed separately before running `repos install`. OIDC and token modes do not require GCP infrastructure. See [Mint administration](../infrastructure/mint-administration.md) and [Advanced setup](../infrastructure/advanced-setup.md). +- **GCP prerequisites** (WIF mode only) — GCP WIF provisioning (`fullsend inference provision`) must be completed separately before running `repos install`. For self-managed mints, mint enrollment (`fullsend mint enroll`) is also required. The hosted community mint needs no enrollment — install the shared Apps and use the CLI defaults. See [Mint administration](../infrastructure/mint-administration.md) and [Advanced setup](../infrastructure/advanced-setup.md). ## Getting started @@ -112,7 +112,7 @@ Set `credential_mode` at the forge level or per-repo in the manifest: forge: github: credential_mode: oidc - mint_url: https://fullsend-mint-example.run.app + mint_url: https://mint.fullsend.sh gitlab: credential_mode: token url: https://gitlab.example.com @@ -169,11 +169,12 @@ Install runs in three phases: drift (synced automatically) and scaffold ref drift (upgraded automatically). -> **Prerequisite:** For repos using `wif` credential mode, GCP -> infrastructure (WIF pools/providers, mint enrollment) must be -> provisioned separately before running install. Repos using `oidc` -> or `token` mode do not require GCP infrastructure. -> See `fullsend inference provision` and `fullsend mint enroll`. +> **Prerequisite:** For repos using `wif` credential mode, GCP WIF +> provisioning (`fullsend inference provision`) must be completed +> before running install. For self-managed mints, also run +> `fullsend mint enroll`. The hosted community mint needs no +> enrollment. Repos using `oidc` or `token` mode do not require +> GCP infrastructure. > **Note:** When your token does not have direct push access to a target > repository, the install command creates a fork and submits the scaffold diff --git a/docs/guides/infrastructure/layered-config-reference.md b/docs/guides/infrastructure/layered-config-reference.md index 284dc4d38a..6ad2f4bbe6 100644 --- a/docs/guides/infrastructure/layered-config-reference.md +++ b/docs/guides/infrastructure/layered-config-reference.md @@ -309,7 +309,7 @@ compiled-in defaults apply: | `allowed_remote_resources` | `["https://raw.githubusercontent.com/fullsend-ai/fullsend/", "https://raw.githubusercontent.com/fullsend-ai/agents/"]` | | `forge` | `""` (GitHub) | | `tracker` | `""` (none — `--tracker` is required unless set) | -| `mint_url` | `"https://fullsend-mint-gljhbkcloq-uc.a.run.app"` (hosted public mint) | +| `mint_url` | `"https://mint.fullsend.sh"` (hosted public mint) | | `inference.provider` | `"vertex"` | | `inference.project` | `""` (empty — must be provided) | | `inference.region` | `"global"` | diff --git a/docs/guides/infrastructure/mint-administration.md b/docs/guides/infrastructure/mint-administration.md index e04e6201b3..6ea0e53ce3 100644 --- a/docs/guides/infrastructure/mint-administration.md +++ b/docs/guides/infrastructure/mint-administration.md @@ -20,17 +20,17 @@ This guide covers deploying and managing the fullsend token mint. The mint is th ## Hosted mint -The fullsend team operates a public hosted mint service. If your organization is enrolled, you can use it directly without deploying your own. +The fullsend team operates a public hosted community mint service. Install the shared public GitHub Apps and use the CLI defaults — no separate enrollment step is required. **Platform GCP project:** The hosted mint currently runs in GCP project `it-gcp-konflux-dev-fullsend` (region `us-central1`). **Mint URL:** ``` -https://fullsend-mint-gljhbkcloq-uc.a.run.app +https://mint.fullsend.sh ``` -Pass this URL as `--mint-url` when running `fullsend github setup`, or set the `FULLSEND_MINT_URL` repository/org variable in GitHub. If you are using the hosted mint, the rest of this guide (deploying, enrolling, troubleshooting) is handled by the fullsend team — you do not need to manage mint infrastructure yourself. +The CLI defaults to this URL. You can also set the `FULLSEND_MINT_URL` repository/org variable in GitHub explicitly. If you are using the hosted community mint, the rest of this guide (deploying, enrolling, troubleshooting) is for platform operators managing self-hosted mints — you do not need to manage mint infrastructure yourself. ## Prerequisites diff --git a/docs/guides/infrastructure/standalone-mint.md b/docs/guides/infrastructure/standalone-mint.md index 48f0f44a26..25ccf5fb15 100644 --- a/docs/guides/infrastructure/standalone-mint.md +++ b/docs/guides/infrastructure/standalone-mint.md @@ -31,8 +31,8 @@ For roles without a local PEM, the optional fallback proxy forwards the request - **Go 1.26+** to build the binary (or use a pre-built release) - **A GitHub organization** where you will install your custom GitHub Apps -- **The hosted mint URL** (optional, for fallback proxy): `https://fullsend-mint-gljhbkcloq-uc.a.run.app` -- **Your organization enrolled in the hosted mint** (optional, for fallback proxy) — see [Mint service administration](mint-administration.md) +- **The hosted mint URL** (optional, for fallback proxy): `https://mint.fullsend.sh` +- **Shared public GitHub Apps installed** (optional, for fallback proxy) — see [Getting Started](../getting-started/) ## Step 1: Create a GitHub App @@ -113,7 +113,7 @@ The standalone mint is configured entirely through environment variables: | Variable | Description | Example | |----------|-------------|---------| | `ALLOWED_WORKFLOW_FILES` | Comma-separated workflow file allowlist; `*` for all | `*` | -| `FALLBACK_MINT_URL` | Upstream mint URL for roles without local PEMs | `https://fullsend-mint-gljhbkcloq-uc.a.run.app` | +| `FALLBACK_MINT_URL` | Upstream mint URL for roles without local PEMs | `https://mint.fullsend.sh` | | `CUSTOM_ROLE_PERMISSIONS` | JSON map of custom role permissions (see below) | `{"scanner":{"contents":"read"}}` | | `PER_REPO_WIF_REPOS` | Comma-separated repos with per-repo WIF treatment. Use `*` for public mint mode (all repos get per-repo treatment). Per-repo callers can only mint to their own repo scope. Callers not in this list fall through to per-org (`ALLOWED_ORGS`) and get org-mode repos shapes. | `myorg/private-repo` | | `WORKFLOW_HOST_REPOS` | Comma-separated repos whose workflows are trusted to call the mint for per-repo callers. Per-org callers are not affected (they hard-wire to `{org}/.fullsend` and upstream). Defaults to `fullsend-ai/fullsend` when unset. | `fullsend-ai/fullsend,myorg/my-workflows` | @@ -138,7 +138,7 @@ export ROLE_APP_IDS='{"triage":"4087047","scanner":"5555555"}' export OIDC_AUDIENCE="fullsend-mint" export PEM_DIR="./pems" export ALLOWED_WORKFLOW_FILES="*" -export FALLBACK_MINT_URL="https://fullsend-mint-gljhbkcloq-uc.a.run.app" +export FALLBACK_MINT_URL="https://mint.fullsend.sh" export CUSTOM_ROLE_PERMISSIONS='{"scanner":{"contents":"read","security_events":"write","metadata":"read"}}' ./fullsend-mint @@ -148,7 +148,7 @@ On startup, the mint logs the configuration: ``` 2026/06/18 12:00:00 custom role permissions registered: [scanner] -2026/06/18 12:00:00 fallback mint configured: https://fullsend-mint-gljhbkcloq-uc.a.run.app (local roles: [scanner triage]) +2026/06/18 12:00:00 fallback mint configured: https://mint.fullsend.sh (local roles: [scanner triage]) 2026/06/18 12:00:00 fullsend-mint starting on :8080 (standalone mode) ``` @@ -264,7 +264,7 @@ When `FALLBACK_MINT_URL` is set, the standalone mint acts as a transparent proxy | `GET /health` | Always handled locally | | `GET /v1/status` | Always handled locally | -The proxy forwards the original OIDC bearer token and request body to the upstream mint, and returns the upstream response verbatim. The upstream mint performs its own OIDC validation — your organization must be enrolled on the upstream mint for proxied requests to succeed. +The proxy forwards the original OIDC bearer token and request body to the upstream mint, and returns the upstream response verbatim. The upstream mint performs its own OIDC validation — the shared public GitHub Apps must be installed in your organization for proxied requests to succeed. When `FALLBACK_MINT_URL` is not set, requests for roles without local PEMs are rejected with a `403 Forbidden` response. @@ -336,7 +336,7 @@ export ROLE_APP_IDS='{"triage":"4087047","scanner":"5555555"}' export OIDC_AUDIENCE="fullsend-mint" export PEM_DIR="./pems" export ALLOWED_WORKFLOW_FILES="*" -export FALLBACK_MINT_URL="https://fullsend-mint-gljhbkcloq-uc.a.run.app" +export FALLBACK_MINT_URL="https://mint.fullsend.sh" export CUSTOM_ROLE_PERMISSIONS='{"scanner":{"contents":"read","security_events":"write","metadata":"read"}}' ./fullsend-mint diff --git a/e2e/admin/admin_test.go b/e2e/admin/admin_test.go index 8d039e5f93..f7a94cee1c 100644 --- a/e2e/admin/admin_test.go +++ b/e2e/admin/admin_test.go @@ -93,7 +93,7 @@ func TestAdminInstallUninstall(t *testing.T) { "admin", "install", env.org, "--skip-app-setup", "--skip-mint-check", - "--mint-url", env.cfg.MintURL, + "--mint-url", e2etest.DefaultPoolOrgInstallMintURL, "--app-set", e2eAppSet, "--enroll-all", "--vendor", @@ -898,7 +898,7 @@ func TestVendorFromSubdirectory(t *testing.T) { "admin", "install", env.org, "--skip-app-setup", "--skip-mint-check", - "--mint-url", env.cfg.MintURL, + "--mint-url", e2etest.DefaultPoolOrgInstallMintURL, "--app-set", e2eAppSet, "--enroll-none", "--vendor", diff --git a/internal/cli/admin.go b/internal/cli/admin.go index 74b731ff9e..110a1c2ed6 100644 --- a/internal/cli/admin.go +++ b/internal/cli/admin.go @@ -38,7 +38,7 @@ import ( // DefaultMintURL is the hosted public mint URL used when --mint-url is not // explicitly provided. Users who self-host a mint can override this via // the --mint-url flag. -const DefaultMintURL = "https://fullsend-mint-gljhbkcloq-uc.a.run.app" +const DefaultMintURL = "https://mint.fullsend.sh" // adminMintDiscovery holds the results of a mint infrastructure discovery call. type adminMintDiscovery struct { @@ -197,6 +197,17 @@ func validateWIFProvider(raw string) error { return nil } +// IsHostedMintURL reports whether raw is the hosted community mint URL +// (mint.fullsend.sh). This check is also used by pkg/e2etest to resolve +// the GCP project for hosted-mint enrollment. +func IsHostedMintURL(raw string) bool { + parsed, err := url.Parse(raw) + if err != nil { + return false + } + return strings.EqualFold(parsed.Hostname(), "mint.fullsend.sh") +} + func validateMintURL(raw string) error { if err := validateMintURLHTTPS(raw); err != nil { return err @@ -205,11 +216,13 @@ func validateMintURL(raw string) error { if err != nil { return err } - if !strings.HasSuffix(parsed.Host, ".run.app") && - !strings.HasSuffix(parsed.Host, ".cloudfunctions.net") { - return fmt.Errorf("--mint-url must be a Cloud Run URL (.run.app or .cloudfunctions.net), got host %q", parsed.Host) + host := parsed.Hostname() + if strings.EqualFold(host, "mint.fullsend.sh") || + strings.HasSuffix(host, ".run.app") || + strings.HasSuffix(host, ".cloudfunctions.net") { + return nil } - return nil + return fmt.Errorf("--mint-url must be a hosted mint or Cloud Run URL (.fullsend.sh, .run.app, or .cloudfunctions.net), got host %q", host) } func validateSkipMintCheck(mintURL string) error { diff --git a/internal/cli/admin_test.go b/internal/cli/admin_test.go index d75839f0ae..3589361b8c 100644 --- a/internal/cli/admin_test.go +++ b/internal/cli/admin_test.go @@ -191,7 +191,7 @@ func TestInstallCmd_PerRepoRejectsNonCloudRunMintURL(t *testing.T) { cmd.SetArgs([]string{"admin", "install", "acme/widget", "--mint-url", "https://evil.example.com", "--inference-project", "my-project"}) err := cmd.Execute() require.Error(t, err) - assert.Contains(t, err.Error(), "--mint-url must be a Cloud Run URL") + assert.Contains(t, err.Error(), "--mint-url must be a hosted mint or Cloud Run URL") } func TestInstallCmd_PerRepoRejectsPerOrgFlags(t *testing.T) { @@ -1780,6 +1780,38 @@ func TestValidateSkipMintCheck(t *testing.T) { require.NoError(t, validateSkipMintCheck("https://mint.example.com/v1/token")) } +func TestValidateMintURL_AcceptsHostedCommunityMint(t *testing.T) { + require.NoError(t, validateMintURL("https://mint.fullsend.sh")) +} + +func TestValidateMintURL_AcceptsCloudRunURL(t *testing.T) { + require.NoError(t, validateMintURL("https://fullsend-mint-abc123.run.app")) +} + +func TestValidateMintURL_AcceptsCloudFunctionsURL(t *testing.T) { + require.NoError(t, validateMintURL("https://us-central1-my-project.cloudfunctions.net")) +} + +func TestValidateMintURL_RejectsArbitraryHosts(t *testing.T) { + err := validateMintURL("https://evil.example.com") + require.Error(t, err) + assert.Contains(t, err.Error(), "got host") +} + +func TestDefaultMintURL_IsHostedCommunity(t *testing.T) { + assert.Equal(t, "https://mint.fullsend.sh", DefaultMintURL) +} + +func TestIsHostedMintURL(t *testing.T) { + assert.True(t, IsHostedMintURL("https://mint.fullsend.sh")) + assert.True(t, IsHostedMintURL("https://mint.fullsend.sh/v1/token")) + assert.True(t, IsHostedMintURL("https://mint.fullsend.sh:443")) + assert.True(t, IsHostedMintURL("https://Mint.Fullsend.SH")) + assert.False(t, IsHostedMintURL("https://evil.example.com")) + assert.False(t, IsHostedMintURL("https://fullsend-mint-abc123.run.app")) + assert.False(t, IsHostedMintURL("")) +} + func TestValidateWIFProvider_Valid(t *testing.T) { valid := []string{ "projects/123456789/locations/global/workloadIdentityPools/fullsend-pool/providers/gh-acme-widget", diff --git a/internal/config/defaults.go b/internal/config/defaults.go index 864045ff9e..bf9a28fec0 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -8,7 +8,7 @@ package config const ( // DefaultPerRepoMintURL is the hosted public mint used when no // mint_url is configured in config.yaml or config.base.yaml. - DefaultPerRepoMintURL = "https://fullsend-mint-gljhbkcloq-uc.a.run.app" + DefaultPerRepoMintURL = "https://mint.fullsend.sh" // DefaultPerRepoInferenceProvider is the default inference backend. DefaultPerRepoInferenceProvider = "vertex" diff --git a/pkg/e2etest/auth.go b/pkg/e2etest/auth.go index d2b56154d9..7a525d970e 100644 --- a/pkg/e2etest/auth.go +++ b/pkg/e2etest/auth.go @@ -39,6 +39,16 @@ func runningInGitHubActions() bool { return os.Getenv("GITHUB_ACTIONS") == "true" } +// DefaultPoolOrgInstallMintURL is written into pool orgs as FULLSEND_MINT_URL by +// admin e2e install tests. Distinct from resolveMintURL() / cli.DefaultMintURL, +// which CI uses for cross-org e2e org locking. +// +// Admin e2e tests exercise per-org installation; workflows on the installed org +// mint against FULLSEND_MINT_URL. The community hosted mint (mint.fullsend.sh) +// runs in public mode and does not support per-org installs, so org-mode admin +// e2e must keep using the legacy per-org hosted dev mint until that changes. +const DefaultPoolOrgInstallMintURL = "https://fullsend-mint-gljhbkcloq-uc.a.run.app" + // resolveMintURL returns the mint endpoint from FULLSEND_MINT_URL or the hosted // default (same as fullsend admin --mint-url). func resolveMintURL() string { @@ -63,7 +73,7 @@ func MintEnrollProjectID(cfg EnvConfig) string { if mintURL == "" { mintURL = cli.DefaultMintURL } - if mintURL == cli.DefaultMintURL { + if mintURL == DefaultPoolOrgInstallMintURL || cli.IsHostedMintURL(mintURL) { return DefaultHostedMintGCPProject } return strings.TrimSpace(cfg.GCPProjectID) diff --git a/pkg/e2etest/auth_test.go b/pkg/e2etest/auth_test.go index 9f6f97c278..6914d95672 100644 --- a/pkg/e2etest/auth_test.go +++ b/pkg/e2etest/auth_test.go @@ -12,15 +12,24 @@ import ( func TestMintEnrollProjectID(t *testing.T) { t.Setenv("E2E_GCP_MINT_PROJECT_ID", "") + + // Pool-org install mint (DefaultPoolOrgInstallMintURL) → hosted project. cfg := EnvConfig{ - MintURL: cli.DefaultMintURL, + MintURL: DefaultPoolOrgInstallMintURL, GCPProjectID: "inference-only-project", } assert.Equal(t, DefaultHostedMintGCPProject, MintEnrollProjectID(cfg)) + // Community mint (cli.DefaultMintURL / mint.fullsend.sh) → hosted project + // via IsHostedMintURL. + cfg.MintURL = cli.DefaultMintURL + assert.Equal(t, DefaultHostedMintGCPProject, MintEnrollProjectID(cfg)) + + // Env override takes precedence. t.Setenv("E2E_GCP_MINT_PROJECT_ID", "override-mint-project") assert.Equal(t, "override-mint-project", MintEnrollProjectID(cfg)) + // Custom (non-hosted) mint → inference project. t.Setenv("E2E_GCP_MINT_PROJECT_ID", "") cfg.MintURL = "https://mint.example.com" assert.Equal(t, "inference-only-project", MintEnrollProjectID(cfg)) @@ -37,7 +46,7 @@ func TestMintEnrollProjectID_EmptyWithoutHostedMint(t *testing.T) { func TestMintEnrollProjectID_RespectsEnvOverride(t *testing.T) { t.Setenv("E2E_GCP_MINT_PROJECT_ID", "from-env") - cfg := EnvConfig{MintURL: cli.DefaultMintURL} + cfg := EnvConfig{MintURL: DefaultPoolOrgInstallMintURL} assert.Equal(t, "from-env", MintEnrollProjectID(cfg)) _ = os.Unsetenv("E2E_GCP_MINT_PROJECT_ID") }