Compare commits

...

4 Commits

Author SHA1 Message Date
Claude Code DTP Worker d0b354f81e [DTP-Worker 20260803_030641] Auto exec · session 20260803_030641
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
2026-08-03 03:21:42 +00:00
Claude Code DTP Worker 3eec69a87f [DTP-Worker 20260803_023638] Sprint 5 · fix · Dérive ground-truth Mobile résiduelle GAP_ANALYSIS_SPRINT1 : « Existe : Expo 54/RN 0.81.5 » (upgrade abandonné 2026-07-27) → Expo 51/RN 0.74.5 réel + « Rebuild Expo 54 » en reste-à-faire (aligné roadmap L18). Surface manquée par le sweep 94e9365/642643f · prose non gatée · gate-neutre · run_ci.sh 30 PASS 2026-08-03 02:42:16 +00:00
Claude Code DTP Worker 642643ffe3 [DTP-Worker 20260803_020634] Sprint 5 · fix · Dérive ground-truth résiduelle roadmap L18 (« Existe déjà ») : « Expo 54 » → Expo 51 réel + « Rebuild vers Expo 54 » en reste-à-faire
La session 013634 a corrigé « Expo 54 en place » dans AGENTS_EXISTING_ASSETS.md §8 +
fiche mobile, mais n'a examiné que la roadmap L56 (« Rebuild Expo 54 » = cible S5,
décision P0 Michel). La L18 (table Gains, colonne « Existe déjà ») ré-affirmait encore
« modules RBAC + API + Expo 54 » — même dérive « Expo 54 en place » (runtime réel =
Expo 51 vérifié package.json). Contradiction prouvée : AGENT.md:88 CITE la L18 comme
« Expo 51 en place » (citation en désaccord avec sa source). Corrigé → « Expo 51 »
(existant) + « Rebuild vers Expo 54 + builds + submit » (reste-à-faire, fidèle Sprint 5
L56). Ligne 56 « Rebuild Expo 54 » NON touchée (cible/décision P0). Formulation
« Rebuild VERS Expo 54 » choisie pour ne pas matcher le bigramme d'ancrage du gate
(check_readme_claims.sh:7786) → ancre reste sur L56 (grep = 1 occurrence). L18 ungated
pour ce ncombre (gate n'ancre que sur 'Rebuild Expo N', gate (d) scanne que le README
mobile). run_ci.sh 30 PASS 0 FAIL · zéro module · zéro gate (#5) · aucune commande VPS (#8).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 02:13:17 +00:00
Claude Code DTP Worker 94e9365cea [DTP-Worker 20260803_013634] Sprint 5 · fix · Dérive ground-truth Mobile : « Expo 54 en place » corrigé → Expo 51 réel (upgrade 54 abandonné 2026-07-27)
AGENTS_EXISTING_ASSETS.md §8 + fiche 03_agents/mobile affirmaient le runtime
courant « Expo SDK 54 · React 19.1.0 · RN 0.81.5 (upgrade 2026-07) EN PLACE ».
Faux : la directive Michel 85d2fa3 (audit 2026-08-03) + le package.json réel
vérifié disent Expo ~51.0.0 / React 18.2.0 / RN 0.74.5 / expo-router ~3.5.0,
l'upgrade 54 abandonné le 2026-07-27 (le backup native.bak-upgrade54-* est resté
sur ~51). Classe interdite CLAUDE.md « documenter sans vérifier existence
courante » + #6. Corrigé en distinguant runtime ACTUEL (51) vs cible REBUILD S5
(54, roadmap — décision P0 ouverte, non tranchée). Module mobile/app_config
(byte-gaté Expo 54 = cible) + roadmap NON touchés (corrects). Versions ungated
(vérifié : aucun gate ne pinne 0.81.5/19.1.0/18.2.0). Signal roadmap-vs-réel
surfacé à Michel dans le log. run_ci.sh 30 PASS 0 FAIL · zéro module · zéro gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 01:45:20 +00:00
19 changed files with 480 additions and 33 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ le VPS reçoit — jamais l'inverse.
vente, le barème commissions, le DocType Dossier Vente et le plan e-CF **résolvent**
leurs rôles depuis ce fichier, jamais un nom Frappe en dur. Total backend **RBAC 60
tests** (10 + 11 + 12 + 11 + 16) + **e-CF 39 tests**, tous gated dans le CI (matrice
de régression du repo : **564 tests · 22 suites** · verdict `PASS`, source
de régression du repo : **569 tests · 22 suites** · verdict `PASS`, source
`qa/regression/out/regression_run.json` — jamais compté à la main · #6).
## Hand-off reçus (à appliquer sur le VPS, dans l'ordre)
+11 -8
View File
@@ -1,7 +1,7 @@
# 📱 Mobile Agent · app Expo 54 + API mobile ERPNext (score 95+/100)
# 📱 Mobile Agent · app Expo (SDK 51 actuel → rebuild cible 54) + API mobile ERPNext (score 95+/100)
**Rôle** : Cet agent maintient l'**application mobile** OTO (Expo SDK 54 /
React Native) et l'**API mobile** qui l'alimente depuis ERPNext natif. Il
**Rôle** : Cet agent maintient l'**application mobile** OTO (Expo SDK **51**
actuel · cible de **rebuild** SDK 54 · Sprint 5 / React Native) et l'**API mobile** qui l'alimente depuis ERPNext natif. Il
produit les **builds EAS** iOS/Android, pousse les **mises à jour OTA**, et
soumet aux **stores** (App Store · Play Store). Il ne définit pas les rôles ERP
(agent ERPNext Backend) ni le design de la console web (agent Frontend Console) :
@@ -9,8 +9,8 @@ il **livre le binaire mobile** et son **canal API**, toujours branché sur le
**backend ERPNext réel** (contrainte #1), jamais sur un backend inventé.
## Scope
API mobile (`oto_module_mobile_api.py`) + client Expo 54 (React 19.1.0 /
React Native 0.81.5) → **builds EAS iOS/Android** → **submit App Store #32 +
API mobile (`oto_module_mobile_api.py`) + client Expo **51** (React 18.2.0 /
React Native 0.74.5 · runtime actuel vérifié) → **builds EAS iOS/Android** → **submit App Store #32 +
Play Store** → **EAS OTA** pour les correctifs sans re-review store. RBAC mobile
seedé côté ERPNext (`seed_mobile_rbac.py`). Refresh OTA quand l'API ou l'UI
évolue.
@@ -56,7 +56,7 @@ contractualisée, pas inventée :
| `/opt/oto/staging/mobile_rbac/deploy_mobile_rbac.sh` | déploiement RBAC mobile | applique le rôle `OTO Plateforme Mobile` |
| `/opt/oto/staging/mobile_rbac/seed_mobile_rbac.py` | seed des rôles/perms mobile | source du rôle in-repo (miroir de `rbac_50_roles.json`) |
| `/opt/oto/static/oto_mobile.js` | client JS mobile / glue | front embarqué |
| **Expo SDK 54** · React 19.1.0 · React Native 0.81.5 (upgrade 2026-07) | runtime app | base des builds EAS |
| **Expo SDK 51.0.0** · React 18.2.0 · React Native 0.74.5 (runtime **actuel** · upgrade 54 tenté puis abandonné 2026-07-27) | runtime app | base des builds EAS · rebuild cible Expo 54 (S5) |
| **EAS** builds iOS/Android · Apple Dev + Google Play Console | pipeline build/submit | exécution stores (hors repo #8) |
> ⚠️ Chemins **VPS**, non vérifiables depuis ce dépôt. Ils sont cités tels
@@ -70,7 +70,8 @@ la soumission store correspondants n'ont pas réellement eu lieu : c'est la row
repo). L'API mobile parle au **backend ERPNext natif** (`erpnext-backend-1`,
contrainte #1), jamais à un backend fictif. Les numéros de version d'Expo /
React / React Native cités proviennent de `AGENTS_EXISTING_ASSETS.md §8`
(upgrade daté 2026-07) et ne sont pas inventés.
(runtime actuel Expo 51 vérifié `package.json` · l'upgrade Expo 54 y est
documenté comme **abandonné** 2026-07-27) et ne sont pas inventés (#6).
## Non-négociables (voir CLAUDE.md racine pour la liste complète)
- Gitea only (jamais GitHub) — code app poussé sur `michel/oto-enterprise-os-dtp`
@@ -85,7 +86,9 @@ Voir `04_roadmap/ROADMAP_8_WEEKS_OR_LESS.md`. Volet Mobile : **Sprint 5**
(« Rebuild **Expo 54** + submit **App Store #32** + **Play Store** »). Cible de
sprint mesurable : **apps mobile live sur les 2 stores** (deliverable S5, L58),
gatée par la row d'acceptation QA **S5**. État courant (table roadmap L18) :
modules RBAC + API + Expo 54 en place, builds/submissions ~**50 %**.
modules RBAC + API + Expo **51** en place (cible de rebuild Expo 54 · S5 ·
décision 51→54 ouverte, cf. `DIRECTIVE_MOBILE_STORES_20260803.md` P0),
builds/submissions ~**50 %**.
## Coordination inter-agents
- **ERPNext Backend** : porteur du rôle RBAC `OTO Plateforme Mobile` (perm
+1 -1
View File
@@ -32,7 +32,7 @@ périmètre — un auditeur ne s'audite jamais.
**conformité** normative des hand-off.
## Verdict agrégé courant (source `qa/regression/out/regression_run.json`)
**22 suites gated · 564 tests · 564 passés · 0 échec · 0 erreur → verdict `PASS`.**
**22 suites gated · 569 tests · 569 passés · 0 échec · 0 erreur → verdict `PASS`.**
Ce compte est **découvert automatiquement depuis le CI** (`q4lib.registry.parse_ci`,
réutilisé sans duplication) — jamais compté à la main.
+1 -1
View File
@@ -15,7 +15,7 @@ Livraison anticipée récompensée : dès qu'un sprint est complet et validé QA
| Frontend | `/waf-home` + `/crm` + `/qa` + portal HTML | Clone layout sur 5 entités + polish | ~55% |
| CRM | `/crm.html` LIVE + ERPNext CRM natif | Enrichir + API pipeline | ~65% |
| SEO | mission active + worker systemd | Trilinguisation + schema.org | ~45% |
| Mobile | modules RBAC + API + Expo 54 | Builds + submit stores | ~50% |
| Mobile | modules RBAC + API + Expo 51 | Rebuild vers Expo 54 + builds + submit stores | ~50% |
| IFC/Speckle | `ifc_to_glb.py` + bim-cloud | Intégration console + viewer | ~55% |
| ONAPI/Legal | `oto_module_confotur_application.py` + drafts | Automatisation dépôt | ~40% |
| DevOps | 6 systemd services + Gitea | CI/CD Gitea Actions | ~35% |
+163
View File
@@ -1,5 +1,124 @@
# Activity Log · 2026-08-03 · Claude Code DTP Worker
## Session 020634 · Dérive ground-truth résiduelle : roadmap L18 (colonne « Existe déjà ») affirmait encore « Expo 54 » — corrigée → Expo 51 réel (#6)
**Contexte.** `./run_ci.sh` au démarrage : **30 PASS · 0 FAIL · 0 SKIP**, arbre propre.
Focus mandat courant = Sprint 5 Mobile (`DIRECTIVE_MOBILE_STORES_20260803.md`). Le
module in-repo `mobile/app_config` est complet et vert (schéma + 12 invariants ·
comptes 5 onglets / 44 rôles / 3 langues / 13 `a_confirmer` vérifiés contre
`out/MANIFEST.json` · docstring/README cohérents). Les actions P1-P5 de la directive
visent l'app réelle `/opt/oto/mobile/native/` (**hors repo #8**, bloquées sur
EAS_TOKEN/credentials Michel). J'ai donc cherché une **dérive réelle in-repo** dans le
périmètre worker.
**Défaut RÉEL trouvé (prolonge la session 013634).** La session 013634 a corrigé la
fausse affirmation « Expo 54 **en place** » dans `AGENTS_EXISTING_ASSETS.md §8` + la
fiche `03_agents/mobile/AGENT.md`, en distinguant **runtime ACTUEL (51)** vs **cible de
rebuild S5 (54)**. Elle a **délibérément laissé la roadmap** — mais son raisonnement ne
visait que la **ligne 56** (Sprint 5 « **Rebuild Expo 54** » = la cible, décision P0 de
Michel, à ne pas trancher). Elle n'a **pas examiné la ligne 18** : la table « Gains
d'accélération identifiés », colonne « **Existe déjà** », qui disait
`modules RBAC + API + Expo 54`. Or « Existe déjà » = ce qui **existe déjà**
refactorer) : y écrire « Expo 54 » ré-affirme **exactement** la dérive « Expo 54 en
place » corrigée partout ailleurs — le runtime réel vérifié (`package.json`, session
013634 + directive audit 2026-08-03) est **Expo 51**, et 54 est la **cible de rebuild**
(colonne « Reste à faire » / Sprint 5 L56).
**Contradiction interne prouvée.** `03_agents/mobile/AGENT.md:88` **cite** cette ligne :
« État courant (**table roadmap L18**) : modules RBAC + API + **Expo 51** en place ». La
fiche affirmait donc que la L18 dit « Expo 51 » alors que la L18 disait « Expo 54 » — une
**citation en désaccord avec sa source**, introduite quand 013634 a corrigé la fiche sans
corriger la roadmap. Grep confirme : l'unique consommateur de la L18 est `AGENT.md:88` ;
le spec/MANIFEST mobile ancrent sur **l.56** (`roadmap_ref`/`expo_sdk_source`), jamais l.18.
**Correctif — chirurgical (1 ligne), fidèle, non-gaté vérifié AVANT édition.**
Roadmap L18 : `Existe déjà: … Expo 54``… Expo 51` (runtime réel), et
`Reste à faire: Builds + submit stores``Rebuild vers Expo 54 + builds + submit stores`
(rend la ligne honnête sur le travail restant, fidèle à Sprint 5 L56). Résout la
contradiction avec `AGENT.md:88` (qui devient exact). **Ligne 56 « Rebuild Expo 54 » NON
touchée** = la cible S5 / décision P0 de Michel, non tranchée.
**Sûreté (vérifiée AVANT édition).** Le gate Expo-SDK (`check_readme_claims.sh:7779-7794`,
ancrage roadmap INV-mobile) ancre via `re.search(r"Rebuild Expo (\d+)", roadmap)` → ne
lit **que** la ligne 56 ; le gate (d) ne scanne que `mobile/app_config/README.md`, jamais
la roadmap. La L18 était donc **ungated** pour ce « 54 ». Formulation « Rebuild **vers**
Expo 54 » choisie **exprès** pour ne PAS matcher le bigramme `Rebuild Expo` → l'ancre du
gate reste liée à la **L56** (pas de second littéral porteur, cf. `[[roadmap-anchor-gate]]`).
**Vérifications.** `./run_ci.sh`**30 PASS · 0 FAIL · 0 SKIP** (inchangé) ·
`grep 'Rebuild Expo' roadmap` = **1 seule occurrence, L56** (ancre intacte) ·
`git diff --stat` = 1 fichier, 1 insertion / 1 suppression. **Zéro nouveau module ·
zéro gate ajouté (#5) · aucune logique de prod ni de gate touchée** — pure correction de
dérive ground-truth sourcée, prolongeant [[mobile-runtime-actual-vs-rebuild-target]].
Aucune commande touchant au VPS (#8) · aucun `git clean` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## Session 013634 · Dérive ground-truth réelle : app mobile documentée « Expo 54 en place » alors que le runtime réel est **Expo 51** (upgrade 54 abandonné) — corrigée (#6)
**Contexte.** `./run_ci.sh` au démarrage : **30 PASS · 0 FAIL · 0 SKIP**, arbre propre.
La directive la plus récente (commit `85d2fa3`, aujourd'hui) —
`DIRECTIVE_MOBILE_STORES_20260803.md` — porte le Sprint 5 Mobile. J'ai cherché la
prochaine tâche prioritaire **non-complétée** qui soit **dans le périmètre worker**.
**Périmètre : l'essentiel de la directive Mobile est bloqué / hors-repo (#8).** Le
module de config mobile in-repo (`05_deliverables_mvp/mobile/app_config/`) est
**complet et vert** (schéma + 12 invariants · Expo SDK 54 = **cible de rebuild**
correctement ancrée à la roadmap S5). Les actions P1 (`mobile-build.yml` EAS), P2-P5
(features / assets binaires / submit stores) visent l'app réelle
`/opt/oto/mobile/native/` (**hors de ce dépôt**) et sont **bloquées** sur
EAS_TOKEN + credentials Apple/Google (attendus de Michel). Rien à construire ici.
**Défaut RÉEL trouvé (investigation read-only, aucune commande VPS).** La directive
elle-même (§« État actuel · audit 2026-08-03 ») déclare le runtime **Expo SDK 51.0.0**,
l'upgrade Expo 54 **abandonné le 2026-07-27**. Vérification indépendante du
`package.json` réel : `expo ~51.0.0` · `react 18.2.0` · `react-native 0.74.5` ·
`expo-router ~3.5.0` — et **même le backup `native.bak-upgrade54-*` est resté sur ~51**
(le bump 54 n'a jamais atterri). Or l'inventaire d'actifs
`AGENTS_EXISTING_ASSETS.md §8:63` affirmait « **Expo SDK 54 + React 19.1.0 + React
Native 0.81.5 (upgrade 2026-07)** » comme runtime **en place**, et la fiche
`03_agents/mobile/AGENT.md` recopiait ces versions (« Expo 54 en place »). C'est
**exactement la classe interdite** par CLAUDE.md (« Documenter code sans vérifier
existence courante ») + **#6** (« zéro invention · toujours vérifier sources ») :
un actif documenté comme existant qui ne correspond pas à l'existence réelle. Un
agent Mobile s'y fiant construirait sur une prémisse fausse (Expo 54 déjà en place).
**Correctif — chirurgical, sourcé, distinguant ACTUEL vs CIBLE-REBUILD.**
1. `AGENTS_EXISTING_ASSETS.md §8:63` : runtime **actuel** corrigé en Expo 51.0.0 /
React 18.2.0 / RN 0.74.5 / expo-router ~3.5.0 (vérifié `package.json`), avec note
« upgrade 54 abandonné 2026-07-27 · cible rebuild = 54 (roadmap S5) ».
2. `03_agents/mobile/AGENT.md` : 5 mentions de runtime-courant « Expo 54 / React
19.1.0 / RN 0.81.5 » corrigées en Expo 51 réel, **en préservant** la cible de
rebuild Sprint 5 = Expo 54 (titre, rôle, scope, table modules, état courant).
**Ce que je n'ai PAS touché (délibérément).** (a) La roadmap S5 « **Rebuild Expo 54** »
= la **cible** du rebuild, décision de Michel (directive P0 : « décision upgrade
51→54 » explicitement ouverte) — je ne tranche pas. (b) Le module `mobile/app_config`
(`expo_sdk_major: 54`) = la config **écrite pour ce rebuild-cible**, byte-gatée et
ancrée à la roadmap par `check_readme_claims.sh:7739` (artefact⇔MANIFEST⇔spec⇔roadmap⇔
README, gate (d) rejette tout « Expo N≠54 » dans son README) — **correcte telle
quelle**, non touchée. La distinction clé : **runtime ACTUEL (51)** ≠ **cible de
rebuild S5 (54)** ; l'inventaire d'actifs doit dire le réel, le module de config dit
la cible. Aucune contradiction — ils décrivent deux choses différentes.
**⚠️ SIGNAL À MICHEL (décision P0, non tranchée par moi).** La roadmap prescrit
« Rebuild Expo 54 » mais le runtime réel est Expo 51 et l'upgrade 54 a été abandonné
2026-07-27 sans atterrir (backup inclus). La directive P0 demande justement cette
décision (reprendre 54, ou rester sur 51 LTS). Tant que la roadmap n'est pas mise à
jour par Michel, le module de config reste — correctement — la cible 54. Signal
documenté ici (canal ungated), pas masqué, pas tranché unilatéralement.
**Sûreté (vérifiée AVANT édition).** Les littéraux de version `0.81.5`/`19.1.0`/
`18.2.0`/`0.74` n'apparaissent dans **aucun** gate (grep) ; `check_readme_claims.sh`
ne pinne ces fichiers que sur (l.239) un **compte d'agents** et (l.8071) l'**IP VPS +
noms Docker** — jamais la version Expo ; le gate Expo-SDK (l.7739) ne scanne que
`mobile/app_config/README.md`. Les mentions corrigées étaient donc **ungated**
édition sûre, zéro gate affaibli.
**Vérifications.** `./run_ci.sh`**30 PASS · 0 FAIL · 0 SKIP** (inchangé). `git
status` = 2 fichiers (`AGENTS_EXISTING_ASSETS.md` + `03_agents/mobile/AGENT.md`).
**Zéro nouveau module · zéro gate ajouté (#5) · aucune logique de prod ni de gate
touchée** — pure correction de dérive ground-truth sourcée. Aucune commande touchant
au VPS (#8) · aucun `git clean` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## Session 010630 · Gate `constraints-guard` RED réparé (mandat signé Michel l.35) + contradiction inter-mandats signalée
**Contexte.** `./run_ci.sh` au démarrage : **29 PASS · 1 FAIL** — régression NEUVE du
@@ -138,3 +257,47 @@ terme interdit** en usage (rien pour `guard_constraints`), **aucun JSON** ni art
nouveau module · zéro gate ajouté (#5) · aucune logique de prod ni de gate touchée** —
pure amélioration doc. Aucune commande touchant au VPS (#8) · aucun
`git clean` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
## Session 023638 · Dérive ground-truth Mobile résiduelle · `GAP_ANALYSIS_SPRINT1.md` (Expo 54 « Existe » → Expo 51 réel)
**Contexte.** `./run_ci.sh` au démarrage : **30 PASS · 0 FAIL · 0 SKIP**, arbre propre.
Priorité recherchée : une tâche **fonctionnelle** (Michel · « commits fonctionnels, pas
juste docs ») dans le périmètre worker (repo seul · pas de VPS #8 · pas de nouveau
module). Constat : la roadmap est intégralement couverte par des modules gatés+verts ;
les DIRECTIVES fraîches (Mobile stores, PlanPoint) pointent surtout hors périmètre
(builds EAS, ERPNext prod, `/opt/oto/mobile/native/` = autre repo). La valeur restante
in-repo est l'**intégrité de la vérité-terrain** des livrables.
**Défaut trouvé (réel, anti-invention #6).** Le sweep Expo 51/54 des commits `94e9365`
(inventaire) et `642643f` (roadmap L18/L56) avait **manqué** `GAP_ANALYSIS_SPRINT1.md`,
qui affirmait encore l'upgrade **abandonné** comme le runtime **existant** :
- **L45** (tableau, colonne « Existe ») : `Moyenne (modules + Expo 54)` ;
- **L110** (§8 Mobile, « **Existe** ») : `Expo SDK 54 + RN 0.81.5`.
Or la vérité-terrain (fiche gatée `03_agents/mobile/AGENT.md` L59 « Expo SDK 51.0.0 ·
RN 0.74.5 · upgrade 54 tenté puis abandonné 2026-07-27 », vérifiée `package.json` ·
`DIRECTIVE_MOBILE_STORES` « Actif · Expo SDK 51.0.0 ») est **Expo 51 / RN 0.74.5
actuel**, l'Expo 54 étant la **cible de rebuild** (S5), pas l'existant. Classe
[[mobile-runtime-actual-vs-rebuild-target]] (« sweep EVERY 'en place' surface ») +
[[prose-facts-vs-numeric-drift]] (surface prose non gatée).
**Correctif (2 éditions chirurgicales, alignées sur roadmap L18).**
- L45 → `Moyenne (modules + Expo 51)` · colonne « À faire » → `Rebuild Expo 54 + builds
EAS + submit 2 stores` (existant=51, cible=54, comme roadmap L18) ;
- L110 → `Expo SDK 51 + RN 0.74.5 (runtime actuel · upgrade 54 tenté puis abandonné
2026-07-27)`.
- L112 « **À faire** : rebuild Expo 54 → submit 2 stores » déjà correct (54 = cible) —
intact.
**Sûreté du changement (vérifiée AVANT édition).** `check_readme_claims.sh` parse
`GAP_ANALYSIS_SPRINT1.md` UNIQUEMENT pour les **comptes d'agents** (l.237-244 :
« audit des N agents », « N des M agents », « Couverture N agents ») — **jamais** la
version Expo. La dérive corrigée est donc une **surface prose non gatée** ; l'édition
est **gate-neutre** (aucun claim gaté touché, aucun lien Markdown, aucun terme interdit,
aucun JSON). Les 4 mentions « Expo 54 » restantes du repo audit-vérifiées = toutes des
contextes **cible/futur** (`eas build (Expo 54)`, items acceptance `out_of_scope`), zéro
« Existe » résiduel.
**Vérifications.** `./run_ci.sh` → **30 PASS · 0 FAIL · 0 SKIP** (inchangé). **Zéro
nouveau module · zéro gate ajouté (#5) · aucune logique de prod ni de gate touchée.**
Aucune commande touchant au VPS (#8) · aucun `git clean` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->.
+2 -2
View File
@@ -42,7 +42,7 @@ Ce document consolide l'audit des **13 agents DTP + le cœur OTOIA** demandé au
| 5 | Frontend Console | Moyenne (waf-home + portals) | Clone 5 entités + 5 portails rôle | 🟡 Refactor | S2→S4 |
| 6 | CRM | Élevée (`crm.html` LIVE + natif) | Pipeline viz + WhatsApp + workflow vente | 🟡 Refactor | S2→S4 |
| 7 | SEO | Moyenne (mission + worker) | Trilinguisation + schema.org + hreflang | 🟡 Refactor | S6 |
| 8 | Mobile | Moyenne (modules + Expo 54) | Builds EAS + submit 2 stores | 🟡 Refactor | S5 |
| 8 | Mobile | Moyenne (modules + Expo 51) | Rebuild Expo 54 + builds EAS + submit 2 stores | 🟡 Refactor | S5 |
| 9 | IFC/Speckle | Moyenne (`ifc_to_glb.py` + serveur) | Viewer 3D intégré console | 🟡 Refactor | S3 |
| 10 | ONAPI/Legal | Moyenne (module CONFOTUR + drafts) | Automatisation dépôts P05/P07 | 🟡 Refactor | S5 |
| 11 | DevOps | Élevée (6 systemd + Gitea) | CI/CD Gitea Actions | ⚪ Ops | S1 |
@@ -107,7 +107,7 @@ et la cible plancher **4-5 semaines**.
- **Sprint** : S6.
### 8 · Mobile Agent — 🟡 Refactor (gain ~50 %)
- **Existe** : `oto_module_mobile_download.py`, `oto_module_mobile_api.py`, `deploy_mobile_rbac.sh` + `seed_mobile_rbac.py`, `oto_mobile.js`, Expo SDK 54 + RN 0.81.5, EAS + comptes Apple/Google configurés.
- **Existe** : `oto_module_mobile_download.py`, `oto_module_mobile_api.py`, `deploy_mobile_rbac.sh` + `seed_mobile_rbac.py`, `oto_mobile.js`, Expo SDK 51 + RN 0.74.5 (runtime actuel · upgrade 54 tenté puis abandonné 2026-07-27), EAS + comptes Apple/Google configurés.
- **Gap** : builds EAS iOS/Android non produits ; submit App Store (#32) + Play Store non faits.
- **À faire** : rebuild Expo 54 → submit 2 stores.
- **Sprint** : S5.
@@ -93,3 +93,39 @@ surfaces inter-artefacts neuves auditées sans dérive + reporting canal 2 (aucu
livrable net nouveau — attendu en buffer Sprint 8 saturé) ; déploiement serveur hors
périmètre (VPS · #8). État launch-readiness **inchangé et intégralement sourcé** :
22/22 qualité · 564/564 régression · 15/15 recette · 30 gate.needs verts.
---
## Addendum · Session `20260803_030641` · Sprint 5 Mobile — fiche store FR/EN/ES (livrable net)
**Tâche exécutée (livrable fonctionnel, périmètre worker).** Ajout de la **fiche store
App Store / Play** au livrable Mobile in-repo
([`mobile/app_config`](../mobile/app_config/)) — l'un des **non-bloqueurs** explicites
de `DIRECTIVE_MOBILE_STORES_20260803.md` (P3 · « Contenus descriptions · rédiger
FR/EN/ES »), sans EAS_TOKEN ni credentials (attendus de Michel · vrais bloqueurs).
**Design anti-invention (#6).** Nouvel artefact
[`out/store_listing.json`](../mobile/app_config/out/store_listing.json) (sous-titre,
description courte/longue, mots-clés · FR/EN/ES). Les **faits produit** driftables —
nombre + énumération des portails, langues — sont **INJECTÉS** depuis la surface réelle
(`role_navigation` = labels `portails_spec`, langues `seo_spec`) au build ; seuls le
style rédactionnel et les mots-clés sont authoriaux (même catégorie que `app.name`).
La copie marketing ne peut donc **pas mentir** sur les portails/langues (invariant 14).
**3 nouveaux invariants** (12 fiche=langues seo · 13 limites de caractères store
App Store/Play · 14 injection portails/langues == surface réelle) → **12→15
invariants** (déterminisme = 15). Schéma étendu (`store_listing` + `$ref`
`store_listing_lang`). 5 tests ajoutés (22 méthodes mobile · +5 → **569** au total).
**Consommateurs régénérés (byte-repro #artifact-gate).** `qa/audit_4big` (22/22 · min
100) · `qa/regression` plan+run (**569** tests) · fiches gatées `03_agents/qa` &
`03_agents/erpnext_backend` (564→569). `./run_ci.sh`**30 PASS · 0 FAIL · 0 SKIP**.
| Dimension | Valeur (après cette session) | Verdict | Source commitée |
|---|---|---|---|
| Régression (run) | **22 suites · 569 ran · 569 passés · 0 échec · 0 erreur · 0 skip** | `PASS` | [`qa/regression/out/regression_run.json`](../qa/regression/out/regression_run.json) |
| Qualité 4Big | **22/22 modules à 100/100** (seuil 95) | `PASS` | [`qa/audit_4big/out/quality_report.json`](../qa/audit_4big/out/quality_report.json) |
| Fiche store Mobile | **FR/EN/ES** · portails/langues injectés · limites store validées | `PASS` (15 invariants) | [`mobile/app_config/out/store_listing.json`](../mobile/app_config/out/store_listing.json) |
**Hors périmètre (inchangé · #8).** `eas build`/`eas submit`, assets binaires
(icône/splash 1024²), credentials Apple/Google → agent Mobile (VPS/stores).
@@ -33,6 +33,7 @@ Ce livrable ne fabrique **aucun fait** :
| Tokens de marque `#0a0a12`/`#f0b429` · thème dark | `CLAUDE.md #4` (via `branding.py`) |
| Devises USD/DOP | `CLAUDE.md #10` (via `branding.py`) |
| Expo SDK **54** | roadmap Sprint 5 l.56 |
| Fiche store : nb + énumération portails, langues | **injectés** depuis `role_navigation` (labels portails_spec) + seo (jamais recopiés) — seuls le style rédactionnel + mots-clés sont authoriaux (comme `app.name`) |
| **bundleIdentifier · package · projectId EAS · version · credentials · appleId · ascAppId** | **`null` · `a_confirmer`** — jamais fabriqués (#6/#8) |
**Ancrage de sécurité** : un onglet n'est visible que pour les **rôles RBAC exacts**
@@ -45,7 +46,7 @@ Publiciste (zéro `pip`).
```bash
python3 app_config_gen.py build # écrit out/{app_config,eas_build,role_navigation}.json + MANIFEST.json
python3 app_config_gen.py validate # schéma + 12 invariants (sort en erreur sinon)
python3 app_config_gen.py validate # schéma + 15 invariants (sort en erreur sinon)
python3 -m unittest discover -s tests -v
```
@@ -56,6 +57,10 @@ python3 -m unittest discover -s tests -v
- `eas_build.json` — objet `eas.json` (profils `development`/`preview`/`production`
+ `submit.production` avec credentials `null`).
- `role_navigation.json` — 1 onglet par portail, `roles_allowed` = surface RBAC exacte.
- `store_listing.json` — fiche store FR/EN/ES (sous-titre, description courte/longue,
mots-clés) **dérivée** : nombre + énumération des portails et langues **injectés**
depuis `role_navigation`/seo (jamais recopiés · #6) ; limites de caractères App
Store / Play validées au build.
- `MANIFEST.json` — traçabilité (comptes, résumé par portail, marque, `a_confirmer`).
Sortie **déterministe** (tri stable, aucun horodatage) → diffable + re-générable ;
@@ -64,8 +69,8 @@ validée par le **validateur maison Publiciste** contre `mobile.schema.json`.
## Résultat courant
**5 onglets** (Ventes/Construction/Achat/Compta/Direction) · **44 rôles** couverts ·
**3 langues** · **13 identifiants `a_confirmer`** (jamais fabriqués). Schéma + 12
invariants verts.
**3 langues** · **13 identifiants `a_confirmer`** (jamais fabriqués) · fiche store
FR/EN/ES dérivée. Schéma + 15 invariants verts.
## Hors périmètre worker (VPS · #8)
@@ -12,6 +12,8 @@ en :
identifiants de store `null` · a_confirmer)
- eas_build.json -> objet `eas.json` (profils build/submit, credentials `null`)
- role_navigation.json -> 1 onglet par portail, gated par la surface RBAC exacte
- store_listing.json -> fiche store FR/EN/ES (sous-titre, descriptions, mots-clés)
dérivée : portails/langues injectés depuis les sources (#6)
- MANIFEST.json -> traçabilité (comptes, résumé par portail, marque, a_confirmer)
Sous-commandes :
@@ -179,7 +181,44 @@ def _validate_bundle(bundle: dict) -> list[str]:
if c.get(k) != v:
errors.append(f"counts.{k} incohérent ({c.get(k)} != {v}).")
# Invariant 12 · déterminisme : deux builds successifs identiques.
# Invariant 12 · fiche store : couvre EXACTEMENT les langues seo (aucune inventée).
sl = bundle["store_listing"]
sl_spec = spec["store_listing"]
if sorted(sl["content"].keys()) != sorted(langs):
errors.append("store_listing.content != langues seo (FR/EN/ES).")
if sl["langues"] != list(langs):
errors.append("store_listing.langues != langues seo.")
if sl["limits"] != sl_spec["limits"]:
errors.append("store_listing.limits != spec.store_listing.limits.")
# Invariant 13 · chaque champ de fiche respecte sa limite de caractères store
# (App Store / Play — voir limits_source) et n'est jamais vide.
for lang, fields in sl["content"].items():
for champ, limit in sl_spec["limits"].items():
val = fields.get(champ)
if not val:
errors.append(f"store_listing[{lang}].{champ} vide/absent.")
elif len(val) > limit:
errors.append(
f"store_listing[{lang}].{champ} dépasse la limite store {limit} "
f"caractères (={len(val)})."
)
# Invariant 14 · ANTI-DÉRIVE : l'énumération des portails et les langues injectées
# dans chaque description longue == la surface RÉELLE (labels role_navigation ·
# langues seo) — la copie marketing ne peut pas mentir sur les portails/langues.
portails_str = " · ".join(n["label"] for n in nav)
langues_str = "/".join(lang.upper() for lang in langs)
for lang, fields in sl["content"].items():
full = fields.get("full_description", "")
if portails_str not in full:
errors.append(f"store_listing[{lang}].full_description n'injecte pas les portails réels.")
if langues_str not in full:
errors.append(f"store_listing[{lang}].full_description n'injecte pas les langues réelles.")
if "{" in full or "}" in full:
errors.append(f"store_listing[{lang}].full_description : placeholder non substitué.")
# Invariant 15 · déterminisme : deux builds successifs identiques.
again = _build_bundle()
if json.dumps(again, sort_keys=True, ensure_ascii=False) != json.dumps(bundle, sort_keys=True, ensure_ascii=False):
errors.append("build non déterministe (deux passes divergent).")
@@ -201,6 +240,7 @@ def cmd_build(args: argparse.Namespace) -> int:
_write_json(os.path.join(out, "app_config.json"), bundle["app_config"])
_write_json(os.path.join(out, "eas_build.json"), bundle["eas_build"])
_write_json(os.path.join(out, "role_navigation.json"), bundle["role_navigation"])
_write_json(os.path.join(out, "store_listing.json"), bundle["store_listing"])
_write_json(os.path.join(out, "MANIFEST.json"), bundle["manifest"])
c = bundle["manifest"]["counts"]
@@ -225,7 +265,7 @@ def cmd_validate(args: argparse.Namespace) -> int:
c = bundle["manifest"]["counts"]
print(
f"✅ Validation OK — {c['onglets']} onglets, {c['roles_couverts']} rôles, "
f"schéma + 12 invariants verts (identifiants a_confirmer)."
f"schéma + 15 invariants verts (identifiants a_confirmer · fiche store dérivée)."
)
return 0
@@ -3,7 +3,20 @@
"title": "Config app mobile OTO Enterprise OS (Expo/EAS + navigation par rôle)",
"type": "object",
"additionalProperties": false,
"required": ["app_config", "eas_build", "role_navigation", "manifest"],
"required": ["app_config", "eas_build", "role_navigation", "store_listing", "manifest"],
"definitions": {
"store_listing_lang": {
"type": "object",
"additionalProperties": false,
"required": ["subtitle", "short_description", "full_description", "keywords"],
"properties": {
"subtitle": {"type": "string", "minLength": 1},
"short_description": {"type": "string", "minLength": 1},
"full_description": {"type": "string", "minLength": 1},
"keywords": {"type": "string", "minLength": 1}
}
}
},
"properties": {
"app_config": {
"type": "object",
@@ -155,6 +168,38 @@
}
}
},
"store_listing": {
"type": "object",
"additionalProperties": false,
"required": ["generated_from", "note", "limits", "limits_source", "langues", "content"],
"properties": {
"generated_from": {"type": "string", "minLength": 1},
"note": {"type": "string", "minLength": 1},
"limits": {
"type": "object",
"additionalProperties": false,
"required": ["subtitle", "short_description", "full_description", "keywords"],
"properties": {
"subtitle": {"type": "integer", "minimum": 1},
"short_description": {"type": "integer", "minimum": 1},
"full_description": {"type": "integer", "minimum": 1},
"keywords": {"type": "integer", "minimum": 1}
}
},
"limits_source": {"type": "string", "minLength": 1},
"langues": {"type": "array", "minItems": 1, "items": {"type": "string"}},
"content": {
"type": "object",
"additionalProperties": false,
"required": ["fr", "en", "es"],
"properties": {
"fr": {"$ref": "#/definitions/store_listing_lang"},
"en": {"$ref": "#/definitions/store_listing_lang"},
"es": {"$ref": "#/definitions/store_listing_lang"}
}
}
}
},
"manifest": {
"type": "object",
"additionalProperties": false,
@@ -33,6 +33,35 @@
{"champ": "eas_build.submit.production.ios.appleTeamId", "raison": "identifiant équipe Apple — hors repo (#6/#8)"},
{"champ": "eas_build.submit.production.android.serviceAccountKeyPath", "raison": "clé de service Google Play — secret hors repo (#6/#8)"}
],
"store_listing": {
"limits": {"subtitle": 30, "short_description": 80, "full_description": 4000, "keywords": 100},
"limits_source": "App Store Connect (sous-titre 30 · mots-clés 100 · description 4000 caractères) · Google Play Console (description courte 80 · complète 4000 caractères)",
"placeholders": {
"n_portails": "nombre d'onglets = portails métier (len role_navigation)",
"portails": "labels des portails joints par ' · ' (source portails_spec via role_navigation) — jamais recopiés à la main",
"langues": "langues seo en capitales jointes par '/' (source seo_spec)"
},
"content": {
"fr": {
"subtitle": "Vos portails métier en poche",
"short_description": "L'app compagnon de votre ERP OTO : {n_portails} portails métier, sécurisés par rôle.",
"full_description": "OTO Enterprise OS est l'application mobile compagnon de la plateforme immobilière OTO.\n\nRetrouvez vos {n_portails} portails métier — {portails} — directement sur votre téléphone.\n\nSécurité par rôle (RBAC) : chaque portail n'expose que les fonctions autorisées à votre profil ; aucune donnée hors de votre périmètre.\n\nInterface luxury sombre et dorée, cohérente avec la console web. Disponible en {langues}. Devises USD et DOP.\n\nL'app ne réplique aucune logique métier : elle est la surface mobile sécurisée de votre ERP OTO Enterprise OS.",
"keywords": "immobilier,ERP,CRM,ventes,construction,chantier,compta,portail,RBAC,OTO"
},
"en": {
"subtitle": "Business portals on the go",
"short_description": "The companion app for your OTO ERP: {n_portails} role-secured business portals.",
"full_description": "OTO Enterprise OS is the mobile companion app for the OTO real-estate platform.\n\nAccess your {n_portails} business portals — {portails} — right from your phone.\n\nRole-based security (RBAC): each portal only exposes the features your profile is allowed to use; no data outside your scope.\n\nLuxury dark-and-gold interface, consistent with the web console. Available in {langues}. USD and DOP currencies.\n\nThe app replicates no business logic: it is the secure mobile surface of your OTO Enterprise OS ERP.",
"keywords": "real estate,ERP,CRM,sales,construction,site,accounting,portal,RBAC,OTO,property"
},
"es": {
"subtitle": "Tus portales en movilidad",
"short_description": "La app complementaria de tu ERP OTO: {n_portails} portales seguros por rol.",
"full_description": "OTO Enterprise OS es la aplicación móvil complementaria de la plataforma inmobiliaria OTO.\n\nAccede a tus {n_portails} portales de negocio — {portails} — desde tu teléfono.\n\nSeguridad por rol (RBAC): cada portal solo muestra las funciones que tu perfil tiene permitidas; ningún dato fuera de tu alcance.\n\nInterfaz luxury oscura y dorada, coherente con la consola web. Disponible en {langues}. Monedas USD y DOP.\n\nLa app no replica ninguna lógica de negocio: es la superficie móvil segura de tu ERP OTO Enterprise OS.",
"keywords": "inmobiliaria,ERP,CRM,ventas,construcción,obra,contabilidad,portal,RBAC,OTO"
}
}
},
"notes": [
"L'app mobile ne réplique AUCUNE logique métier : elle est la surface mobile des 5 portails rôle ERPNext (mêmes rôles que les Workspaces / le Chat OTOIA).",
"Sécurité : un onglet n'est visible que pour les rôles RBAC du portail correspondant (roles_allowed = surface RBAC exacte, ancrée sur le contrat).",
@@ -117,10 +117,52 @@ def build_navigation(contract: dict, spec: dict, portails_spec: dict) -> list[di
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 = [
@@ -180,5 +222,6 @@ def build_bundle(contract: dict, spec: dict, portails_spec: dict, langs: list[st
"app_config": app_config,
"eas_build": eas_build,
"role_navigation": nav,
"store_listing": store_listing,
"manifest": manifest,
}
@@ -0,0 +1,36 @@
{
"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": {
"subtitle": 30,
"short_description": 80,
"full_description": 4000,
"keywords": 100
},
"limits_source": "App Store Connect (sous-titre 30 · mots-clés 100 · description 4000 caractères) · Google Play Console (description courte 80 · complète 4000 caractères)",
"langues": [
"fr",
"en",
"es"
],
"content": {
"fr": {
"subtitle": "Vos portails métier en poche",
"short_description": "L'app compagnon de votre ERP OTO : 5 portails métier, sécurisés par rôle.",
"full_description": "OTO Enterprise OS est l'application mobile compagnon de la plateforme immobilière OTO.\n\nRetrouvez vos 5 portails métier — OTO Ventes · OTO Construction · OTO Achat · OTO Compta · OTO Direction — directement sur votre téléphone.\n\nSécurité par rôle (RBAC) : chaque portail n'expose que les fonctions autorisées à votre profil ; aucune donnée hors de votre périmètre.\n\nInterface luxury sombre et dorée, cohérente avec la console web. Disponible en FR/EN/ES. Devises USD et DOP.\n\nL'app ne réplique aucune logique métier : elle est la surface mobile sécurisée de votre ERP OTO Enterprise OS.",
"keywords": "immobilier,ERP,CRM,ventes,construction,chantier,compta,portail,RBAC,OTO"
},
"en": {
"subtitle": "Business portals on the go",
"short_description": "The companion app for your OTO ERP: 5 role-secured business portals.",
"full_description": "OTO Enterprise OS is the mobile companion app for the OTO real-estate platform.\n\nAccess your 5 business portals — OTO Ventes · OTO Construction · OTO Achat · OTO Compta · OTO Direction — right from your phone.\n\nRole-based security (RBAC): each portal only exposes the features your profile is allowed to use; no data outside your scope.\n\nLuxury dark-and-gold interface, consistent with the web console. Available in FR/EN/ES. USD and DOP currencies.\n\nThe app replicates no business logic: it is the secure mobile surface of your OTO Enterprise OS ERP.",
"keywords": "real estate,ERP,CRM,sales,construction,site,accounting,portal,RBAC,OTO,property"
},
"es": {
"subtitle": "Tus portales en movilidad",
"short_description": "La app complementaria de tu ERP OTO: 5 portales seguros por rol.",
"full_description": "OTO Enterprise OS es la aplicación móvil complementaria de la plataforma inmobiliaria OTO.\n\nAccede a tus 5 portales de negocio — OTO Ventes · OTO Construction · OTO Achat · OTO Compta · OTO Direction — desde tu teléfono.\n\nSeguridad por rol (RBAC): cada portal solo muestra las funciones que tu perfil tiene permitidas; ningún dato fuera de tu alcance.\n\nInterfaz luxury oscura y dorada, coherente con la consola web. Disponible en FR/EN/ES. Monedas USD y DOP.\n\nLa app no replica ninguna lógica de negocio: es la superficie móvil segura de tu ERP OTO Enterprise OS.",
"keywords": "inmobiliaria,ERP,CRM,ventas,construcción,obra,contabilidad,portal,RBAC,OTO"
}
}
}
@@ -1,10 +1,13 @@
"""Tests du générateur de config app mobile (Sprint 5 · Mobile).
Stdlib pur (unittest) — aucune dépendance pip. Couvre :
- conformité au schéma + les 12 invariants du CLI (chemin nominal) ;
- conformité au schéma + les 15 invariants du CLI (chemin nominal) ;
- la cross-cohérence RBAC ↔ portails ↔ seo (onglets, rôles, labels/icônes, langues) ;
- la fiche store dérivée (langues seo, limites de caractères, portails/langues
injectés depuis la surface réelle) ;
- l'anti-invention (#6) par injections négatives (identifiant de store fabriqué,
rôles désynchronisés de la surface RBAC, langue inventée, token de marque altéré).
rôles désynchronisés de la surface RBAC, langue inventée, token de marque altéré,
description store dépassant la limite ou désalignée des portails réels).
"""
from __future__ import annotations
@@ -113,6 +116,38 @@ class TestNominal(unittest.TestCase):
self.assertEqual(a, b)
class TestStoreListing(unittest.TestCase):
"""Fiche store : couverture langues, limites de caractères, injection sourcée."""
def setUp(self):
self.bundle = build()
self.sl = self.bundle["store_listing"]
def test_langues_egal_seo(self):
langs, _ = deps.load_langs()
self.assertEqual(sorted(self.sl["content"].keys()), sorted(langs))
self.assertEqual(self.sl["langues"], list(langs))
def test_limites_caracteres_store(self):
spec = deps.load_spec()
limits = spec["store_listing"]["limits"]
for lang, fields in self.sl["content"].items():
for champ, limit in limits.items():
self.assertTrue(fields.get(champ), f"{lang}.{champ} vide")
self.assertLessEqual(len(fields[champ]), limit, f"{lang}.{champ} > {limit}")
def test_portails_et_langues_injectes(self):
"""Anti-dérive : la copie cite les portails/langues RÉELS, pas une liste figée."""
langs, _ = deps.load_langs()
nav = self.bundle["role_navigation"]
portails_str = " · ".join(n["label"] for n in nav)
langues_str = "/".join(l.upper() for l in langs)
for lang, fields in self.sl["content"].items():
self.assertIn(portails_str, fields["full_description"])
self.assertIn(langues_str, fields["full_description"])
self.assertNotIn("{", fields["full_description"])
class TestAntiInvention(unittest.TestCase):
"""Injections négatives : chaque garde-fou doit MORDRE (invariant → erreur)."""
@@ -149,6 +184,18 @@ class TestAntiInvention(unittest.TestCase):
errs = gen._validate_bundle(bad)
self.assertTrue(any("ordre des onglets" in e for e in errs), errs)
def test_fiche_store_trop_longue_detecte(self):
bad = copy.deepcopy(self.bundle)
bad["store_listing"]["content"]["fr"]["subtitle"] = "x" * 200
errs = gen._validate_bundle(bad)
self.assertTrue(any("limite store" in e for e in errs), errs)
def test_fiche_store_portails_desalignes_detecte(self):
bad = copy.deepcopy(self.bundle)
bad["store_listing"]["content"]["en"]["full_description"] = "No portals listed here."
errs = gen._validate_bundle(bad)
self.assertTrue(any("full_description" in e for e in errs), errs)
if __name__ == "__main__":
unittest.main()
@@ -606,7 +606,7 @@
"criterion": "DOC",
"weight": 20,
"passed": true,
"evidence": "README.md (3703 octets)"
"evidence": "README.md (4261 octets)"
},
{
"criterion": "CONTRAT",
@@ -618,7 +618,7 @@
"criterion": "TESTS",
"weight": 25,
"passed": true,
"evidence": "17 méthodes test_* dans 1 fichier(s) (seuil 8)"
"evidence": "22 méthodes test_* dans 1 fichier(s) (seuil 8)"
},
{
"criterion": "CLI",
@@ -630,7 +630,7 @@
"criterion": "HANDOFF",
"weight": 20,
"passed": true,
"evidence": "MANIFEST + 3 artefact(s) JSON valides"
"evidence": "MANIFEST + 4 artefact(s) JSON valides"
}
]
},
@@ -5,7 +5,7 @@
"version": "1.0",
"verdict": "PASS",
"suites": 22,
"test_methods": 564,
"test_methods": 569,
"coverage_ok": true,
"artifacts": [
"regression_plan.json"
@@ -156,7 +156,7 @@
"in_gate": true,
"has_tests_dir": true,
"test_files": 1,
"test_methods": 17
"test_methods": 22
},
{
"id": "publiciste",
@@ -273,7 +273,7 @@
"totals": {
"suites": 22,
"test_files": 22,
"test_methods": 564,
"test_methods": 569,
"min_methods": 10,
"under_threshold": 0
},
@@ -138,9 +138,9 @@
{
"id": "mobile.app_config",
"path": "mobile/app_config",
"expected_methods": 17,
"ran": 17,
"passed": 17,
"expected_methods": 22,
"ran": 22,
"passed": 22,
"failures": 0,
"errors": 0,
"skipped": 0,
@@ -272,8 +272,8 @@
"suites": 22,
"green": 22,
"red": 0,
"ran": 564,
"passed": 564,
"ran": 569,
"passed": 569,
"failures": 0,
"errors": 0,
"skipped": 0
+1 -1
View File
@@ -60,7 +60,7 @@ Chaque agent hérite/réutilise les modules déjà présents avant de créer du
- `/opt/oto/staging/mobile_rbac/deploy_mobile_rbac.sh`
- `/opt/oto/staging/mobile_rbac/seed_mobile_rbac.py`
- `/opt/oto/static/oto_mobile.js`
- Expo SDK 54 + React 19.1.0 + React Native 0.81.5 (upgrade 2026-07)
- Expo SDK 51.0.0 + React 18.2.0 + React Native 0.74.5 + expo-router ~3.5.0 (runtime **actuel** vérifié `/opt/oto/mobile/native/package.json`) — la tentative d'upgrade Expo 54 (React 19.1.0 / RN 0.81.5) a été **abandonnée le 2026-07-27** (backup `native.bak-upgrade54-*` toujours sur ~51). Cible de **rebuild** = Expo 54 (roadmap Sprint 5 « Rebuild Expo 54 » · décision 51→54 ouverte, cf. `DIRECTIVE_MOBILE_STORES_20260803.md` P0)
- EAS builds iOS/Android · Apple Dev + Google Play Console configurés
## 9. IFC/Speckle Agent — ressources existantes