Repository navigation
RunR 0.6.0
RunR is an open-source, self-hosted serverless platform on Kubernetes and Knative: functions, containers, sandboxes, WebAssembly and durable workflows, with an API, a web console, the runr CLI, SDKs, a Terraform provider and a GitHub Action. You install it on infrastructure you operate, connected or air-gapped. It is developed by BRF Teknoloji and released under the MIT License.
0.6.0 adds environments with promotion, triggers beyond HTTP (schedules with time zones, queues, topics, webhook inboxes, platform events and event sources), the internal network of the regions, secret stores and OpenBao in the region, global projects in several regions behind one host name, the API product of the gateways, organization export and import, runtime patches, load tests, rightsizing and a monthly cost limit; the Terraform provider declares every setting a project or an organization has. Read Upgrading from 0.5.10 below before you upgrade. CHANGELOG.md has the complete history. Downloads, the Terraform provider registry and the documentation are on runr.sh.
Install
- Kubernetes (Helm): the chart comes from BRF's public Helm repository, without credentials:
See the README and docs/install.md;
helm repo add brf-tech https://helm.brf.sh helm upgrade --install runr brf-tech/runr --namespace runr --create-namespace --set domain=runr.example.com
helm show values brf-tech/runrlists every value. A pinned install adds--version 0.6.0; the API and UI images, the region agent and the event gateway follow the chart version. - Single host (Docker Compose, also air-gapped): compose/README.md, from a checkout of the release tag (
git clone -b v0.6.0 https://github.com/BRF-Tech/runr.git). - CLI:
curl -fsSL https://runr.sh/install.sh | shon Linux and macOS. The archives of every platform are at runr.sh/dl (https://runr.sh/dl/cli/v0.6.0/) and attached below; check them againstchecksums.txt(docs/cli.md#install). - Terraform / OpenTofu:
source = "runr.sh/brf-tech/runr",version = "~> 0.6". runr.sh serves the provider registry protocol;terraform initchecks the archive againstSHA256SUMS, signed with the RunR release key (key ID2588846B241BC095) (docs/terraform.md#install). - GitHub Action:
uses: brf-tech/runr/actions/deploy@v0.6.0; it downloads the CLI from runr.sh (docs/github-action.md). - Images: every image is published under
ghcr.io/brf-tech/runrand pulls anonymously.
The documentation is at runr.sh/docs and in the documentation index here.
Upgrading from 0.5.10
Upgrade in place with helm upgrade of the running release; never uninstall and install again. Back up the database and keep APP_KEY first (Procedure in docs/upgrade.md). Values of 0.5.10, --reuse-values included, keep working and nothing has to be set for the upgrade itself, but some defaults change what running installations do: read 0.5.10 → 0.6.0 in docs/upgrade.md first, in particular what to decide before the upgrade.
helm repo update brf-tech
helm upgrade runr brf-tech/runr --version 0.6.0 -n runr --reuse-values- The regions are brought to 0.6.0 by a Job of their own, not by a Helm hook. helm returns once the database steps ran (about a minute, as before). The Job
runr-region-reconcile-<revision>, which helm does not wait for, then runsagent:redeploy --missing,region:reconcileandsecrets:move-to-regionin the background: about 5 minutes per region cluster, with up to three attempts that continue where the last one stopped. Its state shows in the console's region status, the region API (reconcile) and/api/version(region_reconcile) (Order of operations). - The region images are pinned to the release. The region agent and the queue consumer / event gateway run at the installed version (
ghcr.io/brf-tech/runr/agent:0.6.0,queue-consumer:0.6.0) instead of:latest, andregion:reconcilemoves existing regions to them (each rolls once). Regions that pull through a mirror of their own need these images there, plusgateway-auth:1.0.0,image-upload:1.1.0and the new reqs images (New Helm values). - Every region enforces the internal network after the upgrade. A call between two projects needs an entry on the callee's allow list. List such calls before the upgrade and add the entries right after it, or switch a region to audit first (
runr regions set REGION --set internal_auth=audit) (Internal network of the regions). - Existing schedules stay in UTC. The migration pins every schedule that exists to
timezone: UTC; a schedule created afterwards without a zone runs in the organization's time zone (Schedule triggers get a time zone). - Project secret values move into the regions; finalize once 0.6.0 is confirmed. The region Job moves them. Until
--set projectSecrets.finalize=truethe database keeps an encrypted copy of every value and 0.5.10 can be put back safely; after finalizing, 0.5.10 must not be installed again (Project secrets move into the regions, Rolling back to 0.5.10). - Global projects:
bindings, notdata.GET /projects/{project}/globallists the bindings across the regions underbindings; pre-release builds of 0.6.0 called the keydata, and the upgrade migrates an installation that ran one (Global projects). - Upgrade the Terraform provider to 0.6.0 before or with the API. Provider 0.5 cannot create a
runr_domainwithouttls_modeagainst a 0.6.0 API in a region that issues certificates (the API starts the domain incert-manager, provider 0.5 expectsnone); provider 0.6.0 leaves the mode to RunR. Changeversionto"~> 0.6"; a configuration and state of provider 0.5.10 plan no change (Terraform provider, Upgrading from 0.5). - Also:
region:reconcileinstalls cert-manager on region clusters that run none (--set knative.certManager.install=falsekeeps it out), the runtime patch feed is on by default, and the Member role of upgraded tenants gets the new Developer permissions (Permissions).
From 0.5.9 or earlier, read the notes of the releases in between in CHANGELOG.md as well; from 0.5.8 or earlier, add --set api.image.tag= --set ui.image.tag= to that upgrade once (step 3 of the Procedure).
Changes in 0.6.0 (2026-10-23)
Upgrade notes
The 0.5.10 → 0.6.0 section of upgrade.md lists what to decide before
the upgrade, the order of operations and the new Helm values.
- Every region enforces the internal network after the upgrade. A call between two projects
needs an entry on the callee's allow list. Enforced on upgrade; switch a region to audit right
after if needed (runr regions set REGION --set internal_auth=audit): both are new and cannot be
set before the upgrade (upgrade.md). - The regions are brought to the release by a Job of their own, after
helm upgradereturned.
region:reconcileinstalls cert-manager on every region cluster that runs none
(knative.certManager.install: falsekeeps it out) and rolls the region Traefik three times:
about 5 minutes per region cluster, more than the bootstrap hook's two minutes (it was cut off and
loggedregion reconcile skippedwhile the hook succeeded). The hook now does the database steps
only, sohelm upgradetakes about a minute as before; the Jobrunr-region-reconcile-<revision>
(no hook: helm does not wait for it) runsagent:redeploy --missing,region:reconcileand
secrets:move-to-regionwith up to three attempts, each starting steps for an hour
(bootstrap.regionReconcileSeconds) and continuing where the last stopped. Regions it left
behind show in the console's region status, the region API (reconcile),/api/version
(region_reconcile) and a notification of the platform administrators
(upgrade.md). - The region agent and the event gateway run at the installed version, not
:latest.
ghcr.io/brf-tech/runr/agent(also the internal network's authz) andqueue-consumer(queue
consumers, the event gateway, topic publishers) default to the API's own version
(APP_VERSION, set by the chart from itsappVersion: a 0.6.0 install pulls the 0.6.0 images,
helm rollbacktakes them back), andregion:reconcile(stepimage-registry) moves the
regions' agents, queue consumers and topic publishers from:latestto it: each rolls once. The
manual restart of the region agent after the upgrade is gone. A region that pulls through a
mirror of your own needsagent:0.6.0andqueue-consumer:0.6.0there. The upload receiver
defaults toimage-upload:1.1.0, the transfer agent's image (the receiver is the same). A
release publishes the region images withruntime/ci.sh release-images --push
(upgrade.md). - Upgraded tenants' Member role gets the new Developer permissions.
environments.manage,
environments.promote,network.manage,secret_stores.viewandtopics.manageare part of
the built-in Developer role, a fresh tenant's default for members; the tenant migration0108
adds them to the custom role Member that the upgrade to 0.5.0 created, where that role is still
as the upgrade left it. A Member role the organization changed is left alone
(upgrade.md). - Schedule triggers get a time zone; the ones that exist keep UTC. A new schedule without a
zone runs in the organization's time zone (Europe/Istanbulunless set). NameUTCwhere a new
schedule must stay in UTC (upgrade.md). - Global projects fail over on their own. A global project's primary that fails its health
check for 180 s (globalDns.failover.afterSeconds) moves to the next healthy region with its
triggers and data; set the project's failover tomanualto keep it where it is
(upgrade.md).region:reconcilerestarts each region's
PowerDNS server once. - Project secret values move out of the database — and 0.5.10 stays installable until you
finalize. Values written before this release stay encrypted inproject_secretsand keep
working; the region Job of the upgrade moves them into their regions after the rollout
(secrets:move-to-region, idempotent and resumable; run it by hand with--dry-runto check).
Until the upgrade is finalized the database keeps an encrypted copy of every value — the
moved ones and every value set afterwards — so that the 0.5.10 chart can be put back on the
release (helm rollback, orhelm upgradewith the 0.5.10 chart where only one package version
is kept; never uninstall) without its deploys writing empty values; secret store references wait
until then (409 upgrade_not_finalized). Once this release is confirmed, finalize:
helm upgrade … --reuse-values --set projectSecrets.finalize=true(orphp artisan secrets:move-to-region --finalize); 0.5.10 must not be installed again afterwards. Keep
APP_KEYuntil then. Fresh installations are finalized from the start.
projectSecrets.storage: databasepostpones the change for new values
(upgrade.md).
Added
- Projects call each other on the region's internal network
(internal-network.md). Every project has an internal name
<project>.<organization>and answers at<project>.<organization>.runr.internal; its workloads
getRUNR_INTERNAL_TOKEN, which a call sends inX-Runr-Internal-Token.- Projects are isolated from each other, also inside one organization: a project admits itself,
RunR and the projects on its allow list. Another organization's project needs a grant on that
list, which a platform administrator can switch off for the whole install. - The region agent in authz mode (
runr-internal-auth, two pods per Serving namespace) checks
every call on the region Traefik's private entry point and hands the callee the verified
caller inX-Runr-Caller. A caller is the project whose pod the call comes from (known by its
address) or the project its token names. - Every region enforces it, also an existing one after the upgrade (
region:reconcile, step
internal-network): a call between two projects needs an entry on the callee's allow
list. The region settinginternal_auth: auditadmits every call and logs what the allow
lists would refuse, for an operator who wants to look first. - RunR's own traffic keeps working across the upgrade without a redeploy: queue consumers,
functions calling the model services they are bound to (no header needed), the region demo
(its frontend is granted on its backend); the web shell and the model playground sign a
short-lived token. - Access modes: a project is public (the default), public from an IP allow list (its public
host, custom domains and gateway route answer the listed addresses only) or internal only (no
public URL; reached on the internal network). - The project's Network tab,
runr network, the API (/projects/{project}/network), the MCP
toolsnetwork_get,network_set,network_caller_addandnetwork_caller_remove, and the
networksection ofrunr.yamlset both; permissionnetwork.manage. The region setting
internal_auth(enforce / audit) and the platform settinginternal_cross_tenant_grantsare in
the console's region and platform settings. runr regions set REGION --set internal_auth=enforce|auditsets the region's mode from the CLI
(the API's two values; an unset region enforces). A CLI test keeps the keys ofrunr regions setequal to the fields and values of the API's settings and monitoring requests.runtime/ci.sh test-internal-network: the Kind e2e suite of the internal network, with the
agent and queue consumer built from the checkout, and a small model service (the AI suite's tiny
CPU model on hf-serve) bound to one of its projects: the bound project's calls pass with and
without a token and another project's are refused — before and afterregion:reconcilemoves a
region of an earlier release, while a pod keeps calling the model through the switch
(RUNR_E2E_IN_MODEL=0leaves the model out).
- Projects are isolated from each other, also inside one organization: a project admits itself,
- Runtime patches: every project shows why a patch of its runtime came, and decides what to do
with it (Runtime patches). The runtime images get immutable
patch tags (exec-php:8.3.36-r1; the floatingexec-php:8.3points to the newest, the last five
per minor version are kept), and one signed feed tells every RunR installation about them: level
(routine,security,critical,zero-day), reason in English and Turkish, CVEs, link,
upstream release and, for critical and zero-day patches, a deadline.- Workloads run a patch by digest: Knative Services and CronJobs of code-mounted versions the
project's pin or the newest patch, recorded on the deployment; versions built into an image are
builtFROMthe patch by digest; sandboxes and warm pools the newest patch. - Projects have a patch state —
up_to_date,patch_available(with the most urgent level),
pinned,overdue— shown next to the end-of-life badges, in the API (runtime_patchof a
project),project_getandrunr projects get. - Apply redeploys the live version onto the newest patch; pin fixes a project on one of
the last five patches (and redeploys onto it); console dialog with level, reason, CVEs, link
and deadline,POST /api/projects/{project}/runtime-patches/apply,
PUT|DELETE …/runtime-patches/pin, the MCP toolsruntime_patches_get,runtime_patch_apply
andruntime_patch_pin, andrunr projects patches,runr projects patch apply|pin|unpin,
runr runtimes patches. - Deadlines (details): at a critical or zero-day patch's
deadline RunR redeploys every project still behind onto the newest patch — a pin gives way —
and rolls one back to the digest it ran before (pinned there) when the new revision does not
become ready, with a critical notification. Platform administrators switch this off or extend a
patch's deadline. - Notifications by level (details): security patches
in-app to the owner and last deployer of each project behind and the organization's owners and
admins; critical ones also to the platform administrators and by mail; zero-day ones to everyone
whatever their preferences, on the organization's alert channels too, with a console banner
until no project runs behind. Each goes out once per patch and user (notification preference
runtime_patch). - End-of-life reminders 13, 4 and 1 week(s) before a runtime version's end of life to the
owners of its projects and the organization's owners and admins (in-app; by mail a week ahead)
— there were none before (End of life). - Settings → Patches for platform administrators (The Patches
page): the feed's status, last check and signature, Check
now, the policy, every patch with the projects behind it per organization, Remind, Force
now, Extend deadline; a banner announces a newer RunR release the feed names
("RunR 0.5.11 available (zero-day): …"). - The runtime patch feed is verified before it is used (Runtime patch
feed): an OCI artifact (ghcr.io/brf-tech/runr/runtime-patches:feed,
media typeapplication/vnd.runr.patch-feed.v1+json), signed with cosign and checked against
the configured public key in the API. RunR checks its digest every five minutes; a feed with a
wrong signature or schema is ignored, the last good one stays and platform administrators are
told once. Installations without a way out read it through a region's registry.- New Helm values
runtimePatches.feed,runtimePatches.publicKey(by default the key
BRF-Tech/runr-runtimes signs the feed with, so the feed works out of the box; empty: the feed
is off and platform administrators see a warning),runtimePatches.viaRegionand
runtimePatches.autoMigrate(runtimePatches). - Audit events
runtime_patch.apply,.pin,.unpin,.feed.check,.policy.update,
.remind,.force,.deadline.update, andruntime_patch.migrate/runtime_patch.rollback
for what RunR does at a deadline.
- New Helm values
- Workloads run a patch by digest: Knative Services and CronJobs of code-mounted versions the
- Timed warmer: containers kept warm on a weekly schedule (Scaling).
In the scale settings, Advanced: timed warmer takes up to 10 rows of a cron expression and a
container count. Each row means "from this moment on, at least N containers" until the next row
fires, for example 3 containers on weekdays from 09:00 and none from 18:00.- The console explains each row in plain language (English or Turkish), shows a weekly timeline
of the warm containers and, in pay-as-you-go regions, an estimate of the next 30 days in
vCPU-hours, GB-hours and cost. - The crons run in the organization's new time zone setting (Settings,
timezoneof
/api/settings; default Europe/Istanbul). - While a schedule is set it replaces
min_scale;max_scalestill applies and no row may keep
more containers warm. - The value is set on the Knative PodAutoscaler of the live revision every minute: no new
revision, no restart, no deploy needed for a changed schedule. Every deploy starts the new
revision with the value of the moment. - Capacity, organization quota and GPU admission count the schedule's peak. Metering counts the
containers the warmer actually kept warm. - Also in
runr.yaml(scale.schedule), the CLI (runr scale --warm "<cron>=<count>",
--no-warm), the MCP project tools and the Terraform provider (runr_project.scale_schedule). POST /api/scale-schedule/previewevaluates a schedule before it is saved.- New e2e suite
runtime/ci.sh test-warmer(test:e2e --suite=warmer).
- The console explains each row in plain language (English or Turkish), shows a weekly timeline
- A monthly cost limit for pay-as-you-go projects (Billing module,
docs). The project's owner sets one amount in the billing
currency on the project's Billing tab. Under it the tab shows this month so far and a month-end
forecast (so far ÷ elapsed days × days in the month). Fixed-price projects show nothing.- At 80 % the owner is notified. At 100 % the owner chose either another notification or a stop
of the project. Each goes out once per month and limit. - A project the limit stopped is resumed by its owner on the Billing tab once the limit no longer
stops it: raised, removed, switched to notify, or a new month began. With the limit still set,
the first check of a new month resumes it by itself and redeploys the version that ran
(eventproject.resumedwithsystem: "cost_limit"), unless the new month already reached a
stop limit (New month). - The limit is checked every 15 minutes (
billing-cost-limits) and when it is saved, so a stop
limit below this month's cost stops the project right away (the console warns before saving). - New webhook events
project.cost_limit.warningandproject.cost_limit.reached;
project.stoppedcarriessystem: "cost_limit". A project'sstoppedstate says
system: "cost_limit"and names no user. - API:
GET/PUT/DELETE /api/projects/{project}/billing/cost-limitand
POST …/cost-limit/resume. MCP:project_cost_limit_get,project_cost_limit_set. - It is a guardrail the owner sets. RunR still never stops a project because a bill is unpaid.
- At 80 % the owner is notified. At 100 % the owner chose either another notification or a stop
- Billing tools for AI agents (Billing module, MCP tools). While billing
is on, the RunR MCP server has tools for the billing reads and settings an agent needs, each
calling the billing API with its permissions:- bills and their line items, split into the project's own usage, its preview environments
and its queue consumer:billing_bills_list(the organization's, the platform's to the tenant,
the platform's to its tenants),billing_bill_get; a project's Billing tab:
project_billing_get; billing_usage_get(metered usage per project and month),billing_prices_getand
billing_price_set(a price version that changes only the prices given);- queue consumer charging at the region and organization level:
billing_regions_list,
billing_queue_consumers_set; per tenant for platform administrators:
billing_region_organizations,billing_region_organization_set; - fixed allocations:
project_billing_quote,project_fixed_request(request or withdraw),
billing_fixed_requests_list,billing_fixed_request_decide(approve or reject). - A tool's permissions may now name platform administrators (
@platform:billing.view, the rule
of the API's platform routes); the console's tool catalog shows them so.
- bills and their line items, split into the project's own usage, its preview environments
- Load tests of HTTP projects (load-test.md). Project → Load test,
runr loadtest, the MCP toolsload_test_run/load_test_getand
POST /api/projects/{project}/load-testsrun k6 inside the project's region: 10, 50 or 100
concurrent users send a GET, or a POST with a small body, to a path of the project's own URL for
30 s, 1 min or 5 min. The results are p50 / p95 / p99 latency, the error rate and the requests
per second, with the pods the project scaled to and its cold starts in the test window; the
last five runs are shown side by side, and the console follows a running test live.- The target is only the project's function host or one of its active custom domains, resolved
to the region's own ingress: the test measures the function, not the internet. One test per
project at a time, a daily limit per organization (observability.loadTest.dailyLimit, 20). - The k6 Job runs in the region's
runr-loadtestnamespace (created on the first test):
restricted pods without a service account token or secrets, CPU and memory limits, a pod quota,
egress only to the region's Traefik, removed after the test. - Test traffic is metered and billed like any other traffic of the project.
- Running and stopping a test needs
projects.deploy, reading the resultsmetrics.view. - New image
ghcr.io/brf-tech/runr/reqs/k6:1.8.2(the reqs mirror ofgrafana/k6); new Helm
valuesobservability.loadTest.*. - The load-test namespace goes with the region's Knative Serving
(Load tests). Uninstalling Serving (or all of Knative)
leftrunr-loadtestwith its policies, quota and running k6 Jobs on the cluster. It is now
removed with Serving (only when RunR created it), the region's running tests end as failed with
the reason, and the next test after a new install applies the namespace again. The end of a
tenant's share of a platform region removes that tenant's k6 Jobs and their ConfigMaps from the
shared namespace and ends its tests there.
- The target is only the project's function host or one of its active custom domains, resolved
- Deploy strategies: blue-green and canary rollouts per function
(deploy strategies). Direct deploys stay the default; an HTTP or
workflow function can switch on a strategy with its options.- Blue-green: the new revision starts next to the one that serves with 0 % of the traffic and
a test URL of its own (green-<slug>.<function domain>). A promotion moves all traffic at once —
from the console, the API, the MCP tooldeployment_promoteorrunr rollout promote— or by
itself once the revision is ready and an optional health path answered 2xx a number of times
in a row. The previous revision stays warm for a configurable window (15 minutes by default,
previous-<slug>.<function domain>), so a rollback is instant; then it is released. An abort
drops the new revision and the traffic stays. - Canary: the new revision gets its share in steps (10 % → 50 % → 100 % by default, every 5
minutes) and is promoted after the last one. When the new revision's error rate or p95
latency exceeds its threshold during a step, the rollout rolls back by itself. The numbers are
the revision's own (Canary thresholds): its span
metrics while tracing is on (the collector's span metrics carry the dimensionrunr.revision;
region:reconcileupdates a collector installed before), else Knative's request metrics of
the revision where the region's Prometheus collects them; without either the canary
progresses on time only. The rollout'smetricssay which (source,revision). Promote now
and abort work at any time. - Settings in the project's console settings,
GET/PUT /api/projects/{project}/deploy-strategy,
runr strategy, anddeploy:inrunr.yaml(CLI and git push); a change creates no version.
One deploy can override it (strategy,runr deploy --strategy direct). - The rollout endpoints (
…/rollout,…/rollout/promote|abort|rollback), the live rollout
panel of the console (traffic split, steps, health checks, metrics against the thresholds,
test URLs, history) updated over thedeployment.rolloutWebSocket topic, the MCP tools
deployment_promoteanddeployment_abort,deployment_statuswith the rollout,runr rollout status|promote|abort|rollback, and audit events for the settings, the actions and what a
rollout does by itself. - Queue and schedule functions, previews and the deploys of a region move stay direct. While a
rollout runs both revisions run and are metered. The revisions a rollout holds keep their
images through registry retention and are never collected by Knative's revision GC. - New Kind suite
runtime/ci.sh test-deploy-strategies(test:e2e --suite=deploy-strategies).
- Blue-green: the new revision starts next to the one that serves with 0 % of the traffic and
- Rightsizing recommendations. RunR turns what a running project's pods really used into a size
recommendation: "requests 512 MB but used at most 140 MB in the last 14 days → 256 MB is enough",
with what it saves per month when billing is on, applied with one click
(Rightsizing).- Every night the
rightsizingjob reads the day's peaks of every running project from its
region's Prometheus — the highest and the 95th percentile working-set memory and CPU of one
pod, OOM kills, throttled CPU periods, with the size that ran — and keeps them per project and
day (resource_peaks). The first night after the upgrade reads back the last 14 days. - A project's last 14 days are compared with its size: memory from the highest working set ×
1.3, CPU from the 95th percentile × 1.3, rounded up to the next size step; a larger size after
an OOM kill, a memory peak near the limit or CPU throttling at the current size. Projects with
GPUs and projects with too little data (fewer than 3 days, a scale-to-zero function with fewer
than 20 sessions) get none. Thresholds, margins and steps are configurable
(RIGHTSIZING_*). - Applying puts the size into the project's config through the checks of a manual edit (region
capacity, a fixed allocation) as a new version; the next deploy runs it. A recommendation can
be dismissed (until the recommended size changes) or snoozed. Both are audited. - With the Billing module a recommendation shows what it saves per month: an always-on workload
its reservation at the hourly prices, a fixed project the allocation it could lower; a
scale-to-zero function is billed by use, so its smaller size frees capacity, not money
(Billing). Without billing the resources are shown. - Console: a card on the project's page and /rightsizing, the organization's list sorted by
saving, linked from the projects page. API:GET /api/rightsizing,
GET /api/projects/{id}/rightsizing,POST …/rightsizing/apply,POST …/rightsizing/dismiss.
CLI:runr projects rightsizing [NAME]|apply|dismiss. MCP:rightsizing_list,
rightsizing_apply.php artisan rightsizing:runruns it on demand.
- Every night the
- A project has a list of triggers (triggers.md): any combination of HTTP,
schedules, queues, a webhook inbox, topics and platform events, each with its own name, settings,
write-only secrets and an enabled switch.- The first enabled trigger is the primary one:
config.trigger_type/schedule/
queue_streammirror it, so clients of the single-trigger API, the Terraform provider and
runr.yaml'strigger:keep working; setting them changes the primary trigger. GET|POST|PUT /api/projects/{project}/triggers,PATCH|DELETE …/triggers/{trigger},
GET /api/trigger-types; the project resource carriestriggers. runr.yaml takes a
triggers:list. Console: the trigger editor of a project. CLI:runr triggers. MCP:
triggers_list,trigger_add,trigger_update,trigger_remove.- Every existing project gets its trigger from its config with the upgrade (tenant migration
0077_create_trigger_tables). - Webhook inbox: a stable public URL per trigger (
/api/hooks/<context>/<key>) with signature
presets for Stripe, GitHub, Slack, a generic HMAC (header, algorithm, hex / base64, prefix) or
none; a delivery id the inbox already stored is not delivered again. The sender gets 200 once
the request is stored; the function gets it afterwards, retried. Stored events stay for the
trigger's retention (7 days by default) and can be replayed from the console's inbox viewer, the
API,runr triggers replayand MCP (webhook_events_list,webhook_event_replay). The inbox
takesPOSTandPUT; the API document (/api/docs) has an operation for each. - Topics: fan-out topics of the organization on a region's Redis — every subscribed function
gets each event (one consumer group per subscribing trigger). Functions publish with
RUNR_TOPICS_URL/RUNR_TOPICS_TOKENand thepublishhelper of the workflow SDKs; others with
POST /api/topics/{topic}/publish,runr topics publishor the MCP tooltopic_publish. New
permissiontopics.manage(Developer and above) to create and delete topics. A topic's
created_byis the creator's id as a string in every answer — the201of the create was a
number — in the API, its document, the CLI and the MCP tools. - Broker and external sources:
kafka(consumer groups),nats(JetStream durable consumers
or core NATS),rabbitmq(manual acks),mqtt(QoS 1 / 2 with a persistent session),cdc
(Postgres logical replication, in commit order),s3(object created / updated / deleted on any
S3 API) andemail(IMAP) triggers. The event gateway reads them and acknowledges an event only
once it is delivered or dead-lettered; credentials are write-only trigger secrets. The console
renders their settings from the type's fields (GET /api/trigger-types). - Platform events:
deployment.finished,deployment.failed,alert.firedand
alert.resolvedreach functions with aplatform_eventtrigger as CloudEvents
(dev.runr.<event>);runtime.patch_availableandcost.limit_reachedare defined for the
features that raise them. - Audit log: triggers, topics and the internal network.
project.trigger.create,.update,
.replace,.delete,project.trigger.event.replay,project.trigger.events.replay,
topic.create,.update,.delete,.publish,project.network.update,
project.network.caller.addand.remove— names and counts, never secret values or event
bodies (Security). - Schedule triggers run in a time zone (triggers.md):
config.timezone(an IANA zone), else the organization's time zone, the timed warmer's, which
the schedule then follows — a change of the organization's zone reaches the deployed CronJobs
(spec.timeZone) and gateway schedules at once. The trigger API showseffective_timezoneand
timezone_source; the console's trigger editor,runr triggers ls, runr.yaml
(timezone:) and the MCP trigger tools take and show the zone. A tick's data carries
timezoneandlocal_time. The project API's single-trigger fields take
config.schedule_timezone(""= the organization's) and return it with
config.schedule_effective_timezone; Terraform'srunr_projecthastrigger.timezoneand the
computedtrigger.effective_timezone(data.runr_project:schedule_timezone,
schedule_effective_timezone), the MCP toolsproject_create/project_update
schedule_timezone. Backward-compatible zone names (Europe/Kiev) are taken where the API's
PHP knows them — RunR's API image does; the API's tests take such a name from the PHP that
runs them, so they pass on a PHP built against a system tzdata without the legacy names
(Ubuntu 24.04 withouttzdata-legacy) too. - A removed cdc trigger's replication slot is dropped (triggers.md):
when a cdc trigger is removed, renamed or pointed at another slot or database, or its project or
an environment is deleted, RunR drops its slots and the publication it created from the database
once no deployed event gateway reads them — a one-shot pod of the gateway image
(GATEWAY_MODE=cdc-cleanup) in the region, as the gateway reaches the database. The API answer,
the CLI and the console say what is dropped; a notification reports the outcome, with the SQL to
run by hand when the database cannot be reached. The gateway marks the publications it creates,
so one that existed before is never dropped. - runr.yaml
triggers:is checked by the CLI with the API's own rules:php artisan triggers:schemaderives a schema of the trigger entries from the trigger types
(api/resources/triggers/triggers.schema.json) and the CLI embeds it (required keys, types,
ranges, patterns, cron, time zones,delivery). What the API only ignores is now a warning, not
an error:trigger:next totriggers:, asecretskey (not sent), keys a type does not have;
enabled: 1andtrigger.schedule: "@daily"are accepted as by the API. runtime/ci.sh test-trigger-sourcesruns every broker source on Kind — Kafka, NATS JetStream,
RabbitMQ, MQTT (no longer opt-in), Postgres CDC with the slot cleanup, IMAP, next to S3 — and a
schedule time zone, with brokers from the reqs mirror.- The topic publisher counts in the region capacity on every cluster.
runr-topicsis a
platform pod like gateway-auth; it now tolerates dedicated workers (and prefers the other nodes),
so it is scheduled — and counted — also where every node is a dedicated worker, where it stayed
Pending.
- The first enabled trigger is the primary one:
- Secret stores: references without credentials. A project secret variable can refer to a
value in the organization's HashiCorp Vault or OpenBao (Kubernetes or JWT auth), AWS Secrets
Manager, GCP Secret Manager or Azure Key Vault (workload identity federation): store, path and
key. External Secrets Operator — an optional region feature installed from the region's page or
POST /api/knative-regions/{id}/external-secrets, with RunR's mirror of its image, kept at the
pinned chart byregion:reconcileand installed again by a region migration — reads it in the
region as the service accountrunr-ss-<store id>and fills the Secret<slug>-ext-envthe
workload reads. The store never gets a credential from RunR: it trusts the region cluster's
service account issuer, and a store has no setting for a token, password or key. The store's
trust page shows per region the issuer, its keys (JWKS and PEM), the service account, namespace,
subject and audience, and Verify applies and checks the store in a region
(secret stores).- Secret store API, console, CLI and MCP.
/api/secret-storeswithtrustandverify
(permissionssecret_stores.view— Developer has it — andsecret_stores.manage);
PUT /api/projects/{project}/secrets/{name}takes{"reference": {…}}; Settings → Secret
stores and references in the project's secret variables;runr env ref NAME STORE:PATH[#KEY],
runr env sync,runr secret-stores ls|trust; the MCP toolssecret_ref_setand
secret_stores_list, andenv_listshows where each secret lives. - Sync status of references.
secrets:sync-statusreads every two minutes what External
Secrets Operator reports —synced,failedwith the store's message,pending— onto each
reference and onto the stores per region; Sync now (POST …/secrets/refresh,
runr env sync) asks for a read right away.
- Secret store API, console, CLI and MCP.
- OpenBao in the region. A region can run an OpenBao that RunR installs and operates — the
region's Secrets tab,POST /api/knative-regions/{id}/openbao,runr regions openbao—
next to External Secrets Operator, which it needs: one StatefulSet inrunr-openbaowith an
unsealer beside the server, file storage on a volume, TLS issued by RunR, the image from RunR's
mirror (ghcr.io/brf-tech/runr/reqs/openbao), kept at the pinned image and a fresh certificate
byregion:reconcile, installed again by a region migration. While it runs, new secret values of
the region's projects go into it (sourceandstored_inopenbao;PATCH …/openbaowith
default_for_values: falseturns that off): each project reads its own values through a service
account, an OpenBao role and policy of its own, its SecretStore and the ExternalSecret
<slug>-bao-env(OpenBao in the region). The
Secrets tab offers OpenBao's install as soon as the operator installed there is ready, without
a reload.- The region's OpenBao keeps no credential in RunR. Its unseal key goes into the region's
Secretrunr-openbao-unsealonly and its TLS key pair intorunr-openbao-tls; the root token is
revoked once the first install configured it, and RunR logs in through the API server with a
ten-minute token of the service accountrunr-controlthat it never stores. The Postgres role
OpenBao issues dynamic users with gets a password RunR hands over once and OpenBao rotates at
once: afterwards only OpenBao knows it. OpenBao's audit log goes to the container's output.
- The region's OpenBao keeps no credential in RunR. Its unseal key goes into the region's
- Dynamic Postgres credentials. A Postgres attachment with
"credentials": "dynamic"gets a
user of its own from the region's OpenBao instead of the binding's user and password: created
IN ROLEof the binding's role, living a week and renewed every day in the region
(<prefix>PGUSER,<prefix>PGPASSWORD,<prefix>DATABASE_URLfrom<slug>-pgdyn-<…>); a
renewal rolls the workload onto the new user, detaching or switching back revokes the users
(RUNR_OPENBAO_DB_TTL,_MAX_TTL,_REFRESH;runr bindings attach --credentials dynamic|static)
(bindings). - Environments and promotion (Environments). A project runs in several
environments: its own workload —production— and any others you add (dev,staging, …),
each with its own Knative Service<slug>-<name>and URL<slug>-<name>.<function domain>.- Configuration layering: the project's configuration with the environment's overrides (env
vars merged by name, anullremoves one; CPU, memory, scale, timeout, schedule, queue
stream); secret variables of its own over the project's (inherit_secretsoff: only its own);
bindings and custom domains attached to the environment only — staging's pods cannot reach
production's data (binding NetworkPolicies admit them byrunr/environment-id). - An environment can run in another region than its project, within the organization's data
residency. A built version (dependencies, a compile step, WebAssembly) whose image that
region's registry does not hold is built there from its code, FROM the same runtime base: the
deploy, promotion or push waits for the build while the environment's running deployment keeps
serving, and every later deploy of the version into the region runs it at once
(version_region_images, central migration2026_10_22_100001, tenant migration0084). - How many environments a project may have is decided by the quotas: each one reserves its size
in its region's capacity and the organization quota from its creation on. Preview environments
stay separate. - A git branch maps to an environment (
develop→staging): a push deploys it; its
runr.yamldoes not change the project's configuration. - Promotion moves the version an environment runs to the next one — the same version and image,
never built again (in another region: built into its registry) — with the target's
configuration, after a "what will change" preview (code
version and config differences, env var names only). Promotions into production wait for
someone else with the new permissionenvironments.approve(the project can turn the
approval off);environments.manageandenvironments.promoteare part of the Developer
role. Approvals inbox:GET /environment-promotions, the console's Approvals page (live),
runr approvals. - Approvers hear of a promotion that waits for them, the requester of its approval, rejection,
deployment or failure: in-app and by mail through the new Promotions notification
preference, and as the platform eventsenvironment.promotion.pending_approval,.approved,
.rejected,.deployed,.failedforplatform_eventtriggers. - Gateway routes and private database connections per environment: a route leads to the
project's own workload or to one environment (environmenton
POST /gateways/{gateway}/routes;POST /gatewaysgives you a gateway in another region), a
Skupper connection to a managed PostgreSQL database belongs to one environment and reaches only
its pods (central migration2026_10_22_120001, tenant migration0086). - Durable workflows per environment: every environment is a workflow app of its own — its
definitions, its runs against its workload, its own signing key, its cron triggers and queue
bridge — and events belong to an environment by its name (environmentonPOST /api/events;
central migration2026_10_22_110001, tenant migration0085). The workflow API, the MCP
workflow tools andrunr workflows --envtake it; the console's Workflows tab follows the
environment switcher. - Deploy strategies per environment (Deploy strategies):
each environment rolls out with the project's blue-green / canary settings unless it sets its
own strategy or options (kept in its overrides,deploy_strategy). Deploys, promotions into it
and rollbacks follow the environment's settings; its rollouts run on its own workload — test
URL on its host, traffic, canary thresholds on its own traces, the kept-warm previous revision
— and are promoted, aborted and rolled back there, next to production's.
GET|PUT /projects/{project}/deploy-strategytakeenvironment(andreset),GET /projects/{project}/rollout?environment=; rollouts carryenvironment(central migration
2026_10_22_100002, tenant migration0088:deployment_rollouts.environment_id). Console:
the strategy on each environment's card and in the Deployments tab of the selected
environment, the environment's rollout panel; CLIrunr strategy show|set|reset --env,
runr rollout … --env; MCPdeployment_status,deployment_promote,deployment_abort
takeenvironment. - The timed warmer per environment (Scaling): an environment
runs the project's schedule unless it has its own (overrides.scale_schedule;[]keeps no
containers warm there), on its own workload and every revision that takes traffic there (a
rollout of the environment: both), in the organization's time zone; what was applied is
recorded on the environment (warm_scale; central migration2026_10_22_100003, tenant
migration0089) and a schedule turned off there gives the workload itsmin_scaleback.
Environments showscale_schedule,scale_schedule_inheritedandwarm_scale. Console: the
environment's settings → Timed warmer and its card; CLIrunr environments create|update --warm … | --no-warm | --project-warm; MCPenvironment_createtakesscale_scheduleand
the newenvironment_updatechanges an environment's settings and overrides. - Saving an environment's overrides without its variables keeps them for a caller who cannot
see them (noenv.view), and sending them back unchanged needs noenv.edit; the console's
environment form keeps the overrides it does not show (schedule, queue stream, deploy
strategy). - Console: environment switcher, environment settings, promote dialog, approvals inbox. CLI:
runr environments,runr promote,runr approvals,runr deploy --env,
runr bindings attach|detach|attachments --env,runr domains … --env,
runr gateway routes add … --env,runr private-connections … --env,runr workflows … --env.
MCP:
environments_list,environment_create,environment_delete,promote,
promotion_approve;deployandrollbacktake anenvironment. - Metering records the environment of every usage record (
environment_id, and its name:
central migration2026_10_22_130001, tenant migration0087), and bills split a project's
lines per environment ("vCPU hours (staging, eu-1, HTTP)", same totals;GET /billing/usage
too); fixed-region allocations count every environment in the region; a project stop stops
every environment and a resume deploys each one again. - A region move deploys the environments that run on the region again on the new cluster —
also of projects in other regions — like its projects (built into the new registry first when
needed; refused or failed ones are not moved), and its report lists them; a rollback deploys
them again on the restored cluster and removes what the move ran for them from the cluster it
left. A region detached from an organization takes its environments down with its projects.
Kind e2e suite:runtime/ci.sh test-environments. - CLI: gateway routes and private database connections.
runr gateway create REGIONgives you
your gateway in a region,runr gateway routes add GATEWAY PATH -p PROJECT [--env],
runr gateway routes ls|rmadd, list and remove routes;runr private-connections ls|add|status|rm(alsorunr pconn) manage a project's Skupper connections to a managed
PostgreSQL database, the password from stdin or a hidden prompt (CLI).
- Configuration layering: the project's configuration with the environment's overrides (env
- Regions set up cert-manager for the certificates of custom domains. Custom domains in TLS
modecert-managerno longer depend on a cert-manager and a ClusterIssuer the operator installed
by hand (cert-manager).- The Serving install installs
jetstack/cert-managerv1.21.2 (namespacecert-manager, images
from the reqs mirrorghcr.io/brf-tech/runr/reqs/cert-manager-*) where the cluster runs none, and
reuses a cert-manager the cluster already runs — never a second one, never removed by RunR.
region:reconciledoes the same for existing regions and brings RunR's release to this
release's chart. Helmknative.certManager.install: falsestops RunR from installing it. - RunR creates the default ClusterIssuer unless the region setting
cert_manager_cluster_issuer
(orRUNR_CERT_MANAGER_CLUSTER_ISSUER) names one:runr-acme(Let's Encrypt, HTTP-01 through
the region's Traefik) where the region is reachable from the internet and an ACME e-mail is
set — the region settingcert_manager_acme_emailor Helmknative.certManager.acmeEmail,
never an address RunR picks —, elserunr-ca, a region CA whose certificate the console, the
CLI and the API download.cert_manager_issuer_mode(auto,acme,ca) chooses. - New domains start in TLS mode
cert-managerwhere the region issues certificates. Where it
cannot yet, the console, the CLI and the API say why (installing, failed, not installed, no
issuer) and what to do, instead of the raw "no ClusterIssuer" error. - Console: General → cert-manager (status, issuer, the reason of RunR's choice, Download
CA, the issuer mode and ACME e-mail, Install / Remove); the Domains tab shows what new domains
get and offers the region CA. API:GET/POST/DELETE /api/knative-regions/{id}/cert-manager,
…/cert-manager/ca-certificate,GET /api/projects/{id}/domains/ca-certificate,
meta.cert_managerof the domain list. CLI:runr regions cert-manager [install|ca|rm],
runr domains ca, the newrunr regions setkeys. MCP:cert_manager_status,
cert_manager_install. - Removing cert-manager from a region is refused while custom domains use it; RunR's release and
issuers go only when nothing else on the cluster uses cert-manager.
- The Serving install installs
- Global projects: one project in several regions behind one host name
(docs/global-projects.md). A new project type runs in 2 to 8 regions
as<slug>.g.<base domain>.- Modes: HA (the primary region while its copy is healthy, the others when it is not) or
proximity (the closest healthy region, by GeoIP). - GeoIP for clusters without internet access: each region's server has a GeoIP source —
the platform URL, an operator-provided URL (an internal mirror, a file in the region's object
store), a file the operator copies onto the server's volume, or none — and the console shows
whether the server loaded it, from where, or why not. - Global DNS: an operator enables it per platform region. RunR then runs a small PowerDNS
Authoritative server in that region (reqs/pdns-auth:5.0.8, a LoadBalancer on UDP and TCP 53)
and writes the same zone to every server through its HTTP API every minute, repairing drift.
The zone is delegated once with the NS and glue records that Settings → Platform → Global DNS
lists. A region without a public port 53 cannot take part. - Health checks: the project records are PowerDNS LUA records whose
ifurlup()checks of
every region's copy end at the region agent's new global-health listener. It answers from the
Knative Service's readiness, so scaled-to-zero copies stay at zero. - Deploys go to the primary region first. When the deployment is ready, its version rolls out
to the other regions: built again into each region's own registry when it has a build image
(WebAssembly modules too, from the same runtime base by digest; one build per version and
region, shared with the project's environments there), an uploaded CaaS image copied into each
region's registry, then applied there and polled until ready. - Triggers: any trigger list. HTTP and workflow triggers answer in every region; schedules,
queues, topics, webhooks, platform events and broker sources run in the primary only (the other
regions hold the CronJob suspended and the event gateway at zero) and move with the primary. - Failover: a switchover (
PUT /globalwith a new primary) or a failover (POST /global/failover, or automatic after 180 s of a failed primary — per projectautomaticor
manual) moves the primary, its triggers, its data and the DNS answer; the old primary is
fenced and rejoins as a secondary once its cluster answers. - Gateways, GPUs, custom domains: gateway routes from a gateway of any of the project's
regions; GPUs admitted per region (quota counted in every region); custom domains served and
certified by every region (DNS-01 through the global zone after one CNAME of the domain's
_acme-challenge). - TLS: cert-manager issues the wildcard
*.<zone>in every region with DNS-01 through RFC
2136 updates signed with RunR's TSIG key. This is RunR's first use of DNS-01. - Data across regions: a dedicated Postgres binding becomes a CloudNativePG distributed
topology — a replica cluster in every region, streaming from the primary;DATABASE_URL_RO
reads the region's replica,DATABASE_URLwrites to the primary; a switchover promotes with
the demotion token (nothing lost), a failover at once. KV, managed object storage and shared
Postgres stay in their region and are reached from the others through the binding's data
link (a forwarder in each region, mutual TLS by SNI to the binding region's Traefik).
External bindings work as before. A region whose data is not reachable yet stays out of the
DNS answers. Private database connections, model services and dynamic credentials are refused
for global projects, with the reason in the docs. - Billing and capacity: usage is recorded and billed per region, and every copy counts in its
region's capacity. - API, CLI (
runr deploy --global-regions,global:in runr.yaml,runr projects global
withset --failoverandfailover,runr regions global-dnswithgeoip,runr global-dns),
MCP tools (project_global_get,project_global_update,global_dns_status), console
(project type, Regions tab with failover and the data across regions, Global DNS with the
GeoIP source in the region and platform settings, the custom domains' regions), Helm values
globalDns.*(failover.afterSeconds,dataLink.*), aregion:reconcilestep, and the Kind
suiteci.sh test-global-projectson two clusters.
- Modes: HA (the primary region while its copy is healthy, the others when it is not) or
- API product: a gateway's API can be offered to others (docs/api-product.md).
- Every gateway route is
public,keyorjwt. Key and JWT routes are checked by the region's
new gateway-auth service (ghcr.io/brf-tech/runr/gateway-auth, Traefik forwardAuth), which
RunR installs with the first such route. The gateway had no authentication before. - Consumers of a gateway or of the organization, with an external customer number for the
invoicing system, a JWT subject and a status (suspended consumers get 403). - API keys are shown once and stored as a SHA-256 hash with a visible prefix. A key can be
limited to some routes and methods, can expire, and can be rotated with an overlap or revoked;
a revocation reaches the region within about 10 seconds. The function seesX-Runr-Consumer,
never the key. - JWT routes verify tokens with the issuer's JWKS (RS, PS and ES algorithms), issuer and
audience, and map a claim to a consumer. - Plans: requests per second with burst, per minute (sliding window), per day and per month,
counted in the region's Redis. Over a limit the caller gets 429 withRateLimit-*and
Retry-Afterheaders. - Fail closed: while gateway-auth is not installed or not ready, key and JWT routes answer
503auth_unavailableand never reach the function unchecked. The gateway showspendingor
errorwith the reason, and a job opens the routes once gateway-auth is ready. - Usage per hour, consumer, key and route in
GET /api/api-usage: the calls let through and
refused, and what the function answered to the calls let through — status classes, errors
(5xx and calls without an answer), latency p50 / p95 / p99 and average, request and answer
bytes (a WebSocket's traffic included, counted when it closes). gateway-auth's metering proxy (runr-gateway-auth-proxy) measures them after the
check, which stays in front so a route still fails closed; the check hands it a signed ticket,
without which it serves nothing. Percentiles come from a histogram per row, so any grouping adds
up. With the Billing module the usage is also sent as signedapi.usage.hourly,
api.usage.monthlyandapi.usage.quota_exceededevents with the same fields. The console's
Usage tab,runr gateway usageand the MCP toolapi_usageshow them. RunR measures; billing
stays with an external system. - Only through the gateway: a route can make its project's Knative Service cluster-local, so
its own address stops serving. - A route is created with its authentication:
POST /api/gateways/{gateway}/routestakes
auth_mode,jwtandgateway_only, validated as on a change, and the route reaches the
region with its forwardAuth Middleware applied before the IngressRoute that references it — a
key or JWT route is never public, not even between two requests. The console's route form,
runr gateway routes add --auth …, the MCP toolgateway_route_add, Terraform's
runr_gateway_routeand the organization import create it in one call (the CLI and the
provider set it right after the creation against an earlier API). The Kind suite checks it
(api-key-on-create). - Route answers are the route as the gateway shows it:
POSTandPATCHof a route and
GET /api/gateways/{gateway}/routesanswer itsurl,auth_mode,jwt,gateway_only,
project and environment likeGET /api/gateways/{gateway}, sorunr gateway routes addprints
the URL. A path another route of the gateway has is a 422 on creation and on a change of the
path — also when two requests race for it (the unique index's refusal, on SQLite and
PostgreSQL) — not a 500; so is a second route to the project's own workload or to one
environment (a partial unique index for the project's own route: central migration
2026_10_23_100001, tenant migration0109). - The gateway's custom domain is set through the API and served only after its TXT record
and CNAME / address are verified. - Developer page: an optional public page per gateway with its routes and how to call them,
and, when the owner switches it on (developer_page_metrics,runr gateway page --metrics),
each key / JWT route's success rate and latency of the last 7 days — never counts or consumers. region:reconcilerebuilds the routes of every gateway an earlier release built (step
gateway-routes, by the routing revision on its IngressRoute) and applies gateway-auth again
when its Deployment changed, not only its image.- Console (Settings → API gateways),
runr gateway …, eleven MCP tools, the permission
api_products.manage, audit events, Helm valuesapiProduct.*, and the Kind suite
runtime/ci.sh test-api-product.
- Every gateway route is
- Organization export and import (docs/org-export.md). An owner packs
the organization into one versioned file and imports it into another organization, another
RunR installation (self-hosted to hosted, one site to another) or the same one after a
disaster.- The package is a tar compressed with zstd. Its first entry,
manifest.json, records the
schema version, the RunR version and a SHA-256 for every entry. The importer reads its own
schema version and older ones, and refuses a newer one with a clear message. - Always in the package: projects with every version and its code, service configs (every
column), env vars, custom domains, gateways and routes (with their authentication: a key or
JWT route is imported as one, never public), workflow definitions, binding
attachments, observability settings, uptime checks, project access, runtime pins and a
deployment history summary; custom roles, groups, the default role, alert channels and rules,
the status page. - Optional: members (e-mails and roles for re-invitation), the prebuilt images of the
deployed and latest versions, and binding data: Postgrespg_dump, KV keys, object storage
objects. The export shows a size estimate first. - Run history (optional,
include_history): every deployment, the workflow runs RunR keeps
with their steps, the stored trigger events, the usage records and — with the Billing module —
the organization's bills. The import lands them as history that never acts: deployments in a
final state marked imported and never metered, workflow runs in a final state the engine
leaves alone, trigger events that are only delivered when replayed, usage and bills read-only
(GET /api/org-transfers/{id}/history), never charged or billed again. What the target's
retention would prune is left out and shown in the plan
(Run history). - Secrets are excluded by default. When included they are sealed with a passphrase
(Argon2id + XChaCha20-Poly1305) and never written in clear. - Destinations: a streamed download (resumable with
Range), an object binding of the
organization, or an external S3 bucket whose credentials are used for that one export only. - Import:
- A resumable chunked upload — or RunR reads the package straight from an object binding of
the organization (a transfer pod in its region) or an S3 bucket (ranged reads with
If-Match), resumable after the bytes it has and checked by SHA-256; S3 credentials stay in
the encrypted job only. Then a dry-run plan: region mapping, slug and binding-name
collisions with suggested renames, capacity / quota / residency checks, missing runtimes,
and the manual steps (DNS of custom domains, secrets, alert channels, git links, members). - KV data needs a Redis of the same or a newer version: each KV dump records its Redis
version, and the plan refuses a target region whose KV Redis is older (or does not tell its
version), naming both; one binding's data can be left out (skip_binding_data). - Apply creates everything once per entry, so applying again after a failure resumes it. It
can also restore binding data, push images into the target registry, invite members and
deploy.
- A resumable chunked upload — or RunR reads the package straight from an object binding of
- Permissions and limits: owners only (new
org.transferpermission for tokens and custom
roles). Both sides are audited. One export and one import run per organization at a time. - Clients and tools:
/api/org-transfers, the MCP toolsorg_export_start,
org_export_status,org_import_start(from an object binding),org_import_planand
org_import_apply,runr org export|import|transfers|transfer(import --from binding:NAME|s3://bucket/key,--resume,--skip-data,--no-history,export --history,
transfer history), and Settings → Export / Import in the console (upload or a bucket as the
source, live progress, plan and report). - Migrations: central
2026_10_22_520001(org_transfers.import_source) and
2026_10_22_520002/ tenant0107(deployments.imported_at,imported_history). - Region side: a new
transfermode ofruntime/image-upload(image 1.1.0) serves dumps
and images through the Kubernetes API server and receives restores, so the region needs no
route to RunR. Itspush-when-readycommand pushes an imported image. - API image: now includes
zstd. - Kind e2e:
ci.sh test-org-transfer. The tenant-to-tenant case runs intest:e2e-tenancy.
- The package is a tar compressed with zstd. Its first entry,
- Queue consumers can be charged, per region and organization
(Queue consumers). A queue trigger's consumer (50m CPU,
64 MiB, running as long as the project is deployed) is now metered as a usage record of its own.
It is charged only where the region charges queue consumers (charge_queue_consumers, set by
the region's owner), the platform has not switched it off for the tenant on a platform region
(PUT /api/billing/regions/{region}/organizations/{tenant}, which the platform's bill to the
tenant follows too), and the organization has not switched it off for itself in that region
(PUT /api/billing/regions/{region}/queue-consumers). Then it is a line of its own on a
pay-as-you-go bill, and in a fixed region it counts against the project's allocation (until
now the allocation always held it). Off by default: the upgrade raises no bill. The console
sets both in Region settings → Billing and Settings → Billing → Regions & prices. Tenant
migration0074.- Queue consumer choices go with their tenant and region (Billing module,
Queue consumers). A deleted tenant left the platform's choices
for it inbilling_tenant_region_overrides; they are now deleted with the tenant's other central
rows. The end of a region's share (detach) removes the platform's choice for that tenant and the
tenant's own choice for the region (billing_region_overrides), and a deleted region takes every
choice made for it along, centrally and in the tenants' copies.
- Queue consumer choices go with their tenant and region (Billing module,
- Terraform provider: every setting a project or an organization declares
(docs/terraform.md). Each new attribute is optional and keeps the server's
value when it is left out, so a configuration and state of provider 0.5.10 plan no change (a
test applies one, strips its state to the 0.5.10 schema and plans; another runs the 0.5.10
binary itself). What is left out of Terraform are actions and reports, listed with the CLI or
API way in What the provider does not manage.runr_project:triggerssets the whole trigger list — HTTP, schedules, the queue,
workflows, webhook inboxes, topics, platform events and the event sources kafka, nats,
rabbitmq, mqtt, cdc, email and s3 — with each trigger's JSON config and write-only secrets
(urlof a webhook's inbox computed);network(access mode, IP allow list, internal callers
by internal name),deploy_strategy(blue-green and canary with their options) andglobal
(regions, mode, failover; a regional project that gets it is replaced).trigger.typetakes
workflow.- Environments:
runr_environment(branch, approval, region, overrides, timed warmer,
deploy strategy;name = "production"adopts the project's own approval),
runr_environment_secret, andenvironmentonrunr_deployment,runr_binding_attachment,
runr_domain,runr_gateway_routeand the newrunr_private_connection.runr_binding_attachment.credentials
switches a Postgres attachment to dynamic credentials. - API product:
runr_gateway(rate limit, developer page, custom domain),runr_gateway_route
(public, API key or JWT authentication, gateway-only),runr_api_plan,runr_api_consumer,
runr_api_key(the key in the create answer; destroy revokes it). - Secrets, billing, workflows:
runr_secret_storeand therunr_secret_store_trustdata
source,runr_env_var.reference(a variable that refers to a value in a secret store),
runr_cost_limit(resource and data source),runr_workflow(a workflow declared through the
API, paused or active) andrunr_workflow_app(the engine's endpoint override),runr_topic. - Workflow definitions keep their source: one definition is declared or archived on its own
with the newPUT/DELETE /api/projects/{project}/workflows/{name}(workflows.manage,
auditworkflow.declare/workflow.archive);runr_workflowandrunr_workflow_triggeruse
them, so the project's other definitions are never sent and what the function's SDK reported
stayssdk(against an earlier API they declare the set as before). Declaring the set
(PUT …/workflows,runr workflows declare) keeps the source of a definition whose content
does not change; only new and changed definitions becomeapiuntil the next sync. - Reports as data sources:
runr_runtime_patchesandrunr_rightsizing;data.runr_project
reads the trigger list, the network, the deploy strategy and the placement. - Client and fake API: the CLI client creates, changes and deletes secret stores, reads and
sets cost limits, changes plans, keys, gateway rate limits and routes, reads and changes a topic
and sends trigger secrets with a whole list; the fake reproduces the API for each of them
(trigger configs normalized per type, the event sources' required settings and secrets, the
stored list kept in step withconfig.trigger_type, a region's default TLS mode for new
domains).
Changed
- Terraform:
runr_domain.tls_modeleft out is RunR's choice. A new domain withouttls_mode
starts in the mode the API gives it —cert-managerwhere the region issues certificates — and
keeps the mode it has; the provider no longer forcesnone. A domain that provider 0.5 created
withnonekeeps it, and no change is planned. A configured mode other than the region's default
is now set at create (provider 0.5 compared the modes after reading the API's answer and failed
with "inconsistent result after apply"); provider 0.5 against this API therefore cannot create a
domain in a region that issues certificates unlesstls_mode = "cert-manager": upgrade the
provider first. - The API document types a region.
KnativeRegionResource(GET /api/knative-regions, the
region in the answers of the region endpoints) is an object with its fields and their types
instead ofstring: the fields every region has, and — for a region of the request's own
context, not a tenant's synced copy of a platform region — the rest. The answers carry the
same fields as before;knative_settingsis{}instead of[]when a region has no settings. runr workflows app signing-key --envis now--dotenv(prints the key as an env file line):
--envnames the environment in everyrunr workflows
command. Scripts that wrote--env >> .envwrite--dotenv >> .env.- Deployments, versions and projects record the runtime image digest they run
(runtime_image,runtime_digest,runtime_patch_id; migrations of the central and every
tenant schema). Without a known patch — the feed off, a runtime version it does not list, region
builds, uploaded images, custom runtimes, CaaS — workloads run the version image by its tag as
before.runr projects getshows the runtime patch state of a project (Runtime patch), and
runtimes_listthe newest patch of each runtime version.
- Sign in with the user name or the e-mail address. The console's sign-in field is
E-mail or user name (E-posta veya kullanıcı adı) and takes either, so the administrator an
install created signs in asadmin(admin.username,ADMIN_USERNAME) without knowing the
e-mail address typed at install time.POST /api/auth/login(login) and
runr login --with-password --usernametake either as well, in any letter case
(security.md).- An identifier with
@is an e-mail address first, then a user name; without@it is a user
name. One identifier names one account at most: of accounts that differ in letter case only,
the exactly spelled one signs in. - The sign-in rate limit counts per client address and account, so switching between the user
name and the e-mail address no longer starts a new budget. - Registration, the user management and the profile refuse a user name or an e-mail address
that differs from another account's in letter case only. User names keep their characters
(letters, digits,-,_; never@), and the first-administrator bootstrap creates no
account whoseADMIN_USERNAMEcontains@. Existing accounts sign in as before. php artisan runr:horizon-link --userandtenancy:setup --adminfind the account a user
name or e-mail address names the same way: one account, in any letter case.- A migration adds indexes on
lower(username)andlower(email)of the users table.
- An identifier with
- The console's navigation is grouped into a main menu with mega menus. The header no
longer hides the administration pages in the avatar menu and a settings page with ~25 tabs.- Projects is the first main-menu item, on its own. Build & Run (Sandboxes, Sandbox
settings, Models, Runtimes, Images, Events), Data & Integrations (Data, Git providers,
AI agents (MCP)), Observability (Alerts, Tracing, Status page) and Administration
(Members: Users, Roles, Groups · Access & security: API Tokens, Auth Providers, Audit log ·
Organization: Regions & domain, Organization Branding, Billing · Platform: Regions &
settings, Tenants, Resource Usage, Branding, Signup) open mega menus of labelled sections,
each link with an icon and a one-line description. - The header keeps the tenant switcher, the notifications and the user menu: Account
settings (formerly User Settings), My bills, Support Tickets, the theme, the language, the
tutorial and sign-out. The Models and Sandboxes header links and the theme, language and
tutorial buttons moved into the menus. - The menus follow the user's permissions: a link the user may not open is left out, and a
section or group left empty disappears. - Keyboard: ←/→ move along the main menu, ↓/↑ open a mega menu and move in it, Esc closes it;
the menus carryaria-expanded/aria-controlsand mark the current page. - Below 1024 px wide the menus collapse into a drawer opened with the menu button, the groups
as an accordion. - The settings page shows its menu group as the title and the pages of the same section as
tabs instead of every tab at once. Its URLs did not change (/settings?tab=<key>). - Console modules place their pages in the menus with
group,sectionanddescKeyon
settingsTabsor the newnavLinksregistry list
(Architecture); the docs name the new locations. - The main menu fits a 1024 px window in every language. In Turkish, with the tenant switcher
in the header, the group labels of the new main menu ran into the header's right side. When the
labels do not fit the free width, the groups now show their icons (the label stays the button's
name for screen readers and its tooltip); with room, the labels come back. - Console hints name the new menu locations. Hints that still sent users to "Settings → …"
(adding a Git provider, a secret store or a topic, uploading an image, installing tracing,
enabling a runtime, revoking an MCP token, the queue consumer setting of a shared region, a
region without sandbox runtimes) now name the main menu's path, in English and Turkish.
- Projects is the first main-menu item, on its own. Build & Run (Sandboxes, Sandbox
- The queue consumer is the project's event gateway (
runtime/queue-consumer, same image
ghcr.io/brf-tech/runr/queue-consumer, same Deployment<slug>-qc). One delivery core —
CloudEvents over HTTP, retries with backoff, dead letters, per-trigger concurrency, Prometheus
metrics on/metrics— with pluggable sources: Redis Streams (queues, topics), the API's inbox
(webhooks, platform events) and schedules. A project whose only gateway trigger is one queue
keeps exactly the manifests of earlier versions; other gateways readgateway.jsonfrom the
Secret<slug>-qc-config.- A schedule next to other triggers is delivered by the event gateway as a CloudEvent
(dev.runr.schedule.tick) to the project's service instead of running a CronJob pod. A project
whose only trigger is a schedule keeps its CronJob. - The Knative Service of a project without an HTTP (or workflow) trigger is cluster-local, as for
queue projects before. Custom domains, previews and request replay need an enabled HTTP trigger. - The event gateway's legacy env mode keeps the queue consumer's guarantees
(triggers.md). The dead-letter write and the acknowledgement
of a queue entry are oneMULTI/EXECagain (also for queue and topic triggers in
gateway.json), so a gateway that stops in between no longer copies an entry to the dead letter
twice; withoutSINK_URLit delivers tohttp://localhost:8080as the queue consumer did instead
of not starting.CLAIM_MIN_IDLE(30s) sets when another consumer's pending entry is claimed.
- A schedule next to other triggers is delivered by the event gateway as a CloudEvent
- Preview environments are billed. The region agent now tracks the pods of preview
environments (runr/preview-of), and metering counts them like the project's own scale-to-zero
sessions, at the project's size. They appear on the project's bill as a line of their own
(component: preview); in a fixed region the allocation covers them, as it already held them.
Until now previews were neither metered nor billed. Tenant migration0073lets a session belong
to a preview instead of a deployment. An agent takes this up when its pod restarts with the new
image.- Previews of projects with long slugs are metered. A preview's Knative Service
<slug>-pr-<number>keeps to 63 characters, so the slug of a project from an earlier release
(longer than today's 44) lost its end — in a tenant, the tenant's slug with it — and RunR, which
matched the region agent's session events by slug, found no project for them: the preview was
not metered or billed. The agent now sends the pod's project id and organization (labels
runr/preview-of,runr/project-idandrunr/tenant, which preview pods now carry) with every
session event, and RunR finds the project by its id; previews deployed before carry no
runr/tenantand are found by their project id among the tenants (remembered for an hour).
region:reconcile's internal-network step gives such a preview its project's internal name
instead of one parsed from the shortened Service name. A new preview of a tenant's long slug
keeps the slug's-<organization>end in its name (the tenant's own domain alias answers only
names ending in it), and a preview keeps the name it was created with.
- Previews of projects with long slugs are metered. A preview's Knative Service
- Kata hypervisors the region's nodes cannot run are not offered. RunR's node check (the
kata-kvm-probeDaemonSet) now also records each node's/dev/shmsize and whether its systemd
has the D-Bus system bus (shm_mb=anddbus=in therunr.dev/kvm-probeannotation). QEMU
backs its guest's memory with a file in/dev/shmand needs at least 1 GiB there: a node that
runs in a container (Kind, k3d) has Docker's 64 MiB, where QEMU dies of SIGBUS while the guest
boots. Dragonball (Kata's Rust runtime) needs the D-Bus system bus on nodes that run systemd;
Kind's node image has none. Where every Kata node with KVM lacks it, the hypervisor issmall_shm
orno_dbus: the console's hypervisor selector says why, and the install API refuses it and lists
it underunavailablein its 422, so the other hypervisors can still be installed. Nodes checked
by an earlier release, without the size, keep QEMU offered
(/dev/shm (QEMU),
D-Bus (Dragonball)).
Security
- The API image is built on PHP 8.3.36, which fixes two remote code execution flaws in PHP
(CVE-2026-109618, CVE-2026-109619), and its build fails on an older PHP. The API also turns off
PHP's session upload progress tracking, turns onsession.use_strict_modeand no longer sends
X-Powered-By. The opcache JIT is off (opcache stays on): under the image's thread-safe PHP
8.3.36 the tracing JIT crashed API workers (upgrade.md). - The PHP runtime images run PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12 under their tags
(exec-php8.2 to 8.5). A PHP function gets the fixed release with its next deploy. - Cluster-local services were reachable from outside the cluster. The region Traefik served the
cluster-local hosts of every Knative Service (<service>.<namespace>,.svc,
.svc.cluster.local) on the entry point its LoadBalancer publishes, so a request to the region's
external address with such aHostheader reached queue-trigger sinks and model services. They
are now served only on a private entry point (internal, published by the ClusterIP Service
traefik-internal); through the external address they answer404, and Knative's placeholder
Services point in-cluster callers attraefik-internal.- Custom domains, gateway routes and the second address of a base-domain migration hand requests
on to the project's public host instead of its cluster-local one, so they reach public HTTP
projects only. The status check keeps their host on the service's URL when its domain changes.
Gateway routes are served again: they pointed at the Knative Service'sExternalName
placeholder, which Traefik does not route to. - NetworkPolicies in each region admit traffic to revision pods only from Knative Serving's
Traefik, activator and autoscaler, and to the activator only from Traefik and the autoscaler.
Before, any pod of the cluster could call another project's pods directly (<revision>-private,
the pod address) or through the activator, past the route and its visibility. They need a
network plugin that enforces NetworkPolicies
(regions.md). - The web shell's wake-up and keep-alive and the model playground call the service's cluster-local
host throughtraefik-internalinstead of the activator or the revision's pods. region:reconcile(after every upgrade) moves existing regions: stepsprivate-ingressand
network-policy. Traefik is upgraded twice — cluster-local hosts on both entry points while
the KIngresses are made to nametraefik-internal, then on the private one only — and its pods
roll twice. On Kind the move took 154 s without a failed request or queue delivery
(runtime/ci.sh test-network).
- Custom domains, gateway routes and the second address of a base-domain migration hand requests
- An unknown account answers a password sign-in after the same work as a wrong password, so
the answer time does not tell which user names and e-mail addresses exist. Both get the same
401 message. - Pass-through: RunR's database no longer stores the values of project secret variables. A
value goes straight into the Kubernetes Secret<slug>-secret-envof the project's region,
written key by key, and lives there only; RunR keeps the name, a version, who changed it when and
a keyed fingerprint (HMAC-SHA256 withRUNR_SECRET_FINGERPRINT_KEYor a key derived from
APP_KEY, never a plain hash). Reading a value back reads the region and says whether it is
still the value RunR wrote (matches_fingerprint); a region that does not answer fails the
request (503) and changes nothing. A deploy fails with the names of values that are not in the
region; stopping a project keeps them, deleting it removes them; a region migration copies them
to the new cluster and a rollback back, in memory; moving a project to another region copies
them; previews read the project's Secret instead of a copy of it. RunR's own keys
(RUNR_WORKFLOW_SIGNING_KEY,RUNR_MCP_GATEWAY_KEY) stay encrypted in its database — RunR signs
with them — and deploys write them next to the values
(security.md). The Kind suiteruntime/ci.sh test-region-move
checks the copies both ways: values in the region's Secret and in its OpenBao, values set while
the region ran on the new cluster, references, and what the rollback leaves on the new cluster.php artisan secrets:move-to-regionmoves the values earlier releases stored into their
regions: written, read back and compared by fingerprint, then cleared from the database; per
tenant schema, idempotent and resumable, audited assecrets.moved_to_region. The Helm chart's
bootstrap Job runs it after every upgrade.- Downgrade guard for pass-through (Rolling back to 0.5.10).
An installation upgraded from 0.5.10 keeps an encrypted copy of each value in
project_secrets.value(rollback_copy, shown by the API) until the upgrade is finalized, so a
rollback to 0.5.10 deploys the values instead of empty strings; RunR itself keeps reading the
region. A copy that 0.5.10 changed after a rollback is recognised by its fingerprint and moved
again as a new version (also into OpenBao).secrets:move-to-region --finalize(Helm value
projectSecrets.finalize) checks every value in its region, restores one the region lost from
its copy, drops the copies and recordssecrets.pass_through_finalized; until then references
to secret stores are refused, and an organization import lists them as a manual step. The Helm
notes of an upgrade say how to finalize. Migration2026_10_22_500001(tenant schemas:0105).
Fixed
- Long jobs run on their own queue. An organization export or import (4 h by default) and the
copy of an uploaded image into another region of a global project (1 h) ran on the default
queue, whoseretry_after(1900 s) handed a job still running to a second worker that failed
it. They now run on theredis-longconnection (queuelong, Horizon'ssupervisor-long),
whose limits followRUNR_ORG_TRANSFER_JOB_TIMEOUTandRUNR_GLOBAL_IMAGE_COPY_TIMEOUT; the
copy's timeout isRUNR_GLOBAL_IMAGE_COPY_TIMEOUTitself. - A region move routes the custom domains on the new cluster. The move deployed the projects
there but applied only the domains that were waiting to be routed: an active custom domain had
no route on the new cluster and stopped answering once DNS pointed there. Every active custom
domain of the region's projects is now applied on the new cluster (an uploaded certificate is
copied from the old one), and on the restored cluster after a rollback
(Regions). - Gateways of upgraded regions get the current routes without a deploy. Their IngressRoutes
were rebuilt only on the gateway's next sync (a deploy or a route change);region:reconcile
now rebuilds every gateway whose routes an earlier release built — the Host rewrite per route,
the forwardAuth Middlewares and the direct routes of allowlist and gateway-only projects. - Custom domains, domain aliases and gateways follow a domain migration at once. When a
base-domain or organization-domain migration completed or rolled back, custom domains and gateway
routes followed the new hosts only on the next status check (up to a minute of 404). They now
point at the new hosts before Knative switches over (the migration's alias answers there until
then), an organization's alias that sent to the old domain sends to the new one, the region's
gateways are rebuilt on the new domain, and the status check no longer moves a rewrite back to a
domain the region stopped serving while Knative catches up. - A gateway route with several methods is served. Its rule named them in one
Method()
matcher, which the region Traefik's v3 rule syntax refuses; each method has its own matcher now. - A scale-to-zero project keeps its maximum scale (#627). The console's Zero Scale mode
always sentmax_scale: 1, so such a project could never run more than one container under
load. It now has its own maximum (1-1000). The API checks thatmin_scaleis not above
max_scaleon create, update and deploy, instead of Knative refusing the revision. - Gateways use their custom domain and the region's function domain (#628). The gateway's
IngressRoute was built from the base domain, while the project URL came from the region's
function domain: on a region with its own function domain the two disagreed. Both now use the
function domain. A gateway'scustom_domainwas stored but never routed. It now gets its own
IngressRoute (gw-<id>-domain) with the gateway's routes and TLS as a project custom domain: a
cert-manager certificate from the region's ClusterIssuer, else the region's default certificate. - A rollback restores the configuration with the code (#626). Deployments record the effective
configuration they were applied with (config_snapshot); a rollback
(POST /projects/{project}/deployments/{deployment}/rollback, the console's Roll back,
runr rollback, MCProllback) deploys the older version with the scale, env vars, resources,
trigger and timeout it ran with, unless keep the current configuration is chosen. A CaaS
project's image is its version's code: a deploy of an older version now runs that version's
image instead of the one configured now. Secret variables and bindings stay the current ones
(Rollback). - Scale-to-zero projects were metered with no CPU or memory. Metering counted a session's
CPU and memory from figures the region agent never sends, so pay-as-you-go bills of
scale-to-zero projects showed 0 vCPU-hours and 0 GB-hours for them (GPU time was counted). A
session now counts the size its deployment was deployed with for the time its pod ran — CPU,
memory and GPUs alike, the way always-on projects count their reserved size
(Metering and the fixed bill). This needs nothing
from the agent, so the sessions of agents that are not updated yet are metered the same way.- Runs of a schedule trigger are metered. The agent did not report the pods of a project's
CronJob, so scheduled runs were not metered at all. It now reports each run as a session, from
its pod's start until the run finishes (not when the finished pod is removed later). An agent
takes this up when its pod restarts and pulls the new image
(kubectl -n observability rollout restart deployment/runr-agenton the region's cluster). - Sessions of a queue trigger are metered under
queue, with the queue multiplier. The
agent reports every Knative pod as HTTP; RunR now files a session under the trigger its
deployment was deployed with. - Long-running pods are metered for the whole time they run. A session counted on the day
it started, and every session open for 30 minutes was closed as stale. Its real end corrected
it later, but the metering job only recalculates today and yesterday, so a busy service's
pod that ran past the next day lost the rest of its time, and time past a month's end could
belong to a bill that was already closed. Each day now counts the part of a session that ran
on it, and a session still running counts until now. - Sessions are closed by the agent's heartbeat. The agent reports the sessions it still
sees running every minute. A session is closed as stale only when these reports stop for 10
minutes (the agent or the region is gone), as of when its pod was last seen. A heartbeat
reopens a session closed too early and starts the session of a pod whose start was lost
while the agent was disconnected. Sessions of agents that send no heartbeat keep the
30-minute close. Tenant schemas getdeployment_sessions.last_seen_atwith tenant migration
0072. - The metrics summary reports what the sessions used.
GET /api/projects/{id}/metrics/summaryansweredsessions.total_cpu_msand
total_memory_mb_msfrom figures nothing records, always 0; they are now the deployment's
size × the sessions' pod time, as metering counts them.runr metricsshows the CPU as
vCPU time. - Days metered before the upgrade keep their values; today's usage (and yesterday's, in the first
hour of a day) is recalculated by the metering job as usual.php artisan usage:recalculate FROM [TO]recalculates earlier days of existing deployments: open bills read the new values,
closed bills do not change. - Metrics name their units, and the sessions' size × time says what it is. Since the summary
reports the sessions' CPU and memory as the deployment's size × their pod time, the console's
metrics history shows the averages in cores, MiB and requests per second and the sessions'
vCPU-hours and GiB-hours at the deployment's size; memory reads MiB / GiB (it said MB / GB for
the same numbers), and the live bar's last-hour averages are cores and MiB, no longer "per hour".
runr metricsprints the sessions' size × time on a line of its own (vCPU time and MiB- or
GiB-hours of memory) andrunr metrics historyheads its CPU column "CPU CORES". The MCP tool
metrics_summaryaddsunitsand a summary line in those units.
- Runs of a schedule trigger are metered. The agent did not report the pods of a project's
- A change to a project that is deleted meanwhile answers 404, not 500. A config change whose
project another request deleted while it ran (an e2e cleanup did, while the console's save was on
its way) failed with a server error, "Call to a member function update() on null": the config
row had gone with the project. A project's config change (PUT /projects/{project}, also a
rightsizing apply), deploy strategy and network change now answer 404 as for an unknown project,
and so does any other request whose project is gone by the time it fails — such a request is not
reported as an error. - A refused preview says why in the commit status. A preview that was not allowed to start
(the fixed allocation or the organization quota leaves no room, the preview limit, forks off, …)
reported "Replaced by a newer deploy"; it now reports "Preview #12 not deployed: ". - New MCP server: the console showed "A project with this name already exists" before it opened
the new server. The API broadcastsproject.createdas soon as the project exists, while the
modal still saves the starter files and the gateway settings. The projects list behind the modal
got the project, and the modal's name check counted it as taken. The check now leaves out the
project the modal created. A setup step that fails now stays in the modal: the project is
listed, and Continue the setup finishes that project instead of creating a second one. The
modal cannot be closed while the setup runs, and it no longer navigates when the page was left
meanwhile. - Late and out-of-order answers in the console. Pages that load again when a filter, a page,
a tab or a route parameter changes now show the answer of the newest request. Polls stop when the
page is left. An action's answer is not undone by a poll that was sent before it. Specifically:- Project pages:
- Opening another project from a project page (a clone, a notification, a link) now loads that
project. Before, the page kept showing the old project. Leaving with unsaved code asks first
here too, and the clone's permissions are loaded. - The editor shows the files of the version clicked last. Before, a slower answer for an older
version could fill the editor of the latest one, and saving it reverted the code. - Code typed while a save runs stays as unsaved changes.
- A deploy whose follow-up reload fails closes the deploy modal; a deployments list read before
the deploy no longer hides the new deployment. - The MCP tab and its call log, the history chart range and the starter preview show what was
picked last.
- Opening another project from a project page (a clone, a notification, a link) now loads that
- Polls that kept running after their page was left:
- the deployment log panel, the shell terminal (socket and sandbox keepalive);
- the workflow runs and run detail;
- the project security tab and the region supply-chain tab;
- the bindings and data services pages, the runtime builds and the tenant deletion list.
- Forms the background poll overwrote while being edited: git deploy settings, the GPU
settings of a region, the scaling of a model service, the sandbox runtime mapping, other rows
of the sandbox quotas and pools, branding texts after a logo upload, and the billing webhook URL. - Double clicks that sent an action twice: SSO provider toggle, platform admin toggle, tracing
install, supply-chain key generation and admission install, binding restore, demo data, billing
redelivery and markup removal, 2FA confirm (Enter) and recovery code regeneration, the runtime
enable toggle, notification mark-read, and the organization switcher. - An error shown after the action succeeded, when only the follow-up reload failed: tenant
create/delete, region quota assignment (a retry now edits the assignment), SSO group mappings,
project access, tracing removal, git disconnect. - Broadcasts during a reload:
- A deployment status, a new deployment or a finished build that is broadcast while the
project page reloads its lists (for example right after a deploy) is no longer lost. - The projects list keeps a project created, changed or deleted while it loads, also from
another tab or by someone else. - The models page's poll no longer undoes a deploy, stop or delete for a few seconds.
- Mirroring or deleting an offline copy no longer writes back the model as it was at the
click.
- A deployment status, a new deployment or a finished build that is broadcast while the
- Deleting a project from its page keeps the page until the projects list opens (the page
went blank in between). A project deleted elsewhere opens the projects list with a note. - Organization switch: the onboarding wizard loads the new organization's onboarding once;
it loaded twice and could show the first answer. - Install status: a job that finished before its install request answered (sandbox runtimes,
Wasm, Knative components, region demo data) no longer goes back to "installing". The region
setup wizard no longer misses a fast failure. - Shared state after an organization switch or sign-in:
- the effective permissions, the settings and the runtime catalog drop answers read before
the change; - the data residency of region pickers is read again for the new organization;
- notifications are no longer received twice after signing in.
- the effective permissions, the settings and the runtime catalog drop answers read before
- Search, filter and detail views (sandboxes, files, metrics, bills, events, audit log,
users, tickets, alert deliveries, uploaded images, Hub models, groups, SSO buttons on the
sign-in page, model upload abort) show the newest answer. Ticket replies no longer vanish or
show twice.
- Project pages:
- Region setup wizard: a failed storage or registry test left the wizard waiting. The setup job
recorded the failure but broadcast nothing, so the wizard (which follows the setup by its events)
never showed the error. It now broadcastsregion.create_failedwith the setup's id, like every
other failure. - The registry setup gives the StorageClass time to bind its test volume. The PVC test waited a
fixed 60 s, and aWaitForFirstConsumerclass binds only once the consumer pod is scheduled, its
image pulled and the volume provisioned: on a busy cluster or slow storage the setup failed with
"did not provision a volume within 60s". It now waitsRUNR_REGISTRY_PVC_TEST_SECONDS(180 s by
default, 30 to 900), and a volume that does not bind is reported with the real wait and what the
test saw: the claim's phase, the consumer pod's phase and why it waits, and the claim's last
event (e.g.ProvisioningFailedwith its message). The registry setup jobs' timeout grows with
the wait;POST /registry-setup/test-pvcandrunr regions registry test pvcwait as long
(Regions). - Invitations and SSO name accounts by their e-mail address in any letter case (like the
password sign-in). Tenant invitations, their acceptance,
the member search, SSO sign-ins (OAuth, OIDC, SAML) with auto-registration, the organization
import's member grants andtenancy:create --owner-emailmatched addresses exactly, so
Ada@Example.comcould get a second account next toada@example.com. The address now names the
existing account; an invitation is stored, and a new account created, with the address trimmed
and in lower case. An address that several accounts of an earlier release share in different
letter case names none of them: the invitation or SSO sign-in is refused (email_ambiguous)
instead of creating a third. - A new account from an invitation gets a free user name. Accepting an invitation set the
user name to the part of the address before@without checking it, so a name another account
had failed with a database error, and characters such as.or+made a user name the
registration would refuse. It is now built from letters, digits,-and_of the local part,
free in any letter case and of waiting signups, with-2,-3, … when it is taken; SSO
auto-registration uses the same rule. - A sandbox the container runtime cannot start fails with the runtime's error. A pod whose
sandbox the runtime cannot create (a Kata VM that does not boot, a runtime handler the node lacks)
stays pending while the kubelet retries, and its status never says why: the create answered 202
pending, the sandbox failed as a plain start timeout up to 6 minutes later and counted towards
the organization's quota until then. The start now reads the pod's events every 5 s
(sandbox.start_events_seconds); after threeFailedCreatePodSandBoxevents, in about 30 s, the
sandbox fails with the runtime's latest error — the create answers 502start_failed— and no
longer counts towards the quota. A warm pool pod in that state is replaced with the same reason,
and a background start that times out names the pod's last warning event
(Sandbox API). - A Kata install on a region without gVisor no longer fails with 422. The
/dev/kvmcheck runs
before anything else of RunR's sandbox runtimes is on the cluster, and its DaemonSet could not be
applied while therunr-sandboxnamespace did not exist ("Cannot check the cluster for Kata");
the console's check failed the same way. The check creates the namespace now. A namespace RunR
created was also remembered by name for the whole process, so a queue worker that had created
runr-sandboxon one region's cluster skipped it on the next; it is remembered per cluster now.