erplibre/script/mobile/check_bundle_transfer.py

323 lines
12 KiB
Python
Raw Normal View History

[FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du contenu. Le côté application est dans le dépôt mobile ; ce commit porte la vérification et retire le contournement. Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées, et 20 fichiers relus depuis les packs identiques octet pour octet à leur source. L'installation le vérifie et échoue sinon : une application qui ne porte pas le code qu'elle doit montrer n'est pas celle demandée. --- EN --- The stopgap has served its time: the repositories were not embedded at all, and the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now enter as packs — 4 MB slices and one index per repository saying where each file lives — which brings the count to 391 entries without losing any content. The app side lives in the mobile repository; this commit carries the verification and drops the workaround. Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002 entries, and 20 files read back from the packs identical byte for byte to their source. The install verifies it and fails otherwise: an app that does not carry the code it must show is not the one that was asked for. Assisted-by: Claude Opus 5
2026-08-20 03:51:14 -04:00
#!/usr/bin/env python3
# © 2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
"""Le transfert des dépôts ERPLibre dans l'application mobile a-t-il eu lieu ?
L'application embarque le code des dépôts du manifeste pour les parcourir hors
[FIX] mobile: the bundle check refused a real build The checker knew only the pack layout. A real build ships one tar.gz per repository, so it raised "<slug> : index.json absent" and, being guarded by `|| exit 1`, stopped compile_and_run.sh before cap sync — no APK. Broken since 2026-08-20 for anyone on the current mobile main: the parent half of that work landed, the mobile half producing packs never did. It now accepts both layouts, so whichever half lands next, it holds. Two things got better on the way. The byte-for-byte comparison against the source — the only check that proves fidelity rather than coherence — was reporting zero comparisons, because no entry matched the shape it looked for; it now compares twenty. And presence is no longer sampled: streaming all 139 archives costs 6 s and accounts for every one of the 124 350 promised files, where a sample of twenty could not see a ghost it did not draw. The test guarding the ZIP limit demanded a `chunk` field on every file — the pack layout, not the limit. It counts entries now: 278 against 65 535. Its real-bundle class had been taught to skip on this very symptom rather than fail; it runs again. --- FR --- Le vérificateur ne connaissait que la disposition en packs. Une compilation réelle livre un tar.gz par dépôt : il levait « <slug> : index.json absent » et, gardé par `|| exit 1`, arrêtait compile_and_run.sh avant cap sync — pas d'APK. Cassé depuis le 2026-08-20 pour quiconque est sur le main mobile actuel : la moitié parente de ce travail a atterri, la moitié mobile qui produit les packs jamais. Il accepte désormais les deux dispositions : quelle que soit la moitié qui atterrit ensuite, il tient. Deux choses se sont améliorées en chemin. La comparaison octet pour octet contre la source — la seule qui prouve la fidélité et non la cohérence — rapportait zéro comparaison, faute d'entrée à la forme attendue ; elle en compare vingt. Et la présence n'est plus échantillonnée : traverser les 139 archives coûte 6 s et rend compte de chacun des 124 350 fichiers promis, là où vingt tirages ne pouvaient pas voir un fantôme non tiré. Le test qui gardait la limite du ZIP exigeait un champ `chunk` sur chaque fichier — la disposition, pas la limite. Il compte les entrées désormais : 278 pour 65 535. Sa classe sur le vrai bundle avait appris à s'ignorer sur ce symptôme même plutôt qu'à échouer ; elle tourne à nouveau. Assisted-by: Claude Opus 5
2026-08-24 05:50:04 -04:00
ligne. Un APK est un ZIP borné à 65535 entrées, et ces dépôts pèsent plus de
120 000 fichiers : un fichier par source faisait échouer la compilation sur
« Too many zip entries ». Deux dispositions résolvent cela, et ce script
accepte les DEUX — sans quoi il échoue sur la moitié du travail qui n'a pas
encore atterri, ce qui est arrivé :
- ARCHIVE : un `tar.gz` par dépôt, plus un `index.json` listant ses chemins.
Le manifeste porte alors `archive` et `indexUrl`.
- PACKS : des tranches `pack-NNN.bin` par dépôt, et un `index.json` qui dit
pour chaque fichier sa tranche, son offset et sa longueur. Un fichier s'y
relit sans décompresser le reste.
[FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du contenu. Le côté application est dans le dépôt mobile ; ce commit porte la vérification et retire le contournement. Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées, et 20 fichiers relus depuis les packs identiques octet pour octet à leur source. L'installation le vérifie et échoue sinon : une application qui ne porte pas le code qu'elle doit montrer n'est pas celle demandée. --- EN --- The stopgap has served its time: the repositories were not embedded at all, and the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now enter as packs — 4 MB slices and one index per repository saying where each file lives — which brings the count to 391 entries without losing any content. The app side lives in the mobile repository; this commit carries the verification and drops the workaround. Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002 entries, and 20 files read back from the packs identical byte for byte to their source. The install verifies it and fails otherwise: an app that does not carry the code it must show is not the one that was asked for. Assisted-by: Claude Opus 5
2026-08-20 03:51:14 -04:00
Ce script VÉRIFIE ce transfert, et il est fait pour être exécuté par
l'installation comme à la main :
./script/mobile/check_bundle_transfer.py [racine_du_dépôt_mobile]
[FIX] mobile: the bundle check refused a real build The checker knew only the pack layout. A real build ships one tar.gz per repository, so it raised "<slug> : index.json absent" and, being guarded by `|| exit 1`, stopped compile_and_run.sh before cap sync — no APK. Broken since 2026-08-20 for anyone on the current mobile main: the parent half of that work landed, the mobile half producing packs never did. It now accepts both layouts, so whichever half lands next, it holds. Two things got better on the way. The byte-for-byte comparison against the source — the only check that proves fidelity rather than coherence — was reporting zero comparisons, because no entry matched the shape it looked for; it now compares twenty. And presence is no longer sampled: streaming all 139 archives costs 6 s and accounts for every one of the 124 350 promised files, where a sample of twenty could not see a ghost it did not draw. The test guarding the ZIP limit demanded a `chunk` field on every file — the pack layout, not the limit. It counts entries now: 278 against 65 535. Its real-bundle class had been taught to skip on this very symptom rather than fail; it runs again. --- FR --- Le vérificateur ne connaissait que la disposition en packs. Une compilation réelle livre un tar.gz par dépôt : il levait « <slug> : index.json absent » et, gardé par `|| exit 1`, arrêtait compile_and_run.sh avant cap sync — pas d'APK. Cassé depuis le 2026-08-20 pour quiconque est sur le main mobile actuel : la moitié parente de ce travail a atterri, la moitié mobile qui produit les packs jamais. Il accepte désormais les deux dispositions : quelle que soit la moitié qui atterrit ensuite, il tient. Deux choses se sont améliorées en chemin. La comparaison octet pour octet contre la source — la seule qui prouve la fidélité et non la cohérence — rapportait zéro comparaison, faute d'entrée à la forme attendue ; elle en compare vingt. Et la présence n'est plus échantillonnée : traverser les 139 archives coûte 6 s et rend compte de chacun des 124 350 fichiers promis, là où vingt tirages ne pouvaient pas voir un fantôme non tiré. Le test qui gardait la limite du ZIP exigeait un champ `chunk` sur chaque fichier — la disposition, pas la limite. Il compte les entrées désormais : 278 pour 65 535. Sa classe sur le vrai bundle avait appris à s'ignorer sur ce symptôme même plutôt qu'à échouer ; elle tourne à nouveau. Assisted-by: Claude Opus 5
2026-08-24 05:50:04 -04:00
Il échoue quand le transfert est vide, quand un conteneur manque, quand un
index promet un fichier que son conteneur n'a pas, ou quand les octets relus
diffèrent de la source — quatre pannes qu'un simple « la compilation a réussi »
ne dit pas. La présence est prouvée pour CHAQUE fichier promis ; seule la
relecture des octets se fait par échantillon.
[FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du contenu. Le côté application est dans le dépôt mobile ; ce commit porte la vérification et retire le contournement. Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées, et 20 fichiers relus depuis les packs identiques octet pour octet à leur source. L'installation le vérifie et échoue sinon : une application qui ne porte pas le code qu'elle doit montrer n'est pas celle demandée. --- EN --- The stopgap has served its time: the repositories were not embedded at all, and the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now enter as packs — 4 MB slices and one index per repository saying where each file lives — which brings the count to 391 entries without losing any content. The app side lives in the mobile repository; this commit carries the verification and drops the workaround. Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002 entries, and 20 files read back from the packs identical byte for byte to their source. The install verifies it and fails otherwise: an app that does not carry the code it must show is not the one that was asked for. Assisted-by: Claude Opus 5
2026-08-20 03:51:14 -04:00
"""
import argparse
import json
import random
import sys
[FIX] mobile: the bundle check refused a real build The checker knew only the pack layout. A real build ships one tar.gz per repository, so it raised "<slug> : index.json absent" and, being guarded by `|| exit 1`, stopped compile_and_run.sh before cap sync — no APK. Broken since 2026-08-20 for anyone on the current mobile main: the parent half of that work landed, the mobile half producing packs never did. It now accepts both layouts, so whichever half lands next, it holds. Two things got better on the way. The byte-for-byte comparison against the source — the only check that proves fidelity rather than coherence — was reporting zero comparisons, because no entry matched the shape it looked for; it now compares twenty. And presence is no longer sampled: streaming all 139 archives costs 6 s and accounts for every one of the 124 350 promised files, where a sample of twenty could not see a ghost it did not draw. The test guarding the ZIP limit demanded a `chunk` field on every file — the pack layout, not the limit. It counts entries now: 278 against 65 535. Its real-bundle class had been taught to skip on this very symptom rather than fail; it runs again. --- FR --- Le vérificateur ne connaissait que la disposition en packs. Une compilation réelle livre un tar.gz par dépôt : il levait « <slug> : index.json absent » et, gardé par `|| exit 1`, arrêtait compile_and_run.sh avant cap sync — pas d'APK. Cassé depuis le 2026-08-20 pour quiconque est sur le main mobile actuel : la moitié parente de ce travail a atterri, la moitié mobile qui produit les packs jamais. Il accepte désormais les deux dispositions : quelle que soit la moitié qui atterrit ensuite, il tient. Deux choses se sont améliorées en chemin. La comparaison octet pour octet contre la source — la seule qui prouve la fidélité et non la cohérence — rapportait zéro comparaison, faute d'entrée à la forme attendue ; elle en compare vingt. Et la présence n'est plus échantillonnée : traverser les 139 archives coûte 6 s et rend compte de chacun des 124 350 fichiers promis, là où vingt tirages ne pouvaient pas voir un fantôme non tiré. Le test qui gardait la limite du ZIP exigeait un champ `chunk` sur chaque fichier — la disposition, pas la limite. Il compte les entrées désormais : 278 pour 65 535. Sa classe sur le vrai bundle avait appris à s'ignorer sur ce symptôme même plutôt qu'à échouer ; elle tourne à nouveau. Assisted-by: Claude Opus 5
2026-08-24 05:50:04 -04:00
import tarfile
[FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du contenu. Le côté application est dans le dépôt mobile ; ce commit porte la vérification et retire le contournement. Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées, et 20 fichiers relus depuis les packs identiques octet pour octet à leur source. L'installation le vérifie et échoue sinon : une application qui ne porte pas le code qu'elle doit montrer n'est pas celle demandée. --- EN --- The stopgap has served its time: the repositories were not embedded at all, and the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now enter as packs — 4 MB slices and one index per repository saying where each file lives — which brings the count to 391 entries without losing any content. The app side lives in the mobile repository; this commit carries the verification and drops the workaround. Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002 entries, and 20 files read back from the packs identical byte for byte to their source. The install verifies it and fails otherwise: an app that does not carry the code it must show is not the one that was asked for. Assisted-by: Claude Opus 5
2026-08-20 03:51:14 -04:00
from pathlib import Path
# En dessous, ce n'est plus un transfert : c'est un bundle vide qu'on aurait pris
# pour bon. Le seul dépôt odoo en porte près de 40 000 à lui seul.
MIN_FILES = 1000
# Échantillon relu octet pour octet. Tout relire prendrait des minutes pour ne
# rien apprendre de plus : une tranche fausse l'est dès le premier extrait.
SAMPLE = 20
SEED = 7
def _read_json(path: Path):
return json.loads(path.read_text(encoding="utf-8"))
def pack_path(repo_dir: Path, chunk: int) -> Path:
"""Nom de tranche, tel que l'écrit le plugin de compilation."""
return repo_dir / ("pack-%03d.bin" % chunk)
def read_from_pack(repo_dir: Path, entry: dict) -> bytes:
"""Relit un fichier depuis sa tranche. Lève si l'index et le pack mentent."""
chunk = entry["chunk"]
path = pack_path(repo_dir, chunk)
size = entry.get("size", 0)
offset = entry.get("offset", 0)
if not path.is_file():
raise FileNotFoundError(f"tranche absente : {path.name}")
if offset + size > path.stat().st_size:
raise ValueError(
f"{path.name} fait {path.stat().st_size} o, l'index y demande"
f" {size} o à {offset}"
)
with open(path, "rb") as fh:
fh.seek(offset)
data = fh.read(size)
if len(data) != size:
raise ValueError(f"{path.name} : {len(data)} o lus au lieu de {size}")
return data
[FIX] mobile: the bundle check refused a real build The checker knew only the pack layout. A real build ships one tar.gz per repository, so it raised "<slug> : index.json absent" and, being guarded by `|| exit 1`, stopped compile_and_run.sh before cap sync — no APK. Broken since 2026-08-20 for anyone on the current mobile main: the parent half of that work landed, the mobile half producing packs never did. It now accepts both layouts, so whichever half lands next, it holds. Two things got better on the way. The byte-for-byte comparison against the source — the only check that proves fidelity rather than coherence — was reporting zero comparisons, because no entry matched the shape it looked for; it now compares twenty. And presence is no longer sampled: streaming all 139 archives costs 6 s and accounts for every one of the 124 350 promised files, where a sample of twenty could not see a ghost it did not draw. The test guarding the ZIP limit demanded a `chunk` field on every file — the pack layout, not the limit. It counts entries now: 278 against 65 535. Its real-bundle class had been taught to skip on this very symptom rather than fail; it runs again. --- FR --- Le vérificateur ne connaissait que la disposition en packs. Une compilation réelle livre un tar.gz par dépôt : il levait « <slug> : index.json absent » et, gardé par `|| exit 1`, arrêtait compile_and_run.sh avant cap sync — pas d'APK. Cassé depuis le 2026-08-20 pour quiconque est sur le main mobile actuel : la moitié parente de ce travail a atterri, la moitié mobile qui produit les packs jamais. Il accepte désormais les deux dispositions : quelle que soit la moitié qui atterrit ensuite, il tient. Deux choses se sont améliorées en chemin. La comparaison octet pour octet contre la source — la seule qui prouve la fidélité et non la cohérence — rapportait zéro comparaison, faute d'entrée à la forme attendue ; elle en compare vingt. Et la présence n'est plus échantillonnée : traverser les 139 archives coûte 6 s et rend compte de chacun des 124 350 fichiers promis, là où vingt tirages ne pouvaient pas voir un fantôme non tiré. Le test qui gardait la limite du ZIP exigeait un champ `chunk` sur chaque fichier — la disposition, pas la limite. Il compte les entrées désormais : 278 pour 65 535. Sa classe sur le vrai bundle avait appris à s'ignorer sur ce symptôme même plutôt qu'à échouer ; elle tourne à nouveau. Assisted-by: Claude Opus 5
2026-08-24 05:50:04 -04:00
# Le plugin de compilation ecrit les membres du tar prefixes par « ./ », quand
# l'index porte le chemin nu. Sans ce prefixe, extractfile ne trouve rien.
MEMBER_PREFIX = "./"
def index_path(base: Path, proj: dict) -> Path:
"""L'index du dépôt, dans l'une ou l'autre disposition."""
url = proj.get("indexUrl")
if url:
# `indexUrl` est relatif à dist/, quand `base` est dist/repos.
cand = base.parent / url
if cand.is_file():
return cand
cand = base / proj["slug"] / "index.json"
if cand.is_file():
return cand
raise FileNotFoundError(f"{proj['slug']} : index.json absent")
def archive_path(base: Path, proj: dict) -> Path:
"""Le tar.gz du dépôt, ou None si ce dépôt n'est pas en archive."""
rel = proj.get("archive")
if not rel:
return None
cand = base.parent / rel
return cand if cand.is_file() else None
def read_from_archive(
archive: Path, paths: list, promised: list = None
) -> dict:
"""Relit plusieurs fichiers en UNE passe de décompression.
Ouvrir une archive par fichier coûterait quelques secondes chacune sur le
dépôt odoo (150 Mo) : on traverse donc une seule fois en ramassant tout ce
qui est demandé.
`promised` : tous les chemins que l'index annonce pour cette archive. La
traversée étant déjà payée, on en profite pour vérifier qu'ils y sont TOUS
— un échantillon de vingt fichiers ne verrait pas un fantôme qu'il ne tire
pas, et c'est précisément ce que cette passe attrape gratuitement.
"""
wanted = {MEMBER_PREFIX + p: p for p in paths}
found = {}
seen = set()
with tarfile.open(archive, "r:gz") as tar:
for member in tar:
if member.isfile():
seen.add(member.name)
key = wanted.get(member.name)
if key is None:
continue
handle = tar.extractfile(member)
if handle is None:
raise ValueError(
f"{archive.name} : {key} n'est pas un fichier"
)
found[key] = handle.read()
missing = set(wanted.values()) - set(found)
if missing:
raise FileNotFoundError(
f"{archive.name} : {len(missing)} fichier(s) tiré(s) de l'index"
f" et absents de l'archive, dont {sorted(missing)[0]}"
)
if promised:
ghosts = {q for q in promised if MEMBER_PREFIX + q not in seen}
if ghosts:
raise FileNotFoundError(
f"{archive.name} : l'index promet {len(ghosts)} fichier(s) que"
f" l'archive n'a pas, dont {sorted(ghosts)[0]}"
)
return found
def _collect(base: Path, repos: list, report: dict) -> tuple:
"""Range chaque dépôt dans sa disposition, et valide ce qu'il promet.
Extraite de `check` pour la garder sous la complexité que flake8 accepte :
deux dispositions et leurs validations tenaient mal dans une fonction.
"""
pack_pool = []
arch_pool = {}
for proj in repos:
entries = _read_json(index_path(base, proj))
files = [e for e in entries if e.get("type") == "file"]
# Le manifeste annonce un compte : un index plus court est un transfert
# tronqué que rien d'autre ne signale.
promised = proj.get("fileCount")
if promised is not None and promised != len(files):
raise ValueError(
f"{proj['slug']} : le manifeste promet {promised} fichiers,"
f" l'index en porte {len(files)}"
)
report["files"] += len(files)
archive = archive_path(base, proj)
if archive is not None:
report["archives"] += 1
arch_pool.setdefault(archive, (proj, []))[1].extend(files)
continue
repo_dir = base / proj["slug"]
packs = list(repo_dir.glob("pack-*.bin"))
if files and not packs:
# Nommer la tranche ATTENDUE : « pack-*.bin » n'aide pas qui lit un
# journal d'installation et cherche un fichier précis.
chunks = sorted(
{e["chunk"] for e in files if e.get("chunk") is not None}
)
attendu = (
pack_path(repo_dir, chunks[0]).name
if chunks
else "pack-000.bin"
)
raise FileNotFoundError(
f"{proj['slug']} : ni archive, ni {attendu}"
)
report["packs"] += len(packs)
pack_pool += [(proj, repo_dir, e) for e in files if e.get("size")]
return pack_pool, arch_pool
[FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du contenu. Le côté application est dans le dépôt mobile ; ce commit porte la vérification et retire le contournement. Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées, et 20 fichiers relus depuis les packs identiques octet pour octet à leur source. L'installation le vérifie et échoue sinon : une application qui ne porte pas le code qu'elle doit montrer n'est pas celle demandée. --- EN --- The stopgap has served its time: the repositories were not embedded at all, and the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now enter as packs — 4 MB slices and one index per repository saying where each file lives — which brings the count to 391 entries without losing any content. The app side lives in the mobile repository; this commit carries the verification and drops the workaround. Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002 entries, and 20 files read back from the packs identical byte for byte to their source. The install verifies it and fails otherwise: an app that does not carry the code it must show is not the one that was asked for. Assisted-by: Claude Opus 5
2026-08-20 03:51:14 -04:00
def check(
mobile_root: Path, workspace: Path = None, min_files: int = None
) -> dict:
"""Vérifie le transfert et rend un compte-rendu.
`workspace` : racine du checkout ERPLibre. Fournie, un échantillon est
comparé OCTET POUR OCTET à la source — c'est la seule vérification qui
prouve un transfert fidèle, et non seulement cohérent.
"""
# Résolu à l'APPEL, et non dans la signature : un défaut lié à la
# définition ne suit pas la constante si un appelant la change.
min_files = MIN_FILES if min_files is None else min_files
base = mobile_root / "dist" / "repos"
manifest = base / "manifest.json"
if not manifest.is_file():
raise FileNotFoundError(
f"aucun transfert : {manifest} est absent (npm run build ?)"
)
repos = _read_json(manifest)
report = {
"repos": len(repos),
"files": 0,
[FIX] mobile: the bundle check refused a real build The checker knew only the pack layout. A real build ships one tar.gz per repository, so it raised "<slug> : index.json absent" and, being guarded by `|| exit 1`, stopped compile_and_run.sh before cap sync — no APK. Broken since 2026-08-20 for anyone on the current mobile main: the parent half of that work landed, the mobile half producing packs never did. It now accepts both layouts, so whichever half lands next, it holds. Two things got better on the way. The byte-for-byte comparison against the source — the only check that proves fidelity rather than coherence — was reporting zero comparisons, because no entry matched the shape it looked for; it now compares twenty. And presence is no longer sampled: streaming all 139 archives costs 6 s and accounts for every one of the 124 350 promised files, where a sample of twenty could not see a ghost it did not draw. The test guarding the ZIP limit demanded a `chunk` field on every file — the pack layout, not the limit. It counts entries now: 278 against 65 535. Its real-bundle class had been taught to skip on this very symptom rather than fail; it runs again. --- FR --- Le vérificateur ne connaissait que la disposition en packs. Une compilation réelle livre un tar.gz par dépôt : il levait « <slug> : index.json absent » et, gardé par `|| exit 1`, arrêtait compile_and_run.sh avant cap sync — pas d'APK. Cassé depuis le 2026-08-20 pour quiconque est sur le main mobile actuel : la moitié parente de ce travail a atterri, la moitié mobile qui produit les packs jamais. Il accepte désormais les deux dispositions : quelle que soit la moitié qui atterrit ensuite, il tient. Deux choses se sont améliorées en chemin. La comparaison octet pour octet contre la source — la seule qui prouve la fidélité et non la cohérence — rapportait zéro comparaison, faute d'entrée à la forme attendue ; elle en compare vingt. Et la présence n'est plus échantillonnée : traverser les 139 archives coûte 6 s et rend compte de chacun des 124 350 fichiers promis, là où vingt tirages ne pouvaient pas voir un fantôme non tiré. Le test qui gardait la limite du ZIP exigeait un champ `chunk` sur chaque fichier — la disposition, pas la limite. Il compte les entrées désormais : 278 pour 65 535. Sa classe sur le vrai bundle avait appris à s'ignorer sur ce symptôme même plutôt qu'à échouer ; elle tourne à nouveau. Assisted-by: Claude Opus 5
2026-08-24 05:50:04 -04:00
"archives": 0,
[FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du contenu. Le côté application est dans le dépôt mobile ; ce commit porte la vérification et retire le contournement. Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées, et 20 fichiers relus depuis les packs identiques octet pour octet à leur source. L'installation le vérifie et échoue sinon : une application qui ne porte pas le code qu'elle doit montrer n'est pas celle demandée. --- EN --- The stopgap has served its time: the repositories were not embedded at all, and the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now enter as packs — 4 MB slices and one index per repository saying where each file lives — which brings the count to 391 entries without losing any content. The app side lives in the mobile repository; this commit carries the verification and drops the workaround. Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002 entries, and 20 files read back from the packs identical byte for byte to their source. The install verifies it and fails otherwise: an app that does not carry the code it must show is not the one that was asked for. Assisted-by: Claude Opus 5
2026-08-20 03:51:14 -04:00
"packs": 0,
[FIX] mobile: the bundle check refused a real build The checker knew only the pack layout. A real build ships one tar.gz per repository, so it raised "<slug> : index.json absent" and, being guarded by `|| exit 1`, stopped compile_and_run.sh before cap sync — no APK. Broken since 2026-08-20 for anyone on the current mobile main: the parent half of that work landed, the mobile half producing packs never did. It now accepts both layouts, so whichever half lands next, it holds. Two things got better on the way. The byte-for-byte comparison against the source — the only check that proves fidelity rather than coherence — was reporting zero comparisons, because no entry matched the shape it looked for; it now compares twenty. And presence is no longer sampled: streaming all 139 archives costs 6 s and accounts for every one of the 124 350 promised files, where a sample of twenty could not see a ghost it did not draw. The test guarding the ZIP limit demanded a `chunk` field on every file — the pack layout, not the limit. It counts entries now: 278 against 65 535. Its real-bundle class had been taught to skip on this very symptom rather than fail; it runs again. --- FR --- Le vérificateur ne connaissait que la disposition en packs. Une compilation réelle livre un tar.gz par dépôt : il levait « <slug> : index.json absent » et, gardé par `|| exit 1`, arrêtait compile_and_run.sh avant cap sync — pas d'APK. Cassé depuis le 2026-08-20 pour quiconque est sur le main mobile actuel : la moitié parente de ce travail a atterri, la moitié mobile qui produit les packs jamais. Il accepte désormais les deux dispositions : quelle que soit la moitié qui atterrit ensuite, il tient. Deux choses se sont améliorées en chemin. La comparaison octet pour octet contre la source — la seule qui prouve la fidélité et non la cohérence — rapportait zéro comparaison, faute d'entrée à la forme attendue ; elle en compare vingt. Et la présence n'est plus échantillonnée : traverser les 139 archives coûte 6 s et rend compte de chacun des 124 350 fichiers promis, là où vingt tirages ne pouvaient pas voir un fantôme non tiré. Le test qui gardait la limite du ZIP exigeait un champ `chunk` sur chaque fichier — la disposition, pas la limite. Il compte les entrées désormais : 278 pour 65 535. Sa classe sur le vrai bundle avait appris à s'ignorer sur ce symptôme même plutôt qu'à échouer ; elle tourne à nouveau. Assisted-by: Claude Opus 5
2026-08-24 05:50:04 -04:00
"present": 0,
[FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du contenu. Le côté application est dans le dépôt mobile ; ce commit porte la vérification et retire le contournement. Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées, et 20 fichiers relus depuis les packs identiques octet pour octet à leur source. L'installation le vérifie et échoue sinon : une application qui ne porte pas le code qu'elle doit montrer n'est pas celle demandée. --- EN --- The stopgap has served its time: the repositories were not embedded at all, and the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now enter as packs — 4 MB slices and one index per repository saying where each file lives — which brings the count to 391 entries without losing any content. The app side lives in the mobile repository; this commit carries the verification and drops the workaround. Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002 entries, and 20 files read back from the packs identical byte for byte to their source. The install verifies it and fails otherwise: an app that does not carry the code it must show is not the one that was asked for. Assisted-by: Claude Opus 5
2026-08-20 03:51:14 -04:00
"checked": 0,
"compared": 0,
}
[FIX] mobile: the bundle check refused a real build The checker knew only the pack layout. A real build ships one tar.gz per repository, so it raised "<slug> : index.json absent" and, being guarded by `|| exit 1`, stopped compile_and_run.sh before cap sync — no APK. Broken since 2026-08-20 for anyone on the current mobile main: the parent half of that work landed, the mobile half producing packs never did. It now accepts both layouts, so whichever half lands next, it holds. Two things got better on the way. The byte-for-byte comparison against the source — the only check that proves fidelity rather than coherence — was reporting zero comparisons, because no entry matched the shape it looked for; it now compares twenty. And presence is no longer sampled: streaming all 139 archives costs 6 s and accounts for every one of the 124 350 promised files, where a sample of twenty could not see a ghost it did not draw. The test guarding the ZIP limit demanded a `chunk` field on every file — the pack layout, not the limit. It counts entries now: 278 against 65 535. Its real-bundle class had been taught to skip on this very symptom rather than fail; it runs again. --- FR --- Le vérificateur ne connaissait que la disposition en packs. Une compilation réelle livre un tar.gz par dépôt : il levait « <slug> : index.json absent » et, gardé par `|| exit 1`, arrêtait compile_and_run.sh avant cap sync — pas d'APK. Cassé depuis le 2026-08-20 pour quiconque est sur le main mobile actuel : la moitié parente de ce travail a atterri, la moitié mobile qui produit les packs jamais. Il accepte désormais les deux dispositions : quelle que soit la moitié qui atterrit ensuite, il tient. Deux choses se sont améliorées en chemin. La comparaison octet pour octet contre la source — la seule qui prouve la fidélité et non la cohérence — rapportait zéro comparaison, faute d'entrée à la forme attendue ; elle en compare vingt. Et la présence n'est plus échantillonnée : traverser les 139 archives coûte 6 s et rend compte de chacun des 124 350 fichiers promis, là où vingt tirages ne pouvaient pas voir un fantôme non tiré. Le test qui gardait la limite du ZIP exigeait un champ `chunk` sur chaque fichier — la disposition, pas la limite. Il compte les entrées désormais : 278 pour 65 535. Sa classe sur le vrai bundle avait appris à s'ignorer sur ce symptôme même plutôt qu'à échouer ; elle tourne à nouveau. Assisted-by: Claude Opus 5
2026-08-24 05:50:04 -04:00
pack_pool, arch_pool = _collect(base, repos, report)
[FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du contenu. Le côté application est dans le dépôt mobile ; ce commit porte la vérification et retire le contournement. Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées, et 20 fichiers relus depuis les packs identiques octet pour octet à leur source. L'installation le vérifie et échoue sinon : une application qui ne porte pas le code qu'elle doit montrer n'est pas celle demandée. --- EN --- The stopgap has served its time: the repositories were not embedded at all, and the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now enter as packs — 4 MB slices and one index per repository saying where each file lives — which brings the count to 391 entries without losing any content. The app side lives in the mobile repository; this commit carries the verification and drops the workaround. Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002 entries, and 20 files read back from the packs identical byte for byte to their source. The install verifies it and fails otherwise: an app that does not carry the code it must show is not the one that was asked for. Assisted-by: Claude Opus 5
2026-08-20 03:51:14 -04:00
if report["files"] < min_files:
raise ValueError(
f"transfert trop maigre : {report['files']} fichiers pour"
f" {report['repos']} dépôts (au moins {min_files} attendus)"
)
[FIX] mobile: the bundle check refused a real build The checker knew only the pack layout. A real build ships one tar.gz per repository, so it raised "<slug> : index.json absent" and, being guarded by `|| exit 1`, stopped compile_and_run.sh before cap sync — no APK. Broken since 2026-08-20 for anyone on the current mobile main: the parent half of that work landed, the mobile half producing packs never did. It now accepts both layouts, so whichever half lands next, it holds. Two things got better on the way. The byte-for-byte comparison against the source — the only check that proves fidelity rather than coherence — was reporting zero comparisons, because no entry matched the shape it looked for; it now compares twenty. And presence is no longer sampled: streaming all 139 archives costs 6 s and accounts for every one of the 124 350 promised files, where a sample of twenty could not see a ghost it did not draw. The test guarding the ZIP limit demanded a `chunk` field on every file — the pack layout, not the limit. It counts entries now: 278 against 65 535. Its real-bundle class had been taught to skip on this very symptom rather than fail; it runs again. --- FR --- Le vérificateur ne connaissait que la disposition en packs. Une compilation réelle livre un tar.gz par dépôt : il levait « <slug> : index.json absent » et, gardé par `|| exit 1`, arrêtait compile_and_run.sh avant cap sync — pas d'APK. Cassé depuis le 2026-08-20 pour quiconque est sur le main mobile actuel : la moitié parente de ce travail a atterri, la moitié mobile qui produit les packs jamais. Il accepte désormais les deux dispositions : quelle que soit la moitié qui atterrit ensuite, il tient. Deux choses se sont améliorées en chemin. La comparaison octet pour octet contre la source — la seule qui prouve la fidélité et non la cohérence — rapportait zéro comparaison, faute d'entrée à la forme attendue ; elle en compare vingt. Et la présence n'est plus échantillonnée : traverser les 139 archives coûte 6 s et rend compte de chacun des 124 350 fichiers promis, là où vingt tirages ne pouvaient pas voir un fantôme non tiré. Le test qui gardait la limite du ZIP exigeait un champ `chunk` sur chaque fichier — la disposition, pas la limite. Il compte les entrées désormais : 278 pour 65 535. Sa classe sur le vrai bundle avait appris à s'ignorer sur ce symptôme même plutôt qu'à échouer ; elle tourne à nouveau. Assisted-by: Claude Opus 5
2026-08-24 05:50:04 -04:00
flat = [("pack", proj, repo_dir, e) for proj, repo_dir, e in pack_pool]
flat += [
("arch", proj, archive, e)
for archive, (proj, entries) in arch_pool.items()
for e in entries
]
[FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du contenu. Le côté application est dans le dépôt mobile ; ce commit porte la vérification et retire le contournement. Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées, et 20 fichiers relus depuis les packs identiques octet pour octet à leur source. L'installation le vérifie et échoue sinon : une application qui ne porte pas le code qu'elle doit montrer n'est pas celle demandée. --- EN --- The stopgap has served its time: the repositories were not embedded at all, and the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now enter as packs — 4 MB slices and one index per repository saying where each file lives — which brings the count to 391 entries without losing any content. The app side lives in the mobile repository; this commit carries the verification and drops the workaround. Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002 entries, and 20 files read back from the packs identical byte for byte to their source. The install verifies it and fails otherwise: an app that does not carry the code it must show is not the one that was asked for. Assisted-by: Claude Opus 5
2026-08-20 03:51:14 -04:00
random.seed(SEED)
[FIX] mobile: the bundle check refused a real build The checker knew only the pack layout. A real build ships one tar.gz per repository, so it raised "<slug> : index.json absent" and, being guarded by `|| exit 1`, stopped compile_and_run.sh before cap sync — no APK. Broken since 2026-08-20 for anyone on the current mobile main: the parent half of that work landed, the mobile half producing packs never did. It now accepts both layouts, so whichever half lands next, it holds. Two things got better on the way. The byte-for-byte comparison against the source — the only check that proves fidelity rather than coherence — was reporting zero comparisons, because no entry matched the shape it looked for; it now compares twenty. And presence is no longer sampled: streaming all 139 archives costs 6 s and accounts for every one of the 124 350 promised files, where a sample of twenty could not see a ghost it did not draw. The test guarding the ZIP limit demanded a `chunk` field on every file — the pack layout, not the limit. It counts entries now: 278 against 65 535. Its real-bundle class had been taught to skip on this very symptom rather than fail; it runs again. --- FR --- Le vérificateur ne connaissait que la disposition en packs. Une compilation réelle livre un tar.gz par dépôt : il levait « <slug> : index.json absent » et, gardé par `|| exit 1`, arrêtait compile_and_run.sh avant cap sync — pas d'APK. Cassé depuis le 2026-08-20 pour quiconque est sur le main mobile actuel : la moitié parente de ce travail a atterri, la moitié mobile qui produit les packs jamais. Il accepte désormais les deux dispositions : quelle que soit la moitié qui atterrit ensuite, il tient. Deux choses se sont améliorées en chemin. La comparaison octet pour octet contre la source — la seule qui prouve la fidélité et non la cohérence — rapportait zéro comparaison, faute d'entrée à la forme attendue ; elle en compare vingt. Et la présence n'est plus échantillonnée : traverser les 139 archives coûte 6 s et rend compte de chacun des 124 350 fichiers promis, là où vingt tirages ne pouvaient pas voir un fantôme non tiré. Le test qui gardait la limite du ZIP exigeait un champ `chunk` sur chaque fichier — la disposition, pas la limite. Il compte les entrées désormais : 278 pour 65 535. Sa classe sur le vrai bundle avait appris à s'ignorer sur ce symptôme même plutôt qu'à échouer ; elle tourne à nouveau. Assisted-by: Claude Opus 5
2026-08-24 05:50:04 -04:00
sample = random.sample(flat, min(SAMPLE, len(flat)))
def compare(proj, entry, data):
[FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du contenu. Le côté application est dans le dépôt mobile ; ce commit porte la vérification et retire le contournement. Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées, et 20 fichiers relus depuis les packs identiques octet pour octet à leur source. L'installation le vérifie et échoue sinon : une application qui ne porte pas le code qu'elle doit montrer n'est pas celle demandée. --- EN --- The stopgap has served its time: the repositories were not embedded at all, and the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now enter as packs — 4 MB slices and one index per repository saying where each file lives — which brings the count to 391 entries without losing any content. The app side lives in the mobile repository; this commit carries the verification and drops the workaround. Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002 entries, and 20 files read back from the packs identical byte for byte to their source. The install verifies it and fails otherwise: an app that does not carry the code it must show is not the one that was asked for. Assisted-by: Claude Opus 5
2026-08-20 03:51:14 -04:00
report["checked"] += 1
if workspace is None:
[FIX] mobile: the bundle check refused a real build The checker knew only the pack layout. A real build ships one tar.gz per repository, so it raised "<slug> : index.json absent" and, being guarded by `|| exit 1`, stopped compile_and_run.sh before cap sync — no APK. Broken since 2026-08-20 for anyone on the current mobile main: the parent half of that work landed, the mobile half producing packs never did. It now accepts both layouts, so whichever half lands next, it holds. Two things got better on the way. The byte-for-byte comparison against the source — the only check that proves fidelity rather than coherence — was reporting zero comparisons, because no entry matched the shape it looked for; it now compares twenty. And presence is no longer sampled: streaming all 139 archives costs 6 s and accounts for every one of the 124 350 promised files, where a sample of twenty could not see a ghost it did not draw. The test guarding the ZIP limit demanded a `chunk` field on every file — the pack layout, not the limit. It counts entries now: 278 against 65 535. Its real-bundle class had been taught to skip on this very symptom rather than fail; it runs again. --- FR --- Le vérificateur ne connaissait que la disposition en packs. Une compilation réelle livre un tar.gz par dépôt : il levait « <slug> : index.json absent » et, gardé par `|| exit 1`, arrêtait compile_and_run.sh avant cap sync — pas d'APK. Cassé depuis le 2026-08-20 pour quiconque est sur le main mobile actuel : la moitié parente de ce travail a atterri, la moitié mobile qui produit les packs jamais. Il accepte désormais les deux dispositions : quelle que soit la moitié qui atterrit ensuite, il tient. Deux choses se sont améliorées en chemin. La comparaison octet pour octet contre la source — la seule qui prouve la fidélité et non la cohérence — rapportait zéro comparaison, faute d'entrée à la forme attendue ; elle en compare vingt. Et la présence n'est plus échantillonnée : traverser les 139 archives coûte 6 s et rend compte de chacun des 124 350 fichiers promis, là où vingt tirages ne pouvaient pas voir un fantôme non tiré. Le test qui gardait la limite du ZIP exigeait un champ `chunk` sur chaque fichier — la disposition, pas la limite. Il compte les entrées désormais : 278 pour 65 535. Sa classe sur le vrai bundle avait appris à s'ignorer sur ce symptôme même plutôt qu'à échouer ; elle tourne à nouveau. Assisted-by: Claude Opus 5
2026-08-24 05:50:04 -04:00
return
[FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du contenu. Le côté application est dans le dépôt mobile ; ce commit porte la vérification et retire le contournement. Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées, et 20 fichiers relus depuis les packs identiques octet pour octet à leur source. L'installation le vérifie et échoue sinon : une application qui ne porte pas le code qu'elle doit montrer n'est pas celle demandée. --- EN --- The stopgap has served its time: the repositories were not embedded at all, and the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now enter as packs — 4 MB slices and one index per repository saying where each file lives — which brings the count to 391 entries without losing any content. The app side lives in the mobile repository; this commit carries the verification and drops the workaround. Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002 entries, and 20 files read back from the packs identical byte for byte to their source. The install verifies it and fails otherwise: an app that does not carry the code it must show is not the one that was asked for. Assisted-by: Claude Opus 5
2026-08-20 03:51:14 -04:00
src = workspace / proj["path"] / entry["path"]
if not src.is_file():
[FIX] mobile: the bundle check refused a real build The checker knew only the pack layout. A real build ships one tar.gz per repository, so it raised "<slug> : index.json absent" and, being guarded by `|| exit 1`, stopped compile_and_run.sh before cap sync — no APK. Broken since 2026-08-20 for anyone on the current mobile main: the parent half of that work landed, the mobile half producing packs never did. It now accepts both layouts, so whichever half lands next, it holds. Two things got better on the way. The byte-for-byte comparison against the source — the only check that proves fidelity rather than coherence — was reporting zero comparisons, because no entry matched the shape it looked for; it now compares twenty. And presence is no longer sampled: streaming all 139 archives costs 6 s and accounts for every one of the 124 350 promised files, where a sample of twenty could not see a ghost it did not draw. The test guarding the ZIP limit demanded a `chunk` field on every file — the pack layout, not the limit. It counts entries now: 278 against 65 535. Its real-bundle class had been taught to skip on this very symptom rather than fail; it runs again. --- FR --- Le vérificateur ne connaissait que la disposition en packs. Une compilation réelle livre un tar.gz par dépôt : il levait « <slug> : index.json absent » et, gardé par `|| exit 1`, arrêtait compile_and_run.sh avant cap sync — pas d'APK. Cassé depuis le 2026-08-20 pour quiconque est sur le main mobile actuel : la moitié parente de ce travail a atterri, la moitié mobile qui produit les packs jamais. Il accepte désormais les deux dispositions : quelle que soit la moitié qui atterrit ensuite, il tient. Deux choses se sont améliorées en chemin. La comparaison octet pour octet contre la source — la seule qui prouve la fidélité et non la cohérence — rapportait zéro comparaison, faute d'entrée à la forme attendue ; elle en compare vingt. Et la présence n'est plus échantillonnée : traverser les 139 archives coûte 6 s et rend compte de chacun des 124 350 fichiers promis, là où vingt tirages ne pouvaient pas voir un fantôme non tiré. Le test qui gardait la limite du ZIP exigeait un champ `chunk` sur chaque fichier — la disposition, pas la limite. Il compte les entrées désormais : 278 pour 65 535. Sa classe sur le vrai bundle avait appris à s'ignorer sur ce symptôme même plutôt qu'à échouer ; elle tourne à nouveau. Assisted-by: Claude Opus 5
2026-08-24 05:50:04 -04:00
return
[FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du contenu. Le côté application est dans le dépôt mobile ; ce commit porte la vérification et retire le contournement. Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées, et 20 fichiers relus depuis les packs identiques octet pour octet à leur source. L'installation le vérifie et échoue sinon : une application qui ne porte pas le code qu'elle doit montrer n'est pas celle demandée. --- EN --- The stopgap has served its time: the repositories were not embedded at all, and the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now enter as packs — 4 MB slices and one index per repository saying where each file lives — which brings the count to 391 entries without losing any content. The app side lives in the mobile repository; this commit carries the verification and drops the workaround. Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002 entries, and 20 files read back from the packs identical byte for byte to their source. The install verifies it and fails otherwise: an app that does not carry the code it must show is not the one that was asked for. Assisted-by: Claude Opus 5
2026-08-20 03:51:14 -04:00
if data != src.read_bytes():
raise ValueError(
f"{proj['slug']} : {entry['path']} diffère de la source"
)
report["compared"] += 1
[FIX] mobile: the bundle check refused a real build The checker knew only the pack layout. A real build ships one tar.gz per repository, so it raised "<slug> : index.json absent" and, being guarded by `|| exit 1`, stopped compile_and_run.sh before cap sync — no APK. Broken since 2026-08-20 for anyone on the current mobile main: the parent half of that work landed, the mobile half producing packs never did. It now accepts both layouts, so whichever half lands next, it holds. Two things got better on the way. The byte-for-byte comparison against the source — the only check that proves fidelity rather than coherence — was reporting zero comparisons, because no entry matched the shape it looked for; it now compares twenty. And presence is no longer sampled: streaming all 139 archives costs 6 s and accounts for every one of the 124 350 promised files, where a sample of twenty could not see a ghost it did not draw. The test guarding the ZIP limit demanded a `chunk` field on every file — the pack layout, not the limit. It counts entries now: 278 against 65 535. Its real-bundle class had been taught to skip on this very symptom rather than fail; it runs again. --- FR --- Le vérificateur ne connaissait que la disposition en packs. Une compilation réelle livre un tar.gz par dépôt : il levait « <slug> : index.json absent » et, gardé par `|| exit 1`, arrêtait compile_and_run.sh avant cap sync — pas d'APK. Cassé depuis le 2026-08-20 pour quiconque est sur le main mobile actuel : la moitié parente de ce travail a atterri, la moitié mobile qui produit les packs jamais. Il accepte désormais les deux dispositions : quelle que soit la moitié qui atterrit ensuite, il tient. Deux choses se sont améliorées en chemin. La comparaison octet pour octet contre la source — la seule qui prouve la fidélité et non la cohérence — rapportait zéro comparaison, faute d'entrée à la forme attendue ; elle en compare vingt. Et la présence n'est plus échantillonnée : traverser les 139 archives coûte 6 s et rend compte de chacun des 124 350 fichiers promis, là où vingt tirages ne pouvaient pas voir un fantôme non tiré. Le test qui gardait la limite du ZIP exigeait un champ `chunk` sur chaque fichier — la disposition, pas la limite. Il compte les entrées désormais : 278 pour 65 535. Sa classe sur le vrai bundle avait appris à s'ignorer sur ce symptôme même plutôt qu'à échouer ; elle tourne à nouveau. Assisted-by: Claude Opus 5
2026-08-24 05:50:04 -04:00
for kind, proj, container, entry in sample:
if kind == "pack":
compare(proj, entry, read_from_pack(container, entry))
drawn = {}
for kind, proj, container, entry in sample:
if kind == "arch":
drawn.setdefault(container, []).append(entry)
# Toutes les archives sont traversées, pas seulement celles que
# l'échantillon tire : la traversée coûte 6 s pour les 139 dépôts et elle
# prouve la présence de CHACUN des fichiers promis. Un échantillon de vingt
# ne verrait pas un fantôme qu'il ne tire pas. Les octets, eux, ne sont lus
# que pour les tirés — c'est la lecture qui coûte, pas la traversée.
for archive, (proj, entries) in arch_pool.items():
here = drawn.get(archive, [])
blobs = read_from_archive(
archive,
[e["path"] for e in here],
promised=[e["path"] for e in entries],
)
report["present"] += len(entries)
for entry in here:
compare(proj, entry, blobs[entry["path"]])
[FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du contenu. Le côté application est dans le dépôt mobile ; ce commit porte la vérification et retire le contournement. Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées, et 20 fichiers relus depuis les packs identiques octet pour octet à leur source. L'installation le vérifie et échoue sinon : une application qui ne porte pas le code qu'elle doit montrer n'est pas celle demandée. --- EN --- The stopgap has served its time: the repositories were not embedded at all, and the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now enter as packs — 4 MB slices and one index per repository saying where each file lives — which brings the count to 391 entries without losing any content. The app side lives in the mobile repository; this commit carries the verification and drops the workaround. Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002 entries, and 20 files read back from the packs identical byte for byte to their source. The install verifies it and fails otherwise: an app that does not carry the code it must show is not the one that was asked for. Assisted-by: Claude Opus 5
2026-08-20 03:51:14 -04:00
return report
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"mobile_root",
nargs="?",
default="mobile/erplibre_home_mobile",
help="racine du dépôt mobile (défaut : mobile/erplibre_home_mobile)",
)
parser.add_argument(
"--workspace",
default="",
help="racine ERPLibre, pour comparer un échantillon à la source",
)
args = parser.parse_args()
root = Path(args.mobile_root)
ws = Path(args.workspace) if args.workspace else None
try:
rep = check(root, ws)
except (OSError, ValueError, KeyError, json.JSONDecodeError) as exc:
print(f" ⚠ transfert des dépôts : {exc}")
return 1
extra = (
f", {rep['compared']} comparés à la source" if rep["compared"] else ""
)
[FIX] mobile: the bundle check refused a real build The checker knew only the pack layout. A real build ships one tar.gz per repository, so it raised "<slug> : index.json absent" and, being guarded by `|| exit 1`, stopped compile_and_run.sh before cap sync — no APK. Broken since 2026-08-20 for anyone on the current mobile main: the parent half of that work landed, the mobile half producing packs never did. It now accepts both layouts, so whichever half lands next, it holds. Two things got better on the way. The byte-for-byte comparison against the source — the only check that proves fidelity rather than coherence — was reporting zero comparisons, because no entry matched the shape it looked for; it now compares twenty. And presence is no longer sampled: streaming all 139 archives costs 6 s and accounts for every one of the 124 350 promised files, where a sample of twenty could not see a ghost it did not draw. The test guarding the ZIP limit demanded a `chunk` field on every file — the pack layout, not the limit. It counts entries now: 278 against 65 535. Its real-bundle class had been taught to skip on this very symptom rather than fail; it runs again. --- FR --- Le vérificateur ne connaissait que la disposition en packs. Une compilation réelle livre un tar.gz par dépôt : il levait « <slug> : index.json absent » et, gardé par `|| exit 1`, arrêtait compile_and_run.sh avant cap sync — pas d'APK. Cassé depuis le 2026-08-20 pour quiconque est sur le main mobile actuel : la moitié parente de ce travail a atterri, la moitié mobile qui produit les packs jamais. Il accepte désormais les deux dispositions : quelle que soit la moitié qui atterrit ensuite, il tient. Deux choses se sont améliorées en chemin. La comparaison octet pour octet contre la source — la seule qui prouve la fidélité et non la cohérence — rapportait zéro comparaison, faute d'entrée à la forme attendue ; elle en compare vingt. Et la présence n'est plus échantillonnée : traverser les 139 archives coûte 6 s et rend compte de chacun des 124 350 fichiers promis, là où vingt tirages ne pouvaient pas voir un fantôme non tiré. Le test qui gardait la limite du ZIP exigeait un champ `chunk` sur chaque fichier — la disposition, pas la limite. Il compte les entrées désormais : 278 pour 65 535. Sa classe sur le vrai bundle avait appris à s'ignorer sur ce symptôme même plutôt qu'à échouer ; elle tourne à nouveau. Assisted-by: Claude Opus 5
2026-08-24 05:50:04 -04:00
where = []
if rep["archives"]:
where.append(f"{rep['archives']} archives")
if rep["packs"]:
where.append(f"{rep['packs']} tranches")
proven = f", {rep['present']} présences prouvées" if rep["present"] else ""
[FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du contenu. Le côté application est dans le dépôt mobile ; ce commit porte la vérification et retire le contournement. Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées, et 20 fichiers relus depuis les packs identiques octet pour octet à leur source. L'installation le vérifie et échoue sinon : une application qui ne porte pas le code qu'elle doit montrer n'est pas celle demandée. --- EN --- The stopgap has served its time: the repositories were not embedded at all, and the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now enter as packs — 4 MB slices and one index per repository saying where each file lives — which brings the count to 391 entries without losing any content. The app side lives in the mobile repository; this commit carries the verification and drops the workaround. Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002 entries, and 20 files read back from the packs identical byte for byte to their source. The install verifies it and fails otherwise: an app that does not carry the code it must show is not the one that was asked for. Assisted-by: Claude Opus 5
2026-08-20 03:51:14 -04:00
print(
f" {rep['repos']} dépôts, {rep['files']} fichiers en"
[FIX] mobile: the bundle check refused a real build The checker knew only the pack layout. A real build ships one tar.gz per repository, so it raised "<slug> : index.json absent" and, being guarded by `|| exit 1`, stopped compile_and_run.sh before cap sync — no APK. Broken since 2026-08-20 for anyone on the current mobile main: the parent half of that work landed, the mobile half producing packs never did. It now accepts both layouts, so whichever half lands next, it holds. Two things got better on the way. The byte-for-byte comparison against the source — the only check that proves fidelity rather than coherence — was reporting zero comparisons, because no entry matched the shape it looked for; it now compares twenty. And presence is no longer sampled: streaming all 139 archives costs 6 s and accounts for every one of the 124 350 promised files, where a sample of twenty could not see a ghost it did not draw. The test guarding the ZIP limit demanded a `chunk` field on every file — the pack layout, not the limit. It counts entries now: 278 against 65 535. Its real-bundle class had been taught to skip on this very symptom rather than fail; it runs again. --- FR --- Le vérificateur ne connaissait que la disposition en packs. Une compilation réelle livre un tar.gz par dépôt : il levait « <slug> : index.json absent » et, gardé par `|| exit 1`, arrêtait compile_and_run.sh avant cap sync — pas d'APK. Cassé depuis le 2026-08-20 pour quiconque est sur le main mobile actuel : la moitié parente de ce travail a atterri, la moitié mobile qui produit les packs jamais. Il accepte désormais les deux dispositions : quelle que soit la moitié qui atterrit ensuite, il tient. Deux choses se sont améliorées en chemin. La comparaison octet pour octet contre la source — la seule qui prouve la fidélité et non la cohérence — rapportait zéro comparaison, faute d'entrée à la forme attendue ; elle en compare vingt. Et la présence n'est plus échantillonnée : traverser les 139 archives coûte 6 s et rend compte de chacun des 124 350 fichiers promis, là où vingt tirages ne pouvaient pas voir un fantôme non tiré. Le test qui gardait la limite du ZIP exigeait un champ `chunk` sur chaque fichier — la disposition, pas la limite. Il compte les entrées désormais : 278 pour 65 535. Sa classe sur le vrai bundle avait appris à s'ignorer sur ce symptôme même plutôt qu'à échouer ; elle tourne à nouveau. Assisted-by: Claude Opus 5
2026-08-24 05:50:04 -04:00
f" {' et '.join(where) or 'aucun conteneur'}"
f" ({rep['checked']} relus{extra}{proven})"
[FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du contenu. Le côté application est dans le dépôt mobile ; ce commit porte la vérification et retire le contournement. Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées, et 20 fichiers relus depuis les packs identiques octet pour octet à leur source. L'installation le vérifie et échoue sinon : une application qui ne porte pas le code qu'elle doit montrer n'est pas celle demandée. --- EN --- The stopgap has served its time: the repositories were not embedded at all, and the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now enter as packs — 4 MB slices and one index per repository saying where each file lives — which brings the count to 391 entries without losing any content. The app side lives in the mobile repository; this commit carries the verification and drops the workaround. Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002 entries, and 20 files read back from the packs identical byte for byte to their source. The install verifies it and fails otherwise: an app that does not carry the code it must show is not the one that was asked for. Assisted-by: Claude Opus 5
2026-08-20 03:51:14 -04:00
)
return 0
if __name__ == "__main__":
sys.exit(main())