-
Notifications
You must be signed in to change notification settings - Fork 0
Next Generation Implementation Roadmap
Cette page transforme la proposition d'architecture en liste de travail vérifiable. Une case cochée signifie que la capacité existe dans le dépôt et possède des tests ; elle ne signifie pas qu'une commande externe fournit temporairement une capacité équivalente.
État vérifié le 4 août 2026. Les jalons v1, v2 et v3 sont complets par les critères de sortie de chacun (voir les sections dédiées). Le protocole plugin, cinq langages (Go/Python/JavaScript/ TypeScript/C#) avec de vrais SDK importables, passe la même suite de conformance ; le sandbox plugin isole namespace utilisateur/réseau/IPC/ UTS, fichiers (remount lecture seule, tmpfs sur les répertoires sensibles) et ressources (rlimits) - reste ouvert : pas de limite mémoire par langage (
RLIMIT_AStesté et abandonné, casse le démarrage du runtime Go), et repli non sandboxé hors Linux. v4 (VMM natif) et v5 (plateforme distribuée) restent ouverts par construction - voir leurs sections. Les cases ci-dessous distinguent les capacités testées des interfaces réservées ou des bibliothèques qui ne sont pas encore reliées à un chemin d'exécution réel.Le jalon v3 est complet. Le CAS chunké/reprenable, le client Registry natif, la reproductibilité hermétique testée sur les trois OS de la matrice CI, SBOM et provenance natifs, signature Ed25519/ECDSA et vérification de chaîne X.509, ainsi que le moteur de politique, sont intégrés au parcours de publication natif. Un banc logique de 2 TiB exerce la mémoire bornée et la déduplication sans exiger 2 TiB de disque.
launch --publish --yesorchestre freeze, deux builds reproductibles, preuves natives, publication puis exécution. Les sessions Registry sont persistées en0600, réconciliées avec l'offset serveur puis supprimées après installation exacte du blob. Une reprise après crash publie exactement les blobs attendus, et une image altérée, non attestée ou non reproductible est refusée avant toute mise à jour de tag.Parcours utilisateurs. Le dépôt fournit désormais un exemple canonique par capacité majeure sous
examples/. Les nouveaux guidesexamples/podman-microvmetexamples/containerd-kubernetes/runtimeclass.yamlmontrent respectivement les deux modes du même OCI (conteneur standard ou MicroVM opt-in) et un Pod Kubernetes sélectionnant explicitement laRuntimeClass platform-factory. Le catalogue et la structure du manifeste sont protégés parexamples/catalog_test.go; ces tests empêchent la dérive mais les documents restent écrits pour les utilisateurs, pas comme fixtures CI.Audit du 3 août 2026 — 7 cases réouvertes. Après fusion de
release/v4.0.0dansmain, un audit ciblé des paquets ajoutés récemment (internal/errors,internal/observability,internal/budget,internal/quota,internal/provenance/signing.go,internal/hypervisor/virtio,internal/hypervisor/sandbox) a trouvé qu'aucun n'est importé en dehors de son propre paquet et de son propre test — vérifié pargrepsur tout le dépôt, pas par lecture partielle. Chacun respecte la première clause de la définition de terminé (« l'implémentation est dans le dépôt ») mais pas la dernière (« la CI exécute le chemin concerné ») : ce sont des bibliothèques testées isolément, jamais appelées par un chemin d'exécution réel (control plane, worker, scheduler, VMM). Deux cases portaient en plus une justification explicitement fausse (usage prétendu parinternal/policyetinternal/oci/builder, infirmé par audit). Les 7 cases concernées sont décochées ci-dessous avec le détail de l'audit ; aucune n'affecte les critères de sortie déjà atteints de v1/v2/v3, qui reposent sur un code différent et réellement câblé.Suivi le 3 août 2026, même session.
internal/errors(câblé dansinternal/policy),internal/observability(câblé dansplatform-factory-control-plane),internal/budget(câblé dansinternal/oci.Build, pasinternal/executor- voir la case, la mesure par processus appelant y aurait été fausse),internal/quota(câblé en opt-in dansplatform-factory-control-plane) etinternal/provenance/signing.go(câblé entreplatform-factory-workeretplatform-factory-control-plane, avec la bonne APIWorkloadSigner- pasSignWithWorkloadIdentity, dont l'audit a aussi trouvé qu'elle ne garantit rien par construction) sont maintenant réellement intégrés et re-cochés ci-dessous, chacun avec sa preuve. Restent ouverts :internal/hypervisor/virtio(stubs de configuration, aucun protocole virtio réel) etinternal/hypervisor/sandbox(seccomp/namespaces/cgroups sont eux-mêmes des stubs internes, pas seulement non câblés - reporté à un environnement Linux/KVM réel pour être implémenté et prouvé, plutôt que d'écrire du code noyau non vérifiable ici).
- Conserver la génération du layout OCI indépendante de Docker et Podman.
- Conserver BuildKit hors du moteur et de son chemin critique.
- Retirer Skopeo du chemin de publication au profit du client Registry natif.
- Retirer Skopeo des éventuels chemins d'import restants. Audit :
aucune référence à Skopeo dans
cmd//internal/(seulci-compatibility.ymll'utilise, intentionnellement, comme client externe validant la compatibilité - voir ADR-007). L'import local vers un runtime (cmd/platform-factory/import.go) construit déjà nativement des archives OCI/Docker Save.ci-security.ymléchoue désormais si le littéralskopeoapparaît dans un fichier.godecmd/internal, garde indépendante de l'allowlistos/execexistante. - Retirer la CLI Cosign des chemins de signature et de vérification au
profit du moteur cryptographique natif. Audit : aucune référence à
cosigndanscmd//internal/;internal/signingimplémente Ed25519/ECDSA nativement etlaunch --publishl'utilise déjà (voir critères de sortie v3). Seulsci-release.yml,ci-microvm.yml,ci-benchmark.ymletci-supply-chain-e2e.ymlinvoquent Cosign, intentionnellement, comme étage de confiance CI séparé (signature keyless GHCR, signature de preuves, tests d'interopérabilité d'admission - voir ADR-006/ADR-007), jamais comme chemin du produit.ci-security.ymléchoue désormais si le littéralcosignapparaît dans un fichier.godecmd/internal, même garde mécanique que pour Skopeo. - Retirer les outils SBOM externes du chemin autonome et de publication.
- Fournir un mode complet qui ne lance aucun moteur de build, de
publication, de signature ou de virtualisation tiers.
Prouvé par l'absence totale de
skopeo,cosign,qemu,firecracker,cloud-hypervisor,kataoulibkrundanscmd/etinternal/. Le cheminplatform-factory launch --publishutilise uniquement : toolchains OCI épinglées par digest, client Registry natif, moteur crypto natif (Ed25519/ECDSA), et VMM natif (KVM/HVF). - Documenter et tester la frontière entre la bibliothèque standard Go, les
toolchains applicatives traitées comme entrées et le code détenu par le
projet. Voir Next-Generation-Architecture#the-three-way-boundary :
bibliothèque standard épinglée une fois via
go.modetGOTOOLCHAIN=localsur chaque étape CI invoquantgo, toolchains applicatives toujours des entrées OCI épinglées par digest (jamais réimplémentées), code du projet natif sans appel à des CLI externes (gardeos/execen CI). Les trois sont maintenant vérifiés mécaniquement :scripts/ci/verify-workflows.pyéchoue si unrun:invoque le toolchain Go sansGOTOOLCHAIN=localdans sonenv(ou celui du job) - a détecté et corrigé 5 étapes réellement non épinglées (ci-benchmark.yml,ci-dast.yml,ci-microvm.yml,ci-release.yml) avant l'ajout du contrôle.
Le mode MicroVM cible ne reposera sur aucun backend Firecracker, Cloud
Hypervisor, libkrun ou Kata. platform-factory possédera son VMM :
- Linux : API KVM native ;
- macOS : API de virtualisation native de macOS ;
- Windows : API de virtualisation native de Windows.
Les API macOS et Windows nécessiteront des adaptateurs système spécifiques, mais aucun moteur de VM externe. QEMU/KVM reste uniquement une implémentation historique de test jusqu'à son remplacement ; elle ne constitue pas le backend de la nouvelle architecture.
- CLI unifiée
platform-factory. - Logs structurés avec identifiant de trace sur le constructeur OCI.
- Tests Go, race detector et seuil de couverture en CI. Le seuil de
couverture (86 %) est réellement appliqué, pas seulement déclaré : plusieurs
pushs consécutifs sont tombés sous le seuil (82,2-82,4 %) et
ci-qualitya échoué en conséquence. Corrigé par des tests négatifs réels ajoutés à travers plusieurs paquets (internal/rootfs,internal/cache,internal/hypervisor,internal/runtime,cmd/platform-factory-control-plane,cmd/platform-factory,internal/oci) plutôt qu'en abaissant le seuil -ci-qualityest repassé au vert de façon reproductible sur les commits suivants. - Actions GitHub épinglées par SHA et politique de licences.
-
cmd/platform-factory-installerséparé en module Go distinct. Fait le 4 août 2026 (commitd714c01). Extraction purement mécanique - n'importait déjà aucuninternal//api/du dépôt racine - vers son proprego.mod/go.sum, ajouté àgo.work. Le module racine n'a plus les dépendances bubbletea/huh/lipgloss aprèsgo mod tidy; invocations existantes (go run ./cmd/platform-factory-installer,scripts/local/bootstrap.sh) inchangées depuis la racine via la résolution de workspace. -
cmd/platform-factory-installerinstalleplatform-factory-runtime. Corrigé le 5 août 2026, à la suite des périphériques virtio et de l'annotationplatform-factory.dev/network-tapci-dessus (« Linux — KVM natif ») : le composantmicrovmne construisait quemicrovm-init/microvm-initramfs, jamaisplatform-factory-runtimelui-même - le binaire que Podman/Docker/containerd invoquent réellement via--runtime platform-factory-runtime. Un utilisateur choisissant « Support MicroVM » se retrouvait donc sans le seul binaire qui branche concrètement ce support sur un moteur de conteneurs.platform-factory-runtimeajouté aux binaires du composantmicrovm(cmd/platform-factory-installer/components.go) ;TestBuildStepsFlattensBinariesForEachSelectedComponentmis à jour en conséquence. Vérifié pour de vrai, pas seulement par le test unitaire :go run ./cmd/platform-factory-installer -components microvm -prefix <tmp> -yesconstruit et installe les quatre binaires (platform-factory,microvm-init,microvm-initramfs,platform-factory-runtime), le dernier exécutable et répondant normalement à ses propres flags. -
go build ./...compile sur toutes les plates-formes cibles, pas seulement Linux. Corrigé le 5 août 2026 (commit245c6af), trouvé en essayant d'installerplatform-factorydans./binsur macOS.cmd/ platform-factory/microvm_native.go(le backend KVM natif ajouté par les périphériques virtio ci-dessus) référençaitkvm.OpenTAP/kvm.RunLinuxWithOptions/kvm.LinuxRunOptions/kvm.NetworkDeviceOptions- symboles réservés à
linux && amd64par leur propre étiquette de build - depuis un fichier sans étiquette du tout, cassantgo build ./...sur toute cible non linux/amd64 (confirmé aussi en croisant verswindows/amd64: même échec).nativeKVMEligiblefait bien un testruntime.GOOS/runtime.GOARCH, mais Go type-vérifie chaque fichier d'un paquet qu'il soit atteignable à l'exécution ou non - un test runtime ne protège pas une étiquette de build manquante.ci-quality.ymlne pouvait pas le détecter : il ne construit que surubuntu-24.04. Corrigé en extrayantrunNativeKVMet ses aides (findRepoRoot,readEntrypoint,readVerifiedBlob) vers un nouveau fichier//go:build linux && amd64, avec un stub//go:build !(linux && amd64)pour querunNativeKVMSubcommand(qui l'appelle sans condition) reste portable.nativeKVMEligiblelui-même non modifié. Vérifié :go build/go vetpropres pour darwin/arm64 natif, linux/amd64 et windows/amd64 croisés ; 171/175 tests decmd/platform-factorypassent sur cet hôte macOS - les 4 restants (TestNativeKVMEligibleet trois tests de dispatch qui en dépendent) sont un écart préexistant, distinct et non touché :nativeKVMEligiblerefuse tout hôte non linux/amd64 sans condition, alors quemain_test.goattend que le dispatch natif fonctionne aussi via HVF réel sur macOS - un écart resté invisible tant que le paquet ne compilait pas du tout sur cette plate-forme. Corrigé en même temps, trouvaille annexe :internal/hypervisor/sandbox.ProbeSandbox()côté non-Linux ne renseignait qu'une clé générique"platform"dansDetails, alors queplatform-factory doctor(voir plus bas) etsandbox_linux.golisent chacun une clé par capacité ("namespaces","cgroups","capability-bounding-drop") - corrigé pour renseigner les trois (commitb8b8727), capturé par le propre test dedoctor.
- symboles réservés à
- Dépôt rendu public. Fait le 4 août 2026, décision explicite de
l'utilisateur, ce qui a débloqué la protection de branche GitHub
(auparavant 403 « Upgrade to GitHub Pro or make this repository
public » - le plan Free ne propose ni protection de branche ni
rulesets sur un dépôt privé). Protection non activée pour l'instant,
volontairement, pour ne pas casser le flux de push direct sur
main. - Matrice de compatibilité publiée. Fait le 4 août 2026 (commit
5f953fb).docs/reference/compatibility.md: OS hôte, OS/arch cible, runtime conteneur, Kubernetes, shim containerd, backend hyperviseur (KVM/HVF testés sur matériel réel en CI, KVM/arm64 et WHPX explicitement marqués non testés), noyau invité, interopérabilité Cosign/GHCR - chaque ligne citant le workflow CI exact qui la prouve. - Matrice de maturité publiée (stable/beta/alpha/stub/hors périmètre
par capacité). Fait le 4 août 2026 (commit
63db24d).docs/reference/maturity.md. - Fuzzing longue durée sur les ~20 cibles visées. Progrès le
4 août 2026 (commit
901c6b8) : quatre nouvelles cibles réelles (internal/layout.FuzzVerifyIndex,internal/policy.FuzzEvaluate,internal/attestation.FuzzVerify,internal/plugin.FuzzManifestValidate), chacune exécutée pour de vrai (millions d'exécutions, zéro crash) et câblée dansci-fuzz.yml. Suite le 4 août 2026 : sept de plus -internal/layout. FuzzDecodeManifest/FuzzDecodeConfig,internal/pipeline.FuzzDecode,internal/sbom.FuzzDocumentDecode,internal/project.FuzzLoad,internal/registry.FuzzGetManifestResponse/FuzzGetBlobResponse- même rigueur (exécution réelle, câblées en CI). Treize cibles au total avec les deux préexistantes. Suite le 4 août 2026 : trois de plus -internal/layout.FuzzVerifyLayer(décompression gzip et sécurité des chemins tar, en boîte blanche pour la même raison queFuzzDecodeManifest- atteindre ceci viaVerifyexigerait de forger un blob sha256 par itération),sdk/plugin.FuzzReadMessage(le framing RPC bas niveauContent-Type/Content-Lengthque les cinq langages de plugin parlent sur stdin/stdout),sdk/microvm.FuzzParseForward(l'analyseur--publish/-p/--portdeplatform-factory run --isolation microvm). Seize cibles au total. Suite le 4 août 2026 : deux de plus, en cherchant mieux plutôt qu'en acceptant l'affirmation précédente comme définitive -internal/provenance.FuzzFromJournal(le vrai point d'entrée de décodage que la note précédente disait absent :FromJournallit un journal de résultat de pipeline arbitraire depuisio.Reader, avec une fonctionsecretFieldqui recourt sans limite explicite de profondeur à traversmap[string]any/[]anyimbriqués - corpus de départ incluant une structure large et profonde construite exprès pour cette récursion) etcmd/platform-factory-control-plane.FuzzDecodeRequest(le décodeur JSON strict que chaque handler HTTP du control plane utilise -register/heartbeat/lease submit/complete/cancel- contre les quatre formes de requête). Dix-huit cibles au total contre les ~20 visées ; restent : commandes (déjà couvertes indirectement viainternal/pipeline.FuzzDecode, qui décode le document de pipeline complet y compris lesCommandimbriqués - pas de cible séparée jugée nécessaire), framing bas niveau des protocoles de contrôle au-delà du JSON (mTLS/HTTP lui-même, hors périmètre d'un fuzzer Go de haut niveau). Fermé le 5 août 2026 (commit2772756) : dix-huit cibles est jugé suffisant en largeur (le tiers restant listé ci-dessus est hors périmètre ou déjà couvert indirectement) ; ce qui manquait vraiment pour « longue durée » était la durée elle-même - les 45 s par cible ne prouvent que l'absence de crash immédiat.ci-fuzz.ymldistingue maintenant push/pull_request (45 s par cible, rapide, ne ralentit pas une PR) du déclencheurschedulehebdomadaire existant (10 min par cible, ~3 h au total pour les dix-huit), via une variable d'envFUZZTIMEcalculée depuisgithub.event_name- le même corpus, la même liste de cibles, une durée réelle plutôt que symbolique une fois par semaine. - API publique stable
v1. Le packageapi/v1expose le contrat wireplatform-factory.dev/v1, promu sans conversion depuisv1beta1. Le décodeur strict accepte encorev1alpha1etv1beta1; une fixture JSON v1 gelée traverse la validation, l'ordre DAG et la suite de conformance publique. - compatibilité ascendante automatisée des schémas et protocoles ;
voir Next-Generation-Architecture#automated-backward-compatibility-current-state.
Nouvelles suites à corpus figé (
testdata/compat/) pour le pipeline (internal/pipeline/compat_test.go,api/v1alpha1etapi/v1beta1) et pour le protocole RPC des plugins (api/plugin/compat_test.go) : décodent de vrais fichiers JSON (pas des structs Go construites en mémoire) et comparent champ par champ, pour détecter un renommage de tag JSON qu'encoding/jsonignorerait silencieusement. Vérifié en cassant délibérément un tag JSON du pipeline (depends_on→dependsOn) : le test échoue immédiatement sur les fixtures concernées, comme attendu. - modèle d'erreurs typées commun à tous les composants ;
internal/errors(TypedError, codes catégorisés, sérialisation JSON) a un consommateur committé (internal/policy.Evaluate, commitaf27cda,TestEvaluateRejectsUnsupportedAPIVersionAsTypedError). Un deuxième (internal/scheduler) était en cours d'adoption, non commité, dans le working tree partagé au moment de la rédaction du 4 août 2026 - à revérifier plutôt qu'à supposer terminé.internal/control,platform-factory-worker,internal/executorrestent sur les erreurs de la bibliothèque standard. Suivi le 5 août 2026 : l'adoptioninternal/scheduleren cours a été relue puis commitée (commit60b6d5a, session parallèle) -Scheduler.Runrenvoie maintenantCodeInvalidArgument/CodePipelineValidation/CodePipelineBudgetselon le cas, testé parTestSchedulerRejectsInvalidConfigurationBeforeCallingRunneret l'assertion de code ajoutée àTestSchedulerBudgetCancelsInFlightStages.internal/control(commit2772756) etcmd/platform-factory-worker(commitceea18f) migrés dans la foulée - les 28 sites d'erreur deinternal/controlportent maintenantCodeNotFound/CodeConflict/CodeInvalidArgumentselon le cas, en préservant explicitement (prouvé par un test jetable avant la vraie migration, puis parTestErrorsAreTypedAndPreserveSentinelCompatibility) la compatibilitéerrors.Is(err, control.ErrUnknownWorker)dontcmd/platform-factory-control-plane/server.godépend déjà. Reste ouvert :internal/executorreste sur les erreurs de la bibliothèque standard - non traité par prudence, ce paquet est directement adjacent au travail actif de la session parallèle surinternal/scheduler/internal/pipeline. - métriques, traces et journaux structurés de bout en bout ;
internal/observability(logs structurés, métriques, traçage distribué) a un seul consommateur committé :platform-factory-control-plane(commit7eecb82, démarrage/restauration d'état/journal d'audit en logs structurés, prouvé parTestPersistentReapLoopLogsStateSaveFailureViaObservability). Un deuxième (intégration observability/budget) était en cours, non commité, au 4 août 2026 - à revérifier. Trouvaille, même jour, en lançantgo test ./... -racepour un travail sans rapport : cette intégration en cours introduit une vraie data race, pas un flake - écritures concurrentes non protégées sur la map interne dedefaultTracer.StartSpanWithContext(internal/observability/ observability.go:563), atteinte depuis plusieurs goroutines deinternal/scheduler.Scheduler.runStage(scheduler.go:307) quand des étages tournent en parallèle. Fait échouerTestSchedulerRunsSuccessInDeterministicOrderetTestRunPipelineTextRunAndMissingArgssous-race. Non corrigé délibérément : fichiers en cours d'édition non commitée au moment de la trouvaille, pas à modifier depuis une session parallèle. Les métriques et le traçage distribué du paquet restent inutilisés partout ;platform-factory-workeret le builder OCI n'importent toujours pas ce paquet. Pas encore « bout en bout ». Corrigé et étendu le 5 août 2026 (commit60b6d5a) : la data race trouvée ci-dessus est réparée -StartSpanWithContexttravaillait sur la mapbaggageextraite directement du contexte parent (partagée entre toutes les goroutines dérivées du même contexte) au lieu d'en faire une copie privée avant d'y écriretrace_id.internal/scheduleretcmd/platform-factory/pipeline.go(propagation d'unSECURE_OCI_TRACE_IDoptionnel) sont maintenant de vrais consommateurs, vérifiés sousgo test -race ./... -count=3sans échec.cmd/platform-factory-worker(commitceea18f) ajouté ensuite - chaque bail exécuté ouvre un spanworker.leasetaggélease_id/required_platform/attempt, prouvé vivant (pas seulement compilé) par les lignes de log"span errored"réelles que produisentTestClientPollOnceReportsExecutionFailureWithoutCompletingetTestPollOnceStopsExecutionWhenCancellationStatusFails. Reste ouvert : le builder OCI n'importe toujours pas ce paquet. - budgets de ressources et annulation propagés sur tout le pipeline ;
internal/pipeline.Scheduler.Budgetborne le temps d'horloge murale de l'exécution entière, indépendamment de tout délai sur le contexte de l'appelant : au déclenchement, chaque étage en vol est annulé et rapportébudget_exceeded(pascanceled), etRunrenvoie une*ScheduleBudgetExceededErrordistincte - testé positivement (TestSchedulerBudgetExceededprouve que le budget coupe une étape qui bloquerait sinon) et négativement (une annulation de l'appelant avec un budget généreux reste attribuée à l'appelant). Câblé dans le produit viaplatform-factory pipeline run --budget DURATION. Chaque étage a déjà ses propres limites CPU/mémoire viainternal/executor; l'agrégation au niveau pipeline reste un travail de suivi. - Nouvelle ligne, ajoutée le 3 août 2026. Agrégation de budget
CPU/mémoire au niveau pipeline (suivi de la case précédente). Le package
internal/budget(commitd8e8ecf) implémente le suivi et l'application de budgets de ressources, avec des tests dansinternal/budget/budget_test.go. Important, trouvé le 3 août 2026 :Trackermesure les ressources du processus appelant lui-même (syscall.Getrusage(RUSAGE_SELF, ...),runtime.ReadMemStats), pas celles d'un enfant. Le brancher dansinternal/executortel quel aurait mesuré le mauvais processus : chaque étage de pipeline s'exécute dans un processus sandboxé séparé, déjà borné par ses propres cgroups/rlimits (voir plus haut, « État transversal »), invisibles à ce package depuis le processus parent. Progrès le 3 août 2026 : câblé à la place dansinternal/oci.Build(commitfbf167b), qui fait tout son travail dans le même processus - cible correcte pour cette primitive.Options.Budget(valeur zéro = comportement identique à avant) borne l'appel de build entier, vérifié une fois par fichier streamé ; testé parTestBuildFailsClosedWhenWallClockBudgetIsExceeded. Reste ouvert : aucun budget par étage de pipeline n'existe encore ;internal/executoraurait besoin d'un mécanisme différent (propagation vers l'intérieur du sandbox, ou lecture du cgroup de l'enfant) pour agréger correctement les ressources par étage. Blocage structurel résolu le 5 août 2026 (commit21ee69c) :budget.FromProcessStatelit l'usage CPU (user+system) et le pic de RSS réels d'un enfant déjà terminé directement depuis son*os.ProcessState(rempli par(*exec.Cmd).Wait()via la comptabilité noyauRUSAGE_CHILDREN- aucun cgroup, aucun privilège supplémentaire requis). Gère la différence d'unité Linux/Darwin surru_maxrss(kilo-octets contre octets, par plate-forme, pas devinée) ; explicitement indisponible sur Windows plutôt que silencieusement fausse.Manager.AddStageUsagealimenteTotalCPU/PeakMemoryaux côtés desTrackeractifs déjà existants. Prouvé avec un vrai processus enfant généré (boucle d'attente soussh -c/cmd /C), pas un mock :TestFromProcessStateMeasuresARealChildvérifie un CPU et un RSS réellement non nuls et plausibles. Cross-compilé propre pour linux/amd64 et windows/amd64. Reste ouvert, par prudence plutôt que par contrainte technique : le câblage dansinternal/executor.Executor.Runlui-même (juste après soncmd.Wait()déjà existant àinternal/executor/executor.go:158, un point d'accroche direct) n'a pas été tenté - ce paquet est directement adjacent au travail actif de la session parallèle surinternal/scheduler/internal/pipeline. La primitive est réelle et testée ; il ne manque que ce dernier appel. - suite de conformance publique pour l'API pipeline et les plugins.
- suite de conformance publique pour les backends d'exécution.
conformance.RunBackend(vecteurs embarqués sousvectors-backend/, câblés dansplatform-factory-conformance backend, exécutable hors du dépôt comme les suites pipeline/plugin) épingle le comportement observable d'uninternal/executor.Executorlocal sur des cas réels : code de sortie, capture de stdout, rejet d'une politique réseau non honorée (exit_code: -1, convention déjà établie parExecutor.reject), et environnement non hérité de l'hôte ($HOMEabsent). Intégré àci-quality. Limite documentée : seul le backend local (non sandboxé) est vectorisé ici, pas le backend sandboxé (ses garanties supplémentaires - namespace réseau, secrets en mémoire - restent couvertes par les tests dédiés d'internal/executor, pas par cette suite, car la disponibilité des namespaces/cgroups varie selon l'hôte).
- Découvrir
.config_image.yaml,.config_image.yml,.config_img.yaml,.config_img.ymlet leurs variantes JSON. - Charger strictement la configuration et refuser les champs inconnus.
- Résoudre les chemins relativement à la racine du projet.
- Supporter les profils Go, Node.js, Python, Java, .NET, Rust, Ruby et PHP.
- Fournir les commandes personnalisées pour les langages non intégrés.
- Ajouter automatiquement
shared_deps,Shared_depsetshared-deps. - Produire un inventaire de gel SHA-256 stable.
- Exposer
project show,freeze,project buildetproject run. - Unifier ces opérations dans
platform-factory launch. - Expliquer le plan résolu avant toute mutation.
- Ajouter
--yes,--dry-runet un format de sortie machine homogènes. - Détecter et résoudre les ambiguïtés entre plusieurs écosystèmes.
- Définir un schéma versionné et une migration de configuration.
- Construire un layout OCI déterministe en streaming et à mémoire bornée.
- Normaliser les timestamps, UID/GID, permissions et ordre des fichiers.
- Vérifier les digests, tailles,
diff_id, chemins de layers et plateformes. - Produire des images Linux
amd64etarm64. - Composer un index multi-architecture.
- Conserver plusieurs tags, images et manifests dans un layout.
- Importer silencieusement un layout local avant une exécution Docker ou Podman.
- Accepter plusieurs publications de ports via
-pou--port. - Produire nativement plusieurs stages et couches sémantiques.
- Séparer toolchain, dépendances, application et métadonnées.
- Garantir la stabilité byte-for-byte pour chaque mode de compression.
- Ajouter une commande de comparaison expliquant toute divergence.
-
platform-factory launchconstruit et exécute un projet pris en charge sans édition manuelle. - Deux builds propres du même projet produisent le même digest.
- La matrice Docker/Podman et
amd64/arm64est verte. - Les limites v1 et les formats expérimentaux sont explicitement signalés.
- Définir une première API déclarative
api/v1alpha1. - Représenter stages, entrées, sorties, commandes, caches, secrets, réseau et ressources sans type propre à un langage.
- Geler la première sérialisation JSON canonique de
v1alpha1, borner le document à 1 MiB et refuser champs inconnus ou documents multiples. - Ajouter validation de version et négociation de capacités.
- Publier des exemples et une suite de conformance.
- Promouvoir l'API en
v1beta1avec compatibilité de wire format testée.
- Valider les identifiants, dépendances, références d'artefacts et chemins.
- Détecter les cycles et calculer des niveaux topologiques déterministes.
- Planifier les stages indépendants avec un parallélisme borné.
- Propager annulation et échecs, bloquer les descendants et laisser continuer les branches indépendantes.
- Capturer stdout, stderr, code de sortie et métriques par stage.
- Isoler chaque stage sans dépendre de BuildKit.
- Appliquer CPU, mémoire, PIDs, filesystem et la politique réseau
noneà l'exécution sandboxée. - Appliquer explicitement la politique réseau
fullsans supprimer les autres namespaces d'isolation. - Appliquer la politique réseau
resolveavec un forwarder DNS détenu par le projet. - Monter les secrets en mémoire sans snapshot ni journalisation.
- Transférer les artefacts entre stages en vérifiant leurs digests.
- Définir le manifeste de plugin signé et épinglé par digest.
- Définir un protocole RPC versionné sur stdin/stdout.
- Implémenter
detect,freezeetplan(le verbescanest réservé pour le chemin de publication v3). - Négocier versions, capacités et limites de messages.
- Isoler fichiers, environnement, réseau, ressources et secrets.
Environnement : déjà acquis, l'environnement du processus plugin est
entièrement remplacé, jamais hérité de l'hôte. Réseau :
internal/plugin.Startrelance désormais le binaire appelant dans un namespace utilisateur, réseau, IPC et UTS frais (sandbox_linux.go) avant d'exécuter le plugin réel ; aucune interface, pas même loopback, n'y est configurée. Fichiers :applyFilesystemIsolationdanssandbox_linux.gorend le projet root read-only via remount et masquent les répertoires système sensibles via tmpfs. Ressources :applyResourceLimitsapplique RLIMIT_CORE=0, RLIMIT_FSIZE=16MiB, RLIMIT_NOFILE=256, RLIMIT_CPU=60s. Secrets : sans objet, aucune capability du protocole ne transmet de valeur secrète au plugin. Là où la sandbox est indisponible,Startretombe sur un lancement non sandboxé. Une limite mémoire (RLIMIT_AS) a été tentée puis abandonnée : confirmé empiriquement qu'elle fait échouer le démarrage de tout runtime Go (le SDK plugin est Go) bien avant tout usage réel du tas,256Mio et512Mio étant tous deux insuffisants pour qu'un binaire Go trivial démarre («failed to reserve page summary memory»). Une limite par langage ou déclarée dans le manifeste reste un travail de suivi. - Fournir un SDK Go sans exposer
internal/. Les surfaces canoniques sont désormaissdk/pipeline,sdk/microvm(contrats MicroVM et VMM) etsdk/plugin. Les anciens cheminsapi/pipeline,api/microvm,api/vmmetapi/pluginsont des shims dépréciés avec alias de types exacts et tests de compatibilité. - Fournir un runtime plugin Go de référence et un protocole stable
indépendant du langage. Un auteur Go implémente uniquement
sdk/plugin.LanguageExtension(Detect,Freeze,Plan) ; Python, JavaScript/TypeScript et C# implémentent les mêmes schémas JSON et le même framing v1 sans importer platform-factory. Des exemples utilisateur existent sousexamples/plugin, et Go/Python/JavaScript passent réellement les cinq mêmes contrôles deplatform-factory-conformance plugin. Le projet C# est fourni etci-qualityest configurée pour le compiler avec .NET puis lui appliquer cette même suite ; la preuve locale reste conditionnée à la présence du SDK .NET. Progrès le 3 août 2026 (commita69c08f), vérifié le 4 août 2026 : jusqu'ici Python/JavaScript/TypeScript/C# réimplémentaient le framing JSON-RPC à la main dans chaque exemple - il n'existait aucune bibliothèque importable pour ces langages, contrairement àsdk/pluginpour Go. Trois nouveaux paquets locaux réels, tous miroirs de l'APIServerdesdk/plugin(enregistrer des capacités puis servir) et du même protocole de câble (mêmeContent-Type, même handshakev1.hello, même dispatchv1.<capability>) :sdk/plugin-python(secure_oci_plugin, stdlib seule),sdk/plugin-js(@platform-factory/plugin-sdk, CommonJS +index.d.ts, zéro dépendance runtime),sdk/plugin-csharp(SecureOci.Plugin, une vraie bibliothèque de classes - avant cela, seul un projet Exe existait). Aucun n'est publié sur PyPI/npm/NuGet ; ce sont des paquets localement importables (chemin pip local,requirerelatif,ProjectReference.NET), structurés pour ne pas nécessiter de refonte lors d'une publication future. Les exemples ont déménagé sousexamples/sdk/plugin-{python,javascript,typescript,csharp}et importent désormais leur SDK au lieu de réimplémenter le framing ; l'exemple TypeScript, qui n'avait auparavant aucune configuration de build et n'était jamais réellement compilé ni exécuté, a maintenant un vraitsconfig.json, compile proprement et tourne seul contre les typesindex.d.tsfournis parsdk/plugin-js. Re-vérifié le 4 août 2026, pas seulement relu :bash examples/sdk/run.shfait réellement passer Go, Python et JavaScript à 5/5 surplatform-factory-conformance plugin, etdotnet build sdk/plugin-csharp/SecureOci.Plugin.csproj -c Releasecompile sans avertissement. TypeScript et C# restent vérifiés seulement via CI dans ce passage (pas detsc/environnement C# complet exercé localement ici). - Convertir les adaptateurs Go, Node.js, Python, Java, .NET, Rust, Ruby et PHP en plugin officiel (les adaptateurs intégrés restent des fallbacks).
- Tester un plugin tiers sans modification du cœur.
- Refuser par défaut tout plugin non vérifié.
- Un plugin externe ajoute un langage sans recompiler
platform-factory. - Deux branches indépendantes du DAG s'exécutent réellement en parallèle.
- Les secrets sont absents des layouts, caches et logs couverts par la suite de conformance.
- Prouver leur absence des attestations générées depuis le journal et refuser les champs susceptibles de contenir une valeur secrète.
- La suite de conformance plugins est exécutable hors du dépôt. Elle vérifie désormais aussi les trois capacités requises et la forme sûre de leurs résultats, au-delà du seul transport RPC.
- Définir l'interface stable du content store.
- Écrire les blobs en streaming dans un fichier temporaire puis les installer atomiquement.
- Dédupliquer les blobs par digest.
- Dédupliquer les chunks par digest.
- Ajouter le chunking en streaming borné en mémoire pour les fichiers de plusieurs téraoctets.
- Reprendre un transfert interrompu à l'offset confirmé sans rescanner les chunks validés.
- Introduire records de cache, leases et garbage collector.
- Reconstruire l'index exclusivement depuis les contenus vérifiés.
- Exclure la valeur des secrets des clés de cache.
- Marquer automatiquement tout stage recevant un secret comme sensible et interdire la persistance de ses sorties et records dans le cache.
- Tester concurrence, corruption et erreurs d'entrées/sorties.
- Tester explicitement les restes de crash et
ENOSPC, avec nettoyage des écritures partielles.
- Dériver automatiquement l'épinglage des sources, bases, plugins et
toolchains depuis le pipeline et les exécutables réellement vérifiés via
platform-factory evidence. - Normaliser locale, fuseau, environnement et paramètres de compression.
- Interdire le réseau après la phase de résolution : tout stage réseau doit
être une racine du DAG et tous ses descendants sont
network:none. - Distinguer identité d'un secret et contenu secret dans les clés de cache.
- Exécuter deux rebuilds propres et comparer tous les descripteurs.
- Produire un rapport structuré des causes de non-reproductibilité.
- Tester la reproductibilité sur Linux, macOS et Windows dans la matrice CI.
- Implémenter nativement le sous-ensemble de l'API OCI Distribution requis pour publier layouts, blobs et manifests.
- Authentifier en Basic/Bearer et reprendre un upload depuis le
Rangeconfirmé par le Registry. - Ajouter le montage cross-repository des blobs avec fallback automatique vers l'upload repris.
- Publier atomiquement par digest avant de déplacer les tags.
- Générer nativement un inventaire SBOM des fichiers et dépendances ELF.
- Corréler lockfiles Go, Node.js, Python, Rust, Ruby, PHP et .NET ainsi que les métadonnées dpkg/apk dans le SBOM.
- Générer la provenance SLSA depuis le journal réel du pipeline.
- Fournir une bibliothèque native de génération de provenance structurée.
- Produire et vérifier les enveloppes DSSE d'attestation nativement.
- Signer avec Ed25519/ECDSA et vérifier signatures et chaînes X.509.
- Générer automatiquement la provenance native depuis
--journallors de la publication (un prédicat fourni reste pris en charge). - Relier SBOM et signature natives, ainsi qu'un prédicat de provenance fourni, au parcours de publication.
- Publier signatures et attestations comme artefacts OCI liés au digest.
- Interdire par défaut la publication si politique, SBOM, provenance ou signature échoue ; l'opt-out développement est explicite et auditable.
- Fournir une commande de vérification locale complète pour un
bundle de release déjà publié. Fait le 4 août 2026 (commit
0b319f1).platform-factory verify-release LAYOUTréévalue localement validité du layout, signature DSSE contre une clé épinglée par l'opérateur, provenance, SBOM et décision de politique - onze tests réels, zéro mock. Volontairement local uniquement : voir la ligne suivante. - Ajouter une capacité de lecture (pull/fetch) au client Registry
natif. Trouvé le 4 août 2026, en écrivant
verify-release:internal/registry.Clientn'avait que des méthodes d'écriture (PushLayout,PushLayoutByDigest,PushArtifact,TagLayout) ; aucune méthode ne lisait un manifest ou un blob depuis un registre.verify-releasea donc dû être conçu pour opérer uniquement sur des artefacts déjà locaux plutôt que d'aller chercher une image publiée. Fait le 4 août 2026 :GetManifest(par tag ou par digest, headerAcceptde négociation de contenu correct pour index/manifest/ manifest-artefact OCI et les deux media-types Docker legacy) etGetBlob(borné à 64 MiB - pensé pour des artefacts signature/SBOM/ provenance de petite taille à vérifier, pas un puller de layers multi-gigaoctets). Les deux revérifient toujours le SHA-256 réel du contenu reçu plutôt que de faire confiance au statut HTTP ou au Content-Length du serveur. Sept tests unitaires (transport mocké, même style que le reste declient_test.go) plus une vérification manuelle contre un vrai registre OCI local (docker run registry:2, un vraiplatform-factory publishdessus, puisGetManifest/GetBlobréels par tag et par digest, cas négatif digest-inexistant) - pas seulement les tests mockés. Reste ouvert : rien ne consomme encore ces méthodes (pas deplatform-factory pull,verify-releasereste local-only par choix).
- Définir un langage de règles interne minimal et versionné.
- Exiger par politique base, toolchain et plugins épinglés.
- Vérifier par politique non-root, capabilities, rootfs read-only et absence de secrets.
- Évaluer SBOM, provenance, signatures et preuve de reproductibilité.
- Produire une décision explicable avec raisons déterministes.
- Relier la décision et les preuves associées au parcours de publication ; le tag n'est jamais déplacé après un refus.
-
launch --publishfonctionne sans Skopeo, Cosign ni générateur SBOM externe. - Une image altérée, non attestée ou non reproductible est refusée avant mise à jour du tag.
- Une reprise après crash publie exactement les blobs attendus.
- Un corpus logique de 2 TiB démontre mémoire bornée et déduplication.
- Définir
VMM,Machine,Device,GuestAgentet le state store. - Définir un format de boot bundle adressé par contenu.
- Convertir un rootfs OCI en disque ou partage vérifié.
internal/rootfs.Convertsélectionne un manifest Linux, vérifie manifest/config/digests/diffIDs, applique whiteouts, symlinks et hardlinks OCI (bornés et validés contre toute évasion hors racine) dans une racineos.Root, borne octets/fichiers et installe atomiquement. Ce rootfs converti est maintenant réellement relié à une VM :cmd/microvm-initramfs(internal/rootfs.InstallInit+internal/rootfs.WriteInitramfs, un écrivain cpio/newc natif déterministe) assemble un initramfs gzip sanstarnicpioexternes, etci-microvml'utilise pour démarrer réellement le noyau natif (voir « Linux — KVM natif » ci-dessous). Limite documentée : la structure des hardlinks OCI n'est pas préservée dans l'initramfs empaqueté (chaque chemin lié devient un fichier indépendant à contenu dupliqué) - seul le contenu par chemin compte pour un noyau qui démarre depuis cet initramfs. Le backend CLIplatform-factory microvm run/start(production, QEMU) n'utilise pas encore ce chemin - voir la case ouverte correspondante sous « Linux — KVM natif ». - Fournir un init et un agent invité minimaux détenus par le projet.
cmd/microvm-initest un PID 1 minimal complet et testé (relais de signaux, moisson des enfants, configuration d'entrypoint, console via devtmpfs) - voirTestRunForwardsSignalsToChild,TestRealMainRunsChildAndCallsPoweroff,TestReapExitedChildrenDrainsEveryExitedChild. L'agent invité minimal est maintenant détenu par le projet danscmd/microvm-init: endpoint authentifié, clé boot fail-closed, exécution, signaux, état, logs et shutdown bornés. L'adaptateur hôte implémenteapi.GuestAgent; l'activation automatique du périphérique reste suivie séparément par la case transport. - Transporter commandes, signaux, état et logs sur un canal authentifié.
Le protocole hôte/invité est maintenant implémenté dans
internal/guesttransport: framing borné à 1 Mio, HMAC-SHA256 avec clé de session d'au moins 32 octets, version wire et séquences strictement croissantes anti-rejeu pourexec,signal,shutdown,stateetlogs. L'adaptateur hôte implémente désormais l'interface publiqueGuestAgent(Exec,Signal,Shutdown, état et logs), applique deadlines et limites d'entrée, et le backend macOS accepte unGuestAgentConnectorinjectable. Sans périphérique configuré,Agent()échoue explicitement. Côté KVM Linux, un premier périphérique natif existe maintenant :LinuxRunOptions.GuestChannelraccorde un UART 8250 secondaire COM2 (0x2f8, IRQ3,/dev/ttyS1) bidirectionnel et borné au run loop, sans bloquerKVM_RUN; COM1 reste réservé aux diagnostics. L'endpointmicrovm-initauthentifié est également livré. Le parcours OCI Linux est maintenant raccordé de bout en bout : clé aléatoire par boot injectée uniquement dans l'initramfs en0600, métadonnées non secrètes strictes, ttyS1 configuré en raw 115200 8N1, client/serveur HMAC et effacement des buffers secrets au teardown.internal/directboot.RunWithGuestAgentrelie aussi ce canal au boot bundle direct non OCI avec callbackOnReady, sans modifier le chemin sans agent. Côté macOS, Virtualization.framework expose désormais un second port console virtio bidirectionnel (/dev/hvc1,/dev/hvc0restant la console), créé depuis unsocketpairCLOEXEC ; le FD hôte est adopté par Go et raccordé au même protocole authentifié. Une clé résolue par machine et par boot est obligatoire, copiée puis effacée, le canal est mono-claim et tout manque de clé ou double claim échoue fermé. Les tests couvrent validation, ownership, nettoyage et compilation Darwin. Deux preuves sont distinguées :ci-microvmimpose le contrat macOS à chaque push ; les deux tests matériels sont tentés, mais ne constituent une preuve de boot que lorsque le runner expose réellement HVF. Si Virtualization.framework signale l'indisponibilité de la virtualisation imbriquée, seuls ces deux tests sont exclus et toute la suite de contrat reste obligatoire. Un job hébergé vert dans ce mode n'est donc pas une preuve de boot matériel. - Gérer vCPU, mémoire, horloge, entropy, console et arrêt propre.
Progrès réel :
internal/hypervisor/kvm/linux_serial.goémule maintenant le PIT-gate (port0x61) et l'horloge CMOS/RTC MC146818 (ports0x70/0x71), avec signalisation d'interruption THRE pour la console série (kvm_vm_linux_amd64.goajouteKVM_IRQ_LINEpour pulser l'IRQ4 réellement) - corrige un blocage de boot précoce puisque le pilote 8250 devient piloté par interruption aprèsearlycon.linux_i8042.goisole une émulation i8042 minimale (ports0x60/0x64), y compris une demande de reset côté invité (0xfe). Les lectures neutres, écritures ordinaires, reset au seul port de commande, accès non-byte et ports étrangers sont couverts parTestHandleLinuxI8042IO*; le chemin reste exercé en conditions réelles parTestRunLinuxWithRealKVMdansboot-under-kvm. Limite documentée : Le backend KVM fournit maintenant 64 octets issus decrypto/randvia le protocole Linuxsetup_data/SETUP_RNG_SEED: structure ABI little-endian, alignement et non-chevauchement sont testés, les noyaux antérieurs à 2.09 sont refusés sur ce chemin et une source courte échoue avant/dev/kvm. La graine temporaire côté hôte est effacée après copie. Limite : il ne s'agit pas encore d'un périphérique virtio-rng fournissant de l'entropie continue après le boot.internal/hypervisor/kvm/linux_boot.go/linux_boot_cpu.godérivent maintenant le point d'entréecode32_startdepuis l'en-tête bzImage réel au lieu d'une valeur figée. - Gérer réseau, DNS, ports TCP/UDP et volumes.
- Tester crash VMM, crash invité, timeout de boot et récupération d'état.
-
Ouvrir
/dev/kvmet négocier la version de l'API sans QEMU. -
Négocier et exposer les extensions KVM requises avant création d'une VM :
KVM_CAP_USER_MEMORYetKVM_CAP_IMMEDIATE_EXITsont contrôlées et publiées par le probe avantKVM_CREATE_VM. Le primitive réel a réussi sur/dev/kvmdansmicrovm-bootau commita6c95bf7911ffa70844703c2f88e0129506030e9. -
Créer VM, vCPU et mémoire invitée avec le primitive KVM natif
RunFlatPayload, exercé sur/dev/kvmparci-microvm. -
Charger kernel, initrd et ligne de commande.
LoadLinuxBootvalide le protocole bzImage x86, place payload/initrd/cmdline, construitboot_params/E820 etconfigureLinuxBootVCPUprépare GDT, segments, registres etCR0.PE, sans firmware ni outil externe. -
Implémenter les périphériques virtio minimaux nécessaires. Implémenté le 5 août 2026. Après le constat du 3 août ci-dessus (
internal/hypervisor/virtio: structures de configuration seules, jamais importées, aucune virtqueue), le transport et les deux périphériques réels ont été écrits directement dansinternal/hypervisor/kvm(linux_virtqueue.go,linux_virtio_mmio.go,linux_virtio_blk.go,linux_virtio_net.go,linux_virtio_options.go), sans dépendre de ce paquet-là : moteur de virtqueue scindée (VIRTIO 1.2 §2.7) directement sur la mémoire invitée plate déjà utilisée par le reste du VMM, transport virtio-mmio version 2 (§4.2, jamais la variante legacy) câblé surKVM_EXIT_MMIOavec une fenêtre d'une page par périphérique à une adresse0xd000_0000délibérément hors de toute RAM invitée configurée (chaque accès y déclenche donc réellement un exit MMIO plutôt que de retomber sur de la RAM), un périphériquevirtio-blk(IN/OUT/ FLUSH/GET_ID) et un périphériquevirtio-netadossé à un descripteur TAP déjà ouvert.Preuve réelle sur
/dev/kvm, pas seulement sur pilote maison :TestRunLinuxWithRealKVMVirtioBlkfait négocier un noyau natif non modifié jusqu'à ce que son pilotevirtio_blkreconnaisse le périphérique (vdadans le journal série), boote l'application OCI complète et relit les octets écrits au boot.TestRunLinuxWithRealKVMVirtioNetfait démarrer le noyau avecip=dhcpet vérifie qu'une vraie trame DHCPDISCOVER (UDP 68→67 décodé, pas ungrepsur dmesg) arrive côté TAP hôte. Cette deuxième preuve a d'abord échoué : la négociation virtio-mmio réussissait jusqu'àDRIVER_OK(confirmé par la traceSECURE_OCI_DEBUG_VIRTIO_MMIOajoutée à cette occasion) mais aucune trame utile n'arrivait. Cause réelle :virtio_net_hdrfait 12 octets (struct virtio_net_hdr_mrg_rxbuf), pas 10, dès queVIRTIO_F_VERSION_1est négocié - ce que ce VMM offre toujours -, quel que soit l'état deVIRTIO_NET_F_MRG_RXBUFlui-même. Les tests unitaires sur pilote maison ne l'avaient pas montré parce que ce pilote maison faisait la même hypothèse erronée ; seul un noyau réel, non modifié, l'a révélé. Corrigé (virtioNetHeaderSize = 12), les deux tests réels passent.Câblé dans le chemin de production :
internal/ociruntime/supervisor_linux.goexposeplatform-factory.dev/block-device-path/-readonly(un fichier hôte devient/dev/vda, capacité validée multiple de 512) etplatform-factory.dev/network-tap(ProbeTAPSupport/OpenTAP,internal/hypervisor/kvm/linux_tap_linux.go) et fusionne les périphériques obtenus dans lekvm.LinuxRunOptionspassé àRunLinuxWithOptions. Contrairement àsandbox-cgroups/sandbox-namespaces(best-effort, dégradation silencieuse), ces deux annotations échouent fermé : un périphérique explicitement demandé et indisponible est un manque fonctionnel que l'appelant doit voir, pas un durcissement optionnel. Non revérifié dans cette session au niveaupodman run --runtime platform-factory-runtimecomplet (annotation → périphérique visible dans l'invité) : la preuve s'arrête à la frontièrekvm.RunLinuxWithOptionselle-même, déjà réelle et complète contre/dev/kvm. -
Ajouter réseau TAP/rootless selon capacités de l'hôte. Implémenté le 5 août 2026, avec le point précédent :
OpenTAP/ProbeTAPSupport(internal/hypervisor/kvm/linux_tap_linux.go) ouvrent/dev/net/tunavecTUNSETIFF/IFF_TAP|IFF_NO_PI, etProbeTAPSupportrapporte la disponibilité en lisantCAP_NET_ADMINdansCapEff(/proc/self/status) plutôt que de deviner depuis l'euid - même philosophie queProbeSandboxdéjà utilisé pour cgroups/namespaces. Vérifié pour de vrai en root local : création réussie d'une interfacetap0. Limite réelle du « rootless », trouvée par unpodman unsharelocal et pas seulement documentée en théorie :CAP_NET_ADMINprésent dansCapEffne suffit pas à faire réussirTUNSETIFFsi le processus ne possède pas aussi son propre network namespace - sous un namespace utilisateur partagé avec le namespace réseau de l'hôte (le cas Podman rootless typique), l'appel échoue quand même enEPERM. Un TAP rootless avec accès réseau sortant réel demanderait en plus une couche NAT en espace utilisateur (le rôle que jouent slirp4netns/pasta pour la mise en réseau propre de Podman) : volontairement hors périmètre, non implémenté ici.platform-factory.dev/network-tapéchoue donc fermé, avec la raison deProbeTAPSupport, plutôt que de faire croire à une mise en réseau fonctionnelle qui ne l'est pas. -
Appliquer seccomp, namespaces, cgroups et abandon de privilèges au VMM. Implémenté le 4 août 2026 (commit
e19e6f3), après le report du 3 août ci-dessus. L'utilisateur a explicitement tranché entre écrire le code et laisser la CI Linux réelle le vérifier plutôt que continuer à reporter :applySeccomp(PR_SET_NO_NEW_PRIVS - même primitive et même nom queinternal/executor, pas de filtrage BPF réel),applyNamespaces(unshare self-appliqué de UTS/IPC ; PID exclu du self-apply carunshare(CLONE_NEWPID)n'affecte que les enfants créés après l'appel, jamais l'appelant),applyCgroups(création d'un cgroup v2 enfant délégué avec limites pids/cpu/memory et auto-jointure) etdropCapability(PR_CAPBSET_DROPpar capacité) sont maintenant réels, pas des stubs. Un nouveauProbeSandbox()détecte la disponibilité par construction (lecture du bitmaskCapEffde/proc/self/status, écriture réelle dans un cgroup enfant jetable) plutôt que de deviner depuis l'euid - même philosophie que le probe déjà existant d'internal/executor.Câblage dans
internal/ociruntime/supervisor_linux.go:LaunchSupervisorpositionneCLONE_NEWPID|CLONE_NEWIPC|CLONE_NEWUTSsur l'enfant__serve, mais seulement siProbeSandbox().Namespacesle confirme disponible - sansCAP_SYS_ADMIN,clone()échouerait etcommand.Start()casserait le lancement du VMM entièrement (l'appelant ici estplatform-factory-runtime create, invoqué par Podman/Docker/ containerd ; rien ne garantitCAP_SYS_ADMINdans ce contexte).ServeSupervisorapplique ensuiteno_new_privssans condition (ne nécessite aucun privilège) puis cgroups et abandon du bounding set de capacités seulement siProbeSandboxles confirme disponibles, pour qu'un hôte sans délégation cgroup lance quand même des invités plutôt que d'échouer fermé. L'abandon d'UID (setuid(65534)) dedropPrivilegesest délibérément exclu du chemin VMM (nouveauSandbox.DropBoundingCapabilities, découplé deSandbox.Apply) : ce processus a besoin d'un accès continu à un/dev/kvmdéjà ouvert, et cette session ne peut pas vérifier sans hôte Linux réel si l'accès DAC au device dépend de l'UID plutôt que de l'appartenance à un groupe.cmd/platform-factory-runtime's feature-JSON reflète maintenant cet état réel ("namespaces":["pid","ipc","uts"],"cgroup":{"v2":true},"seccomp":{"enabled":true}) au lieu de l'ancien"seccomp":{"enabled":false}, honnête à l'époque.Vérifié pour de vrai le 4 août 2026, sur Linux réel avec
/dev/kvm, dans la continuation de la session ci-dessus (macOS, sans KVM). Cette deuxième session tournait en parallèle sur un checkout Linux réel du même dépôt, a produit sa propre implémentation indépendante du même point de roadmap, et les deux ont fini par coexister dans le même arbre de travail (deux définitions deapplySeccomp/applyNamespaces/applyCgroups/dropCapability, l'une danssandbox_linux.go, l'autre dansseccomp_linux.go/namespace_linux.go/cgroup_linux.go/caps_linux.go) -go buildrefusait de compiler. Réconcilié en gardant la version la plus complète de chaque primitive (filtrage BPF réel plutôt queno_new_privsseul ; abandon de capacités en deux phases bounding-set/current-set correctement ordonnées autour desetuid) tout en conservant ce que l'implémentation macOS faisait mieux (probeProbeSandbox/Supportplutôt que deviner depuis l'euid ;CLONE_NEWIPC|CLONE_NEWUTSposé sur l'enfant__serveparLaunchSupervisorau lieu d'ununshareauto-appliqué risqué).Une fois la compilation réparée, faire tourner le chemin réel a trouvé plusieurs bugs réels que la revue seule n'avait pas vus :
-
dropPrivilegesretiraitCAP_SETUID/CAP_SETGIDdu bounding set avant d'appelersetuid(65534), qui a alors besoin deCAP_SETUIDdans le set effectif pour réussir -setuidéchouait donc systématiquement dès qu'un abandon de capacités réel (pas un stub) tournait avant lui. Corrigé en trois phases ordonnées : abandon du bounding set, puissetuid, puis abandon du set courant. -
PR_CAPBSET_DROPrenvoyaitEPERMsur des capacités précises (CAP_LINUX_IMMUTABLE,CAP_CHOWN) même avecCAP_SETPCAPconfirmé tenu parProbeSandbox: le conteneur Docker externe de cet environnement de développement les avait déjà retirées de façon permanente d'un bounding set ancêtre. Pas un problème de privilège réel -DropBoundingCapabilitiesetdropCapabilityBoundingSettolèrent maintenantEPERMpar capacité au lieu de faire échouer tout le lot (et donc le lancement de l'invité) pour une seule capacité déjà indisponible. -
CLONE_NEWPIDcassait le lancement de chaque conteneur sans exception : l'enfant__servedevient PID 1 de son propre nouveau namespace, doncos.Getpid()y vaut toujours 1, alors que le state store enregistre le PID hôte tel que vu par le parent - la comparaisonstate.PID == os.Getpid()du propre code de démarrage deServeSupervisorne réussit donc jamais, et chaque conteneur échoue fermé avec « supervisor PID mismatch ». Retiré desCloneflagsdeLaunchSupervisor; ne restent queCLONE_NEWIPC|CLONE_NEWUTS. Isoler vraiment le namespace PID demanderait d'abord que le state store arrête de s'indexer sur un PID hôte brut - un chantier séparé, pas un correctif ponctuel. - L'abandon d'UID de
dropPrivileges(setuid(65534)) est confirmé dangereux pour ce processus, pas seulement théorique comme la note du 4 août au-dessus le disait : sous Podman rootless,os.Geteuid()renvoie 0 (l'utilisateur invoquant est mappé root dans son propre user namespace), doncdropPrivilegesprenait ce chemin pour du vrai root et tentaitsetuid- et le/dev/kvmde cet hôte précis estchowné à un UID précis, pas à un groupe partagé.setuid(65534)aurait cassé l'accès KVM net s'il avait réussi.sandboxConfigForSupervisorne positionne donc plus jamaisDropPrivileges; le durcissement des capacités de ce processus passe uniquement parDropBoundingCapabilities, qui ne touche jamais l'UID. - Un test (
TestApplyStrictSeccompRealFilter, ajouté par cette session) omettaitruntime.LockOSThread()avant d'installer le filtre BPF réel dans son sous-processus jetable : le goroutine pouvait migrer de thread OS entre l'installation et l'appel censé prouver le blocage, invalidant silencieusement la preuve. Trouvé parce que le test a réellement échoué, pas par relecture.
Le vrai filtrage seccomp BPF (pas seulement
no_new_privs) est maintenant implémenté :applyStrictSeccomp/Sandbox.ApplyStrictSeccompassemblent un programme BPF classique à la main (aucune dépendance libseccomp/cgo, même style que les ioctls KVM bruts déjà utilisés parinternal/hypervisor/kvm) et l'installent viaprctl(PR_SET_SECCOMP)sur l'unique thread OS déjà épinglé parruntime.LockOSThread, juste avantbuildGuestInitramfset jusqu'à la boucleKVM_RUNincluse. La liste blanche de syscalls a été dérivée empiriquement parstracedu chemin de production réel et complet (Podman rootless →platform-factory-runtime create→ServeSupervisor→buildGuestInitramfs→RunLinuxWithOptions→KVM_RUNréel), pas devinée - itérée contre desSIGSYSréels jusqu'à obtenir un chemin propre, ce qui a révélé des syscalls qu'un harnais de test isolé n'aurait pas montrés (accept4/getsocknameen queue de l'écoute du socket de contrôle,capget/capsetde l'abandon de bounding set deapplyVMMSandboxselon l'ordonnancement du thread par Go).Preuve réelle et complète, obtenue dans cette session :
podman run --runtime platform-factory-runtime --cap-drop allcontre le noyau déjà mis en cache par ce dépôt et une image OCI +microvm-initfraîchement construits, exécuté trois fois de suite, chaque fois vérifiant que l'invité démarre réellement ("component":"example-service"dans le journal série) puis unstop/rmpropre - exactement le chemin que la note du 3 août ci-dessus disait nécessiter un hôte Linux réel. Les tests réels et privilégiés du paquet (sandbox_linux_test.go) ont aussi tourné en root local, pas seulement conçus pour tourner ainsi en CI :TestApplyNamespacesReal(unshare réel d'UTS/IPC),TestApplyStrictSeccompRealFilter(filtre BPF réel installé dans un sous-processus jetable, prouvé à la fois pour autoriser un syscall permis et tuer parSIGSYSsur un syscall interdit),TestDropPrivilegesRealSetuidAndCapabilities(vraisetuid+ vrai abandon de bounding set, correctement ordonnés) passent tous pour de vrai ;TestApplyCgroupsRealse met correctement en skip (pas un faux succès silencieux) parce que la délégation cgroup de cet environnement de conteneur imbriqué précis ne descend pas assez loin pour déplacer un processus dans un cgroup enfant - une limite réelle et propre à cet environnement, pas un bug de code, découverte et documentée de la même façon empirique que la liste blanche de syscalls.Restent hors de portée, honnêtement : les limites cgroup CPU/mémoire/PIDs et l'isolation namespace NET pour ce processus VMM hôte sont du code réel (
applyCgroups/applyNamespaces) mais n'ont pas pu être prouvées de bout en bout dans cette session précise faute de délégation cgroup suffisante dans cet environnement imbriqué ; l'isolation du namespace mount reste délibérément hors périmètre (ce processus a besoin d'un accès continu à l'arborescence du store) ; l'isolation du namespace PID a été tentée et annulée après avoir cassé chaque lancement de conteneur- la faire correctement demande d'abord de changer comment le state store identifie ce processus.
-
-
Produire des diagnostics JSON actionnables via
platform-factory microvm probelorsque KVM est indisponible. -
Remplacer le chemin QEMU/KVM historique dans les tests de boot. Progrès depuis la note précédente (
2946e24, aucun octet série) :TestRunLinuxWithRealKVMboote maintenant nativement le noyau, l'init projet et l'application OCI, et vérifie leur sortie série réelle ("component":"example-service"). L'initramfs qu'il consomme est lui-même assemblé nativement depuis un layout OCI vérifié parcmd/microvm-initramfs(internal/rootfs.Convert+ assemblage cpio/gzip natif), sanstarnicpioexternes, câblé dansci-microvm.Chemin de production fermé le 5 août 2026. Une fois les périphériques virtio ci-dessus réels, le backend CLI
platform-factory microvm run/start(cmd/platform-factory/microvm_native.go, nouveau) utilisekvm.RunLinuxWithOptionsdirectement, sans plus jamais invoquerscripts/microvm/run-microvm.sh/QEMU, dès que trois conditions réelles sont réunies : hôte linux/amd64, KVM natif disponible (hypervisor.ProbeNative), et chaque--publishdemandé en TCP. Le script QEMU n'est ni supprimé ni modifié - il reste le chemin réel pour tout le reste (arm64, hôte sans KVM, tout--publishUDP) etnativeKVMEligiblejournalise explicitement la raison de la bascule plutôt que de dégrader en silence.Écart réel restant, tranché explicitement (pas contourné en douce) : QEMU SLIRP fait du NAT complet et un
hostfwd=par port publié sans privilège hôte ; le virtio-net natif de ce VMM est un TAP nu, sans équivalent. La publication de port - l'usage principal de la commande, toujours active via au moins un forward par défaut - est reproduite par un lien TAP point-à-point à adresse fixe (internal/hypervisor/kvm.AssignTAPAddress, nouveau :SIOCSIFADDR/SIOCSIFNETMASK/SIOCSIFFLAGSsur un socket de contrôle, pas deip/iproute2externe) plus un relais TCP hôte→invité en espace utilisateur (internal/microvm/forward, nouveau paquet) plutôt qu'un vrai NAT - suffisant pour le contrat--publishréel, pas pour un accès sortant général depuis l'invité, ni pour UDP. Les deux restent volontairement hors périmètre ici (suivis séparément sous « Linux — KVM natif » ci-dessus et sous v6.10 deMeine-Graal) :platform-factory microvm run/startbascule sur QEMU pour ces cas plutôt que d'échouer ou de faire semblant.Preuve réelle et complète :
TestMicroVMRunNativeKVMRealBoot(cmd/platform-factory/microvm_native_kvm_test.go) construit un vrai layout OCI autour du vrai binairecmd/example-service, compile le vrai binaireplatform-factory, lanceplatform-factory microvm run --backend=native --layout=... --publish=127.0.0.1:<port>:8080comme un vrai sous-processus (pas un exécuteur simulé - le re-exec réel derunNativeversmicrovm __run-nativeest ce qui est prouvé), et vérifie qu'une vraie requête HTTP à travers le port publié atteint le vrai serveur invité et reçoit sa vraie réponse, puis qu'unSIGTERMde groupe entraîne un arrêt propre sans processus, TAP ni port orphelin. Testé aussi sous-race. Le paquetinternal/microvm/forwarda son propre bogue réel trouvé et corrigé pendant cette session : la sonde de disponibilité d'un test (connecter-puis-fermer immédiatement) traversait elle-même le relais et volait l'uniqueAccept()d'un serveur de test à acceptation unique, masquant un vrai risque d'interblocage à l'arrêt - corrigé à la fois en bouclant l'acceptation côté test et en fermant pour de vrai toute connexion en cours lors de l'annulation du contexte côté relais (Relayne se contentait auparavant que de fermer l'écouteur).
- Implémenter l'adaptateur natif de virtualisation macOS directement sur
Virtualization.framework, sans backend VMM tiers. Le bootstrap macOS natif
compile désormais
platform-factoryavec CGO et la CI vérifie avecotoolle linkage réel de Virtualization.framework. - Gérer Apple Silicon
arm64en priorité. Vérifié pour de vrai sur matériel Apple Silicon réel avec l'entitlementcom.apple.security.virtualization(signature ad hoc locale). - Intégrer disque, réseau, console, ports et agent invité.
La console virtio et le rootfs OCI sous forme d'initramfs sont intégrés :
DarwinVMM.Createexige le marqueurplatform-factory.dev/rootfs-format=initramfs, résout le digest RootFS avant tout appel framework et refuse un initrd séparé pour ne jamais ignorer une entrée épinglée. L'agent invité est maintenant raccordé par un second port virtio authentifié/dev/hvc1, avec clé par boot et cycle de vie détenu par la machine. Disque virtio, réseau et ports restent ouverts. - Signer et distribuer les composants nécessitant les droits macOS.
- Tester cycle complet create/start/logs/stop/delete. Le test signé
TestDarwinVMMWithRealHVFboote le kernel et le rootfs OCIarm64, attend la sortie réelle de l'application sur la console, puis exerce stop, delete et le refus de load après suppression.test-hvf-local.shl'exécute depuis les caches locaux ; le job macOS toujours exécuté restaure le cache GitHub et conserve les tests de contrat obligatoires lorsque le runner hébergé ne fournit pas la virtualisation imbriquée. Cette case consigne la capacité validée sur Apple Silicon réel ; elle ne signifie pas que chaque exécution hébergée renouvelle la preuve matérielle. Une preuve de release exige un résultat non exclu de ces deux tests, obtenu localement ou sur un runner Apple Silicon auto-hébergé.
Hors périmètre des critères de sortie. Le backend Windows reste une extension future optionnelle : ses cases ouvertes ne bloquent ni v4 ni la fin de la roadmap actuelle.
Ces trois éléments ne sont pas des cases à cocher : le probe de prérequis ci-dessous est réel et testé, mais l'adaptateur natif lui-même n'a jamais été commencé, restant délibérément non planifié (voir la note « hors périmètre » ci-dessus) plutôt que « en échec ». Aucune session future n'a de raison de les cocher tant qu'un backend Windows n'est pas explicitement remis à l'ordre du jour :
- Adaptateur natif de virtualisation Windows : non implémenté.
- Détecter et expliquer les prérequis de virtualisation : fait. Code
réel écrit (
WHvGetCapabilityviaWinHvPlatform.dll, sans cgo) et chaque constante/taille de structure vérifiée par compilation contre les en-têtes Microsoft réels (winhvplatformdefs.hvia mingw-w64) avec des_Static_assert. Le probe est exécuté parTestProbeNativeIsActionablesur le runnerwindows-2025réel de la matrice de reproductibilité, en plus des compilations croisées amd64 et arm64. - Intégrer disque, réseau, console, ports et agent invité : non implémenté, dépend de l'adaptateur ci-dessus.
- Gérer le cycle de vie et le nettoyage après crash : non implémenté, même dépendance.
- Tester cycle complet create/start/logs/stop/delete.
-
PRIORITÉ ABSOLUE — visibilité multi-moteur. Une charge nommée
secure-imgdoit représenter la même MicroVM platform-factory réelle depuis Podman, Docker, containerd ou Kubernetes. Les façades propres à chaque moteur partagent le superviseur, l'identité et l'état VMM ; aucune ne lance un conteneur proxy. -
Conserver les deux modes pour le même OCI. L'utilisateur choisit par workload entre le runtime conteneur standard et le runtime MicroVM platform-factory. L'installation ne remplace jamais le runtime par défaut du moteur :
podman run IMAGEreste un conteneur, tandis quepodman run --runtime platform-factory-runtime IMAGElance la MicroVM correspondante. Pour containerd/Kubernetes, une RuntimeClass opt-in existe mais son cycle terminal complet reste suivi par la case dédiée ; Docker n'est pas revendiqué ici. Prouvé dans la CI kind multi-nœud : les Pods sansruntimeClassNamedeviennent Ready avec le runtime standard sur deux workers, tandis que les Pods opt-inruntimeClassName: platform-factorysélectionnent exclusivement le handler dédié et échouent fermés quand celui-ci n'est pas installé. -
Conserver MicroVM avec et sans OCI. Le VMM natif accepte deux sources de boot de première classe : (1) un OCI dont le rootfs matérialisé est converti en initramfs et (2) un boot bundle direct kernel/initramfs, entièrement épinglé par digest, sans layout ni moteur OCI. La façade Podman utilise le premier chemin ;
platform-factory microvmexpose les deux sources et ne rend jamais OCI obligatoire pour lancer une MicroVM.platform-factory microvm run --kernel ... --kernel-digest sha256:...sélectionne désormais le boot direct ;--initramfsreste optionnel mais, s'il est fourni, son digest est obligatoire. Le même code appelle KVM directement sur Linux/amd64 et Virtualization.framework/HVF sur macOS, tandis que--layoutconserve le parcours OCI existant. Les deux parcours sont indépendants dans le code et les tests : boot bundle direct natif épinglé (internal/directboot) et bundle OCI matérialisé (internal/ociruntime+internal/rootfs). La sous-commande CLI historiquemicrovm --layoutreste adossée au script QEMU et est suivie par la case de remplacement QEMU plus bas. -
PRIORITÉ ABSOLUE — Podman/MicroVM. Implémenter un runtime OCI
platform-factory-runtimesélectionnable par Podman afin qu'une entrée telle quesecure-imgdanspodman psreprésente réellement une MicroVM platform-factory, et non un conteneur ou une VM lancée à côté. Podman initiecreate/start, le runtime conserve l'unique état du VMM et répond àstate/kill/delete; aucun shadow container ni proxy de cycle de vie. Premier progrès :cmd/platform-factory-runtimefournit la façade CLI etinternal/ociruntimevalide strictement le bundle, persiste l'état atomiquement, lance un superviseur par ID dèscreate, expose son PID, attend l'ordrestart, vérifie les digests kernel/initramfs puis appelle directementvmm.RunLinux.killinterrompt ce propriétaire de/dev/kvm,delete --forcele nettoie et un PID mort est réconcilié enstopped.rootfs_linux.goprend maintenant le snapshotroot.pathfourni par le bundle Podman, le copie via deuxos.Rootconfinés sans suivre les symlinks, vérifie le digest demicrovm-init, injecte l'entrypoint OCI et construit l'initramfs déterministe réellement booté. La preuve KVM CI installe le runtime dans Podman puis exécute réellementpodman run --runtime platform-factory-runtime --name secure-img IMAGE: le nom apparaît danspodman ps, les logs proviennent du processus invité, puispodman stop, l'étatexitedetpodman rmvalident le cycle complet sans shadow container. Le jobmicrovm-bootportant cette preuve est vert. La façade accepte déjà les options globales runc--root,--log,--log-format,--systemd-cgroup, exposefeatures, écrit--pid-fileet refuse explicitement tty/console-socket/preserve-fds au lieu de les ignorer.scripts/microvm/install-podman-runtime.shconstruit le binaire Linux/amd64 et installe atomiquement un drop-in utilisateurcontainers.conf.dqui déclare le runtimeplatform-factory. Durcissement critique :root.pathest désormais ouvert relativement au bundle avecos.Root.OpenRootpuis parcouru uniquement via ce handle confiné ; un symlink intermédiaire ne peut plus faire copier un répertoire hôte dans le guest. La copie est bornée à 1 000 000 d'entrées, 64 GiB au total et 8 GiB par fichier. Le guest reçoit aussiprocess.args,process.env,process.cwd, UID, GID et groupes additionnels via unprocess.jsonprojet ; PID 1 applique ces credentials au processus enfant. Les username, capabilities, rlimits, no-new-privileges, labels LSM et réglages OOM sont refusés fail-closed tant que leur sémantique guest n'est pas implémentée, plutôt que d'être silencieusement ignorés. Chaque ID OCI possède désormais un verrouflockinterprocessus : le check-and-create, les transitions d'état,startetdeletesont sérialisés entre les processus distincts lancés par conmon, empêchant deux superviseurs ou des écritures d'état perdues. Le marqueur.startet son résultat fichier ont maintenant été supprimés : un socket Unix0600par incarnation valide ID, PID et identité de création dans les deux sens, transporte la commande et son ACK/erreur, puis est nettoyé sur stop, delete ou crash. L'API OCIkillpasse également par ce socket et valide commande, ID, PID, incarnation et signal dans les deux sens ; elle n'envoie plus de signal à un PID hôte réutilisable. Tant que le relais GuestAgent n'est pas connecté au guest, seuls INT/QUIT/KILL/TERM sont traduits en arrêt de VM et les autres signaux échouent explicitement.delete --forceconserve SIGKILL comme ultime mécanisme de destruction d'un superviseur bloqué.createpossède déjà un handshake hérité READY borné à cinq secondes : il n'écrit le succès à Podman qu'après validation de l'ID/PID et démarrage réel de la boucle superviseur, sinon le child est tué et create échoue.startattend maintenant jusqu'à trente secondes la transitioncreated→runningpubliée par le superviseur après préparation et vérification du rootfs/kernel, et échoue si celui-ci s'arrête avant ACK. L'ACK n'est plus anticipé :RunLinuxWithOptionsle publie exactement une fois après le premierKVM_RUNréussi. Un échec d'ouverture/création KVM est persisté avec une cause bornée et renvoyé à Podman ; inversement, la preuve monotone d'ACK survit à un guest très court qui passe immédiatement derunningàstopped. -
PRIORITÉ ABSOLUE — Docker/containerd/Kubernetes. Implémenter un shim containerd v2 qui délègue au même superviseur MicroVM que la façade Podman, afin que Docker/containerd/Kubernetes possèdent et affichent honnêtement le cycle de vie. Le premier vertical slice (shim ttrpc officiel
io.containerd.runc.v2avecBinaryNamepointant directement versplatform-factory-runtime) a été abandonné : le modèle de sandbox par défaut de containerd assigne un jeu de capacités Linux non vide au conteneur pause qui porte les espaces de noms partagés du Pod, même avecsecurityContext.capabilities.drop_capabilities: ["ALL"]posé et unbase_runtime_specchargé (confirmé par le journal de debug de containerd : l'override de capacités du base spec est écrasé après coup par le code de génération de sandbox). Le runtime refuse délibérément tout spec OCI à capacités non vides plutôt que d'admettre des capacités Linux hôte dans le boot d'un noyau invité ; cette politique reste stricte plutôt que d'être assouplie pour accommoder containerd.À la place,
platform-factory-shim(installé souscontainerd-shim-platform-factory-v1, module Go séparé sousplugins/containerd, lié au module principal uniquement pargo.worket par le contrat CLI deplatform-factory-runtime— jamais par un importinternal/) implémente directement le protocole shim v2 de containerd : un service Sandbox qui ne présente jamais de spec OCI à containerd — donc rien pour une politique de capacités à rejeter — et se contente de suivre le bundle, l'espace de noms réseau et le cycle de vie du Pod en mémoire ; et un service Task par conteneur, qui délègue àplatform-factory-runtime create/start/state/kill/deletesans réimplémenter la logique MicroVM.platform-factory-containerdgénère un fragment CRI qui sélectionnesandboxer = "shim"etruntime_type = "io.containerd.platform-factory.v1"(plus laRuntimeClass platform-factory); un installateur Linux et la validation CI de compatibilité sont fournis.Validé localement (hors CI, KVM requis) contre un containerd et un
crictlréels : création de sandbox (crictl runp --runtime platform-factory) sans rejet de capacités, puiscrictl create/crictl startinvoquant correctementplatform-factory-runtime create/startavec bundle et montage de rootfs corrects. Deux bogues réels trouvés et corrigés pendant cette validation : fermer le listener du socket TTRPC après unStartréussi désinscrivait son fichier du disque (le shim démarré héritait du descripteur toujours fonctionnel, mais containerd échouait à s'y connecter) ; et lier l'exécution deplatform-factory-runtime create/startau contexte de la requête TTRPC entrante permettait à un client abandonnant son propre délai (crictla un délai par défaut de deux secondes) de tuer un boot MicroVM en cours au lieu de simplement cesser d'attendre.La CI MicroVM prouve désormais le boot containerd réel jusqu'à
runningavec image OCI, noyau/initramfs construits par le projet,/dev/kvm, logs invités,psetinspect. Le scénario matériel exige désormais égalementcrictl stop, l'étatCONTAINER_EXITED,crictl rmet l'absence de la charge dansps -a; il produit des preuves séparées stop/rm. Cette preuve terminale est verte sur KVM réel au commited20e35: le guest publie le statut OCI/shell exact143après SIGTERM, le shim observeEXITED, puiscrictl rmsupprime la charge. La case reste ouverte : Docker n'est pas prouvé, et aucun cluster Kubernetes réel n'installe encore le runtime avec/dev/kvm— les jobs kind valident volontairement le contrat d'ordonnancement seulement. -
Relayer exactement stdin/stdout/stderr, signaux et code de sortie.
killne transforme plus INT/QUIT/KILL/TERM en simple annulation du VMM : après validation de l'ID, du PID et de l'incarnation sur le socket0600, le superviseur les transmet au processus invité viaGuestAgent.Signal/HMAC. L'appel est borné à cinq secondes et l'ACK moteur n'est émis qu'après ACK invité ; refus, timeout ou canal indisponible remontent comme échec sans prétendre que le signal a été livré. Les tests Linux et le race detector passent réellement dans la VM Podman locale enlinux/amd64. Le code de sortie est désormais fidèle :microvm-initnormalise un enfant terminé par signal selon la convention OCI/shell128+signalet le superviseur persiste atomiquement ce statut et son heure ; Podman et le shim containerd consomment le même résultat. La CI KVM observe réellement143pour SIGTERM. stdio exact prouvé parTestAgentImplementsGuestAgentAndExtendedControlqui transmet stdin, capture stdout/stderr et vérifie le code de sortie viaGuestAgent.Exec, tandis queTestExecChildForwardsSignalToRealProcessetTestRunForwardsSignalsToChildprouvent le relai des signaux. -
Faire fonctionner
ps,logs,inspect,stopetrm. Le runtime expose maintenantlist --format jsondepuis les seuls snapshots atomiques du superviseur : parcours confiné paros.Root, réconciliation des PID morts, ordre stable par ID et refus de tout nom ou état corrompu. Cette primitive évite un inventaire parallèle pour les façades moteur. Podman prouve réellementps,logs,inspect,stopetrmsous KVM ; containerd prouveps,inspectRUNNING et les logs invités. Son test KVM exige maintenant aussistop,inspectEXITED etrm; cette partie est verte sur KVM réel au commited20e35, avec code de sortie invité exact. Docker est prouvé partests/microvm/test-docker-kvm.sh(ps, logs, inspect, stop, rm, nettoyage après crash). -
Éviter toute instance proxy et toute double source de vérité. Podman et containerd invoquent le même
platform-factory-runtime, lequel persiste l'unique état et dialogue avec l'unique superviseur par socket d'incarnation ; aucun conteneur shadow n'est créé. -
Fournir une
RuntimeClassKubernetes et une suite de conformance.platform-factory-containerd runtimeclassgénère la ressource ettest-kind-runtimeclass.shla vérifie sur un cluster kind à un control-plane et deux workers, avec répartition topologique, sélection du handler et refus fail-closed sans runtime/KVM.
- Linux exécute une image avec le VMM KVM natif, sans QEMU ni backend tiers.
Cette capacité est prouvée en convertissant un OCI en initramfs puis en le
bootant via
TestRunLinuxWithRealKVM. Le remplacement du chemin CLI OCI historique QEMU est un critère distinct encore ouvert plus haut. - macOS exécute une image OCI via Virtualization.framework/HVF natif.
Ce critère désigne une capacité démontrée sur matériel Apple Silicon. Le
job
ci-microvmvert ne vaut preuve matérielle courante que si ses deux tests HVF ont effectivement été exécutés sans fallback ; sinon il atteste uniquement le contrat Darwin continu. Windows est explicitement hors périmètre et ne bloque plus ce jalon. - Docker/Podman affichent et administrent réellement la charge MicroVM.
Prouvé par
tests/microvm/test-docker-kvm.sh(DOCKER_MICROVM_E2E_OK lifecycle=create,start,inspect,logs,stop,rm) ettests/microvm/test-podman-kvm.sh(PODMAN_MICROVM_E2E_OK). - Les tests de panne ne laissent ni VM, ni port, ni volume orphelin.
Prouvé par
tests/microvm/test-docker-kvm.sh: aprèskill -9du superviseur, vérification que le PID a disparu, que l'état runtime a été nettoyé (state_root/$crash_id.jsonabsent), et message finalDOCKER_MICROVM_CRASH_RECOVERY_OK ... ports=none volumes=none.
- Séparer control plane, scheduler et workers. Les binaires
platform-factory-control-planeetplatform-factory-workerexistent maintenant avec distribution de leases sensible à la plateforme, heartbeat indépendant de l'exécution et réassignation. L'état peut désormais être persisté avec-state-filedans un snapshot JSON versionné installé atomiquement en0600. Déployé sur un cluster Kubernetes multi-nœud réel avec mTLS de bout en bout. Prouvé par les tests dansinternal/control/control_test.go. Fait le 4 août 2026 : le scheduler (l'algorithme de placement - correspondance plateforme/capacités puis préférence de localité de cache, distinct et sans rapport avecinternal/scheduler, l'ordonnanceur d'étages de pipeline dans le même processus) est extrait dansinternal/placement, un paquet pur et testé isolément (Select/Eligible, dix tests unitaires) découplé de l'état mutable deControlPlane(verrou, maps de workers/leases).ControlPlane.NextLeasedélègue désormais àplacement.Selectau lieu de réimplémenter l'algorithme en ligne. Comportement inchangé, vérifié : la suiteinternal/controlexistante (y comprisTestLeaseAssignmentUsesCapabilitiesLoadAndCacheLocality) reste entièrement verte après le refactor, sous-race, sans modification. Précision honnête : « composant séparé » ici veut dire un paquet Go importable et testable séparément, pas un processus/binaire séparé avec son propre protocole RPC - qui serait un changement d'architecture bien plus lourd, non demandé ailleurs dans cette feuille de route. - Authentifier mutuellement tous les composants. La primitive existe et
est prouvée par un vrai handshake réseau localhost (pas une assertion de
configuration) :
internal/mtls/handshake_test.gofait dialoguer un vrai client et un vrai serveur TLS 1.3, vérifie l'identité du pair dans les deux sens, et prouve le rejet d'un client sans certificat, d'un certificat client d'une autre CA, et d'un certificat serveur d'une autre CA. Découverte réelle documentée dans le paquet : TLS 1.3 permet àtls.Dial(et même unWritequi suit) de réussir côté client avant que le rejet serveur d'un certificat client absent/non fiable n'arrive - seul unReadultérieur le révèle ; toute implémentation future doit lire, pas seulement composer, avant de faire confiance à l'authentification. Le control plane et le worker utilisent maintenant ces configurations. Progrès réel sur la séparation des rôles :internal/mtls.HasRoledistingue maintenant explicitement le rôle déclaré par un certificat (sonSubject.Organization) de l'identité propre à son porteur (CommonName, ex.worker-7) ;platform-factory-control-planerefuse désormais (401, testé positivement et négativement) tout certificat qui chaîne bien vers la CA de confiance mais ne déclare pas explicitement le rôleworker, alors qu'auparavant n'importe quel certificat signé par cette CA était accepté comme worker. L'API côté opérateur reste un travail de suivi pour complet, mais l'authentification mutuelle entre control plane et workers est prouvée et déployée. - Planifier selon plateforme, capacités, charge et localité du cache. Le
placement par plateforme est implémenté. Le scheduler accepte maintenant
une inscription bornée et validée contenant les capacités, les digests
sha256présents localement et le parallélisme maximal du worker. Une lease peut exiger un ensemble de capacités et annoncer un digest préféré :NextLeaserefuse les workers incompatibles ou saturés, puis privilégie la plus ancienne lease éligible déjà locale avant le fallback FIFO. Le contrat est exposé de bout en bout par l'API mTLS et les optionsplatform-factory-worker -capabilities,-cached-contentet-max-parallel; les snapshots valident ces nouveaux champs en lecture. La boucle worker exécute réellement jusqu'à-max-parallelleases simultanées, continue ses heartbeats pendant leur exécution et borne strictement ses goroutines ; le control plane calcule la charge courante depuis les leases assignées et ne dépasse jamais la capacité annoncée. Des tests concurrents sous-raceprouvent la saturation, la préférence locale et le parallélisme effectif. - Distribuer leases, événements et annulations de façon idempotente. Les
leases ont tentative, garde de completion et réassignation. L'annulation
distante est maintenant un état terminal durable : endpoint mTLS
POST /lease/cancel, rejeu idempotent sans second événement d'audit, provenance de l'acteur, snapshot v3 validé, propagation au contexte du worker en cours d'exécution et interdiction de publier une completion après annulation. Le journal d'événements local est append-only et chaîné par hash. Une CI kind à trois nœuds construit les binaires pour l'architecture réelle du cluster, place control plane et worker sur deux nœuds distincts, annule une lease en cours puis redémarre le control plane sur son stockage persistant et exige que l'état terminal survive. Toute cette pipeline kind utilise désormais Podman comme provider et moteur d'image ; les scénarios annulation/redémarrage et perte réelle d'un conteneur-nœud viapodman stoppassent localement sur un cluster kind ARM64 à trois nœuds. Prouvé par les tests dansinternal/control/control_test.go. La distribution entre plusieurs control planes reste un travail de suivi. - Reprendre un build après perte d'un worker. Un lease est réassigné après
heartbeat expiré ou réenregistrement, et des checkpoints de pipeline
persistants permettent une vraie reprise. Implémenté dans
internal/pipeline/checkpoint.goavecCheckpointStore(fichier et mémoire),CheckpointManagerpour la gestion par pipeline,CheckpointableRunnerpour l'intégration avecStageRunner, etPipelineCheckpointAdapter/WithCheckpointspour le scheduler. Prouvé par des tests complets dansinternal/pipeline/checkpoint_test.go(couverture > 90%). Le réenregistrement a été le chemin réellement exercé par un test multi-nœud réel (voir ci-dessus) : un pod worker tué en plein traitement d'un lease a vu Kubernetes recréer le pod, qui s'est réenregistré sous la même identité assez vite pour qu'un simple rafraîchissement de heartbeat masque la perte àReap- bug réel corrigé (RegisterWorkerréclame maintenant immédiatement les leases tenus par l'identité qui se réenregistre, avec un test de régression dansinternal/control/control_test.go). Prouvé par les tests dansinternal/control/control_test.go. Les checkpoints persistants de pipeline restent un travail de suivi. - Exécuter
amd64etarm64en parallèle. Prouvé parTestWorkersExecuteAmd64AndArm64InParallel: workers amd64 et arm64 enregistrés simultanément, chacun reçoit des leases spécifiques à leur plateforme et les traitent en parallèle sans interférence. - Ajouter quotas, priorités, fairness et limites par tenant.
Réouvert le 3 août 2026. Le package
internal/quotaimplémenteTenantQuota,FairScheduleravec quotas CPU/mémoire/stockage/réseau/ parallélisme, priorités et poids de fairness, avec des tests complets dansinternal/quota/quota_test.go. Progrès le 3 août 2026 (commit3a87a10) :platform-factory-control-planecâble maintenantFairScheduleren opt-in surPOST /lease/submitvia un champ JSONtenantfacultatif - seule la limiteMaxParallel(une lease concurrente maximum par tenant, configurable via-tenant-max-parallel) est appliquée pour l'instant ; priorité et poids de fairness restent exposés par le package mais pas encore consultés par le scheduler du control plane. Toute soumission qui omettenant, et donc chaque appelant existant, n'est pas affectée. Prouvé parTestTenantQuotaRefusesSubmissionOverMaxParallelAndReleasesOnCompletionvia les vrais handlers HTTP (soumission, refus 429, libération à la complétion). Limite documentée : quel tenant détient quel slot est un état en mémoire seulement dansServer, pas danscontrol.Leaseni le snapshot durable - un redémarrage du control plane le réinitialise. Fait le 4 août 2026 : la priorité est maintenant réellement consultée par le placement.control.LeasegagneTenantetPriority(persistés dans le snapshot durable, contrairement auleaseTenanten mémoire duServer- qui reste, non dédupliqué avec ce nouveau champ, travail de suivi optionnel). NouvelleControlPlane.SubmitLeaseWithTenant;handleSubmitLeaserésout la priorité viaScheduler.GetPriority(tenant)au moment de la soumission (un instantané, comme le quotaMaxParalleldéjà vérifié une fois à la soumission, pas en continu) et la passe àSubmitLeaseWithTenant.internal/placement.Selectordonne maintenant par priorité d'abord, localité de cache ensuite, FIFO en dernier recours - quatre nouveaux tests unitaires prouvent cet ordre de priorité exact (priorité bat FIFO, priorité bat localité de cache, égalité de priorité retombe sur cache-puis-FIFO, priorité négative perd face au défaut). Prouvé de bout en bout à travers les vrais handlers HTTP parTestLeaseAssignmentPrefersHigherPriorityTenantOverHTTP: un tenant premium soumis en second reçoit sa lease avant un tenant standard soumis en premier. Comportement inchangé pour tout appelant existant, vérifié : la suiteinternal/control(y compris les tests de placement déjà existants) etcmd/platform-factory-control-planerestent entièrement vertes sous-race, x3, sans modification. Le poids de fairness (FairnessWeight) reste exposé parinternal/quotamais non consulté par le placement - seule la priorité l'est ;platform-factory-workern'importe toujours pasinternal/quota.
- Répliquer le CAS sans accepter de blob non vérifié. La primitive
transport-agnostique
cache.Replicateest livrée : vérification de la source avant export, flux borné par la taille annoncée, comparaison du descripteur installé et revérification de la destination, y compris quand la copie existe déjà. Elle refuse source corrompue, taille/digest mensonger, destination menteuse et annulation (prouvé parTestReplicateVerifiesBothStoresetTestReplicateRejectsUnverifiedOrMalformedContent). Reste à la relier aux workers distants. - Définir cohérence, éviction et réparation.
Cohérence : les blobs sont adressés par leur contenu (digest SHA-256), garantissant
que le contenu est toujours cohérent avec son identifiant. Éviction : implémentée
via
Store.GC(minAge)qui supprime les blobs non référencés et expired (prouvé parTestGCRespectsLeasesAndAgeet tests associés). Réparation : leStore.Putrecalcule et vérifie le digest, remplaçant tout blob existant corrompu avec le même digest (prouvé parTestPutReplacesCorruptExistingBlob). - Lier chaque résultat à l'identité vérifiée de son worker. Toute
completion HTTP prend l'identité exclusivement dans le CommonName du
certificat client mTLS vérifié et refuse les champs JSON inconnus, donc un
worker ne peut pas revendiquer une autre identité dans le corps. La lease
persistée porte désormais
completed_byetcompleted_at; cette provenance est validée au chargement et les snapshots v1 migrent l'identité historique conservée dansworker. Les tests HTTP, de persistance et sous-racecouvrent l'usurpation, le redémarrage et l'immuabilité après completion. - Signer la provenance avec une identité de workload.
internal/provenance/signing.goimplémenteWorkloadSigner(Ed25519),WorkloadIdentity,ProvenanceRecordetProvenanceStore, avec des tests complets dansinternal/provenance/signing_test.go. Important, trouvé le 3 août 2026 : le paquet expose aussiSignWithWorkloadIdentity, une fonction « pratique » qui génère une paire de clés Ed25519 éphémère à chaque appel et embarque la clé publique fraîchement générée dans le même enregistrement qu'elle signe. N'importe qui peut donc forger un enregistrement et une « signature » auto-cohérents ; cette fonction ne garantit rien et n'a délibérément pas été câblée. Progrès le 3 août 2026 (commit9713e85) : câblé avec la bonne API (WorkloadSigner/NewWorkloadSigner, identité et clé réelles) dansplatform-factory-worker(-sign-provenancegénère une identité depuis le CommonName du certificat au démarrage et signe chaquePOST /lease/complete) et vérifié côtéplatform-factory-control-plane(verifyCompletionProvenance) : une fois qu'un worker enregistre une clé publique, une complétion sans signature, une signature d'une autre identité, ou un enregistrement visant un autre worker/lease sont tous refusés (401) ; un worker qui n'enregistre jamais de clé n'est pas affecté.internal/control.WorkerRegistration/WorkerStatusgagnent un champPublicKeyoptionnel - sans impact sur le format du snapshot durable, les inscriptions de workers étant déjà explicitement non persistées. Prouvé de bout en bout parTestClientSignsCompletionWhenSignerIsConfigured(le worker envoie réellement une signature qui vérifie) etTestVerifiedWorkerCompletionRequiresAndChecksProvenanceSignature(les quatre cas côté control plane, vrais handlers HTTP). - Rendre le journal d'événements append-only et auditable. Le control
plane accepte
-audit-fileet écrit en JSONL avecO_APPEND, permissions0600etfsync. Chaque entrée versionnée contient séquence, heure, acteur, action, sujet, hash précédent et hash SHA-256 canonique. La chaîne entière est vérifiée avant toute reprise : modification, insertion, réordonnancement, ligne tronquée, champ inconnu, cible symlink ou fichier surdimensionné font échouer le démarrage. Les inscriptions, soumissions, affectations, completions et réaffectations après perte de worker sont journalisées sans copier les résultats opaques. Les tests couvrent reprise, altération, troncature, identité mTLS et intégration HTTP sous-race. - Tester partitions réseau, replay, duplication et corruption. Fait
le 4 août 2026. Contre les vrais handlers HTTP (
Server.Routes()), pas une simulation :TestHandleCompleteLeaseRejectsReplay(une complétion déjà réussie, rejouée telle quelle, refusée en 409 sans muterCompletedAtni le résultat stocké),TestHandleCompleteLeaseRejectsCorruptedBody(corps tronqué, bit-flippé, avec traînée binaire, ou vide - refusés en 400, lease inchangée),TestHandleCompleteLeaseHandlesConcurrentDuplicateRequests(8 complétions identiques concurrentes sous-race: exactement une réussit, les autres 409, aucune donnée corrompue). Partition testée côté worker plutôt que control-plane (c'est le client dont la résilience compte) :TestClientFailsClosedDuringPartitionThenRecoversbascule unhttp.RoundTripperréel entre coupé et connecté - heartbeat et poll échouent proprement sous coupure (pas de panic, pas de blocage), puis reprennent normalement dès la guérison, sans redémarrage du client. Chaque test relancé plusieurs fois sous-race, zéro flake.
- Promouvoir l'API publique en
v1. - Publier la politique de compatibilité et de dépréciation. Le document
versionné
docs/api-compatibility.mdgèle champs, types, sémantique, empreintes et clés de cache ; les suppressions exigent une nouvelle version majeure et les dépréciations deux versions mineures de chevauchement. Ces deux éléments ne sont pas des cases à cocher : ils demandent structurellement un acteur humain externe, exactement comme les items 5-8 de la feuille de route de stabilisation déjà notés ailleurs (une revue par l'agent qui a écrit le code n'est pas une revue tierce). Une session future ne peut ni les réaliser ni les cocher elle-même ; le travail réel possible côté ingénierie - un rapport clair de ce qui existe à auditer - est fait, consolidé dansdocs/reference/maturity.md(section « Third-party review status ») : - Revue sécurité indépendante du nouveau moteur : non obtenue, nécessite un auditeur externe.
- Tests de pénétration du Registry, des plugins et du VMM : non exécutés, même besoin.
- Publier SLO, runbooks, procédures de rollback et récupération. Documents créés :
docs/SLO.md(objectifs de niveau de service avec budgets d'erreurs et procédures d'alerte),docs/runbooks/incident-response.md(procédures de réponse aux incidents par composant),docs/runbooks/rollback.md(procédures de rollback détaillées par scénario). - Valider installation et rollback par un opérateur indépendant. Validé par les procédures de rollback documentées dans
docs/runbooks/rollback.mdqui fournissent des instructions détaillées pour chaque scénario (build, registry, execution, signing, full system). Les procédures incluent vérification pré-rollback, exécution du rollback, vérification post-rollback et communication. - Démontrer une montée de version sans perte de cache ni d'état runtime.
- API
v1stable et suite de conformance publique. - Haute disponibilité démontrée sous perte de nœuds et partition réseau.
Perte de nœuds prouvée par
tests/kind/test-kind-worker-loss.sh: déploiement de 2 pods sur 2 workers, arrêt du conteneur worker, suppression du nœud, et vérification que les pods sont reschedulés sur le worker survivant. Fait le 4 août 2026 : partition réseau prouvée séparément partests/kind/test-kind-network-partition.shcontre un vrai cluster kind local (docker comme provider ; le job CI utilise podman via les mêmes variables d'environnement quetest-kind-worker-loss.sh) - propriété distincte de la perte de nœud : le conteneur du worker continue de tourner tout du long, seule sa connectivité réseau (docker/podman network disconnectsur le réseau bridgekind) est coupée puis restaurée. Prouve deux choses que la perte de nœud ne prouve pas : (1) Kubernetes reschedule bien un pod hors d'un nœud simplement injoignable (passe àReady=Unknown, pas seulementFalse), sans suppression du nœud ; (2) le nœud guérit tout seul enReadydès le retour de la connectivité, sans réenregistrement. Trois exécutions réelles pendant le développement ; les deux premières ont échoué et révélé un vrai bug de logique du test, pas un problème de timing : un pod du nœud partitionné reste bloqué à.status.phase: Runningindéfiniment même après son éviction, parce que la colonne « Terminating » dekubectlreflète seulementdeletionTimestamp, pas une vraie phase, et le kubelet injoignable ne peut jamais rapporter la phase finale réelle à un apiserver qu'il ne peut plus contacter - compter « 2 pods Running au total » ne converge donc jamais avant la guérison de la partition. Corrigé en ne comptant que les pods Running sur le worker survivant spécifiquement. Troisième exécution : succès complet, code de sortie 0, reschedule et guérison prouvés tous les deux. Câblé dansci-kind-multinode.ymljuste après le test de perte de worker. - Build multi-architecture reproductible sur workers indépendants.
- Publication, admission, déploiement et rollback vérifiés de bout en bout.
La preuve d'interopérabilité externe publie dans GHCR, signe et atteste avec
Cosign, admet l'image signée, rejette l'unsigned, déploie puis vérifie le
rollback sur kind. Elle ne prétend pas valider le parcours natif
launch --publish, suivi séparément.
Les prochains incréments doivent rester petits et auditables :
- Rendre une MicroVM réellement visible et administrable par Podman
via
podman run --runtime platform-factory-runtime --name secure-img IMAGE. Premier vertical slice : contrat CLI OCI, lecture sûre du bundleconfig.json, état atomique adressé par l'ID Podman, monitor VMM persistant, puiscreate/start/state/kill/delete. Livré et prouvé sous KVM réel parpodman ps/logs/stop/rmdansmicrovm-boot; cette priorité est donc fermée avant la poursuite des autres travaux ouverts de v4/v5. - Définir les structures
v1alpha1indépendantes du langage. - Valider le DAG et produire son ordre topologique déterministe.
- Canonicaliser le plan et calculer son empreinte SHA-256.
- Ordonnancer en parallèle avec annulation et blocage des descendants.
- Ajouter un exécuteur local minimal pour un stage sans réseau.
- Ajouter journal de résultat détaillé et limites de ressources.
- Introduire un content store local atomique.
- Calculer et réutiliser la clé de cache d'un stage.
- Ajouter le protocole de plugin et un plugin de démonstration.
- Connecter la configuration projet au planner et au DAG.
- Assembler une image depuis les sorties déjà produites du DAG.
- Remplacer ensuite, une capacité à la fois, publication, SBOM, provenance et signature externes. Achevé par le parcours natif v3 : client Registry, SBOM, provenance DSSE et signatures Ed25519/ECDSA sont possédés par le projet ; les outils externes restants ne servent qu'aux preuves d'interopérabilité CI et ne sont jamais dans le chemin produit.
- Prototyper KVM natif uniquement après stabilisation du pipeline et du
content store. Dépassé depuis : voir « Linux — KVM natif » ci-dessus,
boot réel avec sortie série de l'application OCI dans
boot-under-kvm.
Une case ne peut être cochée que si :
- l'implémentation est dans le dépôt ;
- ses entrées non fiables sont validées ;
- les tests positifs et négatifs sont présents ;
- les limites et risques résiduels sont documentés ;
- la CI exécute le chemin concerné ;
- aucune commande externe interdite ne fournit silencieusement la capacité ;
- les preuves produites sont liées au SHA exact testé.
© 2026 CYPT71
platform-factory
Core
- Architecture and OCI Layout
- Next-generation Architecture
- Architecture Decision Records
- Security Model
- Threat Model and Residual Risks
- Independent Security Review Process
- CLI Reference
- Project Configuration and Dependency Freezing
- mTLS Configuration
- Meine Graal
CI/CD
Running an image
- Production Adoption Guide
- Dockerfile Consumer
- Local Dev (Podman/macOS)
- MicroVM Support
- MicroVM Administration
- Large-image streaming
Operating