import Foundation
import Containerization
import ContainerizationOCI

/// Rootfs management, per-container clones, seeding, and on-disk GC.
///
/// The old runtime built the sandbox image on-device with `container build`. The framework has no
/// Dockerfile builder, and provisioning in a builder VM at runtime is slow and needs the agent's
/// package registries reachable — so instead the `nucleic-sandbox` image is built in CI and pushed
/// to a registry, and the engine just **pulls + unpacks** it to an ext4 (cached per image version),
/// then **clones** that per container so each gets an isolated writable rootfs. Users install no
/// build tools; the toolchain ships inside the published image.
extension ContainerEngine {
    // MARK: - On-disk layout

    private var rootfsDir: URL { storageRoot.appendingPathComponent("rootfs", isDirectory: true) }
    private var instancesDir: URL { storageRoot.appendingPathComponent("instances", isDirectory: true) }

    /// A filesystem-safe form of an image reference (e.g. `ghcr.io/abkslm/nucleic-sandbox:v3` →
    /// `ghcr.io_abkslm_nucleic-sandbox_v3`), used as the cache filename stem.
    static func sanitizeRef(_ ref: String) -> String {
        String(ref.map { ($0 == "/" || $0 == ":") ? "_" : $0 })
    }

    /// Cache file for an unpacked image, keyed by a filesystem-safe form of the ref.
    private func rootfsCacheURL(for imageRef: String) -> URL {
        rootfsDir.appendingPathComponent(Self.sanitizeRef(imageRef) + ".ext4")
    }

    /// Sidecar next to a cached rootfs recording the **manifest digest** of the image that produced
    /// it. The asset auto-update check compares this against the registry's current digest to decide
    /// whether a newer image is available — without a re-pull. Written on unpack; removed alongside
    /// the cache it describes.
    private func rootfsDigestURL(for imageRef: String) -> URL {
        rootfsDir.appendingPathComponent(Self.sanitizeRef(imageRef) + ".ext4.digest")
    }

    /// The manifest digest recorded for the currently-cached rootfs of `imageRef`, or `nil` if the
    /// rootfs isn't cached or predates digest tracking.
    func recordedRootfsDigest(for imageRef: String) -> String? {
        guard let text = try? String(contentsOf: rootfsDigestURL(for: imageRef), encoding: .utf8)
        else { return nil }
        let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
        return trimmed.isEmpty ? nil : trimmed
    }

    /// Per-container writable rootfs clone.
    func instanceRootfsURL(for name: String) -> URL {
        instancesDir.appendingPathComponent(name + ".ext4")
    }

    /// Host-side sidecar recording the ``seedRecipe`` a reused clone was last seeded under, so a
    /// relaunch after an app update that changed the seed instrumentation (the `git`/`gh` interceptor
    /// shims, the command interceptor) can re-seed the clone in place — the container analogue of the
    /// VM base recipe check. Kept next to the clone; cleaned up with it in ``reconcileDisk``.
    private func instanceSeedRecipeURL(for name: String) -> URL {
        instancesDir.appendingPathComponent(name + ".seed")
    }

    /// The **seed-recipe version** the app currently installs into a fresh container. Bump this
    /// whenever the seed instrumentation changes in a way an already-cloned container must pick up: the
    /// `git`/`gh` interceptor shim sources (``gitInterceptorSource`` / ``ghInterceptorSource``) or the
    /// command interceptor (``CommandInterceptor/installScript()``). A relaunch then re-seeds reused
    /// clones so they get the new shims instead of keeping the old ones. (The image toolchain itself is
    /// versioned separately by the image tag — see `ProjectSandbox.defaultImage`.)
    ///
    /// `2` adds nash's trusted policy file (docs/NASH.md §4.3).
    /// `3` adds the Codex edit gate — its `PreToolUse` hook program plus the root-owned managed
    /// config that declares it (``CodexToolGate``, docs/LOCKING.md §4.6).
    static let seedRecipe = 3

    /// The recipe *as recorded*, which is the version plus any seeded content that follows a live
    /// setting rather than the app build. Today that is the ``ContainerServiceSettings/legacyShell``
    /// rollback lever, which the nash policy file carries: since the lever no longer travels as an
    /// (agent-writable) env var, flipping it has to invalidate a reused clone's seed so the next
    /// start rewrites the file. Without this, the emergency lever would only reach containers
    /// created after the flip.
    static var seedRecipeStamp: String {
        "\(seedRecipe)" + (ContainerServiceSettings.legacyShell ? "+legacyShell" : "")
    }

    /// Read the recipe stamp a reused clone was seeded under, or `nil` if never recorded
    /// (pre-tracking or never seeded).
    private func recordedSeedRecipe(for name: String) -> String? {
        guard let text = try? String(contentsOf: instanceSeedRecipeURL(for: name), encoding: .utf8)
        else { return nil }
        return text.trimmingCharacters(in: .whitespacesAndNewlines)
    }

    /// Record the current ``seedRecipeStamp`` for `name` after a (re)seed. Best-effort.
    func recordSeedRecipe(for name: String) {
        try? Self.seedRecipeStamp.write(
            to: instanceSeedRecipeURL(for: name), atomically: true, encoding: .utf8)
    }

    // MARK: - Per-container rootfs (clone of the cached image)

    /// Produce the writable rootfs `Mount` for a container: ensure the image's rootfs is built and
    /// cached, then return a per-container clone of it. `needsSeed` is true for a fresh clone and also
    /// when a reused clone was seeded under an older ``seedRecipe`` (an app update changed the
    /// interceptor shims) — so the caller re-installs the instrumentation in place either way.
    /// `freshlyCloned` distinguishes the two: a reused clone keeps the agent's working files, so
    /// heavier one-time checks (`verifyControlBridge`) that only depend on the unchanged image stay
    /// gated to a genuine fresh clone.
    func instanceRootfs(for spec: ContainerSpec) async throws
        -> (rootfs: Containerization.Mount, freshlyCloned: Bool, needsSeed: Bool)
    {
        try FileManager.default.createDirectory(at: instancesDir, withIntermediateDirectories: true)
        let instanceURL = instanceRootfsURL(for: spec.name)
        func reuse() -> (Containerization.Mount, Bool, Bool) {
            let staleSeed = recordedSeedRecipe(for: spec.name) != Self.seedRecipeStamp
            return (
                Containerization.Mount.block(format: "ext4", source: instanceURL.path, destination: "/"),
                false, staleSeed)
        }
        if FileManager.default.fileExists(atPath: instanceURL.path) { return reuse() }
        let cacheURL = try await ensureCachedRootfs(imageRef: spec.image)
        // Re-check after the pull: `ensureCachedRootfs` suspends for as long as a download takes
        // (minutes on a first run or an image update's re-pull), so the clone must not assume the
        // destination is still absent — a clone that appeared meanwhile is reused, not collided with.
        if FileManager.default.fileExists(atPath: instanceURL.path) { return reuse() }
        let base = Containerization.Mount.block(format: "ext4", source: cacheURL.path, destination: "/")
        // Copy to a temp sibling and publish with a rename, so a copy interrupted midway (app quit,
        // disk full) leaves no truncated `.ext4` behind for the next launch to reuse as a good clone.
        let tmpURL = instancesDir.appendingPathComponent(".clone-\(UUID().uuidString).partial")
        try? FileManager.default.removeItem(at: tmpURL)
        do {
            _ = try base.clone(to: tmpURL.path)
            try FileManager.default.moveItem(at: tmpURL, to: instanceURL)
        } catch {
            try? FileManager.default.removeItem(at: tmpURL)
            throw error
        }
        let cloned = Containerization.Mount.block(
            format: "ext4", source: instanceURL.path, destination: "/")
        return (cloned, true, true)
    }

    // MARK: - Cached image rootfs

    /// Ensure the rootfs for `imageRef` is pulled and cached, returning the cache file URL. Every
    /// image — the default `nucleic-sandbox` (built in CI, pushed to a registry) and any custom
    /// bring-your-own ref alike — is simply pulled and unpacked. There is no on-device provisioning:
    /// the toolchain + Claude Code live in the published image, so users need no build tools.
    ///
    /// Coalesced per ref (see ``pulling``): concurrent cold starts on the same image share one
    /// pull+unpack rather than each downloading it into the same cache file.
    private func ensureCachedRootfs(imageRef: String) async throws -> URL {
        if let inFlight = pulling[imageRef] { return try await inFlight.value }
        // Slot cleared by the task itself, so a cancelled joiner can't free it mid-pull. See the
        // same shape in `ensureRunning`.
        let task = Task<URL, Error> {
            defer { pulling[imageRef] = nil }
            return try await pullAndUnpackRootfs(imageRef: imageRef)
        }
        pulling[imageRef] = task
        return try await task.value
    }

    private func pullAndUnpackRootfs(imageRef: String) async throws -> URL {
        let cacheURL = rootfsCacheURL(for: imageRef)
        if FileManager.default.fileExists(atPath: cacheURL.path) {
            // Auto-update on request: with the setting on, a freshly-requested sandbox picks up a
            // newer image rather than reusing the cached one. Compare the recorded manifest digest
            // against the registry's current digest (a cheap HEAD, no layer download); on a mismatch
            // discard the stale cache and fall through to re-pull the current image. Any check
            // failure (offline, auth, no recorded digest) leaves the cache in place — a sandbox that
            // could otherwise start must never be blocked on an update check.
            if ContainerServiceSettings.autoCheckImageUpdates,
                await cachedRootfsIsStale(imageRef: imageRef)
            {
                await discardCachedRootfs(imageRef: imageRef)
            } else {
                return cacheURL
            }
        }
        try FileManager.default.createDirectory(at: rootfsDir, withIntermediateDirectories: true)

        // The dominant first-run download — surface it to the Control panel + chat for the whole
        // pull-then-unpack span (one begin/end, with the phase advanced from .image to .unpack).
        beginDownload(.image)
        defer { endDownload() }

        // Unpack into a temp file (EXT4Unpacker refuses to overwrite an existing path), then publish
        // atomically so a partial/interrupted pull never leaves a corrupt cache in place.
        let tmpURL = rootfsDir.appendingPathComponent(".pull-\(UUID().uuidString).ext4")
        try? FileManager.default.removeItem(at: tmpURL)
        do {
            let store = try sharedImageStore()
            let image: Containerization.Image
            do {
                // Pull with registry credentials when configured (private GHCR), else anonymously.
                // The progress handler streams layer byte totals into the panel's determinate bar.
                image = try await store.pull(
                    reference: imageRef, platform: .current, auth: registryAuth(for: imageRef),
                    progress: { [self] events in await advanceDownload(events, phase: .image) })
            } catch {
                throw ContainerError.imagePullFailed("\(imageRef): \(error)")
            }
            // 8 GiB ext4: room for the image contents plus the agent's working files. Unpack reports
            // no byte stream, so this stage shows as an indeterminate "Unpacking sandbox image" bar.
            setDownloadPhase(.unpack)
            let unpacker = EXT4Unpacker(blockSizeInBytes: 8 * 1024 * 1024 * 1024)
            _ = try await unpacker.unpack(image, for: .current, at: tmpURL)
            try? FileManager.default.removeItem(at: cacheURL)
            try FileManager.default.moveItem(at: tmpURL, to: cacheURL)
            // Record the manifest digest this rootfs was unpacked from, so the asset auto-update
            // check can tell whether the registry has moved on without a re-pull. Best-effort.
            try? image.digest.write(to: rootfsDigestURL(for: imageRef), atomically: true, encoding: .utf8)
            return cacheURL
        } catch {
            try? FileManager.default.removeItem(at: tmpURL)
            if let ce = error as? ContainerError { throw ce }
            throw ContainerError.rootfsBuildFailed("\(imageRef): \(error)")
        }
    }

    // MARK: - Seeding (per fresh container)

    /// One-time per-container setup, run as root via `exec` (replaces the old `container exec sh -c`
    /// seed). Marks the bind-mounted repos as a git "safe directory" (the agent runs as the host
    /// uid against root-owned mounts, which trips git's dubious-ownership guard), gives the agent's
    /// uid a passwd entry, and — for Nucleic Control containers — installs the `git`/`gh` interceptor
    /// shims and the command interceptor ahead of the real binaries on PATH. Best-effort.
    func seed(_ spec: ContainerSpec, in container: LinuxContainer) async {
        var script = "git config --system --add safe.directory '*' 2>/dev/null || true"
        // nash's trusted operator policy (docs/NASH.md §4.3), written from the one place in the
        // container's life that runs as root. Unconditional: every image Nucleic starts may end up
        // running nash (narOS bakes it; older images get it seeded), and the file is inert where it
        // doesn't. narOS also bakes a copy, so a container started outside this seed path is still
        // covered; this write refreshes it with the current lever state.
        script += "; " + CommandInterceptor.nashPolicyInstallScript() + " || true"
        if let uid = spec.runAsUID {
            // narOS images bake the `agent` user (uid `ContainerSpec.narosAgentUID` — the default
            // single-user-Mac host uid), so on the default path getent hits and nothing is
            // appended. A different host uid (second macOS user) or a legacy/custom image still
            // gets an entry, with nash as the login shell where the image has it (NASH.md §7.2)
            // and bash elsewhere.
            let gid = spec.runAsGID ?? 0
            script += "; getent passwd \(uid) >/dev/null 2>&1"
                + " || { s=/bin/bash; [ -x \(CommandInterceptor.nashPath) ] && s=\(CommandInterceptor.nashPath);"
                + " echo \"nucleic:x:\(uid):\(gid):Nucleic Agent:/tmp:$s\" >> /etc/passwd 2>/dev/null; } || true"
        }
        if spec.installGitInterceptor {
            script += "; mkdir -p /usr/local/bin"
            script += installShimSnippet(Self.gitInterceptorSource, at: "/usr/local/bin/git")
            script += installShimSnippet(Self.ghInterceptorSource, at: "/usr/local/bin/gh")
            script += "; " + CommandInterceptor.installScript()
            // The Codex edit gate (docs/LOCKING.md §4.6): a root-owned managed config declaring a
            // `PreToolUse` hook, so EVERY codex in this box — the app-server backend, `codex exec`,
            // a nested `codex` the agent runs itself — acquires its file locks before an
            // `apply_patch` lands, the way Claude's tool bridge does. Installed alongside the
            // interceptor shims and scoped identically (Nucleic Control containers, which is
            // locking's scope), and root-owned for the same reason nash's policy is: a gate the
            // gated party can rewrite is not a gate.
            script += "; " + CodexToolGate.installScript() + " || true"
        }
        _ = try? await runToCompletion(container, ["sh", "-c", script])
    }

    /// Preflight for the mandatory vsock control plane: the guest must be able to run the
    /// in-container control bridge — `node` on PATH plus the bridge script the init already tried
    /// to launch at start. Both ship in every narOS agent image and in sandbox images ≥ `v4`; a
    /// custom image lacking either would leave the agent with no route to the host control
    /// endpoint, surfacing only as the CLI's opaque "Available MCP tools: none". Run once per
    /// fresh clone; throws an actionable error.
    ///
    /// narOS fast path (NAROS.md §7.3, NASH.md §7.6): `ID=naros` with an agent/runner `VARIANT_ID`
    /// in `/etc/os-release` means node, the bridge, and nash are present by construction — probe
    /// done. Other narOS variants (a bare `naros-base`) and everything else run the per-component
    /// checks and keep the degrade posture.
    func verifyControlBridge(_ spec: ContainerSpec, in container: LinuxContainer) async throws {
        let probe = "{ grep -qs '^ID=naros$' /etc/os-release"
            + " && grep -Eqs '^VARIANT_ID=(agent|runner)$' /etc/os-release; }"
            + " || { command -v node >/dev/null 2>&1 && test -f \(Self.controlBridgeGuestPath); }"
        let exitCode = (try? await runToCompletion(container, ["sh", "-c", probe])) ?? -1
        guard exitCode != 0 else { return }
        throw ContainerError.startFailed(
            "image \(spec.image) can't run the mandatory vsock control plane: it must provide "
                + "`node` and \(Self.controlBridgeGuestPath). Base custom images on "
                + "\(ProjectSandbox.defaultImage) (or convert a Debian-family image with "
                + "`apt install naros-keyring naros-tier-agent`).")
    }

    // MARK: - Disk GC (the daemonless replacement for orphan reaping)

    /// On launch / after teardown: delete per-container rootfs clones whose name isn't in
    /// `keepNames` (dead per-session containers, plus the shared control containers — which are
    /// always rebuilt fresh), then prune superseded image caches. There are no live VMs to reap;
    /// the registry is empty in a fresh process.
    ///
    /// GC is scoped to THIS build channel: several installed builds (release + beta + local dev)
    /// share this store, each owning only its `ContainerManager.channelSuffix`-tagged clones, so a
    /// beta launch must not reap a release's per-session clones (and vice-versa).
    public func reconcileDisk(keepNames: Set<String>) async {
        if let clones = try? FileManager.default.contentsOfDirectory(
            at: instancesDir, includingPropertiesForKeys: nil)
        {
            for clone in clones {
                // A `.clone-…partial` is a copy that never got renamed into place (the app quit
                // mid-clone) — garbage, unless a start is in flight right now and it's that copy.
                if clone.lastPathComponent.hasPrefix(".clone-") {
                    guard !isStarting else { continue }
                    try? FileManager.default.removeItem(at: clone)
                    continue
                }
                guard clone.pathExtension == "ext4" else { continue }
                let name = clone.deletingPathExtension().lastPathComponent
                guard ContainerManager.ownsContainer(named: name) else { continue }  // not our channel
                if !keepNames.contains(name) {
                    try? FileManager.default.removeItem(at: clone)
                    try? FileManager.default.removeItem(at: instanceSeedRecipeURL(for: name))
                }
            }
        }
        // Same for an interrupted image pull's temp file, which no prefix/family check below matches
        // — skipped entirely while a pull is in flight, since one of them would be its staging file.
        if pulling.isEmpty,
            let staged = try? FileManager.default.contentsOfDirectory(
                at: rootfsDir, includingPropertiesForKeys: nil)
        {
            for file in staged where file.lastPathComponent.hasPrefix(".pull-") {
                try? FileManager.default.removeItem(at: file)
            }
        }
        await pruneObsoleteRootfs()
    }

    /// Force a genuinely fresh re-pull of the default image on the next session. Deletes the cached
    /// ext4 rootfs **and** evicts the image from the content-addressed image store (with a blob GC),
    /// so a tag re-pushed under the same name (e.g. a refreshed Codex CLI baked into a rebuilt image)
    /// is re-resolved and re-imported from the registry rather than served from locally-cached blobs.
    /// Without the store eviction the re-pull would still re-resolve the tag and download any *new*
    /// layers, but the superseded blobs would linger on disk; evicting reclaims that space and makes
    /// "fresh pull" unambiguous. Mirrors the old `removeDefaultImage` used by "Force re-creation".
    public func removeDefaultRootfs() async {
        await discardCachedRootfs(imageRef: ProjectSandbox.defaultImage)
    }

    /// Delete the cached ext4 rootfs and its digest sidecar for `imageRef`, and evict the image from
    /// the content-addressed store (with a blob GC), so the next `ensureCachedRootfs` re-resolves and
    /// re-pulls the current digest from the registry rather than serving locally-cached blobs. Shared
    /// by ``removeDefaultRootfs`` (Force re-creation) and the auto-update-on-request path.
    private func discardCachedRootfs(imageRef: String) async {
        try? FileManager.default.removeItem(at: rootfsCacheURL(for: imageRef))
        try? FileManager.default.removeItem(at: rootfsDigestURL(for: imageRef))
        if let store = try? sharedImageStore() {
            try? await store.delete(reference: imageRef, performCleanup: true)
        }
    }

    /// Whether the cached rootfs for `imageRef` is superseded by a newer image on the registry, used
    /// to gate the auto-update-on-request re-pull. Compares the digest recorded at unpack time against
    /// a fresh registry HEAD. Returns `false` (keep the cache) when there's no recorded digest to
    /// compare against or the registry can't be reached — availability beats freshness, so an
    /// unreachable registry or an untracked cache never blocks a sandbox that could otherwise start.
    private func cachedRootfsIsStale(imageRef: String) async -> Bool {
        guard let localDigest = recordedRootfsDigest(for: imageRef),
            let remoteDigest = try? await remoteImageDigest(for: imageRef)
        else { return false }
        return remoteDigest != localDigest
    }

    /// Remove cached rootfs files for superseded versions of the default image — the leftovers after
    /// the default image tag is bumped (e.g. an old `…nucleic-sandbox_v2` after moving to `v3`).
    /// Matches by the default ref's family (everything before its tag), so a custom bring-your-own
    /// cache is never touched. Best-effort.
    public func pruneObsoleteRootfs() async {
        let defaultRef = ProjectSandbox.defaultImage
        // The family is the ref without its tag; a ':' that sits after the last '/' is a tag
        // separator (not a registry host:port), so strip from there.
        let familyRef: String = {
            guard let colon = defaultRef.lastIndex(of: ":") else { return defaultRef }
            if let slash = defaultRef.lastIndex(of: "/"), slash > colon { return defaultRef }
            return String(defaultRef[..<colon])
        }()
        let familyPrefix = Self.sanitizeRef(familyRef) + "_"  // the sanitized tag separator
        let current = rootfsCacheURL(for: defaultRef).lastPathComponent
        guard let files = try? FileManager.default.contentsOfDirectory(
            at: rootfsDir, includingPropertiesForKeys: nil) else { return }
        for file in files where file.pathExtension == "ext4" {
            let base = file.lastPathComponent
            if base.hasPrefix(familyPrefix) && base != current {
                try? FileManager.default.removeItem(at: file)
                try? FileManager.default.removeItem(at: file.appendingPathExtension("digest"))
            }
        }
    }

    // MARK: - Asset auto-update check (base image)

    /// The current manifest digest the registry serves for `imageRef` — a cheap HEAD request (no
    /// layer download), using the same reference parse + credentials as the pull. Throws on any
    /// network/registry/auth error so the caller can distinguish "couldn't check" from "up to date".
    private func remoteImageDigest(for imageRef: String) async throws -> String {
        let ref = try Reference.parse(imageRef)
        guard let tag = ref.tag ?? ref.digest else {
            throw ContainerError.imagePullFailed("\(imageRef): missing tag/digest")
        }
        let client = try RegistryClient(reference: imageRef, auth: registryAuth(for: imageRef))
        let descriptor = try await client.resolve(name: ref.path, tag: tag)
        return descriptor.digest
    }

    /// Check whether the registry serves a newer sandbox base image than the one currently unpacked,
    /// by comparing manifest digests (the recorded rootfs sidecar vs. a fresh registry HEAD). This is
    /// the asset auto-update check: it lets a refreshed image re-pushed to the pinned tag reach the
    /// app without an app release. Returns `.notInstalled` when nothing is cached yet (the first
    /// session pulls the latest anyway), `.unknown` when the registry can't be reached, and never
    /// re-pulls — applying an available update is the caller's explicit action (Force re-creation).
    public func checkDefaultImageUpdate() async -> ImageUpdateStatus {
        let imageRef = ProjectSandbox.defaultImage
        guard FileManager.default.fileExists(atPath: rootfsCacheURL(for: imageRef).path) else {
            return .notInstalled
        }
        // No recorded digest means this rootfs predates digest tracking; a fresh remote digest gives
        // us nothing to compare against, so report unknown rather than a spurious "update available".
        guard let localDigest = recordedRootfsDigest(for: imageRef) else {
            return .unknown("no local digest recorded")
        }
        do {
            let remoteDigest = try await remoteImageDigest(for: imageRef)
            return remoteDigest == localDigest ? .upToDate : .updateAvailable
        } catch {
            return .unknown("\(error)")
        }
    }

    // MARK: - Exec helper

    /// Run a one-shot command to completion in a container (no stdio piped) and return its exit
    /// code. Used for seeding and provisioning.
    private func runToCompletion(_ container: LinuxContainer, _ argv: [String], uid: UInt32 = 0)
        async throws -> Int32
    {
        let id = "admin-\(UUID().uuidString.prefix(8))"
        let process = try await container.exec(id) { p in
            p.arguments = argv
            p.user = ContainerizationOCI.User(uid: uid, gid: 0)
            p.terminal = false
        }
        try await process.start()
        return try await process.wait().exitCode
    }

    /// A `&& …` shell fragment that decodes a base64'd shim source to `path` and makes it
    /// executable. Centralized so the `git` and `gh` shims install identically.
    private func installShimSnippet(_ source: String, at path: String) -> String {
        let b64 = Data(source.utf8).base64EncodedString()
        return " && printf %s \(shellQuote(b64)) | base64 -d > \(path)"
            + " && chmod 0755 \(path)"
    }

    private func shellQuote(_ s: String) -> String {
        "'" + s.replacingOccurrences(of: "'", with: "'\\''") + "'"
    }

    // The sandbox image's toolchain (git, gh, build-essential, python3, Claude Code, …) is now
    // baked into the published `nucleic-sandbox` image at build time — see
    // containers/nucleic-sandbox/Dockerfile and the CI workflow that pushes it. Nothing is
    // provisioned on-device; the engine only pulls + unpacks.

    // MARK: - In-guest interceptor shim sources

    /// Source of the `git` interceptor shim installed at `/usr/local/bin/git` (which precedes
    /// `/usr/bin` on the container's default PATH) in Nucleic Control containers. It runs the
    /// real git transparently (stdio inherited, exit status preserved), then — for mutating
    /// ops — reports the invocation to the Nucleic host via the per-session callback URL/token
    /// injected into the exec env (`NUCLEIC_GIT_HOOK_URL`, `NUCLEIC_GIT_HOOK_TOKEN`,
    /// `NUCLEIC_SESSION_ID`). Implemented in Node (always present in the image) so JSON
    /// encoding and the HTTP POST are robust; reporting is strictly best-effort.
    nonisolated static let gitInterceptorSource = #"""
        #!/usr/bin/env node
        'use strict';
        // Nucleic git interceptor. See ContainerEngine.gitInterceptorSource.
        const { spawnSync } = require('child_process');
        const http = require('http');

        const REAL_GIT = process.env.NUCLEIC_REAL_GIT || '/usr/bin/git';
        const args = process.argv.slice(2);

        const result = spawnSync(REAL_GIT, args, { stdio: 'inherit' });
        const code = (result.status != null) ? result.status : (result.signal ? 1 : 0);

        let done = false;
        function finish() { if (done) return; done = true; process.exit(code); }

        function firstSubcommand(a) {
          for (let i = 0; i < a.length; i++) {
            const t = a[i];
            if (t === '-C' || t === '-c') { i++; continue; }
            if (t.charAt(0) === '-') continue;
            return t;
          }
          return null;
        }

        // Report only mutating / state-changing ops; reads are skipped to avoid spamming the
        // host. The host re-classifies the argv authoritatively.
        const REPORTABLE = new Set([
          'merge','commit','rebase','cherry-pick','revert','reset','push','pull','fetch','clone',
          'switch','checkout','branch','tag','stash','init','am','restore','mv','rm','apply','format-patch',
        ]);

        const url = process.env.NUCLEIC_GIT_HOOK_URL;
        const token = process.env.NUCLEIC_GIT_HOOK_TOKEN;
        const sub = firstSubcommand(args);
        let target = null;
        if (url) { try { target = new URL(url); } catch (e) { target = null; } }

        if (!token || !target || !sub || !REPORTABLE.has(sub)) {
          finish();
        } else {
          const body = JSON.stringify({
            argv: args, cwd: process.cwd(), exitCode: code,
            subcommand: sub, sessionId: process.env.NUCLEIC_SESSION_ID || null,
          });
          const req = http.request({
            hostname: target.hostname,
            port: target.port,
            path: target.pathname || '/git-event',
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              'Authorization': 'Bearer ' + token,
              'Content-Length': Buffer.byteLength(body),
            },
            timeout: 1500,
          }, function (res) { res.resume(); res.on('end', finish); res.on('error', finish); });
          req.on('error', finish);
          req.on('timeout', function () { req.destroy(); finish(); });
          req.end(body);
        }
        """#

    /// Source of the `gh` interceptor shim installed at `/usr/local/bin/gh` (ahead of `/usr/bin/gh`
    /// on PATH) in Nucleic Control containers. The structural twin of `gitInterceptorSource`: it
    /// runs real `gh` transparently (stdio inherited, exit status preserved), then — for the
    /// agentic-action nouns (`pr`, `issue`, `release`, `repo`, `api`, …) — reports the invocation to
    /// the host via the per-session callback (`NUCLEIC_GH_HOOK_URL`, `NUCLEIC_GH_HOOK_TOKEN`,
    /// `NUCLEIC_SESSION_ID`). The host re-classifies argv authoritatively (`GhCommandSummary`),
    /// dropping reads; reporting is strictly best-effort and never blocks or alters the gh call.
    /// If real `gh` is missing it exits 127 rather than masking the failure with the shim's own 0.
    nonisolated static let ghInterceptorSource = #"""
        #!/usr/bin/env node
        'use strict';
        // Nucleic gh interceptor. See ContainerEngine.ghInterceptorSource.
        const { spawnSync } = require('child_process');
        const http = require('http');

        const REAL_GH = process.env.NUCLEIC_REAL_GH || '/usr/bin/gh';
        const args = process.argv.slice(2);

        const result = spawnSync(REAL_GH, args, { stdio: 'inherit' });
        // A spawn error (e.g. real gh absent) must not be reported as success.
        const code = (result.status != null) ? result.status : (result.error ? 127 : (result.signal ? 1 : 0));

        let done = false;
        function finish() { if (done) return; done = true; process.exit(code); }

        // The first non-flag token is gh's noun (`pr`, `issue`, …); gh has no value-taking global
        // flags before it. The host decides verb-level mutating-vs-read.
        function firstNoun(a) {
          for (let i = 0; i < a.length; i++) {
            if (a[i].charAt(0) === '-') continue;
            return a[i];
          }
          return null;
        }

        // Report only the nouns that can carry an agentic action; the host (GhCommandSummary)
        // drops the read verbs (list/view/status) within them.
        const REPORTABLE = new Set([
          'pr','issue','release','repo','gist','workflow','run','secret','variable','label',
          'cache','ssh-key','gpg-key','api',
        ]);

        const url = process.env.NUCLEIC_GH_HOOK_URL;
        const token = process.env.NUCLEIC_GH_HOOK_TOKEN;
        const noun = firstNoun(args);
        let target = null;
        if (url) { try { target = new URL(url); } catch (e) { target = null; } }

        if (!token || !target || !noun || !REPORTABLE.has(noun)) {
          finish();
        } else {
          const body = JSON.stringify({
            argv: args, cwd: process.cwd(), exitCode: code,
            subcommand: noun, sessionId: process.env.NUCLEIC_SESSION_ID || null,
          });
          const req = http.request({
            hostname: target.hostname,
            port: target.port,
            path: target.pathname || '/gh-event',
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              'Authorization': 'Bearer ' + token,
              'Content-Length': Buffer.byteLength(body),
            },
            timeout: 1500,
          }, function (res) { res.resume(); res.on('end', finish); res.on('error', finish); });
          req.on('error', finish);
          req.on('timeout', function () { req.destroy(); finish(); });
          req.end(body);
        }
        """#
}
