Add packaged user manual and configuration guides to web console

This commit is contained in:
Nico committed 2026-10-23 15:18:02 +02:00
1 parent b42bafb711
commit 085a5c4026
17 files changed
+586 -7

No files matched your search

+3
View File
@@ -21,6 +21,9 @@ The [Linux deployment guide](docs/linux-deployment.md) covers systemd services,
protected credentials, HTTPS and non-billable readiness checks.
The [integration key manager](docs/integration-keys.md) lets signed-in users
create, reveal and revoke project-scoped machine credentials.
The web tab **Benutzeranleitung** includes the [user guide](docs/user-guide.md)
and operator documentation, including project/source/database configuration.
After editing bundled documentation, run `python scripts/build_user_manual.py`.
Requires Python 3.11 or newer.
+5 -2
View File
@@ -4,7 +4,8 @@ Die API erstellt interne Case-Pakete und veröffentlicht sie atomar in der Inbox
Externe Programme liefern Metadaten und Dateien; sie erzeugen und bearbeiten keine
internen Manifeste. Die API führt keine Analyse und keine LLM-Aufrufe aus. Ein
dauerhaft laufender [Analyseworker](analysis-worker.md) übernimmt anschließend die
Aufnahme und Verarbeitung. Die allgemeine Weboberfläche bleibt ein weiterer Ausbauschritt.
Aufnahme und Verarbeitung. Die [Weboberfläche](web-console.md) bietet Übersicht,
Auftragsübergabe, Testerantworten und Ergebnisdownloads.
## Lokaler Start
@@ -17,7 +18,9 @@ $env:VBA_INTAKE_TOKEN = [Convert]::ToHexString([System.Security.Cryptography.Ran
```
Der API-Token ist ein eigener Zugangsschlüssel, kein Gemini-Key. Programme erhalten
je einen Client und einen Token aus einer serverseitigen Umgebungsvariable. Der
vorzugsweise einen Schlüssel aus der [Web-Schlüsselverwaltung](integration-keys.md).
Alternativ bleiben Clients mit Token aus einer serverseitigen Umgebungsvariable
unterstützt; der Beispielstart oben zeigt diese Variante. Der
Token benötigt 32–256 Zeichen aus `A-Z a-z 0-9 _ -`. Auf älterem Windows PowerShell
kann ein solcher Token über einen Passwortmanager erzeugt und als Variable gesetzt
werden. Der Beispielstart bindet nur an `127.0.0.1:8080`. Für entfernte Nutzung sind
+114
View File
@@ -0,0 +1,114 @@
# Projekt und Recherchezugriffe konfigurieren
Die Projektkonfiguration wird über Dateien verwaltet, nicht über die Webseite.
Sie legt fest, welche Werkzeuge ein Auftrag verwenden darf. Ein aufrufendes
Programm kann weder Repository noch DB-Zugänge oder Budgets mit einem Auftrag
überschreiben. Aktuell ist das Profil `wow/vcore` mit dem Importadapter `vcore`
implementiert; andere Projekttypen erfordern entsprechende Adapter und Profile.
## Zuordnung in api.toml
Ein Projektelement ordnet eine feste Projekt-ID einem Profil und dessen
Konfigurationsdateien zu. Relative Pfade beziehen sich auf die API-Konfigurationsdatei.
Der Erfassungsworker muss genau dieselbe Projekt-/Profilzuordnung senden und
sein Integrationsschlüssel muss für das Projekt freigegeben sein.
```toml
[[projects]]
id = "viano-core"
type = "wow"
profile = "vcore"
adapter = "vcore"
planning_mode = "joint"
[projects.settings]
project_config = "project.toml"
llm_config = "llm.toml"
investigation_config = "investigation.bounded.toml"
budget_config = "budget.human-tests.toml"
```
Die Linux-Vorlagen stehen unter `deploy/linux/`; der Installer legt die
Konfiguration unter `/etc/viano-bug-analysis` an. Er überschreibt bestehende
Dateien nicht. Die ausführliche Vcore-Projektvorlage steht unter
`config/project.example.toml`, die Relationsregeln unter
`config/project-relations.vcore.toml`.
## Source und Datenbank in project.toml
```toml
relations_file = "project-relations.vcore.toml"
[project]
type = "wow"
profile = "vcore"
[source]
path = "/srv/viano-bug-analysis/viano-core"
url = "https://gitea.viano-software.de/Nico/Viano-Core"
commit = "VOLLSTAENDIGEN_40_STELLIGEN_COMMIT_EINTRAGEN"
[database]
host = "127.0.0.1"
port = 3306
database = "cata_world"
user = "viano_reader"
password_env = "VIANO_DB_PASSWORD"
snapshot_id = "KENNUNG_DER_TATSAECHLICHEN_DATENGRUNDLAGE"
tables = ["creature_template", "creature"]
```
Das Beispiel zeigt nur den Aufbau und enthält absichtlich ungültige Platzhalter.
Die beiden Tabellen sind keine vollständige Vcore-Freigabeliste. Für das echte
Profil die vollständige Vorlage einschließlich Limits, Enrichment und
Analysewerkzeugregeln verwenden und passend prüfen.
Der Source-Checkout muss lokal vorhanden sein und den konfigurierten Commit
enthalten. Die URL allein klont oder aktualisiert kein Repository. Der Worker
liest Git-Objekte dieses festen Stands und benötigt keine Schreibrechte auf den
Checkout. Die Linux-Vorlage enthält eine gezielte Git-Vertrauenskonfiguration
für den root-eigenen Checkout. Neue Source-Stände bewusst bereitstellen und
deren vollständigen Commit konfigurieren.
Der DB-Administrator legt einen Benutzer mit ausschließlich passenden SELECT-
Rechten an. Zusätzlich begrenzt `tables` die zugänglichen Tabellen. Diese
Freigabeliste ersetzt keine serverseitigen Rechte. Account- oder Charakterdaten
gehören nicht zur standardmäßigen World-Recherche. Für entfernte DBs einen
geschützten Netzwerkweg nutzen; der aktuelle Provider konfiguriert selbst kein TLS.
Das Passwort steht entweder in der durch `password_env` benannten
Umgebungsvariable oder einer separaten geschützten Datei über `password_file`.
Nur eine dieser Varianten setzen. Unter Linux lädt der Worker beispielsweise
`VIANO_DB_PASSWORD` aus seiner root-geschützten `worker.env`.
Passwörter nicht in Fallakten, LLM-Prompts oder Versionsverwaltung ablegen.
Die LLM bekommt Werkzeugergebnisse statt Zugangsdaten.
## Weitere Konfigurationen
| Datei | Zuständigkeit |
| --- | --- |
| api.toml | HTTP-Bindung, Workspace, Projekte, Nutzerdatei und optionale env-basierte Maschinenclients |
| worker.toml | Verweis auf api.toml, Polling, Wiederholungsabstände, Speicherreserve und Telemetrie |
| llm.toml | Anbieter, Modell, Schlüssel-Environment, Timeouts, Ausgabe-/Denkeinstellungen und Cache |
| investigation.bounded.toml | Untersuchungsmodus und begrenztes Kontextfenster |
| budget.human-tests.toml | Fachliche Budgetregeln und menschliche Testanfragen |
| telemetry.toml | Heartbeats und lokale Betriebsbeobachtung |
| project-relations.vcore.toml | Projektspezifische Beziehungen für Recherchewerkzeuge |
Die versionierte Modellvorlage `config/llm.gemini-cache.example.toml` verwendet
`GEMINI_API_KEY`. Das ist unabhängig vom Integrationsschlüssel für externe
Auftragsübergaben. Die API erhält `api.env`; der Analyseworker erhält
`worker.env` mit Cloud- und DB-Zugangsdaten.
## Prüfen und ändern
Der Befehl `python -m viano_bug_analysis.deployment check` prüft mit Rolle und
Konfigurationspfad die Betriebsbereitschaft ohne Modellanfrage. Die
[Linux-Anleitung](linux-deployment.md) zeigt die vollständigen Befehle unter der
tatsächlichen Dienstidentität. Die Prüfung umfasst Git-Objekte und freigegebene
DB-Schemata; die Gültigkeit des Cloud-Schlüssels wird nicht durch einen bezahlten
Modellaufruf getestet. `--offline` prüft keine Git-/DB-Verbindung.
Vor Code- oder Profiländerungen den Worker kontrolliert anhalten und die
Auswirkungen auf laufende Akten prüfen. Der Worker bindet Fälle an ihren
Konfigurationsstand. Eine Änderung ist kein Weg zum Zurücksetzen von Budgets;
abweichende Bindungen können eine Betreiberprüfung erfordern. Bestehende Dateien
und Zugangsdaten vor Änderungen sichern.
+42
View File
@@ -0,0 +1,42 @@
# Benutzeranleitung in der Weboberfläche – 2026-10-23
Ergebnis: eigener Reiter mit neun Dokumenten, Inhaltsverzeichnis, sicheren
Markdown-Darstellungen und Markdown-Download. Neue allgemeine Bedienungs- und
Funktionsbeschreibung sowie Projekt-/Source-/DB-/Modellkonfigurationsanleitung.
Vorhandene Einrichtungsanleitungen werden eingebunden, nicht als Serverpfade
verlinkt. Die Übergabe-API-Dokumentation wurde an den vorhandenen Webbetrieb
und die Web-Schlüsselverwaltung angepasst.
Quellen: `docs/` und `deploy/capture-worker/README.md`, feste Auswahlliste in
`scripts/build_user_manual.py`. Die generierte Webressource wird mit dem Paket
ausgeliefert. Ein Drift-Test verhindert unbemerkte Abweichungen von den Quellen.
Keine tatsächlichen Konfigurationen oder Zugangsdaten im Bundle.
Validierung:
```text
python -m pytest tests/test_user_manual.py tests/test_dashboard.py tests/test_web_auth.py tests/test_human_web.py -q
28 passed, 1 dependency deprecation warning
node tests/dashboard-smoke.mjs
PASS, console_errors: [], overflow: false
python scripts/build_user_manual.py --check
PASS
node --check src/viano_bug_analysis/web/user-manual.js
PASS
pip wheel --no-deps --no-index --no-build-isolation .
PASS, manual JSON and renderer included in wheel
```
Nach Ergänzung des Pflegehinweises die manuelspezifischen Tests erneut bestanden.
Browser prüft Login, Dokumentauswahl, Inhaltsverzeichnis, Linux-Befehle,
Konfigurationstabellen, HTML-/URL-Abwehr im Renderer und Logout. Desktop- und
Mobilansicht visuell kontrolliert. Quellen bleiben als Markdown separat pflegbar.
Die Darstellung unterstützt die im Korpus verwendeten Überschriften, Listen,
Tabellen, Codeblöcke, Inline-Code, Hervorhebungen und bekannte Dokumentverweise;
beliebiges HTML wird als Text angezeigt.
Vor lokalen Tests Trials 16.939.129.428 Bytes und Fehlerkorpus 2.471.041 Bytes;
kleine temporäre synthetische Testdaten und Paket-Builds halten das Gesamtbudget
weiter unter 20 GB. Temporäres Wheel nach Prüfung entfernt. Keine realen
Fallkopien, Modellanfragen, Cloudkosten, Dienststarts oder Serveränderungen.
`docs/architecture.md` unverändert. Kein Push.
+153
View File
@@ -0,0 +1,153 @@
# BugAnalysis verwenden
BugAnalysis untersucht gemeldete Softwarefehler und übergibt das Ergebnis an die
nächste zuständige Stelle. Das Programm erstellt selbst keinen fertigen Patch
und spielt keine Änderungen auf einen Server ein. Der aktuelle Erfassungsadapter
ist für WoW/Vcore eingerichtet.
## So arbeitet BugAnalysis
1. Eine Person oder ein Erfassungsprogramm übergibt Bugbeschreibung und
Diagnosearchive. Die Übergabe-API erstellt daraus eine Fallakte.
2. Ein dauerhaft laufender Analyseworker übernimmt den Auftrag und lädt die
Konfiguration des angegebenen Projekts.
3. Der Casebuilder wertet Beobachtungen und Telemetrie aus. Er kann relevante
Identitäten mit den konfigurierten Recherchewerkzeugen auflösen.
4. Die LLM interpretiert den Fall und entwickelt Hypothesen. Im gemeinsamen
Planungsmodus geschieht beides in einem Aufruf.
5. Die Untersuchung prüft Hypothesen mit Source- und DB-Werkzeugen. Eine knappe
Untersuchungsschleife sammelt Ergebnisse; Prüfungen und Konsolidierungen
übernehmen diese in den strukturierten Arbeitsstand. Werkzeuge lesen Daten;
Zugangsdaten erhält die LLM nicht.
6. Die finale Diagnose bewertet die Befunde und kann einen Frühabschluss
annehmen oder weitere Untersuchung verlangen. Fachliche Budgets begrenzen
einzelne Untersuchungen. Ein bestimmter Erfolg ist nicht garantiert.
7. Das Ergebnis enthält Diagnose und Aufträge für Codingagent und menschlichen
Entwickler. Ein Auftrag darf ausdrücklich erklären, dass keine Arbeit für
den jeweiligen Empfänger vorliegt. Fehlende Daten zur Umsetzung können als
menschlicher Auftrag übergeben werden, ohne die erfolgreiche Analyse zu sperren.
Ein Ergebnis bleibt eine begründete Analyse, keine Garantie für einen wirksamen
Fix. Nach Umsetzung und Tests können Rückmeldungen eine neue Analyserevision
derselben Akte auslösen. Behauptungen, Testerbeobachtungen und bestätigte Befunde
sollten bei der Bewertung auseinandergehalten werden.
## Anmeldung und Start
Die Webadresse im Browser öffnen und mit Benutzername und Passwort anmelden.
Benutzerkonten werden ausschließlich per privilegierter Konsole angelegt;
eine Selbstregistrierung gibt es nicht. Alle menschlichen Benutzer haben die
gleichen Rechte für alle Projekte. Externe Programme benutzen eigene
Integrationsschlüssel mit Projektfreigaben.
API und Analyseworker sind getrennte Dienste. Das Öffnen der Webseite startet
keinen Worker und sendet keine Modellanfrage. Der laufende Worker verarbeitet
Aufträge selbstständig; die Webseite ist die Übersicht und alternative Bedienung.
## Übersicht und laufende Fälle
Die Übersicht zeigt erreichbare Worker, den bearbeiteten Fall und dessen
Fortschrittsbalken. Der Balken folgt abgeschlossenen Phasen, nicht vergangener
Zeit. Während einer längeren Untersuchung kann er daher unverändert bleiben.
Abgeschlossene Fälle verschwinden aus der aktiven Workeransicht.
Die Diagramme zeigen gemessenen Tokenverbrauch. Korrektur- und Wiederholungsaufrufe
bilden eine eigene Kategorie. Denktokens sind bereits in den Ausgabetokens
enthalten und werden nicht doppelt addiert. Fehlende Messwerte werden als solche
ausgewiesen. Diese Zahlen sind keine verbindliche Cloud-Abrechnung und umfassen
nur die erhaltenen Akten im gewählten Messumfang.
Über Fallnummern lässt sich die Detailansicht öffnen: Beobachtungen, Einordnung,
Hypothesen, Befunde, Diagnose und Aufträge. Frühere Revisionen bleiben über die
Revisionsauswahl einsehbar. Die vollständigen Daten stehen im herunterladbaren ZIP.
## Neue Aufträge und Rückgaben
Unter **Auftrag übergeben** Projekt, Bug-ID, Fallname und Beobachtungen eingeben.
Für einen neuen Vcore-Fall das wowdiag-Archiv und möglichst das dazugehörige
wowsrc-Archiv derselben Erfassung auswählen. Die Bug-ID muss mit den Archiven
übereinstimmen. Zusätzliche Dateien lassen sich als Anhänge hinzufügen.
Nach dem Absenden bestätigt die API die Annahme. Das ist noch kein Analyseerfolg.
Bei einer unterbrochenen Übergabe verwendet das Formular bei unveränderten
Eingaben denselben Idempotenzschlüssel, damit kein doppelter Auftrag entsteht.
Für eine Rückgabe den Auftragstyp **Abgeschlossene Akte zurückgeben** wählen,
die aktuelle abgeschlossene Case-ZIP hochladen und Ergebnis sowie neue
Beobachtungen eintragen. Beispielsweise kann ein getesteter Patch den Fehler
nicht behoben haben. BugAnalysis ergänzt die Akte und öffnet eine neue Revision.
Blockierte oder noch laufende Akten sind keine Abschlussrückgaben.
Automatische Erfassung erfolgt über den wowdiag-Worker und dieselbe Übergabe-API.
Externe Programme erstellen keine internen Case-Manifeste selbst.
## Testanfragen beantworten
Die Seite **Testanfragen** listet offene menschliche Recherche- und Testfragen
mit Projekt. Nach Zeitpunkt oder Projekt sortieren, eine Anfrage auswählen und
zunächst **Bearbeiten** drücken. Erst dann wird das Antwortformular freigegeben.
Die Anfrage ist während der Session exklusiv gebunden. Andere Personen können
sie nicht gleichzeitig bearbeiten. Beobachtungen und verlangte Angaben sachlich
eintragen und absenden. Bei Sessionablauf oder Abmeldung wird die Bearbeitung
wieder freigegeben. Nach gültiger Antwort setzt der Worker die Analyse mit dem
vorhandenen Stand fort; fachliche Budgets werden nicht erneuert.
Eine Testanfrage während der Ursachenklärung ist etwas anderes als ein
Human-Dev-Auftrag im fertigen Ergebnis. Letzterer gehört zur weiteren Umsetzung.
## Bearbeitete Fälle und Downloads
Unter **Bearbeitete Fälle** stehen Ergebnisse, übergebene Fälle, blockierte Fälle
und gescheiterte Analysen in einer gemeinsamen Liste. Projekt, Kategorie und
Suchbegriff können kombiniert werden; die Sortierung ist separat wählbar.
Links lädt der Pfeilknopf die aktuelle vollständige Fallakte als ZIP herunter.
Ein Download ändert den Fallstatus nicht und bestätigt keine Übergabe.
| Zustand | Bedeutung |
| --- | --- |
| Ergebnis bereit / OUTPUT_READY | Analyseergebnis für die weitere Bearbeitung; Routingziel Outbox |
| Übergeben / HANDED_OFF | Die Akte wurde intern als übergeben markiert |
| Blockiert / BLOCKED | Analyse wartet, beispielsweise auf Testerantwort oder technische Behebung |
| Analyse gescheitert / FAILED_TO_ANALYZE | Analyse konnte nicht erfolgreich abschließen; Routingziel Review |
Blockiert bedeutet nicht abgeschlossen. Sperrgrund und Detailansicht erklären
die nächsten Schritte. Technische Fehler oder unzureichende Eingaben sind von
einem fachlichen Diagnoseergebnis zu unterscheiden. Die Ablagespalte zeigt das
Routingziel, keinen unabhängigen Verzeichnisbestand.
## Betrieb steuern
Die Betriebssteuerung in der Übersicht erlaubt das Pausieren neuer Fälle eines
Projekts. Bereits begonnene Fälle dürfen weiterlaufen. Geeignete technische
Fehlerfälle können über eine Fortsetzungsanforderung nach Behebung wieder
freigegeben werden. Diese Aktion ist kein beliebiger Neustart und setzt keine
Budgets zurück. Menschliche Testantworten werden auf ihrer eigenen Seite erfasst.
## Programme anbinden
Unter **API-Schlüssel** einen Programmnamen und die erlaubten Projekte auswählen.
Den erzeugten Schlüssel über **Anzeigen** oder **Kopieren** abrufen und im
aufrufenden Dienst geschützt hinterlegen. Schlüssel sind später erneut anzeigbar.
**Widerrufen** verhindert weitere authentifizierte Anfragen mit diesem Schlüssel.
Die Schlüssel dienen zur Auftragsübergabe und zum lesenden Projektzugriff;
Maschinenprogramme können darüber keine anderen Schlüssel verwalten oder
menschliche Testerantworten ausfüllen. Ein Integrationsschlüssel ist kein
Gemini- oder anderer Modellanbieter-Schlüssel.
## Einrichtung und Hilfe
- [Linux-Installation und Betrieb](linux-deployment.md)
- [Projekt-, Source-, DB- und Modellkonfiguration](project-configuration.md)
- [Übergabe-API und Rückgaben](intake-api.md)
- [Analyseworker und Wiederanlauf](analysis-worker.md)
- [Benutzerkonten](user-accounts.md)
- [Integrationsschlüssel und deren Sicherung](integration-keys.md)
- [Weboberfläche im Detail](web-console.md)
- [Erfassungsworker v0.4](capture-worker.md)
Vor dem ersten Feldbetrieb auf dem Zielserver Bereitschaftsprüfung, HTTPS,
Dienstneustart und Backups prüfen. Danach einen kontrollierten echten Auftrag
einschließlich Rückfrage und Ergebnisabholung durchführen. Die LLM-Zugriffe
erzeugen erst beim laufenden Analysebetrieb Kosten.
+19
View File
@@ -15,6 +15,25 @@ Für entfernte Zugriffe HTTPS verwenden. Der Analyseworker läuft unabhängig da
[Analyseworker](analysis-worker.md) beschrieben. Das Öffnen der Übersicht löst
keine Modellanfragen aus.
## Benutzeranleitung
Der Reiter **Benutzeranleitung** enthält die allgemeine Bedienung und
Funktionsweise sowie Anleitungen für Linux, Projekt-/DB-/Sourcekonfiguration,
Übergabe-API, Analyseworker, Nutzerkonten, Integrationsschlüssel, Weboberfläche
und Erfassungsworker. Dokumentauswahl, Inhaltsverzeichnis und interne Verweise
erlauben Navigation ohne Server-Dateizugriff. Markdown-Downloads sind ebenfalls
möglich. Der Reiter benötigt eine angemeldete Sitzung; sein Aufruf startet keine
Analyse. Tabellen, Codebeispiele und Überschriften werden als sichere DOM-Elemente
dargestellt, eingebettetes HTML wird nicht ausgeführt.
Für Dokumentationsänderungen die ursprünglichen Markdown-Dateien unter `docs/`
beziehungsweise `deploy/capture-worker/README.md` bearbeiten und danach
`python scripts/build_user_manual.py` ausführen. Die feste Auswahlliste steht im
Skript; tatsächliche lokale Konfigurationen oder Secrets werden nicht gebündelt.
`python scripts/build_user_manual.py --check` und die Tests erkennen veraltete
Webfassungen. Das erzeugte `web/user-manual.json` wird mit dem Python-Paket
ausgeliefert, sodass keine lokale Kopie des Dokumentationsordners nötig ist.
## Worker und Fortschritt
Die Startseite listet lebende Worker und darunter den jeweils bearbeiteten Fall
+1 -1
View File
@@ -21,7 +21,7 @@ where = ["src"]
[tool.setuptools.package-data]
"viano_bug_analysis.schemas" = ["*.json"]
"viano_bug_analysis.llm.prompts" = ["*.txt"]
"viano_bug_analysis.web" = ["*.html", "*.css", "*.js"]
"viano_bug_analysis.web" = ["*.html", "*.css", "*.js", "user-manual.json"]
[tool.pytest.ini_options]
testpaths = ["tests"]
+38
View File
@@ -0,0 +1,38 @@
"""Bundle selected, non-secret operator docs for installed web applications.
Edit the original Markdown files, then run this script. --check detects drift.
"""
import argparse
import json
from pathlib import Path
ROOT=Path(__file__).resolve().parents[1]
DOCUMENTS=(
('user-guide','Verwendung und Funktionsweise','docs/user-guide.md'),
('linux-deployment','Linux-Installation und Betrieb','docs/linux-deployment.md'),
('project-configuration','Projekt-, Source-, DB- und Modellkonfiguration','docs/project-configuration.md'),
('intake-api','Übergabe-API und Rückgaben','docs/intake-api.md'),
('analysis-worker','Analyseworker und Wiederanlauf','docs/analysis-worker.md'),
('user-accounts','Benutzerkonten','docs/user-accounts.md'),
('integration-keys','Integrationsschlüssel','docs/integration-keys.md'),
('web-console','Weboberfläche im Detail','docs/web-console.md'),
('capture-worker','Erfassungsworker v0.4','deploy/capture-worker/README.md'),
)
TARGET=ROOT/'src/viano_bug_analysis/web/user-manual.json'
def bundle():
return {'format_version':1,'documents':[{'id':identifier,'title':title,
'markdown':(ROOT/path).read_text(encoding='utf-8')} for identifier,title,path in DOCUMENTS]}
def main():
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--check',action='store_true');args=parser.parse_args()
content=json.dumps(bundle(),ensure_ascii=False,indent=2)+'\n'
if args.check:
if not TARGET.exists() or TARGET.read_text(encoding='utf-8')!=content:
parser.exit(1,'User manual is stale; run python scripts/build_user_manual.py\n')
else:TARGET.write_text(content,encoding='utf-8',newline='\n')
if __name__=='__main__':main()
+8 -2
View File
@@ -89,6 +89,11 @@ def create_app(config):
async def health(request):
return JSONResponse({'status': 'OK'})
async def manual(request):
authenticate(request)
data=json.loads(files('viano_bug_analysis.web').joinpath('user-manual.json').read_text(encoding='utf-8'))
return JSONResponse(data,headers={'Cache-Control':'no-store'})
async def integration_keys(request):
# Machine credentials can never manage or reveal credentials.
if request.headers.get('authorization'):raise APIError('AUTH_SESSION_REQUIRED',403)
@@ -218,11 +223,11 @@ def create_app(config):
async def web(request):
from starlette.responses import Response
name = request.path_params.get('asset', 'index.html')
if name not in {'index.html', 'dashboard.js', 'dashboard.css', 'case-detail.js','operations.js','test-requests.js','integration-keys.js'}:
if name not in {'index.html', 'dashboard.js', 'dashboard.css', 'case-detail.js','operations.js','test-requests.js','integration-keys.js','user-manual.js'}:
raise APIError('API_NOT_FOUND', 404)
data = files('viano_bug_analysis.web').joinpath(name).read_bytes()
response = Response(data, media_type={'index.html': 'text/html', 'dashboard.js': 'text/javascript',
'dashboard.css': 'text/css', 'case-detail.js':'text/javascript','operations.js':'text/javascript','test-requests.js':'text/javascript','integration-keys.js':'text/javascript'}[name])
'dashboard.css': 'text/css', 'case-detail.js':'text/javascript','operations.js':'text/javascript','test-requests.js':'text/javascript','integration-keys.js':'text/javascript','user-manual.js':'text/javascript'}[name])
response.headers.update({'Content-Security-Policy': "default-src 'self'; script-src 'self'; style-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'",
'X-Content-Type-Options': 'nosniff', 'Referrer-Policy': 'no-referrer', 'Cache-Control': 'no-store'})
return response
@@ -296,6 +301,7 @@ def create_app(config):
app = Starlette(routes=[Route('/', web), Route('/assets/{asset}', web),
Route('/v1/auth/{action}',auth,methods=['GET','POST']),
Route('/v1/manual',manual),
Route('/v1/integration-keys',integration_keys,methods=['GET','POST']),
Route('/v1/integration-keys/{identifier}/{action}',integration_keys,methods=['POST']),
Route('/v1/overview', overview), Route('/healthz', health), Route('/v1/profiles', profiles),
@@ -116,6 +116,10 @@ function syncDetail(){
if(!detailLoading&&(!selectedCase||detailHash!==location.hash||(!location.hash.includes('/revision/')&&current?.package_generation!==selectedCase.package_generation)))loadDetail(location.hash);
}
function route(){
if(typeof manualRoute==='function'&&manualRoute()){
if(typeof clearKeys==='function')clearKeys();return;
}
if($('manual-page'))$('manual-page').hidden=true;
if(typeof keysRoute==='function'&&keysRoute())return;
if($('keys-page')){$('keys-page').hidden=true;clearKeys();}
if(typeof testRoute==='function'&&testRoute())return;
+1
View File
@@ -5,3 +5,4 @@
.case-link{color:#4f61c7;font-weight:650;text-decoration:none}.case-link:hover{text-decoration:underline}.detail-panel>summary{font-size:18px;font-weight:650;cursor:pointer}.detail-body{margin-top:20px}.semantic-text{color:#45516a;white-space:pre-wrap;overflow-wrap:anywhere;margin:5px 0 12px}.semantic-fields{display:grid;grid-template-columns:minmax(130px,190px) minmax(0,1fr);gap:12px 22px;margin:0}.semantic-fields dt{font-size:12px;color:#7c8599;font-weight:600}.semantic-fields dd{margin:0;min-width:0}.semantic-fields .semantic-fields{grid-template-columns:130px minmax(0,1fr)}.semantic-list{display:grid;gap:10px}.semantic-item{border-left:2px solid #e6eaf6;padding-left:13px}.statement small{display:block;color:#9299a8}.reference-list{color:#7785ae;font-size:11px;overflow-wrap:anywhere}.hypothesis-card{border:1px solid #e6e9f1;border-radius:9px;padding:20px;margin-bottom:16px}.hypothesis-heading{display:flex;align-items:center;justify-content:space-between;gap:12px}.hypothesis-heading h3{font-size:16px;margin:0}.hypothesis-heading .badge{flex-shrink:0}.hypothesis-card .detail-panel{margin:16px 0 0;padding:14px;box-shadow:none}.hypothesis-card .detail-panel>summary{font-size:13px}.evidence-columns{display:grid;grid-template-columns:1fr 1fr;gap:26px;margin-top:18px}.evidence-columns h4{font-size:12px;color:#68758c}.evidence-columns>div{background:#f7f8fc;padding:16px;border-radius:7px;min-width:0}.detail-controls{display:flex;align-items:center;gap:16px}.detail-controls label{margin:0;min-width:170px}.detail-controls button{margin-top:20px}.history-note{background:#f1f3fd;color:#6472ae;padding:12px;border-radius:6px;font-size:12px}.assignment-status{font-weight:650;color:#34436a}.human-document{white-space:pre-wrap;overflow-wrap:anywhere;font:13px/1.7 system-ui}.next-actions p{color:#5f6d85}#case-detail .panel{scroll-margin-top:20px}@media(max-width:900px){.semantic-fields,.semantic-fields .semantic-fields{grid-template-columns:1fr;gap:4px}.semantic-fields dd{margin-bottom:12px}.detail-controls{flex-wrap:wrap}}@media(max-width:700px){.hypothesis-heading{align-items:flex-start;flex-direction:column}.evidence-columns{grid-template-columns:1fr}.detail-controls{width:100%}.detail-controls button{margin-top:0}.hypothesis-card{padding:14px}.rail{flex-wrap:wrap}.rail nav{flex-wrap:wrap}}
#key-projects{border:1px solid var(--line);border-radius:8px;padding:12px;margin-bottom:14px}#key-projects label{display:flex;align-items:center;gap:10px;margin:8px 0}#key-projects input{width:auto;margin:0}#key-list button{margin:10px 8px 0 0}#key-list input{font-family:monospace}
#result-rows .package-icon{width:34px;height:34px;padding:0;font-size:22px;line-height:1;border-radius:8px}#result-rows td small{display:block;max-width:320px;overflow-wrap:anywhere;color:var(--muted);font-size:11px}
.manual-selector{display:flex;align-items:end;gap:18px;flex-wrap:wrap}.manual-selector label{flex:1;min-width:230px;margin:0}.manual-selector button{margin:0}.manual-layout{display:grid;grid-template-columns:250px minmax(0,1fr);gap:24px;align-items:start}.manual-outline{position:sticky;top:20px;max-height:85vh;overflow:auto;padding:18px}.manual-outline h2{font-size:14px;margin-bottom:14px}#manual-toc{display:grid;gap:5px}#manual-toc button{background:transparent;color:var(--muted);text-align:left;font-size:12px;padding:7px;border-radius:5px}#manual-toc button:hover{background:#eef0fd;color:var(--accent)}#manual-toc .manual-top-heading{font-weight:700;color:var(--ink)}.manual-body{line-height:1.75;overflow-wrap:anywhere;min-width:0}.manual-body h2{font-size:26px;margin:0 0 20px}.manual-body h3{margin:30px 0 10px;font-size:19px}.manual-body h4{margin:24px 0 8px}.manual-body h2,.manual-body h3,.manual-body h4{scroll-margin-top:20px}.manual-body p,.manual-body li{color:#45516a}.manual-body li{margin-bottom:8px}.manual-body a{color:#4f61c7}.manual-body pre{background:#f3f5fa;border:1px solid var(--line);border-radius:8px;padding:16px;overflow:auto;max-width:100%;white-space:pre;font-size:12px}.manual-body code{font-family:ui-monospace,Consolas,monospace}.manual-body p code,.manual-body li code{background:#eef0f7;padding:2px 4px;border-radius:3px}.manual-body td,.manual-body th{white-space:normal;min-width:120px;vertical-align:top}.manual-body ul,.manual-body ol{padding-left:24px}@media(max-width:1000px){.manual-layout{grid-template-columns:1fr}.manual-outline{position:static;max-height:260px}}@media(max-width:700px){.manual-body{padding:20px}.manual-body h2{font-size:22px}}
+1
View File
@@ -31,6 +31,7 @@ function logout(remote=true) {
if(typeof clearOperations==='function')clearOperations();
if(typeof clearTests==='function')clearTests();
if(typeof clearKeys==='function')clearKeys();
if(typeof clearManual==='function')clearManual();
['active-count','token-count','correction-count','finished-count'].forEach(id=>$(id).textContent='—');
$('pie').getContext('2d').clearRect(0,0,360,360);
}
+3 -2
View File
@@ -2,11 +2,11 @@
<html lang="de">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>BugAnalysis · Übersicht</title><link rel="stylesheet" href="/assets/dashboard.css">
<script src="/assets/dashboard.js" defer></script><script src="/assets/integration-keys.js" defer></script>
<script src="/assets/dashboard.js" defer></script><script src="/assets/integration-keys.js" defer></script><script src="/assets/user-manual.js" defer></script>
<script src="/assets/case-detail.js" defer></script><script src="/assets/operations.js" defer></script><script src="/assets/test-requests.js" defer></script></head>
<body>
<aside class="rail"><a class="brand" href="/">V<span>BA</span><small>BugAnalysis</small></a>
<nav aria-label="Navigation"><a href="#overview" class="selected">Übersicht</a><a href="#cases">Laufende Fälle</a><a href="#results">Bearbeitete Fälle</a><a href="#tests">Testanfragen</a><a href="#submit">Auftrag übergeben</a><a href="#keys">API-Schlüssel</a></nav>
<nav aria-label="Navigation"><a href="#overview" class="selected">Übersicht</a><a href="#cases">Laufende Fälle</a><a href="#results">Bearbeitete Fälle</a><a href="#tests">Testanfragen</a><a href="#submit">Auftrag übergeben</a><a href="#keys">API-Schlüssel</a><a href="#manual">Benutzeranleitung</a></nav>
<div class="rail-bottom">ANALYSEBETRIEB<br><span id="connection">Nicht verbunden</span><button id="logout" type="button" hidden>Abmelden</button></div></aside>
<main id="overview">
<header><div><div class="eyebrow">BETRIEBSÜBERSICHT</div><h1>Analyse im Blick.</h1><p>Aktuelle Arbeit und messbarer Verbrauch.</p></div><div class="live"><span class="dot"></span><span id="updated">Warte auf Verbindung</span></div></header>
@@ -42,5 +42,6 @@
<div id="return-files" hidden><label>Abgeschlossene Case-ZIP<input id="case-package" type="file" accept=".zip"></label><label>Ergebnis<select id="outcome"><option value="patch_failed">Patch behebt den Fehler nicht</option><option value="validation_failed">Validierung fehlgeschlagen</option><option value="new_evidence">Neue Belege</option><option value="review_feedback">Review-Rückmeldung</option></select></label></div>
<label>Zusätzliche Anhänge<input id="attachments" type="file" multiple></label><button id="send" type="submit">Auftrag einreichen</button><p id="receipt" role="status"></p></form></section>
<section class="panel" id="keys-page" hidden><div class="eyebrow">INTEGRATIONEN</div><h2>API-Schlüssel</h2><p>Für Auftragsübergabe und lesenden Zugriff auf ausgewählte Projekte. Keine Betriebssteuerung oder Nutzerverwaltung.</p><form id="key-form"><label>Programmname<input id="key-name" maxlength="100" required placeholder="Erfassungsworker"></label><fieldset id="key-projects"><legend>Freigegebene Projekte</legend></fieldset><button type="submit">Schlüssel erstellen</button></form><p id="key-status" role="status"></p><div id="key-list"></div></section>
<section id="manual-page" hidden><div class="panel manual-selector"><label>Anleitung auswählen<select id="manual-document"></select></label><button id="manual-download" type="button" disabled>Markdown herunterladen</button></div><div class="manual-layout"><aside class="panel manual-outline" aria-label="Inhaltsverzeichnis"><h2>In diesem Dokument</h2><div id="manual-toc"></div></aside><article class="panel manual-body" id="manual-content" aria-live="polite"></article></div></section>
<footer>Verbrauch aller erhaltenen, freigegebenen Akten und Revisionen. Keine Google-Abrechnung. Fehlende Messwerte werden kenntlich gemacht.</footer>
</div></main></body></html>
+88
View File
@@ -0,0 +1,88 @@
'use strict';
let manualData=null,manualLoading=null,manualCurrent=null;
function clearManual(){manualData=null;manualLoading=null;manualCurrent=null;$('manual-document').replaceChildren();$('manual-content').replaceChildren();$('manual-toc').replaceChildren();$('manual-download').disabled=true;}
function manualLink(target){
if(/^https?:\/\//i.test(target)){try{const url=new URL(target);if(!url.username&&!url.password)return {href:url.href,external:true};}catch{}return null;}
const match=target.match(/^(?:\.\.\/|\.\/|docs\/)*([a-z0-9-]+)\.md(?:#([a-z0-9-]+))?$/);
if(match&&manualData?.documents.some(d=>d.id===match[1]))return {href:`#manual/${match[1]}`};
return null;
}
function manualInline(parent,value){
// Render only supported Markdown with DOM nodes. Never interpret source HTML.
const pattern=/(`[^`]+`|\*\*[^*]+\*\*|\[[^\]]+\]\([^\s)]+\))/g;let cursor=0;
for(const match of value.matchAll(pattern)){
parent.append(document.createTextNode(value.slice(cursor,match.index)));const part=match[0];
if(part.startsWith('`'))parent.append(text('code',part.slice(1,-1)));
else if(part.startsWith('**'))parent.append(text('strong',part.slice(2,-2)));
else{const link=part.match(/^\[([^\]]+)\]\((.+)\)$/),destination=manualLink(link[2]);
if(destination){const anchor=text('a',link[1]);anchor.href=destination.href;if(destination.external){anchor.target='_blank';anchor.rel='noopener noreferrer';}parent.append(anchor);}
else parent.append(document.createTextNode(link[1]));
}
cursor=match.index+part.length;
}
parent.append(document.createTextNode(value.slice(cursor)));
}
function renderManualMarkdown(markdown){
const fragment=document.createDocumentFragment(),lines=markdown.replace(/\r/g,'').split('\n'),headings=[];
const cells=line=>line.trim().replace(/^\||\|$/g,'').split('|').map(s=>s.trim());
let index=0;
while(index<lines.length){
const line=lines[index];if(!line.trim()){index++;continue;}
if(/^\s*```/.test(line)){
const code=[];index++;while(index<lines.length&&!/^\s*```/.test(lines[index]))code.push(lines[index++]);index++;
const pre=document.createElement('pre');pre.append(text('code',code.join('\n')));fragment.append(pre);continue;
}
const heading=line.match(/^(#{1,6})\s+(.+)$/);
if(heading){const node=document.createElement('h'+Math.min(heading[1].length+1,6));node.id='manual-heading-'+headings.length;manualInline(node,heading[2]);headings.push({id:node.id,label:node.textContent,level:heading[1].length});fragment.append(node);index++;continue;}
if(line.includes('|')&&index+1<lines.length&&/^\s*\|?\s*:?-{3,}/.test(lines[index+1])){
const wrap=text('div','','table-wrap'),table=document.createElement('table'),head=document.createElement('thead'),row=document.createElement('tr');
cells(line).forEach(value=>{const cell=document.createElement('th');manualInline(cell,value);row.append(cell);});head.append(row);table.append(head);index+=2;
const body=document.createElement('tbody');while(index<lines.length&&lines[index].includes('|')&&lines[index].trim()){
const row=document.createElement('tr');cells(lines[index++]).forEach(value=>{const cell=document.createElement('td');manualInline(cell,value);row.append(cell);});body.append(row);
}table.append(body);wrap.append(table);fragment.append(wrap);continue;
}
const item=line.match(/^\s*(?:[-*]|\d+\.)\s+(.+)$/);
if(item){const ordered=/^\s*\d+\./.test(line),list=document.createElement(ordered?'ol':'ul');
while(index<lines.length){const match=lines[index].match(ordered?/^\s*\d+\.\s+(.+)$/:/^\s*[-*]\s+(.+)$/);if(!match)break;index++;let value=match[1];
while(index<lines.length&&/^\s{2,}\S/.test(lines[index])&&!/^\s*(?:[-*]|\d+\.)\s+/.test(lines[index]))value+=' '+lines[index++].trim();
const entry=document.createElement('li');manualInline(entry,value);list.append(entry);
}fragment.append(list);continue;
}
const paragraph=[line];index++;
while(index<lines.length&&lines[index].trim()&&!/^(?:#{1,6}\s|\s*```|\s*[-*]\s|\s*\d+\.\s)/.test(lines[index]))paragraph.push(lines[index++]);
const node=document.createElement('p');manualInline(node,paragraph.join(' '));fragment.append(node);
}
return {fragment,headings};
}
function displayManual(identifier){
const previous=manualCurrent?.id;
manualCurrent=manualData.documents.find(d=>d.id===identifier);
if(!manualCurrent){$('manual-content').replaceChildren(text('p','Diese Anleitung ist nicht vorhanden.'));$('manual-toc').replaceChildren();$('manual-download').disabled=true;return;}
$('manual-document').value=manualCurrent.id;const rendered=renderManualMarkdown(manualCurrent.markdown);
$('manual-content').replaceChildren(rendered.fragment);$('manual-toc').replaceChildren();$('manual-download').disabled=false;
if(previous&&previous!==identifier)window.scrollTo(0,0);
rendered.headings.forEach(heading=>{const button=text('button',heading.label);button.type='button';button.className=heading.level===1?'manual-top-heading':'';button.onclick=()=>document.getElementById(heading.id)?.scrollIntoView({behavior:'smooth',block:'start'});$('manual-toc').append(button);});
}
function manualRoute(){
if(!/^#manual(?:\/|$)/.test(location.hash))return false;
if($('manual-page').hidden)window.scrollTo(0,0);
['overview-page','results','case-detail','submit','test-page','keys-page'].forEach(id=>$(id).hidden=true);$('manual-page').hidden=false;
document.querySelector('header h1').textContent='Benutzeranleitung.';
document.querySelectorAll('nav a').forEach(a=>a.classList.toggle('selected',a.hash==='#manual'));
if(!token)return true;
const generation=session;
const selected=()=>location.hash.slice('#manual/'.length)||'user-guide';
if(manualData){displayManual(location.hash==='#manual'?'user-guide':selected());return true;}
if(!manualLoading){$('manual-content').replaceChildren(text('p','Anleitungen werden geladen …'));
manualLoading=api('/v1/manual').then(data=>{
if(generation!==session)return;manualData=data;$('manual-document').replaceChildren(...data.documents.map(d=>new Option(d.title,d.id)));
if(/^#manual(?:\/|$)/.test(location.hash))displayManual(location.hash==='#manual'?'user-guide':selected());
}).catch(e=>{if(generation===session){error(`Anleitung nicht verfügbar: ${e.message}`);$('manual-content').replaceChildren(text('p','Die Anleitung konnte nicht geladen werden. Bitte erneut öffnen.'));}}).finally(()=>{if(generation===session)manualLoading=null;});
}
return true;
}
$('manual-document').addEventListener('change',()=>{location.hash='#manual/'+$('manual-document').value;});
$('manual-download').addEventListener('click',()=>{
if(!manualCurrent)return;const url=URL.createObjectURL(new Blob([manualCurrent.markdown],{type:'text/markdown;charset=utf-8'})),link=document.createElement('a');
link.href=url;link.download=manualCurrent.id+'.md';document.body.append(link);link.click();link.remove();setTimeout(()=>URL.revokeObjectURL(url),30000);
});
@@ -0,0 +1,50 @@
{
"format_version": 1,
"documents": [
{
"id": "user-guide",
"title": "Verwendung und Funktionsweise",
"markdown": "# BugAnalysis verwenden\n\nBugAnalysis untersucht gemeldete Softwarefehler und übergibt das Ergebnis an die\nnächste zuständige Stelle. Das Programm erstellt selbst keinen fertigen Patch\nund spielt keine Änderungen auf einen Server ein. Der aktuelle Erfassungsadapter\nist für WoW/Vcore eingerichtet.\n\n## So arbeitet BugAnalysis\n\n1. Eine Person oder ein Erfassungsprogramm übergibt Bugbeschreibung und\n Diagnosearchive. Die Übergabe-API erstellt daraus eine Fallakte.\n2. Ein dauerhaft laufender Analyseworker übernimmt den Auftrag und lädt die\n Konfiguration des angegebenen Projekts.\n3. Der Casebuilder wertet Beobachtungen und Telemetrie aus. Er kann relevante\n Identitäten mit den konfigurierten Recherchewerkzeugen auflösen.\n4. Die LLM interpretiert den Fall und entwickelt Hypothesen. Im gemeinsamen\n Planungsmodus geschieht beides in einem Aufruf.\n5. Die Untersuchung prüft Hypothesen mit Source- und DB-Werkzeugen. Eine knappe\n Untersuchungsschleife sammelt Ergebnisse; Prüfungen und Konsolidierungen\n übernehmen diese in den strukturierten Arbeitsstand. Werkzeuge lesen Daten;\n Zugangsdaten erhält die LLM nicht.\n6. Die finale Diagnose bewertet die Befunde und kann einen Frühabschluss\n annehmen oder weitere Untersuchung verlangen. Fachliche Budgets begrenzen\n einzelne Untersuchungen. Ein bestimmter Erfolg ist nicht garantiert.\n7. Das Ergebnis enthält Diagnose und Aufträge für Codingagent und menschlichen\n Entwickler. Ein Auftrag darf ausdrücklich erklären, dass keine Arbeit für\n den jeweiligen Empfänger vorliegt. Fehlende Daten zur Umsetzung können als\n menschlicher Auftrag übergeben werden, ohne die erfolgreiche Analyse zu sperren.\n\nEin Ergebnis bleibt eine begründete Analyse, keine Garantie für einen wirksamen\nFix. Nach Umsetzung und Tests können Rückmeldungen eine neue Analyserevision\nderselben Akte auslösen. Behauptungen, Testerbeobachtungen und bestätigte Befunde\nsollten bei der Bewertung auseinandergehalten werden.\n\n## Anmeldung und Start\n\nDie Webadresse im Browser öffnen und mit Benutzername und Passwort anmelden.\nBenutzerkonten werden ausschließlich per privilegierter Konsole angelegt;\neine Selbstregistrierung gibt es nicht. Alle menschlichen Benutzer haben die\ngleichen Rechte für alle Projekte. Externe Programme benutzen eigene\nIntegrationsschlüssel mit Projektfreigaben.\n\nAPI und Analyseworker sind getrennte Dienste. Das Öffnen der Webseite startet\nkeinen Worker und sendet keine Modellanfrage. Der laufende Worker verarbeitet\nAufträge selbstständig; die Webseite ist die Übersicht und alternative Bedienung.\n\n## Übersicht und laufende Fälle\n\nDie Übersicht zeigt erreichbare Worker, den bearbeiteten Fall und dessen\nFortschrittsbalken. Der Balken folgt abgeschlossenen Phasen, nicht vergangener\nZeit. Während einer längeren Untersuchung kann er daher unverändert bleiben.\nAbgeschlossene Fälle verschwinden aus der aktiven Workeransicht.\n\nDie Diagramme zeigen gemessenen Tokenverbrauch. Korrektur- und Wiederholungsaufrufe\nbilden eine eigene Kategorie. Denktokens sind bereits in den Ausgabetokens\nenthalten und werden nicht doppelt addiert. Fehlende Messwerte werden als solche\nausgewiesen. Diese Zahlen sind keine verbindliche Cloud-Abrechnung und umfassen\nnur die erhaltenen Akten im gewählten Messumfang.\n\nÜber Fallnummern lässt sich die Detailansicht öffnen: Beobachtungen, Einordnung,\nHypothesen, Befunde, Diagnose und Aufträge. Frühere Revisionen bleiben über die\nRevisionsauswahl einsehbar. Die vollständigen Daten stehen im herunterladbaren ZIP.\n\n## Neue Aufträge und Rückgaben\n\nUnter **Auftrag übergeben** Projekt, Bug-ID, Fallname und Beobachtungen eingeben.\nFür einen neuen Vcore-Fall das wowdiag-Archiv und möglichst das dazugehörige\nwowsrc-Archiv derselben Erfassung auswählen. Die Bug-ID muss mit den Archiven\nübereinstimmen. Zusätzliche Dateien lassen sich als Anhänge hinzufügen.\n\nNach dem Absenden bestätigt die API die Annahme. Das ist noch kein Analyseerfolg.\nBei einer unterbrochenen Übergabe verwendet das Formular bei unveränderten\nEingaben denselben Idempotenzschlüssel, damit kein doppelter Auftrag entsteht.\n\nFür eine Rückgabe den Auftragstyp **Abgeschlossene Akte zurückgeben** wählen,\ndie aktuelle abgeschlossene Case-ZIP hochladen und Ergebnis sowie neue\nBeobachtungen eintragen. Beispielsweise kann ein getesteter Patch den Fehler\nnicht behoben haben. BugAnalysis ergänzt die Akte und öffnet eine neue Revision.\nBlockierte oder noch laufende Akten sind keine Abschlussrückgaben.\n\nAutomatische Erfassung erfolgt über den wowdiag-Worker und dieselbe Übergabe-API.\nExterne Programme erstellen keine internen Case-Manifeste selbst.\n\n## Testanfragen beantworten\n\nDie Seite **Testanfragen** listet offene menschliche Recherche- und Testfragen\nmit Projekt. Nach Zeitpunkt oder Projekt sortieren, eine Anfrage auswählen und\nzunächsLine truncated
},
{
"id": "linux-deployment",
"title": "Linux-Installation und Betrieb",
"markdown": "# Linux-Betrieb\n\nAPI und Analyseworker laufen als getrennte systemd-Dienste. Ein Auftragseingang\nweckt keinen neuen Prozess: Der dauerhaft laufende Worker übernimmt Inbox-Aufträge.\nDie Weboberfläche ist eine alternative Bedienung desselben Arbeitsablaufs.\nDie Installation startet keine Dienste und führt keine Modellanfrage aus.\n\n## Voraussetzungen und Verzeichnisse\n\nLinux mit systemd, Python 3.11 oder neuer inklusive venv/pip, Git und Nginx.\nDiese Programme über die Paketverwaltung des Servers installieren. Die aktuelle\nProjektvorlage ist für WoW/Vcore eingerichtet; sie ist kein allgemeiner Adapter.\n\n| Pfad | Zweck und Rechte |\n| --- | --- |\n| `/opt/viano-bug-analysis` | Root-eigener Checkout und virtuelle Python-Umgebung; Dienst darf nur lesen |\n| `/etc/viano-bug-analysis` | Root-eigene Konfiguration, Gruppe `buganalysis`, Verzeichnis 0750, Dateien 0640 |\n| `/etc/viano-bug-analysis/*.env` | Zugangsdaten, root:buganalysis 0600; systemd liest sie vor dem Benutzerwechsel |\n| `/etc/viano-bug-analysis/auth/users.sqlite3` | Root-verwaltete Nutzer, Gruppe `buganalysis` darf lesen |\n| `/var/lib/viano-bug-analysis` | Dienst-eigene Fallakten, Laufzeitdaten und Websessions |\n| `/srv/viano-bug-analysis/viano-core` | Root-eigener Source-Checkout, Dienst hat Leserechte |\n\nDie env-Dateien sind durch Betriebssystemrechte geschützte Klartextdateien, kein\nverschlüsselter Tresor. Sie gehören nicht ins Repository oder in Diagnosepakete.\nAPI und Worker erhalten unterschiedliche Dateien: Nur der Worker erhält Cloud-\nund DB-Zugangsdaten. Die Diensthärtung und EnvironmentFile-Nutzung folgen der\n[systemd-Dokumentation](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html).\n\nDie [Webverwaltung für Integrationsschlüssel](integration-keys.md) ergänzt die\nenv-basierte Maschinenanmeldung. Ihre SQLite und separate Schlüsseldatei müssen\nebenfalls gesichert werden; beide liegen standardmäßig unter der Fallablage.\n\n## Installation\n\nDen vertrauenswürdigen BugAnalysis-Checkout als root unter\n`/opt/viano-bug-analysis` ablegen. Code und Unterverzeichnisse dürfen nicht für\nandere Benutzer schreibbar sein. Dann:\n\n```sh\ncd /opt/viano-bug-analysis\npython3 deploy/install.py --dry-run\nsudo python3 deploy/install.py\n```\n\nDer Installer legt den Dienstbenutzer `buganalysis`, Konfigurationen, venv und\nUnits an. Er installiert die Python-Abhängigkeiten und lädt systemd neu.\nBestehende Zielkonfigurationen und Units werden erhalten. Updates an bereits\ninstallierten Units daher bewusst mit den versionierten Vorlagen abgleichen.\nBei Installationsfehlern bleiben vorhandene Dateien erhalten; erneutes Ausführen\nsetzt die Einrichtung fort. Der Installer verändert keine bestehende Datenbank.\n\n## Einmalige Konfiguration\n\nMit `sudoedit` die Dateien unter `/etc/viano-bug-analysis` bearbeiten:\n\n1. `project.toml`: DB-Adresse/Port, Datenbank und nur lesenden Benutzer einstellen,\n tatsächliche Snapshotkennung und vollständigen Source-Commit eintragen.\n Die ausgelieferten Null-Commit- und `CONFIGURE_ME`-Werte sind Platzhalter.\n2. Den Vcore-Checkout als root unter `/srv/viano-bug-analysis/viano-core` anlegen;\n der konfigurierte Commit muss lokal vorhanden und für `buganalysis` lesbar sein.\n Der Dienst lädt keine Commits nach. `gitconfig` vertraut ausschließlich diesem\n konkreten Root-Checkout, nicht beliebigen Repositories.\n3. `worker.env`: `GEMINI_API_KEY=...` und `VIANO_DB_PASSWORD=...` eintragen.\n Keine `export`-Anweisungen verwenden. Für Sonderzeichen gelten die\n EnvironmentFile-Quotingregeln von systemd. Werte nicht als Shellargumente übergeben.\n4. `api.env`: `VBA_INTAKE_TOKEN=...` für den Erfassungsworker setzen\n (32–256 Zeichen aus Buchstaben, Ziffern, `_` und `-`). Dies ist ein separater\n Integrationsschlüssel, kein Gemini-Key. Wird keine externe API-Integration\n benötigt, den zugehörigen `[[clients]]`-Block aus `api.toml` entfernen.\n5. `llm.toml`: gewünschtes Modell, Anbieter und Cacheeinstellungen kontrollieren.\n Investigation-, Budget- und Telemetrie-Dateien sind separat konfigurierbar.\n\nDie DB-Verbindung benötigt einen Benutzer mit ausschließlich passenden\nSELECT-Rechten auf die freigegebenen World-Tabellen. Diese Rechte legt der\nDB-Administrator an; BugAnalysis vergibt keine Grants. Der Readiness-Test belegt\nLesbarkeit, nicht die Abwesenheit zusätzlicher Schreibrechte. Für entfernte DBs\neinen geschützten Netzwerkweg verwenden: Der aktuelle DB-Provider konfiguriert\nselbst kein TLS. Zugang zu Produktionsdaten ist für die lokale Entwicklung\nweiterhin nicht erforderlich.\n\nWebnutzer anlegen; das Passwort wird verdeckt abgefragt:\n\n```sh\nsudo /opt/viano-bug-analysis/.venv/bin/python -m viano_bug_analysis.api create_user NAME --config /etc/viano-bug-analysis/api.toml --service-group buganalysis\n```\n\nNutzerverwaltung bleibt root vorbehalten. Siehe [Nutzerverwaltung](user-accounLine truncated
},
{
"id": "project-configuration",
"title": "Projekt-, Source-, DB- und Modellkonfiguration",
"markdown": "# Projekt und Recherchezugriffe konfigurieren\n\nDie Projektkonfiguration wird über Dateien verwaltet, nicht über die Webseite.\nSie legt fest, welche Werkzeuge ein Auftrag verwenden darf. Ein aufrufendes\nProgramm kann weder Repository noch DB-Zugänge oder Budgets mit einem Auftrag\nüberschreiben. Aktuell ist das Profil `wow/vcore` mit dem Importadapter `vcore`\nimplementiert; andere Projekttypen erfordern entsprechende Adapter und Profile.\n\n## Zuordnung in api.toml\n\nEin Projektelement ordnet eine feste Projekt-ID einem Profil und dessen\nKonfigurationsdateien zu. Relative Pfade beziehen sich auf die API-Konfigurationsdatei.\nDer Erfassungsworker muss genau dieselbe Projekt-/Profilzuordnung senden und\nsein Integrationsschlüssel muss für das Projekt freigegeben sein.\n\n```toml\n[[projects]]\nid = \"viano-core\"\ntype = \"wow\"\nprofile = \"vcore\"\nadapter = \"vcore\"\nplanning_mode = \"joint\"\n[projects.settings]\nproject_config = \"project.toml\"\nllm_config = \"llm.toml\"\ninvestigation_config = \"investigation.bounded.toml\"\nbudget_config = \"budget.human-tests.toml\"\n```\n\nDie Linux-Vorlagen stehen unter `deploy/linux/`; der Installer legt die\nKonfiguration unter `/etc/viano-bug-analysis` an. Er überschreibt bestehende\nDateien nicht. Die ausführliche Vcore-Projektvorlage steht unter\n`config/project.example.toml`, die Relationsregeln unter\n`config/project-relations.vcore.toml`.\n\n## Source und Datenbank in project.toml\n\n```toml\nrelations_file = \"project-relations.vcore.toml\"\n[project]\ntype = \"wow\"\nprofile = \"vcore\"\n\n[source]\npath = \"/srv/viano-bug-analysis/viano-core\"\nurl = \"https://gitea.viano-software.de/Nico/Viano-Core\"\ncommit = \"VOLLSTAENDIGEN_40_STELLIGEN_COMMIT_EINTRAGEN\"\n\n[database]\nhost = \"127.0.0.1\"\nport = 3306\ndatabase = \"cata_world\"\nuser = \"viano_reader\"\npassword_env = \"VIANO_DB_PASSWORD\"\nsnapshot_id = \"KENNUNG_DER_TATSAECHLICHEN_DATENGRUNDLAGE\"\ntables = [\"creature_template\", \"creature\"]\n```\n\nDas Beispiel zeigt nur den Aufbau und enthält absichtlich ungültige Platzhalter.\nDie beiden Tabellen sind keine vollständige Vcore-Freigabeliste. Für das echte\nProfil die vollständige Vorlage einschließlich Limits, Enrichment und\nAnalysewerkzeugregeln verwenden und passend prüfen.\n\nDer Source-Checkout muss lokal vorhanden sein und den konfigurierten Commit\nenthalten. Die URL allein klont oder aktualisiert kein Repository. Der Worker\nliest Git-Objekte dieses festen Stands und benötigt keine Schreibrechte auf den\nCheckout. Die Linux-Vorlage enthält eine gezielte Git-Vertrauenskonfiguration\nfür den root-eigenen Checkout. Neue Source-Stände bewusst bereitstellen und\nderen vollständigen Commit konfigurieren.\n\nDer DB-Administrator legt einen Benutzer mit ausschließlich passenden SELECT-\nRechten an. Zusätzlich begrenzt `tables` die zugänglichen Tabellen. Diese\nFreigabeliste ersetzt keine serverseitigen Rechte. Account- oder Charakterdaten\ngehören nicht zur standardmäßigen World-Recherche. Für entfernte DBs einen\ngeschützten Netzwerkweg nutzen; der aktuelle Provider konfiguriert selbst kein TLS.\n\nDas Passwort steht entweder in der durch `password_env` benannten\nUmgebungsvariable oder einer separaten geschützten Datei über `password_file`.\nNur eine dieser Varianten setzen. Unter Linux lädt der Worker beispielsweise\n`VIANO_DB_PASSWORD` aus seiner root-geschützten `worker.env`.\nPasswörter nicht in Fallakten, LLM-Prompts oder Versionsverwaltung ablegen.\nDie LLM bekommt Werkzeugergebnisse statt Zugangsdaten.\n\n## Weitere Konfigurationen\n\n| Datei | Zuständigkeit |\n| --- | --- |\n| api.toml | HTTP-Bindung, Workspace, Projekte, Nutzerdatei und optionale env-basierte Maschinenclients |\n| worker.toml | Verweis auf api.toml, Polling, Wiederholungsabstände, Speicherreserve und Telemetrie |\n| llm.toml | Anbieter, Modell, Schlüssel-Environment, Timeouts, Ausgabe-/Denkeinstellungen und Cache |\n| investigation.bounded.toml | Untersuchungsmodus und begrenztes Kontextfenster |\n| budget.human-tests.toml | Fachliche Budgetregeln und menschliche Testanfragen |\n| telemetry.toml | Heartbeats und lokale Betriebsbeobachtung |\n| project-relations.vcore.toml | Projektspezifische Beziehungen für Recherchewerkzeuge |\n\nDie versionierte Modellvorlage `config/llm.gemini-cache.example.toml` verwendet\n`GEMINI_API_KEY`. Das ist unabhängig vom Integrationsschlüssel für externe\nAuftragsübergaben. Die API erhält `api.env`; der Analyseworker erhält\n`worker.env` mit Cloud- und DB-Zugangsdaten.\n\n## Prüfen und ändern\n\nDer Befehl `python -m viano_bug_analysis.deployment check` prüft mit Rolle und\nKonfigurationspfad die Betriebsbereitschaft ohne Modellanfrage. Die\n[Linux-Anleitung](linux-deployment.md) zeigt die vollständigen Befehle unter der\ntatsächlichen Dienstidentität. Die Prüfung umfasst Git-Objekte und freigegebene\nDB-Schemata; die Gültigkeit des Cloud-Schlüssels wird nLine truncated
},
{
"id": "intake-api",
"title": "Übergabe-API und Rückgaben",
"markdown": "# Übergabe-API v1\n\nDie API erstellt interne Case-Pakete und veröffentlicht sie atomar in der Inbox.\nExterne Programme liefern Metadaten und Dateien; sie erzeugen und bearbeiten keine\ninternen Manifeste. Die API führt keine Analyse und keine LLM-Aufrufe aus. Ein\ndauerhaft laufender [Analyseworker](analysis-worker.md) übernimmt anschließend die\nAufnahme und Verarbeitung. Die [Weboberfläche](web-console.md) bietet Übersicht,\nAuftragsübergabe, Testerantworten und Ergebnisdownloads.\n\n## Lokaler Start\n\n```powershell\n.\\.venv\\Scripts\\python.exe -m pip install -e '.[api]'\nCopy-Item config/api.example.toml config/api.local.toml\n$env:VBA_INTAKE_TOKEN = [Convert]::ToHexString([System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32))\n.\\.venv\\Scripts\\python.exe -m viano_bug_analysis.api check --config config/api.local.toml\n.\\.venv\\Scripts\\python.exe -m viano_bug_analysis.api serve --config config/api.local.toml\n```\n\nDer API-Token ist ein eigener Zugangsschlüssel, kein Gemini-Key. Programme erhalten\nvorzugsweise einen Schlüssel aus der [Web-Schlüsselverwaltung](integration-keys.md).\nAlternativ bleiben Clients mit Token aus einer serverseitigen Umgebungsvariable\nunterstützt; der Beispielstart oben zeigt diese Variante. Der\nToken benötigt 32–256 Zeichen aus `A-Z a-z 0-9 _ -`. Auf älterem Windows PowerShell\nkann ein solcher Token über einen Passwortmanager erzeugt und als Variable gesetzt\nwerden. Der Beispielstart bindet nur an `127.0.0.1:8080`. Für entfernte Nutzung sind\nHTTPS und ein passend konfigurierter Reverseproxy erforderlich. Tokens nicht in\nURLs oder Git-Konfiguration ablegen. Zugriff auf das Workspace-Dateisystem bleibt\nauf den Dienst und vertrauenswürdige Betreiber beschränkt.\n\n`projects` ist die serverseitige Freigabeliste. Jeder Auftrag benennt `project_id`\nund `profile_id`. Er kann keine Repository-URLs, DB-Zugänge, Budgets oder\nKonfigurationspfade überschreiben. Optionale `projects.settings` halten die spätere\nWorker-Konfiguration bereit; die Eingangsschicht verwendet diese Dateien nicht für\nAnalysen. Aktuell ist der Importadapter `vcore` für `wow/vcore` implementiert.\nWeitere Projekte benötigen passende Adapter/Profile, keine separat gepflegten Forks.\n\n## Endpunkte\n\n| Methode/Pfad | Bedeutung |\n|---|---|\n| `GET /healthz` | Minimale öffentliche Erreichbarkeitsprüfung |\n| `GET /v1/profiles` | Für diesen Client freigegebene Projekte/Profile |\n| `GET /v1/schema` | Öffentliches JSON-Schema der Übergabemetadaten |\n| `GET /v1/overview` | Projektbezogene Worker-/Fallübersicht und Tokenstatistik |\n| `POST /v1/submissions` | Multipart-Upload, `201` bei neuer Einreihung, `200` bei Wiederholung |\n| `GET /v1/submissions/{id}` | Quittung und aktueller verwalteter Zustand |\n| `GET /v1/submissions/{id}/package` | Abgeschlossene Akte herunterladen; sonst `409` |\n\nAlle Endpunkte außer Health benötigen `Authorization: Bearer TOKEN`.\nPOST benötigt zusätzlich `Idempotency-Key: frei-gewaehlte-eindeutige-id` (1–128\nZeichen, Buchstaben/Ziffern sowie Punkt, Unterstrich, Bindestrich; erstes Zeichen\nalphanumerisch). Derselbe Client/Schlüssel mit denselben Metadaten und Dateiinhalten\nliefert dieselbe Quittung, auch nach einem Neustart. Abweichender Inhalt ergibt\n`409 API_IDEMPOTENCY_CONFLICT`. Dateinamen und Multipart-Grenzen zählen nicht als\nInhaltsänderung; Rollen und Reihenfolge der Dateien zählen. Ein zweiter neuer\nAuftrag für dasselbe Projekt und dieselbe Case-ID wird zurückgewiesen.\n\n## Neuer Auftrag\n\n`metadata` ist ein UTF-8-JSON-Textfeld, kein Datei-Upload. Dateien: genau einmal\n`wowdiag`, optional einmal `wowsrc`, optional mehrfach `attachments`. Für Vcore muss\ndie Case-ID dem Bug des Erfassungsarchivs entsprechen (`BUG-000020`). Beide\nErfassungsarchive müssen zueinander passen. Beispiel `submission.json`:\n\n```json\n{\n \"kind\": \"new_case\",\n \"project_id\": \"viano-core\",\n \"profile_id\": \"vcore\",\n \"case_id\": \"BUG-000020\",\n \"report\": {\n \"title\": \"Wolpertinger fehlen\",\n \"observations\": [\"Nach Questannahme waren im untersuchten Gebiet keine Tiere sichtbar.\"],\n \"expected_behavior\": [\"Fangbare Tiere sind im Gebiet vorhanden.\"]\n },\n \"versions\": {\n \"reported_server\": \"commit-des-gemeldeten-serverstands\",\n \"capture_server\": \"commit-des-erfassungsstands\"\n }\n}\n```\n\n```powershell\ncurl.exe --fail-with-body -H \"Authorization: Bearer $env:VBA_INTAKE_TOKEN\" -H \"Idempotency-Key: bug20-capture-001\" -F \"metadata=<submission.json\" -F \"wowdiag=@wowdiag.tar.gz\" -F \"wowsrc=@wowsrc.tar.gz\" http://127.0.0.1:8080/v1/submissions\n```\n\nVersionsangaben werden als unbestätigte Angaben des Einlieferers gespeichert, nicht\nals verifizierte technische Fakten. Sie ersetzen keine Prüfung des Deployments.\nZusätzliche Anhänge werden unter generierten internen Namen unverändert erhalten;\nsie werden nicht ausgeführt. OriginLine truncated
},
{
"id": "analysis-worker",
"title": "Analyseworker und Wiederanlauf",
"markdown": "# Dauerhafter Analyseworker\n\nDer Worker verbindet die Übergabe-API mit dem vorhandenen Analyseablauf. Er nimmt\nPakete aus derselben Inbox auf, wählt die serverseitige Projektkonfiguration und\nbearbeitet aktive Fälle bis zu Outbox oder Review. Er erstellt keine Patches,\nführt keine Builds aus und verändert keine Projekt-Datenbank.\n\n## Konfiguration\n\n`config/worker.example.toml` verweist auf die Konfiguration der Übergabe-API.\nAPI und Worker müssen denselben Workspace und Projektkatalog verwenden. Der\nWorker benötigt die API-Clienttokens nicht, wohl aber die in seiner LLM-Konfiguration\ngenannten Provider-Zugangsdaten. Beide Prozesse dürfen getrennt laufen.\n\nFür jedes freigegebene Projekt werden die Analysekonfigurationen in der\nAPI-Konfiguration hinterlegt:\n\n```toml\n[[projects]]\nid = \"viano-core\"\ntype = \"wow\"\nprofile = \"vcore\"\nadapter = \"vcore\"\nplanning_mode = \"joint\"\n[projects.settings]\nproject_config = \"project.local.toml\"\nllm_config = \"llm.early-review.local.toml\"\ninvestigation_config = \"investigation.bounded.toml\"\n```\n\nDie Pfade sind relativ zur API-Konfigurationsdatei. `llm_config` ist erforderlich.\nWeitere optionale Einstellungen: `analysis_config`, `budget_config`,\n`diagnosis_config`, `change_request_config`, `correction_config`. Ohne explizite\nDatei gelten deren bestehende Standardwerte. Für den aktuellen Vcore-Betrieb die\nerprobte `investigation.bounded.toml` verwenden; ältere ausführliche Promptmodi\nkönnen an der unveränderten 20.000-Zeichen-Grenze scheitern.\n\n`planning_mode` ist je Projekt `joint` (Standard) oder `separate`. Die Profilbindung\neiner Akte muss vollständig zum Katalog passen: `project.id`, `type`, `profile`.\nAlte manuelle Pakete ohne `project.id` werden nicht automatisch zugeordnet.\nAndere Projektadapter bleiben ein eigener Ausbauschritt.\n\n## Start und Prüfung\n\n```powershell\n.\\.venv\\Scripts\\python.exe -m viano_bug_analysis.worker check --config config/worker.local.toml\n.\\.venv\\Scripts\\python.exe -m viano_bug_analysis.worker run --config config/worker.local.toml\n```\n\n`check` lädt Konfiguration und prüft die Anwesenheit der LLM-Zugangsdaten. Es sendet\nkeine Modellanfrage und ist kein Erreichbarkeitstest für API, Datenbank oder\nRepository. `run` verarbeitet angenommene Aufträge und kann deshalb kostenpflichtige\nLLM-Aufrufe auslösen. Der reine Start ohne anstehende Fälle führt keine Modellaufrufe\naus. `run --once` führt genau einen Scheduler-Durchgang aus, nicht einen vollständigen\nBuglauf. Auch dieser Modus kann LLM-Aufrufe auslösen.\n\nAPI zusätzlich in einem zweiten Prozess starten:\n\n```powershell\n.\\.venv\\Scripts\\python.exe -m viano_bug_analysis.api serve --config config/api.local.toml\n```\n\nFür diese Entwicklung wurden die zuvor nicht vorhandenen lokalen Dateien\n`config/api.local.toml` und `config/worker.local.toml` mit dem Vcore-Profil vorbereitet.\nSie sind nicht versioniert und enthalten keine Zugangsschlüssel. Vorhandene lokale\nKonfigurationen werden bei späteren Änderungen nicht automatisch überschrieben.\n\n## Scheduling und Wiederanlauf\n\n- Pro Durchgang erhält jeder berechtigte aktive Fall einen Dispatch. Danach\n darf der nächste Fall arbeiten. Gibt es Fortschritt, folgt der nächste Durchgang\n unmittelbar; nur im Leerlauf oder bei Wartefällen greift das Pollintervall.\n- Der Dienst läuft als einzelner Analyseprozess. Eine OS-Dateisperre verhindert\n einen zweiten Worker im selben Workspace. Die Fall-Lease ist mit dem manuellen\n Trialbefehl geteilt. API-Veröffentlichungen verwenden weiterhin die kurzen\n Checkpoint-/Inbox-Sperren; während einer Modellanfrage bleibt die API benutzbar.\n- Paketmanifest und Checkpoints bleiben maßgeblich. Es wird nur die neueste\n Generation pro Fall geprüft, nicht jede historische Generation erneut geöffnet.\n- Bei Neustart werden verwaltete aktive Fälle und noch nicht übernommene Uploads\n wiedergefunden. PREPARED-Veröffentlichungen der API werden vor der Verarbeitung\n abgeschlossen. Rückläufer starten die bereits eröffnete neue Analyse-Revision;\n alte Interpretation und Hypothesen werden nicht als neue Ergebnisse behandelt.\n- Bestehende LLM-Operationskennungen bleiben stabil. Der Dienst ändert keine\n Versuchskennungen, Budgets, Iterationsgrenzen oder Korrekturregeln, um einen\n blockierten Fall weiterzutreiben.\n- `RETRY_PENDING`/`DEFERRED` beachten die gespeicherte Wartezeit. Ein unerwarteter\n Konfigurations-/Vorbereitungsfehler erhält einen lokalen Cooldown; andere Fälle\n werden weiterbearbeitet. Eine ausdrücklich zurückgegebene `ERROR`-Antwort oder\n unerwartete Exception während eines Dispatch erzeugt `ATTENTION`, statt dieselbe\n Fehlantwort endlos abzuspielen oder möglicherweise erneut Modellkosten auszulösen.\n- BLOCKED bleibt BLOCKED, wenn eine menschliche Testerantwort benötigt wird,\n automatische Wiederholung untersagt ist, Modellvalidierung geschLine truncated
},
{
"id": "user-accounts",
"title": "Benutzerkonten",
"markdown": "# Benutzeranmeldung und exklusive Bearbeitung\n\nWebbenutzer melden sich mit Benutzername und Passwort an. Alle Benutzer besitzen\ndieselben Rechte für alle Projekte. Es gibt weder Administrator-/Normalbenutzer-\nKlassen noch eine Benutzerverwaltung im Web. Bestehende Bearer-Tokens bleiben\nausschließlich für externe API-Integrationen nutzbar und behalten deren bisherigen\nZugriffsgrenzen. Fehlende Token-Umgebungsvariablen deaktivieren die jeweilige\nIntegration; die Benutzeranmeldung benötigt keine Token-Zufallsgenerierung.\n\n## Konsolenverwaltung\n\nDie Befehle erfordern unter Linux effektive root-Rechte (normalerweise `sudo`),\nunter Windows eine als Administrator gestartete Konsole. Sie benötigen keinen\nGemini-Schlüssel und senden keine Modellanfrage.\n\n```bash\nsudo .venv/bin/python -m viano_bug_analysis.api create_user nico --config config/api.local.toml --service-group buganalysis\nsudo .venv/bin/python -m viano_bug_analysis.api change_pw_user nico --config config/api.local.toml\nsudo .venv/bin/python -m viano_bug_analysis.api delete_user nico --config config/api.local.toml\n```\n\nOhne Passwortargumente erfolgt eine verdeckte Abfrage. Die gewünschten expliziten\nArgumentformen funktionieren ebenfalls:\n\n```text\ncreate_user NAME PASSWORT --config DATEI\ndelete_user NAME --config DATEI\nchange_pw_user NAME ALTES_PASSWORT NEUES_PASSWORT --config DATEI\n```\n\nExplizite Passwortargumente können in Shellhistorie/Prozessargumenten auftauchen;\nfür den normalen Betrieb die verdeckte Eingabe verwenden. Benutzernamen bestehen\naus 1–64 ASCII-Buchstaben/Ziffern sowie Punkt, Unterstrich oder Bindestrich und\nbeginnen mit einem Buchstaben oder einer Ziffer. Namen sind nicht case-sensitiv.\nBeim Passwortwechsel wird das alte Passwort geprüft. Passwortwechsel und Löschung\nentwerten bestehende Sitzungen und deren Bearbeitungssperren ohne API-Neustart.\n\nWindows, in einer Administrator-PowerShell:\n\n```powershell\n.\\.venv\\Scripts\\python.exe -m viano_bug_analysis.api create_user nico --config config/api.local.toml\n```\n\n## SQLite-Dateien und Betriebssystemrechte\n\nBenutzer liegen standardmäßig in `<workspace>/auth/users.sqlite3`. Die API öffnet\ndiese Datei ausschließlich schreibgeschützt. Gesalzene scrypt-Hashes ersetzen\nKlartextpasswörter. Sitzungen, Anmeldebegrenzung und Bearbeitungssperren liegen\nseparat in `<workspace>/runtime/web-sessions/sessions.sqlite3`; Sitzungstokens\nwerden dort nur als Hash gespeichert.\n\nProduktionskonfiguration, Pfade relativ zur Konfigurationsdatei oder absolut:\n\n```toml\n[auth]\nusers_database = \"/etc/viano-bug-analysis/users.sqlite3\"\nsession_seconds = 3600\n```\n\nDie Benutzerdatei und ihr Verzeichnis gehören unter Linux root; CLI-Befehle setzen\n0640 bzw. 0750. `--service-group buganalysis` erlaubt dem dedizierten Dienstkonto\ndas Lesen, nicht die Benutzerverwaltung. Verzeichnisvorfahren dürfen nicht durch\nunvertrauenswürdige Benutzer austauschbar sein. Der Dienst benötigt Schreibzugriff\nauf seinen Workspace und die Sitzungsdatei. Unter Windows entsprechend geschützte\nNTFS-Verzeichnisse/ACLs verwenden: Administratoren verwalten die Benutzerdatei,\ndas Dienstkonto liest sie. Die Konsolenbefehle prüfen auch dort die erhöhte\nAdministratorberechtigung. Wer Programmcode oder Authentifizierungsdateien selbst\nändern kann, liegt außerhalb der Web-Zugriffsgrenze; deshalb sind diese OS-Rechte\nBestandteil der Servereinrichtung.\n\nSQLite benötigt keinen MariaDB-Dienst. Für einen Umzug die Benutzerdatei und die\nProjekt-/Workspace-Daten sichern; die separate Sitzungsdatei kann bei gestoppter\nAPI bewusst verworfen werden, um alle Sitzungen und Sperren zu beenden.\n\n## Sitzungen und Bearbeiten\n\nDie Anmeldung erzeugt ein HttpOnly-/SameSite-Strict-Cookie; unter HTTPS zusätzlich\nSecure. Entfernte Zugriffe über HTTPS betreiben. Mutationen benötigen den an die\nSitzung gebundenen CSRF-Header. Die Oberfläche hält nur diesen CSRF-Wert im\nArbeitsspeicher; Passwörter und Sitzungstokens werden nicht im Local Storage\ngespeichert. Sitzungen gelten standardmäßig absolut eine Stunde, konfigurierbar\nvon 60 Sekunden bis 24 Stunden; regelmäßige Seitenaktualisierungen verlängern sie\nnicht endlos. Nach einem Neuladen kann eine gültige Sitzung weiterverwendet werden.\nAnmeldeversuche werden serverseitig zeitlich begrenzt.\n\nEine Testanfrage lässt sich zunächst nur ansehen. Erst **Bearbeiten** übernimmt\ndie Anfrage atomar für die aktuelle Sitzung. Eine zweite Sitzung erhält\n`API_TEST_CLAIMED` und eine verständliche Meldung. Erst dann erscheinen Eingabefelder.\nDie Serverprüfung verlangt dieselbe Sperre auch beim Absenden; das Ausblenden\nvon Feldern allein ist nicht die Zugriffskontrolle. Identische Wiederholungen\nbleiben idempotent, abweichende zweite Antworten werden zurückgewiesen.\n\n**Bearbeitung freigeben**, Abmeldung oder Sitzungsablauf machen die Anfrage für\nandere Sitzungen verfügbar. Der AblauLine truncated
},
{
"id": "integration-keys",
"title": "Integrationsschlüssel",
"markdown": "# Integrationsschlüssel\n\nAngemeldete Webnutzer finden unter **API-Schlüssel** eine eigene Verwaltungsseite.\nProgrammname und mindestens ein Projekt auswählen, dann den Schlüssel erstellen.\n**Anzeigen** ruft ihn bei Bedarf erneut ab; **Verbergen** entfernt ihn aus dem\nEingabefeld. **Kopieren** verwendet die Zwischenablage (HTTPS oder localhost).\n**Widerrufen** sperrt ihn sofort für nachfolgende API-Anfragen. Bereits laufende\nAnfragen werden dadurch nicht abgebrochen. Ein widerrufener Schlüssel wird nicht\nmehr angezeigt; für einen Ersatz einen neuen Schlüssel erstellen.\n\nAlle angemeldeten menschlichen Nutzer haben dieselben Verwaltungsrechte.\nMaschinenzugänge können weder Schlüssel verwalten noch andere Schlüssel anzeigen.\nWebmutationen einschließlich Anzeigen benötigen die vorhandene CSRF-Absicherung.\n\nDas aufrufende Programm sendet `Authorization: Bearer <Schlüssel>` an die\nÜbergabe-API. Neue Schlüssel erlauben Auftragsübergabe einschließlich Rückgaben\nund lesenden Zugriff auf die ausgewählten Projekte. Sie erlauben keine\nBetriebssteuerung oder Beantwortung menschlicher Testanfragen. Zugriff auf weitere\nProjekte erfordert einen neuen Schlüssel. Bestehende env-basierte Integrationen\nbleiben unverändert nutzbar und erscheinen nicht in der SQLite-Schlüsselliste.\n\n## Speicherung und Sicherung\n\n- SQLite unter `<workspace>/runtime/integrations/keys.sqlite3` speichert Metadaten,\n SHA-256-Prüfwerte und mit Fernet authentifiziert verschlüsselte Schlüssel.\n- Der separate Serverschlüssel entsteht beim ersten Erstellen automatisch unter\n `<workspace>/runtime/integration-secrets/master.key`, Dateimodus 0600.\n Nutzerkonten und ihre root-verwaltete SQLite-Datei werden nicht verändert.\n- Mit `VIANO_INTEGRATION_KEY_FILE` kann die API stattdessen eine separate\n Schlüsseldatei verwenden. Vorhandene Dateien müssen einen gültigen Fernet-Key\n enthalten und für den API-Dienst lesbar sein; fehlt die Datei beim ersten\n Erstellen, muss der Dienst sie im gewählten Verzeichnis anlegen dürfen.\n- SQLite und Serverschlüssel getrennt geschützt sichern. Das Laufzeitverzeichnis\n enthält damit dauerhaft notwendige Integrationsdaten und darf bei einer\n Bereinigung von Sessions nicht pauschal gelöscht werden. Unter Windows passende\n Dateisystem-ACLs verwenden; POSIX-Modusbits ersetzen dort keine ACLs.\n- Geht der Serverschlüssel verloren oder wird er unpassend ausgetauscht, können\n Schlüssel nicht mehr angezeigt werden. Das System ersetzt ihn bei bestehenden\n Einträgen nicht stillschweigend. Vorhandene Schlüssel können anhand ihrer\n Prüfwerte weiterhin authentifiziert und über die Weboberfläche widerrufen werden.\n Zur Wiederherstellung die richtige Schlüsseldatei aus der Sicherung zurückspielen.\n\nNach einem Update die API-Abhängigkeiten neu installieren (`.[api]` umfasst jetzt\n`cryptography`) und den API-Dienst neu starten. Eine Datenbankmigration per Hand\nist nicht nötig. Keine Änderung oder Neueingabe des Gemini-Schlüssels erforderlich.\n"
},
{
"id": "web-console",
"title": "Weboberfläche im Detail",
"markdown": "# Webübersicht\n\nDie Betriebsübersicht wird durch die bestehende Übergabe-API ausgeliefert:\n\n```powershell\n.\\.venv\\Scripts\\python.exe -m viano_bug_analysis.api serve --config config/api.local.toml\n```\n\nAnschließend `http://127.0.0.1:8080/` öffnen und mit **Benutzername und Passwort**\nanmelden. Benutzer werden ausschließlich über privilegierte Konsolenbefehle\nangelegt, geändert und gelöscht, siehe [Benutzerverwaltung](user-accounts.md).\nAlle Webbenutzer haben dieselben Rechte für alle Projekte. Die unten beschriebenen\nClient-/Projektberechtigungen gelten weiterhin für externe API-Integrationen.\nFür entfernte Zugriffe HTTPS verwenden. Der Analyseworker läuft unabhängig davon wie unter\n[Analyseworker](analysis-worker.md) beschrieben. Das Öffnen der Übersicht löst\nkeine Modellanfragen aus.\n\n## Benutzeranleitung\n\nDer Reiter **Benutzeranleitung** enthält die allgemeine Bedienung und\nFunktionsweise sowie Anleitungen für Linux, Projekt-/DB-/Sourcekonfiguration,\nÜbergabe-API, Analyseworker, Nutzerkonten, Integrationsschlüssel, Weboberfläche\nund Erfassungsworker. Dokumentauswahl, Inhaltsverzeichnis und interne Verweise\nerlauben Navigation ohne Server-Dateizugriff. Markdown-Downloads sind ebenfalls\nmöglich. Der Reiter benötigt eine angemeldete Sitzung; sein Aufruf startet keine\nAnalyse. Tabellen, Codebeispiele und Überschriften werden als sichere DOM-Elemente\ndargestellt, eingebettetes HTML wird nicht ausgeführt.\n\nFür Dokumentationsänderungen die ursprünglichen Markdown-Dateien unter `docs/`\nbeziehungsweise `deploy/capture-worker/README.md` bearbeiten und danach\n`python scripts/build_user_manual.py` ausführen. Die feste Auswahlliste steht im\nSkript; tatsächliche lokale Konfigurationen oder Secrets werden nicht gebündelt.\n`python scripts/build_user_manual.py --check` und die Tests erkennen veraltete\nWebfassungen. Das erzeugte `web/user-manual.json` wird mit dem Python-Paket\nausgeliefert, sodass keine lokale Kopie des Dokumentationsordners nötig ist.\n\n## Worker und Fortschritt\n\nDie Startseite listet lebende Worker und darunter den jeweils bearbeiteten Fall\nmit seinem Namen und einem Ladebalken. Der bestehende Dienst erlaubt einen Worker\nje Workspace; die Darstellung ist bereits als Liste aufgebaut. Die Einführung\neines parallelen Worker-Pools ist damit nicht verbunden.\n\nDer Balken folgt abgeschlossenen Arbeitsschritten, nicht vergangener Zeit. Seine\nsieben Anteile entsprechen intern Aufnahme, Fallaufbau, Interpretation,\nHypothesenbildung, Untersuchung, Diagnose und Codingauftrag. Die Oberfläche führt\ndiese Stufen nicht einzeln auf. Der Codingauftrag ist die letzte Stufe, auch wenn\nsein Dokument fachlich erklärt, dass kein Codingauftrag benötigt wird. Bei\ngemeinsamer Interpretation/Hypothesengenerierung können zwei Anteile gleichzeitig\nfertig werden. Eine lang laufende Untersuchung füllt den Balken nicht künstlich.\n\nDer aktive Fall verschwindet bei `OUTPUT_READY`, `HANDED_OFF` oder\n`FAILED_TO_ANALYZE` aus der Workeransicht und der Tabelle laufender Fälle. Er bleibt\nin der Verbrauchsstatistik enthalten. Wartende Fälle bleiben in der Fallübersicht,\nbelegen aber keinen arbeitenden Worker. Ein alter Dienstsnapshot ohne lebenden\nTelemetrie-Heartbeat wird nicht als aktiver Worker dargestellt.\n\n## Verbrauch\n\n- Kreisdiagramm: bekannte Eingabe- und Ausgabetokens je Phase; zusätzliche\n Korrektur-/Reparatur-/Wiederholungsaufrufe haben ein eigenes orangefarbenes Segment.\n Die Legende zeigt Fallaufbau, Fallplanung, Hypothesen, Untersuchung, Diagnose und\n Codingauftrag auch bei null gemessenen Tokens. Nullwerte erzeugen keinen\n künstlichen Tortenanteil. Gemeinsame Planungsaufrufe bleiben einmal gezählt.\n- Balkendiagramm: Eingabe und Ausgabe einschließlich Denken je Kategorie.\n- Der erste reguläre Aufruf bleibt bei seiner Phase, auch wenn sein Ergebnis später\n zurückgewiesen wurde. Seine Korrektur wird ausschließlich dem Korrektursegment\n zugeordnet. Abgeleitete, deterministisch reparierte Antwortdokumente sind keine\n zusätzlichen Anbieteraufrufe und werden nicht erneut gezählt.\n- Denktokens sind bereits Bestandteil der Ausgabetokens. Sie werden weder zum\n Kreisdiagramm noch zum Gesamtverbrauch noch einmal hinzuaddiert.\n- Neue LLM-Runden speichern ihre Verbrauchszuordnung deterministisch aus der\n Operationskennung. Das verändert keine Prompts, Modellparameter oder Budgets.\n Bereits gespeicherte Aufrufe bleiben unverändert. Bei älteren Akten wird nur\n eindeutig erkennbare Korrekturinformation übernommen; sonst erscheint der Anteil\n als „Nicht zugeordnet“.\n- Fehlende Messwerte bleiben unbekannt. Die Diagramme summieren nur bekannte Werte\n und weisen unvollständige Aufrufe aus. Bei vollständig fehlender Tokennutzung\n lässt sich deren Größe nicht als Kreisanteil darstellen.\n\nDer Messumfang umfasst **alle erhaltenen, für den Client freigegebenen Akten und\nderLine truncated
},
{
"id": "capture-worker",
"title": "Erfassungsworker v0.4",
"markdown": "# wowdiag-worker v0.4 – BugAnalysis-Übergabe\n\nErweiterung des vorgelegten v0.3-Workers. Die ursprüngliche Queue-Claim-Logik und\nDB-Struktur bleiben erhalten. Keine Änderung der aktiven `config.toml`, kein\nDienststart und keine Datenbankmigration durch diese Dateien.\n\nNach erfolgreicher wowdiag-/wowsrc-Erzeugung wird ein Versandauftrag dauerhaft\nin einer separaten lokalen SQLite-Outbox gespeichert. `diagnostic_runs.completed`\nbedeutet weiterhin **Erfassung abgeschlossen**, nicht Analyse abgeschlossen oder\nAPI-Zustellung bestätigt. Die Versandquittung ist über `--handoff-status` sichtbar.\nDie API übernimmt den Fall anschließend in ihren eigenen Analyseworker.\n\n## Einrichtung\n\n1. Bestehende `wowdiag_worker.py` sichern und die v0.4-Datei sowie\n `buganalysis_handoff.py` nebeneinander installieren. Python 3.11 und PyMySQL\n bleiben die Voraussetzungen; der HTTP-Client benötigt keine neue Bibliothek.\n2. Den Abschnitt aus `buganalysis.config.example.toml` in die vorhandene\n Worker-Konfiguration übernehmen. Tatsächliche URL und Projektzuordnung setzen.\n Der URL muss die Serverwurzel sein; HTTPS ist für entfernte Server erforderlich.\n3. Einen Integrationsschlüssel in BugAnalysis für das Projekt erzeugen und im\n Dienst-Environment als `VBA_INTAKE_TOKEN` hinterlegen. Unter Linux eine geschützte\n systemd-EnvironmentFile verwenden; den Schlüssel nicht in Skriptargumente,\n Repository oder Worker-Logs schreiben. Bestehende Unit um EnvironmentFile\n ergänzen und anschließend kontrolliert neu starten.\n4. SQLite-Outbox und Diagnosearchive bis zur bestätigten Zustellung erhalten.\n Das Outbox-Verzeichnis muss für den Worker schreibbar sein und einen lokalen\n Datenträger mit SQLite-Dateisperren verwenden. Die Datei nicht zwischen\n unabhängigen Installationen teilen.\n\nOhne aktivierten `[buganalysis]`-Abschnitt bleibt der bisherige Ablauf erhalten.\nDas Flag bedeutet ausschließlich Erfassung plus Übergabe; der Worker löst keine\nModellanfrage selbst aus. Ein laufender BugAnalysis-Worker kann nach Annahme\nallerdings sofort eine kostenpflichtige Analyse beginnen.\n\n## Wiederholungen und Status\n\n```sh\npython3 wowdiag_worker.py --config config.toml --handoff-status\npython3 wowdiag_worker.py --config config.toml --retry-handoffs\n```\n\nDiese beiden Befehle führen keine Erfassung aus. Der normale Dauerdienst sendet\nfällige Aufträge vor dem nächsten Capture; `--once` versucht sie auch danach.\nNetzwerkfehler, Serverausfälle, temporäre Ablehnungen und fehlende/ungültige\nZugangsdaten bleiben `pending`. Nach Behebung werden dieselben Inhalte mit\ndemselben Idempotenzschlüssel versandt. Auch eine verlorene Erfolgsantwort legt\ndadurch keinen zweiten Fall an. Es werden keine HTTP-Redirects verfolgt.\n\nUnveränderliche Metadaten, Ziel-URL, Archivpfade und Inhaltsprüfwerte stehen in der\nOutbox; Zugangsdaten stehen dort nicht. Archivänderung oder endgültiger HTTP-\nKonflikt wird `attention` und benötigt Betreiberprüfung. Ein Lease verhindert\ngleichzeitigen regulären Versand durch mehrere Worker-Prozesse. Ein Prozessabbruch\nlässt den Auftrag nach Ablauf des Lease erneut versuchen. Es gibt keine automatische\nNeuerzeugung von Archiven oder ein Zurücksetzen der ursprünglichen Queue-Claims.\n\nBereits vor dem Update abgeschlossene Erfassungen werden nicht automatisch\nnachträglich eingesendet. Ein erneuter neuer Auftrag für dieselbe Bug-ID wird von\nBugAnalysis bewusst abgewiesen, wenn der Fall bereits existiert. Rückgaben nach\neinem Patch erfolgen über den separaten `case_return`-Vertrag und nicht durch\nautomatisches Umdeuten neuer Captures.\n\nDer Übergabeumschlag nennt die Bug-ID und den Erfassungs-Commit. Die wirklichen\nTesterbeobachtungen, Titel und Beziehungen liest der BugAnalysis-Importer direkt\naus den unveränderten Archiven. Der Worker erfindet keine Beobachtungen und gibt\nden Erfassungsstand nicht als Stand der ursprünglichen Fehlermeldung aus.\n\n## Lokale Prüfung im BugAnalysis-Checkout\n\n`python -m pytest tests/profiles/vcore/test_capture_handoff.py` prüft den Versand gegen die echte\nlokale Intake-API mit synthetischen Archiven. Kein echter Server, keine Cloud-\nAnfrage, keine Änderung der Diagnose-Queue. Ein Server-Feldtest mit einem neuen\nBug und Dienstneustart steht nach der Konfiguration noch aus.\n"
}
]
}
+14
View File
@@ -96,7 +96,21 @@ try {
if(await evaluate("document.querySelector('#key-list input').value!==''"))throw new Error('Hidden key retained in input');
await evaluate("window.confirm=()=>true;Array.from(document.querySelectorAll('#key-list button')).find(b=>b.textContent==='Widerrufen').click()");
await until(()=>evaluate("document.getElementById('key-list').textContent.includes('Widerrufen')"));
await evaluate("location.hash='#manual'");
await until(()=>evaluate("!document.getElementById('manual-page').hidden&&document.getElementById('manual-content').textContent.includes('So arbeitet BugAnalysis')"));
if(!await evaluate("document.getElementById('manual-document').options.length===9&&document.querySelectorAll('#manual-toc button').length>5"))throw new Error('Manual catalog or table of contents missing');
const safe=await evaluate("(()=>{const rendered=renderManualMarkdown('<script>alert(1)</script> [bad](javascript:alert)');const root=document.createElement('div');root.append(rendered.fragment);return !root.querySelector('script,a')&&root.textContent.includes('<script>');})()");
if(!safe)throw new Error('Manual renderer interpreted unsafe markup');
await writeFile(path.join(output,'user-manual-desktop.png'),Buffer.from((await send('Page.captureScreenshot',{format:'png'})).data,'base64'));
await evaluate("document.getElementById('manual-document').value='linux-deployment';document.getElementById('manual-document').dispatchEvent(new Event('change'))");
await until(()=>evaluate("document.querySelector('#manual-content pre')&&document.getElementById('manual-content').textContent.includes('systemd-run')"));
await evaluate("document.getElementById('manual-document').value='project-configuration';document.getElementById('manual-document').dispatchEvent(new Event('change'))");
await until(()=>evaluate("document.getElementById('manual-content').textContent.includes('password_env')&&document.querySelector('#manual-content table')"));
await send('Emulation.setDeviceMetricsOverride',{width:390,height:1200,deviceScaleFactor:1,mobile:true});await pause(200);
if(await evaluate('document.documentElement.scrollWidth>innerWidth'))throw new Error('Manual mobile overflow');
await writeFile(path.join(output,'user-manual-mobile.png'),Buffer.from((await send('Page.captureScreenshot',{format:'png'})).data,'base64'));
await evaluate("document.getElementById('logout').click()");
if(!await evaluate("document.getElementById('manual-content').childElementCount===0"))throw new Error('Logout retained manual content');
if(!await evaluate("document.getElementById('key-list').childElementCount===0"))throw new Error('Logout retained keys');
if(!await evaluate("document.getElementById('content').hidden&&!document.getElementById('login').hidden&&document.getElementById('token-count').textContent==='—'"))throw new Error('Logout retained visible case data');
if(!await evaluate("document.getElementById('case-detail').childElementCount===0&&document.getElementById('result-rows').childElementCount===0"))throw new Error('Logout retained detail data');
+42
View File
@@ -0,0 +1,42 @@
import importlib.util
import json
from pathlib import Path
import subprocess
import sys
from importlib.resources import files
from starlette.testclient import TestClient
from viano_bug_analysis.api import APIConfig,Project
from viano_bug_analysis.api.app import create_app
ROOT=Path(__file__).resolve().parents[1]
def test_bundle_matches_authoritative_sources_and_is_packaged():
spec=importlib.util.spec_from_file_location('manual_builder',ROOT/'scripts/build_user_manual.py')
builder=importlib.util.module_from_spec(spec);spec.loader.exec_module(builder)
packaged=json.loads(files('viano_bug_analysis.web').joinpath('user-manual.json').read_text(encoding='utf-8'))
assert packaged==builder.bundle()
assert len(packaged['documents'])==9
assert len({d['id'] for d in packaged['documents']})==9
assert 'user-manual.json' in (ROOT/'pyproject.toml').read_text()
subprocess.run([sys.executable,str(ROOT/'scripts/build_user_manual.py'),'--check'],check=True)
def test_manual_requires_login_and_does_not_run_worker(tmp_path):
config=APIConfig(tmp_path,(Project('p','wow','vcore'),),())
app=create_app(config);app.state.sessions.users.manage('create_user','reader','password')
client=TestClient(app)
assert client.get('/v1/manual').status_code==401
client.post('/v1/auth/login',json={'username':'reader','password':'password'})
response=client.get('/v1/manual')
assert response.status_code==200 and response.headers['cache-control']=='no-store'
docs={d['id']:d for d in response.json()['documents']}
assert 'So arbeitet BugAnalysis' in docs['user-guide']['markdown']
assert 'password_env' in docs['project-configuration']['markdown']
assert 'retry-handoffs' in docs['capture-worker']['markdown']
assert str(tmp_path) not in response.text
assert not (tmp_path/'runtime/worker/status.json').exists()
assert client.get('/assets/user-manual.js').status_code==200
assert client.get('/assets/user-manual.json').status_code==404