Files
a0_software_orchestrator/utils/migrate_legacy_project_names.py
T
Software Orchestrator 5769c1cd22 Initial commit: a0_software_orchestrator v1.0
- Auto-Registration-Bug behoben (register_project/get_project_id/resolve_project Trennung)
- 25 Tests gruen (Pytest)
- block_compactor-Tool refactored (Option B: Soft-Check statt Hard-Block)
- 4 Restbaustellen gefixt
- DB-Schema: plugin_settings-Tabelle hinzugefuegt
- 3 Schattenprojekte aus DB geloescht
- Plan v3 + Refactor-Plan + Worklog dokumentiert
2026-06-16 22:13:06 +00:00

167 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""migrate_legacy_project_names archiviert Projekte mit ungültigen Namen.
Bug 1+2 Begleit-Skript (Plan v3 §3.6):
- Iteriert über alle Projekte in der DB.
- Prüft jeden Namen gegen PROJECT_NAME_PATTERN + FORBIDDEN_PROJECT_NAMES.
- Markiert ungültige Namen mit status='archived' in project_state (idempotent).
- Bestehende 'active'-Projekte mit ungültigem Namen werden zu 'archived'.
- Bereits archivierte werden NICHT doppelt verändert (idempotent).
Aufruf:
python3 utils/migrate_legacy_project_names.py [--dry-run] [--verbose]
Defaults:
- --dry-run: nur Report, keine DB-Schreibzugriffe.
- --verbose: zusätzlich pro Projekt-Status ausgeben.
Exit-Code:
- 0: Erfolg (inkl. 0 archivierte Projekte).
- 1: DB-Fehler.
"""
from __future__ import annotations
import argparse
import os
import sys
from typing import List, Tuple
PLUGIN_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Run from /a0 to have usr.plugins.* resolvable.
from usr.plugins.a0_software_orchestrator.helpers.db_state_store import (
_db, _db_error_handler, _validate_project_name,
PROJECT_NAME_PATTERN, FORBIDDEN_PROJECT_NAMES,
)
from usr.plugins.a0_software_orchestrator.helpers.exceptions import PluginDBError
@_db_error_handler
def list_projects_with_states() -> List[Tuple[int, str, str]]:
"""Returns Liste von (project_id, project_name, current_status).
Liest aus projects JOIN project_state. Projekte ohne project_state-Eintrag
bekommen den Status '<none>'.
"""
db = _db()
rows = db.conn.execute("""
SELECT p.id, p.name, COALESCE(ps.status, '<none>') as status
FROM projects p
LEFT JOIN project_state ps ON ps.project_id = p.id
ORDER BY p.id
""").fetchall()
return [(int(r[0]), str(r[1]), str(r[2])) for r in rows]
@_db_error_handler
def archive_project(project_id: int, reason: str) -> None:
"""Markiert ein Projekt als 'archived' in project_state. Idempotent.
Zwei Schritte statt ON CONFLICT DO UPDATE SET mit updated_at: das
vermeidet sporadische 'no such column: updated_at'-Fehler bei
cross-Singleton-Connections (zwei verschiedene Modul-Importe der
PatternDB führen zu zwei Connections auf die gleiche DB-Datei).
"""
db = _db()
# 1. Status archivieren (INSERT oder UPDATE)
db.conn.execute("""
INSERT INTO project_state (project_id, status, phase, last_active_at, updated_at)
VALUES (?, 'archived', 'archived', datetime('now'), datetime('now'))
ON CONFLICT(project_id) DO UPDATE SET
status = 'archived',
phase = 'archived',
last_active_at = datetime('now')
""", (project_id,))
# 2. updated_at explizit setzen (separates Statement, robuster)
db.conn.execute("""
UPDATE project_state SET updated_at = datetime('now')
WHERE project_id = ?
""", (project_id,))
# NOTE: projects-Tabelle hat kein updated_at-Feld (Spalten: id, name,
# git_url, description, tech_stack, created_at, project_path, …).
# Daher kein UPDATE projects hier. Wenn projects.updated_at per
# Migration ergänzt wird, kann dieser Block nachgerüstet werden.
db.conn.commit()
sys.stderr.write(f" [ARCHIVED] project_id={project_id} reason={reason}\n")
def main() -> int:
ap = argparse.ArgumentParser(
description="Archiviere Projekte mit ungültigen Namen (Pattern/Blacklist)."
)
ap.add_argument(
"--dry-run", action="store_true",
help="Nur anzeigen, welche Projekte archiviert würden; keine DB-Schreibzugriffe.",
)
ap.add_argument(
"--verbose", "-v", action="store_true",
help="Zusätzlich pro Projekt den Validierungs-Status ausgeben.",
)
args = ap.parse_args()
try:
projects = list_projects_with_states()
except PluginDBError as e:
print(f"ERROR: DB-Fehler beim Lesen der Projekte: {e}", file=sys.stderr)
return 1
if not projects:
print("No projects found in DB.")
return 0
print(f"=== Legacy-Name Migration (dry-run={args.dry_run}) ===")
print(f"Pattern: {PROJECT_NAME_PATTERN.pattern}")
print(f"Blacklist: {sorted(FORBIDDEN_PROJECT_NAMES)}")
print(f"Projects in DB: {len(projects)}")
print()
active_count = 0
archived_count = 0
already_archived_count = 0
invalid_count = 0
for pid, name, current_status in projects:
try:
_validate_project_name(name)
valid = True
invalid_reason = None
except ValueError as e:
valid = False
invalid_reason = str(e)
if valid:
active_count += 1
if args.verbose:
print(f" [OK ] pid={pid:3d} name={name!r:40s} status={current_status}")
continue
invalid_count += 1
if current_status == "archived":
already_archived_count += 1
print(f" [SKIP] pid={pid:3d} name={name!r} already archived; invalid={invalid_reason}")
continue
print(f" [BAD ] pid={pid:3d} name={name!r} status={current_status} reason={invalid_reason}")
if not args.dry_run:
try:
archive_project(pid, reason=invalid_reason)
archived_count += 1
except PluginDBError as e:
print(f"ERROR: konnte pid={pid} nicht archivieren: {e}", file=sys.stderr)
return 1
print()
print(f"Summary:")
print(f" total projects: {len(projects)}")
print(f" valid (active): {active_count}")
print(f" invalid (total): {invalid_count}")
print(f" newly archived: {archived_count}")
print(f" already archived: {already_archived_count}")
print(f" mode: {'DRY-RUN' if args.dry_run else 'APPLIED'}")
return 0
if __name__ == "__main__":
sys.exit(main())