[DTP-Worker 20260803_093713] Auto exec · session 20260803_093713
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 / CRM · Financement Bancaire (gate hypothécaire RD) (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 / PIE · manifest de dépendances (Annexe 12 · V10.1) (push) Has been cancelled
CI / E2E baseline Playwright (manuel) (push) Has been cancelled
CI / Gate qualité (agrégat) (push) Has been cancelled

This commit is contained in:
Claude Code DTP Worker
2026-08-03 09:52:14 +00:00
parent 6de6dbd9fa
commit 4f31d0c8c5
25 changed files with 1441 additions and 69 deletions
+22 -1
View File
@@ -542,10 +542,31 @@ jobs:
- name: Tests générateur config app mobile
run: python3 -m unittest discover -s tests -v
# --------------------------------------------------------------------------
# PIE · manifest de dépendances (Annexe 12 · V10.1 · directive Project Identity
# Engine du 2026-08-03). La faisabilité = SSOT : ce module livre le P0 (schéma
# Project Master Data + graphe faisabilité→livrables downstream, §88). Zéro
# invention (#6) : contenu transcrit de la directive ; codes projets ancrés à
# CLAUDE.md §Projets ; annexe/version verbatim dans la directive ; modules
# downstream « gated » = répertoires de livrables réels (FS). Le worker n'écrit
# PAS le storage VPS /opt/oto/data/pie/{code}/ (#8). Schéma + 10 invariants.
# stdlib pur, zéro pip.
# --------------------------------------------------------------------------
pie-manifest-tests:
name: PIE · manifest de dépendances (Annexe 12 · V10.1)
runs-on: ubuntu-latest
defaults:
run:
working-directory: 05_deliverables_mvp/pie/manifest
steps:
- uses: actions/checkout@v4
- name: Tests générateur manifest PIE
run: python3 -m unittest discover -s tests -v
gate:
name: Gate qualité (agrégat)
runs-on: ubuntu-latest
needs: [constraints-guard, validate-json, check-docs, check-artifacts, check-regression, check-ci-integrity, check-readme-claims, publiciste-tests, rbac-tests, faisabilite-gen-tests, rbac-fixtures-tests, rbac-userperm-tests, rbac-roleprofile-tests, rbac-applyplan-tests, bancable-tests, crm-workflow-vente-tests, crm-dossier-vente-tests, crm-commissions-tests, crm-financement-bancaire-tests, fiscal-ecf-tests, frontend-portails-tests, legal-confotur-tests, qa-audit-5d-tests, seo-tests, chat-otoia-tests, qa-audit-4big-tests, demo-scenario-tests, qa-regression-tests, devops-deploy-runbook-tests, qa-acceptance-tests, mobile-app-config-tests]
needs: [constraints-guard, validate-json, check-docs, check-artifacts, check-regression, check-ci-integrity, check-readme-claims, publiciste-tests, rbac-tests, faisabilite-gen-tests, rbac-fixtures-tests, rbac-userperm-tests, rbac-roleprofile-tests, rbac-applyplan-tests, bancable-tests, crm-workflow-vente-tests, crm-dossier-vente-tests, crm-commissions-tests, crm-financement-bancaire-tests, fiscal-ecf-tests, frontend-portails-tests, legal-confotur-tests, qa-audit-5d-tests, seo-tests, chat-otoia-tests, qa-audit-4big-tests, demo-scenario-tests, qa-regression-tests, devops-deploy-runbook-tests, qa-acceptance-tests, mobile-app-config-tests, pie-manifest-tests]
steps:
- name: Résultat
run: echo "✅ Gate qualité 4Big franchi — tous les checks verts."
@@ -149,7 +149,7 @@
"file": "out/quality_report.json",
"pointer": "/coverage/ci_modules_count",
"label": "Modules sous gate qualité",
"valeur": 22,
"valeur": 24,
"resolu": true
}
]
@@ -58,7 +58,7 @@
| À dire | Chiffre | Source (preuve) |
|---|---|---|
| Seuil bloquant 4Big | 95 | `qa/audit_4big` · `out/quality_report.json#/pass_score` |
| Modules sous gate qualité | 22 | `qa/audit_4big` · `out/quality_report.json#/coverage/ci_modules_count` |
| Modules sous gate qualité | 24 | `qa/audit_4big` · `out/quality_report.json#/coverage/ci_modules_count` |
### 6. Le dossier de vente, pièce bancable finale · 2 min
@@ -89,6 +89,7 @@
"faisabilite/bancable": "contenu",
"seo": "contenu",
"demo/scenarios": "contenu",
"pie/manifest": "contenu",
"qa/audit_5d": "verification-qa",
"qa/audit_4big": "verification-qa",
"qa/regression": "verification-qa",
@@ -5,14 +5,14 @@
"self_module": "devops/deploy_runbook",
"counts": {
"phases": 7,
"modules": 23,
"modules_mapped": 23,
"modules": 24,
"modules_mapped": 24,
"confirmations": 7,
"confirmations_referenced": 7
},
"coverage": {
"ci_modules_count": 23,
"mapped_modules_count": 23,
"ci_modules_count": 24,
"mapped_modules_count": 24,
"bijective": true,
"missing_in_map": [],
"extra_in_map": [],
@@ -188,6 +188,10 @@
"module": "faisabilite/generator",
"ci_job": "faisabilite-gen-tests"
},
{
"module": "pie/manifest",
"ci_job": "pie-manifest-tests"
},
{
"module": "publiciste",
"ci_job": "publiciste-tests"
@@ -0,0 +1,4 @@
# Caches Python
__pycache__/
*.pyc
# Le dossier out/ EST commité (manifest de dépendances versionné) — voir README.
@@ -0,0 +1,78 @@
# PIE · Manifest de dépendances · Annexe 12 · V10.1
**Directive** : [`DIRECTIVE_PIE_PROJECT_IDENTITY_ENGINE_20260803.md`](../../../DIRECTIVE_PIE_PROJECT_IDENTITY_ENGINE_20260803.md)
(Michel · 2026-08-03) · **Auto-score 4Big : 96/100** (≥95 requis · CLAUDE.md #5).
## Rôle
Le **Project Identity Engine (PIE)** pose la faisabilité comme **Project DNA ·
source unique de vérité (SSOT)** : aucun livrable downstream (brochure, kit
banquier, site, app, contrat…) ne doit être recréé à la main si la donnée existe
déjà dans la faisabilité, et toute modification de la faisabilité régénère
sélectivement les livrables concernés.
Ce module livre le **P0** de la directive : le **schéma Project Master Data** +
le **manifest de dépendances** (§88 de la directive) — le graphe
`faisabilité → livrables downstream` versionnable, cross-vérifié.
Ce que ce worker **ne fait pas** (côté agents · VPS · CLAUDE.md #8) : le storage
réel `/opt/oto/data/pie/{code}/`, la génération effective des Brand Books / logos
(Flux) / PDF, et l'écriture dans ERPNext / Speckle. Ici : uniquement le contrat
de dépendances, à consommer par ces agents.
## Sortie (`out/`, commité)
| Fichier | Contenu |
|---|---|
| `pie_manifest.json` | 12 groupes Master Data · 4 règles de synchronisation · 13 livrables downstream · workflow 10 étapes · 9 marques · storage layout |
| `MANIFEST.json` | Traçabilité : sources d'ancrage + comptes dérivés + références des modules gated |
## Contenu (transcrit VERBATIM de la directive — zéro invention · #6)
- **Project Master Data** — 12 groupes (§16-29) : identité, localisation,
architecture, unités, budget, marketing, BIM, finances, juridique, images,
services, calendrier.
- **Règles de synchronisation** — 4 déclencheurs (§82-86) :
`prix` → brochures · kit banquier · contrats · site · app ·
`typologie` → plans · rendus · fiche unité · BIM ·
`amenities` → brochures · site · slogan · brand book ·
`livraison` → calendrier · brochures · trackers chantier.
- **Workflow** — 10 étapes (§108-133), de la validation faisabilité à la publication.
- **Marques** — 9 projets (§90-102), codes ancrés à `CLAUDE.md §Projets`.
### Downstream déjà gated par un module MVP (4/13)
| Livrable | Module implémenteur |
|---|---|
| Kit banquier | [`faisabilite/bancable`](../../faisabilite/bancable/README.md) |
| Contrats types | [`legal/confotur`](../../legal/confotur/README.md) |
| Page projet (site) | [`publiciste`](../../publiciste/README.md) |
| Section app mobile | [`mobile/app_config`](../../mobile/app_config/README.md) |
Les 9 autres (brand book, logos, brochures PDF, plans, rendus, fiche unité, BIM,
slogan, calendrier, trackers chantier) sont marqués `a_construire` (P1-P4 de la
directive) — aucun sur-engagement.
## Invariants (schéma + 10, ré-affichés à l'exécution)
1. Schéma draft-07 de `pie_manifest.json`.
2. 12 groupes Master Data, clés uniques == ensemble canonique (§16-29).
3. 4 règles sync, triggers == `{prix, typologie, amenities, livraison}` (§82-86).
4. Intégrité référentielle : chaque `downstream` + `trigger_group` pointe du réel.
5. Registre downstream : `statut` cohérent (`gated` ⇔ module non nul).
6. Chaque module downstream non nul **est un répertoire de livrable réel** (FS).
7. Workflow = étapes `1..N` contiguës et uniques.
8. Codes marques ⊆ `CLAUDE.md §Projets` (ancrage constitution).
9. `annexe` (12) + `version_workflow` (V10.1) **verbatim dans la directive**.
10. Storage layout hors-repo (`/opt/oto/data/pie/…`) — jamais le `out/` du module (#8).
## Usage
```bash
python3 pie_manifest_gen.py validate # schéma + 10 invariants, sans écrire
python3 pie_manifest_gen.py build # écrit out/pie_manifest.json + out/MANIFEST.json
python3 -m unittest discover -s tests -v
```
Sortie **déterministe** (tri stable, aucun horodatage) → re-générable
byte-identique, gaté par `ci/check_artifacts.sh`.
@@ -0,0 +1,37 @@
{
"generated_from": "pie_spec.json",
"claude_md_source": "CLAUDE.md",
"directive_source": "DIRECTIVE_PIE_PROJECT_IDENTITY_ENGINE_20260803.md",
"engine": "OTO Project Identity Engine (PIE)",
"annexe": 12,
"version_workflow": "V10.1",
"ssot": "faisabilite",
"counts": {
"master_data_groups": 12,
"downstream_total": 13,
"downstream_gated": 4,
"downstream_a_construire": 9,
"sync_rules": 4,
"workflow_steps": 10,
"brands": 9
},
"modules_gated_references": [
{
"cle": "app",
"module": "mobile/app_config"
},
{
"cle": "contrats",
"module": "legal/confotur"
},
{
"cle": "kit_banquier",
"module": "faisabilite/bancable"
},
{
"cle": "site",
"module": "publiciste"
}
],
"note_anti_invention": "Toute donnée est transcrite de la directive PIE (SSOT · #6) : aucune valeur projet (prix, surfaces, marges) n'est fabriquée ici — le PIE les EXTRAIT de la faisabilité validée côté VPS (/opt/oto/data/pie/{code}/, jamais écrit par ce worker · #8). Les codes projets sont ancrés à CLAUDE.md §Projets ; annexe/version à la directive ; les modules downstream « gated » à des répertoires de livrables réels."
}
@@ -0,0 +1,369 @@
{
"engine": "OTO Project Identity Engine (PIE)",
"annexe": 12,
"version_workflow": "V10.1",
"ssot": "faisabilite",
"source_spec": "pie_spec.json",
"master_data_groups": [
{
"cle": "architecture",
"libelle": "Architecture",
"champs": [
"style",
"matériaux",
"ambiances",
"palette matériaux"
]
},
{
"cle": "bim",
"libelle": "BIM",
"champs": [
"IFC/GLB",
"plans",
"coupes",
"panoramas"
]
},
{
"cle": "budget",
"libelle": "Budget",
"champs": [
"CAPEX",
"coûts construction",
"marges",
"break-even",
"apport promoteur"
]
},
{
"cle": "calendrier",
"libelle": "Calendrier",
"champs": [
"phasage travaux",
"jalons",
"livraison"
]
},
{
"cle": "finances",
"libelle": "Finances",
"champs": [
"plan de financement",
"fideicomiso",
"CONFOTUR",
"exonérations"
]
},
{
"cle": "identite",
"libelle": "Identité",
"champs": [
"nom",
"type",
"concept",
"vision",
"mission",
"valeurs"
]
},
{
"cle": "images",
"libelle": "Images",
"champs": [
"rendus extérieurs",
"intérieurs",
"aériens",
"ambiances"
]
},
{
"cle": "juridique",
"libelle": "Juridique",
"champs": [
"titres",
"permis",
"licences",
"contrats types"
]
},
{
"cle": "localisation",
"libelle": "Localisation",
"champs": [
"adresse exacte",
"coordonnées GPS",
"province",
"ville",
"quartier"
]
},
{
"cle": "marketing",
"libelle": "Marketing",
"champs": [
"positionnement",
"clientèle cible",
"USPs",
"canaux"
]
},
{
"cle": "services",
"libelle": "Services",
"champs": [
"amenities",
"conciergerie",
"services inclus"
]
},
{
"cle": "unites",
"libelle": "Unités",
"champs": [
"typologies",
"surfaces",
"orientations",
"quantités",
"prix",
"disponibilités"
]
}
],
"downstream_registry": [
{
"cle": "app",
"libelle": "Section app mobile (portail Client)",
"module": "mobile/app_config",
"statut": "gated"
},
{
"cle": "bim",
"libelle": "IFC + GLB + Speckle",
"module": null,
"statut": "a_construire"
},
{
"cle": "brand_book",
"libelle": "Brand Book PDF (~30-50 pages)",
"module": null,
"statut": "a_construire"
},
{
"cle": "brochures",
"libelle": "Brochures PDF FR/EN/ES",
"module": null,
"statut": "a_construire"
},
{
"cle": "calendrier",
"libelle": "Calendrier / phasage travaux",
"module": null,
"statut": "a_construire"
},
{
"cle": "contrats",
"libelle": "Contrats types (Promesa · Fideicomiso · HOA)",
"module": "legal/confotur",
"statut": "gated"
},
{
"cle": "fiche_unite",
"libelle": "Fiche unité",
"module": null,
"statut": "a_construire"
},
{
"cle": "kit_banquier",
"libelle": "Kit banquier PDF trilingue",
"module": "faisabilite/bancable",
"statut": "gated"
},
{
"cle": "plans",
"libelle": "Plans (générés depuis BIM)",
"module": null,
"statut": "a_construire"
},
{
"cle": "rendus",
"libelle": "Rendus extérieurs/intérieurs/aériens",
"module": null,
"statut": "a_construire"
},
{
"cle": "site",
"libelle": "Page projet vente.otov7.com/projets/{slug}/",
"module": "publiciste",
"statut": "gated"
},
{
"cle": "slogan",
"libelle": "Slogan / tagline",
"module": null,
"statut": "a_construire"
},
{
"cle": "trackers_chantier",
"libelle": "Trackers chantier",
"module": null,
"statut": "a_construire"
}
],
"sync_rules": [
{
"trigger": "amenities",
"trigger_group": "services",
"downstream": [
"brochures",
"site",
"slogan",
"brand_book"
]
},
{
"trigger": "livraison",
"trigger_group": "calendrier",
"downstream": [
"calendrier",
"brochures",
"trackers_chantier"
]
},
{
"trigger": "prix",
"trigger_group": "unites",
"downstream": [
"brochures",
"kit_banquier",
"contrats",
"site",
"app"
]
},
{
"trigger": "typologie",
"trigger_group": "unites",
"downstream": [
"plans",
"rendus",
"fiche_unite",
"bim"
]
}
],
"workflow": [
{
"etape": 1,
"libelle": "Validation Faisabilité (par OTO Auditeur Faisabilité IA)"
},
{
"etape": 2,
"libelle": "Extraction Project Master Data (master.json)"
},
{
"etape": 3,
"libelle": "Brand Book génération"
},
{
"etape": 4,
"libelle": "Logos génération (Flux + variantes SVG)"
},
{
"etape": 5,
"libelle": "Thème appliqué (CSS variables + design tokens)"
},
{
"etape": 6,
"libelle": "Documents commerciaux (brochures · kit banquier · Trousse · contrats)"
},
{
"etape": 7,
"libelle": "Portails web (page projet · viewer 3D · portail client)"
},
{
"etape": 8,
"libelle": "Applications mobiles (section projet + tab dédié)"
},
{
"etape": 9,
"libelle": "Synchronisation (ERPNext · CRM · BIM · OTO AI · OFAC/PEP/FinCEN)"
},
{
"etape": 10,
"libelle": "Publication (site · app · portail admin · ERPNext · Speckle · GSC)"
}
],
"brands": [
{
"code": "P01",
"marque": "Coralis",
"localisation": "San Cristóbal"
},
{
"code": "P02",
"marque": "Coral del Sur",
"localisation": "San Cristóbal"
},
{
"code": "P03",
"marque": "Nakua",
"localisation": "Bávaro"
},
{
"code": "P04",
"marque": "Xamaná Cantiles",
"localisation": "Palenque"
},
{
"code": "P05",
"marque": "Las Colinas",
"localisation": "Najayo"
},
{
"code": "P06",
"marque": "Coco Real",
"localisation": "Punta Cana Bávaro"
},
{
"code": "P07",
"marque": "Aqua Terra",
"localisation": "Las Terrenas"
},
{
"code": "P08",
"marque": "Fasano Espirilla",
"localisation": "Santo Domingo Espirilla"
},
{
"code": "P09",
"marque": "Résidence Gazcue",
"localisation": "Santo Domingo centre historique"
}
],
"storage_layout": {
"racine": "/opt/oto/data/pie/{code}/",
"artefacts": [
{
"chemin": "master.json",
"role": "Project Master Data (SSOT extrait de la faisabilité)"
},
{
"chemin": "brand/",
"role": "Brand Book assets"
},
{
"chemin": "logos/",
"role": "Logos SVG/PNG + variantes"
},
{
"chemin": "documents/",
"role": "PDF générés (brochures · kit banquier · contrats)"
},
{
"chemin": "manifest.json",
"role": "Manifest de dépendances + checksums (régénération sélective)"
}
]
}
}
@@ -0,0 +1,107 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://oto.dtp/schemas/pie/pie_manifest.schema.json",
"title": "PIE · manifest de dépendances (pie_manifest.json)",
"description": "Sortie déterministe du Project Identity Engine (Annexe 12 · V10.1). Décrit le graphe de dépendances faisabilité(SSOT)→livrables downstream. Zéro invention (#6) : contenus transcrits de la directive PIE ; codes projets ancrés à CLAUDE.md §Projets.",
"type": "object",
"required": ["engine", "annexe", "version_workflow", "ssot", "source_spec", "master_data_groups", "downstream_registry", "sync_rules", "workflow", "brands", "storage_layout"],
"additionalProperties": false,
"properties": {
"engine": { "type": "string", "minLength": 1 },
"annexe": { "type": "integer" },
"version_workflow": { "type": "string", "minLength": 1 },
"ssot": { "type": "string", "minLength": 1 },
"source_spec": { "type": "string", "minLength": 1 },
"master_data_groups": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["cle", "libelle", "champs"],
"additionalProperties": false,
"properties": {
"cle": { "type": "string", "minLength": 1 },
"libelle": { "type": "string", "minLength": 1 },
"champs": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }
}
}
},
"downstream_registry": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["cle", "libelle", "module", "statut"],
"additionalProperties": false,
"properties": {
"cle": { "type": "string", "minLength": 1 },
"libelle": { "type": "string", "minLength": 1 },
"module": { "type": ["string", "null"] },
"statut": { "type": "string", "enum": ["gated", "a_construire"] }
}
}
},
"sync_rules": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["trigger", "trigger_group", "downstream"],
"additionalProperties": false,
"properties": {
"trigger": { "type": "string", "minLength": 1 },
"trigger_group": { "type": "string", "minLength": 1 },
"downstream": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }
}
}
},
"workflow": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["etape", "libelle"],
"additionalProperties": false,
"properties": {
"etape": { "type": "integer", "minimum": 1 },
"libelle": { "type": "string", "minLength": 1 }
}
}
},
"brands": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["code", "marque", "localisation"],
"additionalProperties": false,
"properties": {
"code": { "type": "string", "pattern": "^P0[1-9]$" },
"marque": { "type": "string", "minLength": 1 },
"localisation": { "type": "string", "minLength": 1 }
}
}
},
"storage_layout": {
"type": "object",
"required": ["racine", "artefacts"],
"additionalProperties": false,
"properties": {
"racine": { "type": "string", "minLength": 1 },
"artefacts": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["chemin", "role"],
"additionalProperties": false,
"properties": {
"chemin": { "type": "string", "minLength": 1 },
"role": { "type": "string", "minLength": 1 }
}
}
}
}
}
}
}
@@ -0,0 +1,248 @@
#!/usr/bin/env python3
"""Générateur du manifest de dépendances PIE · Annexe 12 · V10.1.
Directive : `DIRECTIVE_PIE_PROJECT_IDENTITY_ENGINE_20260803.md` (Michel · 2026-08-03).
« La faisabilité devient le Project DNA · source unique de vérité (SSOT). »
Ce module livre le **P0** du PIE : le schéma Project Master Data + le manifest de
dépendances (§88 de la directive) qui liste, pour chaque déclencheur de la
faisabilité, les livrables downstream à régénérer — et lesquels sont DÉJÀ gated
par un module MVP existant vs restent à construire (P1-P4).
Ce worker n'écrit JAMAIS sur le VPS (#8) : le storage réel du PIE
(`/opt/oto/data/pie/{code}/`) et la génération effective des Brand Books / logos /
PDF restent côté agents (Flux, ERPNext, OTOIA). Ici : uniquement le contrat de
dépendances versionnable, cross-vérifié contre CLAUDE.md, la directive et le FS.
Sous-commandes :
build [-o OUT] → écrit pie_manifest.json + MANIFEST.json
validate → (re)génère en mémoire, valide schéma + 10 invariants ;
sort en erreur sinon.
Sortie déterministe (tri stable, aucun horodatage) → diffable + re-générable.
"""
from __future__ import annotations
import argparse
import json
import os
import sys
_HERE = os.path.dirname(os.path.abspath(__file__))
_DELIVERABLES = os.path.normpath(os.path.join(_HERE, "..", "..")) # 05_deliverables_mvp/
sys.path.insert(0, _HERE)
sys.path.insert(0, os.path.join(_DELIVERABLES, "publiciste"))
from pielib import builder, deps # noqa: E402
from lib import validator as maison # type: ignore # noqa: E402
_SPEC_PATH = os.path.join(_HERE, "pie_spec.json")
_SCHEMA_PATH = os.path.join(_HERE, "pie.schema.json")
_DEFAULT_OUT = os.path.join(_HERE, "out")
# Ensemble documenté des déclencheurs de synchronisation (directive §82-86).
_DECLENCHEURS = {"prix", "typologie", "amenities", "livraison"}
# Clés canoniques des 12 groupes Project Master Data (directive §16-29).
_GROUPES_CANONIQUES = {
"identite", "localisation", "architecture", "unites", "budget", "marketing",
"bim", "finances", "juridique", "images", "services", "calendrier",
}
def _eprint(*args) -> None:
print(*args, file=sys.stderr)
def _load(path: str) -> dict:
with open(path, encoding="utf-8") as fh:
return json.load(fh)
def _write_json(path: str, data) -> None:
with open(path, "w", encoding="utf-8") as fh:
json.dump(data, fh, ensure_ascii=False, indent=2)
fh.write("\n")
def _validate(bundle: dict) -> list[str]:
"""Schéma de sortie + 10 invariants (SSOT · ancrages CLAUDE.md/directive/FS).
Ledger des invariants (le compte « 10 » est ré-affiché à l'exécution) :
1 · schéma draft-07 de pie_manifest.json
2 · 12 groupes Master Data, clés uniques == ensemble canonique (§16-29)
3 · 4 règles sync, triggers == {prix, typologie, amenities, livraison}
4 · chaque downstream référencé existe dans le registre ; trigger_group réel
5 · registre downstream : statut cohérent avec présence d'un module
6 · chaque module downstream non nul est un répertoire de livrable réel (FS)
7 · workflow = étapes 1..N contiguës et uniques
8 · codes marques ⊆ CLAUDE.md §Projets (ancrage constitution)
9 · annexe (12) + version_workflow (V10.1) verbatim dans la directive
10 · storage layout hors-repo (/opt/oto/data/...) — jamais le out/ du module (#8)
"""
schema = _load(_SCHEMA_PATH)
errors: list[str] = []
man = bundle["manifest"]
trace = bundle["traceability"]
# 1 · schéma de sortie.
errors += list(maison.validate(man, schema))
# 2 · Project Master Data : 12 groupes, clés uniques, == ensemble canonique.
groups = man["master_data_groups"]
cles = [g["cle"] for g in groups]
if len(cles) != 12:
errors.append(f"master_data_groups : {len(cles)} groupes (attendu 12 · §16-29)")
if len(set(cles)) != len(cles):
errors.append("master_data_groups : clés dupliquées")
if set(cles) != _GROUPES_CANONIQUES:
manquants = _GROUPES_CANONIQUES - set(cles)
surplus = set(cles) - _GROUPES_CANONIQUES
errors.append(f"master_data_groups : clés ≠ ensemble canonique "
f"(manquants={sorted(manquants)} · surplus={sorted(surplus)})")
# 3 · règles de synchronisation : 4 triggers == ensemble documenté (§82-86).
triggers = [r["trigger"] for r in man["sync_rules"]]
if set(triggers) != _DECLENCHEURS:
errors.append(f"sync_rules : triggers {sorted(set(triggers))}"
f"{sorted(_DECLENCHEURS)} (§82-86)")
if len(set(triggers)) != len(triggers):
errors.append("sync_rules : trigger dupliqué")
# 4 · intégrité référentielle : downstream + trigger_group pointent du réel.
registry_keys = {d["cle"] for d in man["downstream_registry"]}
group_keys = set(cles)
for r in man["sync_rules"]:
if r["trigger_group"] not in group_keys:
errors.append(f"sync_rule {r['trigger']!r} : trigger_group "
f"{r['trigger_group']!r} absent des Master Data groups")
for t in r["downstream"]:
if t not in registry_keys:
errors.append(f"sync_rule {r['trigger']!r} : downstream {t!r} "
f"absent du downstream_registry")
# 5 · registre downstream : statut cohérent (gated ⇔ module non nul).
for d in man["downstream_registry"]:
attendu = "gated" if d["module"] else "a_construire"
if d["statut"] != attendu:
errors.append(f"downstream {d['cle']!r} : statut {d['statut']!r} "
f"incohérent avec module {d['module']!r}")
# 6 · chaque module downstream non nul EST un répertoire de livrable réel (FS).
for d in man["downstream_registry"]:
if d["module"] and not deps.module_exists(d["module"]):
errors.append(f"downstream {d['cle']!r} : module {d['module']!r} "
f"introuvable sous 05_deliverables_mvp/")
# 7 · workflow : étapes 1..N contiguës et uniques.
etapes = [s["etape"] for s in man["workflow"]]
if etapes != list(range(1, len(etapes) + 1)):
errors.append(f"workflow : étapes non contiguës/desordonnées : {etapes}")
# 8 · codes marques ⊆ CLAUDE.md §Projets (ancrage constitution · #6).
codes = {b["code"] for b in man["brands"]}
canoniques = deps.claude_md_project_codes()
if not canoniques:
errors.append("CLAUDE.md §Projets : aucun code Pxx lu (ancrage impossible)")
elif not codes <= canoniques:
errors.append(f"brands : codes {sorted(codes - canoniques)} absents de "
f"CLAUDE.md §Projets")
# 9 · annexe + version verbatim dans la directive (ancrage directive · #6).
decl = deps.directive_declares(trace["directive_source"],
f"Annexe {man['annexe']}", man["version_workflow"])
for needle, present in decl.items():
if not present:
errors.append(f"directive {trace['directive_source']!r} : "
f"« {needle} » introuvable (ancrage rompu)")
# 10 · storage layout hors-repo : aucun artefact PIE n'atterrit dans le out/
# du module (le worker n'écrit JAMAIS le storage VPS · #8).
racine = man["storage_layout"]["racine"]
if not racine.startswith("/opt/oto/data/pie/"):
errors.append(f"storage_layout.racine {racine!r} hors /opt/oto/data/pie/")
if os.path.abspath(deps.module_out_dir()).startswith(os.path.abspath("/opt/oto/data")):
errors.append("out/ du module confondu avec le storage VPS (#8)")
# Traçabilité : comptes du manifeste cohérents avec les tableaux.
c = trace["counts"]
if c["master_data_groups"] != len(groups):
errors.append("counts.master_data_groups incohérent")
if c["downstream_total"] != len(man["downstream_registry"]):
errors.append("counts.downstream_total incohérent")
if c["sync_rules"] != len(man["sync_rules"]):
errors.append("counts.sync_rules incohérent")
if c["workflow_steps"] != len(man["workflow"]):
errors.append("counts.workflow_steps incohérent")
if c["brands"] != len(man["brands"]):
errors.append("counts.brands incohérent")
gated = sum(1 for d in man["downstream_registry"] if d["statut"] == "gated")
if c["downstream_gated"] != gated:
errors.append("counts.downstream_gated incohérent")
return errors
def _build() -> dict:
return builder.build_bundle(_load(_SPEC_PATH))
def cmd_build(args: argparse.Namespace) -> int:
bundle = _build()
errors = _validate(bundle)
if errors:
_eprint("❌ Manifest invalide — génération refusée (anti-régression) :")
for e in errors:
_eprint(f" - {e}")
return 1
out = os.path.abspath(args.out)
os.makedirs(out, exist_ok=True)
_write_json(os.path.join(out, "pie_manifest.json"), bundle["manifest"])
_write_json(os.path.join(out, "MANIFEST.json"), bundle["traceability"])
c = bundle["traceability"]["counts"]
print(f"✅ Manifest PIE généré dans {out}")
print(f" pie_manifest.json : {c['master_data_groups']} groupes Master Data · "
f"{c['sync_rules']} règles sync · {c['downstream_total']} livrables downstream "
f"({c['downstream_gated']} gated · {c['downstream_a_construire']} à construire) · "
f"{c['workflow_steps']} étapes workflow · {c['brands']} marques")
print(" ⚠ Storage PIE réel (/opt/oto/data/pie/{code}/) + génération Brand Book / "
"logos / PDF côté agents (Flux · ERPNext · OTOIA · VPS · #8).")
return 0
def cmd_validate(args: argparse.Namespace) -> int:
bundle = _build()
errors = _validate(bundle)
if errors:
_eprint("❌ Validation KO :")
for e in errors:
_eprint(f" - {e}")
return 1
c = bundle["traceability"]["counts"]
print(f"✅ Validation OK — PIE Annexe {bundle['manifest']['annexe']} · "
f"{bundle['manifest']['version_workflow']} : schéma + 10 invariants verts "
f"({c['downstream_total']} downstream · {c['brands']} marques).")
return 0
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description="Générateur du manifest de dépendances PIE (Annexe 12 · V10.1).")
sub = p.add_subparsers(dest="cmd", required=True)
pb = sub.add_parser("build", help="génère pie_manifest.json / MANIFEST.json")
pb.add_argument("-o", "--out", default=_DEFAULT_OUT, help="dossier de sortie (défaut: ./out)")
pb.set_defaults(func=cmd_build)
pv = sub.add_parser("validate", help="valide le bundle (schéma + 10 invariants) sans écrire")
pv.set_defaults(func=cmd_validate)
args = p.parse_args(argv)
return args.func(args)
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,75 @@
{
"engine": "OTO Project Identity Engine (PIE)",
"annexe": 12,
"version_workflow": "V10.1",
"ssot": "faisabilite",
"directive": "DIRECTIVE_PIE_PROJECT_IDENTITY_ENGINE_20260803.md",
"master_data_groups": [
{ "cle": "identite", "libelle": "Identité", "champs": ["nom", "type", "concept", "vision", "mission", "valeurs"] },
{ "cle": "localisation","libelle": "Localisation","champs": ["adresse exacte", "coordonnées GPS", "province", "ville", "quartier"] },
{ "cle": "architecture","libelle": "Architecture","champs": ["style", "matériaux", "ambiances", "palette matériaux"] },
{ "cle": "unites", "libelle": "Unités", "champs": ["typologies", "surfaces", "orientations", "quantités", "prix", "disponibilités"] },
{ "cle": "budget", "libelle": "Budget", "champs": ["CAPEX", "coûts construction", "marges", "break-even", "apport promoteur"] },
{ "cle": "marketing", "libelle": "Marketing", "champs": ["positionnement", "clientèle cible", "USPs", "canaux"] },
{ "cle": "bim", "libelle": "BIM", "champs": ["IFC/GLB", "plans", "coupes", "panoramas"] },
{ "cle": "finances", "libelle": "Finances", "champs": ["plan de financement", "fideicomiso", "CONFOTUR", "exonérations"] },
{ "cle": "juridique", "libelle": "Juridique", "champs": ["titres", "permis", "licences", "contrats types"] },
{ "cle": "images", "libelle": "Images", "champs": ["rendus extérieurs", "intérieurs", "aériens", "ambiances"] },
{ "cle": "services", "libelle": "Services", "champs": ["amenities", "conciergerie", "services inclus"] },
{ "cle": "calendrier", "libelle": "Calendrier", "champs": ["phasage travaux", "jalons", "livraison"] }
],
"downstream_registry": [
{ "cle": "brochures", "libelle": "Brochures PDF FR/EN/ES", "module": null },
{ "cle": "kit_banquier", "libelle": "Kit banquier PDF trilingue", "module": "faisabilite/bancable" },
{ "cle": "contrats", "libelle": "Contrats types (Promesa · Fideicomiso · HOA)","module": "legal/confotur" },
{ "cle": "site", "libelle": "Page projet vente.otov7.com/projets/{slug}/","module": "publiciste" },
{ "cle": "app", "libelle": "Section app mobile (portail Client)", "module": "mobile/app_config" },
{ "cle": "plans", "libelle": "Plans (générés depuis BIM)", "module": null },
{ "cle": "rendus", "libelle": "Rendus extérieurs/intérieurs/aériens", "module": null },
{ "cle": "fiche_unite", "libelle": "Fiche unité", "module": null },
{ "cle": "bim", "libelle": "IFC + GLB + Speckle", "module": null },
{ "cle": "slogan", "libelle": "Slogan / tagline", "module": null },
{ "cle": "brand_book", "libelle": "Brand Book PDF (~30-50 pages)", "module": null },
{ "cle": "calendrier", "libelle": "Calendrier / phasage travaux", "module": null },
{ "cle": "trackers_chantier", "libelle": "Trackers chantier", "module": null }
],
"sync_rules": [
{ "trigger": "prix", "trigger_group": "unites", "downstream": ["brochures", "kit_banquier", "contrats", "site", "app"] },
{ "trigger": "typologie", "trigger_group": "unites", "downstream": ["plans", "rendus", "fiche_unite", "bim"] },
{ "trigger": "amenities", "trigger_group": "services", "downstream": ["brochures", "site", "slogan", "brand_book"] },
{ "trigger": "livraison", "trigger_group": "calendrier", "downstream": ["calendrier", "brochures", "trackers_chantier"] }
],
"workflow": [
{ "etape": 1, "libelle": "Validation Faisabilité (par OTO Auditeur Faisabilité IA)" },
{ "etape": 2, "libelle": "Extraction Project Master Data (master.json)" },
{ "etape": 3, "libelle": "Brand Book génération" },
{ "etape": 4, "libelle": "Logos génération (Flux + variantes SVG)" },
{ "etape": 5, "libelle": "Thème appliqué (CSS variables + design tokens)" },
{ "etape": 6, "libelle": "Documents commerciaux (brochures · kit banquier · Trousse · contrats)" },
{ "etape": 7, "libelle": "Portails web (page projet · viewer 3D · portail client)" },
{ "etape": 8, "libelle": "Applications mobiles (section projet + tab dédié)" },
{ "etape": 9, "libelle": "Synchronisation (ERPNext · CRM · BIM · OTO AI · OFAC/PEP/FinCEN)" },
{ "etape": 10, "libelle": "Publication (site · app · portail admin · ERPNext · Speckle · GSC)" }
],
"brands": [
{ "code": "P01", "marque": "Coralis", "localisation": "San Cristóbal" },
{ "code": "P02", "marque": "Coral del Sur", "localisation": "San Cristóbal" },
{ "code": "P03", "marque": "Nakua", "localisation": "Bávaro" },
{ "code": "P04", "marque": "Xamaná Cantiles", "localisation": "Palenque" },
{ "code": "P05", "marque": "Las Colinas", "localisation": "Najayo" },
{ "code": "P06", "marque": "Coco Real", "localisation": "Punta Cana Bávaro" },
{ "code": "P07", "marque": "Aqua Terra", "localisation": "Las Terrenas" },
{ "code": "P08", "marque": "Fasano Espirilla", "localisation": "Santo Domingo Espirilla" },
{ "code": "P09", "marque": "Résidence Gazcue", "localisation": "Santo Domingo centre historique" }
],
"storage_layout": {
"racine": "/opt/oto/data/pie/{code}/",
"artefacts": [
{ "chemin": "master.json", "role": "Project Master Data (SSOT extrait de la faisabilité)" },
{ "chemin": "brand/", "role": "Brand Book assets" },
{ "chemin": "logos/", "role": "Logos SVG/PNG + variantes" },
{ "chemin": "documents/", "role": "PDF générés (brochures · kit banquier · contrats)" },
{ "chemin": "manifest.json", "role": "Manifest de dépendances + checksums (régénération sélective)" }
]
}
}
@@ -0,0 +1,5 @@
"""PIE · manifest de dépendances · bibliothèque interne.
- deps → ancrages hors-module (CLAUDE.md §Projets · directive PIE · FS des livrables)
- builder → assemblage déterministe du manifest + du MANIFEST de traçabilité
"""
@@ -0,0 +1,100 @@
"""Assemblage déterministe du manifest PIE depuis `pie_spec.json`.
Entrée : le contrat PIE (taxonomie Project Master Data, registre downstream,
règles de synchronisation, workflow 10 étapes, marques, storage layout) —
transcrit VERBATIM de la directive PIE (SSOT · #6, aucune valeur inventée).
Sortie : un bundle déterministe (tri stable, aucun horodatage) → reproductible
bit-à-bit à contrat constant, donc diffable et re-générable en CI
([[artifact-reproducibility-gate]]).
Le builder n'ajoute AUCUN chiffre propre à OTO : il résout le `statut` de chaque
livrable downstream (« gated » si un module de livrado réel l'implémente déjà,
sinon « a_construire ») et un manifeste de traçabilité (comptes dérivés).
"""
from __future__ import annotations
from typing import Any
from . import deps
def _statut(module: str | None) -> str:
"""« gated » si un module réel implémente déjà le downstream, sinon « a_construire »."""
return "gated" if module else "a_construire"
def build_bundle(spec: dict) -> dict[str, Any]:
"""Transforme le contrat PIE en manifest de dépendances + manifeste de traçabilité."""
groups = sorted(spec["master_data_groups"], key=lambda g: g["cle"])
registry = [
{
"cle": d["cle"],
"libelle": d["libelle"],
"module": d["module"],
"statut": _statut(d["module"]),
}
for d in sorted(spec["downstream_registry"], key=lambda d: d["cle"])
]
sync_rules = [
{
"trigger": r["trigger"],
"trigger_group": r["trigger_group"],
"downstream": list(r["downstream"]), # ordre directive préservé (§82-86)
}
for r in sorted(spec["sync_rules"], key=lambda r: r["trigger"])
]
workflow = sorted(spec["workflow"], key=lambda s: s["etape"])
brands = sorted(spec["brands"], key=lambda b: b["code"])
manifest_out = {
"engine": spec["engine"],
"annexe": spec["annexe"],
"version_workflow": spec["version_workflow"],
"ssot": spec["ssot"],
"source_spec": "pie_spec.json",
"master_data_groups": groups,
"downstream_registry": registry,
"sync_rules": sync_rules,
"workflow": workflow,
"brands": brands,
"storage_layout": spec["storage_layout"],
}
gated = [d for d in registry if d["statut"] == "gated"]
a_construire = [d for d in registry if d["statut"] == "a_construire"]
traceability = {
"generated_from": "pie_spec.json",
"claude_md_source": "CLAUDE.md",
"directive_source": spec["directive"],
"engine": spec["engine"],
"annexe": spec["annexe"],
"version_workflow": spec["version_workflow"],
"ssot": spec["ssot"],
"counts": {
"master_data_groups": len(groups),
"downstream_total": len(registry),
"downstream_gated": len(gated),
"downstream_a_construire": len(a_construire),
"sync_rules": len(sync_rules),
"workflow_steps": len(workflow),
"brands": len(brands),
},
"modules_gated_references": [
{"cle": d["cle"], "module": d["module"]} for d in gated
],
"note_anti_invention": (
"Toute donnée est transcrite de la directive PIE (SSOT · #6) : aucune "
"valeur projet (prix, surfaces, marges) n'est fabriquée ici — le PIE "
"les EXTRAIT de la faisabilité validée côté VPS (/opt/oto/data/pie/{code}/, "
"jamais écrit par ce worker · #8). Les codes projets sont ancrés à "
"CLAUDE.md §Projets ; annexe/version à la directive ; les modules "
"downstream « gated » à des répertoires de livrables réels."
),
}
return {"manifest": manifest_out, "traceability": traceability}
@@ -0,0 +1,60 @@
"""Ancrages hors-module du PIE manifest (anti-dérive · anti-invention #6).
Le manifest PIE ne se valide pas seulement contre lui-même : trois faits sont
RE-DÉRIVÉS de sources de vérité EXTERNES au module, jamais recopiés à la main —
1. Les codes projets (`brands[].code`) sont confrontés à `CLAUDE.md §Projets`
(la constitution · [[claude-md-constant-anchor-gate]]). Une marque PIE dont
le code ne figure pas dans la constitution est une invention refusée.
2. `annexe` (12) et `version_workflow` (V10.1) doivent apparaître VERBATIM dans
la directive PIE. Renuméroter l'annexe dans la directive sans régénérer ici
casse la validation ([[roadmap-anchor-gate]] appliqué à la directive).
3. Chaque `downstream_registry[].module` non nul doit être un répertoire de
livrable RÉEL sous `05_deliverables_mvp/` (comme fiscal/ecf vérifie ses
contrats voisins). Un module fantôme (renommé/supprimé) casse la validation.
Zéro dépendance pip · zéro réseau · aucune écriture VPS (#8) : lecture seule de
fichiers déjà versionnés dans le dépôt.
"""
from __future__ import annotations
import os
import re
_HERE = os.path.dirname(os.path.abspath(__file__))
_MODULE_DIR = os.path.normpath(os.path.join(_HERE, "..")) # pie/manifest/
DELIVERABLES_ROOT = os.path.normpath(os.path.join(_HERE, "..", "..", "..")) # 05_deliverables_mvp/
_REPO_ROOT = os.path.normpath(os.path.join(DELIVERABLES_ROOT, ".."))
_CLAUDE_MD = os.path.join(_REPO_ROOT, "CLAUDE.md")
def claude_md_project_codes() -> set[str]:
"""Codes Pxx de la ligne `## Projets` de CLAUDE.md (source de vérité #Projets)."""
with open(_CLAUDE_MD, encoding="utf-8") as fh:
text = fh.read()
# Section entre « ## Projets » et le prochain titre « ## ».
m = re.search(r"^##\s+Projets\s*\n(.*?)(?:\n##\s|\Z)", text, re.S | re.M)
section = m.group(1) if m else ""
return set(re.findall(r"\bP0[1-9]\b", section))
def directive_declares(directive_relpath: str, *needles: str) -> dict[str, bool]:
"""Vrai/faux, pour chaque `needle`, s'il apparaît verbatim dans la directive PIE."""
path = os.path.join(_REPO_ROOT, directive_relpath)
try:
with open(path, encoding="utf-8") as fh:
text = fh.read()
except OSError:
return {n: False for n in needles}
return {n: (n in text) for n in needles}
def module_exists(rel_module: str) -> bool:
"""Le module de livrable `rel_module` (ex. 'faisabilite/bancable') existe-t-il ?"""
return os.path.isdir(os.path.join(DELIVERABLES_ROOT, rel_module))
def module_out_dir() -> str:
"""Répertoire de sortie du module (le SEUL endroit où ce worker écrit · #8)."""
return os.path.join(_MODULE_DIR, "out")
@@ -0,0 +1,194 @@
#!/usr/bin/env python3
"""Tests du générateur de manifest de dépendances PIE · Annexe 12 · V10.1.
Stdlib pur (`unittest`) → aucune installation pip requise sur le runner Gitea.
La bibliothèque `jsonschema` sert d'*oracle* quand elle est présente.
Axes :
1. BASELINE : le contrat vanille passe schéma + 10 invariants, est déterministe,
et l'artefact `out/` commité == build frais (reproductibilité · #6).
2. MUTATION : chaque invariant attrape bien la dérive qu'il protège (on casse
le bundle en mémoire et on vérifie que `_validate` remonte une erreur).
3. ANCRAGES : codes marques ⊆ CLAUDE.md §Projets ; annexe/version verbatim dans
la directive ; modules downstream « gated » = répertoires de livrables réels.
"""
from __future__ import annotations
import copy
import json
import os
import subprocess
import sys
import tempfile
import unittest
_HERE = os.path.dirname(os.path.abspath(__file__))
_MODULE = os.path.normpath(os.path.join(_HERE, ".."))
_DELIVERABLES = os.path.normpath(os.path.join(_MODULE, "..", ".."))
sys.path.insert(0, _MODULE)
sys.path.insert(0, os.path.join(_DELIVERABLES, "publiciste"))
from pielib import builder, deps # noqa: E402
from lib import validator as maison # type: ignore # noqa: E402
import pie_manifest_gen as gen # noqa: E402
try:
import jsonschema # type: ignore
_HAS_JSONSCHEMA = True
except Exception: # pragma: no cover
_HAS_JSONSCHEMA = False
def _load(path: str) -> dict:
with open(path, encoding="utf-8") as fh:
return json.load(fh)
class Baseline(unittest.TestCase):
"""Le contrat vanille passe schéma + 10 invariants et est déterministe."""
def setUp(self):
self.bundle = gen._build()
def test_validate_clean(self):
self.assertEqual(gen._validate(self.bundle), [])
def test_counts_match_directive(self):
c = self.bundle["traceability"]["counts"]
self.assertEqual(c["master_data_groups"], 12) # §16-29
self.assertEqual(c["sync_rules"], 4) # §82-86
self.assertEqual(c["workflow_steps"], 10) # §108-133
self.assertEqual(c["brands"], 9) # §90-102
def test_deterministic_rebuild(self):
self.assertEqual(gen._build(), gen._build())
def test_schema_oracle(self):
if not _HAS_JSONSCHEMA:
self.skipTest("jsonschema absent")
schema = _load(gen._SCHEMA_PATH)
jsonschema.validate(self.bundle["manifest"], schema) # ne lève pas
class Reproducibility(unittest.TestCase):
"""L'artefact commité `out/` == build frais (byte-identique)."""
def test_committed_matches_fresh(self):
out = os.path.join(_MODULE, "out")
if not os.path.isdir(out):
self.skipTest("out/ non commité")
with tempfile.TemporaryDirectory() as tmp:
rc = gen.main(["build", "-o", tmp])
self.assertEqual(rc, 0)
for name in ("pie_manifest.json", "MANIFEST.json"):
with open(os.path.join(out, name), encoding="utf-8") as a, \
open(os.path.join(tmp, name), encoding="utf-8") as b:
self.assertEqual(a.read(), b.read(), f"{name} a dérivé de sa source")
class Anchors(unittest.TestCase):
"""Les ancrages hors-module pointent bien vers du réel (anti-invention #6)."""
def setUp(self):
self.man = gen._build()["manifest"]
def test_brand_codes_subset_of_claude_md(self):
codes = {b["code"] for b in self.man["brands"]}
canon = deps.claude_md_project_codes()
self.assertTrue(canon, "aucun code Pxx lu dans CLAUDE.md §Projets")
self.assertTrue(codes <= canon, f"codes hors constitution : {codes - canon}")
def test_annexe_version_in_directive(self):
decl = deps.directive_declares(self.man_directive(), "Annexe 12", "V10.1")
self.assertTrue(all(decl.values()), f"ancrage directive rompu : {decl}")
def test_gated_modules_exist_on_fs(self):
for d in self.man["downstream_registry"]:
if d["statut"] == "gated":
self.assertTrue(deps.module_exists(d["module"]),
f"module gated introuvable : {d['module']}")
def man_directive(self) -> str:
return gen._build()["traceability"]["directive_source"]
class Mutations(unittest.TestCase):
"""Chaque invariant attrape la dérive qu'il protège."""
def setUp(self):
self.bundle = gen._build()
def _breaks(self, mutate) -> None:
bundle = copy.deepcopy(self.bundle)
mutate(bundle)
self.assertNotEqual(gen._validate(bundle), [],
"la mutation aurait dû être refusée")
def test_inv2_missing_group(self):
self._breaks(lambda b: b["manifest"]["master_data_groups"].pop())
def test_inv2_wrong_group_key(self):
def m(b):
b["manifest"]["master_data_groups"][0]["cle"] = "inventé"
self._breaks(m)
def test_inv3_unknown_trigger(self):
def m(b):
b["manifest"]["sync_rules"][0]["trigger"] = "couleur"
self._breaks(m)
def test_inv4_dangling_downstream(self):
def m(b):
b["manifest"]["sync_rules"][0]["downstream"].append("fantome")
self._breaks(m)
def test_inv4_dangling_trigger_group(self):
def m(b):
b["manifest"]["sync_rules"][0]["trigger_group"] = "fantome"
self._breaks(m)
def test_inv5_status_incoherent(self):
def m(b):
b["manifest"]["downstream_registry"][0]["statut"] = "gated"
b["manifest"]["downstream_registry"][0]["module"] = None
self._breaks(m)
def test_inv6_phantom_module(self):
def m(b):
for d in b["manifest"]["downstream_registry"]:
if d["module"]:
d["module"] = "faisabilite/inexistant"
return
self._breaks(m)
def test_inv7_non_contiguous_workflow(self):
def m(b):
b["manifest"]["workflow"][0]["etape"] = 99
self._breaks(m)
def test_inv8_alien_brand_code(self):
def m(b):
b["manifest"]["brands"][0]["code"] = "P99"
self._breaks(m)
def test_inv9_wrong_annexe(self):
def m(b):
b["manifest"]["annexe"] = 7 # « Annexe 7 » n'est pas dans la directive
self._breaks(m)
def test_inv10_storage_not_vps(self):
def m(b):
b["manifest"]["storage_layout"]["racine"] = "05_deliverables_mvp/pie/"
self._breaks(m)
def test_count_incoherent(self):
def m(b):
b["traceability"]["counts"]["brands"] = 999
self._breaks(m)
if __name__ == "__main__": # pragma: no cover
unittest.main()
@@ -150,9 +150,13 @@
"roadmap_line": 76,
"statement": "Plateforme démo live · 5 portails · CRM · BIM auto · OTOIA voice",
"status": "in_repo",
"evidence_modules": ["qa/regression", "devops/deploy_runbook"],
"evidence_modules": ["qa/regression", "devops/deploy_runbook", "pie/manifest"],
"evidence_artifacts": [],
"out_of_scope": [
{
"item": "Project Identity Engine complet (Brand Books/logos Flux, storage /opt/oto/data/pie/, sync ERPNext/Speckle/OTO AI) — seul le P0 (manifest de dépendances faisabilité(SSOT)→downstream) est in_repo ; P1-P4 restent à construire",
"source": "directive PIE (Annexe 12 · V10.1 · 2026-08-03) §160-166 · génération effective côté agents Flux/ERPNext/OTOIA (VPS · CLAUDE.md #8)"
},
{
"item": "déploiement production complet + monitoring exécutés sur le VPS (run-book appliqué)",
"source": "CLAUDE.md #8 (VPS) — l'exécution du run-book de déploiement revient à l'agent DevOps / ERPNext Backend"
@@ -10,13 +10,13 @@
"deliverables_total": 15,
"in_repo": 15,
"out_of_scope_full": 0,
"gated_modules": 23,
"cited_modules": 23,
"out_of_scope_items": 13
"gated_modules": 24,
"cited_modules": 24,
"out_of_scope_items": 14
},
"coverage": {
"ci_modules_count": 23,
"cited_modules_count": 23,
"ci_modules_count": 24,
"cited_modules_count": 24,
"bijective": true,
"missing_in_matrix": [],
"phantom_evidence": [],
@@ -136,10 +136,12 @@
"sprint": "S8",
"expected": [
"devops/deploy_runbook",
"pie/manifest",
"qa/regression"
],
"cited": [
"devops/deploy_runbook",
"pie/manifest",
"qa/regression"
],
"missing": [],
@@ -277,16 +277,25 @@
"module": "devops/deploy_runbook",
"ci_job": "devops-deploy-runbook-tests",
"gated": true
},
{
"module": "pie/manifest",
"ci_job": "pie-manifest-tests",
"gated": true
}
],
"artifacts": [],
"out_of_scope": [
{
"item": "Project Identity Engine complet (Brand Books/logos Flux, storage /opt/oto/data/pie/, sync ERPNext/Speckle/OTO AI) — seul le P0 (manifest de dépendances faisabilité(SSOT)→downstream) est in_repo ; P1-P4 restent à construire",
"source": "directive PIE (Annexe 12 · V10.1 · 2026-08-03) §160-166 · génération effective côté agents Flux/ERPNext/OTOIA (VPS · CLAUDE.md #8)"
},
{
"item": "déploiement production complet + monitoring exécutés sur le VPS (run-book appliqué)",
"source": "CLAUDE.md #8 (VPS) — l'exécution du run-book de déploiement revient à l'agent DevOps / ERPNext Backend"
}
],
"n_modules": 2,
"n_modules": 3,
"all_modules_gated": true,
"all_artifacts_exist": true,
"has_evidence": true
@@ -4,7 +4,7 @@
"audit": "OTO QA · Audit 4Big Qualité",
"version": "1.0",
"verdict": "PASS",
"modules_audited": 23,
"modules_audited": 24,
"min_score": 100,
"pass_score": 95,
"coverage_ok": true,
@@ -5,8 +5,8 @@
"pass_score": 95,
"coverage": {
"ok": true,
"ci_modules_count": 23,
"registry_modules_count": 23,
"ci_modules_count": 24,
"registry_modules_count": 24,
"missing_in_registry": [],
"missing_in_ci": [],
"not_in_gate": [],
@@ -424,6 +424,48 @@
}
]
},
{
"id": "crm-financement-bancaire",
"path": "crm/financement_bancaire",
"sprint": "S4",
"archetype": "generator",
"applicable_weight": 100,
"earned_weight": 100,
"score": 100,
"verdict": "PASS",
"checks": [
{
"criterion": "DOC",
"weight": 20,
"passed": true,
"evidence": "README.md (6280 octets)"
},
{
"criterion": "CONTRAT",
"weight": 20,
"passed": true,
"evidence": "financement.schema.json"
},
{
"criterion": "TESTS",
"weight": 25,
"passed": true,
"evidence": "35 méthodes test_* dans 1 fichier(s) (seuil 8)"
},
{
"criterion": "CLI",
"weight": 15,
"passed": true,
"evidence": "financement_bancaire_gen.py (argparse + __main__)"
},
{
"criterion": "HANDOFF",
"weight": 20,
"passed": true,
"evidence": "MANIFEST + 6 artefact(s) JSON valides"
}
]
},
{
"id": "crm-workflow-vente",
"path": "crm/workflow_vente",
@@ -550,48 +592,6 @@
}
]
},
{
"id": "crm-financement-bancaire",
"path": "crm/financement_bancaire",
"sprint": "S5",
"archetype": "generator",
"applicable_weight": 100,
"earned_weight": 100,
"score": 100,
"verdict": "PASS",
"checks": [
{
"criterion": "DOC",
"weight": 20,
"passed": true,
"evidence": "README.md (6280 octets)"
},
{
"criterion": "CONTRAT",
"weight": 20,
"passed": true,
"evidence": "financement.schema.json"
},
{
"criterion": "TESTS",
"weight": 25,
"passed": true,
"evidence": "35 méthodes test_* dans 1 fichier(s) (seuil 8)"
},
{
"criterion": "CLI",
"weight": 15,
"passed": true,
"evidence": "financement_bancaire_gen.py (argparse + __main__)"
},
{
"criterion": "HANDOFF",
"weight": 20,
"passed": true,
"evidence": "MANIFEST + 6 artefact(s) JSON valides"
}
]
},
{
"id": "legal-confotur",
"path": "legal/confotur",
@@ -886,6 +886,48 @@
}
]
},
{
"id": "pie-manifest",
"path": "pie/manifest",
"sprint": "S8",
"archetype": "generator",
"applicable_weight": 100,
"earned_weight": 100,
"score": 100,
"verdict": "PASS",
"checks": [
{
"criterion": "DOC",
"weight": 20,
"passed": true,
"evidence": "README.md (4022 octets)"
},
{
"criterion": "CONTRAT",
"weight": 20,
"passed": true,
"evidence": "pie.schema.json"
},
{
"criterion": "TESTS",
"weight": 25,
"passed": true,
"evidence": "20 méthodes test_* dans 1 fichier(s) (seuil 8)"
},
{
"criterion": "CLI",
"weight": 15,
"passed": true,
"evidence": "pie_manifest_gen.py (argparse + __main__)"
},
{
"criterion": "HANDOFF",
"weight": 20,
"passed": true,
"evidence": "MANIFEST + 1 artefact(s) JSON valides"
}
]
},
{
"id": "qa-acceptance",
"path": "qa/acceptance",
@@ -972,8 +1014,8 @@
}
],
"totals": {
"modules": 23,
"pass": 23,
"modules": 24,
"pass": 24,
"fail": 0,
"min_score": 100,
"max_score": 100,
@@ -83,7 +83,8 @@
{ "id": "qa-regression", "path": "qa/regression", "sprint": "S8", "archetype": "generator", "source": "roadmap Sprint 8 · Regression tests exhaustifs (matrice méta-niveau) · ci.yml qa-regression-tests" },
{ "id": "devops-deploy-runbook", "path": "devops/deploy_runbook", "sprint": "S8", "archetype": "generator", "source": "roadmap Sprint 8 · DevOps Deployment production complet (run-book VPS unifié) · ci.yml devops-deploy-runbook-tests" },
{ "id": "qa-acceptance", "path": "qa/acceptance", "sprint": "S8", "archetype": "generator", "source": "roadmap Sprint 8 · QA buffer (matrice d'acceptation / traçabilité MVP roadmap → preuve) · ci.yml qa-acceptance-tests" },
{ "id": "mobile-app-config", "path": "mobile/app_config", "sprint": "S5", "archetype": "generator", "source": "roadmap Sprint 5 · Mobile (config app Expo/EAS + navigation par rôle) · ci.yml mobile-app-config-tests" }
{ "id": "mobile-app-config", "path": "mobile/app_config", "sprint": "S5", "archetype": "generator", "source": "roadmap Sprint 5 · Mobile (config app Expo/EAS + navigation par rôle) · ci.yml mobile-app-config-tests" },
{ "id": "pie-manifest", "path": "pie/manifest", "sprint": "S8", "archetype": "generator", "source": "directive PIE (Project Identity Engine · Annexe 12 · V10.1 · 2026-08-03) livrée durant le buffer S8 (roadmap L72-76) · manifest de dépendances faisabilité(SSOT)→downstream · ci.yml pie-manifest-tests" }
],
"notes": [
"Audit de MÉTA-NIVEAU : chaque note de module est CALCULÉE depuis des faits du dépôt (présence README/schéma, nombre de méthodes test_*, entrypoint CLI, intégrité out/) — jamais saisie à la main (#6).",
@@ -4,8 +4,8 @@
"matrix": "OTO QA · Matrice de régression exhaustive",
"version": "1.0",
"verdict": "PASS",
"suites": 23,
"test_methods": 604,
"suites": 24,
"test_methods": 624,
"coverage_ok": true,
"artifacts": [
"regression_plan.json"
@@ -17,8 +17,8 @@
],
"coverage": {
"ok": true,
"suites_count": 23,
"gated_in_ci": 24,
"suites_count": 24,
"gated_in_ci": 25,
"self_module_excluded": "qa/regression",
"self_module_gated": true,
"missing_tests_dir": [],
@@ -169,6 +169,17 @@
"test_files": 1,
"test_methods": 22
},
{
"id": "pie.manifest",
"path": "pie/manifest",
"jobs": [
"pie-manifest-tests"
],
"in_gate": true,
"has_tests_dir": true,
"test_files": 1,
"test_methods": 20
},
{
"id": "publiciste",
"path": "publiciste",
@@ -282,9 +293,9 @@
],
"under_threshold": [],
"totals": {
"suites": 23,
"test_files": 23,
"test_methods": 604,
"suites": 24,
"test_files": 24,
"test_methods": 624,
"min_methods": 10,
"under_threshold": 0
},