[Feat] Add secure trial inference setup - #1710
Merged
Merged
Conversation
…back key A hosting provisioner can inject a capped, Roomote-minted OpenRouter key as R_TRIAL_OPENROUTER_API_KEY. The key acts as the OpenRouter credential only while no operator key is configured (runtime or saved), so connecting a real key always wins. A fresh deployment holding the trial key and no inference choices of its own is seeded with OpenRouter's new Efficient preset (gpt-5.6-luna for every role) as ordinary editable config, so the setup wizard skips the inference step, first tasks run on an inexpensive default, and every model/provider control keeps working. - Efficient preset on the OpenRouter catalog entry - resolveModelProviderEnvValue gains a runtime-env-only trial fallback tier - gateway coverage and control-plane key resolution honor the fallback, so the key stays on the control plane and never reaches a sandbox (also blocked from worker env outright) - buildSetupModelStatus satisfies the provider through the fallback and flags trialKeySatisfied so Settings keeps the connect/edit affordances - first-boot seeding in the setup status command, guarded to never overwrite operator choices - docs: environment-variables entry
Contributor
|
No new changes since the prior review. See task
Reviewed 86b3b8f |
…t hermeticity - Reserve R_TRIAL_OPENROUTER_API_KEY (via TRIAL_MODEL_PROVIDER_ENV_VAR_FALLBACKS) in CONTROL_PLANE_ENV_VAR_NAMES so the generic environment editor rejects it and the job env-injection denylist strips a persisted copy from sandboxes - Take a FOR UPDATE lock on the deployment_settings row (inserting the bare row first if missing) so trial seeding cannot overwrite a concurrent operator save - Treat any role-model env override (R_SMALL_MODEL, R_PLANNING_MODEL, ...) as an operator model choice that blocks seeding, not just R_MODEL - Make the seeding tests hermetic: stub all role-model and provider credential env vars so host shells with R_MODEL or provider keys set cannot change outcomes
…efaults The preset previously pinned codeReview and planning to medium while the shared role defaults are high. Leaving the efforts unset means the runtime applies the same per-role defaults as a hand-configured model, and the preset can never drift from them.
The inference step no longer auto-skips on trial deployments. Instead of seeding on the first setup-status read, the step now leads with a 'Start with free credits' card (rendered when a provider is trial-key satisfied) next to the existing connect-your-own-provider flow. - new setupNew.chooseTrialInference mutation: applies the Efficient preset and records the provider choice inside the same FOR UPDATE-locked transaction; no-ops once any inference choice exists and refuses when a real provider is already connected - getSetupNewStatusCommand no longer seeds; a fresh trial deployment has no model config, so the step renders naturally until a choice is made - trial-satisfied providers no longer present as configured-via-env in the step, so picking OpenRouter from the list asks for a real key - tests: command guards, trial card render/click, editable key field
The credentials dialog treated any runtime-satisfied key as an existing primary credential, so on a trial-only deployment the unlocked Edit flow enabled Save with an empty form that the server validator then rejected. A trial fallback key is not an operator credential: the empty form stays unsubmittable until a real key is typed.
Contributor
|
Applied #1735's setup UI and restored both Back paths in |
brunobergher
approved these changes
Aug 27, 2026
…ff env The hosting-injected R_TRIAL_OPENROUTER_API_KEY becomes a delivery mechanism only: the first admin setup-status read imports it once into encrypted Settings storage (stamping trialInferenceKeyImportedAt so it is never re-imported), and every runtime read — the inference gateway, credit balance, provider status, the wizard's trial offer — resolves the stored key alone. Deleting the Roomote inference provider therefore disables the trial permanently, even while hosting keeps injecting the variable. - resolveModelProviderEnvValue / resolveModelRuntimeEnv skip the runtime env tier for the Roomote inference key name - buildSetupModelStatus never counts the env value for the roomote provider; connection = the stored row - chooseSetupTrialInference requires the stored key and runs the import first; the wizard gates the trial card on savedApiKeySatisfied - docs note that deleting the provider disables the trial permanently
The docs promised deletion as the disable path, but the delete command hard-rejected the roomote provider and the Settings row had no control. Roomote inference now deletes like any stored-key provider: its credential is the imported Settings row, the existing last-provider guard still applies, and the import marker keeps the hosting-injected variable from re-creating it. The managed row gains a delete control that names the permanent consequence.
The balance test injected the key through the runtime env, which the stored-key model deliberately ignores. It now seeds a real encrypted Settings row (round-tripping actual encryption rather than the mock the column type bypassed on insert) and proves the env-delivered value never reaches the balance endpoint, plus a case for no-stored-key returning null.
Review fixes for the free-trial inference feature: - Count the hidden roomote row toward the last-provider delete guard only while the trial is actually in use, and mirror that in the Settings UI, so a merely-imported trial key can no longer unlock deleting the last real provider - Include the GitHub Copilot subscription in the delete guard's status build so trial deletion works when Copilot is the only other provider - Stop counting a leftover task-model-settings row as a prior inference choice, so choosing the trial seeds models instead of silently no-opping after the last provider was deleted - Strip the settings-only trial key from control-plane helper OpenCode envs so deleting the provider disables the trial there too - Re-import a rotated injected trial key while the stored key exists, and skip the write transaction and row lock on the common already-imported status-read path - Cache the roomote gateway key with a 30s TTL to keep DB reads and decryption off the per-request hot path - Centralize the settings-only env-var policy behind one predicate and the roomote/OpenRouter model-id aliasing behind shared helpers - Filter hosting-managed providers by the catalog hidden flag instead of id checks, merge the duplicated credit-balance fetchers, skip the roomote body rewrite's JSON parse on the common path, and fix stale docs and the test mock's env precedence
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.
What
Free-trial inference for hosted deployments: hosting injects a Roomote-minted, spend-capped OpenRouter key, and the app exposes it as a first-class hidden Roomote inference provider (
roomote/...model ids, a default 'Roomote Trial' preset on Luna) that new deployments can choose during setup — 'Use free Roomote trial inference' vs connecting their own provider. Enforcement of the budget is the key's own OpenRouter spend limit; the existing credit-balance readout becomes the burndown.How
roomote): hidden catalog entry backed byR_TRIAL_OPENROUTER_API_KEY, gateway upstream = OpenRouter, its own rebased model catalog and trial preset. Only hosting can enable it; it is never a user connection.trialInferenceKeyImportedAtin setup state so it is never re-imported), and every runtime read — inference gateway, credit balance, provider status, the wizard's trial offer — resolves the stored key alone. Deleting the Roomote inference provider disables the trial permanently, even while hosting keeps injecting the variable.CONTROL_PLANE_ENV_VAR_NAMES(reserved from the generic env editor, stripped from job env) and the worker-env blocklist; the gateway path allowlist restricts it to inference endpoints; sandboxes never see it. A Roomote-model task on a deployment whose key is gone gets an actionable 'connect an inference provider' error.Pairs with
RooCodeInc/Roomote-Cloud#212 (per-deployment key minting with a $5 cap, one grant per account).
Notes