Decidere dove vivono i pacchetti di un’organizzazione non è una scelta di tooling: è architettura della supply chain. Su AZ-400 la domanda non è “come pubblico un pacchetto npm”, ma “dato questo perimetro di team e questo modello di promozione, quale topologia di feed e quale schema di versionamento reggono nel tempo”. Diamo per acquisiti i meccanismi base e concentriamoci sulle decisioni di design e sui trade-off.
GitHub Packages o Azure Artifacts: il criterio di scelta
Entrambi ospitano pacchetti privati e si integrano con la CI della rispettiva piattaforma. Le discriminanti architetturali sono due: il modello di permessi e la capacità di fare da proxy alle registry pubbliche.
GitHub Packages eredita la topologia di GitHub. Per impostazione predefinita un pacchetto collegato a un repository ne eredita i permessi di accesso, ma non la visibilità, che resta una scelta separata. Alcune registry supportano permessi granulari, definibili indipendentemente dal repository — Container registry, npm, NuGet e RubyGems — mentre Apache Maven e Gradle supportano solo permessi repository-scoped: il pacchetto resta sempre legato a un repo e ne segue le regole. È una differenza che pesa: se l’ecosistema è Java e serve un perimetro condiviso fra decine di team, il modello repository-scoped obbliga a modellare la sicurezza sul repository invece che sul dominio del pacchetto. Nei workflow Actions il GITHUB_TOKEN consente di pubblicare, installare, eliminare e ripristinare i pacchetti associati al repository del workflow: perfetto nel mono-repo, insufficiente quando il consumer sta altrove.
Azure Artifacts ruota invece attorno al feed, contenitore multi-protocollo (tra cui NuGet, npm, Maven, Python, Cargo e Universal Packages) con un controllo di accesso proprio, indipendente dai repository di codice. I ruoli sono espliciti: Feed and Upstream Reader (Collaborator) per leggere e salvare da upstream, Feed Publisher (Contributor) per pubblicare, promuovere e deprecare.
Il criterio si riduce a questo. Se il codice vive su GitHub, il consumo resta interno alla stessa organizzazione e non serve mediare le registry pubbliche, GitHub Packages riduce le parti in movimento. Se il requisito è una sorgente di verità unica e multi-linguaggio, con proxy delle registry pubbliche e promozione per livello di qualità, la risposta è Azure Artifacts. Le due opzioni non si escludono: build su GitHub Actions e pubblicazione verso un feed Azure Artifacts è legittima quando la governance delle dipendenze è centralizzata.
Progettare feed e view: separare qualità e identità
Il primo bivio è lo scope del feed. Un feed project-scoped eredita la visibilità del progetto e ne porta il nome nell’URL; è lo scope raccomandato. Un feed organization-scoped è privato di default e sempre disponibile dal menu dei feed. Il vincolo decisivo: un feed organization-scoped non può essere convertito in project-scoped, quindi la scelta va presa all’inizio. C’è poi un costo operativo asimmetrico: per raggiungere un feed project-scoped da una pipeline di un altro progetto occorre concedere il ruolo sia al Project Collection Build Service sia al Build Service del progetto che esegue la pipeline, mentre per un feed organization-scoped basta il primo.
Il secondo bivio riguarda le view. Ogni feed nasce con @local, @prerelease e @release; le ultime due sono suggerite e possono essere rinominate o eliminate. La regola che governa tutto il design è che si pubblica solo sul feed base, quindi in @local: le view sono read-only e ci si arriva per promozione, non per push.
Questa asimmetria è il punto architetturale. Il numero di versione comunica bene la natura e il rischio di una modifica, noti quando lo sviluppo comincia. Non comunica la qualità, nota solo dopo la validazione, quando il pacchetto è già stato costruito e versionato. Le view risolvono esattamente questo: un consumer connesso a @release vede solo le versioni che hanno superato il gate, senza che nessuno debba ricostruire o ri-numerare il pacchetto. Il design corretto è quindi una pipeline che pubblica su @local e un rilascio che promuove verso @prerelease e poi @release, con la promozione come punto di controllo governato.
Un effetto collaterale prezioso: i pacchetti promossi a una view sono esenti dalle retention policy. La promozione è quindi anche la leva corretta per conservare indefinitamente una versione critica, invece di disattivare la retention sull’intero feed. Infine, perché un altro feed possa usare il tuo come upstream la visibilità va aperta ai membri dell’organizzazione o del tenant Microsoft Entra ID; per contro, in un feed pubblico ogni view è accessibile a chiunque.
Upstream source: restore deterministico e integrità
L’upstream source trasforma il feed da repository privato a unico punto di ingresso di ogni dipendenza, incluse quelle da registry pubbliche. Il beneficio di design non è la comodità, sono tre proprietà.
La prima è il restore deterministico. Azure Artifacts impone un ordine di ricerca fisso: prima i pacchetti pubblicati direttamente sul feed, poi quelli già salvati da upstream, infine gli upstream nell’ordine configurato. È l’opposto di NuGet con più sorgenti in configurazione, che interroga i feed in parallelo e processa le risposte in ordine di arrivo, con esiti non riproducibili. Corollario: il file di configurazione deve referenziare un solo feed, azzerando l’ereditarietà dai file di livello superiore.
La seconda è la resilienza: ogni pacchetto installato da un upstream viene salvato nel feed e resta disponibile anche se la registry pubblica è in manutenzione. Le versioni sono immutabili e i pacchetti salvati restano anche se l’upstream viene rimosso.
La terza è l’integrità. Con un upstream abilitato non puoi pubblicare una versione che esiste già in quell’upstream: è una difesa strutturale contro la sostituzione di pacchetti. Per un override consapevole la sequenza è disabilitare l’upstream, pubblicare, riabilitare.
Sull’ordine degli upstream: con sole registry pubbliche è irrilevante; mescolando feed interni e registry pubbliche le pubbliche vanno prima, salvo quando l’organizzazione ricompila internamente pacchetti open source per sicurezza o compliance, caso in cui il feed con le versioni personalizzate va messo davanti.
Versionamento: SemVer, CalVer e artifact di pipeline
SemVer (major.minor.patch) è lo standard per le librerie consumate da altro codice, perché codifica un contratto: il major segnala il breaking change, il minor le funzionalità aggiunte in compatibilità, il patch la correzione. È ciò che serve al consumer per decidere se aggiornare, e ciò che rende sensati i range di dipendenza. Il suffisso di prerelease è il complemento naturale delle view: una prerelease promossa a @prerelease dice insieme cosa cambia e a che punto della validazione si trova.
CalVer (schemi datati, per esempio 2026.07.1) comunica invece quando un artefatto è stato prodotto, non che cosa rompe. Ha senso per deliverable a cadenza fissa, agent, immagini o distribuzioni, dove il consumer ragiona per data di rilascio e finestra di supporto e non esiste un contratto API da onorare. Il trade-off è netto: rende immediata la lettura dell’anzianità e inutile quella del rischio. Regola di design: SemVer per ciò che viene compilato dentro altro codice, CalVer per ciò che viene installato o distribuito. Applicare CalVer a una libreria condivisa toglie ai consumer l’unico segnale automatico di breaking change.
Per gli artifact di pipeline il requisito cambia ancora: non serve un contratto, servono correlazione e unicità. Ogni esecuzione deve produrre una versione irripetibile e riconducibile al commit, tipicamente combinando il numero di build o di run con il SHA. Vale poi il principio build once, deploy many: l’artefatto è prodotto una volta sola e attraversa gli ambienti immutato, mentre a cambiare sono configurazione e promozione. Ricompilare per ogni ambiente rompe la tracciabilità e vanifica i test a monte.
Trappole tipiche d’esame
- “Un solo punto di configurazione per tutte le dipendenze, incluse quelle pubbliche, con restore riproducibile” → feed con upstream source e configurazione che punta a un solo feed; non elencare più sorgenti nel file di configurazione.
- “Solo le versioni validate visibili ai consumer, senza ricompilare o cambiare numero di versione” → promozione a @release; non un feed separato per il rilascio né una rinumerazione.
- “Un pacchetto certificato non deve mai essere rimosso dalla pulizia automatica” → promuovilo a una view, che lo rende esente dalle retention policy; non disattivare la retention sull’intero feed.
- “Impedire la pubblicazione interna di una versione che collide con un pacchetto pubblico” → comportamento nativo con upstream abilitato.
- “Team Java, decine di repository, perimetro di accesso ai pacchetti indipendente dai repository” → Azure Artifacts: su GitHub Packages Maven e Gradle supportano solo permessi repository-scoped.
- “Agent distribuito a cadenza fissa, nessun contratto API verso i consumer” → CalVer; SemVer qui non aggiunge informazione utile.
- “Il feed è organization-scoped ma serve la visibilità ereditata dal progetto” → non è convertibile: creare un nuovo feed project-scoped e migrare il contenuto.