Dette technique et backlog d'amélioration¶
Status: CURRENT / GOVERNANCE
Règle¶
Cette page contient les dettes encore pertinentes sur la branche de référence et les travaux de développement qui ne doivent pas être confondus avec un état CURRENT tant qu'ils ne sont pas intégrés et revalidés.
Une dette exploitable décrit : constat, risque, propriétaire cible, action, interdictions, preuves attendues et critères de sortie.
Priorités :
| Priorité | Signification |
|---|---|
| P0 | viole ou menace une frontière fondamentale / intégrité / reprise |
| P1 | dette importante pour scalabilité, exploitation ou maintenabilité |
| P2 | amélioration utile mais non bloquante |
TD-WP-CORE-001 — Responsabilités métier encore hébergées dans le plugin¶
Statut : OPEN
Priorité : P0
Couche : Plugin WordPress -> Platform
Cible : src/
Constat¶
plugins/ccx-feeds-industrial/ contient encore des zones historiques qui réalisent ou orchestrent des décisions devant pouvoir fonctionner indépendamment de WordPress : mapping, classification, identity, vertical routing, quality, pipeline et use cases applicatifs.
Risque¶
- WordPress reste nécessaire pour exécuter une partie du moteur catalogue ;
- double architecture
src/+ plugin ; - règles marchands dispersées ;
- scaling des workers plus difficile ;
- dette qui augmente avec le nombre de marchands ;
- risque de deuxième vérité métier.
Action¶
Migrer responsabilité par responsabilité vers src/ : caractérisation -> contrat -> extraction -> adapter/wrapper mince -> migration des consommateurs -> certification -> suppression legacy.
Interdit¶
- nouvelle logique métier dans les zones historiques ;
- déplacement massif sans caractérisation ;
- déplacement d'une heuristique marchande vers le Domain Core générique sans preuve de généralité ;
- suppression sans preuve des consommateurs.
Critères de sortie¶
- aucune décision catalogue critique n'exige le bootstrap WordPress ;
- wrappers WordPress restants minces ;
- frontières protégées par tests d'architecture ;
- certification Platform sans WordPress PASS ;
- consommateurs legacy migrés ou explicitement bornés.
TD-WP-RUNTIME-002 — Worker principal encore couplé à WP-CLI¶
Statut : OPEN
Priorité : P1
Couche : Runtime / Adapter
Constat¶
Le playbook CURRENT de synchronisation utilise encore wp ccx sync start --feed=<merchant> dans le conteneur worker.
Risque¶
WordPress reste une dépendance d'exécution pour des traitements non intrinsèquement CMS et complique le scaling des workers.
Action¶
Introduire progressivement un entrypoint Platform neutre pour les use cases background. Conserver WP-CLI comme adapter opérateur/compatibilité jusqu'à migration des appels.
Critères de sortie¶
- un worker peut synchroniser un marchand sans charger WordPress ;
- mêmes contrats, métriques et sémantiques de retry ;
- WP-CLI délègue au même use case ou devient optionnel ;
- certification Runtime hors WordPress.
TD-MERCHANT-003 — Isolation et fairness multi-marchands à généraliser¶
Statut : OPEN
Priorité : P1
Couche : Runtime / Ingestion
Constat¶
La cible dizaines puis centaines de marchands exige une isolation explicite par marchand, run, batch et job, avec contrôle de concurrence et backpressure.
Critères de sortie¶
merchant_id, run/batch/job traçables de bout en bout ;- traitements bornés ;
- retry/replay idempotent ;
- concurrence par marchand configurable ;
- backlog et lag observables ;
- panne d'un marchand sans blocage global.
TD-INCREMENTAL-004 — Refresh incrémental généralisé non encore CURRENT¶
Statut : OPEN / DEVELOPMENT EVIDENCE EXISTS
Priorité : P1
Couche : Application / Runtime / Projections
Constat¶
La cible industrielle exige des refreshs scoped pour les offres, identités, produits et projections impactés, tout en conservant un full rebuild canonique pour recovery et certification.
Des travaux dédiés existent sur des branches de développement, notamment autour du scoped canonical refresh et du replay des impacts pending. Ils ne doivent pas être décrits comme CURRENT sur la branche de référence avant intégration et revalidation complète.
Critères de sortie¶
- changements prix/stock ne déclenchent pas de rebuild inutile ;
- scope d'impact explicite ;
- retry post-commit idempotent ;
- suppressions/disparitions couvertes ;
- équivalence incremental final state == full rebuild final state démontrée ;
- tests Runtime et architecture sur la branche de référence ;
- documentation CURRENT mise à jour après intégration.
TD-CAPACITY-005 — Capacité VPS non encore certifiée quantitativement¶
Statut : IN PROGRESS / P1.5F CLOSED_PASS / P1.5G S5 CLOSED_PASS
Priorité : P1
Couche : Operations / Performance
Constat¶
Aucune valeur durable ne doit être affirmée sur le nombre exact d'offres, de marchands ou de refreshs par jour sans benchmark représentatif sur le VPS courant.
Action¶
Construire un capacity benchmark reproductible mesurant : débit ingestion/normalisation/persistence/identity/projection, CPU, RAM, I/O, DB latency, queue lag et projection lag.
P1.5A fixe désormais le contrat de mesure dans monitoring/capacity-metrics.json et docs/operations/capacity-slo-contract.md. Cette étape est une fondation Git-only : elle ne certifie encore aucune capacité Runtime.
P1.5B1 instrumente maintenant les deltas source/normalized/persisted et la réussite des sync batches sous forme d'événements structurés bornés. Ils ne sont pas encore exportés dans Prometheus et ne ferment donc pas la dette de capacité.
P1.5B2 ajoute les produits projetés au grain modèle pour models/variants/specifications/gallery et le résultat success/error du job de refresh projection Runtime. Les rebuilds CLI hors chemin sync et les métriques offer-change/identity restent à traiter.
P1.5B3 ajoute sync_batch{result="error"} avant durable success et interdit explicitement les proxys faux pour offer_changes et identity_products. Ces deux métriques nécessitent désormais un lot B4 dédié aux frontières sémantiques canoniques.
P1.5B4A ajoute une frontière sémantique pure, câble offer_changes{change_type="updated"} sur le replay après COMMIT et définit la traduction terminale identity vers resolved/unresolved/conflict. Le sync normal offer create/update/delete et la matérialisation terminale identity au grain produit restent le scope B4B.
P1.5B4B1 ajoute le diff canonique created/updated/deleted du sync normal à partir d'un snapshot TEMPORARY de ccx_offers_norm_v2. Le mécanisme exclut les champs opérationnels, conserve prix/stock/identity/classification dans le hash et ne dépend pas de affected_rows ni de row_hash. Le crash-window après upsert et avant émission reste documenté : la mesure n'est pas exactly-once.
P1.5B4B2A1 couvre Taxonomy V2 apply/rollback/cleanup. Apply et rollback utilisent un diff explicite entre l'offre canonique courante et la cible du snapshot ; cleanup compte chaque batch via son predicate sémantique avant UPDATE et n'émet qu'après COMMIT. Les retours SQL affected_rows/updated_total restent interdits comme source du counter. Governance apply/rollback reste le dernier writer direct canonique connu à raccorder ; l'identity terminale product-grain reste non matérialisée.
P1.5B4B2A2 couvre governance apply/rollback autour de ccx_governance_rebuild_offers_norm_for_run(). Le before-snapshot TEMPORARY est limité aux offer_key du run, mémorise présence + hash sémantique et réutilise le contrat B4B1. Les deltas created/updated/deleted sont calculés après rebuild et ne dépendent ni de affected_rows, ni de updated_count, ni du nombre de lignes du journal offer_items. Aucun writer direct connu de ccx_offers_norm_v2 ne reste hors instrumentation sur la révision auditée ; les limites restantes sont exactly-once/crash-window et l'identity terminale product-grain.
P1.5B4B2B1 crée la première autorité automatique terminale Identity au grain produit dans src/Domain, avec fingerprints déterministes et table métier ccx_product_identity_resolutions_v1 créée via la migration versionnée IDENTITY-001. WpdbIdentityResolutionStore garantit qu'un retry au même decision_fingerprint n'incrémente pas generation. P1.5B4B2B1A réaligne le vocabulaire Domain sur le contrat normatif resolved/unknown/ambiguous/conflict ; le label capacity unresolved reste uniquement une agrégation de unknown et ambiguous. Les anciennes lignes B1 resolution_status=unresolved restent lisibles comme unknown. Aucun compteur identity n'est encore émis : B2 doit câbler le Runtime et un event/outbox durable par génération avant toute prétention exactly-once.
P1.5B4B2B2A ajoute la migration moderne IDENTITY-002 et l'outbox ccx_product_identity_capacity_outbox_v1, unique par resolution_id + generation. L'UPSERT de l'état terminal et l'INSERT outbox avec no-op explicite sur clé dupliquée sont maintenant dans la même transaction InnoDB : un échec de staging outbox rollback la génération, un retry identique ne duplique pas l'événement, et une nouvelle décision crée une nouvelle génération/event. Aucun counter Runtime n'est encore émis ; B2B doit collecter/finaliser les groupes product-grain et B2C doit livrer l'outbox avec lease/retry/ACK.
P1.5B4B2B2B câble maintenant la collecte Runtime au grain produit. Le sync capture les subjects du feed avant/après l'upsert canonique, batch-load les preuves actuelles multi-sources depuis ccx_offers_norm_v2, déduplique les observations normalisées et finalise via ProductIdentityGroupResolver -> FinalizeProductIdentityResolution -> WpdbIdentityResolutionStore. EAN est prioritaire ; product_identity_key ne sert que de clé de regroupement lorsque l'EAN est absent. La boundary est schema-gated par IDENTITY-001/002 et n'émet toujours aucun counter directement. Il ne reste que B2C pour le lease/drain/ACK de l'outbox vers ccx_identity_products_total.
P1.5B4B2B2C câble le lease/drain/ACK durable de ccx_product_identity_capacity_outbox_v1 vers ccx_identity_products_total. Le claim utilise FOR UPDATE, lease_token, leased_until, reprise des leases expirés et backoff exponentiel. Le mapping Runtime conserve resolved, agrège unknown+ambiguous sous le label unresolved, et garde conflict. L'ACK n'intervient qu'après émission réussie. La livraison transport reste explicitement at-least-once : un crash après émission mais avant ACK peut rejouer le même event_id. Le terminal store reste indépendant du logger ; maintain() assure la récupération des pending/leases expirés. P1.5B peut alors être fermé pour les événements Runtime, mais P1.5 global reste ouvert jusqu'à la matérialisation live et aux benchmarks/certifications ultérieurs.
P1.5B est désormais CLOSED_PASS pour les événements Runtime sur main@9cc0462b75f9b14b1bcbf6fbbbec1cd04413d0eb. Cette fermeture ne certifie ni l'export Prometheus live, ni l'exactly-once end-to-end, ni la capacité quantitative du VPS. Les collecteurs DB/queue/projection et l'instrumentation frontend basculent explicitement dans P1.5C.
P1.5C1 ajoute les collectors read-only ccx_catalog_offers, ccx_sync_queue_depth et ccx_sync_queue_lag_seconds dans src/Application + src/Infrastructure. Ils relisent uniquement les tables canoniques, réutilisent les statuts Runtime pending|running|partial et pending|processing, zero-fill les feeds bornés sans backlog et n'introduisent aucune persistance métrique. L'export Prometheus live reste hors C1.
P1.5C2 sélectionne le plugin MariaDB natif query_response_time comme source de distribution DB, versionne son chargement/configuration et ajoute un collector read-only de buckets read|write. Les compteurs Handler_write + Handler_update + Handler_delete deviennent la source vérifiée de DB_writes_per_sec. SQL/digest/table/URL restent interdits comme labels. L'export Prometheus et la validation live de la source restent downstream.
P1.5C3 ajoute un lag projection réellement corrélé dans le même finalize : horloge monotone capturée après l'upsert canonique réussi, timestamp de completion par étape de projection, max par projection sur les verticales affectées et émission uniquement lorsqu'un diff sémantique created/updated/deleted prouve une mutation. Les MAX(updated_at) indépendants et la durée totale du sync restent interdits comme approximation. L'export Prometheus reste downstream.
P1.5C4 instrumente le cycle frontend public avec horloge monotone hrtime(true) jusqu'au shutdown, route classes bornées home|catalog|product|other_public et status classes 2xx|3xx|4xx|5xx. Aucun path/slug/URL/product ID ni code HTTP brut ne devient label. Admin/CLI/cron/AJAX/REST/health sont exclus par le bootstrap frontend. Les deux séries restent structurées mais non exportées vers Prometheus live.
P1.5C1A remplace Promtail comme autorité active de collecte des logs par Grafana Alloy, à parité de sources et de destination Loki. Le state Alloy est séparé sur /mnt/data, l'ancien state Promtail reste monté read-only uniquement pour importer les positions, et les paths sont fail-closed avec create_host_path: false. Cette migration ne matérialise encore aucune série ccx_* dans Prometheus ; elle prépare l'autorité de collecte avant le lot d'export live.
P1.5C1B matérialise les événements Runtime structurés vers le /metrics interne d'Alloy puis les scrape dans Prometheus. Le transport capacity devient always-on sur stderr hors WP_DEBUG; chaque série utilise un stage.match et un stage.label_keep spécifique afin que seuls les labels autorisés entrent dans Prometheus. Le job Prometheus conserve uniquement ccx_.*. Les custom counters Alloy restent mémoire/resettable et ne renforcent pas les garanties source at-least-once/non-exactly-once. Les collectors read-only catalogue/queue et la distribution MariaDB restent à exposer séparément ; aucune mutation live n'est certifiée par ce lot.
P1.5C1C matérialise les gauges read-only catalogue/queue via le transport Runtime borné puis Alloy. P1.5C2B complète les sources DB : les buckets cumulatifs MariaDB query_response_time sont rendus sans reconstruction en histogramme Prometheus classique, Handler_write + Handler_update + Handler_delete devient ccx_db_row_writes_total, le fichier textfile est validé par promtool avant remplacement atomique et node-exporter réutilise son autorité de scrape existante. Ce lot reste Git-only/non-live-certifié.
P1.5D ajoute les 19 recording rules ccx_capacity_* prévues par le contrat et un dashboard Grafana unique Capacity & SLO qui ne consomme que ces règles. Trois alertes reprennent uniquement les objectifs de conception déjà documentés (frontend p95 300 ms, 5xx 0,1 %, projection lag 5 min) et restent explicitement design_target_unvalidated. Aucun seuil CPU/RAM/DB/queue/IO/throughput n'est inventé avant les mesures P1.5F/G. Le lot reste Git/off-live et prépare P1.5E.
P1.5E versionne le harness reproductible dans scripts/capacity-benchmark-harness.sh et son contrat machine-readable. Il impose SHA Git exact, worktree propre, dataset/config/workload/verifier fingerprintés, 1 warm-up + au moins 3 runs mesurés, business verifier obligatoire, capture des 19 séries ccx_capacity_* et état restart/OOM des containers. Le mode par défaut reste plan-only et le harness n'a aucune autorité start/stop/restart. P1.5E ne certifie encore aucun chiffre de capacité ; P1.5F doit établir la baseline production read-only.
P1.5F-prep versionne désormais le contrat S0, un workload d'observation read-only de 300 secondes et un business verifier qui exige les 19 recording rules live, la cohérence du nombre d'offres et l'absence de restart/OOM. Le pack est Git-ready et gardé en CI mais ne ferme pas P1.5F : l'exécution réelle sur le SHA déployé, avec dataset fingerprinté, 1 warm-up + 3 runs mesurés et artefacts hors dépôt, reste obligatoire avant toute baseline quantitative.
P1.5F dataset-preflight ajoute l'exécutable read-only qui matérialise précisément les métadonnées exigées par le harness : offers depuis ccx_offers_norm_v2, products depuis ccx_canonical_products_v1, merchant_count depuis l'autorité de déploiement CCX_ACTIVE_FEEDS vérifiée identique WordPress/worker, et fingerprint depuis le dernier ccx_feed_files.file_hash de chaque feed actif. Le preflight recoupe aussi ccx_capacity_total_offers avec le compte canonique et écrit ses preuves hors dépôt. Il ne ferme toujours pas P1.5F sans exécution live.
P1.5F certifier ajoute un point d'entrée opérateur unique, toujours read-only : preflight → plan → 1 warm-up + 3 runs mesurés → validation des artefacts. Il reprend automatiquement les métadonnées du dataset, vérifie les fingerprints manifest/workload/verifier et compare l'identité, le restart count et le started_at des sept containers requis avant/après la baseline complète. Le résultat reste LIVE_EVIDENCE_PASS_PENDING_GIT_CLOSURE : le script ne ferme pas lui-même la dette ni la phase.
P1.5G définit les scénarios contrôlés S1-S6. P1.5F est CLOSED_PASS et S5 projection_rebuild est désormais CLOSED_PASS avec preuve live enregistrée dans benchmarks/capacity/p1-5g-s5-live-closure.json. S1, S2, S3 et S6 restent bloqués sur leurs workload/verifier/recovery versionnés ; S4 reste en plus soumis à une autorité explicite de concurrence sur la révision déployée. P1.5G global reste ouvert.
P1.5H-prep versionne le contrat du certificat final sans produire de chiffre : l'état reste NOT_CERTIFIED. La preuve S5 est disponible mais l'évidence P1.5G reste partielle ; chaque capacité doit rester bornée à l'enveloppe observée, avec médiane/pire run/min/max/scénarios sources, marge et provenance explicites.
Critères de sortie¶
- dataset et scénario versionnés ;
- baseline reproductible ;
- capacité et marge documentées ;
- seuils d'alerte ;
- critères mesurés indiquant quand séparer workers ou DB.
P1.5 live-prep systemd détache désormais les collectors C1C/C2B du checkout historique : le helper DB et les quatre unités sont installés atomiquement depuis une release Git exacte, avec rollback/idempotence et sans activer ni arrêter les timers. La mise en service live reste séparément autorisée.
TD-DR-006 — Disaster Recovery à certifier régulièrement¶
Statut : OPEN / CONTINUOUS
Priorité : P1
Couche : Operations
Constat¶
Disposer de commandes de backup/restore est nécessaire mais une capacité de reprise industrielle exige des exercices réellement exécutés et datés.
Critères de sortie récurrents¶
- backup hors VPS ;
- contrôle d'intégrité ;
- restore test réussi ;
- temps de reprise mesuré ;
- projections/rebuilds validés ;
- procédure actualisée après chaque changement significatif.
Cette dette est continue : une ancienne restauration réussie ne certifie pas éternellement la révision courante.
Familles de dette à surveiller¶
Sources de vérité concurrentes¶
Deux services, tables, plugins ou frontends ne doivent pas prétendre décider du même concept.
Logique métier dans la mauvaise couche¶
Renderer, Runtime, Adapter, SQL ou WordPress ne deviennent pas propriétaires d'une décision parce que l'implémentation y est pratique.
Compatibilité permanente¶
Un shim sans critères de retrait finit par devenir une architecture parallèle.
Full processing inutile¶
Un traitement global sur chaque petit changement peut devenir une dette de scalabilité même s'il est fonctionnellement correct.
Observabilité insuffisante¶
Un traitement qu'on ne peut pas corréler à merchant/run/batch/job devient très coûteux à exploiter à grande échelle.
Format d'une nouvelle entrée¶
ID : TD-XXX
Statut : OPEN / VALIDATING / IN PROGRESS / BLOCKED / RESOLVED
Priorité : P0 / P1 / P2
Composant :
Couche :
Constat observé :
Preuves :
Risque :
Propriétaire TARGET :
Action proposée :
Ce qui est interdit :
Tests / audits :
Critères de sortie :
Rollback / recovery :
Révision vérifiée :
Fermeture¶
Une dette est fermée lorsque le problème n'est plus observable sur la branche de référence, les frontières sont protégées, les consommateurs ont migré, la documentation CURRENT est corrigée et les tests/runtime requis prouvent le comportement.