Skip to main content

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

OperationRequired capabilityExact scope
statusreleases:readURL Project/Environment
Function/schema catalogsreleases:readURL Project/Environment plus exact code target
Data Admin get/querydata:readURL Project/Environment plus exact code target/schema
Data Admin insert/replace/deletedata:writesame scope plus exact opn_* intent and OCC where applicable
publish and Release validationreleases:publishURL Project/Environment
promote and rollbackchannels:promoteURL Project/Environment
log snapshotlogs:readURL Project/Environment
log followlogs:followURL Project/Environment, rechecked during stream
archive statuslogs:readURL Project/Environment
hot-log retentionlogs:pruneURL 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:

  • 401 after the single refresh attempt: run runku login again; do not replace it with rk_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:prune denial;
  • 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.