+--------------------------------------------+
| Glacier roadmap                            |
+--------------------------------------------+

What's left to build, in priority order. Status as of 2026-10-07.
Design detail lives in the companion docs; this file says WHAT and in
what order, and links to WHERE:

    SECURITY_DESIGN.txt   integrity, scope separation, refusal matrix
    PACKAGE_SCOPES.txt    layout, link routing, /etc config handling
    IMAGE_DESIGN.txt      system images as bootstrap output, sysroot
    REPO_DESIGN.txt       remote repositories, signing, keyring
    GLACIER_BOOTSTRAP.txt glacier-bootstrap plan, prerequisites, decisions

Mark an item [DONE yyyy-mm-dd] rather than deleting it, so the history
stays readable. CLAUDE.md "Known open items" tracks small bugs; this
file tracks work.


+--------------------------------------------+
| 1. Install Everest with glacier alone      |
+--------------------------------------------+

The critical path: from "QEMU boots an initramfs assembled by a script"
(~/Projects/everest) to "glacier installs a system".

  1.1  [DONE 2026-10-05: see CLAUDE.md "Install root / sysroot" and
       tests/sysroot_alpine.sh.]
       sysroot in the library (IMAGE_DESIGN section 3). Every path is
       hardcoded to /glacier and /usr. Needs separate install-root
       (where bytes are written) and runtime-root (what symlink targets
       say). The trap: prune_stale_links follows absolute links, so a
       naive prefix would judge the image's links against the HOST's
       filesystem. Required test: an image build leaves the host's /usr
       and /glacier byte-identical.

  1.2  [DONE 2026-10-05: -R/--root, strict by default under -R, --strict.]
       syspkg --root DIR (refuses unless DIR and its ancestors are
       root-owned and not group/world-writable) and --strict (any link
       refusal fails the transaction; an image's /usr starts empty).
       gl_create_fs_tree already takes a root prefix.

  1.3  [DONE 2026-10-06: glacier-ng/glacier-bootstrap, GLACIER_BOOTSTRAP
       section 10; Everest's initramfs is built with it and boots.]
       glacier-bootstrap, local mode (GLACIER_BOOTSTRAP.txt sections 1-6).
       Template index -> resolve -> verify -> syspkg --root, one
       transaction. Never writes into TARGET itself.
       [DONE 2026-10-07: repositories. TARGET's repos.conf + keys come
       from a package (Everest: everest-repos); bootstrap then runs
       syspkg -R TARGET -y, which reads TARGET's own keyring; exit 7 on
       a failed fetch. GLACIER_BOOTSTRAP section 10. Later, maybe: an
       opt-in copy of the host's repos.conf (pacstrap's mirrorlist).]

  1.4  [DONE 2026-10-06: built by ~/Projects/everest/scripts/container.sh,
       in templates/minimal.template, boot-verified in QEMU; full
       regression passes.]
       Packages for glacier itself, so an installed system can manage
       itself: luajit, libarchive (+ zlib), libconfig, libglacier-ng,
       glacier-ng (syspkg, gpkg, gstore), doas.
       Decided 2026-10-06:
         - base: luajit, zlib, libarchive, libconfig, libglacier-ng,
           glacier-ng. extra: doas (replaceable, e.g. by sudo).
         - glacier-ng ships syspkg, gpkg and gstore only. glacier-bootstrap,
           gbuild, gworld and gsha256sum become a later "glacier-tools"
           package in extra (after the release, which brings a real
           install Makefile).
         - everest-bootscripts ships /etc/doas.conf: `permit :wheel`, plus
           `permit nopass :wheel as root cmd /usr/bin/gstore args -n`.
         - Layout: scripts + lib/ in usr/lib/glacier-ng/, usr/bin/{syspkg,
           gpkg,gstore} symlink into it (realpath lands in the store, lib/
           beside it); gpkg moves to the same real-path loading.
       As built: zlib 1.3.2, libarchive 3.8.9 (gzip + tar only; every
       other codec, crypto library and front-end disabled), libconfig
       1.8.2 (no C++), luajit 2.1.1788856981 (commit c6ffc141),
       libglacier-ng 4.0.0, glacier-ng 4.0.0, doas 6.8.2 (OpenDoas, no
       PAM, no timestamp). The doas.conf rule is given for both `cmd
       gstore` and `cmd /usr/bin/gstore`: OpenDoas matches what was typed.
       luajit is linked -static-libgcc, since Everest ships no libgcc_s
       (see section 6).

  1.5  [DEFERRED 2026-10-06 by the user: manual installation is one of
       Everest's main features, so the installer comes later. 3.1 is
       next.]
       [2026-10-07, the user: a manual install works from a live Everest
       system or any Linux host; the live system is the preferred,
       officially supported way. The user is trying the first disk
       install (QEMU) themselves.]
       The installer. Undesigned; needs its own design session
       (SECURITY_DESIGN section 10). Partitioning, a bootloader or an
       EFI-stub kernel on the ESP, copying the kernel there (a symlink
       won't do on FAT), a disk image.

  Already done on this path: hash verification, package symlinks, the
  merged-/usr tree on syspkg -n, /etc config handling, a QEMU-bootable
  musl + busybox + linux + everest-bootscripts set.


+--------------------------------------------+
| 2. Package-manager features                |
+--------------------------------------------+

  2.1  UPGRADES. Spec (2026-10-04):
         - Replacing an installed version is `-lO` (local file) — plain
           -l refuses ("extra::foo 1.0.0 is already installed.", nothing
           more). [DONE 2026-10-04]
         - The replaced version is BACKED UP: its files compressed into
           <scope>/backup/<repo>/<name>/<name>-<ver>.gpkg + .sha256, a
           complete installable package (saved manifest included).
           KEEP_OLD in glacier.conf = how many backups to keep per
           package (0 = none). No default: glacier.conf ships with
           KEEP_OLD = 1, and an operation that would replace a version
           refuses if it's missing or invalid. [DONE 2026-10-04]
         - A restore interface for backups: later. (A backup can already
           be reinstalled with -lO.)
         - `gpkg -u` / `syspkg -u` = upgrade from a REMOTE repository;
           waits for 3.1, like -f. [DONE 2026-10-07: REPO_DESIGN
           section 6, "As implemented".]
         - /etc follows the existing config rules regardless.

  2.2  [DONE 2026-10-07: REPO_DESIGN section 6, "As implemented".]
       gpkg -f / syspkg -f: MERGE. Spec (2026-10-04): exactly like -l,
       except the package comes from a remote repository by name
       instead of from a local file. Same summary, verification, scope
       rules and transaction. Depends on 3.1 (remote repositories +
       signing): until then there is nothing safe to fetch from.

  2.3  Dependencies. [DONE 2026-10-05; automatic closure DONE
       2026-10-07 with -f/-u (REPO_DESIGN section 6).] Spec (2026-10-05), all three checks in one pass, no override flag,
       every problem reported (one line each) before failing:
         - A dependency's PKG_VER is a MINIMUM (installed >= PKG_VER).
           Matched by name, any repo.
         - A user package's dependency may be met by system scope or the
           user's own scope. syspkg -x checks system-scope dependents
           only.
         - Conflicts: both directions (new lists installed, or installed
           lists new), by name; PKG_VER is ignored.
         - Missing dependencies REFUSE until 3.1; then automatic closure
           (GLACIER_BOOTSTRAP 9e).
         - Index records auto vs explicit now: <ver>/.glacier-auto in the
           store, index line repo::name::version::auto|explicit.
         - Messages:
             extra::foo requires bar >= 1.2.0, which is not installed.
             Removing base::musl breaks extra::foo.
             extra::foo conflicts with extra::bar.
         - install: refuse if a dependency isn't installed (or in the
           same transaction);
         - REMOVE: refuse to remove a package others depend on. Today
           nothing stops `syspkg -x musl` from breaking every binary on
           the system. Highest-value item in this section;
         - conflicts: refuse to install a package that conflicts with
           an installed one.
       9e DECIDED 2026-10-05: automatic closure, after 3.1 (see above).

  2.4  Queries: list installed packages, show a package's details, find
       which package owns a file. The index has the data; no CLI shows it.

  2.5  Rollback. Every commit moves the previous state to old/ and then
       deletes it. Keep it (or the last N), plus a command to return to
       it. Interacts with 2.1's KEEP_OLD.

  2.6  [DONE 2026-10-05: refused in every scope, before the prompt and at
       commit (deps.c check_collisions).]
       File conflicts between packages: two packages shipping the same
       path — whichever links last silently wins. Detect and refuse.

  2.7  glacier.conf parsing. [DONE 2026-10-04 for integers:
       gl_conf_get_int, no defaults, missing/unreadable/wrong-type are
       distinct errors.] Still open: rejecting UNKNOWN keys (CLAUDE.md bug
       10's lesson — a misspelled KEEP_OLD currently reads as "not set",
       which at least fails loudly).

  2.8  gstore: -s (sync) is a stub; there's no restore command although
       gstore -b's output tells you to run -r (gl_restore_istore exists).

  2.9  User-scope config files: user packages still link etc/ into a
       links tree that's rebuilt on every commit.

  2.10 Hardlink entries in package archives are skipped at extraction.


+--------------------------------------------+
| 3. Security                                |
+--------------------------------------------+

  3.1  Remote repositories + signing (authenticity, keyring). The agreed
       next major item. Design: REPO_DESIGN.txt (2026-10-06; decisions in
       sections 1 and 10, order of work section 9). [IN PROGRESS
       2026-10-06: steps 1 and 2 DONE (gsign; the local base rule,
       repos.conf + keyring, gbuild -k, bootstrap carries .sig, Everest
       signs with everest-2026) and 3 (grepo; the signed index;
       syspkg/gpkg -y refresh with rollback state) DONE; step 4 (-f with
       closure, -u, the cache) DONE 2026-10-07; step 5 (glacier-
       bootstrap -r) next, then 6 (user repos).] Unblocks remote
       glacier-bootstrap. Until then, GLACIER_BOOTSTRAP's pinned
       templates are the safe bridge for remote fetches.

  3.2  Hash verification lives in the CLIs' shared module. A third-party
       tool calling gl_install_pkg directly isn't verified. Consider
       moving it into the library, like the base-in-user-scope refusal.

  3.3  Manifests don't record the SYS_PROFILE they were built under, so a
       package built for the wrong target can't be detected.

  3.4  gstore -n accepts UIDs with no passwd entry (pre-provisions a
       directory nobody owns yet; a typo creates a stray one).


+--------------------------------------------+
| 4. Build tooling                           |
+--------------------------------------------+

  4.1  gbuild system profiles (-P ARCH-LIBC[-FEATURE...]) and gworld's
       SYS_PROFILE recipe field. CLAUDE.md described both as done; neither
       exists in the tree (only one gbuild, in glacier-ng). Today the
       target libc is decided by where gbuild runs (~/Projects/everest
       builds in Alpine for musl).
       [2026-10-07, the user: Everest will run under glibc as well as
       musl, so profiles are a real requirement, not a nicety. The
       libc is picked at install time (Gentoo/Void style), one per
       system. Open: how packages are kept apart (section 6).]

  4.2  Move ~/Projects/everest's per-package gbuild commands into gworld
       recipes once 4.1 lands.


+--------------------------------------------+
| 5. Performance (not urgent)                |
+--------------------------------------------+

  5.1  gl_relink_store / gl_rebuild_index rescan the whole store on every
       commit. Fine at current scale; a dirty set is the fix.


+--------------------------------------------+
| 6. Decisions waiting                       |
+--------------------------------------------+

  - Images are only ever bootstrap OUTPUT (IMAGE_DESIGN 9a /
    GLACIER_BOOTSTRAP 9a).
  - Where the pinning helper lives (GLACIER_BOOTSTRAP 9f).
  - 2.1's open questions a-d (upgrade backups).
  - extra vs community: should they ever behave differently?
  - The installer (1.5), deferred.
  - libgcc (1.4): luajit is built with -static-libgcc because Everest
    has no libgcc_s. Claude's call, not yet reviewed; the alternative is
    a GCC runtime package in base, which later C++/unwinding packages
    would need anyway.
  - glibc and musl (4.1): DECIDED 2026-10-07 that the user picks the
    libc at install time, like Gentoo's profiles / Void's glibc and musl
    ISOs; one libc per system. Still open: how packages are kept apart
    (Claude recommends both per-libc repos, as Void, AND a libc field in
    manifest + index that the library refuses on a mismatch with the
    system's recorded libc).


+--------------------------------------------+
| 7. Done                                    |
+--------------------------------------------+

  [DONE 2026-10-07] -f and -u from repositories (3.1 step 4; 2.1's -u,
  2.2, 2.3's closure): first repo serving a name wins; missing deps
  added auto, too-old ones upgraded; downloads in <base>/cache/REPO,
  hashed against the signed index, manifest must match the entry,
  deleted after commit. tests/fetch_alpine.sh (84 checks, 17 mutants).

  [DONE 2026-10-06] Packages for glacier itself (1.4): zlib, libarchive,
    libconfig, luajit, libglacier-ng, glacier-ng in base, doas in extra;
    everest-bootscripts 1.1.0 ships /etc/doas.conf. Everest's initramfs
    installs all 11 packages; in QEMU, syspkg/gpkg run from the store,
    syspkg -x musl is refused (dependents), a wheel user runs doas
    gstore -n without a password, then gpkg -l. gpkg loads lib/ from its
    realpath. tests/installed_alpine.sh (30 checks) installs glacier in
    that layout and runs it from the store. Found on the way: gbuild's
    clone warning broke builds from a commit hash (CLAUDE.md bug 28);
    syspkg/gstore never checked the ancestors of libglacier-ng's store
    dir, which the installed .so's live in (bug 29; both mutations
    caught).
  [DONE 2026-10-07] glacier-bootstrap configures repositories (1.3):
    syspkg -R's -y/-f/-u use TARGET's own repos.conf and keys (each
    file's real path must stay inside TARGET); bootstrap runs
    syspkg -R T -y when T's repos.conf gives a url (exit 7 on failure,
    a warning when none does). Everest: new base package everest-repos
    1.0.0 (/etc/glacier/repos.conf + everest-2026.pub), moved out of
    everest-bootscripts (now 1.3.0). bootstrap_alpine.sh 100 checks
    (6 mutants, all caught); repo/fetch suites updated; full regression
    green; Everest rebuilt and booted.
  [DONE 2026-10-06] glacier-bootstrap, local mode (1.3): template
    (NAME=, PKG=name ver [sha256:pin]; strict), verified private copies
    handed to syspkg -R in one transaction, template-level dependency /
    conflict / name checks, -n, -t. tests/bootstrap_alpine.sh (77
    checks, 11 mutations caught). ~/Projects/everest's initramfs is now
    a glacier-installed system (templates/minimal.template), booted in
    QEMU: /glacier/sys, merged-/usr links into the store, /etc copied,
    a non-root user runs busybox.

  [DONE 2026-10-05] syspkg -R/--root DIR + --strict (1.2) and file
    collisions (2.6): gl_check_sysroot (root-owned, non-symlink, not
    group/world-writable, every ancestor), ctx->strict (a would-be link
    refusal fails the transaction before the prompt and at commit; -O
    still displaces), two packages shipping one path refused in every
    scope. tests/root_alpine.sh (50 checks, 7 mutations caught). gpkg
    silently ignored unknown flags (CLAUDE.md bug 27).
  [DONE 2026-10-05] sysroot in the library (1.1): *_at contexts and
    gl_init_sys_at, install paths in the context with runtime translation
    at link/prune/displace, /usr and /etc under the sysroot, user scope
    refuses one. tests/sysroot_alpine.sh (33 checks, 8 mutations caught).
    Found on the way: live-context removal deleted foreign files
    (CLAUDE.md bug 26).
  [DONE 2026-10-05] Dependencies and conflicts (2.3): src/deps.c
    (gl_check_plan before the prompt; gl_check_txn enforced by
    gl_commit_transaction; live-context installs/removals checked inside
    gl_install_pkg/gl_remove_pkg), index field 4 auto|explicit +
    .glacier-auto + gl_set_pkg_auto, gl_read_pkg_manifest split out of
    gl_install_pkg. tests/deps_alpine.sh (41 checks, 8 mutations caught).
    Found on the way: the live index was rewritten through a seeded
    hardlink (CLAUDE.md bug 25), tests/test_index_abort.lua.
  [DONE 2026-10-05] System scope world-readable (found while specifying
    2.3): /glacier/sys was root 0700 throughout, so non-root users could
    run nothing syspkg installed. Dirs 0755 / index 0644, and created
    modes no longer depend on the umask. tests/sys_perms_alpine.sh.

  [DONE 2026-10-04] Version replacement + backups (2.1): -lO replaces,
    KEEP_OLD backups, gl_backup_pkg / gl_prune_backups / gl_conf_get_int,
    the manifest saved in the store (.glacier-manifest), add_to_archive
    learns symlinks (gstore -b silently dropped them too), gpkg modifier
    flags (-O -S -V) order-independent so `gpkg -lO` works.
    tests/upgrade_alpine.sh (29 checks).
  [DONE 2026-10-04] Correctness batch:
    - gbuild: autotools detected before a bare Makefile (bug 8 had
      regressed); --cross-prefix fixed (its option-table key was
      misspelled); -v/--version print the version as documented, with
      --verbose for verbose output.
    - gpkg -x: summary resolved from the index (was "unknown::pkg
      0.0.0"); a package that isn't installed fails before the prompt.
    - gpkg: precise refusal for a missing / squatted / loosened user
      directory (was "No index found"), via gl_usr_base_status.
    - gpkg -h lists -l and -S; -f/-u described by the 2.1/2.2 specs.
    - Up-to-date reinstall logs once.
    - Link conflicts reported once, by the package that has them, not
      again on every later system commit (.glacier-fresh marker).
    - A failed extraction no longer leaves a half-extracted version dir
      (in a live context, every retry then said "already installed").
    - Dead old-layout code removed from pkg.c (gl_prepare_pkgdir and the
      gl_usr_* / gl_sys_* dir_t constants).
    - tests/glacier_ffi.lua is a wrapper over lib/glacier_cdef.lua; its
      own copy had already drifted (missing GL_INSTALL_ERR_SCOPE).
    - Dockerfile.arch / .debian moved to the per-uid layout (root-owned
      /app, libglacier-ng built from source, the gstore -n doas rule).
  [DONE 2026-10-04] Hash verification; package symlinks; per-uid user
    layout + doas gstore -n; syspkg -n top-level tree; /etc config
    handling (copy + .new); -O/--overwrite; everest-bootscripts.
