@@ -118,157 +118,237 @@ <h2>4. How the Characters relate to Each Other</h2>
118118 < p > Table: A Declarative Software Architecture Workflow</ p >
119119
120120 <!-- SVG: zoomable without JS -->
121- < svg class ="infra-svg " viewBox ="0 0 2472 872 " preserveAspectRatio ="xMidYMid meet "
122- xmlns ="http://www.w3.org/2000/svg " role ="img " aria-labelledby ="svgTitle ">
123- < title id ="svgTitle "> Declarative GitOps workflow from Git to running services</ title >
124-
125- <!-- Gradient for node strokes -->
126- < defs >
127- < linearGradient id ="grad " x1 ="0 " y1 ="0 " x2 ="1 " y2 ="1 ">
128- < stop offset ="0% " stop-color ="#60a5fa "/>
129- < stop offset ="100% " stop-color ="#00ff00 "/>
130- </ linearGradient >
131- </ defs >
132-
133- <!-- ========== Nodes ========== -->
134- <!-- Git -->
135- < g id ="git ">
136- < rect x ="1979 " y ="8 " width ="232 " height ="66 " rx ="6 " fill ="#111827 " stroke ="url(#grad) " stroke-width ="2 "/>
137- < text x ="2095 " y ="41 " text-anchor ="middle " fill ="#fff " font-size ="14 "> Git Repository: single source of truth</ text >
138- </ g >
139-
140- <!-- CI -->
141- < g id ="ci ">
142- < rect x ="910 " y ="166 " width ="232 " height ="66 " rx ="6 " fill ="#111827 " stroke ="url(#grad) " stroke-width ="2 "/>
143- < text x ="1026 " y ="199 " text-anchor ="middle " fill ="#fff " font-size ="14 "> CI Pipeline: GitHub/GitLab Actions</ text >
144- </ g >
145-
146- <!-- Argo CD -->
147- < g id ="argocd ">
148- < rect x ="910 " y ="324 " width ="232 " height ="66 " rx ="6 " fill ="#111827 " stroke ="url(#grad) " stroke-width ="2 "/>
149- < text x ="1026 " y ="357 " text-anchor ="middle " fill ="#fff " font-size ="14 "> Argo CD: Declarative Continuous Delivery</ text >
150- </ g >
151-
152- <!-- K8s -->
153- < g id ="k8s ">
154- < rect x ="641 " y ="482 " width ="232 " height ="66 " rx ="6 " fill ="#111827 " stroke ="url(#grad) " stroke-width ="2 "/>
155- < text x ="757 " y ="515 " text-anchor ="middle " fill ="#fff " font-size ="14 "> Kubernetes Cluster: NixOS nodes</ text >
156- </ g >
157-
158- <!-- Observability -->
159- < g id ="obs ">
160- < rect x ="257 " y ="640 " width ="232 " height ="66 " rx ="6 " fill ="#111827 " stroke ="url(#grad) " stroke-width ="2 "/>
161- < text x ="373 " y ="673 " text-anchor ="middle " fill ="#fff " font-size ="14 "> Observability & Policy</ text >
162- </ g >
163-
164- <!-- ========== Edges ========== -->
165- < defs >
166- < marker id ="arrow " viewBox ="0 0 10 10 " refX ="9 " refY ="5 "
167- markerWidth ="6 " markerHeight ="6 " orient ="auto ">
168- < path d ="M0 0L10 5L0 10z " fill ="#00ff00 "/>
169- </ marker >
170- </ defs >
171-
172- <!-- Git → CI -->
173- < path d ="M1979 41L1026 120 " stroke ="#00ff00 " stroke-width ="2 "
174- marker-end ="url(#arrow) "/>
175- < text x ="1502 " y ="85 " fill ="#fff " font-size ="12 " text-anchor ="middle "> Pull-Request → Review → Merge</ text >
176-
177- <!-- CI → Argo CD -->
178- < path d ="M1026 232L1026 324 " stroke ="#00ff00 " stroke-width ="2 "
179- marker-end ="url(#arrow) "/>
180- < text x ="1046 " y ="278 " fill ="#fff " font-size ="12 "> Promotes validated artefacts</ text >
181-
182- <!-- Argo CD → K8s -->
183- < path d ="M1026 390L757 482 " stroke ="#00ff00 " stroke-width ="2 "
184- marker-end ="url(#arrow) "/>
185- < text x ="891 " y ="445 " fill ="#fff " font-size ="12 "> Kustomize/Helm manifests</ text >
186-
187- <!-- K8s → Observability -->
188- < path d ="M641 548L373 640 " stroke ="#00ff00 " stroke-width ="2 "
189- marker-end ="url(#arrow) "/>
190-
191- <!-- Git → NixOS flake -->
192- < path d ="M1979 60L1301 120 " stroke ="#00ff00 " stroke-width ="2 "
193- marker-end ="url(#arrow) "/>
194- < text x ="1640 " y ="95 " fill ="#fff " font-size ="12 "> NixOS host specs: flake.nix</ text >
195-
196- <!-- Git → Terraform -->
197- < path d ="M1979 75L1559 120 " stroke ="#00ff00 " stroke-width ="2 "
198- marker-end ="url(#arrow) "/>
199- < text x ="1769 " y ="95 " fill ="#fff " font-size ="12 "> Terraform infra: main.tf</ text >
200-
201- <!-- Git → Dockerfile -->
202- < path d ="M1982 90L1823 120 " stroke ="#00ff00 " stroke-width ="2 "
203- marker-end ="url(#arrow) "/>
204- < text x ="1902 " y ="105 " fill ="#fff " font-size ="12 "> Dockerfiles & image lockfiles</ text >
205-
206- <!-- Git → Argo Apps -->
207- < path d ="M2100 90L2105 120 " stroke ="#00ff00 " stroke-width ="2 "
208- marker-end ="url(#arrow) "/>
209- < text x ="2105 " y ="105 " fill ="#fff " font-size ="12 "> Argo CD Apps & Projects: YAML</ text >
210-
211- <!-- Git → DSPy -->
212- < path d ="M2209 90L2367 120 " stroke ="#00ff00 " stroke-width ="2 "
213- marker-end ="url(#arrow) "/>
214- < text x ="2288 " y ="105 " fill ="#fff " font-size ="12 "> DSPy pipelines: Python</ text >
215-
216- <!-- Argo CD → Namespaces -->
217- < path d ="M984 423L984 482 " stroke ="#00ff00 " stroke-width ="2 "
218- marker-end ="url(#arrow) "/>
219- < text x ="1004 " y ="452 " fill ="#fff " font-size ="11 "> Namespace A</ text >
220-
221- < path d ="M1204 423L1204 482 " stroke ="#00ff00 " stroke-width ="2 "
222- marker-end ="url(#arrow) "/>
223- < text x ="1224 " y ="452 " fill ="#fff " font-size ="11 "> Namespace B</ text >
224-
225- < path d ="M1424 423L1424 482 " stroke ="#00ff00 " stroke-width ="2 "
226- marker-end ="url(#arrow) "/>
227- < text x ="1444 " y ="452 " fill ="#fff " font-size ="11 "> Namespace C</ text >
228-
229- <!-- K8s → infra add-ons -->
230- < path d ="M631 548L631 640 " stroke ="#00ff00 " stroke-width ="2 "
231- marker-end ="url(#arrow) "/>
232- < text x ="541 " y ="594 " fill ="#fff " font-size ="11 "> MetalLB / Cilium / cert-manager</ text >
233-
234- < path d ="M908 548L908 640 " stroke ="#00ff00 " stroke-width ="2 "
235- marker-end ="url(#arrow) "/>
236- < text x ="818 " y ="594 " fill ="#fff " font-size ="11 "> External-DNS / CSI storage</ text >
237-
238- < path d ="M1141 548L1141 640 " stroke ="#00ff00 " stroke-width ="2 "
239- marker-end ="url(#arrow) "/>
240- < text x ="1051 " y ="594 " fill ="#fff " font-size ="11 "> HPA / VPA / PDB</ text >
241-
242- < path d ="M1379 548L1379 640 " stroke ="#00ff00 " stroke-width ="2 "
243- marker-end ="url(#arrow) "/>
244- < text x ="1289 " y ="594 " fill ="#fff " font-size ="11 "> PodSecurityPolicies / OPA Gatekeeper</ text >
245-
246- < path d ="M1616 548L1616 640 " stroke ="#00ff00 " stroke-width ="2 "
247- marker-end ="url(#arrow) "/>
248- < text x ="1526 " y ="594 " fill ="#fff " font-size ="11 "> DSPy Jobs: Cron</ text >
249-
250- < path d ="M1804 548L1804 640 " stroke ="#00ff00 " stroke-width ="2 "
251- marker-end ="url(#arrow) "/>
252- < text x ="1714 " y ="594 " fill ="#fff " font-size ="11 "> Game Services</ text >
253-
254- <!-- Observability → deeper tooling -->
255- < path d ="M124 708L124 800 " stroke ="#00ff00 " stroke-width ="2 "
256- marker-end ="url(#arrow) "/>
257- < text x ="80 " y ="754 " fill ="#fff " font-size ="11 "> Prometheus → Grafana</ text >
258-
259- < path d ="M375 708L375 800 " stroke ="#00ff00 " stroke-width ="2 "
260- marker-end ="url(#arrow) "/>
261- < text x ="331 " y ="754 " fill ="#fff " font-size ="11 "> Loki / Tempo traces</ text >
262-
263- < path d ="M622 708L622 800 " stroke ="#00ff00 " stroke-width ="2 "
264- marker-end ="url(#arrow) "/>
265- < text x ="578 " y ="754 " fill ="#fff " font-size ="11 "> Alertmanager → PagerDuty</ text >
266-
267- < path d ="M899 708L899 800 " stroke ="#00ff00 " stroke-width ="2 "
268- marker-end ="url(#arrow) "/>
269- < text x ="855 " y ="754 " fill ="#fff " font-size ="11 "> Policy-as-code: OPA / Kyverno</ text >
270- </ svg >
121+ <!-- Fully-zoomable SVG (no JS) -->
122+ < svg class ="infra-svg "
123+ viewBox ="0 0 2472 872 "
124+ preserveAspectRatio ="xMidYMid meet "
125+ xmlns ="http://www.w3.org/2000/svg "
126+ aria-labelledby ="title ">
127+ < title id ="title "> Declarative GitOps workflow (Git → CI → Argo CD → K8s → Observability)</ title >
128+
129+ <!-- ---------- Styles ---------- -->
130+ < style >
131+ /* global */
132+ .infra-svg {
133+ width : 100% ;
134+ max-height : 70vh ;
135+ cursor : zoom-in;
136+ background : # 111827 ;
137+ border : 1px solid # 374151 ;
138+ border-radius : 6px ;
139+ touch-action : manipulation; /* allow pinch-zoom on touch */
140+ }
141+
142+ /* nodes */
143+ .node rect {
144+ fill : # 111827 ;
145+ stroke : # 60a5fa ;
146+ stroke-width : 2 ;
147+ }
148+ .node text {
149+ fill : # fff ;
150+ font-size : 14px ;
151+ font-family : -apple-system, BlinkMacSystemFont, "Segoe UI" , Helvetica, Arial, sans-serif;
152+ text-anchor : middle;
153+ dominant-baseline : middle;
154+ }
155+
156+ /* edges */
157+ .edge {
158+ fill : none;
159+ stroke : # 00ff00 ;
160+ stroke-width : 2 ;
161+ }
162+ .edge-label {
163+ fill : # fff ;
164+ font-size : 12px ;
165+ font-family : -apple-system, BlinkMacSystemFont, "Segoe UI" , Helvetica, Arial, sans-serif;
166+ text-anchor : middle;
167+ }
168+
169+ /* arrowheads */
170+ .arrow {
171+ fill : # 00ff00 ;
172+ }
173+ </ style >
271174
175+ <!-- ---------- Arrow marker ---------- -->
176+ < defs >
177+ < marker id ="arrowhead " viewBox ="0 0 10 10 " refX ="9 " refY ="5 "
178+ markerWidth ="6 " markerHeight ="6 " orient ="auto " class ="arrow ">
179+ < path d ="M0 0L10 5L0 10z "/>
180+ </ marker >
181+ </ defs >
182+
183+ <!-- ---------- Nodes ---------- -->
184+ <!-- Git -->
185+ < g class ="node " transform ="translate(2095 41) ">
186+ < rect x ="-116 " y ="-33 " width ="232 " height ="66 " rx ="6 "/>
187+ < text > Git Repository: single source of truth</ text >
188+ </ g >
189+
190+ <!-- CI -->
191+ < g class ="node " transform ="translate(1026 199) ">
192+ < rect x ="-116 " y ="-33 " width ="232 " height ="66 " rx ="6 "/>
193+ < text > CI Pipeline: GitHub/GitLab Actions</ text >
194+ </ g >
195+
196+ <!-- Argo CD -->
197+ < g class ="node " transform ="translate(1026 357) ">
198+ < rect x ="-116 " y ="-33 " width ="232 " height ="66 " rx ="6 "/>
199+ < text > Argo CD: Declarative Continuous Delivery</ text >
200+ </ g >
201+
202+ <!-- K8s -->
203+ < g class ="node " transform ="translate(757 515) ">
204+ < rect x ="-116 " y ="-33 " width ="232 " height ="66 " rx ="6 "/>
205+ < text > Kubernetes Cluster: NixOS nodes</ text >
206+ </ g >
207+
208+ <!-- Observability -->
209+ < g class ="node " transform ="translate(373 673) ">
210+ < rect x ="-92 " y ="-22.5 " width ="184 " height ="45 " rx ="6 "/>
211+ < text > Observability & Policy</ text >
212+ </ g >
213+
214+ <!-- Git → NixOS -->
215+ < g class ="node " transform ="translate(1301 199) ">
216+ < rect x ="-110 " y ="-22.5 " width ="220 " height ="45 " rx ="6 "/>
217+ < text > NixOS host specs: flake.nix</ text >
218+ </ g >
219+
220+ <!-- Git → Terraform -->
221+ < g class ="node " transform ="translate(1559 199) ">
222+ < rect x ="-98 " y ="-22.5 " width ="196 " height ="45 " rx ="6 "/>
223+ < text > Terraform infra: main.tf</ text >
224+ </ g >
225+
226+ <!-- Git → Dockerfile -->
227+ < g class ="node " transform ="translate(1823 199) ">
228+ < rect x ="-116 " y ="-33 " width ="232 " height ="66 " rx ="6 "/>
229+ < text > Dockerfiles & image lockfiles</ text >
230+ </ g >
231+
232+ <!-- Git → Argo Apps -->
233+ < g class ="node " transform ="translate(2105 199) ">
234+ < rect x ="-116 " y ="-33 " width ="232 " height ="66 " rx ="6 "/>
235+ < text > Argo CD Apps & Projects: YAML</ text >
236+ </ g >
237+
238+ <!-- Git → DSPy -->
239+ < g class ="node " transform ="translate(2367 199) ">
240+ < rect x ="-96 " y ="-22.5 " width ="192 " height ="45 " rx ="6 "/>
241+ < text > DSPy pipelines: Python</ text >
242+ </ g >
243+
244+ <!-- Namespaces -->
245+ < g class ="node " transform ="translate(984 515) ">
246+ < rect x ="-61 " y ="-22.5 " width ="122 " height ="45 " rx ="6 "/>
247+ < text > Namespace A</ text >
248+ </ g >
249+ < g class ="node " transform ="translate(1204 515) ">
250+ < rect x ="-61 " y ="-22.5 " width ="122 " height ="45 " rx ="6 "/>
251+ < text > Namespace B</ text >
252+ </ g >
253+ < g class ="node " transform ="translate(1424 515) ">
254+ < rect x ="-61 " y ="-22.5 " width ="122 " height ="45 " rx ="6 "/>
255+ < text > Namespace C</ text >
256+ </ g >
257+
258+ <!-- Add-ons -->
259+ < g class ="node " transform ="translate(631 673) ">
260+ < rect x ="-116 " y ="-33 " width ="232 " height ="66 " rx ="6 "/>
261+ < text > MetalLB / Cilium / cert-manager</ text >
262+ </ g >
263+ < g class ="node " transform ="translate(908 673) ">
264+ < rect x ="-111 " y ="-22.5 " width ="222 " height ="45 " rx ="6 "/>
265+ < text > External-DNS / CSI storage</ text >
266+ </ g >
267+ < g class ="node " transform ="translate(1141 673) ">
268+ < rect x ="-72 " y ="-22.5 " width ="144 " height ="45 " rx ="6 "/>
269+ < text > HPA / VPA / PDB</ text >
270+ </ g >
271+ < g class ="node " transform ="translate(1379 673) ">
272+ < rect x ="-116 " y ="-33 " width ="232 " height ="66 " rx ="6 "/>
273+ < text > PodSecurityPolicies / OPA Gatekeeper</ text >
274+ </ g >
275+ < g class ="node " transform ="translate(1616 673) ">
276+ < rect x ="-71 " y ="-22.5 " width ="142 " height ="45 " rx ="6 "/>
277+ < text > DSPy Jobs: Cron</ text >
278+ </ g >
279+ < g class ="node " transform ="translate(1804 673) ">
280+ < rect x ="-66 " y ="-22.5 " width ="133 " height ="45 " rx ="6 "/>
281+ < text > Game Services</ text >
282+ </ g >
283+
284+ <!-- Observability leaf nodes -->
285+ < g class ="node " transform ="translate(124 831) ">
286+ < rect x ="-116 " y ="-33 " width ="232 " height ="66 " rx ="6 "/>
287+ < text > Prometheus → Grafana dashboards</ text >
288+ </ g >
289+ < g class ="node " transform ="translate(375 831) ">
290+ < rect x ="-85 " y ="-22.5 " width ="171 " height ="45 " rx ="6 "/>
291+ < text > Loki / Tempo traces</ text >
292+ </ g >
293+ < g class ="node " transform ="translate(622 831) ">
294+ < rect x ="-111 " y ="-22.5 " width ="222 " height ="45 " rx ="6 "/>
295+ < text > Alertmanager → PagerDuty</ text >
296+ </ g >
297+ < g class ="node " transform ="translate(899 831) ">
298+ < rect x ="-116 " y ="-33 " width ="232 " height ="66 " rx ="6 "/>
299+ < text > Policy-as-code: OPA / Kyverno</ text >
300+ </ g >
301+
302+ <!-- ---------- Edges ---------- -->
303+ <!-- Git → CI -->
304+ < path d ="M1979 74L1026 166 " class ="edge " marker-end ="url(#arrowhead) "/>
305+ < text x ="1502 " y ="120 " class ="edge-label "> Pull-Request → Review → Merge</ text >
306+
307+ <!-- CI → Argo CD -->
308+ < path d ="M1026 232L1026 324 " class ="edge " marker-end ="url(#arrowhead) "/>
309+ < text x ="1046 " y ="278 " class ="edge-label "> Promotes validated artefacts</ text >
310+
311+ <!-- Argo CD → K8s -->
312+ < path d ="M1026 390L757 482 " class ="edge " marker-end ="url(#arrowhead) "/>
313+ < text x ="891 " y ="445 " class ="edge-label "> Kubernetes manifests: Kustomize/Helm</ text >
314+
315+ <!-- K8s → Observability -->
316+ < path d ="M757 548L373 640 " class ="edge " marker-end ="url(#arrowhead) "/>
317+
318+ <!-- Git → NixOS -->
319+ < path d ="M1979 90L1301 166 " class ="edge " marker-end ="url(#arrowhead) "/>
320+
321+ <!-- Git → Terraform -->
322+ < path d ="M1979 105L1559 166 " class ="edge " marker-end ="url(#arrowhead) "/>
323+
324+ <!-- Git → Dockerfile -->
325+ < path d ="M1982 120L1823 166 " class ="edge " marker-end ="url(#arrowhead) "/>
326+
327+ <!-- Git → Argo Apps -->
328+ < path d ="M2100 120L2105 166 " class ="edge " marker-end ="url(#arrowhead) "/>
329+
330+ <!-- Git → DSPy -->
331+ < path d ="M2209 120L2367 166 " class ="edge " marker-end ="url(#arrowhead) "/>
332+
333+ <!-- Argo CD → Namespaces -->
334+ < path d ="M984 423L984 482 " class ="edge " marker-end ="url(#arrowhead) "/>
335+ < path d ="M1204 423L1204 482 " class ="edge " marker-end ="url(#arrowhead) "/>
336+ < path d ="M1424 423L1424 482 " class ="edge " marker-end ="url(#arrowhead) "/>
337+
338+ <!-- K8s → Add-ons -->
339+ < path d ="M631 548L631 640 " class ="edge " marker-end ="url(#arrowhead) "/>
340+ < path d ="M908 548L908 640 " class ="edge " marker-end ="url(#arrowhead) "/>
341+ < path d ="M1141 548L1141 640 " class ="edge " marker-end ="url(#arrowhead) "/>
342+ < path d ="M1379 548L1379 640 " class ="edge " marker-end ="url(#arrowhead) "/>
343+ < path d ="M1616 548L1616 640 " class ="edge " marker-end ="url(#arrowhead) "/>
344+ < path d ="M1804 548L1804 640 " class ="edge " marker-end ="url(#arrowhead) "/>
345+
346+ <!-- Observability → leaf nodes -->
347+ < path d ="M124 708L124 800 " class ="edge " marker-end ="url(#arrowhead) "/>
348+ < path d ="M375 708L375 800 " class ="edge " marker-end ="url(#arrowhead) "/>
349+ < path d ="M622 708L622 800 " class ="edge " marker-end ="url(#arrowhead) "/>
350+ < path d ="M899 708L899 800 " class ="edge " marker-end ="url(#arrowhead) "/>
351+ </ svg >
272352 < hr >
273353
274354 < h2 > 5. A Day in the Life of a Change</ h2 >
0 commit comments