GovStack: Modulární rámec pro digitální vládu

GovStack je mezinárodní iniciativa zahájená v roce 2020 Estonsko, Německo, ITU (Mezinárodní telekomunikační unie) e DIAL (Digital Impact Alliance) s cílem je sdílet nástroje, znalosti a osvědčené postupy potřebné k budování služeb digitální publikum ve velkém, aniž byste museli pokaždé začínat od nuly.

Základní myšlenka je jednoduchá, ale účinná: namísto vývoje monolitických systémů pro každou zemi vládní digitální služba je rozložena na BuildingBlock (funkční moduly), které nabízejí specifickou schopnost (totožnost, platby, zasílání zpráv, registry atd.) a to lze je libovolně kombinovat a budovat jakoukoli službu. Stavební bloky jsou interoperabilní, opakovaně použitelný a nezávislý na implementaci: GovStack definuje specifikace, nikoli software.

V roce 2025, se spuštěním GovSpecs 2.0 (strategie 2025–2027), GovStack vylepšil integrací nových stavebních bloků, aktualizací specifikací interoperability a definováním odstupňovaný model vyspělosti na podporu zemí s velkou digitální kapacitou jiný. Více než 20 zemí aktivně využívá přístup GovStack.

Co se naučíte

  • 9 základních stavebních bloků GovStack a jejich technické specifikace
  • Jak mapovat stávající italské PA služby (SPID, CIE, pagoPA) na GovStack Building Blocks
  • Referenční architektura GovStack: vrstvený model a integrační sběrnice
  • GovSpecs 2.0: co je nového ve strategii 2025–2027
  • Praktická implementace Building Block Identity s OpenID Connect
  • Building Block Payments: integrace s národními platebními systémy
  • Consent Building Block: Správa souhlasu v souladu s GDPR
  • Jak vyhodnotit, zda je GovStack vhodný pro váš kontext

9 základních stavebních kamenů

GovStack definuje 9 základních stavebních bloků (publikovaných v nové specifikaci 2025), které pokrývají křížové funkce nezbytné pro jakoukoli digitální vládní službu:

BuildingBlock Funkce Protokoly / standardy Italský příklad
Identita Autentizace a správa identit OIDC, SAML, W3C DID Peněženka SPID, CIE, EUDI
Platby Zpracování plateb a převodů ISO 20022, REST API payPA
Souhlas Sběr a správa souhlasů Rozsahy GDPR, DPIA, OAuth CMP s GDPR-by-Design
Digitální registry Vedení autoritativních registrů (matrika, katastr nemovitostí) REST API, FHIR, CKAN ANPR, katastr nemovitostí, profesní rejstřík
Zasílání zpráv Bezpečná komunikace mezi vládou a občany SMTP, Push, WebSocket, MQTT IO App, PEC, upozornění pagoPA
Zprostředkování informací Bezpečná výměna dat mezi systémy X-Road, REST, GraphQL PDND, AgID interoperabilita
Registrace Registrace osob/subjektů pro služby REST API, OAuth 2.0 Portál INPS, SUAP, komunální portály
Plánovač Správa schůzek a rezervací iCalendar, REST API Health CUP, digitální počítadlo
Pracovní postup Orchestrování procesů a procedur BPMN, vzor SAGA, REST Systémy řízení praxe PA

Referenční architektura GovStack

GovStack navrhuje vrstvenou architekturu, ve které jsou stavební bloky umístěny ve vrstvách s jasnými povinnostmi:

  • Vrstva 0 – Infrastruktura: cloud, síť, zabezpečení. BB nejsou závislé na konkrétním cloudu; mohou běžet na AWS, Azure, GCP nebo on-premise infrastruktuře.
  • Vrstva 1 - Základní stavební bloky: 9 základních BB. Jsou to autonomní služby, které odhalují Standardizovaná RESTful API. Každý BB má veřejnou specifikaci (schemata OpenAPI + JSON).
  • Vrstva 2 – Sdílené služby: průřezové služby, jako je centralizované protokolování, síť služeb, API brána, zjišťování služeb. Tyto služby podporují všechny BB.
  • Vrstva 3 – Aplikace: skutečné digitální služby (zápis do školy, žádost o certifikát, platba kolkovného), které řídí podkladové BB.
# Architettura di un servizio PA con Building Block GovStack
# Esempio: Servizio di iscrizione scolastica online

# Il servizio orchestra 5 Building Block:
# 1. Identity BB - autenticazione genitore con SPID/CIE
# 2. Registration BB - raccolta dati del bambino
# 3. Digital Registries BB - verifica iscrizione anagrafica
# 4. Consent BB - consenso al trattamento dati minori
# 5. Messaging BB - conferma iscrizione via IO App/email
# 6. Payments BB - pagamento tassa iscrizione via pagoPA

from dataclasses import dataclass
from typing import Optional
import httpx

@dataclass
class SchoolEnrollmentRequest:
    parent_session_token: str   # Token da Identity BB (SPID/CIE)
    child_fiscal_code: str
    school_code: str
    year: int

class SchoolEnrollmentService:
    """
    Servizio iscrizione scolastica che orchestra i Building Block GovStack.
    Pattern: Saga orchestrator (gestione stati distribuiti con compensazioni).
    """

    def __init__(
        self,
        identity_bb_url: str,
        registry_bb_url: str,
        consent_bb_url: str,
        messaging_bb_url: str,
        payments_bb_url: str
    ):
        self.identity_url = identity_bb_url
        self.registry_url = registry_bb_url
        self.consent_url = consent_bb_url
        self.messaging_url = messaging_bb_url
        self.payments_url = payments_bb_url

    async def process_enrollment(self, request: SchoolEnrollmentRequest) -> dict:
        """
        Saga: processo iscrizione con compensazioni in caso di errore.
        Ogni step è idempotente e reversibile.
        """
        saga_log = []

        try:
            # Step 1: Verifica identità genitore tramite Identity BB
            parent_identity = await self._verify_parent_identity(
                request.parent_session_token
            )
            saga_log.append({"step": "identity_verified", "status": "ok"})

            # Step 2: Verifica residenza bambino tramite Digital Registries BB
            child_registry = await self._verify_child_in_registry(
                request.child_fiscal_code,
                parent_identity["fiscal_number"]
            )
            saga_log.append({"step": "registry_verified", "status": "ok"})

            # Step 3: Raccolta consenso GDPR tramite Consent BB
            consent_id = await self._collect_consent(
                parent_identity["fiscal_number"],
                purpose="school_enrollment_data_processing"
            )
            saga_log.append({"step": "consent_collected", "consent_id": consent_id})

            # Step 4: Registrazione iscrizione tramite Registration BB
            enrollment_id = await self._register_enrollment(
                child_fc=request.child_fiscal_code,
                school_code=request.school_code,
                year=request.year,
                parent_fc=parent_identity["fiscal_number"]
            )
            saga_log.append({"step": "enrollment_registered", "enrollment_id": enrollment_id})

            # Step 5: Pagamento tassa (se prevista) tramite Payments BB
            payment_url = await self._create_payment(
                parent_fc=parent_identity["fiscal_number"],
                enrollment_id=enrollment_id,
                amount_cents=1500  # 15 euro
            )
            saga_log.append({"step": "payment_created", "payment_url": payment_url})

            # Step 6: Notifica conferma tramite Messaging BB
            await self._send_confirmation(
                parent_fc=parent_identity["fiscal_number"],
                enrollment_id=enrollment_id,
                payment_url=payment_url
            )
            saga_log.append({"step": "notification_sent", "status": "ok"})

            return {
                "status": "success",
                "enrollment_id": enrollment_id,
                "payment_url": payment_url,
                "saga_log": saga_log
            }

        except Exception as e:
            # Compensazione: rollback degli step completati in ordine inverso
            await self._compensate(saga_log, e)
            raise

    async def _verify_parent_identity(self, session_token: str) -> dict:
        """Chiama il Building Block Identity per validare il token SPID/CIE."""
        async with httpx.AsyncClient() as client:
            response = await client.post(
                f"{self.identity_url}/v1/tokens/validate",
                json={"token": session_token}
            )
            response.raise_for_status()
            return response.json()  # Returns: fiscal_number, name, surname, etc.

    async def _verify_child_in_registry(self, child_fc: str, parent_fc: str) -> dict:
        """Chiama il Building Block Digital Registries per verificare l'anagrafe."""
        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"{self.registry_url}/v1/citizens/{child_fc}/family-relations",
                params={"parent_fiscal_code": parent_fc}
            )
            response.raise_for_status()
            return response.json()

    async def _collect_consent(self, parent_fc: str, purpose: str) -> str:
        """Registra il consenso tramite il Building Block Consent."""
        async with httpx.AsyncClient() as client:
            response = await client.post(
                f"{self.consent_url}/v1/consents",
                json={
                    "citizen_pseudonym": self._pseudonymize(parent_fc),
                    "purpose": purpose,
                    "legal_basis": "GDPR Art. 6(1)(e)",
                    "version": "2025-01"
                }
            )
            response.raise_for_status()
            return response.json()["consent_id"]

    async def _compensate(self, saga_log: list, error: Exception):
        """
        Compensazione Saga: rollback in ordine inverso degli step completati.
        Garantisce consistenza anche in caso di errori parziali.
        """
        for step in reversed(saga_log):
            try:
                if step["step"] == "consent_collected":
                    await self._revoke_consent(step["consent_id"])
                elif step["step"] == "enrollment_registered":
                    await self._cancel_enrollment(step["enrollment_id"])
            except Exception as compensation_error:
                # Logga l'errore di compensazione ma continua
                print(f"Compensation error for {step['step']}: {compensation_error}")

    def _pseudonymize(self, fiscal_code: str) -> str:
        import hashlib, hmac
        key = b"secret-vault-key"  # In produzione: usa un HSM
        return hmac.new(key, fiscal_code.encode(), hashlib.sha256).hexdigest()[:32]

GovSpecs 2.0: Co je nového ve strategii 2025–2027

GovSpecs 2.0, oznámený GovStack v roce 2025, zavádí důležité aktualizace k předchozí verzi specifikací:

  • 4úrovňový model zralosti: z "Počáteční fáze" (pouze školení a architektura vysoká úroveň) na „pokročilou integraci“ (více BB integrovaných do vnitrostátních rámců interoperability). To umožňuje zemím přijmout GovStack podle svých vlastních priorit a schopností.
  • Podpora pro ověřitelné přihlašovací údaje: Building Block Identity nyní obsahuje specifikace pro W3C Verifiable Credentials, v souladu s eIDAS 2.0 a evropským programem EUDI Wallet.
  • DO BB: nový stavební blok pro integraci komponent umělé inteligence (jazykové modely, predikce, klasifikace) ve vládních službách s důrazem na transparentnost a vysvětlitelnost.
  • Specifikace OpenAPI 3.1: Všechna BB API jsou nyní zdokumentována s OpenAPI 3.1, s Kompletní schémata JSON a testovatelné příklady prostřednictvím SwaggerUI.
  • Testování shody: Automatizovaný rámec pro ověření implementace of BB vyhovuje specifikacím GovStack.

Implementace Building Block Identity s OIDC

Identita stavebního bloku je nejkritičtější a nejsložitější na implementaci. GovStack určuje, že musí vystavit následující rozhraní API:

# Building Block Identity - Implementazione minima conforme GovStack
# OpenAPI 3.1 compatible - FastAPI implementation

from fastapi import FastAPI, HTTPException, Header, Depends
from fastapi.security import HTTPBearer
from pydantic import BaseModel
from typing import Optional
import jwt

app = FastAPI(
    title="Identity Building Block",
    description="GovStack Identity BB - Conforme a GovSpecs 2.0",
    version="2.0.0"
)

security = HTTPBearer()

# --- Modelli ---

class TokenValidationRequest(BaseModel):
    token: str
    expected_acr: Optional[str] = None  # Livello autenticazione richiesto

class IdentityResponse(BaseModel):
    sub: str                    # Identificativo presso l'IdP
    fiscal_number: Optional[str] = None  # Codice Fiscale (SPID/CIE)
    given_name: str
    family_name: str
    birthdate: Optional[str] = None
    acr: str                    # Livello autenticazione effettivo
    auth_time: int              # Timestamp autenticazione
    session_valid_until: int    # Scadenza sessione

class SessionCreationRequest(BaseModel):
    idp_id: str                 # Identity Provider scelto dall'utente
    redirect_uri: str
    acr_values: str = "https://www.spid.gov.it/SpidL2"
    scope: list = ["openid", "profile"]
    ui_locales: str = "it"

# --- Endpoints del Building Block Identity ---

@app.post("/v1/sessions",
    summary="Avvia sessione di autenticazione",
    tags=["Sessions"],
    response_model=dict)
async def create_authentication_session(request: SessionCreationRequest) -> dict:
    """
    GovStack Identity BB - Avvia il flusso di autenticazione.
    Restituisce l'URL di redirect verso l'IdP.
    """
    # Genera state e nonce per sicurezza
    import secrets
    session_id = secrets.token_urlsafe(32)
    state = secrets.token_urlsafe(32)
    nonce = secrets.token_urlsafe(32)

    # Recupera metadata dell'IdP dalla federazione OIDC
    idp_metadata = await get_idp_metadata(request.idp_id)

    # Costruisce URL autorizzazione (PKCE + Request Object)
    auth_url = build_oidc_auth_url(
        idp_auth_endpoint=idp_metadata["authorization_endpoint"],
        client_id=CLIENT_ID,
        redirect_uri=request.redirect_uri,
        scope=request.scope,
        state=state,
        nonce=nonce,
        acr_values=request.acr_values,
        private_key=SIGNING_KEY
    )

    # Salva sessione (Redis o DB)
    await session_store.save(session_id, {
        "state": state, "nonce": nonce, "idp_id": request.idp_id
    })

    return {"session_id": session_id, "authorization_url": auth_url}

@app.post("/v1/tokens/validate",
    summary="Valida un token di sessione",
    tags=["Tokens"],
    response_model=IdentityResponse)
async def validate_token(request: TokenValidationRequest) -> IdentityResponse:
    """
    GovStack Identity BB - Valida un token e restituisce l'identità.
    I servizi chiamanti usano questo endpoint per verificare l'autenticazione.
    """
    try:
        # Decodifica e valida il token (firma, scadenza, audience)
        claims = jwt.decode(
            request.token,
            JWKS,
            algorithms=["RS256"],
            audience=CLIENT_ID
        )

        # Verifica livello autenticazione se richiesto
        if request.expected_acr:
            actual_acr = claims.get("acr", "")
            if not _meets_acr_requirement(actual_acr, request.expected_acr):
                raise HTTPException(
                    status_code=403,
                    detail=f"Insufficient authentication level. Required: {request.expected_acr}"
                )

        return IdentityResponse(
            sub=claims["sub"],
            fiscal_number=claims.get("fiscal_number"),
            given_name=claims["given_name"],
            family_name=claims["family_name"],
            birthdate=claims.get("birthdate"),
            acr=claims.get("acr", ""),
            auth_time=claims.get("auth_time", 0),
            session_valid_until=claims.get("exp", 0)
        )
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token expired")
    except jwt.InvalidTokenError as e:
        raise HTTPException(status_code=401, detail=f"Invalid token: {str(e)}")

@app.delete("/v1/sessions/{session_id}",
    summary="Termina sessione (logout)",
    tags=["Sessions"])
async def end_session(session_id: str) -> dict:
    """
    GovStack Identity BB - Logout con propagazione verso l'IdP.
    Implementa OIDC Back-Channel Logout per notificare tutti i RP attivi.
    """
    session = await session_store.get(session_id)
    if not session:
        raise HTTPException(status_code=404, detail="Session not found")

    # Notifica logout all'IdP (OIDC Back-Channel Logout)
    idp_metadata = await get_idp_metadata(session["idp_id"])
    if "end_session_endpoint" in idp_metadata:
        await propagate_logout(idp_metadata["end_session_endpoint"], session)

    await session_store.delete(session_id)
    return {"status": "session_terminated"}

def _meets_acr_requirement(actual: str, required: str) -> bool:
    """Verifica che il livello di autenticazione effettivo soddisfi il requisito."""
    ACR_LEVELS = {
        "https://www.spid.gov.it/SpidL1": 1,
        "https://www.spid.gov.it/SpidL2": 2,
        "https://www.spid.gov.it/SpidL3": 3,
        "https://www.cie.gov.it/cie/aa": 2,
    }
    return ACR_LEVELS.get(actual, 0) >= ACR_LEVELS.get(required, 0)

Building Block Messaging: IO aplikace a vládní oznámení

Building Block Messaging řídí komunikaci mezi vládou a občany. V Itálii služba je více v souladu se specifikací GovStack Messaging I App (io.italia.it), národní aplikace pro komunikaci s PA spravovanou společností PagoPA S.p.A.

# Building Block Messaging - Integrazione con IO App
# API REST di IO App per invio messaggi ai cittadini

import httpx
from pydantic import BaseModel
from typing import Optional

class IOMessage(BaseModel):
    fiscal_code: str           # Codice Fiscale del destinatario
    time_to_live: int = 3600  # Secondi di validità notifica push
    content: dict              # Contenuto del messaggio
    default_addresses: Optional[dict] = None  # Fallback email

class IOAppClient:
    """
    Client per le API di IO App.
    Le API IO App sono disponibili su https://developer.io.italia.it
    """

    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.io.italia.it/api/v1"

    async def send_message(self, message: IOMessage) -> dict:
        """
        Invia un messaggio a un cittadino tramite IO App.
        Il cittadino deve aver attivato il proprio profilo IO.
        """
        async with httpx.AsyncClient() as client:
            response = await client.post(
                f"{self.base_url}/messages",
                headers={
                    "Ocp-Apim-Subscription-Key": self.api_key,
                    "Content-Type": "application/json"
                },
                json={
                    "fiscal_code": message.fiscal_code,
                    "time_to_live": message.time_to_live,
                    "content": message.content,
                    "default_addresses": message.default_addresses
                }
            )
            response.raise_for_status()
            return response.json()

    async def check_profile(self, fiscal_code: str) -> bool:
        """
        Verifica se un cittadino ha attivato il profilo IO e accetta messaggi dalla PA.
        """
        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"{self.base_url}/profiles/{fiscal_code}",
                headers={"Ocp-Apim-Subscription-Key": self.api_key}
            )
            if response.status_code == 404:
                return False
            response.raise_for_status()
            profile = response.json()
            return profile.get("sender_allowed", False)

# Utilizzo: notifica iscrizione scolastica
async def notify_enrollment(fiscal_code: str, enrollment_id: str, payment_url: str):
    io_client = IOAppClient(api_key="your-io-api-key")

    # Verifica se il cittadino usa IO App
    has_io = await io_client.check_profile(fiscal_code)

    if has_io:
        # Messaggio strutturato IO App con CTA pagamento
        message = IOMessage(
            fiscal_code=fiscal_code,
            content={
                "subject": f"Iscrizione Scolastica {enrollment_id} - Conferma",
                "markdown": f"""
## La tua iscrizione è stata registrata

L'iscrizione con codice **{enrollment_id}** è stata registrata con successo.

Per completare la procedura, effettua il pagamento della tassa di iscrizione tramite il
link qui sotto.

**Importo**: 15,00 €

[Paga ora]({payment_url})

Per assistenza: [Ufficio Scolastico](https://istruzione.comune.esempio.it)
""",
                "payment_data": {
                    "amount": 1500,
                    "notice_number": enrollment_id,
                    "payee": {
                        "fiscal_code": "COMUNE_FC",
                        "name": "Comune di Esempio"
                    }
                }
            },
            default_addresses={"email": None}  # No fallback email
        )
        await io_client.send_message(message)
    else:
        # Fallback: email tradizionale (da implementare)
        await send_email_notification(fiscal_code, enrollment_id, payment_url)

Mapa GovStack na italském ekosystému

Itálie má již pokročilý ekosystém OOP, který se přirozeně mapuje na stavební bloky GovStack. Pro italského vývojáře přijetí GovStack neznamená začít od nuly: znamená to reorganizovat stávající integrace podle standardizovaného modulárního modelu.

BB GovStack Italská implementace Úroveň zralosti Mezera k vyplnění
Identita SPID, CIE, eIDAS Vysoká (úroveň 3–4) Kompletní sjednocení OIDC, EUDI Wallet
Platby payPA Vysoká (úroveň 4) Kompletní GPD REST API
Zasílání zpráv IO aplikace, PEC Vysoká (úroveň 3) Penetrace IO App (v některých oblastech stále nízká)
Digitální registry ANPR, katastr nemovitostí, PDND Střední (úroveň 2–3) Interoperabilita mezi registry je stále částečná
Zprostředkování informací PDND Střední (úroveň 2) Adopce PDND stále roste
Souhlas Různé (nestandardizované) Nízká (úroveň 1) Potřebujeme centralizovanou platformu pro konsensus
Plánovač Krajský zdravotní POHÁR Nízký-Střední (úroveň 1-2) Regionální fragmentace; žádný národní standard
Pracovní postup PA cvičné systémy (heterogenní) Nízká (úroveň 1) Velká heterogenita; je potřeba standardizace

Kdy přijmout GovStack

GovStack je zvláště vhodný, když:

  • Stavíte a nová digitální služba PA od nuly a chcete se vyhnout závislosti na dodavatelích
  • Potřebujete integrovat stávající systémy a chtít sdílený referenční architektonický model
  • Působíte v kontextu mnohonárodnostní (mezinárodní spolupráce, přeshraniční služby)
  • Chcete přispívat do open source ekosystému GovStack a těžit z implementací v jiných zemích

GovStack je méně vhodný, když máte velmi specifické služby pro italský kontext, když systémy stávající (SPID, pagoPA) již splňují vaše požadavky bez režie dodatečné abstrakce, nebo když máte omezené prostředky ke správě složitosti distribuované federace.

Závěr: Série GovTech v perspektivě

Tento článek uzavírá sérii GovTech věnovanou digitalizaci veřejné správy. Prozkoumali jsme celý ekosystém: od veřejné digitální infrastruktury (DPI) po identitu Evropská unie (eIDAS 2.0, EUDI Wallet), od konkrétní implementace SPID a CIE k ochraně dat (GDPR-by-Design), od přístupnosti (WCAG 2.1 AA) po otevřená data (DCAT-AP_IT, CKAN) až mezinárodní modulární rámce (GovStack).

Společné vlákno je vždy stejné: veřejné digitální služby musí být včetně, bezpečné, interoperabilní a opakovaně použitelné. GovStack se svými standardizovanými stavebními bloky dodává společný slovník a sdílený architektonický model k dosažení těchto cílů v celosvětovém měřítku.

Celá řada GovTech

  • #00: Digitální veřejná infrastruktura – architektura a stavební blok
  • #01: eIDAS 2.0 a EUDI Wallet – příručka pro vývojáře
  • #02: OpenID Connect for Government Identity
  • #03: Open Data API Design – publikování a konzumace veřejných dat
  • #04: GDPR-by-Design - Architektonické vzory pro veřejné služby
  • #05: Přístupné uživatelské rozhraní pro implementaci PA - WCAG 2.1 AA
  • #06: Integrace vládního API - SPID, CIE a IT digitální služby
  • #07: GovStack Building Block - (tento článek)