Sammelcommit fuer den ueber mehrere Sitzungen gewachsenen, bisher nicht
eingecheckten Stand. Getrennte Commits waeren ehrlicher gewesen, sind es
nachtraeglich aber nicht mehr: BugRepo, changelog.json und die Doku tragen
Aenderungen aus allen Straengen gleichzeitig, und keine Zwischenstufe waere
lauffaehig.
ROADMAP IM DEPLOYMENTCENTER (2.7.0)
Planungsstand, Reihenfolge und Zustaendigkeit lagen in handgepflegten
Markdown-Dateien neben einem Bugtracker, der genau diese drei Dinge bereits
fuehrt. Zwei Pfleger fuer dasselbe Feld - Drift war eine Frage der Zeit.
Ab jetzt: Zustand im DC, Begruendung in der Datei. Kein Feld hat zwei Pfleger.
- Migration 016: epic, roadmap_rank, target_build, defer_trigger,
doc_anchor, Status "deferred", Tabelle roadmap_epics.
KEINE Roadmap-Tabelle - ein Eintrag ist ein bugtracker_item.
- Zugehoerigkeit ist ein Feld: Ein Item steht auf der Roadmap, wenn es ein
epic hat. Ein zweites Kennzeichen daneben koennte auseinanderlaufen.
- Kennung ist die Item-ID. Handvergebene Kuerzel ("A12") erzeugen die
schlimmste Fehlerart: Beide Seiten stimmig, Verschiedenes gemeint, weil
die Kennung nach einer Loeschung neu vergeben wurde.
- Zurueckstellen verlangt defer_trigger. Ohne benannten Anlass ist ein
Vorhaben nicht zurueckgestellt, sondern vergessen. Die Regel sitzt im
Repository, damit sie auf allen drei Schreibwegen gleich greift.
- Modules/Bugtracker/Roadmap.php, /api/bugtracker/v1/roadmap.php,
WebUI-Modul mit Filtern, cli/roadmap.php --write/--check,
dc_roadmap und dc_roadmap_set im MCP-Adapter.
- cli/roadmap.php --write ERZEUGT den Zustandsteil der Markdown-Datei;
--check prueft nur die Prosa. Was erzeugt wird, kann nicht driften.
- docs/ROADMAP_MIGRATION.md: Uebernahme fuer die anderen Projekte.
TESTINSTANZ UND TESTTOR
- dc-dev.mhdf.de: zweite Installation, eigene DB, eigene Schluessel,
Basic-Auth davor, Rocket.Chat aus.
- 124 Testfaelle in tests/e2e gegen API, Rechte, Migrationen, Sperre.
- scripts/deploy.py bringt vor jedem Upload erst den anstehenden Stand auf
die Testinstanz und laesst dann den Testlauf laufen. Erst bei
Rueckgabewert 0 geht etwas auf die Produktion (--ohne-test als Notausgang).
Andersherum prueft der Lauf aelteren Code als den, der gleich hochgeht.
- tests/ ist aus dem Produktiv-Deploy ausgeschlossen; testctl.php kann
nicht versehentlich auf dc.mhdf.de landen.
- Ein Lauf mit mehr uebersprungenen als geprueften Faellen endet mit
Rueckgabewert 2 statt 0. Nichts ist kaputt, geprueft wurde es aber auch
nicht - der Unterschied darf nicht am Rueckgabewert verschwinden.
SUPERVISOR, PROJEKTLANDSCHAFT, NACHPRUEFUNG (2.6.0/2.6.1)
- Migration 013: projektuebergreifend melden (dev_agent, evidence,
triage_external, affects).
- Migration 014: supervisor_findings, Katalog, fuenf serverseitige
Detektoren im Tick.
- Migration 015: Nachpruefung als Arbeitsschlange statt Benachrichtigung.
- DEV-Lizenzschluessel zur Selbstbedienung, dc-testkit fuer die Test-VM.
- MCP-Adapter: 15 Werkzeuge, ohne Fremdabhaengigkeiten, mit Abgrenzung
von Fremddaten gegen Prompt-Injektion.
BEHOBEN
- observe und status im Supervisor legten den Zustand des Befundes als
"status" in die Antwort. Http::ok() merged ['status' => 'success']
darunter, also gewann der Befundzustand - eine angenommene Meldung kam
als status="open" an. Heisst jetzt finding_status. War Item #59.
Migrationen 013-016 sind auf dem Produktivserver noch NICHT ausgefuehrt.
Ohne sie bricht das WebUI, weil die Roadmap-Ansicht bei jedem Seitenaufruf
gegen bugtracker_items.epic abfragt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
133 lines
4.7 KiB
PHP
133 lines
4.7 KiB
PHP
<?php
|
|
|
|
/**
|
|
* GET /api/bugtracker/v1/projects
|
|
*
|
|
* Projekt-Discovery fuer Agenten: welche Projekte gibt es, wie heissen ihre
|
|
* Slugs, wo liegt das Repository und wie viele Items sind offen.
|
|
*
|
|
* SICHERHEITSAENDERUNG: verlangt jetzt ein Token mit "bugtracker:read"
|
|
* (oder hoeher). Zuvor war die Projektliste oeffentlich abrufbar.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../../../../src/bootstrap.php';
|
|
|
|
use Deploymentcenter\Core\ApiAuth;
|
|
use Deploymentcenter\Core\Db;
|
|
use Deploymentcenter\Core\Http;
|
|
use Deploymentcenter\Core\ProjectLinks;
|
|
use Deploymentcenter\Modules\Bugtracker\BugRepo;
|
|
|
|
Http::beginJson(['GET', 'POST', 'OPTIONS'], true);
|
|
|
|
if (!in_array(Http::method(), ['GET', 'POST'], true)) {
|
|
Http::fail(405, 'method_not_allowed', 'Dieser Endpunkt erwartet GET oder POST.');
|
|
}
|
|
|
|
$db = Db::init();
|
|
$context = ApiAuth::requireScope($db, Http::method() === 'POST' ? 'bugtracker:report' : 'bugtracker:read');
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// POST: Beziehung des eigenen Projekts zu einem anderen festhalten.
|
|
// ---------------------------------------------------------------------------
|
|
// Ersetzt die von Hand gepflegte Zeile "Deploymentcenter-Anbindung: SDK 2.5.1"
|
|
// in jeder Projekt-CLAUDE.md. Eine Angabe, die an N Stellen nachgezogen werden
|
|
// muss, ist nach dem ersten Vergessen falsch - und zwar schweigend.
|
|
//
|
|
// from_slug kommt wie reported_from aus der Token-Bindung, nicht aus dem Body:
|
|
// ein Projekt traegt seine eigene Anbindung ein, nicht die eines anderen.
|
|
if (Http::method() === 'POST') {
|
|
$home = ApiAuth::homeProject($context);
|
|
|
|
if ($home === null) {
|
|
Http::fail(
|
|
400,
|
|
'no_home_project',
|
|
'Dieses Token ist an kein Projekt gebunden. Eine Beziehung braucht aber ein Projekt, '
|
|
. 'von dem aus sie gilt.'
|
|
);
|
|
}
|
|
|
|
$target = is_string(Http::input('to_slug')) ? trim((string)Http::input('to_slug')) : null;
|
|
|
|
if ($target === null || $target === '' || $target === $home) {
|
|
Http::fail(
|
|
400,
|
|
'invalid_target',
|
|
'Das Feld "to_slug" muss ein anderes Projekt als das eigene bezeichnen.'
|
|
);
|
|
}
|
|
|
|
$kind = is_string(Http::input('kind')) ? (string)Http::input('kind') : 'uses_sdk';
|
|
|
|
if (!in_array($kind, ProjectLinks::KINDS, true)) {
|
|
Http::fail(400, 'invalid_kind', 'Unbekannte Art der Beziehung.', null, [
|
|
'allowed' => ProjectLinks::KINDS,
|
|
]);
|
|
}
|
|
|
|
$version = is_string(Http::input('integrated_version'))
|
|
? trim((string)Http::input('integrated_version'))
|
|
: null;
|
|
|
|
$links = new ProjectLinks($db);
|
|
|
|
if (!$links->record($home, $target, $kind, $version !== '' ? $version : null)) {
|
|
Http::fail(500, 'link_failed', 'Die Beziehung konnte nicht gespeichert werden.');
|
|
}
|
|
|
|
Http::ok([
|
|
'from_slug' => $home,
|
|
'to_slug' => $target,
|
|
'kind' => $kind,
|
|
'integrated_version' => $version,
|
|
'message' => $version !== null
|
|
? sprintf('"%s" nutzt "%s" in Fassung %s.', $home, $target, $version)
|
|
: sprintf('Beziehung "%s" -> "%s" festgehalten.', $home, $target),
|
|
]);
|
|
}
|
|
|
|
$repo = new BugRepo($db);
|
|
$projects = $repo->getProjects();
|
|
|
|
// Ein Laufzeit-Token sieht nur sein eigenes Projekt. Ein Werkbank-Token sieht
|
|
// alle: es soll ja gerade entscheiden koennen, wohin ein Fehler gehoert - und
|
|
// dafuer muss es die Nachbarn kennen.
|
|
$bound = ApiAuth::projectFilter($context, true);
|
|
if ($bound !== null) {
|
|
$projects = array_values(array_filter(
|
|
$projects,
|
|
static fn(array $p): bool => (string)$p['slug'] === $bound
|
|
));
|
|
}
|
|
|
|
// Das Heimatprojekt bleibt auch beim Werkbank-Token bekannt. Es ist der
|
|
// Bezugspunkt, gegen den die Beziehungen ausgewiesen werden.
|
|
$home = ApiAuth::homeProject($context);
|
|
$links = new ProjectLinks($db);
|
|
|
|
Http::ok([
|
|
'count' => count($projects),
|
|
'home_project' => $home,
|
|
'projects' => array_map(static function (array $p) use ($links, $home): array {
|
|
$slug = (string)$p['slug'];
|
|
|
|
return [
|
|
'id' => (int)$p['id'],
|
|
'slug' => $slug,
|
|
'name' => (string)$p['name'],
|
|
'notes' => $p['notes'] ?? null,
|
|
'repo_url' => $p['repo_url'] ?? null,
|
|
'default_agent' => $p['default_agent'] ?? null,
|
|
'open_items' => (int)($p['open_items'] ?? 0),
|
|
'critical_items' => (int)($p['critical_items'] ?? 0),
|
|
|
|
// Wie haengt dieses Projekt mit dem eigenen zusammen? null heisst
|
|
// schlicht "gar nicht" und ist der Normalfall.
|
|
'relation' => $home !== null ? $links->between($home, $slug) : null,
|
|
];
|
|
}, $projects),
|
|
]);
|