Merge the power fixes: multiplier, active gating, honest tiers, shield repair

F1 power supply multiplies the resolved value (operator ruling), so zero
supply means zero and a flat stat still responds. F2 the bus agrees with
Active in both directions. F3 an empty tier reports no demand instead of
full supply, and sub-epsilon demand is rationed and subtracted. F4/F5 the
shield restores while inactive and CanTakeHit no longer mutates. F6/F7 the
validator names every stat a request reads and the tools validate before
writing. F8 re-anchors two dead mutation harnesses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-19 06:04:12 +02:00
commit cd47eefe9e
21 changed files with 667 additions and 108 deletions

View file

@ -72,12 +72,16 @@ public static class CultRecordRefs
new CultRecordRef<T>(cache.TryGetHandle(document)?.Key ??
throw new InvalidOperationException($"This cache holds no such {document.GetType().Name}."));
// Writes a document to its home store; the cache mints its key the first time. Every catalog write in this
// repository (tools, tests, migration scripts) goes through this one function, so it is where R-heat's
// validation closes the gap AetheriaStores.Open leaves: Open only checks what is already on disk when a
// session starts, not what a session then writes. Validating here means an invalid heat response is refused
// before the write ever reaches disk, not just the next time someone reopens the file.
public static CultRecordRef<T> Upsert<T>(this CultCache cache, T document) where T : class
// F7 (docs/stats-and-power-cut.md, Soul pass 2026-09-19): the validation body itself, pulled out of Upsert
// below so it has exactly one owner. Every catalog write in this repository was supposed to go through
// Upsert, but three of tools/AetherDb/Program.cs's own migration commands (Dangling, ShieldMigrate,
// BrownoutMigrate) cannot: they repair an EXISTING record in place and must preserve its key, which Upsert's
// own cache.UpsertAsync(document) does not support (Upsert mints or resolves a key from the document itself,
// not from a caller-supplied one). Rather than leave those three writing raw and unvalidated -- as they did
// before this fix -- they call this method directly, immediately before their own identity-preserving
// batch.Upsert(document.GetType(), document, key) commit, so the same rules run either way. Takes `object`
// (not a generic T) because that is what those three tools' own (Document, Key) change lists are typed as.
public static void Validate(object document)
{
if (document is EquippableItemData data)
{
@ -92,6 +96,17 @@ public static class CultRecordRefs
StatValidation.ValidateNoPowerSupplyOnRequest(consumable.Name, consumable.Behaviors);
StatValidation.ValidateRoleUsage(consumable.Name, consumable.Roles, consumable.Behaviors);
}
}
// Writes a document to its home store; the cache mints its key the first time. Every catalog write in this
// repository (tools, tests, migration scripts) goes through this one function, or through Validate above
// directly when the caller must preserve an existing key (see Validate's own comment) -- so this is where
// R-heat's validation closes the gap AetheriaStores.Open leaves: Open only checks what is already on disk
// when a session starts, not what a session then writes. Validating here means an invalid heat response is
// refused before the write ever reaches disk, not just the next time someone reopens the file.
public static CultRecordRef<T> Upsert<T>(this CultCache cache, T document) where T : class
{
Validate(document);
return new CultRecordRef<T>(cache.UpsertAsync(document).Result.Key);
}
}