Run artifacts
A run artifact is something a workflow run produced that a person can look at — a built web application or an Android APK — rendered inside the conversation that owns the mission, with a durable identity that outlives any single viewing session. The run builds the thing; Genesis serves it on our own infrastructure and streams pixels to the operator's browser, so the operator's clicks are real clicks on the running app while its code never reaches their machine.
Run artifacts are per-tenant: every tool and endpoint is gated by the tenant-admin policy, and
tenancy is implicit via the per-tenant database (no TenantId column — ADR-008), exactly like missions,
tasks and workflows. The record lives in the owning tenant's database, so an id minted for one tenant is
structurally invisible to another.
This page covers both surfaces:
- Operator / product — what an artifact is, the two kinds, live-vs-durable, the visibility model, and the isolation boundary as it actually is today.
- Architecture — the record, how a run's build becomes a servable target, the capture pipeline, the read-only/single-writer render session, and the tool surface.
Status (2026-08): staging-only, default-off. The whole surface is behind
Artifacts:Enabled(default off) and the interactive render session behindFeatures:OperatorBrowseInputLock(default off). The isolation caveat below is load-bearing — read it before enabling anything.
1. Operator guide
1.1 What an artifact is
| Concept | Meaning |
|---|---|
| Artifact | A durable record (stringId, 8 chars) of one viewable thing a run produced, scoped to (run, node) — one artifact per produced node. |
| Kind | WebApp (a served page, driven by the browser pool) or AndroidApp (an APK, rendered by the emulator pool). Modelled so a third kind adds no schema change. |
| Captures | The durable side: an ordered set of PNG stills plus a stable CaptureSetHash, and enough build provenance to respawn. This is what makes an artifact reopenable and shareable. |
| Session | The ephemeral side: a running pod pair (the served app + the pixel broker) with a TTL, spawned on demand from the run's build and reclaimed when it expires. |
| Visibility | Private (the producing tenant admin only, the birth default) · Tenant (any authorised member) · Link (an unauthenticated link token — captures only, never a live session). |
| Session state | A distinct, separately-rendered state: no session, live, session-expired, spawn-failed, build-unavailable, over-cap — never a blank frame. |
1.2 Live vs durable
A live render session cannot be kept forever, so the stable identity must survive it. Reopening an artifact after its session has died shows its captures, not an error, and offers to respawn if the build is still available. A link viewer only ever sees captures — never an input-capable session.
1.3 The interactive session
The authorised operator attaches interactive by default — real keyboard and mouse into the running app. Additional viewers of the same session attach read-only, and a read-only attach looks read-only: their input is dropped at the broker while pixels keep flowing. Input is single-writer — exactly one attached viewer holds the input lock at a time; handing it over is explicit, and two people never drive the same session simultaneously. The in-pod browser is pinned to the served app's origin: a navigation off that origin is blocked and shown to the operator as a refusal, never silently followed.
1.4 Making an artifact reachable by a link is a write
Producing an artifact is a run side-effect and needs no new approval. Enabling Link visibility is a
boundary crossing: it is write-classified, gated, and recorded (who enabled it and when). An automated
identity is refused — exactly as decision_respond refuses one on a governance change; only an
authenticated human may open a link.
1.5 The isolation boundary — read this
Within a tenant, a preview artifact (model-written code) and that tenant's client test environments share the render boundary. That is deliberate: a tenant is one customer's data domain.
The per-tenant boundary is NOT enforced at the network layer on the current substrate. The render pool is one cluster-shared instance, and both clusters run a CNI (Flannel) that ships no NetworkPolicy engine — so the network policies that exist are inert. Measured from inside the render pod, untrusted build output can reach the Genesis core API, Postgres, Keycloak, the git mirror, the MCP endpoint and another tenant's services; the app-layer auth (MCP/API tokens, DB credentials) still holds, but the network does not. The origin-pin stops an operator click from becoming an off-origin request; it does not stop the served app's own on-load fetches from reaching internal services.
This is why the feature is staging-only and default-off. It is bounded there by five properties —
no content (a structural clone, no client repos / real tenant data / real identities), a single
operator, flag-off, no credential path to prod, and no private network path to prod. The precondition
for any prod or multi-tenant use is a policy-capable CNI (Cilium/Calico) plus pool hardening
(authenticated noVNC ports, browser_run_spec/eval gated, no mounted service-account token,
per-tenant render namespaces). That gap is recorded as a compliance gap, not carried silently by the
feature flag.
If the app under test can reach anything stateful, an operator's clicks cause real writes — visible before they click, not discovered after.
2. Architecture
2.1 The record
RunArtifact (tenant DB, run_artifacts) carries: the artifact StringId; MissionId / RunId /
TaskDefId / NodeId provenance; Kind / Visibility / SessionState (string-persisted so a third
kind needs no migration); the link fields (LinkToken, expiry, enabled-by/at); the build reference
(BuildRef, branch, sha, tenant slug, environment id — enough to respawn); the capture set
(CaptureSetHash + an ordered CaptureManifestJson); and the current session handle when one is live.
Uniqueness is (RunId, NodeId) NULLS NOT DISTINCT — one artifact per produced node. EnvironmentId is
a plain string, not a foreign key: the environment record lives in the control-plane database.
The record is the missing producer for a consumer that was already shipped fail-closed:
RunTargetProvisioner.ResolveRunArtifactRef now does a true lookup of the build a run produced instead
of deriving one from an operator template.
2.2 From build to served target
There is no image build in Genesis core — the path is git-clone plus an ArgoCD-rendered
environment. A WebApp is served by a serve mode of the run-built-target chart: the run branch is
cloned into an ephemeral pod that builds and serves the app on a port; BuildRef is that in-cluster
service URL. The browser pool leases a session, navigates to it, and streams headed Chrome over noVNC —
the same brokering path the emulator/APK kind uses. The serve pod keeps a hardened posture (no mounted
service-account token, non-root, seccomp, all capabilities dropped).
2.3 Captures
Captures are pixels, not code. A still is persisted through the attachment service (base64 in
Postgres — there is no object store yet, so a "screencast" is a bounded sequence of stills, not video),
appended to the ordered manifest, and fingerprinted by a stable SHA-256 over the ordered attachment ids.
They are served through a dedicated endpoint that forces image/png — captured HTML/JS is never served
as an active document from a trusted origin.
2.4 The render session
The pixel broker is the operator-browse VNC proxy. For a read-only viewer, a stateful RFB
message-framing filter drops whole input messages (key/pointer/clipboard) while forwarding display
messages, and fails closed on anything it cannot frame. Who may write is a core-authoritative
input lock streamed to the broker; the broker caches the holder locally and treats itself as
read-only if the lock stream drops or goes stale (> 2 s). The enforcement is gated by
Features:OperatorBrowseInputLock (default off — the legacy verbatim proxy when off).
2.5 Tool surface
Read-only MCP: artifact_list (by mission or run), artifact_get (metadata + capture URLs), and
artifact_show (emit the render block into the conversation so the card renders inline). All are
tenant-admin, gated by Artifacts:Enabled, and born attributed. Link publication is deliberately not
an MCP tool — it is a human action behind the decision chokepoint.
2.6 What is not implemented
Serving artifact HTML to a human's own browser (a different, origin-isolation security model); editing, versioning or diffing artifacts; and video screencasts (deferred until an object store exists). The per-tenant network boundary (see §1.5) is the load-bearing prerequisite for prod.