Slice 6 of the dep-fetcher plan wired engine.install_root() and
engine.config(key) as Lua-level bindings. Use them when available;
keep the slice-5 env-var + hardcoded-URL fallbacks so this lib still
loads correctly under engines that pre-date the bindings.
Resolution chain is now:
install_root : opts -> engine.install_root() -> SPOREL_INSTALL_ROOT
gitea_base : opts -> engine.config('gitea_base') -> hardcoded default
No version bump: behavior unchanged for callers that pass opts.* or
that run under engines without the new bindings.
Pure-Lua manifest-walker for the dep-fetcher slice 5. Reads a module's
manifest.module, walks the transitive lib-dep tree, detects pin
conflicts (hard-fail), and uses lib-core.git to ensure every lib in
the closure is at the pinned v<X.Y.Z> tag.
State-check covers cases A-E from the dep-fetcher architecture spec:
missing (clone), clean (no-op), dirty (warn), wrong-tag (silent
checkout), broken (error).
Ships an inline JSON decoder (~80 lines, deterministic, no deps)
because the engine has no generic engine.json.decode binding yet —
engine.asset.load_json is sandboxed to the asset-tree and cannot read
fixture/install paths.
Stop-gaps documented in the lib (resolved by slice 6):
- opts.install_root substitutes for engine.install_root() binding
- gitea_base hardcoded; slice 6 sources from engine.json via
engine.config(key)
Gitea-slug derivation handles the .git-suffix exception: lib-core.git
maps to sporel-lib-core.git-lib because Gitea reserves the *.git
suffix pattern. Encoded as generic predicate so future .git-suffixed
libs work without code change.