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) |