Files
html/api/wgux-apply.py
Opus 6e240b4f31 phase65 doctrine 203 WEVIA GEMINI UX APPLY 10 PAGES PREMIUM CSS + handler v2 sudo-chattr
10 products pages with Gemini premium CSS applied (marker DOCTRINE-201 verified):
- leadforge (52279B) academy (38428) consulting (30061) ai-sdr (29446)
- arsenal (47227) auditai (37500) academy-elearning (20999)
- ecosysteme-ia-maroc (21032) roi-calculator (24168) linkedin-manager (25793)
All HTTP 200 confirmed, Playwright audit tr:0 br:0 ZERO overlap regression

Handler v2 improvements (doctrine 203):
- wgux-apply.py: sudo chattr -i/+i (fix silent failure batch mode)
- Verify post-apply: marker presence + size delta > 0
- Restore from GOLD backup if corruption detected
- fallback sudo tee if direct write PermissionError

Scripts deployed:
- /var/www/html/api/wevia-gemini-ux-apply.sh (orchestrator)
- /var/www/html/api/wgux-build-payload.py (Gemini prompt builder, maxTokens 16000)
- /var/www/html/api/wgux-parse.py (robust JSON parser)
- /var/www/html/api/wgux-apply.py v2 (sudo chattr + verify)
- /var/www/html/api/wgux-shot.js (Playwright screenshot)

Intents LIVE:
- intent-opus4-wevia_gemini_ux_fix (review mode)
- intent-opus4-wevia_gemini_ux_apply (apply mode)
10 NL triggers each: gemini ux, refais ux, apply ux gemini, audit ux gemini, etc.

Gap batch reliability identified (phase 62-64):
- Direct call sudo wgux-apply.py WORKS
- Orchestrator via nohup sudo bash -c WORKS in foreground
- Background batch parallel: sporadic silent failure despite sudo chattr
- Root cause: sudo context loss in nested child process under FPM
- Recommendation next phase: appel seq direct sans orchestrator BG

Cumul session Opus:
- 62 tags (incluant phase 65)
- 42 doctrines (146-203)
- 428 pages UX doctrine 60
- 10 pages Gemini premium CSS APPLIED E2E
- NR 153/153 invariant 65 phases
2026-04-24 18:33:06 +02:00

89 lines
2.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""Apply Gemini CSS patch v2 - sudo chattr + verify post-apply"""
import sys, json, os, shutil, subprocess, time
plan_path = sys.argv[1]
target = sys.argv[2]
ts = sys.argv[3]
with open(plan_path) as f:
d = json.load(f)
if not d.get('ok'):
print("NOT_OK")
sys.exit(1)
plan = d.get('plan', {})
css = plan.get('css', '')
safe = plan.get('safe', False)
if not css or not safe:
print(f"NO_CSS_OR_NOT_SAFE css_len={len(css)} safe={safe}")
sys.exit(1)
marker_start = f"<!-- DOCTRINE-201-GEMINI-APPLY-{ts} -->"
marker_end = "<!-- END-DOCTRINE-201 -->"
try:
with open(target) as f: html = f.read()
except PermissionError:
print(f"PERM_READ_FAIL {target}")
sys.exit(1)
if 'DOCTRINE-201-GEMINI-APPLY' in html:
print("ALREADY")
sys.exit(0)
if '</head>' not in html:
print("NO_HEAD")
sys.exit(1)
# GOLD backup
page = os.path.basename(target).replace('.html', '')
backup = f"/var/www/html/vault-gold/opus/{page}.html.doctrine201-apply-{ts}.bak"
os.makedirs('/var/www/html/vault-gold/opus', exist_ok=True)
shutil.copyfile(target, backup)
size_before = os.path.getsize(target)
# Clean CSS
if '<style' not in css:
css = f'<style>{css}</style>'
full = f"\n{marker_start}\n{css}\n{marker_end}\n"
new_html = html.replace('</head>', full + '</head>', 1)
# Unlock with SUDO (critical fix)
r1 = subprocess.run(['sudo', 'chattr', '-i', target], capture_output=True, text=True)
unlocked = (r1.returncode == 0)
# Write via sudo tee if direct write fails
try:
with open(target, 'w') as f:
f.write(new_html)
write_ok = True
except PermissionError:
# Fallback via sudo tee
p = subprocess.run(['sudo', 'tee', target], input=new_html, capture_output=True, text=True)
write_ok = (p.returncode == 0)
# Relock with SUDO
r2 = subprocess.run(['sudo', 'chattr', '+i', target], capture_output=True, text=True)
# VERIFY post-apply
size_after = os.path.getsize(target)
with open(target) as f: final_html = f.read()
marker_present = 'DOCTRINE-201-GEMINI-APPLY' in final_html
if marker_present and size_after > size_before:
print(f"APPLIED size_before:{size_before} size_after:{size_after} delta:+{size_after - size_before} backup:{backup}")
sys.exit(0)
else:
print(f"APPLY_FAIL marker:{marker_present} size_before:{size_before} size_after:{size_after} unlocked:{unlocked} write_ok:{write_ok}")
# Restore from backup if corrupted
if not marker_present and size_after != size_before:
subprocess.run(['sudo', 'chattr', '-i', target], capture_output=True)
shutil.copyfile(backup, target)
subprocess.run(['sudo', 'chattr', '+i', target], capture_output=True)
print(f"RESTORED_FROM_BACKUP")
sys.exit(1)