+------------------------------------+
| Package scopes and symlink routing |
+------------------------------------+

This document records the design decisions behind how glacier packages
are scoped, how their repo determines where their symlinks land, and
what "boot-critical" means in this system. It exists because these
rules aren't obvious from reading the code alone, and getting them
wrong (particularly the symlink-placement rule) has real consequences
up to and including destroying a live /usr.


+------------------------------------+
| 1. The two scopes                  |
+------------------------------------+

USR (GL_SCOPE_USR)
    Per-uid packages. Index/store/links all live under a specific
    uid's tree:
        /glacier/usr/index/<uid>
        /glacier/usr/store/<uid>
        /glacier/usr/links/<uid>
    Symlinks always land under /glacier/usr/links/<uid>/{bin,...}.
    There is no base-repo exception for USR scope. Nothing installed
    under a per-user scope is ever considered part of the base system,
    so nothing here ever touches /usr.

SYS (GL_SCOPE_SYS)
    System-wide packages, not owned by any particular uid:
        /glacier/sys/index
        /glacier/sys/store
    Symlink destination depends on the package's repo (see section 3).
    Requires root. gpkg enforces this via geteuid() before it will
    even attempt a system-scope operation.


+------------------------------------+
| 2. Repo semantics                  |
+------------------------------------+

A package's repo (PKG_REPO in its manifest) is not just a label for
organizing the store — under system scope, it is the one thing that
decides where that package's activation symlinks physically land.

    base
        Packages that make up a minimal working system. ALL packages
        in this repo are considered boot-critical BY DEFINITION — this
        is a repo-level classification, not a per-package flag. A
        package doesn't have to be individually essential for booting
        to count; if it's filed under base, it's treated as part of
        the base system, full stop.

    extra
        Software that may be important but is not necessary to run
        Everest, nor to install it.

    community
        Everything else.

    NOTE: extra and community are currently NOT distinguished by the
    library itself — both simply mean "not base," and both route to
    the same default system-scope links location. If they need to
    diverge further later (different confirmation prompts, different
    trust handling, whatever), that is a deliberate future change, not
    something already implemented.


+------------------------------------+
| 3. Symlink placement rule          |
+------------------------------------+

    scope == USR                         -> /glacier/usr/links/<uid>
    scope == SYS && repo == "base"       -> /usr              (*)
    scope == SYS && repo != "base"       -> /glacier/sys/links

(*) This is the base-system exception. Packages that are part of the
    minimal working system get their symlinks placed directly into
    /usr/bin, /usr/lib, etc. — real FHS locations — so that anything
    with a hardcoded path (init scripts, systemd units, shebang lines)
    works without needing glacier-aware PATH setup.

    Everything an operator installs under system scope that ISN'T
    part of the base system stays isolated under /glacier/sys/links,
    same as it always has. The base system is the exception to the
    norm, not the other way around.

This decision is made per-package, at the point where a package's repo
is actually known (inside gl_link_pkg / gl_unlink_pkg / the walk loop
in gl_relink_store) — NOT baked into the context at creation time.
gl_context_t's links_path field always holds the DEFAULT destination
for that scope; the /usr override is computed separately per package.


+------------------------------------+
| 4. Why /usr as a target is safe    |
+------------------------------------+

/usr is not glacier-exclusive territory the way /glacier/sys/links is.
It holds plenty of content glacier has no business touching. Every
place that reads or writes into a links destination therefore treats
that destination as "possibly shared, not owned":

  - is_glacier_symlink(path, store_prefix) is the single source of
    truth for "did glacier itself create this." It reports true only
    if `path` is a symlink whose target lives under the relevant
    store path. Anything else — a real file, a directory, a foreign
    symlink pointing somewhere else entirely — is never touched.

  - gl_link_pkg refuses to overwrite an existing path unless
    is_glacier_symlink says it's safe to replace. It will not clobber
    a foreign file that happens to occupy the same path.

  - gl_relink_store no longer wipes and rebuilds its links directory
    wholesale (that was safe only when the target was glacier-only
    territory). It prunes ONLY stale symlinks glacier itself created
    (identified via is_glacier_symlink, removed only if their store
    target no longer exists) and leaves everything else alone.

  - A single system-scope relink pass prunes BOTH possible
    destinations (/glacier/sys/links and /usr), since it can't know in
    advance whether any base-repo packages are involved without
    checking both.

  - The stage tree's links directory is no longer hardlink-seeded at
    all. It was always unused (gl_link_pkg only ever runs against a
    LIVE context; gl_relink_store always builds its own fresh live
    context rather than touching a staged one) — and now that live
    links_path can be /usr, seeding it would mean hardlinking the
    entire /usr tree on every staged system transaction, and risking
    EXDEV outright if /usr and the stage area are on different
    filesystems.


+------------------------------------+
| 5. Updating base-system packages   |
+------------------------------------+

Base-repo packages are tracked in the system index like anything else,
and updating them works exactly the same way updating any other
package does — same staged transaction, same commit/rollback. No
separate mechanism was built for this, because none was needed:

    gpkg -s newmusl.gpkg

...updates musl in place under system scope, symlinks and all,
whether it was originally installed by a bootstrap tool or by a normal
`gpkg -s` call later. There is no meaningful distinction between
"how it originally got there" and "how you update it."

Replacing something as fundamental as libc on a LIVE running system is
safe under this model specifically because of POSIX unlink semantics:
a process that already has a shared object open keeps working off the
old inode even after the symlink swap happens. Only newly-spawned
processes after the swap see the new version. This falls directly out
of the atomic-rename commit design already in place — nothing extra
was added to make it work.


+------------------------------------+
| 6. Open items                      |
+------------------------------------+

  - grootstrap has not been rebuilt against this design yet. The
    simplification this design enables: once gpkg/gstore themselves
    exist on the target (still a bootstrapping problem that needs
    solving separately), EVERY package — base included — can go
    through the same `gpkg -s -l` path. There is no longer a need for
    a separate raw-extraction special-case for base packages
    specifically; the repo alone routes them to /usr correctly.

  - extra vs community currently behave identically (see section 2).
    Whether they should diverge, and how, is undecided.

  - The bootstrapping-a-bootstrapper problem (getting gpkg/gstore onto
    a target filesystem before there's a working package manager to
    install them with) is still unsolved and orthogonal to everything
    in this document.
