# naros-agent — the agent-experience tier (NAROS.md §4, §6; milestone N2).
#
# Built FROM the imported naros-base rootfs (mmdebstrap tar → docker import), so the base
# layer — trixie snapshot, narOS identity, nash FORCED via dpkg-divert, naros-init, the
# naros CLI — is exactly the naros-base image, and this file layers only the agent tier:
# the apt half via the naros-tier-agent meta-package (build-essential, python3, the modern
# CLI kit, nucleic-bridge), Node 22 (NodeSource), rustup + stable Rust, Go, mise, warm
# shared caches under /opt/cache, the `agent` user, the agent CLIs, and Playwright with a
# bundled Chromium. It replaces containers/nucleic-sandbox (last tag: v7) as the default
# sandbox image — keep `ProjectSandbox.defaultImage` (Sources/NucleicCore/Project.swift)
# pinned to a published tag of THIS image.
#
# Build via build.sh (assembles the context: this directory + the dist/pool apt pool).
# NOTE: every RUN here executes under nash — /bin/sh is already diverted in naros-base.
# That is deliberate dogfood (this build is part of nash's M3 validation surface); nash's
# `-c` parse-failure fallback re-execs the preserved /usr/bin/bash.real, so a nash
# regression degrades loudly in CI rather than silently corrupting the image. Scripts
# piped on stdin do not cross that fallback and must choose their interpreter explicitly.
ARG BASE=naros-base:build
FROM ${BASE}

# arm64 | amd64, set by buildx (or build.sh). Toolchain pins live here — bump them
# together with os/VERSION so a release records one coherent toolchain set (§6.2); the
# versions actually installed are recorded in /etc/naros/manifest.json either way.
ARG TARGETARCH
ARG NODE_MAJOR=22
ARG GO_VERSION=1.26.5
ARG GO_SHA256_AMD64=5c2c3b16caefa1d968a94c1daca04a7ca301a496d9b086e17ad77bb81393f053
ARG GO_SHA256_ARM64=fe4789e92b1f33358680864bbe8704289e7bb5fc207d80623c308935bd696d49
ARG MISE_VERSION=v2026.7.7
ARG MISE_SHA256_AMD64=0953810c2785eb4a75159f67f8b5721c4f3c80b8a6a812015d5af7d7fbd1b8a4
ARG MISE_SHA256_ARM64=c4e542b53a15d2ec641e072f7b2d9da8a0554b92fd2c09a51febde32c6080ab8
ARG SWIFT_VERSION=6.3.3
ARG SWIFT_SHA256_AMD64=19e0c78cad5418ad48bfa87aa20c53ac9ac9996d1695d04dd94f7c7ea4eb133f
ARG SWIFT_SHA256_ARM64=ecba8ef87b54a5048d466af500f3169c939a6b8a2cb7c600f76b5184457f293a

# Warm shared caches at fixed world-readable paths (§6.3). Exported here so the BUILD's
# own installs warm them; the runtime equivalent for agent shells is
# /etc/profile.d/naros-env.sh (ContainerEngine ignores OCI env — nash -l sources
# profile.d), and `naros info` records them in the manifest.
ENV npm_config_cache=/opt/cache/npm \
    PIP_CACHE_DIR=/opt/cache/pip \
    UV_CACHE_DIR=/opt/cache/uv \
    CARGO_HOME=/opt/cache/cargo \
    RUSTUP_HOME=/opt/rustup \
    GOMODCACHE=/opt/cache/gomod \
    PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers \
    PATH=/opt/mise/shims:/opt/cache/cargo/bin:/usr/local/go/bin:/opt/swift/usr/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

# The local apt pool (CI's just-built Nucleic packages) rides in only for this stage's
# installs; the hosted signed repo (naros-keyring, already in the base) is the runtime
# channel. The Debian half still resolves from the snapshot mirror already present in
# /etc/apt/sources.list — same pin, same bytes, as the base build.
COPY pool /tmp/naros-pool

# Node 22 first (NodeSource — narOS no longer inherits node from a base image): its
# `nodejs` deb must be the one satisfying nucleic-bridge's Depends before the tier meta
# resolves, or apt would pull trixie's older nodejs alongside it.
RUN set -eu; \
    echo "deb [trusted=yes] copy:///tmp/naros-pool ./" > /etc/apt/sources.list.d/naros-pool.list; \
    # The base bakes naros-keyring's hosted-repo entry (naros.sources) — the runtime
    # apt channel. Set it aside for the whole build (here, and under playwright
    # --with-deps below) so THIS stage installs only from the local pool + Node source
    # above: the freshly built naros-tier-agent must resolve from this run's pool, not a
    # same-version deb already published to the hosted channel. It also keeps the image
    # build independent of the hosted repo — the same reason the base rootfs smoke test
    # scopes sources.list.d away. Restored in the final stage so the image ships it.
    mv /etc/apt/sources.list.d/naros.sources /tmp/naros.sources.disabled 2>/dev/null || true; \
    mkdir -p -m 755 /etc/apt/keyrings; \
    curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
      -o /etc/apt/keyrings/nodesource.asc; \
    echo "deb [signed-by=/etc/apt/keyrings/nodesource.asc] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" \
      > /etc/apt/sources.list.d/nodesource.list; \
    apt-get update; \
    apt-get install -y --no-install-recommends nodejs; \
    apt-get install -y --no-install-recommends naros-tier-agent; \
    rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/naros-pool.list /tmp/naros-pool; \
    node --version | grep -q "^v${NODE_MAJOR}\."; \
    # Debian ships fd as fdfind; agents reach for `fd`.
    ln -sf /usr/bin/fdfind /usr/local/bin/fd

# Rust (rustup + stable, minimal profile) and Go (upstream tarball, pinned + checksummed).
# CARGO_HOME doubles as the shared cargo cache/bin dir; RUSTUP_HOME holds toolchains.
# Everything world-readable so the non-root agent uses them in place (§6.1: no /root
# permission hacks — system paths or /opt by convention).
RUN set -eu; \
    # nash's parse fallback applies to `-c`, not scripts supplied on stdin.
    # Upstream installers are outside the dogfood surface, so run them with the
    # preserved Debian bash instead of feeding them to the diverted sh/bash.
    curl -fsSL https://sh.rustup.rs | /usr/bin/bash.real -s -- -y --profile minimal \
      --default-toolchain stable --no-modify-path; \
    "$CARGO_HOME/bin/rustc" --version; \
    case "$TARGETARCH" in \
      amd64) go_sha="$GO_SHA256_AMD64";; \
      arm64) go_sha="$GO_SHA256_ARM64";; \
      *) echo "unsupported TARGETARCH: $TARGETARCH" >&2; exit 1;; \
    esac; \
    curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${TARGETARCH}.tar.gz" -o /tmp/go.tgz; \
    echo "$go_sha  /tmp/go.tgz" | sha256sum -c -; \
    tar -C /usr/local -xzf /tmp/go.tgz; rm /tmp/go.tgz; \
    go version

# mise — the on-demand toolchain manager ("get me Java 21" without apt archaeology).
# System-wide: binary in /usr/local/bin, data/shims under /opt/mise (world-writable like
# the caches — per-session rootfs clones make that private copy-on-write state).
RUN set -eu; \
    case "$TARGETARCH" in \
      amd64) mise_arch=x64; mise_sha="$MISE_SHA256_AMD64";; \
      arm64) mise_arch=arm64; mise_sha="$MISE_SHA256_ARM64";; \
    esac; \
    curl -fsSL "https://github.com/jdx/mise/releases/download/${MISE_VERSION}/mise-${MISE_VERSION}-linux-${mise_arch}.tar.gz" \
      -o /tmp/mise.tgz; \
    echo "$mise_sha  /tmp/mise.tgz" | sha256sum -c -; \
    tar -C /tmp -xzf /tmp/mise.tgz; \
    install -m 0755 /tmp/mise/bin/mise /usr/local/bin/mise; \
    rm -rf /tmp/mise /tmp/mise.tgz; \
    mkdir -p /opt/mise/shims; \
    MISE_DATA_DIR=/opt/mise mise --version

# Swift — the swift.org release toolchain (pinned + checksummed, same shape as Go above).
# Baked because Nucleic itself is a Swift package and agents work on Swift repos all day:
# swiftc, SwiftPM, swift-testing, sourcekit-lsp and swift-format all run in-container now.
# It does NOT replace host_exec / the macOS VM for anything Apple-SDK — there is no
# SwiftUI/AppKit/UIKit or xcodebuild on Linux — but pure-Swift targets build and test here.
#
# The `debian12` slice is the newest Debian build swift.org publishes; it runs unmodified on
# trixie (verified: swift build/test/swiftc on the 26.07 image) once libncurses6 is present,
# which naros-tier-agent now pulls along with the rest of the toolchain's runtime libs
# (libxml2, libcurl4, libsqlite3-0, libuuid1, libtinfo6, zlib1g). Debian's own `swiftlang`
# is deliberately NOT used here: trixie carries 6.0.3, too old for a swift-tools-version 6.2
# package like this repo's. (The VM desktop rootfs is the mirror-image case — forky's
# libxml2 soname bump to .so.16 makes the swift.org build unloadable there, so it takes
# forky's swiftlang 6.2.3 instead; see os/mkimage/profiles/vm-desktop.pkgs.)
#
# LLDB is stripped: upstream links it against libpython3.11, which trixie does not ship, so
# lldb/lldb-dap/liblldb could never load — dropping them keeps 338 MB out of the image and
# makes `swift repl` fail as a plain "not found" instead of a loader error. Nothing else in
# the toolchain depends on liblldb (checked via NEEDED).
RUN set -eu; \
    case "$TARGETARCH" in \
      amd64) swift_slice=debian12; swift_sha="$SWIFT_SHA256_AMD64";; \
      arm64) swift_slice=debian12-aarch64; swift_sha="$SWIFT_SHA256_ARM64";; \
      *) echo "unsupported TARGETARCH: $TARGETARCH" >&2; exit 1;; \
    esac; \
    swift_tag="swift-${SWIFT_VERSION}-RELEASE"; \
    curl -fsSL "https://download.swift.org/swift-${SWIFT_VERSION}-release/${swift_slice}/${swift_tag}/${swift_tag}-${swift_slice}.tar.gz" \
      -o /tmp/swift.tgz; \
    echo "$swift_sha  /tmp/swift.tgz" | sha256sum -c -; \
    mkdir -p /opt/swift; \
    tar -C /opt/swift --strip-components=1 -xzf /tmp/swift.tgz; rm /tmp/swift.tgz; \
    rm -f /opt/swift/usr/bin/lldb /opt/swift/usr/bin/lldb-dap \
          /opt/swift/usr/bin/lldb-server /opt/swift/usr/bin/lldb-argdumper \
          /opt/swift/usr/lib/liblldb.so*; \
    chmod -R a+rX /opt/swift; \
    swift --version | grep -q "Swift version ${SWIFT_VERSION}"

# Runtime environment for agent shells: cache paths, toolchain PATH entries, mise
# activation. nash -l sources /etc/profile.d, which is how every containerized exec
# (ContainerEngine exec → nash -lc) sees this without OCI env.
COPY files/etc/profile.d/naros-env.sh /etc/profile.d/naros-env.sh

# The `agent` user (§6.1): fixed uid 501 — the uid ContainerEngine execs as on a
# default single-user Mac (getuid() of the first macOS user), so bind-mount ownership
# and the baked passwd entry agree without seeding. Login shell nash; passwordless sudo
# via a drop-in mirroring the Linux VM guest. (Asserted in CI smoke + a Swift test
# against ContainerSpec.narosAgentUID — keep the three in lockstep.)
COPY files/etc/sudoers.d/naros-agent /etc/sudoers.d/naros-agent
RUN set -eu; \
    useradd --uid 501 --user-group --create-home --home-dir /home/agent \
      --shell /usr/local/bin/nash agent; \
    chmod 0440 /etc/sudoers.d/naros-agent; \
    visudo -cf /etc/sudoers.d/naros-agent; \
    chmod 0755 /home/agent

# Agent CLIs, pinned @latest at build so every rebuild ships the current releases (and
# the models they unlock); they update in place via ContainerManager.updateAgentCLIs
# between rebuilds. Deliberately npm/vendor installs, not debs (NAROS.md §3.1). The npm
# installs run with the shared cache env above, warming /opt/cache/npm as a side effect.
RUN npm install -g @anthropic-ai/claude-code@latest \
    && npm install -g @openai/codex@latest \
    # yarn: the node:22 base image shipped it, so agents (and repos' packageManager
    # fields) expect it — parity-sweep guards it.
    && npm install -g yarn \
    && claude --version && codex --version && yarn --version

# xAI Grok Build via its official installer, relocated from the 0700 /root to a
# world-traversable /opt and relinked onto PATH (same dance as nucleic-sandbox v4+ —
# narOS keeps agent-reachable installs out of /root by convention). As with rustup, the
# stdin-fed upstream script needs the preserved real bash rather than diverted `bash`.
RUN curl -fsSL https://x.ai/cli/install.sh | /usr/bin/bash.real \
    && rm -f /usr/local/bin/grok /usr/local/bin/agent \
    && mv /root/.grok /opt/grok \
    && chmod -R a+rX /opt/grok \
    && ln -s /opt/grok/bin/grok /usr/local/bin/grok \
    && ln -s /opt/grok/bin/agent /usr/local/bin/agent \
    && /opt/grok/bin/grok --version

# Headless browser tool — Playwright + bundled Chromium at the fixed world-readable
# path, exactly as in nucleic-sandbox v7: any uid can launch it, and an agent's local
# `npm install playwright` / `pip install playwright` reuses the browsers instead of
# re-downloading. `--with-deps` apt-installs Chromium's shared libraries (root here).
RUN npm install -g playwright@latest \
    && mkdir -p /opt/playwright-browsers \
    && playwright install --with-deps chromium \
    && chmod -R a+rX /opt/playwright-browsers \
    && rm -rf /var/lib/apt/lists/* \
    && playwright --version

# Warm the cargo sparse-index + a first crate into the shared cache (network-light
# `cargo add serde` for agents), then open the caches to every uid: world-writable is
# safe here because each session's container gets its own copy-on-write rootfs clone.
RUN set -eu; \
    tmp="$(mktemp -d)"; cd "$tmp"; \
    cargo init -q --name warmcache .; \
    cargo add -q serde >/dev/null 2>&1; \
    cargo fetch -q; \
    cd /; rm -rf "$tmp"; \
    mkdir -p /opt/cache/npm /opt/cache/pip /opt/cache/uv /opt/cache/gomod; \
    chmod -R a+rwX /opt/cache /opt/mise; \
    chmod -R a+rX /opt/rustup /usr/local/go

# Stamp the tier: os-release VARIANT and the §6.3 capability manifest (tier, every baked
# toolchain + version, cache paths) so `naros info` / the host probe read one file
# instead of probing binary-by-binary.
RUN set -eu; \
    # Restore the hosted runtime apt channel set aside at the start of the build.
    mv /tmp/naros.sources.disabled /etc/apt/sources.list.d/naros.sources 2>/dev/null || true; \
    sed -i -e 's/^VARIANT=.*/VARIANT="agent"/' -e 's/^VARIANT_ID=.*/VARIANT_ID=agent/' /etc/os-release; \
    node_v="$(node --version)"; \
    python_v="$(python3 -c 'import platform; print(platform.python_version())')"; \
    rust_v="$(rustc --version | awk '{print $2}')"; \
    go_v="$(go version | awk '{print $3}')"; \
    mise_v="$(mise --version 2>/dev/null | awk '{print $1}')"; \
    gcc_v="$(gcc -dumpfullversion)"; \
    swift_v="$(swift --version | awk '/Swift version/ {print $3; exit}')"; \
    pw_v="$(playwright --version | awk '{print $2}')"; \
    jq --arg node "$node_v" --arg python "$python_v" --arg rust "$rust_v" \
       --arg go "$go_v" --arg mise "$mise_v" --arg gcc "$gcc_v" --arg swift "$swift_v" \
       --arg playwright "$pw_v" \
       '.tier = "agent" | .variant = "agent" \
        | .toolchains = {node: $node, python: $python, rust: $rust, go: $go, mise: $mise, \
                         gcc: $gcc, swift: $swift} \
        | .caches = {npm: "/opt/cache/npm", pip: "/opt/cache/pip", uv: "/opt/cache/uv", \
                     cargo: "/opt/cache/cargo", gomod: "/opt/cache/gomod"} \
        | .playwright = {version: $playwright, browsers: "/opt/playwright-browsers", chromium: true}' \
       /etc/naros/manifest.json > /etc/naros/manifest.json.new; \
    mv /etc/naros/manifest.json.new /etc/naros/manifest.json; \
    naros info --json \
      | jq -e '.tier == "agent" and .variant == "agent" and .toolchains.node != null' > /dev/null

WORKDIR /workspace
