]> git.hungrycats.org Git - bees/commitdiff
config: separate frozen version aliases from tracking ones
authorZygo Blaxell <bees@furryterror.org>
Sat, 29 Aug 2026 18:36:56 +0000 (14:36 -0400)
committerZygo Blaxell <bees@furryterror.org>
Sat, 5 Sep 2026 04:03:58 +0000 (00:03 -0400)
The alias table maps every name to a version string, which makes two
quite different kinds of name look identical at the point of use.
STABLE and CURRENT resolve through build constants, so what they name
changes when a constant is bumped; v0.11 names one fixed schema and will
go on naming it however far development moves ahead.

That difference does not matter while a name is only ever a choice a
person makes in a config file.  It starts to matter as soon as a name can
be stored in structure that outlives the build that read it -- a schema's
inheritance edge, for one, where an edge written as CURRENT would
silently re-parent itself when the constant moved, and become a self-edge
once CURRENT reached that schema.

Record the distinction in the table rather than leaving it to be
rediscovered.  No behaviour changes.

Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Zygo Blaxell <bees@furryterror.org>
src/bees-config.cc
src/bees-config.h

index 80773a6bffe809ad15e5c18b445f7d19aff06d67..ffe59cb76b17645319c6e0c37170e75b36b26df9 100644 (file)
@@ -38,20 +38,28 @@ insert_map_unique(map<string, Value> &mr, const string &key, const Value &val)
        THROW_CHECK1(runtime_error, key, rv.second);
 }
 
-const map<string, string> &
+const map<string, BeesConfig::VersionAlias> &
 BeesConfig::version_aliases()
 {
-       static const map<string, string> s_aliases {
-               { "STABLE",  s_stable_version  },
-               { "CURRENT", s_current_version },
+       static const map<string, VersionAlias> s_aliases {
+               { "STABLE",  { s_stable_version,  false } },
+               { "CURRENT", { s_current_version, false } },
                // bees v0.11 shipped config schema version 1.  That schema is
                // frozen, so the release name goes on selecting it however far
                // CURRENT moves ahead.
-               { "v0.11",   "1"               },
+               { "v0.11",   { "1",               true  } },
        };
        return s_aliases;
 }
 
+string
+BeesConfig::resolve_version(const string &name)
+{
+       const auto &aliases = version_aliases();
+       const auto found = aliases.find(name);
+       return found == aliases.end() ? name : string(found->second.target);
+}
+
 BeesConfig::BeesConfig(const string &path, const Fd &fd) :
        m_root_fd(fd),
        m_root_path(path)
@@ -99,7 +107,7 @@ BeesConfig::BeesConfig(const string &path, const Fd &fd) :
 
        // Set version aliases
        for (const auto &i : version_aliases()) {
-               insert_map_unique(*s_defaults, i.first, s_defaults->at(i.second));
+               insert_map_unique(*s_defaults, i.first, s_defaults->at(i.second.target));
        }
 }
 
index 73df51a5bfdd4aaac8c664fd76d77de5918b429a..6fda8c7062b164efc21772c37ea0e25ee3f799aa 100644 (file)
@@ -49,18 +49,38 @@ class BeesConfig {
        /// that version 1 lacks.
        static constexpr const char *s_stable_version = "1";
 
-       /** \brief Named aliases accepted wherever a config schema version is.
+       /** \brief What a version alias resolves to, and whether it can move.
+
+           A frozen alias names one fixed schema and goes on naming it however
+           far development moves ahead, so it is safe to write anywhere a
+           reference outlives the build that read it: a config file, or a
+           schema's inheritance edge.
+
+           A tracking alias resolves through one of the build constants above
+           instead, so what it names changes when that constant is bumped.
+           That is what makes it useful as a choice a person makes at the
+           outermost layer, and what makes it unsafe as stored structure --
+           an inheritance edge written as CURRENT would silently re-parent
+           itself, and become a self-edge once CURRENT reached that schema.
+       */
+       struct VersionAlias {
+               /// Concrete schema version this name resolves to.
+               const char *target;
+               /// False when @c target follows a build constant and may move.
+               bool frozen;
+       };
 
-           Two kinds of name live here.  STABLE and CURRENT track this build
-           and therefore resolve through the constants above, so they name a
-           different schema as the constants move.  A release alias such as
-           "v0.11" names a frozen schema directly and keeps naming it no
-           matter how far development has moved on.
+       /** \brief Named aliases accepted wherever a config schema version is.
 
            Every consumer resolves through this one table so a name cannot
            mean one thing in the defaults map and another somewhere else.
        */
-       static const map<string, string> &version_aliases();
+       static const map<string, VersionAlias> &version_aliases();
+
+       /// Resolve @p name through version_aliases().  Returns @p name unchanged
+       /// when it is not an alias, so callers can pass either and check the
+       /// result against the registered versions once.
+       static string resolve_version(const string &name);
 
        /// Hierarchy of Innies (one per config layer)
        Innie m_argv;     ///< Values provided via command-line flags.