Files
slp-modular-cms/aidlc-docs/features/gitea-deployment-workflow/construction/plans/u2-data-durability-fd-questions.md
T
SluijsensandClaude Opus 5 5f3eda2680 Makes a redeploy safe for the key ring and the schema
Nothing here is visible in normal operation. Its whole purpose is that
swapping the release directory on deploy cannot silently destroy state.

Data Protection secures the API keys that authenticate master/slave
communication. Two separate defaults would each have destroyed them:
keys are held on the filesystem, which a release swap discards, and the
application discriminator is derived from the content root path, which
changes with every release directory — so even keys stored in a database
would have stopped being derivable. Keys now live in
ApplicationDbContext and the discriminator is a fixed constant.

Losing them produces no error. It produces stored keys that no longer
decrypt, which presents as an apparent network fault between a Master
and its slaves and is easily misdiagnosed. That is also why the tests
assert the resulting configuration rather than the registration: the
XmlRepository must be the EF one and the discriminator must be the
constant, plus a round-trip proving a value encrypted before a deploy is
readable after one. A test that only checked "Data Protection is
registered" would have passed in the broken case too.

Both modules previously called AddDataProtection() themselves. Module
registration runs after the host's, so those calls re-registered the
configuration chain and would have overridden the persistent store while
IDataProtector still resolved. They are removed, with a comment at each
site — the deletion otherwise looks like a regression. Each module's own
test project now guards against it being reintroduced.

ApplicationDbContext also migrates itself at startup. Deploy targets
offer no CLI, so migrations cannot be a manual step on the server.
Failures are classified rather than treated alike: a connection failure
means the database is not up yet, normal when the app and the database
start together after a reboot, and is retried with backoff; a migration
failure means something is broken and fails at once. Either way the
process does not start, which is what makes the liveness health check
trustworthy — an application that cannot reach its schema never answers
/health, so monitoring goes red instead of reporting a healthy instance
that cannot serve a request.

The cost of migrating without a human gate is that migrations must stay
forward-compatible and non-destructive, since rollback is "redeploy the
previous release". The new migration is purely additive.

Also wires this and the preceding hosting commit into both hosts, as
they touch the same lines of Program.cs.

Two constraints are enforced by documentation rather than code, and
belong in the deployment instructions: the key table must never be
pruned, and only one instance may migrate a given database at a time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHoJpxYXzHACSQguHrC5fw
2026-07-28 00:00:45 +02:00

4.8 KiB

Functional Design Questions — U2 Data Durability

Vul je keuze in achter elke [Answer]:-tag. Kies de laatste optie (Anders) als niets past.


Question 1 — Waar komt de application discriminator vandaan?

Context: de application discriminator bepaalt of twee processen dezelfde Data Protection-sleutels kunnen gebruiken. Standaard leidt ASP.NET Core hem af uit het content root-pad — en dat verandert bij élke atomaire release-switch. Zonder expliciete waarde is de key ring dus alsnog effectief weg na een deploy, ondanks dat hij in de database staat.

Hij moet dus vast staan. De vraag is waar die waarde vandaan komt.

A) Een vaste constante in de code (bijv. "SlpModularCms") — kan niet per ongeluk verkeerd gezet worden, en is voor alle instanties gelijk B) Uit configuratie, met een vaste standaardwaarde — dan kun je per klant/instantie een eigen waarde zetten als dat ooit nodig is C) Uit configuratie, verplicht in te vullen — dwingt een bewuste keuze af per omgeving X) Anders (beschrijf hieronder na de Answer:-tag)


Question 2 — Moeten de sleutels versleuteld in de database staan?

Context: PersistKeysToDbContext slaat de sleutels standaard onversleuteld op als XML in de tabel. Wie de database kan lezen, kan daarmee de opgeslagen slave-API-keys ontsleutelen.

Op Windows lost DPAPI dit normaal op, maar dat werkt niet op Linux (de Pi), dus dat is hier geen optie. Het alternatief is versleutelen met een X.509-certificaat — maar dan moet dat certificaat mee gedeployed worden en beschikbaar blijven, wat een nieuwe versie van hetzelfde probleem introduceert: raak je het certificaat kwijt, dan zijn de sleutels alsnog onleesbaar.

SECURITY-01 vraagt om versleuteling at rest.

A) Onversleuteld in de database, en de encryptie-at-rest van de database zelf is de maatregel — vastleggen als bewuste onderbouwde keuze, met de eis dat de databaseverbinding TLS gebruikt en de database niet publiek benaderbaar is B) Versleutelen met een X.509-certificaat — sterker, maar verplaatst het bewaarprobleem naar het certificaat en voegt een deploystap toe C) Onversleuteld nu, en certificaat-encryptie als apart vervolgpunt vastleggen X) Anders (beschrijf hieronder na de Answer:-tag)


Question 3 — Wat gebeurt er als de database bij het opstarten net niet bereikbaar is?

Context: je koos fail fast bij een migratiefout. Maar er is een verschil tussen "de migratie klopt niet" (echt fout) en "de database is er nog even niet" (tijdelijk) — bijvoorbeeld als de app en de SQL Server-container tegelijk opstarten na een herstart van de Pi.

Bij strikte fail-fast start de app dan niet, en moet iets anders hem opnieuw starten.

A) Strikt fail fast, geen retry — de procesmanager (systemd) herstart de app toch al automatisch, dus dat lost het vanzelf op B) Een korte retry met toenemende wachttijd (bijv. 5 pogingen over ~30 seconden) en dán pas falen — vangt het opstartvenster af zonder een echte fout te verbergen C) Retry alleen bij verbindingsfouten, direct falen bij een migratiefout — onderscheid tussen "nog niet bereikbaar" en "kapot" X) Anders (beschrijf hieronder na de Answer:-tag)


Question 4 — Wat als twee instanties tegelijk opstarten en migreren?

Context: Database.Migrate() is niet ontworpen om veilig gelijktijdig te draaien. In jouw huidige opzet draait er één instantie per omgeving, dus dit speelt nu niet. Maar de master/slave-opzet betekent dat er meerdere instanties naar verschillende databases wijzen, en een herstart kan ze wel gelijktijdig laten opstarten.

A) Negeren — één instantie per database, dus dit kan niet voorkomen. Wel als aanname vastleggen B) Een migratielock in de database gebruiken zodat gelijktijdig migreren veilig is — robuuster, maar meer complexiteit voor een situatie die zich nu niet voordoet C) Alleen documenteren in de deployment-instructies dat instanties niet gelijktijdig gemigreerd moeten worden X) Anders (beschrijf hieronder na de Answer:-tag)


Question 5 — Sleutellevensduur en rotatie

Context: standaard maakt Data Protection elke 90 dagen een nieuwe sleutel aan en houdt oude sleutels beschikbaar om bestaande waarden te kunnen blijven ontsleutelen. Voor de versleutelde slave-API-keys betekent dat: die blijven leesbaar, ook na rotatie, zolang de oude sleutels in de tabel blijven staan.

A) De standaard van 90 dagen aanhouden en oude sleutels nooit opruimen — bestaande waarden blijven altijd leesbaar B) Een langere levensduur instellen zodat er minder sleutels ontstaan C) De standaard aanhouden, plus expliciet vastleggen in de documentatie dat de sleuteltabel nooit opgeschoond mag worden — want dat zou de opgeslagen API-keys onleesbaar maken X) Anders (beschrijf hieronder na de Answer:-tag)