Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 65 additions & 6 deletions scripts/audit/detect_organ_duplication.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
(la porte de sortie vit dans le body). Les jumeaux ``X.lean`` / ``X_en.lean``
d'un meme lake partagent leur serie proprietaire : ils ne se signalent pas.

Sous-modules (#13902, item 4) : une collision avec un organe de la section
``submodules`` (les cinq depots sous-modules) exige la section « ce que cette
extraction rend a l'amont » (ou « ne rend rien ») -- la copie declaree seule
ne blanchit pas : extraire l'ame d'un sous-module sans nommer le contrat de
retour est l'affaiblissement que l'EPIC interdit.

Sortie : une ligne par collision au format
COLLISION <serie demandeuse|?> (<fichier>) -> <serie organe> (<symbole>)
--check : exit 1 si au moins une collision non declaree.
Expand Down Expand Up @@ -60,6 +66,17 @@
r"\bcopie\s+(?:pedagogique\s+)?(?:declaree|fidele)\b", re.IGNORECASE
)

# Contrat de retour amont (#13902, item 4) : une collision avec un organe
# SUBMODULE (les cinq depots sous-modules de .gitmodules, geste de perennite)
# n'est pas blanchie par la seule copie declaree -- le body doit dire ce que
# l'extraction rend a l'amont, ou declarer explicitement ne rien rendre.
PAYBACK_RE = re.compile(
r"rend\w*\s+a\s+l['’]\s*amont"
r"|cette\s+extraction\s+rend"
r"|ne\s+rend\s+rien",
re.IGNORECASE,
)

# --- Lean (#20210) ----------------------------------------------------------

# Declaration en tete de ligne de CODE (commentaires deja retires par mask_lean).
Expand All @@ -79,6 +96,14 @@
LEAN_TREE_DIR = "MyIA.AI.Notebooks"
LEAN_PATHSPECS = (":(glob)MyIA.AI.Notebooks/**/*.lean", ":(glob)MyIA.AI.Notebooks/**/lakefile.*")

# Fixtures du detecteur lui-meme : ses propres tests ajoutent des lignes
# « class <symbole> » DANS des chaines de patch de test -- ce ne sont pas des
# extractions, et l'organe qui se flaggerait lui-meme rendrait son advisory
# inutilisable sur ses propres PRs. Exemption bornee a ce fichier seul.
SELF_FIXTURE_FILES = frozenset({
"scripts/tests/test_detect_organ_duplication.py",
})


def strip_accents(text: str) -> str:
import unicodedata
Expand All @@ -92,7 +117,16 @@ def is_declared(body: str) -> bool:
return bool(DECLARATION_RE.search(strip_accents(body)))


def is_payback(body: str) -> bool:
if not body:
return False
return bool(PAYBACK_RE.search(strip_accents(body)))


def load_index(path: Path):
"""(owners, symbol_owners, submodule_names) : la section ``submodules``
(#13902) charge les memes champs que ``series`` -- ses organes exigent le
contrat de retour amont au lieu de la copie declaree."""
data = yaml.safe_load(path.read_text(encoding="utf-8"))
series = data.get("series") or {}
if not isinstance(series, dict) or not series:
Expand All @@ -104,7 +138,17 @@ def load_index(path: Path):
owners[name] = owner_path
for sym in entry.get("symbols") or []:
symbol_owners.setdefault(str(sym), name)
return owners, symbol_owners
submodule_names = set()
submodules = data.get("submodules") or {}
for name, entry in submodules.items():
owner_path = str(entry.get("owner_path", "")).rstrip("/")
owners[name] = owner_path
submodule_names.add(name)
for sym in entry.get("symbols") or []:
# setdefault : un symbole deja proprietaire cote series reste a
# son premier proprietaire -- la curation exclut les doublons.
symbol_owners.setdefault(str(sym), name)
return owners, symbol_owners, submodule_names


def resolve_series(path: str, owners: dict):
Expand Down Expand Up @@ -133,7 +177,9 @@ def parse_patch(patch_text: str, owners: dict, symbol_owners: dict):
if m:
current_file = m.group(2)
continue
if current_file is None or not line.startswith("+") or line.startswith("+++"):
if current_file is None or current_file in SELF_FIXTURE_FILES:
continue
if not line.startswith("+") or line.startswith("+++"):
continue
content = line[1:].lstrip()
if content.startswith("#"):
Expand Down Expand Up @@ -490,6 +536,7 @@ def git_diff(base: str, head: str, cwd: Path) -> str:
result = subprocess.run(
["git", "diff", f"{base}...{head}", "--unified=0"],
capture_output=True, text=True, cwd=str(cwd), check=True,
encoding="utf-8", errors="replace",
)
return result.stdout

Expand All @@ -506,6 +553,7 @@ def git_show(ref: str, path: str, cwd: Path):
def git_merge_base(base: str, head: str, cwd: Path) -> str:
result = subprocess.run(
["git", "merge-base", base, head], capture_output=True, text=True, cwd=str(cwd),
encoding="utf-8", errors="replace",
)
found = result.stdout.strip()
return found if result.returncode == 0 and found else base
Expand Down Expand Up @@ -551,7 +599,7 @@ def main(argv=None) -> int:
if not args.patch and not args.base:
args.base = "origin/main"

owners, symbol_owners = load_index(args.index)
owners, symbol_owners, submodule_names = load_index(args.index)
cwd = args.cwd or Path.cwd()

if args.patch:
Expand All @@ -566,6 +614,7 @@ def main(argv=None) -> int:
if args.body_file else ""
)
declared = is_declared(body)
payback = is_payback(body)

collisions = parse_patch(patch_text, owners, symbol_owners)
blocks = parse_added(patch_text)
Expand All @@ -580,17 +629,27 @@ def main(argv=None) -> int:
undeclared = []
for file, file_series, owner, sym in collisions:
origin = file_series if file_series else "?"
if declared:
if owner in submodule_names:
# Contrat #13902 : la copie declaree ne suffit pas, seule la
# section de retour amont blanchit une extraction de sous-module.
if payback:
print(f"EXEMPTED {origin} ({file}) -> {owner} ({sym}) [retour amont declare]")
else:
print(f"COLLISION {origin} ({file}) -> {owner} ({sym}) "
f"[retour amont manquant -- #13902]")
undeclared.append((file, owner, sym))
elif declared:
print(f"EXEMPTED {origin} ({file}) -> {owner} ({sym}) [copie declaree]")
else:
print(f"COLLISION {origin} ({file}) -> {owner} ({sym})")
undeclared.append((file, owner, sym))

status = "EXEMPTED" if declared and collisions else (
status = "EXEMPTED" if collisions and not undeclared else (
"BLOCKED" if undeclared else "CLEAN"
)
print(f"{status} {len(collisions)} collision(s), {len(undeclared)} non declaree(s), "
f"{len(owners)} series dans l'index, {len(lean_owners)} lakes Lean")
f"{len(owners) - len(submodule_names)} series + {len(submodule_names)} submodules "
f"dans l'index, {len(lean_owners)} lakes Lean")

if args.check and undeclared:
return 1
Expand Down
106 changes: 106 additions & 0 deletions scripts/audit/organ_api_index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,109 @@ series:
- add_score_claim
- slugify
# Mesure : 36 notebooks, 19 symboles .ipynb.

submodules:
# Contrat de retour amont (#13902, item 4) : une collision avec un organe de
# cette section n'est PAS blanchie par la seule « copie declaree » -- le body
# doit porter la section « ce que cette extraction rend a l'amont » (ou
# declarer explicitement ne rien rendre). Ces depots sont des sous-modules =
# geste de perennite (mandat user 2026-09-01) : extraire leur ame sans nommer
# le contrat de retour est le mecanisme d'affaiblissement que l'EPIC interdit.
#
# Source des symboles : MESURE au 2026-10-23 sur les arbres GitHub des cinq
# depots AUX GITLINKS EPINGLES d'origin/main (git ls-tree), convention C# une
# classe publique par fichier, spot-checks Fallacy.cs (« public class
# Fallacy ») et VettingLevel.cs (« public enum VettingLevel »). Curation :
# noms distinctifs seulement -- les noms d'algorithmes standards (Differential
# Evolution, Simulated Annealing...) sont exclus : reimplementer un algorithme
# de manuel n'est pas extraire l'organe. Borne connue : DEF_RE ne voit que
# def/class -- un « record », « enum » ou « type » F# ajoute hors du
# sous-module n'est pas attrape.
semantic-fleet:
owner_path: MyIA.AI.Notebooks/GenAI/SemanticKernel/semantic-fleet
remote: MyIntelligenceAgency/semantic-fleet
pin: 2a9d48b4a899
organ: "Connectors.AI.MultiConnector (fork SK v0.34.3) -- vetting, analyse multi-completion, couts par appel"
symbols:
- MultiCompletionAnalysis
- MultiCompletionAnalysisSettings
- ConnectorPromptEvaluation
- VettingLevel
- CallRequestCostCreditor
- CompletionJob
- MultiConnectorConfiguration
- MultiOpenAICompatibleConnectorConfiguration

Argumentum:
owner_path: MyIA.AI.Notebooks/SymbolicAI/Argument_Analysis/Argumentum
remote: ArgumentumGames/Argumentum
pin: e62787eaaecd
organ: "AssetConverter + entites du corpus (fallacies, vertus, scenarios) -- la surface C# mesuree au pin"
symbols:
- Fallacy
- Virtue
- ArgumentVirtue
- Scenario
- SexyContentData
- DatasetUpdater
- DatasetUpdaterRootConfig
- RecordsUpdater
- AssetConverterConfig
- BatchImageConverterConfig
- PromptExample

MetaGeneticSharp:
owner_path: MyIA.AI.Notebooks/Search/MetaGeneticSharp
remote: jsboige/MetaGeneticSharp
pin: 3a7d91e57b63
organ: "meta-genetique : MetaGeneticAlgorithm + crossovers geometriques (Domain)"
symbols:
- MetaGeneticAlgorithm
- EukaryoteChromosome
- GeometricCrossover
- GeometricConverter
- TypedGeometricConverter
- KendallTauEmbedding
- OrderedEmbedding
- InsertionEmbedding
- EdgeEmbedding
- IdentityEmbedding
- KnownCompoundMetaheuristics
- SubEvolutionContext

Z3.Linq:
owner_path: MyIA.AI.Notebooks/SymbolicAI/SMT/Z3.Linq
remote: MyIntelligenceAgency/Z3.Linq
pin: 8a2562dba761
organ: "binding LINQ->Z3 : Theorem<S>, rewriters d'attributs, exemples Sudoku/RiverCrossing"
symbols:
- Theorem
- SudokuTheorem
- SudokuGridTheorem
- SudokuTheoremRewriter
- TheoremGlobalRewriterAttribute
- TheoremPredicateRewriterAttribute
- BitVecWidthAttribute
- SolverKind
- MissionariesAndCannibals

Automata:
owner_path: MyIA.AI.Notebooks/SymbolicAI/SMT/Automata
remote: MyIntelligenceAgency/Automata
pin: b1335eebafac
organ: "automates symboliques (Microsoft) : CharSetSolver, tree algebra, BDD algebras"
symbols:
- Automaton
- CharSetSolver
- TreeTheory
- TreeRule
- TreeTransducer
- BooleanAlgebraZ3
- Z3Provider
- RankedAlphabet
- ValueTerm
- BREX
- BREXManager
- BDDAlgebra
- ConditionalDerivative
- BasicCountingSet
Loading
Loading