Files
Claude Code DTP Worker d0b354f81e
CI / Contraintes NON-NÉGOCIABLES (CLAUDE.md) (push) Has been cancelled
CI / Validation JSON (schémas Faisabilité) (push) Has been cancelled
CI / Qualité documentaire (liens + 4Big) (push) Has been cancelled
CI / Reproductibilité des artefacts out/ (build == commité) (push) Has been cancelled
CI / Fraîcheur matrice de régression (run == commité) (push) Has been cancelled
CI / Intégrité du câblage CI (gate agrège tout · gates statiques verrouillés) (push) Has been cancelled
CI / Intégrité des chiffres du README (valeur == artefact cité · (push) Has been cancelled
CI / Publiciste · parser + schéma + generator (unittest) (push) Has been cancelled
CI / RBAC · 50 rôles + schéma (unittest) (push) Has been cancelled
CI / Faisabilité · générateur 4 volets + round-trip (unittest) (push) Has been cancelled
CI / RBAC · fixtures ERPNext (Role + Custom DocPerm) (push) Has been cancelled
CI / RBAC · plan User Permission (row-level) (push) Has been cancelled
CI / RBAC · Role Profile (bundles par portail) (push) Has been cancelled
CI / RBAC · run-book d'application unifié (agrégat 3 volets) (push) Has been cancelled
CI / Faisabilité · dossier bancable trilingue FR/EN/ES (push) Has been cancelled
CI / CRM · workflow vente ERPNext (lead → CONFOTUR) (push) Has been cancelled
CI / CRM · DocType porteur OTO Dossier Vente (push) Has been cancelled
CI / CRM · barème commissions vendeurs (push) Has been cancelled
CI / Fiscal · e-CF DGII (Compupar) (push) Has been cancelled
CI / Frontend · Workspaces 5 portails rôle (push) Has been cancelled
CI / Legal · DocType CONFOTUR Application (push) Has been cancelled
CI / QA · Audit 5D conformité (push) Has been cancelled
CI / SEO · mots-clés trilingues + schema.org + hreflang (push) Has been cancelled
CI / Chat OTOIA · montage par portail (Custom Block) (push) Has been cancelled
CI / QA · Audit 4Big (95+/100 sur 100% deliverables) (push) Has been cancelled
CI / Démo · Scénarios (run-sheet P07 banquier / P05 client) (push) Has been cancelled
CI / QA · Matrice de régression exhaustive (Sprint 8) (push) Has been cancelled
CI / DevOps · Run-book de déploiement VPS unifié (Sprint 8) (push) Has been cancelled
CI / QA · Matrice d'acceptation / traçabilité MVP (Sprint 8) (push) Has been cancelled
CI / Mobile · config app Expo/EAS (navigation par rôle) (push) Has been cancelled
CI / E2E baseline Playwright (manuel) (push) Has been cancelled
CI / Gate qualité (agrégat) (push) Has been cancelled
[DTP-Worker 20260803_030641] Auto exec · session 20260803_030641
2026-08-03 03:21:42 +00:00

228 lines
8.6 KiB
Python

"""Construction du bundle de config app mobile depuis les sources committées.
Entrées :
- contract : rbac_50_roles.json (portails_business + rôles · source de vérité
des ONGLETS et de leurs rôles autorisés).
- spec : mobile_spec.json (choix de config : nom/slug/scheme, profils EAS,
ordre des onglets, liste des champs `a_confirmer`).
- portails_spec : labels + icônes des 5 portails (mise en page · même source que les
Workspaces).
- langs : langues FR/EN/ES + défaut (seo_spec · source unique).
Sortie : bundle déterministe (tri stable, aucun horodatage) :
- app_config -> objet Expo `app.config` (thème dark+doré #4, locales seo,
identifiants de store `null` · a_confirmer).
- eas_build -> objet `eas.json` (profils de build + submit, credentials `null`).
- role_navigation -> 1 onglet par portail métier, gated par la surface RBAC exacte.
- manifest -> traçabilité (comptes, résumé par portail, marque, a_confirmer).
Anti-invention (#6) : aucun identifiant de store/build fabriqué (tous `null`) ; rôles =
surface RBAC exacte du portail (mêmes valeurs que les Has Role des Workspaces) ; tokens
de marque + devises + langues repris verbatim de leurs sources uniques.
"""
from __future__ import annotations
from typing import Any
from . import deps
def _labels_icons(portails_spec: dict) -> tuple[dict, dict]:
labels = {p["key"]: p["label"] for p in portails_spec["portails"]}
icons = {p["key"]: p["icon"] for p in portails_spec["portails"]}
return labels, icons
def build_app_config(spec: dict, langs: list[str], default_lang: str) -> dict[str, Any]:
"""Objet Expo `app.config`. Thème #4 ; identifiants de store `null` (#6/#8)."""
app = spec["app"]
b = deps.branding
return {
"expo": {
"name": app["name"],
"slug": app["slug"],
"scheme": app["scheme"],
"version": None,
"orientation": app["orientation"],
"userInterfaceStyle": app["user_interface_style"],
"backgroundColor": b.COLOR_BG,
"primaryColor": b.COLOR_ACCENT,
"icon": None,
"splash": {
"backgroundColor": b.COLOR_BG,
"resizeMode": "contain",
"image": None,
},
"locales": {lang: None for lang in langs},
"ios": {
"bundleIdentifier": None,
"buildNumber": None,
"supportsTablet": True,
},
"android": {
"package": None,
"versionCode": None,
},
"extra": {
"expoSdkMajor": app["expo_sdk_major"],
"defaultLocale": default_lang,
"devises": [b.DEVISE_PRIMAIRE, b.DEVISE_SECONDAIRE],
"eas": {"projectId": None},
},
"plugins": [],
}
}
def build_eas(spec: dict) -> dict[str, Any]:
"""Objet `eas.json` : profils de build + submit ; credentials `null` (#6/#8)."""
return {
"cli": {"version": None, "appVersionSource": spec["eas"]["app_version_source"]},
"build": {
"development": {
"developmentClient": True,
"distribution": "internal",
"channel": "development",
},
"preview": {"distribution": "internal", "channel": "preview"},
"production": {"channel": "production", "autoIncrement": True},
},
"submit": {
"production": {
"ios": {"appleId": None, "ascAppId": None, "appleTeamId": None},
"android": {"serviceAccountKeyPath": None, "track": "internal"},
}
},
}
def build_navigation(contract: dict, spec: dict, portails_spec: dict) -> list[dict[str, Any]]:
"""1 onglet par portail métier, dans l'ordre de la spec ; rôles = surface RBAC exacte."""
roles_map = deps.roles_by_portail(contract)
labels, icons = _labels_icons(portails_spec)
nav: list[dict[str, Any]] = []
for key in spec["portail_order"]:
roles = sorted(roles_map.get(key, []))
nav.append(
{
"portail": key,
"label": labels[key],
"icon": icons[key],
"route": f"/{key}",
"roles_allowed": roles,
"n_roles": len(roles),
}
)
return nav
def build_store_listing(spec: dict, nav: list[dict], langs: list[str]) -> dict[str, Any]:
"""Fiche store (App Store / Play) DÉRIVÉE des sources · #6.
Le worker ne SOUMET pas aux stores (agent Mobile · #8) : il produit la fiche
versionnable (sous-titre, descriptions, mots-clés FR/EN/ES). Les faits produit
prêts à dériver — nombre de portails, énumération des portails, langues — sont
INJECTÉS depuis la surface réelle (`role_navigation` = labels portails_spec,
langues seo), jamais recopiés à la main : la copie marketing ne peut donc pas
mentir sur les portails/langues. Seuls le style rédactionnel et les mots-clés
sont des choix authoriaux (mêmes que `app.name`), portés par la spec.
"""
sl_spec = spec["store_listing"]
portails_str = " · ".join(n["label"] for n in nav)
langues_str = "/".join(lang.upper() for lang in langs)
n_portails = str(len(nav))
def _render(text: str) -> str:
return (
text.replace("{n_portails}", n_portails)
.replace("{portails}", portails_str)
.replace("{langues}", langues_str)
)
content = {
lang: {champ: _render(text) for champ, text in sl_spec["content"][lang].items()}
for lang in langs
}
return {
"generated_from": "mobile_spec.json · store_listing (gabarits rédactionnels) + role_navigation (portails) + seo_spec (langues)",
"note": (
"Fiche store DÉRIVÉE : nombre/énumération des portails et langues injectés "
"depuis les sources (jamais recopiés · #6). Le worker ne build ni ne submit "
"aux stores — hand-off agent Mobile (#8)."
),
"limits": sl_spec["limits"],
"limits_source": sl_spec["limits_source"],
"langues": list(langs),
"content": content,
}
def build_bundle(contract: dict, spec: dict, portails_spec: dict, langs: list[str], default_lang: str) -> dict[str, Any]:
app_config = build_app_config(spec, langs, default_lang)
eas_build = build_eas(spec)
nav = build_navigation(contract, spec, portails_spec)
store_listing = build_store_listing(spec, nav, langs)
b = deps.branding
portails_meta = [
{
"portail": n["portail"],
"label": n["label"],
"icon": n["icon"],
"nb_roles": n["n_roles"],
}
for n in nav
]
manifest = {
"generated_from": spec["generated_from"],
"spec_version": spec["version"],
"source_version": contract["version"],
"roadmap_ref": spec["roadmap_ref"],
"app": {
"name": spec["app"]["name"],
"slug": spec["app"]["slug"],
"scheme": spec["app"]["scheme"],
"expo_sdk_major": spec["app"]["expo_sdk_major"],
},
"counts": {
"onglets": len(nav),
"portails": len(nav),
"roles_couverts": sum(n["n_roles"] for n in nav),
"langues": len(langs),
"identifiants_a_confirmer": len(spec["a_confirmer"]),
},
"brand": {
"fond": {"valeur": b.COLOR_BG, "source": "CLAUDE.md #4"},
"accent": {"valeur": b.COLOR_ACCENT, "source": "CLAUDE.md #4"},
},
"devises": {
"primaire": b.DEVISE_PRIMAIRE,
"secondaire": b.DEVISE_SECONDAIRE,
"source": "CLAUDE.md #10",
},
"langues": {
"liste": list(langs),
"defaut": default_lang,
"source": "seo/seo_spec.json · site.langs",
},
"a_confirmer": spec["a_confirmer"],
"identifiants_statut": "a_confirmer",
"portails": portails_meta,
"hand_off": (
"Build Expo 54 + submit App Store #32 / Play Store exécutés hors de ce "
"worker (agent Mobile · #8) : ce livrable ne produit que la config "
"versionnable. Les identifiants/credentials `null` sont renseignés au "
"build sur le poste/CI Mobile, jamais fabriqués ici (#6)."
),
}
return {
"app_config": app_config,
"eas_build": eas_build,
"role_navigation": nav,
"store_listing": store_listing,
"manifest": manifest,
}