Authenticated remote lifecycle
This runbook operates one Product Environment through runku-server using the session created by
runku login. It covers the complete source acceptance profile: publish, validate, promote,
invoke, inspect/follow logs, publish a replacement, and roll back. Application invocation still
uses rk_pub_* or rk_sec_*; operator lifecycle operations use rk_at_v1_* through the protected
CLI session file. The credentials are intentionally not interchangeable.
Prepare the Product Environment
The current source composition exposes one initialized Product Environment per server process. Prepare it once with its own persistent root and listener:
runku init --root /var/lib/runku/product --workspace local --listen 127.0.0.1:3210
runku dev --root /var/lib/runku/product --prepare
--prepare initializes repositories and Application Credentials but does not leave a development
server running. Protect the complete root as authoritative product state. Do not put it on an
ephemeral container filesystem.
Configure the Management server with PostgreSQL-backed Platform Identity and the Product root:
export RUNKU_IDENTITY_DATABASE_URL='postgres://runku_management:REDACTED@postgres.example/runku_identity'
export RUNKU_PLATFORM_IDENTITY_PEPPER='REDACTED_URL_SAFE_BASE64_32_BYTES'
export RUNKU_STATE_DIRECTORY='/var/lib/runku/platform'
export RUNKU_PRODUCT_ROOT='/var/lib/runku/product'
export RUNKU_MANAGEMENT_LISTEN='127.0.0.1:3220'
runku-server check
runku-server migrate
runku-server serve
The Product Gateway starts lazily after the first successful Channel promotion. The server keeps the Product process lease, refreshes serving catalogs, and stops the listener/background tasks when the server exits. A second process cannot own the same Product root.
Enroll and select an operator session
Bootstrap the first owner with the protected invitation written by the first server start, or enroll a delegated operator. With an external OIDC provider configured, use the browser flow:
runku login
The CLI offers a prior authentication origin when present, otherwise defaults to
https://api.runku.app, discovers the available methods, and displays the Management origin that
will be stored. For a self-hosted installation or automation, make the choice explicit:
RUNKU_INVITATION='rk_inv_v1_...' runku login \
--url https://runku.example.com \
--device release-laptop \
--browser \
--code-env RUNKU_INVITATION
unset RUNKU_INVITATION
Later logins for the linked external identity omit the invitation. --browser forces browser OIDC
but is not required for the interactive flow. Invitation-only installations can select the hidden
prompt or use --code-env for automation. The current CLI stores one active profile containing
separate authentication and Management origins. Use an absolute RUNKU_CONFIG_HOME when automation
must isolate profiles.
Every --remote project command obtains the server origin from that stored profile and the exact
Project/Environment from --root. It rejects a malformed/symlinked session file, retries once with
the rotating refresh token after a 401, safely replaces the file, and never falls back to an
Application or Development key.
Interactive runku link discovers /v1/auth/resources at the stored authentication origin, not at
the separately advertised Management origin. The v1 catalog accepts at most 1,024 entries and 1
MiB; selection is strictly one-based, so 0 and values beyond the displayed list fail closed.
When the local source directory does not itself contain the server's provisioned Product state, bind it only after login has proved access to the intended remote scope:
runku link --root /workspace/application \
--project-id prj_... --environment-id env_...
The command calls authenticated status first. Only a successful exact-scope response permits
local initialization and creation of the non-secret Management-origin descriptor. A 401, 403,
invalid response, or transport failure leaves a previously uninitialized directory unchanged.
The valid status of a newly provisioned Environment is revision zero with no Releases or Channels,
so first-link does not require a seed publish.
Once present, the descriptor makes every later --remote command reject a different Management
origin. This prevents login-profile substitution; server-side authorization is still reloaded on
every operation and the descriptor is never treated as a credential.
Publish and promote the first Release
Build locally and capture paths/IDs from JSON instead of guessing output names:
runku build --root /var/lib/runku/product >build.json
manifest_path="$(jq -r .manifestPath build.json)"
artifact_path="$(jq -r .artifactPath build.json)"
release_id="$(jq -r .releaseId build.json)"
Remote publication always requires an explicit Workspace HEAD precondition. Use empty only for
the first publication of that Workspace:
runku publish --remote --root /var/lib/runku/product \
--manifest "$manifest_path" \
--artifact "$artifact_path" \
--expected-head empty >publish.json
revision_id="$(jq -r .revisionId publish.json)"
runku release --remote --root /var/lib/runku/product \
--release "$release_id" >release.json
runku promote --remote --root /var/lib/runku/product \
--channel stable \
--release "$release_id" \
--expected empty >promote.json
runku status --remote --root /var/lib/runku/product
Publication sends one bounded canonical binary frame. The server validates Project binding,
manifest canonicality, artifact digest/format, Workspace CAS, and operator scope before moving the
Workspace pointer. Repeating the same package and original precondition returns the same revision
with replayed: true; it does not create a second Release. A different package with a stale head
returns exit 4.
Release validation and promotion are separate. A successful publication is not traffic. A
successful Release outcome must be servable; promotion then makes the target active and moves
the Channel through repository CAS. Serving refresh is asynchronous, so probes should retry for a
bounded interval until the reported Release ID matches the promoted binding.
Invoke through the Product listener using an Application key and an explicit target:
curl --fail-with-body \
--request POST \
--header 'Content-Type: application/json' \
--header "x-runku-key: $RUNKU_KEY" \
--data '{
"version": 1,
"target": "channel:stable",
"function": "version.current",
"arguments": {"type":"null"}
}' \
http://127.0.0.1:3210/v1/query
The rk_at_v1_* operator token is not accepted by the Product Gateway as an Application key.
Historical and realtime logs
Read a bounded historical page with exact filters:
runku logs --remote --root /var/lib/runku/product \
--release "$release_id" --limit 100
runku logs --remote --root /var/lib/runku/product \
--after logc_123 --request req_... --stream platform
Follow uses one long-lived NDJSON response rather than issuing a client HTTP request every 250 ms:
runku logs --remote --root /var/lib/runku/product \
--release "$release_id" --after logc_123 --follow
The server performs bounded repository reads behind that connection and reloads the current
operator session/grants before each page. It never accepts Project or Environment IDs from a log
record as authorization input. Revoking the device session, removing logs:follow, or disabling
the operator terminates the stream; a client cannot continue seeing new records with authority
that has been withdrawn. Snapshot reads require logs:read; streaming additionally requires
logs:follow.
Inspect immutable history coverage and apply retention through the same authenticated Product scope. Dry-run first; applying requires the exact Environment ID:
runku logs archive-status --remote --root /var/lib/runku/product
runku logs prune --remote --root /var/lib/runku/product \
--before-micros 1735689600000000 --maximum 1000
runku logs prune --remote --root /var/lib/runku/product \
--before-micros 1735689600000000 --maximum 1000 \
--apply --environment env_01...
Operational events include exact Project, Environment, Release, Function, request, invocation, Application Client, and credential attribution. They exclude keys, JWTs, arguments, results, and secret values. The Product profile keeps the hot tier in its dedicated SQLite database and archives immutable Parquet to the configured filesystem or S3-compatible store. DuckDB reads historical segments in-process; optional OTLP export remains independent. Raw logs are not written to Platform Identity PostgreSQL.
Promote a replacement and roll back
After changing source, build a new immutable package and use the observed previous revision:
runku build --root /var/lib/runku/product >build-v2.json
runku publish --remote --root /var/lib/runku/product \
--manifest "$(jq -r .manifestPath build-v2.json)" \
--artifact "$(jq -r .artifactPath build-v2.json)" \
--expected-head "$revision_id" >publish-v2.json
release_v2="$(jq -r .releaseId build-v2.json)"
runku release --remote --root /var/lib/runku/product \
--release "$release_v2" --against stable
runku promote --remote --root /var/lib/runku/product \
--channel stable --release "$release_v2" --expected "$release_id"
Rollback requires the exact current Channel binding and never bypasses compatibility:
runku rollback --remote --root /var/lib/runku/product \
--channel stable \
--expected "$release_v2" \
--to "$release_id"
Rollback changes future target resolution. Already pinned invocations/scheduled work remain on
their exact Release, and data/schema effects are not undone. If the expected Release is stale,
exit 4 means another operator changed the Channel; re-read status before taking further action.
Authorization and failure behavior
| Operation | Required capability | Exact scope |
|---|---|---|
| status | releases:read | URL Project/Environment |
| Function/schema catalogs | releases:read | URL Project/Environment plus exact code target |
| Data Admin get/query | data:read | URL Project/Environment plus exact code target/schema |
| Data Admin insert/replace/delete | data:write | same scope plus exact opn_* intent and OCC where applicable |
| publish and Release validation | releases:publish | URL Project/Environment |
| promote and rollback | channels:promote | URL Project/Environment |
| log snapshot | logs:read | URL Project/Environment |
| log follow | logs:follow | URL Project/Environment, rechecked during stream |
| archive status | logs:read | URL Project/Environment |
| hot-log retention | logs:prune | URL Project/Environment plus apply confirmation |
Authentication occurs before the Product adapter is called. A valid operator without the
capability receives 403; a malformed/expired/revoked session receives 401; a different
configured Product scope is not opened. Product errors are sanitized as invalid, not found,
conflict, operation-ID reuse, schema validation, unavailable, or corruption without leaking paths,
tokens, source, or stored values. Existing 0.4.5 grants do not acquire data:* implicitly; opt in
by updating the intended grant instead of widening environments:manage.
Managed control planes update authority through
PUT /v1/auth/managed/operators/{operatorId}/grants with the separately configured internal token.
Each body is a complete source-owned Project-role set and a monotonic sourceRevision; an empty set
is an intentional revocation. Reuse the exact revision only for an identical replay. Never attempt
rollback with a lower revision or use an operator/Application bearer for this endpoint. Once the
response reports applied: true, existing sessions are immediately constrained because ordinary
authorization and every log-follow iteration reload current grants.
Recovery rules:
401after the single refresh attempt: runrunku loginagain; do not replace it withrk_sec;403: request the minimum missing capability at the intended scope;- exit
4: fetch status/current Workspace state and reconcile CAS intent; - exit
5: verify server/storage health and retry exact idempotent bytes with bounded backoff; - exit
6: stop writes, preserve Product state, and follow corruption/restore procedures; - interrupted publish: repeat the same canonical package and precondition, then inspect
replayed; - interrupted promotion/rollback: read status before retrying.
Installation acceptance
Before admitting production traffic, exercise this lifecycle against the installed version and the intended OIDC provider:
- invitation bootstrap without an IdP;
- Authorization Code + PKCE, an incorrect password rejection, and invitation-bound enrollment;
- invitation replay rejection and linked-identity re-login;
- authenticated publish/replay/release/promote/invoke/log snapshot/log stream;
- authenticated archive inspection, retention dry-run/apply, and
logs:prunedenial; - a second Release, exact Channel CAS, and rollback behavior;
- missing authentication, insufficient capability, and cross-Environment denial;
- live log-stream termination after session revocation and recovery through OIDC re-login.
Record the exact server and CLI versions, timestamps, request/operation IDs, and redacted results. Keycloak can be used as a disposable standards fixture, but it does not qualify a different provider: provider selection and acceptance remain installation decisions.