fix(engine): make .seed_version marker authoritative (no boot refusal)
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 53s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m2s

The seed-drift guard shipped as refuse-boot: the backend exited when
BACKEND_DICT_VERSION disagreed with the flat dir's recorded .seed_version. On
the test contour that turned a harmless-in-intent action — bumping the
TEST_DICT_VERSION variable to the active release (v1.2.1) on a volume seeded as
v1.0.0 — into a crash loop, because DICT_VERSION is the *seed* of a fresh
volume, not the active version (which the admin console drives).

Make the marker authoritative instead: OpenWithVersions resolves the flat dir's
version from .seed_version when present and ignores bootVersion on an
already-seeded volume; bootVersion only seeds a fresh volume's marker. So a
bumped build seed on a live volume is a no-op (it can't relabel live bytes and
can't void games pinned to the prior label), and it correctly seeds the next
fresh volume. The subdirectory scan now skips the resolved seed, so a version
also present as a subdir (e.g. v1.2.1 uploaded via the console while the build
seed is bumped to v1.2.1) is still loaded rather than shadowed by the flat bytes.

Tests: marker-wins over a bumped boot version; a bumped boot keeps the matching
subdir resident (the live-contour case). Docs updated (ARCHITECTURE §5, READMEs,
compose/.env, PRERELEASE DV) from "refuses to boot" to "marker wins / ignored".

Verified locally against v1.2.1: gofmt, build, vet, unit, integration green.
This commit is contained in:
Ilia Denisov
2026-06-20 20:06:57 +02:00
parent d40fe1edec
commit 95f5703372
9 changed files with 119 additions and 69 deletions
+14 -12
View File
@@ -70,20 +70,22 @@ func Open(dir, version string, variants ...Variant) (*Registry, error) {
// immediate subdirectory of dir: a subdirectory named V contributes, under
// version V, the variants whose committed DAWG it carries. This is the
// restart-side of the admin dictionary reload — a version reloaded into dir/<V>/
// at runtime is resident again after a restart. A subdirectory named like the
// boot version is skipped (the flat dir already is the boot version). It records
// and enforces a seed-version marker on the flat dir (see checkSeedMarker), failing
// when the build seed was bumped on an already-seeded volume. A partially loaded
// at runtime is resident again after a restart. The flat dir's version is resolved
// from its .seed_version marker (see resolveSeedVersion): a fresh dir records
// bootVersion, an already-seeded dir keeps its recorded label and ignores bootVersion,
// so a bumped build seed never relabels live bytes. A subdirectory named like the
// resolved seed version is skipped (the flat dir already is it). A partially loaded
// registry is closed before any error is returned.
func OpenWithVersions(dir, bootVersion string) (*Registry, error) {
r, err := Open(dir, bootVersion)
// Resolve the flat dir's version from its seed marker first: on an already-seeded
// volume the marker wins and bootVersion is ignored, so a bumped build seed cannot
// relabel live bytes (see resolveSeedVersion).
seed, err := resolveSeedVersion(dir, bootVersion)
if err != nil {
return nil, err
}
// Guard the seed-drift footgun before scanning for additional versions: refuse
// to boot when the flat dir was seeded under a different version than bootVersion.
if err := checkSeedMarker(dir, bootVersion); err != nil {
_ = r.Close()
r, err := Open(dir, seed)
if err != nil {
return nil, err
}
entries, err := os.ReadDir(dir)
@@ -92,9 +94,9 @@ func OpenWithVersions(dir, bootVersion string) (*Registry, error) {
return nil, fmt.Errorf("engine: scan dictionary dir %s: %w", dir, err)
}
for _, e := range entries {
// Skip non-directories, the boot version (already loaded as the flat dir)
// and dot-prefixed directories (the upload staging area, dir/.staging/).
if !e.IsDir() || e.Name() == bootVersion || strings.HasPrefix(e.Name(), ".") {
// Skip non-directories, the resolved seed version (already loaded as the flat
// dir) and dot-prefixed directories (the upload staging area, dir/.staging/).
if !e.IsDir() || e.Name() == seed || strings.HasPrefix(e.Name(), ".") {
continue
}
if _, err := r.LoadAvailable(filepath.Join(dir, e.Name()), e.Name()); err != nil {