Public log (/changelog + /roadmap) — Architecture & Reference
Source de verite pour le pipeline qui alimente /changelog et /roadmap. Mis a jour : 2026-09 (platform-ops/2640).
Source de verite pour le pipeline qui alimente
/changeloget/roadmap. Mis a jour : 2026-09 (platform-ops/2640).
1. Le defaut que ce pipeline corrige
.github/workflows/auto-changelog.yml POST chaque commit pousse sur
main a POST /api/admin/changelog/auto
(src/app/api/admin/changelog/auto/route.ts), qui cherche un
RoadmapItem dont commitSha egale le sha du commit. Cette colonne
n'est ecrite que par le champ texte de l'editeur admin
(src/app/(dashboard)/admin/content/roadmap/_components/ roadmap-editor.tsx) ou par l'outil MCP complete_task
(src/app/api/mcp/roadmap/route.ts). La flotte n'appelle ni l'un ni
l'autre : son modele operatoire est 100% fichiers
(backlog/<pillar>/<id>-<slug>.md, docs/team/protocol.md §1). Chaque
commit ressortait donc skipped: no-matching-roadmap-item, et au
2026-09-12 /changelog affichait une seule entree (le seed
prisma/seed-changelog.ts) pour 481 items archives et 248 PR mergees
depuis. Ce workflow reste dans le depot (il ne fait de mal a rien), mais
le mecanisme reel est desormais celui decrit ici.
Depuis septembre 2026, le chemin reel n'est meme plus un commit : c'est
une pull request. POST /api/webhooks/github
(dev-console.md §7) recoit pull_request et
pull_request_review, correle la PR a son item par le trailer Backlog:
que le protocole exige deja (§5) ou par la branche fleet/..., fait
avancer le RoadmapItem, et ouvre l'entree ChangelogEntry DRAFT au
merge — une par PR mergee, dedupliquee sur prUrl. auto-changelog.yml
n'est pas supprime pour autant : il ne fait de mal a rien, et le
supprimer serait un changement sans benefice.
2. La chaine
backlog/**/*.md (public: "true")
│ scripts/derive-public-log.mjs ← pre-push + CI (--check)
▼
src/services/public-log/public-log.generated.ts ← artefact versionne
│ src/services/public-log/sync.ts ← cold-start, latche
▼
RoadmapItem (IN_PROGRESS) + ChangelogEntry (DRAFT, groupe par semaine)
│ cron IA hebdomadaire (ai-platform/2641, pas encore livre)
▼
/admin/content/changelog + /admin/content/roadmap → un humain valide
▼
/changelog · /changelog/[id] · /changelog/rss.xml · /roadmap
3. Opt-in, jamais automatique
Un item de backlog n'entre dans le manifeste que s'il porte
public: "true" dans son frontmatter (backlog/_template.md). Absent =
prive, y compris pour les 481 items deja archives avant ce pipeline :
aucune retro-publication en masse n'a ete faite, decider quels items
historiques sont surs a montrer (trente sont des correctifs de securite)
est une decision produit, pas quelque chose qu'un script derive tranche.
public_title / public_summary sont facultatifs : ecrits par l'agent
qui livre, ils font autorite ; absents, le composer hebdomadaire
(ai-platform/2641) les redige.
Une release n'est pas une PR
La roadmap suit la regle de la flotte : un item, une PR. Le changelog, lui, compte en releases, et une release groupe ce qui a du sens ensemble :
release: "<cle-kebab>"dans le frontmatter d'un item le range dans cette release. Plusieurs items (donc plusieurs PR) qui partagent la cle font UNE entree ; une grosse PR seule peut aussi etre sa propre release ;- sans
release, l'item retombe dans la semaine ISO de son archivage (weekKey()), le comportement d'avant ; groupShippedByRelease(src/services/public-log/sync.ts) cree la release enDRAFTau premier item, puis, tant qu'elle estDRAFT, ajoute les puces des items livres plus tard dedans. Ajouter seulement : une ligne retouchee par un admin reste, et une releaseCOMPLETEDn'est plus jamais touchee. La publier reste un clic humain dans/admin/content/changelog.
Avant le lancement public, /changelog ne montre qu'une release de
lancement construite depuis SHIPPED, derriere CHANGELOG_LAUNCHED
(src/app/(marketing)/changelog/_components/release-extras.ts). Passer ce
drapeau a true bascule la page sur ces releases : c'est l'item growth-web/3031, bloque jusqu'au lancement.
Cette regle a ete contournee par la table, pas par le manifeste (
growth-web/3001, 2026-09-25). Le sync de la flotte (src/services/fleet/sync.ts) ecrit dans le MEMERoadmapItemune ligne par item suivi, sans opt-in, pour la console operateur./roadmap,/roadmap/[id]et le sitemap ne filtraient que le statut : environ 300 items internes etaient publies, titres de failles de securite ouvertes compris. Tout lecteur public applique desormaisPUBLIC_ROADMAP_WHERE(src/services/public-log/public-roadmap.ts), qui exclut toute ligne portantbacklogRef, etsrc/test/public-roadmap-hides-fleet-rows.test.tsrefuse un lecteur qui l'oublie.
Deux listes, derivees separement par scripts/derive-public-log.mjs
(reutilise parseFrontmatter de scripts/fleet-status.mjs) :
SHIPPED— tout itempublic: "true"sousbacklog/_archive/**, avecarchivedAt(date du commit qui a deplace le fichier dans_archive/,git log -1 --format=%cI).NOW— tout itempublic: "true"dontstatusestclaimed/in-progressET qui porte unbranch:(le verrou fleet reel,docs/team/protocol.md§1 — un item sans branche n'est pas effectivement en cours au sens de la flotte).
pnpm log:derive # regenere le manifeste
pnpm log:derive:check # verifie sans ecrire (CI + pre-push)
4. Pourquoi un artefact committe, pas une lecture runtime de backlog/
Une fonction serverless Vercel ne voit que ce que Next trace.
next.config.mjs's outputFileTracingIncludes ne declare que trois
fichiers, tous pour /admin/settings/security — backlog/ n'est
importe par aucun module applicatif, donc absent du bundle. La
derivation tourne donc la ou le depot existe entierement : pre-push et
CI, jamais au runtime. Le resultat, un module TypeScript importe
statiquement, suit la convention des trois autres artefacts generes du
depot (schema-guard.generated.ts, client-namespaces.generated.ts,
shopify-taxonomy.generated.ts) : declare -merge linguist-generated
dans .gitattributes, generateur dans
scripts/resolve-generated-conflicts.mjs, garde par
src/test/generated-artifacts-have-a-generator.test.ts.
5. Le sync runtime : create-only, jamais d'ecrasement
src/services/public-log/sync.ts tourne au cold-start
(src/instrumentation-node.ts, meme latch claimDeployOnce que
syncMarketplaceListings), et force-triggerable par un admin via
POST /api/admin/public-log/sync.
Il ne modifie jamais une ligne existante, seulement en cree une nouvelle quand elle est absente.
Ce paragraphe a cesse d'etre vrai pour l'AUTRE sync. La colonne marqueur qui manquait ici existe depuis
RoadmapItem.backlogRef, etsrc/services/fleet/sync.tsl'utilise pour rafraichir en toute securite les lignes qu'il a lui-meme ecrites (dev-console.md§4). Le sync du log public, lui, reste create-only : il n'ecrit pasbacklogRef, donc il n'a toujours aucun moyen de reconnaitre ses propres lignes, et le raisonnement ci-dessous s'applique tel quel.
Pas de colonne marqueur pour
distinguer « ecrit par ce sync, sans risque a rafraichir » de « edite a
la main par un admin, a laisser tranquille » (contrairement a
syncMarketplaceListings et son ADMIN_HELD_STATUSES) : le choix le
plus sur est donc d'ecrire une fois puis de laisser la ligne au
proprietaire qui la possede ensuite (/admin/content/roadmap,
/admin/content/changelog). Le vrai re-composeur, qui doit decider
« ce DRAFT a-t-il besoin d'etre etendu », est le cron IA hebdomadaire
(ai-platform/2641, pas encore livre) — sur le modele de
composeWeeklyEdition (src/services/bulletin/compose-weekly.ts) et
son dedupeKey, pas un sync aveugle.
Rien de ce que ce sync ecrit n'est visible publiquement : RoadmapItem
est cree IN_PROGRESS, ChangelogEntry DRAFT — les deux etats que
/changelog (filtre COMPLETED) et /roadmap (n'exclut que
CANCELLED, donc IN_PROGRESS EST visible — c'est le point : un item
« Now » doit se voir) traitent deja correctement aujourd'hui.
6. Identite sans migration de schema
prisma/schema.prisma est un hot file et pnpm db:guard refuse toute
colonne NOT NULL sans defaut (cf. CLAUDE.md, section « Schema sync en
deploy »). Ce pipeline n'ajoute aucune colonne :
| Entite | Identite | Meme forme que |
|---|---|---|
ChangelogEntry (release hebdo) | version: weekKey() (2026-W36) + commitSha: null | prisma/seed-changelog.ts::seededEntryWhere |
RoadmapItem ("Now") | branchName (deja une colonne, le verrou fleet) | — |
7. Ce qui reste a faire
ai-platform/2641— le composer IA hebdomadaire qui redige la copie EN structuree (New / Improved / Fixed) quandpublicTitle/publicSummarysont absents, sur le modele decompose-weekly.ts.growth-web/2642— la refonte visuelle :/changeloggroupe par semaine avec ses sections,/roadmapen trois voies Now (derive) / Next / Later (editorial). A concevoir une fois que des items reels auront opte in : au merge deplatform-ops/2640,SHIPPED/NOWsont legitimement vides.growth-web/2643— nettoyage du composantfeedback-form.tsx, de la branche anonyme de/api/feedbacket du namespace i18nfeedback, orphelins depuis le retrait de la page publique/feedback(desormais unpermanentRedirectvers/contact).
8. /feedback
Retiree comme page publique : elle dupliquait le dropdown feedback du
dashboard (src/components/shared/feedback/dropdown.tsx, cable dans
user-actions.tsx) pour un public qui n'a le plus souvent pas de
dashboard, et son propre parcours affiche decrivait ce meme pipeline
cassé (« it moves to the changelog automatically »). src/app/(marketing)/ feedback/page.tsx est desormais un permanentRedirect("/contact") — le
/contact existant est deja topic-route. Les six liens entrants (deux
CTA /roadmap, un /roadmap/[id], un /tutorials, un
/community/forum, un tool-detail-view.tsx) pointent maintenant
directement sur /contact.