Rejestr konfiguracji (ConfigReg) — silnik platformy Soneta
Rejestr konfiguracji to zrzut całej konfiguracji bazy danych do drzewa węzłów, które
da się zserializować do JSON, porównać z innym zrzutem, scalić i ponownie zapisać do bazy.
Służy do wersjonowania ustawień, śledzenia zmian i przenoszenia konfiguracji między bazami
(np. testowa → operacyjna).
Ten skill opisuje mechanizm od strony kodu C# — dla osób, które rozwijają silnik,
piszą providery albo sterują zakresem rejestru w swoich modułach.
Kiedy ten skill, a kiedy inny
| Potrzeba |
Skill |
Rejestr konfiguracji: providery, węzły, *.reg.json, [ConfigReg], merge/diff/zapis |
/soneta-config-reg (ten) |
Import/eksport danych przez XML (<session>, *.dbinit.xml, datapack, demo) |
/soneta-config |
ORM: Row/Table/Module, sesje i transakcje, GuidedRow, OnImporting/OnImported |
/soneta-programming |
Definicja tabel i kolumn (w tym <attribute>ConfigReg(...)</attribute>) |
/soneta-business-xml |
Formularz ConfigRegManager.General.pageform.xml, grid drzewa |
/soneta-form-xml |
| Operacje na bazie z CLI, weryfikacja na żywej aplikacji |
/soneta-tools |
Nie myl dwóch mechanizmów przenoszenia ustawień. XML (<session>, /soneta-config)
przenosi wskazane rekordy i jest starszym, ogólnym mechanizmem importu danych. ConfigReg
przenosi stan konfiguracji jako całość, z wykrywaniem różnic i scalaniem. Mają rozłączny kod.
Mapa plików
Cały mechanizm jest zamknięty w ~30 plikach i nie ma konsumentów poza UI i testami.
| Obszar |
Ścieżka |
| Silnik |
Soneta.Business\Config\Reg\ — namespace Soneta.Config.Reg |
Trwałość (tabela ConfigRegistry) |
Soneta.Business\Config\Reg\ManagementTools.business.xml |
| UI (formularz operacji + lista rejestrów) |
Soneta.Business.UI\UI\ConfigRegManager*, Soneta.Business.UI\ViewInfos\ConfigRegViewInfo.cs |
| Atrybut sterujący |
Soneta.Business\Config\Reg\ConfigRegAttribute.cs (typ w ns Soneta.Business) |
| Testy — najlepsza specyfikacja mechanizmu |
Soneta.Business.Test\ConfigRegTest.cs |
Model danych: węzeł ConfigReg
Jedna klasa (ConfigReg.cs) reprezentuje każdy poziom drzewa — moduł, tabelę, wiersz, pole,
węzeł konfiguracji, plik storage. Odróżnia je zestaw właściwości, nie typ.
| Właściwość |
Znaczenie |
Name / IsNamed |
Węzeł nazwany = właściwość/tabela/moduł. Węzeł bez nazwy = element kolekcji (wiersz). Name na węźle bez nazwy rzuca wyjątek — sprawdzaj IsNamed. |
EditValue / EditValueType |
Wartość węzła. Może przechowywać Func<ConfigReg, object?> — wtedy liczy się leniwie przy pierwszym odczycie. Setter konwertuje wartość przez ConvertEditValue. |
Children / IsLeaf |
Children == null oznacza liść. Pusta lista to nie liść, tylko węzeł bez dzieci. |
ChildrenMode |
Properties (obiekt), PartialCollection (lista dodająca/aktualizująca), StrictCollection (lista określająca kolekcję w całości — brakujące wiersze zostaną usunięte). |
RegState |
Unchanged / Added / Modified / Deleted — wynik porównania lub scalenia. |
IsKeyValue |
Pole wchodzące w tożsamość wiersza (klucz unikalny lub Guid). Nigdy nie jest usuwane przez RemoveDefaults/RemoveUnchanged. |
IsTreeStructure |
Węzeł należy do struktury drzewiastej (tabela z relacją do samej siebie). |
IsConflict / Resolution |
Konflikt scalania — patrz „Stan mechanizmu” niżej. |
Metody, po które sięgniesz najczęściej:
Find(path) / FindRequired(path) — nawigacja ścieżką, np.
reg.FindRequired("Business/FeatureDefs/Name=CECHA/Category"). Składnię ścieżek opisuje
references/json-format.md.
DebugInfo — czytelny zrzut całego poddrzewa z wcięciami i znacznikami [+] [*] [-].
Testy porównują właśnie ten tekst; przy diagnozie zaczynaj od niego.
EqualsByKeys(other) — tożsamość węzłów przy porównywaniu i scalaniu. Kolejność reguł:
nazwa (gdy oba nazwane) → wszystkie pola IsKeyValue → Guid → zgodność wszystkich liści.
RemoveDefaults() / RemoveUnchanged() — odchudzanie drzewa przed zapisem.
ToJson(session, options) — serializacja; wywołana na węźle innym niż root „opakowuje” go
w sztuczną ścieżkę od roota, degradując po drodze StrictCollection → PartialCollection.
Drzewo providerów
Drzewo nie jest budowane przez same węzły — buduje je hierarchia providerów. Każdy provider
zna jeden poziom: potrafi wyliczyć swoje dzieci i wskazać provider odpowiedzialny za każde
z nich.
RootConfigRegProvider "Register"
├── ModuleConfigRegProvider po jednym na moduł sesji
│ ├── ConfigModuleConfigRegProvider "Config" → CfgNode: podwęzły + "@atrybuty"
│ └── TableConfigRegProvider tabele konfiguracyjne
│ └── RowConfigRegProvider pola wiersza
│ ├── SubRowConfigRegProvider pola typu SubRow
│ └── SubTableConfigRegProvider kolekcje podrzędne ($strict)
├── AddinsConfigRegProvider "Addins" → SystemInfo z Addin
└── StorageConfigRegProvider "Storage" → pliki i foldery StorageProvider
Które tabele trafiają do rejestru (ModuleConfigRegProvider.cs): IsConfig && IsGuided,
z pominięciem tabel oznaczonych [ConfigReg(Hide = true)] oraz tabel mających klucz guidowany;
dodatkowo dołączane są tabele z [ConfigReg(Include = true)]. Tabele bez rekordów są usuwane
z drzewa przez HasAnyChild.
Szczegóły kontraktu IConfigRegProvider, opis każdego providera i przepis na dodanie
własnego — references/providers.md.
Potok operacji
ConfigRegLoader.FromCurrentDatabase(opts, rootProvider, loadOpts) ← stan bazy
ConfigRegLoader.FromJson(json, session) ← zrzut z pliku/tabeli
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
ConfigRegDiffer ConfigRegMerger ConfigRegConcater
(co się zmieniło) (nanieś źródło (zbierz wybrane węzły
na cel, ustaw + zależne wiersze)
RegState)
│
▼
ConfigRegSessionUpdater.Update() → zapis do sesji
ConfigReg.ToJson(session) → plik *.reg.json / tabela ConfigRegistry
Kanoniczne użycie (wprost z ConfigRegTest.UpdateSessionFromJson):
var opts = new ConfigRegOptions(session);
var provider = new RootConfigRegProvider(opts);
var dbReg = ConfigRegLoader.FromCurrentDatabase(opts, provider, ConfigRegLoadOptions.Default);
var jsonReg = ConfigRegLoader.FromJson(json, session);
var reg = new ConfigRegMerger(dbReg, provider).Merge(jsonReg); // nanosi zmiany na dbReg
new ConfigRegSessionUpdater(reg, session, provider).Update(); // zapisuje do sesji
Opis każdego etapu, tabeli ConfigRegistry i pięciu operacji UI —
references/pipelines.md.
Punkty rozszerzeń
Z zewnątrz modułu: atrybut [ConfigReg]
To jedyny publiczny punkt sterowania — IConfigRegProvider jest internal, więc dodatek
spoza Soneta.Business nie napisze własnego providera.
| Właściwość |
Gdzie stosować |
Efekt |
Hide = true |
klasa tabeli |
Wyklucza tabelę mimo że jest konfiguracyjna i guidowana. Przykład: CfgNodes (Soneta.Business\Config\CfgNodes.cs) — konfiguracja jest w rejestrze reprezentowana węzłem Config, nie surową tabelą. |
Ignore = true |
właściwość wiersza, pole rekordu, właściwość CfgNodeProxy |
Pomija pojedynczą wartość. Stosuj dla sekretów i danych lokalnych: Operator.Password, OperatorEx.PasswordHistory, ViewLicensesHash w Soneta.BI. |
Include = true |
klasa tabeli |
Dołącza tabelę niekonfiguracyjną. Przykład: Dictionary (Soneta.Business\Db\Dictionary.cs). |
ChildrenType |
klasa CfgNodeProxy |
Wymusza typ tworzonych węzłów konfiguracji. Przykład: Wskaźniki w Soneta.KadryPlace → CfgNodeType.HistoryNode (węzły kluczowane datą). |
W business.xml atrybut zapisuje się jako element kolumny — patrz Business.business.xml:
<col name="Password" type="string" length="64" required="true" modifier="protected" specialaccess="true">
<attribute>ConfigReg(Ignore=true)</attribute>
</col>
Zasada przy nowej tabeli konfiguracyjnej: jeśli tabela jest config + guidowana, znajdzie
się w rejestrze automatycznie i jej zawartość pojedzie do innych baz. Zanim ją dodasz, przejrzyj
kolumny pod kątem haseł, hashy, liczników i identyfikatorów instalacji — te oznacz Ignore.
Wewnątrz silnika: nowy provider
Krótko: zaimplementuj IConfigRegProvider, zwróć węzły z GetChildren, w GetByReg odwzoruj
węzeł na provider potomny, a zapis obsłuż w SaveChild rodzica (provider zapisuje własne
dzieci, nie siebie). Pełny przepis wraz z pułapkami — references/providers.md.
Reguły, które łatwo złamać
SaveChild należy do rodzica. ConfigRegSessionUpdater woła provider.SaveChild(child, …)
na providerze poziomu wyżej. Provider nigdy nie zapisuje węzła, do którego sam jest przypisany.
- Odłożone akcje (
delayed). Referencje do wierszy i invokeImported trafiają na listę
List<Method> delayed, wykonywaną dopiero po przejściu całego drzewa — bo wiersz docelowy może
jeszcze nie istnieć w momencie ustawiania pola. Jeśli w SaveChild dotykasz innego wiersza,
odłóż to działanie zamiast wykonywać od razu.
StrictCollection kasuje. Kolekcja oznaczona $strict określa stan docelowy w całości;
wiersze nieobecne w rejestrze zostaną usunięte. ConfigRegSessionUpdater odmówi zapisu, jeśli
w takiej kolekcji znajdzie dzieci w stanie innym niż Added/Unchanged.
- Leniwe
EditValue. Odczyt EditValue może uruchomić zapytanie do bazy albo odczyt pliku.
W pętlach diagnostycznych używaj IsNamed/Name, nie TextValue.
ConvertEditValue jest jedynym miejscem konwersji. Wartości z JSON przychodzą jako
string/decimal/bool i typ docelowy ustala się dopiero przy scalaniu (GetChildType).
Nowy typ wartości obsłuż tam, nie w providerze.
- Zapis idzie przez
session.Logout(true) z InImport = true (ConfigRegSessionUpdater.cs)
— omija część walidacji biznesowej. Nie wołaj Update() wewnątrz własnej transakcji.
Stan mechanizmu — czego jeszcze nie ma
Zanim zaproponujesz zmianę opartą o te elementy, sprawdź, czy w ogóle działają:
- Wykrywanie konfliktów jest niedokończone. Gałąź ustawiająca konflikt na liściu jest
zakomentowana w
ConfigRegMerger.cs, a test ConfigReg_MergeWithConflicts ma
[Ignore("Mechanizm konfliktów do implementacji.")]. UI już pokazuje filtr „Tylko konflikty”
i ConflictResolution, ale logika rozstrzygania nie jest napisana.
ConfigRegDiffer.Result nigdy nie jest ustawiane — testy asertują, że jest null.
ConfigRegOptions.UseStringFieldAsKeyValue jest zadeklarowane i nigdzie nieczytane.
RegType.Backup nie jest nigdzie zapisywane; wszystkie ścieżki zapisu wpisują Operational.
- Kilkanaście testów ma
[Ignore] („Wyłączony do czasu weryfikacji”, „Bledy na pipeline”).
Za specyfikację przyjmuj testy przechodzące, a przy zmianach sprawdź, czy któryś z wyłączonych
nie opisuje właśnie tego, co poprawiasz.
Testy jako specyfikacja
Soneta.Business.Test\ConfigRegTest.cs pokrywa mechanizm gęściej niż jakakolwiek dokumentacja.
Przy zmianie w silniku zacznij od znalezienia testu opisującego zachowanie:
| Zachowanie |
Test |
Budowa drzewa z sesji BSubstitute |
ConfigReg_CreateReg_FromSubstitute |
| Pomijanie pustych tabel |
ConfigReg_ShouldNoRegNode_WhenEmptyTables |
Serializacja i odczyt RegState |
ConfigReg_SaveToJson_WhenRegStateIsChanged, ConfigReg_LoadFromJson_WhenRegStateIsChanged |
| Brak różnic przy tym samym stanie |
ConfigReg_DiffsNoChanges_WhenSameConfigRegBSubstitute |
| Dodanie / zmiana / usunięcie wiersza |
ConfigReg_DiffsAdded_…, ConfigReg_DiffsDeleted_…, ConfigReg_UpdateSession_AddRow, …_DeleteRow |
$strict na SubTable |
ConfigReg_UpdateSession_WhenStrictSubTable, …_StrictCollection |
Konfiguracja CfgNode |
ConfigReg_UpdateSession_AddConfig, …_UpdateConfig, …_DeleteConfig |
| Storage i addiny |
ConfigReg_UpdateSession_AddStorage, …_AddAddin, …_CreateFilesElement, …_DeleteFileElement |
| Struktury drzewiaste |
ConfigReg_UpdateSession_LoadingTree, …_LoadingTreeByGuid |
Wybieranie węzłów w UI (Concater) |
ConfigReg_Concater_AddDeleteAdd |
Pomocnik UpdateSessionFromJson(session, jsonData) w tym pliku to najkrótsza droga do napisania
nowego testu: podajesz fragment JSON od poziomu modułu, a on robi load → merge → update.
Powiązania
- /soneta-config — drugi mechanizm przenoszenia ustawień
(XML
<session>, *.dbinit.xml, datapack) oraz inwentaryzacja menu.
- /soneta-programming — ORM pod spodem:
Row/Table/Module,
GuidedRow i Datapack, sesje i transakcje, OnImporting/OnImported, Context, testy
integracyjne.
- /soneta-business-xml — deklaracja tabel konfiguracyjnych
i
<attribute>ConfigReg(...)</attribute> w kolumnach.
- /soneta-form-xml — formularz zarządzania rejestrem
(grid drzewiasty,
Appearance po RegState).
- /soneta-erp — mapa wyboru skilla.
1---2name: soneta-config-reg3description: REJESTR KONFIGURACJI (ConfigReg) platformy Soneta (enova365, Triva) od strony kodu C#: drzewo węzłów, providery `IConfigRegProvider` (Root/Module/Table/Row/SubRow/SubTable/Storage/Addins), atrybut `[ConfigReg(Hide/Ignore/Include/ChildrenType)]`, format `*.reg.json`, potok Loader → Differ/Merger → SessionUpdater. Używaj ZAWSZE gdy użytkownik: (1) pyta o rejestr konfiguracji, ConfigReg, `Soneta.Config.Reg`, tabelę `ConfigRegistry` lub menu „Zarządzanie konfiguracją"; (2) chce włączyć/wyłączyć tabelę, kolumnę lub węzeł z rejestru (`Ignore`, `Hide`, `Include`, `ChildrenType`); (3) pisze provider rejestru, obsługę zapisu (`SaveChild`, `delayed`), porównywanie (`ConfigRegDiffer`), scalanie (`ConfigRegMerger`), serializację (`RegConfigJsonConverter`); (4) buduje lub debuguje `*.reg.json` — sigile `$strict`, `$v`, `#klucz`, `@atrybut`, `$blob`, ścieżki `Business/FeatureDefs/Name=CECHA`; (5) przenosi ustawienia między bazami przez rejestr. Import/eksport przez XML (`dbinit.xml`) to INNY mechanizm → /soneta-config.4---56# Rejestr konfiguracji (ConfigReg) — silnik platformy Soneta78Rejestr konfiguracji to **zrzut całej konfiguracji bazy danych do drzewa węzłów**, które9da się zserializować do JSON, porównać z innym zrzutem, scalić i ponownie zapisać do bazy.10Służy do wersjonowania ustawień, śledzenia zmian i przenoszenia konfiguracji między bazami11(np. testowa → operacyjna).1213Ten skill opisuje mechanizm **od strony kodu C#** — dla osób, które rozwijają silnik,14piszą providery albo sterują zakresem rejestru w swoich modułach.1516## Kiedy ten skill, a kiedy inny1718| Potrzeba | Skill |19|---|---|20| Rejestr konfiguracji: providery, węzły, `*.reg.json`, `[ConfigReg]`, merge/diff/zapis | **/soneta-config-reg** (ten) |21| Import/eksport danych przez **XML** (`<session>`, `*.dbinit.xml`, datapack, demo) | [/soneta-config](../soneta-config/SKILL.md) |22| ORM: `Row`/`Table`/`Module`, sesje i transakcje, `GuidedRow`, `OnImporting`/`OnImported` | [/soneta-programming](../soneta-programming/SKILL.md) |23| Definicja tabel i kolumn (w tym `<attribute>ConfigReg(...)</attribute>`) | [/soneta-business-xml](../soneta-business-xml/SKILL.md) |24| Formularz `ConfigRegManager.General.pageform.xml`, grid drzewa | [/soneta-form-xml](../soneta-form-xml/SKILL.md) |25| Operacje na bazie z CLI, weryfikacja na żywej aplikacji | [/soneta-tools](../soneta-tools/SKILL.md) |2627**Nie myl dwóch mechanizmów przenoszenia ustawień.** XML (`<session>`, `/soneta-config`)28przenosi *wskazane rekordy* i jest starszym, ogólnym mechanizmem importu danych. ConfigReg29przenosi *stan konfiguracji* jako całość, z wykrywaniem różnic i scalaniem. Mają rozłączny kod.3031## Mapa plików3233Cały mechanizm jest zamknięty w ~30 plikach i nie ma konsumentów poza UI i testami.3435| Obszar | Ścieżka |36|---|---|37| Silnik | `Soneta.Business\Config\Reg\` — namespace `Soneta.Config.Reg` |38| Trwałość (tabela `ConfigRegistry`) | `Soneta.Business\Config\Reg\ManagementTools.business.xml` |39| UI (formularz operacji + lista rejestrów) | `Soneta.Business.UI\UI\ConfigRegManager*`, `Soneta.Business.UI\ViewInfos\ConfigRegViewInfo.cs` |40| Atrybut sterujący | `Soneta.Business\Config\Reg\ConfigRegAttribute.cs` (typ w ns `Soneta.Business`) |41| Testy — **najlepsza specyfikacja mechanizmu** | `Soneta.Business.Test\ConfigRegTest.cs` |4243## Model danych: węzeł `ConfigReg`4445Jedna klasa (`ConfigReg.cs`) reprezentuje każdy poziom drzewa — moduł, tabelę, wiersz, pole,46węzeł konfiguracji, plik storage. Odróżnia je zestaw właściwości, nie typ.4748| Właściwość | Znaczenie |49|---|---|50| `Name` / `IsNamed` | Węzeł **nazwany** = właściwość/tabela/moduł. Węzeł **bez nazwy** = element kolekcji (wiersz). `Name` na węźle bez nazwy rzuca wyjątek — sprawdzaj `IsNamed`. |51| `EditValue` / `EditValueType` | Wartość węzła. Może przechowywać `Func<ConfigReg, object?>` — wtedy liczy się **leniwie** przy pierwszym odczycie. Setter konwertuje wartość przez `ConvertEditValue`. |52| `Children` / `IsLeaf` | `Children == null` oznacza liść. Pusta lista to **nie** liść, tylko węzeł bez dzieci. |53| `ChildrenMode` | `Properties` (obiekt), `PartialCollection` (lista dodająca/aktualizująca), `StrictCollection` (lista określająca kolekcję w całości — brakujące wiersze zostaną **usunięte**). |54| `RegState` | `Unchanged` / `Added` / `Modified` / `Deleted` — wynik porównania lub scalenia. |55| `IsKeyValue` | Pole wchodzące w tożsamość wiersza (klucz unikalny lub `Guid`). Nigdy nie jest usuwane przez `RemoveDefaults`/`RemoveUnchanged`. |56| `IsTreeStructure` | Węzeł należy do struktury drzewiastej (tabela z relacją do samej siebie). |57| `IsConflict` / `Resolution` | Konflikt scalania — patrz „Stan mechanizmu” niżej. |5859Metody, po które sięgniesz najczęściej:6061- `Find(path)` / `FindRequired(path)` — nawigacja ścieżką, np.62 `reg.FindRequired("Business/FeatureDefs/Name=CECHA/Category")`. Składnię ścieżek opisuje63 [references/json-format.md](references/json-format.md).64- `DebugInfo` — czytelny zrzut całego poddrzewa z wcięciami i znacznikami `[+] [*] [-]`.65 Testy porównują właśnie ten tekst; przy diagnozie zaczynaj od niego.66- `EqualsByKeys(other)` — **tożsamość węzłów** przy porównywaniu i scalaniu. Kolejność reguł:67 nazwa (gdy oba nazwane) → wszystkie pola `IsKeyValue` → `Guid` → zgodność wszystkich liści.68- `RemoveDefaults()` / `RemoveUnchanged()` — odchudzanie drzewa przed zapisem.69- `ToJson(session, options)` — serializacja; wywołana na węźle innym niż root „opakowuje” go70 w sztuczną ścieżkę od roota, degradując po drodze `StrictCollection` → `PartialCollection`.7172## Drzewo providerów7374Drzewo nie jest budowane przez same węzły — buduje je hierarchia providerów. Każdy provider75zna *jeden poziom*: potrafi wyliczyć swoje dzieci i wskazać provider odpowiedzialny za każde76z nich.7778```79RootConfigRegProvider "Register"80├── ModuleConfigRegProvider po jednym na moduł sesji81│ ├── ConfigModuleConfigRegProvider "Config" → CfgNode: podwęzły + "@atrybuty"82│ └── TableConfigRegProvider tabele konfiguracyjne83│ └── RowConfigRegProvider pola wiersza84│ ├── SubRowConfigRegProvider pola typu SubRow85│ └── SubTableConfigRegProvider kolekcje podrzędne ($strict)86├── AddinsConfigRegProvider "Addins" → SystemInfo z Addin87└── StorageConfigRegProvider "Storage" → pliki i foldery StorageProvider88```8990**Które tabele trafiają do rejestru** (`ModuleConfigRegProvider.cs`): `IsConfig && IsGuided`,91z pominięciem tabel oznaczonych `[ConfigReg(Hide = true)]` oraz tabel mających klucz guidowany;92dodatkowo dołączane są tabele z `[ConfigReg(Include = true)]`. Tabele bez rekordów są usuwane93z drzewa przez `HasAnyChild`.9495Szczegóły kontraktu `IConfigRegProvider`, opis każdego providera i przepis na dodanie96własnego — [references/providers.md](references/providers.md).9798## Potok operacji99100```101 ConfigRegLoader.FromCurrentDatabase(opts, rootProvider, loadOpts) ← stan bazy102 ConfigRegLoader.FromJson(json, session) ← zrzut z pliku/tabeli103 │104 ┌─────────────────────┼─────────────────────┐105 ▼ ▼ ▼106 ConfigRegDiffer ConfigRegMerger ConfigRegConcater107 (co się zmieniło) (nanieś źródło (zbierz wybrane węzły108 na cel, ustaw + zależne wiersze)109 RegState)110 │111 ▼112 ConfigRegSessionUpdater.Update() → zapis do sesji113 ConfigReg.ToJson(session) → plik *.reg.json / tabela ConfigRegistry114```115116Kanoniczne użycie (wprost z `ConfigRegTest.UpdateSessionFromJson`):117118```csharp119var opts = new ConfigRegOptions(session);120var provider = new RootConfigRegProvider(opts);121122var dbReg = ConfigRegLoader.FromCurrentDatabase(opts, provider, ConfigRegLoadOptions.Default);123var jsonReg = ConfigRegLoader.FromJson(json, session);124125var reg = new ConfigRegMerger(dbReg, provider).Merge(jsonReg); // nanosi zmiany na dbReg126new ConfigRegSessionUpdater(reg, session, provider).Update(); // zapisuje do sesji127```128129Opis każdego etapu, tabeli `ConfigRegistry` i pięciu operacji UI —130[references/pipelines.md](references/pipelines.md).131132## Punkty rozszerzeń133134### Z zewnątrz modułu: atrybut `[ConfigReg]`135136To jedyny publiczny punkt sterowania — `IConfigRegProvider` jest `internal`, więc dodatek137spoza `Soneta.Business` nie napisze własnego providera.138139| Właściwość | Gdzie stosować | Efekt |140|---|---|---|141| `Hide = true` | klasa tabeli | Wyklucza tabelę mimo że jest konfiguracyjna i guidowana. Przykład: `CfgNodes` (`Soneta.Business\Config\CfgNodes.cs`) — konfiguracja jest w rejestrze reprezentowana węzłem `Config`, nie surową tabelą. |142| `Ignore = true` | właściwość wiersza, pole rekordu, właściwość `CfgNodeProxy` | Pomija pojedynczą wartość. Stosuj dla **sekretów i danych lokalnych**: `Operator.Password`, `OperatorEx.PasswordHistory`, `ViewLicensesHash` w `Soneta.BI`. |143| `Include = true` | klasa tabeli | Dołącza tabelę niekonfiguracyjną. Przykład: `Dictionary` (`Soneta.Business\Db\Dictionary.cs`). |144| `ChildrenType` | klasa `CfgNodeProxy` | Wymusza typ tworzonych węzłów konfiguracji. Przykład: `Wskaźniki` w `Soneta.KadryPlace` → `CfgNodeType.HistoryNode` (węzły kluczowane datą). |145146W `business.xml` atrybut zapisuje się jako element kolumny — patrz `Business.business.xml`:147148```xml149<col name="Password" type="string" length="64" required="true" modifier="protected" specialaccess="true">150 <attribute>ConfigReg(Ignore=true)</attribute>151</col>152```153154**Zasada przy nowej tabeli konfiguracyjnej:** jeśli tabela jest `config` + guidowana, znajdzie155się w rejestrze automatycznie i jej zawartość pojedzie do innych baz. Zanim ją dodasz, przejrzyj156kolumny pod kątem haseł, hashy, liczników i identyfikatorów instalacji — te oznacz `Ignore`.157158### Wewnątrz silnika: nowy provider159160Krótko: zaimplementuj `IConfigRegProvider`, zwróć węzły z `GetChildren`, w `GetByReg` odwzoruj161węzeł na provider potomny, a zapis obsłuż w `SaveChild` **rodzica** (provider zapisuje własne162dzieci, nie siebie). Pełny przepis wraz z pułapkami — [references/providers.md](references/providers.md).163164## Reguły, które łatwo złamać165166- **`SaveChild` należy do rodzica.** `ConfigRegSessionUpdater` woła `provider.SaveChild(child, …)`167 na providerze *poziomu wyżej*. Provider nigdy nie zapisuje węzła, do którego sam jest przypisany.168- **Odłożone akcje (`delayed`).** Referencje do wierszy i `invokeImported` trafiają na listę169 `List<Method> delayed`, wykonywaną dopiero po przejściu całego drzewa — bo wiersz docelowy może170 jeszcze nie istnieć w momencie ustawiania pola. Jeśli w `SaveChild` dotykasz innego wiersza,171 odłóż to działanie zamiast wykonywać od razu.172- **`StrictCollection` kasuje.** Kolekcja oznaczona `$strict` określa stan docelowy w całości;173 wiersze nieobecne w rejestrze zostaną usunięte. `ConfigRegSessionUpdater` odmówi zapisu, jeśli174 w takiej kolekcji znajdzie dzieci w stanie innym niż `Added`/`Unchanged`.175- **Leniwe `EditValue`.** Odczyt `EditValue` może uruchomić zapytanie do bazy albo odczyt pliku.176 W pętlach diagnostycznych używaj `IsNamed`/`Name`, nie `TextValue`.177- **`ConvertEditValue` jest jedynym miejscem konwersji.** Wartości z JSON przychodzą jako178 `string`/`decimal`/`bool` i typ docelowy ustala się dopiero przy scalaniu (`GetChildType`).179 Nowy typ wartości obsłuż tam, nie w providerze.180- **Zapis idzie przez `session.Logout(true)` z `InImport = true`** (`ConfigRegSessionUpdater.cs`)181 — omija część walidacji biznesowej. Nie wołaj `Update()` wewnątrz własnej transakcji.182183## Stan mechanizmu — czego jeszcze nie ma184185Zanim zaproponujesz zmianę opartą o te elementy, sprawdź, czy w ogóle działają:186187- **Wykrywanie konfliktów jest niedokończone.** Gałąź ustawiająca konflikt na liściu jest188 zakomentowana w `ConfigRegMerger.cs`, a test `ConfigReg_MergeWithConflicts` ma189 `[Ignore("Mechanizm konfliktów do implementacji.")]`. UI już pokazuje filtr „Tylko konflikty”190 i `ConflictResolution`, ale logika rozstrzygania nie jest napisana.191- **`ConfigRegDiffer.Result` nigdy nie jest ustawiane** — testy asertują, że jest `null`.192- **`ConfigRegOptions.UseStringFieldAsKeyValue`** jest zadeklarowane i nigdzie nieczytane.193- **`RegType.Backup`** nie jest nigdzie zapisywane; wszystkie ścieżki zapisu wpisują `Operational`.194- Kilkanaście testów ma `[Ignore]` („Wyłączony do czasu weryfikacji”, „Bledy na pipeline”).195 **Za specyfikację przyjmuj testy przechodzące**, a przy zmianach sprawdź, czy któryś z wyłączonych196 nie opisuje właśnie tego, co poprawiasz.197198## Testy jako specyfikacja199200`Soneta.Business.Test\ConfigRegTest.cs` pokrywa mechanizm gęściej niż jakakolwiek dokumentacja.201Przy zmianie w silniku zacznij od znalezienia testu opisującego zachowanie:202203| Zachowanie | Test |204|---|---|205| Budowa drzewa z sesji `BSubstitute` | `ConfigReg_CreateReg_FromSubstitute` |206| Pomijanie pustych tabel | `ConfigReg_ShouldNoRegNode_WhenEmptyTables` |207| Serializacja i odczyt `RegState` | `ConfigReg_SaveToJson_WhenRegStateIsChanged`, `ConfigReg_LoadFromJson_WhenRegStateIsChanged` |208| Brak różnic przy tym samym stanie | `ConfigReg_DiffsNoChanges_WhenSameConfigRegBSubstitute` |209| Dodanie / zmiana / usunięcie wiersza | `ConfigReg_DiffsAdded_…`, `ConfigReg_DiffsDeleted_…`, `ConfigReg_UpdateSession_AddRow`, `…_DeleteRow` |210| `$strict` na `SubTable` | `ConfigReg_UpdateSession_WhenStrictSubTable`, `…_StrictCollection` |211| Konfiguracja `CfgNode` | `ConfigReg_UpdateSession_AddConfig`, `…_UpdateConfig`, `…_DeleteConfig` |212| Storage i addiny | `ConfigReg_UpdateSession_AddStorage`, `…_AddAddin`, `…_CreateFilesElement`, `…_DeleteFileElement` |213| Struktury drzewiaste | `ConfigReg_UpdateSession_LoadingTree`, `…_LoadingTreeByGuid` |214| Wybieranie węzłów w UI (`Concater`) | `ConfigReg_Concater_AddDeleteAdd` |215216Pomocnik `UpdateSessionFromJson(session, jsonData)` w tym pliku to najkrótsza droga do napisania217nowego testu: podajesz fragment JSON od poziomu modułu, a on robi load → merge → update.218219## Powiązania220221- [/soneta-config](../soneta-config/SKILL.md) — drugi mechanizm przenoszenia ustawień222 (XML `<session>`, `*.dbinit.xml`, datapack) oraz inwentaryzacja menu.223- [/soneta-programming](../soneta-programming/SKILL.md) — ORM pod spodem: `Row`/`Table`/`Module`,224 `GuidedRow` i `Datapack`, sesje i transakcje, `OnImporting`/`OnImported`, `Context`, testy225 integracyjne.226- [/soneta-business-xml](../soneta-business-xml/SKILL.md) — deklaracja tabel konfiguracyjnych227 i `<attribute>ConfigReg(...)</attribute>` w kolumnach.228- [/soneta-form-xml](../soneta-form-xml/SKILL.md) — formularz zarządzania rejestrem229 (grid drzewiasty, `Appearance` po `RegState`).230- [/soneta-erp](../soneta-erp/SKILL.md) — mapa wyboru skilla.