Optional IDE provisioning and named members

This optional extension supplies a JetBrains remote backend; it is not required for the CLI workflow in CLI-first arena workflow. Provisioning is a separate factory context, not an escape hatch from an execution arena. Plain CLion/PyCharm need neither AI Assistant nor Junie nor a vendor-egress exception. Ordinary managed arena.py create/add does not expose backend/seed flags.

The current lower-level tooling imports a backend, snapshots reviewed UI files and initializes runtime copies. Accepted policy keeps acquisition/download access separate from execution: the arena retains its inference-only external path, with no JetBrains/vendor Internet route. Real downloader, GUI, licensing and offline reconnect acceptance remain manual/unverified, not fixture-certified. See Known issues and assurance limits for broader runtime and containment limits.

Three-stage provisioning factory

        ---
config:
  flowchart:
    nodeSpacing: 18
    rankSpacing: 25

---
flowchart TD
    V["Vendor artifacts / offline inputs<br/>origin review required"]
    B["Separate reviewed backend<br/>pinned build/platform + manifest"]
    P["(1) Authorized scratch provisioning<br/>compatible dev image; no task/secrets"]
    R["(2) Human review / snapshot<br/>stopped writers; exact UI files + hashes"]
    S["Reusable UI-only seed<br/>pinned image/profile/build"]
    M["(3) Private untrusted member runtime<br/>RW copies; no JetBrains Internet route"]
    I["Existing broker / egress policy<br/>permitted inference export only"]
    V -->|"trusted acquisition + verification"| B
    V -.->|"conditional authorized provisioning access only"| P
    B -->|"backend mounted RO"| P
    P -->|"candidate settings; not automatically trusted"| R
    R -->|"sanitize + hash + snapshot"| S
    S -->|"copy once into new compatible runtime"| M
    B -->|"backend mounted RO; staged integration only"| M
    M -->|"permitted inference export only"| I
    classDef external fill:#eeeeee,stroke:#666666,color:#333333
    classDef guard fill:#e4effb,stroke:#35669a,color:#193d62
    classDef accepted fill:#e4f0e6,stroke:#477650,color:#25472b
    classDef untrusted fill:#fbe1dc,stroke:#a74432,color:#54251e
    class V external
    class B,P,R,I guard
    class S accepted
    class M untrusted
    

Figure 1. Provisioning → reviewed seed → private runtime: a conceptual lifecycle, not proof of automated factory or real IDE acceptance.

  1. Provision/acquire: the operator selects a compatible development image, independently reviewed backend and client materials. Default scratch provisioning is offline/internal. Any temporary download access needs separate authorization; --allow-downloads selects pasta, not a vendor-only allowlist. The operator must assess its reachable networks before starting it.

  2. Review/promote: stop host/container writers, inspect only selected UI preferences, remove sensitive content and snapshot the reviewed hashes. Backend and seed remain separate pinned inputs; a changed scratch runtime is not a seed.

  3. Initialize/use: trusted setup copies the seed into a new member’s private runtime and mounts the backend read-only. Reusing a seed means independent copies, never one shared writable tree. New copies require the exact recorded image ID/profile and matching backend product/build/platform. Initialized runtime is preserved rather than reseeded on supported replacement; persistence is not backup. Runtime edits never synchronize to the seed. A new seed requires a separate authorized provisioning/review/snapshot cycle.

Blue labels identify operator controls, green the accepted reusable input, gray external inputs and red the mutable untrusted runtime. Words and copy/RO/RW labels carry the same meaning without color. These are documented stages, not a new one-command factory or managed IDE-member deployment interface.

Select the reference variables explicitly: TOOLS is the absolute trusted tools/ai directory, ROOT the private management root, ARENA an existing arena name and EVIDENCE a new private host evidence directory outside mounts. These are optional provisioning inputs, not variables from the CLI recipe.

Layout and prerequisites

ROOT defaults to $HOME/.local/share/dev-environment when not overridden; keep it outside all task mounts. The following map describes setup.py’s current lower-level preparation, including scratch provisioning and staged IDE-enabled development members. It does not add IDE support to managed add.

ROOT/                              private host management root
  artifacts/jetbrains/ARTIFACT/
    backend/                       reviewed backend files
    metadata.json                  separate origin/integrity record
  seed/jetbrains/SEED_PROFILE/
    config/                        selected reviewed UI files
    cache/                         empty in accepted snapshot
    share/                         empty in accepted snapshot
    metadata.json                  exact identity/hash selection
  runtime/ENVIRONMENT/
    jetbrains/{config,cache,share}/ private mutable copies
    state/                         XDG state; NOT jetbrains/state
    agents/{config,cache,data,state,codex}/  separate agent state
    metadata.json                  initialization/selection record
  cache/ENVIRONMENT/ccache/         optional development compiler cache
  ssh/ENVIRONMENT/                  private identities/pins + RO guards
  quadlet/ENVIRONMENT.*             staged source units
PROVISION_SCRATCH/                  new empty scratch workspace OUTSIDE ROOT
IDE_SCRATCH/                        distinct scratch member workspace
        ---
config:
  flowchart:
    nodeSpacing: 20
    rankSpacing: 30
    subGraphTitleMargin:
      bottom: 45
      top: 8

---
flowchart TD
    subgraph HOST["Private host ROOT"]
        B["artifacts/jetbrains/ARTIFACT/backend<br/>operator-controlled reviewed input"]
        S["seed/jetbrains/SEED_PROFILE<br/>config + empty cache/share; not mounted"]
        R["runtime/ENVIRONMENT/jetbrains<br/>config / cache / share: private RW copies"]
        X["runtime/ENVIRONMENT/state + agents<br/>separate private mutable state"]
        G["ssh/ENVIRONMENT + cache/ENVIRONMENT<br/>RO guards / optional RW ccache"]
    end
    W["Separate selected workspace/data<br/>scratch only during provisioning"]
    subgraph MEMBER[" "]
        MB["/opt/jetbrains/backend<br/>RO backend"]
        MR["/home/ci/.config + .cache + .local/share<br/>RW private IDE state"]
        MX["/home/ci/.local/state + .local/ai-agent<br/>RW separate state"]
        MG["RO SSH/agent/cache guards<br/>RW optional compiler cache"]
        MW["/home/ci/workspace + selected data<br/>mutable task contents"]
    end
    S -->|"initial copy only; never shared RW"| R
    B -->|"RO bind"| MB
    R -->|"RW binds"| MR
    X -->|"RW binds"| MX
    G -->|"distinct RO/RW binds; see table"| MG
    W -->|"workspace RW; data RO/RW as approved"| MW
    classDef guard fill:#e4effb,stroke:#35669a,color:#193d62
    classDef external fill:#eeeeee,stroke:#666666,color:#333333
    classDef untrusted fill:#fbe1dc,stroke:#a74432,color:#54251e
    style HOST fill:#f8fafc,stroke:#87939e
    style MEMBER fill:#f8fafc,stroke:#87939e
    class B,S,G guard
    class R,X external
    class MB,MR,MX,MG,MW,W untrusted
    

Figure 2. Host inputs and private copies mapped into one untrusted member; arrows describe copy or bind access, not containment certification.

Current setup.py bind map (host paths relative to ROOT)

Host source

Member destination

Access / lifecycle

artifacts/jetbrains/ARTIFACT/backend

/opt/jetbrains/backend

RO; separate reviewed artifact

runtime/ENVIRONMENT/jetbrains/config

/home/ci/.config

RW; initial seed copy

runtime/ENVIRONMENT/jetbrains/cache

/home/ci/.cache

RW; initial empty seed copy

runtime/ENVIRONMENT/jetbrains/share

/home/ci/.local/share

RW; initial empty seed copy

runtime/ENVIRONMENT/state

/home/ci/.local/state

RW; newly created, not seeded

runtime/ENVIRONMENT/agents

/home/ci/.local/ai-agent

RW; separate agent state

cache/ENVIRONMENT/ccache

/var/cache/shyft-ccache

RW only when enabled; dev only, not provisioning

ssh/ENVIRONMENT/{ccache.conf,sshd_config}

/etc/ai-ccache.conf, /etc/ssh/ai_sshd_config

RO when cache policy selected

ssh/ENVIRONMENT/authorized_keys, ssh/ENVIRONMENT/host/ssh_host_ed25519_key

/run/ai-ssh/authorized_keys, /run/ai-ssh/ssh_host_ed25519_key

RO dedicated member SSH material

ssh/ENVIRONMENT/agent-tools

/opt/ai-agents

RO host-controlled launchers/guards

Selected workspace and optional data outside ROOT

/home/ci/workspace, /home/ci/data/{ro,rw}-INDEX

Workspace RW; selected data RO/RW; no data in provisioning

The agent launcher sets its XDG config/cache/data/state beneath /home/ci/.local/ai-agent/{config,cache,data,state} and CODEX_HOME beneath .../codex; these are not IDE seed contents. No provider credential belongs in IDE/agent runtime or reusable seed state. RO describes container bind access, not host filesystem immutability. Backend/seed identity manifests detect changes. Setup stages directories at mode 0700 and non-script files at 0600 (scripts 0755), under the deployment user’s ownership; keep-id uses inspected image ci IDs. Private SELinux mode uses Z for runtime/guards/workspace, but the reusable backend uses z. These labels do not authorize relabeling unrelated host trees.

Host shell means Bash on the Linux rootless Podman workstation, ordinary user, using trusted tools/ai outside execution mounts. Python 3.11+, jq, OpenSSH, working keep-id storage and a reachable user systemd/Quadlet are prerequisites. Run from a non-traced shell and stop at every failed precondition/command; do not copy later blocks over a partial attempt. Evidence directories must exist privately outside all execution mounts, with each selected output absent. The canonical root must be private/user-owned; symlink components, existing artifact/seed names, nonprivate ancestors and common-lock contention are refused, not overwritten. Do not chmod/relabel unrelated trees to bypass those checks.

Acquisition and origin evidence

On an authorized connected acquisition host, obtain and authenticate the downloader through the official offline guide. No real downloader was supplied during implementation. Inspect its actual help first:

: "${DOWNLOADER:?approved absolute path to authenticated supplied downloader}"
/bin/bash "$DOWNLOADER" --help

The guide documented -h as an alternative; use that only for the supplied version if needed. No download flags are prescribed here without actual help. Confirm bounded product/build/platform filters, output semantics and budgets before acquisition; missing filters can download everything. Select one exact supported product/build/backend platform (no latest, wildcard, version range or unapproved EAP) and matching client/JBR for each local client platform. Preserve products.json, authenticated vendor KEYS and independent verification evidence. A download’s own hash is not origin verification. PyCharm edition and remote-development support require separate confirmation.

Create ORIGIN_JSON outside the arena from actual independent verification, replacing every placeholder:

{
  "schema_version": 1,
  "source_url": "https://download.jetbrains.com/REPLACE-WITH-EXACT-ARCHIVE-PATH",
  "vendor_sha256": "REPLACE-WITH-64-LOWERCASE-HEX-FROM-INDEPENDENT-VENDOR-EVIDENCE",
  "verification_method": "vendor-checksum",
  "verification_reference": "REPLACE-WITH-CHECKSUM-URL-OR-VERIFIED-SIGNATURE-AND-KEY-REFERENCE",
  "verified_by": "REPLACE-WITH-OPERATOR",
  "verified_at": "REPLACE-WITH-VERIFICATION-TIMESTAMP"
}

verification_method also accepts vendor-signature. Import records operator attestation, checks official JetBrains HTTPS origin fields and checksum equality; it does not fetch vendor metadata or independently verify signatures.

Import and scratch-only provisioning

Assign these values from the reviewed backend archive, its metadata, the chosen image’s embedded provenance and the operator’s free-port/subnet inventory. All paths must be absolute. PROVISION_SCRATCH and IDE_SCRATCH must be distinct new paths outside ROOT and all existing workspace/data mounts. Use fresh product-specific artifact/seed/provisioning names. Do not mount personal home, real projects/data, provider secrets or credential stores into provisioning.

: "${TOOLS:?absolute trusted tools/ai path}" "${ROOT:?private canonical root}"
: "${UNITS:?absolute user Quadlet source directory}" "${EVIDENCE:?private evidence directory outside mounts}"
: "${IMAGE:?reviewed preloaded compatible image}" "${IMAGE_PROFILE:?matching embedded profile}"
: "${PRODUCT:?clion or pycharm}" "${BUILD:?exact reviewed backend build}"
: "${PLATFORM:?matching linux-x64 or linux-arm64}" "${PRODUCT_DIR:?actual product config directory, e.g. CLion2026.2}"
: "${ARTIFACT:?new product-build-platform artifact name}" "${SEED_PROFILE:?new reviewed UI seed name}"
: "${APPROVED_ARCHIVE:?authenticated backend tar archive}" "${VENDOR_SHA256:?independently verified vendor checksum}"
: "${ORIGIN_JSON:?reviewed origin attestation JSON}" "${SELINUX_MODE:?none or private; private required on SELinux hosts}"
: "${PROVISION_NAME:?new scratch-only provisioning identity}"
: "${PROVISION_PORT:?unused host-loopback port, e.g. 2209}"
: "${PROVISION_SUBNET:?unused checked internal IPv4 /24, e.g. 10.92.0.0/24}"
: "${PROVISION_SCRATCH:?new dedicated empty provisioning workspace}"
: "${IDE_SCRATCH:?different new dedicated empty IDE member workspace}"
test "$PROVISION_SCRATCH" != "$IDE_SCRATCH"
mkdir -m 700 "$PROVISION_SCRATCH" "$IDE_SCRATCH"
python3 -B "$TOOLS/ide/jetbrains/provision.py" import --root "$ROOT" \
  --name "$ARTIFACT" --product "$PRODUCT" --build "$BUILD" --platform "$PLATFORM" \
  --archive "$APPROVED_ARCHIVE" --sha256 "$VENDOR_SHA256" --origin "$ORIGIN_JSON"
BACKEND="$ROOT/artifacts/jetbrains/$ARTIFACT/backend"
jq '{product,build,platform,archive_sha256,origin_evidence}' "$ROOT/artifacts/jetbrains/$ARTIFACT/metadata.json"
python3 -B "$TOOLS/setup.py" prepare --root "$ROOT" --role provision \
  --name "$PROVISION_NAME" --port "$PROVISION_PORT" --image-profile "$IMAGE_PROFILE" \
  --image "$IMAGE" --workspace "$PROVISION_SCRATCH" --subnet "$PROVISION_SUBNET" \
  --backend "$BACKEND" --selinux "$SELINUX_MODE"

Import checks product-info.json product/build/Linux launch architecture and safe single-top-directory tar extraction. ZIP/DMG/installers, hardlinks, sparse or special files, duplicate/traversal paths and escaping/dangling/cyclic symlinks fail closed; internal relative JBR links are retained without writing through links. Default limits are 8 GiB compressed/summed member bytes and 200,000 entries; --max-bytes/--max-members are explicit operator overrides. Owners/modes are not preserved beyond executable status. Review the resulting manifest.

Preparation stages units, but starts nothing. Default provisioning has a checked dedicated internal network and host-loopback SSH, never the development/egress networks. Network=none cannot provide published SSH. If temporary downloads are explicitly authorized, a new provisioning name with --allow-downloads uses temporary pasta and stages no network unit; this is not runtime policy. Provisioning cannot autostart and supplies no project/data/provider-secret mounts.

For default offline provisioning only, inspect the generated sources and verify no user/system unit-name collisions. After explicit operator authorization, install only absent sources (the selected user source directory must exist):

(
  set -eu
  for suffix in network container; do
    test ! -e "$UNITS/$PROVISION_NAME.$suffix" && test ! -L "$UNITS/$PROVISION_NAME.$suffix"
  done
  set -C
  for suffix in network container; do
    cat "$ROOT/quadlet/$PROVISION_NAME.$suffix" > "$UNITS/$PROVISION_NAME.$suffix"
  done
)
systemctl --user daemon-reload
systemctl --user start "$PROVISION_NAME.service"
ssh -F "$ROOT/ssh/$PROVISION_NAME/config" "$PROVISION_NAME" true

Do not apply the two-source example to connected pasta provisioning; that branch has only its container source. Partial installation is not acceptance: retain evidence, stop and reconcile rather than blindly retrying over conflicts.

Client GUI: standalone Gateway/plain IDE on the same workstation selects generated pinned SSH configuration (127.0.0.1, selected provisioning port, ci), existing backend /opt/jetbrains/backend and scratch project /home/ci/workspace. Existing-backend GUI labels/switches are version-dependent; confirm them first. Verify backend/client/JBR build, indexing, compiler/Python, reconnect and shutdown. Keep host-side IDE credentials outside reusable state.

Licensing is a manual prerequisite. The current CLion registration and offline-work guide describe commercial offline activation codes obtained on an authorized connected machine. Non-commercial CLion cannot obtain such a code and requires initial online account activation; confirm the selected PyCharm requirements independently. License server/Vault access is an extra dependency, not baseline egress permission. Report GUI acceptance blocked if no approved offline-compatible licensing exists. Never put codes, cookies, license stores or account/password data in seeds/evidence.

Snapshot reviewed UI files only

Disconnect clients, close projects, stop every host/container writer and prevent restarts throughout review/copying. Review individual UTF-8 regular files in the provisioning runtime; sanitize them there, then record each actual SHA256 in REVIEWED_SELECTION_JSON. Never recursively select the profile. Example selection shape (replace directory/hash with the actual reviewed file):

{
  "schema_version": 1, "reviewed": true,
  "files": [
    {"path": "config/JetBrains/CLion2026.2/options/editor.xml",
     "sha256": "REPLACE-WITH-64-LOWERCASE-HEX-OF-REVIEWED-FILE"}
  ]
}

Allowlisted options are editor.xml, colors.scheme.xml, code.style.schemes.xml, keymap.xml, laf.xml, ui.lnf.xml and ide.general.xml; single keymaps/*.xml, colors/*.icls and codestyles/*.xml files are also supported in the selected product directory. Credential/path-history names and common content markers are rejected, but this is not a comprehensive secret detector. Human review remains mandatory. Limits: 1 MiB/file, 1,000 files/32 MiB total, no symlink ancestors; hashes include valid empty files. Build/platform are operator declarations: verify against backend metadata/GUI, not UI bytes alone.

Quadlet normally removes the container on stop. Create an unstarted network-none inspect record with the stopped runtime’s pinned image and exact mounts; it is not GUI evidence or a writer. Host shell:

systemctl --user stop "$PROVISION_NAME.service"
test "$(systemctl --user show "$PROVISION_NAME.service" -p ActiveState --value)" = inactive
RECORD="$PROVISION_NAME-seed-record"
PROVISION_IMAGE=$(jq -er .image_id "$ROOT/runtime/$PROVISION_NAME/metadata.json")
CI_UID=$(jq -er .ci_uid "$ROOT/runtime/$PROVISION_NAME/metadata.json")
CI_GID=$(jq -er .ci_gid "$ROOT/runtime/$PROVISION_NAME/metadata.json")
podman create --name "$RECORD" --pull=never --network=none --entrypoint=/bin/true \
  --userns="keep-id:uid=$CI_UID,gid=$CI_GID" --user "$CI_UID:$CI_GID" \
  -v "$ROOT/runtime/$PROVISION_NAME/jetbrains/config:/home/ci/.config:ro" \
  -v "$ROOT/runtime/$PROVISION_NAME/jetbrains/cache:/home/ci/.cache:ro" \
  -v "$ROOT/runtime/$PROVISION_NAME/jetbrains/share:/home/ci/.local/share:ro" "$PROVISION_IMAGE"
: "${REVIEWED_SELECTION_JSON:?explicit reviewed exact file/hash selection outside arena}"
python3 -B "$TOOLS/ide/jetbrains/provision.py" snapshot --root "$ROOT" \
  --profile "$SEED_PROFILE" --runtime "$PROVISION_NAME" --container "$RECORD" \
  --product "$PRODUCT" --build "$BUILD" --platform "$PLATFORM" \
  --product-dir "$PRODUCT_DIR" --selection "$REVIEWED_SELECTION_JSON" --writers-stopped
podman rm "$RECORD"
jq . "$ROOT/seed/jetbrains/$SEED_PROFILE/metadata.json"

Snapshot checks image ID, stopped state/mounts and other active overlapping containers before/after copying. The common setup lock cannot freeze unrelated host writers. Keep them stopped; --writers-stopped is an acknowledgement, not a substitute. The new seed’s full file/hash mapping, profile and exact image ID are checked on runtime preparation. Cache/share are empty, with no backend, plugins, recent projects, licenses, logs or agents. Existing names/incomplete state are refused; use a new profile for each review.

Add a separate named IDE member

The CLI arena.py add does not currently expose backend/seed flags. Integrating an IDE backend/seed into a managed member remains an advanced staged operation; do not assume ordinary add installs an IDE. The legacy add-environment interface accepts a reviewed member JSON with backend and seed fields, but stages files only and does not establish managed deployment ownership. It must not be mixed into the managed create/add/remove workflow without a separately reviewed adoption/deployment plan. Preserve existing initialized CLI state and keys. Optional offline GUI connection, licensing and reconnect remain manual checks; no AI plugin or vendor-egress exception is required.

Gateway client, offline inputs and tunnel boundary

A seed supplies neither local client/JBR nor metadata hosting. Prestage matching client/JBR, products.json and authenticated KEYS on the Gateway client. On Linux, user-scoped files under ~/.config/JetBrains/RemoteDev/ select URLs. Artifact hosting/trusted TLS is not implemented here. Any approved serving service must be provisioning-only, loopback-only on a distinct port, with read-only artifacts and no inference credentials/config; never reuse ai-api:8080 or an SSH port. Preserve literal <PRODUCT_CODE>.

Client shell, only after supplying authenticated URLs and confirming the selected Gateway’s settings. Existing files require deliberate backup/review, not overwrite:

: "${BACKEND_URL_ROOT:?approved backend metadata URL root on client-accessible provisioning service}"
: "${CLIENT_URL:?matching client directory URL}" "${JBR_URL:?matching client JBR directory URL}"
: "${KEYS_URL:?authenticated vendor KEYS URL}"
SETTINGS="$HOME/.config/JetBrains/RemoteDev"
(
  set -eu
  for setting in productsInfoUrl clientDownloadUrl jreDownloadUrl pgpPublicKeyUrl; do
    test ! -e "$SETTINGS/$setting" && test ! -L "$SETTINGS/$setting"
  done
  mkdir -p "$SETTINGS"
  set -C
  printf '%s\n' "$BACKEND_URL_ROOT/<PRODUCT_CODE>/products.json" > "$SETTINGS/productsInfoUrl"
  printf '%s\n' "$CLIENT_URL" > "$SETTINGS/clientDownloadUrl"
  printf '%s\n' "$JBR_URL" > "$SETTINGS/jreDownloadUrl"
  printf '%s\n' "$KEYS_URL" > "$SETTINGS/pgpPublicKeyUrl"
)
cat "$SETTINGS/productsInfoUrl"

Restart Gateway and verify matching artifacts/reconnect with external Internet unavailable under existing approved client policy. URL creation is not successful GUI acceptance. Other client OS setting locations and version-management policy are in the official guide; disabling updates does not supply missing artifacts.

The SSH examples assume Gateway and Podman run on the same Linux workstation. SSH 127.0.0.1:2211 refers to that host’s loopback publication, not backend localhost. A separate client needs explicitly reviewed transport/tunneling to the deployment host; never widen SSH publication to 0.0.0.0. Localhost artifact URLs refer to the client’s machine, not deployment host/backend/broker. Permitted SSH local TCP forwarding reaches backend loopback only; root/password, agent/X11, remote forwarding and tunnel devices are disabled. The tunnel remains an additional boundary exposing selected backend services to client software.

On the deployment host/client respectively, inspect effective settings/listeners:

ssh -G -F "$ROOT/ssh/$ENVIRONMENT/config" "$ENVIRONMENT"
podman port "$ENVIRONMENT"
ss -ltnp

Confirm pinned host key, no agent forwarding, host publication/client listeners loopback-only on IPv4/IPv6, and actual backend destinations of local forwards. Assess client filesystem/credentials/integrations. Optional MCP needs separate version support, authentication, loopback binding, capability/permission and onward-access review; do not publish its listener or grant blanket filesystem/ terminal access. No AI plugin is required for these checks.

Synthetic tests/jetbrains.py safety and disposable provisioning evidence do not download/execute real IDEs or prove licensing/client/tunnel behavior. See Standalone verification commands and evidence boundary. Optional GNOME/Wayland RDP is a separate final manual experiment: record actual host/server/client versions, transport/authentication, reconnect and logout/session lifetime. A previously stable session on another node is not acceptance here. No RDP installation or host-policy change is automated or authorized by this reference.

Mount layout: separate follow-up design

The current layout already groups IDE state into broad dedicated private .config, .cache and .local/share binds. Agent state, XDG state, compiler cache and read-only guards have independent paths and lifecycles. Never substitute the operator’s personal home/config/cache/local directories for these sources.

Proposed, not implemented: consider consolidating only dedicated per-member state if a later source-driven design demonstrates a benefit. A broad .local bind would also cover executable paths and overlap agent/XDG state; merging config/cache can change guard precedence, seed scope and ownership. Fewer mounts do not strengthen containment. Keep RO backend/guards and separate ccache policy unless an explicitly reviewed design preserves their authority and persistence.

Such a change needs separate migration/compatibility tests for initialized members, UID/GID and SELinux handling, overlapping bind precedence, independent seed copies and sanitization, changed-image refresh/rollback, no state loss/reseed, scripting cache exclusion, retained recreation and multi-member separation. A managed pinned backend/seed interface and repeatable factory CLI also require their own design and acceptance, including real client/offline/licensing evidence. None is supplied by this documentation edit. Use Arena administration reference for current ownership and recovery, Bounded security verification for bounded evidence and Arena troubleshooting and known issues for safe diagnosis.