Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d0b354f81e | |||
| 3eec69a87f | |||
| 642643ffe3 | |||
| 94e9365cea |
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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% |
|
||||
|
||||
@@ -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é -->.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user