From 4a16943d3eecbf9467b4456c849a8797bd25e31c Mon Sep 17 00:00:00 2001 From: Loretta Date: Wed, 15 Jul 2026 10:16:26 +0200 Subject: [PATCH] Centralize environment state in AcEnv framework Refactored FruitBankConst and FruitBankConstClient to delegate PROD/DEV environment logic to AcEnv.IsProdDb, removing local state and events. Updated MGGRID_TODO.md with ADR reference. Added ADR 0002 documenting the migration of environment infrastructure (BaseUrl, env state, badge) to the framework. Updated settings.local.json with new doc/code read commands and improved build error reporting. --- .claude/settings.local.json | 9 ++- FruitBank.Common.Server/FruitBankConst.cs | 17 +++-- FruitBank.Common/FruitBankConstClient.cs | 29 +++------ ...environment-infrastructure-to-framework.md | 63 +++++++++++++++++++ docs/adr/README.md | 1 + 5 files changed, 89 insertions(+), 30 deletions(-) create mode 100644 docs/adr/0002-environment-infrastructure-to-framework.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json index b0dab7b9..df15baad 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -100,7 +100,14 @@ "PowerShell(\\(Get-Content \"H:\\\\Applications\\\\Aycode\\\\Source\\\\AyCode.Blazor\\\\AyCode.Blazor.Components\\\\docs\\\\MGGRID\\\\MGGRID_TODO.md\" | Measure-Object -Line\\).Lines)", "PowerShell(\"=== FruitBank.Common ===\")", "Bash(git check-ignore *)", - "PowerShell(dotnet build \"H:\\\\Applications\\\\Mango\\\\Source\\\\FruitBankHybridApp\\\\FruitBank.Common.Server\\\\FruitBank.Common.Server.csproj\" -v q --nologo 2>&1 | Select-String -Pattern \"error CS\" | Select-Object -First 20; \"--- CS hibak szama: ---\"; \\(dotnet build \"H:\\\\Applications\\\\Mango\\\\Source\\\\FruitBankHybridApp\\\\FruitBank.Common.Server\\\\FruitBank.Common.Server.csproj\" -v q --nologo 2>&1 | Select-String -Pattern \"error CS\" | Measure-Object\\).Count)" + "PowerShell(dotnet build \"H:\\\\Applications\\\\Mango\\\\Source\\\\FruitBankHybridApp\\\\FruitBank.Common.Server\\\\FruitBank.Common.Server.csproj\" -v q --nologo 2>&1 | Select-String -Pattern \"error CS\" | Select-Object -First 20; \"--- CS hibak szama: ---\"; \\(dotnet build \"H:\\\\Applications\\\\Mango\\\\Source\\\\FruitBankHybridApp\\\\FruitBank.Common.Server\\\\FruitBank.Common.Server.csproj\" -v q --nologo 2>&1 | Select-String -Pattern \"error CS\" | Measure-Object\\).Count)", + "PowerShell(git -C \"H:\\\\Applications\\\\Mango\\\\Source\\\\FruitBankHybridApp\" show --stat --name-only dbc7fd3)", + "Read(//h/Applications/Aycode/Source/AyCode.Core/AyCode.Services/docs/**)", + "Read(//h/Applications/Aycode/Source/AyCode.Core/AyCode.Services/docs/SIGNALR_DATASOURCE/**)", + "Read(//h/Applications/Aycode/Source/AyCode.Core/AyCode.Services.Server/docs/**)", + "Read(//h/Applications/Aycode/Source/AyCode.Core/AyCode.Services/SignalRs/**)", + "Read(//h/Applications/Aycode/Source/AyCode.Core/AyCode.Services.Server/SignalRs/**)", + "WebFetch(domain:raw.githubusercontent.com)" ] } } diff --git a/FruitBank.Common.Server/FruitBankConst.cs b/FruitBank.Common.Server/FruitBankConst.cs index ab279d71..b41b9f2e 100644 --- a/FruitBank.Common.Server/FruitBankConst.cs +++ b/FruitBank.Common.Server/FruitBankConst.cs @@ -27,9 +27,10 @@ namespace FruitBank.Common.Server /// public const string PreOrderWindowEnd = "PreOrderWindowEnd"; - // ── Environment flag — method-only surface on purpose: no property to bind/serialize/read accidentally ── - - private static bool? _isProd; + // ── Environment flag → AcEnv.IsProdDb (AyCode.Core framework, 2026-07) ── + // Egy-forrás keret-állapot (ugyanaz, amit a kliens tükröz). A method-only felület szándékosan megmarad: + // nincs property, amit véletlenül bind-olni/szerializálni lehetne. A set-once őr (a szerver DB-je nem + // változhat futásidőben) itt marad — az AcEnv-írás előtt. /// /// Set once by the plugin startup after DB-catalog classification. @@ -37,19 +38,17 @@ namespace FruitBank.Common.Server /// public static void SetIsProd(bool isProd) { - if (_isProd.HasValue && _isProd.Value != isProd) + if (AcEnv.IsProdDbHydrated && AcEnv.IsProdDb != isProd) throw new InvalidOperationException( - $"IsProd is already set to {_isProd.Value}; refusing to change it to {isProd}."); + $"IsProd is already set to {AcEnv.IsProdDb}; refusing to change it to {isProd}."); - _isProd = isProd; + AcEnv.SetIsProdDb(isProd); } /// /// True = PROD database. Throws until SetIsProd ran — an uninitialized read must never return a default. /// - public static bool IsProd() - => _isProd ?? throw new InvalidOperationException( - "IsProd has not been initialized — SetIsProd must run at startup before any read."); + public static bool IsProd() => AcEnv.IsProdDb; static FruitBankConst() { diff --git a/FruitBank.Common/FruitBankConstClient.cs b/FruitBank.Common/FruitBankConstClient.cs index f02fdf00..093e820f 100644 --- a/FruitBank.Common/FruitBankConstClient.cs +++ b/FruitBank.Common/FruitBankConstClient.cs @@ -17,30 +17,19 @@ public static class FruitBankConstClient set => AcEnv.BaseUrl = value; } - // A szerver-környezet (PROD/DEV) — az érték KIZÁRÓLAG a szerver IsProdEnvironment() válaszából jöhet - // (a szerver a DB-katalógusból sorolja be magát, fail-closed módon), URL-alakból/configból SOHA. - // Az EGYETLEN értékadási pont: FruitBankSignalRClient (kapcsolat-felépüléskor hidratál, ld. EnsureEnvironmentAsync). - // Hidratálatlan olvasás → exception (fail fast); sikertelen lekérdezés → marad hidratálatlan (fail closed). - private static bool? _isProd; - public static bool IsProd - => _isProd ?? throw new InvalidOperationException( - "FruitBankConstClient.IsProd nincs hidratálva — a FruitBankSignalRClient a kapcsolat felépülésekor tölti fel a szerver IsProdEnvironment() válaszából (await client.EnsureEnvironmentAsync())."); + // IsProd → AcEnv.IsProdDb (AyCode.Core framework, 2026-07): a szerver ADAT-környezete (a mögöttes DB PROD-e), + // NEM build-konfiguráció. Delegát az egy-forrás keret-állapotra (fail-fast + változás-esemény) — a hívóhelyek + // változatlanok. Az érték forrása: a FruitBankSignalRClient a kapcsolat felépülésekor hidratál (EnsureEnvironmentAsync). + public static bool IsProd => AcEnv.IsProdDb; + public static bool IsProdHydrated => AcEnv.IsProdDbHydrated; + public static void SetIsProd(bool isProd) => AcEnv.SetIsProdDb(isProd); - /// Az IsProd TÉNYLEGES változásakor tüzel (első hidratálás + announce-detektált env-váltás) — - /// a megjelenítők (title-badge, UI-sáv) feliratkozási pontja. Az értékadás forrása változatlanul EGY hely. - public static event Action? IsProdChanged; - - public static void SetIsProd(bool isProd) + public static event Action? IsProdChanged { - var changed = _isProd != isProd; - _isProd = isProd; - - if (changed) IsProdChanged?.Invoke(isProd); + add => AcEnv.IsProdDbChanged += value; + remove => AcEnv.IsProdDbChanged -= value; } - /// True, ha az IsProd már hidratált — kapu-logikához (első-connect await) és a push-összevetéshez. - public static bool IsProdHydrated => _isProd != null; - public static string DefaultHubName = "fbHub"; public static string LoggerHubName = "loggerHub"; diff --git a/docs/adr/0002-environment-infrastructure-to-framework.md b/docs/adr/0002-environment-infrastructure-to-framework.md new file mode 100644 index 00000000..41288598 --- /dev/null +++ b/docs/adr/0002-environment-infrastructure-to-framework.md @@ -0,0 +1,63 @@ +# ADR 0002: Környezet-infrastruktúra (BaseUrl, szerver-adatkörnyezet, env-badge) keretbe emelése + +## Status + +Accepted (2026-07-15) — végrehajtás lépésenként, per-lépés review-val; 1. lépés (BaseUrl → AcEnv) kész. + +## Context + +- **Kiváltó incidens:** a fbHub és a loggerHub URL-je két külön forrásból élt (`AcHubConnection:Url` appsettings vs. `FruitBankConstClient.BaseUrl` kód-konstans) → a kliens a helyi nop helyett a PROD `shop.fruitbank.hu`-ra töltötte fel a logokat, órákig félrevitt nyomozással. A gyökér: az URL-infrastruktúra a fogyasztói const-osztályon, több igazságforrással. +- **Frissen épült env-lánc (2026-07):** szerver-adatkörnyezet (PROD/DEV) kliens-oldali ismerete — first-connect kapu (await, fail-closed), szerver push-on-connect announce (env-váltás riasztással), `[PROD]/[DEV]` title-badge, MSTest prod-guard. Élesben igazolva (DEV→PROD DB-váltás restarttal: announce + riasztás + title-váltás működött). **Mindez FruitBank-oldalon született, pedig app-agnosztikus.** +- **Redundancia:** ugyanaz a fogalom kétszer — szerver `FruitBankConst.SetIsProd` + kliens `FruitBankConstClient.IsProd`; a teszt-guard, badge, jövőbeli destruktív-védelem mind külön-külön kötné be. +- **„IsProd" tengely-probléma:** három független „prod"-tengely létezik — (1) build-konfiguráció (Debug/Release), (2) adat-környezet (melyik DB-katalógus), (3) hosting env (ASPNETCORE_ENVIRONMENT). A Debug-build + PROD-DB + Development-hosting kombináció élő teszttel bizonyított — egyetlen `IsProd` név ezeket összemosná. +- **Időzítés:** a közös nagyker-réteg terv (VeggieVoyage) előtt; a ház-mintában van közvetlen precedens: **GridLayout-roaming** (framework-fenntartott tagek `AcSignalRTags` 9001x + framework-kliens-logika + fogyasztói endpoint-implementáció). +- **Scope IN:** AcEnv állapotok (BaseUrl, IsProdDb); framework-tagek; kliens-base env-gépezet; hub-base announce; Blazor title-réteg; FruitBank-vékonyítás delegátokkal. **Scope OUT:** hosting-env tengely (később, read-only diagnosztika); `IsProdBuild` (független bónusz); `AcEnv.SetConfiguration` config-egységesítés (ACCORE-LOG-T-N2D8 — szinergia, nem feltétel). + +## Decision + +**A környezet-infrastruktúra a keretbe kerül** — a fogyasztó csak opt-inel, endpointot implementál és domain-konstansokat tart: + +1. **Egy-forrás állapotok az `AcEnv`-en** (AyCode.Core): `BaseUrl` ✅ (kész) és `IsProdDb` (+`Set`/`Hydrated`/`Changed` esemény) — a szervernél a DB-katalógus-besorolás tölti (fail-closed), a kliens tükrözi (kapu + announce). A megosztás legitim: a kliensnek nincs saját DB-je, az ő adatkörnyezete a szerveré. +2. **Tengelyenkénti nevezés:** `IsProdDb` (adat-tengely, wire-en szinkronizált) ≠ `IsProdBuild` (build-tengely, processenkénti, SOHA nem megy wire-ra) ≠ hosting env. Nincs csupasz `IsProd` a kereten. +3. **Framework-fenntartott SignalR-tagek** (`AcSignalRTags`, a GridLayout 9001x mintájára): query 90020, announce 90021 — a fogyasztói 210/211 kivezetve (wire-váltás: kliens+szerver együtt-deploy). +4. **Kliens-gépezet az `AcSignalRClientBase`-be:** first-connect kapu (await + dedup: pontosan 1 wire-hívás), `EnsureEnvironmentAsync`, fail-closed nyers query (timeout ≠ `default(bool)`!), announce-elfogás a dispatch-ben (a fogyasztói `MessageReceived` nem is látja). **Opt-in**: `EnvironmentFeatureEnabled => false` default — más fogyasztók (pl. TourIAm) szervere nem ismeri az endpointot, default-on fail-closed törést okozna. +5. **Hub-base announce:** push-on-connect az `AcEnv.IsProdDb`-ből (best-effort, a kapcsolatot nem dönti). +6. **Blazor title-réteg** (AyCode.Blazor.Components): `IAcAppTitleService` + browser-implementáció + ``; MAUI-implementáció fogyasztói marad (a keretnek nincs MAUI-assemblyje). +7. **Delegát-minta a fogyasztón:** a `FruitBankConstClient` érintett tagjai átlátszó delegátként maradnak (`BaseUrl` ✅) — nulla törés, a hívóhelyek változatlanok, a felfedezhetőség megmarad (static class nem örökölhet → tag-migráció + delegát, nem literál öröklés). +8. **Fogyasztónál marad:** domain-konstansok (táblanevek, DomainDescription, email-sablonok), a katalógus-besorolás hívási helye, a query-endpoint implementáció (framework-taggel, GridLayout-minta), MAUI title-impl, AppName. + +## Consequences + +**Positive:** +- Minden jövőbeli app (VeggieVoyage, közös nagyker-réteg) **ingyen** kapja a teljes env-láncot: kapu, announce+riasztás, badge, teszt-guard. +- A szerver/kliens redundancia megszűnik (egy állapot, egy esemény, sok olvasó); a BaseUrl-divergencia osztálya strukturálisan kizárt. +- Egységes fail-fast (hidratálatlan olvasás dob) + fail-closed (nem igazolható env = tiltás) szemantika keret-szinten. + +**Negative:** +- Wire-tag átszámozás (210/211 → 9002x): kliens + szerver **együtt-deploy** kötelező a váltáskor. +- `AcEnv` statikus állapot: process-szintű — párhuzamos teszt-kollekciók osztoznak rajta (a jelen MSTest-felállásban nem gond, de tudni kell róla). +- Az opt-in flag elfelejtése = a feature **némán off** — a keret-doksiban kiemelten dokumentálandó. +- A delegát-réteg fegyelmet kér: új infra-tag a KERETRE kerüljön, ne a fogyasztói const-ra. + +**Follow-ups required:** +- 2. lépés: `IsProdDb` → `AcEnv` (FruitBank-oldali `IsProd`-tagok delegátként). +- 3. lépés: `AcSignalRTags` 90020/90021 + kliens-base gépezet + opt-in flag + teszt-guard helper a keretbe. +- 4. lépés: hub-base announce. +- 5. lépés: Blazor title-réteg + FruitBank-átkötés (MainLayout → ``). +- Bónusz/később: `IsProdBuild` (+ opcionális build↔DB keresztellenőrzés, opt-in); `AcEnv.SetConfiguration` (N2D8); SignalR-intervallum konstansok configba (a `SignarlR` typóval együtt kivezetve); MAUI-Android `10.0.2.2` BaseUrl-kérdés. + +## Alternatives considered + +- **Fogyasztói szinten hagyni** (rejected): minden app újraimplementálná; a redundancia már most élő bugot szült (BaseUrl-divergencia). +- **Teljes átnevezés homlokzat nélkül** (a fogyasztói kód mindenhol `AcEnv.*`-ot hív) (rejected): ~12 hívóhely churn, és a csapat a megszokott helyen (`FruitBankConstClient`) keresi az értéket — a delegát ugyanazt az egy-forrást adja nulla töréssel. + - *Reversibility:* a delegát később bármikor kivezethető, ha a csapat átszokott. +- **Egyetlen `AcEnv.IsProd` név tengely-bontás nélkül** (rejected): a build/adat/hosting tengelyek összemosása — a Debug-build + PROD-DB élő eset bizonyítja, hogy függetlenek; a névnek a forrást kell mondania (`IsProdDb`). +- **Reconnect-enkénti újra-lekérdezés a push-on-connect helyett** (rejected mint végállapot): működik, de ellentmond a „csak az elején 1x" elvnek; a push-on-CHANGE pedig konstrukciósan halott (az env-váltás csak restarttal jöhet, ami bontja a csatornát, amin a push utazna) — ezért query-once + announce-on-connect a végleges alak. +- **Külön köztes osztály (`AcEnvironmentAwareSignalRClientBase`)** (rejected): felesleges hierarchia-szint; az opt-in flag az `AcSignalRClientBase`-en ugyanazt adja ceremónia nélkül. + +## Related + +- Related ADRs: — +- Related TODOs/Issues: `ACCORE-LOG-T-N2D8` (AcEnv.SetConfiguration — szinergia), `ACCORE-LOG-T-D7S3` (protokoll-diagnosztikai logger, átgondolandó), `ACCORE-SBP-T-T5R2` (protokoll-trace Trace-szintre), `ACBLAZOR-GRID-T-E3B6` (template-ErrorBoundary). +- Precedens: GridLayout-roaming (framework-tag `AcSignalRTags` 9001x + fogyasztói endpoint) — `Nop.Plugin.Misc.AIPlugin/docs/SIGNALR/README.md` „Grid Layout Roaming". +- External references: — diff --git a/docs/adr/README.md b/docs/adr/README.md index 5e4f2e98..3f6d9eb8 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -9,3 +9,4 @@ Architektúra-szintű döntések durable rekordjai (Nygard-stílus): egy fájl = | ADR | Cím | Status | |---|---|---| | [0001](0001-non-measurable-item-derived-weight.md) | Nem mérendő tétel súlya — derivált pallet-mérés a termék átlagsúlyából és tárájából | Proposed (2026-07-14) | +| [0002](0002-environment-infrastructure-to-framework.md) | Környezet-infrastruktúra (BaseUrl, szerver-adatkörnyezet, env-badge) keretbe emelése | Accepted (2026-07-15) |