operations-playbook.yaml
Pfad: doku/operations-playbook.yaml
Ext: yaml
Größe: 8775 Bytes
Geändert: 2026-07-16 12:00:06+02
version: 1
project: demo.karlkratz.com
language: de
last_reviewed: 2026-07-16
storage:
dokumentation:
canonical_store:
type: postgres-table
table: public.dokumentation
repo: Demo\Infrastructure\Persistence\PostgresDokumentationRepository
route_read_list: "GET /doku"
route_read_item: "GET /doku/{slug}"
route_save: "POST /doku/{slug}"
route_history: "GET /doku/{slug}/{version}"
write_mode:
edit_only_via_route: true
create_only_via_sql: true
create_flow:
required: "INSERT INTO dokumentation (slug, titel, body, tags) ... ON CONFLICT (slug) DO NOTHING;"
migration_entry_points:
- migrations/0002_dokumentation.sql
- migrations/0003_dokumentation_seed.sql
- migrations/0008_system_seed.sql
edit_flow:
controller: Demo\Infrastructure\Http\DokuSpeichernAction
use_case: Demo\Application\Dokumentation\SpeichereInhalt
converter: Demo\Infrastructure\Content\HtmlToMarkdown
repository_call: speichereInhalt($slug, $markdown)
persistence_query: UPDATE dokumentation SET body = :body WHERE slug = :slug
redirect: "/doku/{slug}"
side_effect: audit.fn_write_history auf public.history_dokumentation
query_flow:
controller: Demo\Infrastructure\Http\DokuDetailAction
use_cases:
- Demo\Application\Dokumentation\ZeigeDokumentation
- Demo\Application\Dokumentation\ListeDokumentationVersionen
- Demo\Application\Dokumentation\ZeigeDokumentationVersion
renderer:
current_and_edit: Demo\Infrastructure\Content\MarkdownToHtml
view: src/Infrastructure/Http/view/doku-detail.php
prozesse:
canonical_store:
type: postgres-table
table: public.prozesse
detail_table: public.prozess_schritt
repo: Demo\Infrastructure\Persistence\PostgresProzessRepository
ports: Demo\Domain\Prozess\ProzessRepository
route_list: "GET /prozesse"
route_detail: "GET /prozesse/{slug}"
scope: read_only_in_ui
update_flow:
direct_ui_edit: false
persistence_via_db:
prefer_migration_files:
- migrations/0005_prozesse.sql
- migrations/0006_prozesse_seed.sql
- migrations/0008_system_seed.sql
min_verification: sql constraints and trigger checks
code_spiegel:
canonical_store:
type: postgres-table
table: public.code_datei
history_table: public.history_code_datei
repo: Demo\Infrastructure\Persistence\PostgresCodeDateiRepository
repo_use_cases:
- Demo\Application\Code\ListeCodeDateien
- Demo\Application\Code\ZeigeCodeDatei
- Demo\Application\Code\SynchronisiereCodeDateien
- Demo\Application\Code\ListeCodeDateiVersionen
- Demo\Application\Code\ZeigeCodeDateiVersion
refresh_flow:
on_routes:
- "GET /code"
- "GET /code/{id}"
- "GET /code/{id}/{version}"
scanner: Demo\Infrastructure\Code\CodeDateiScanner
sync_use_case: Demo\Application\Code\SynchronisiereCodeDateien
scanner_extensions: ['php','js','css','sql','sh','yml','yaml','json']
scanner_ignores: ['.git','credentials','node_modules','vendor','tmp']
sync_policy:
existing_path_equal_hash: no_update
existing_path_changed_hash: update
missing_in_scan: delete
new_in_scan: insert
sync_sequence:
1: sync->execute(scanner->scan())
2: list/view use case reads from DB
history:
trigger_mode: automatic via audit.fn_write_history
source_table: public.code_datei
operations:
doku_templates:
check_if_exists:
description: "Vor jedem Write prüfen, ob Slug schon existiert."
sql: |
SELECT id
FROM public.dokumentation
WHERE slug = :slug;
create_new_dokumentation:
description: "Neue Doku nur per SQL anlegen. Anschließend via UI editierbar."
example_rows:
- slug: "neue-doku"
titel: "Neue Dokumentation"
body: |
# Neue Dokumentation
Kurze Einordnung, Zielbild und Ablaufbeschreibung.
- Erstes Kapitel: Kontext
- Zweites Kapitel: Umsetzung
tags:
- dokumentation
- inhalt
- slug: "projekt-ueberblick"
titel: "Projekt-Überblick"
body: |
# Projekt-Überblick
Diese Seite beschreibt Zweck, Architektur und Betriebsweise.
## Architektur
Hexagonal aufgebaut mit klarer Schichtentrennung.
tags:
- system
- ueberblick
sql: |
INSERT INTO public.dokumentation (slug, titel, body, tags)
VALUES (:slug, :titel, :body_markdown, :tags)
ON CONFLICT (slug) DO UPDATE
SET body = EXCLUDED.body,
titel = EXCLUDED.titel,
tags = EXCLUDED.tags;
note: >
`ON CONFLICT` verhindert Fehler bei Wiederholung und hält den Flow robust.
Für reine Neuanlage kann `DO NOTHING` genutzt werden.
optional_followup:
- "INSERT INTO public.doku_prozess (doku_id, prozess_id) ... optional"
- "Link zu Prozessdoku per `SELECT id ...` + `INSERT` über prozess_id möglich"
create_or_update_via_ui:
description: "Update bestehender Slugs über den definierten System-Flow."
steps:
- "GET /doku/{slug}"
- "Inhalt ändern und abschicken"
- "POST /doku/{slug}"
route_call: "POST /doku/{slug}"
form_field: "inhalt"
request_payload_hint: |
inhalt=<html-sanitized-content>
success_observable:
- "Redirect auf /doku/{slug}"
- "Neue Zeile in public.history_dokumentation nach dem ersten Update"
latest_history_probe:
sql: |
SELECT history_id, operation, geaendert_am
FROM public.history_dokumentation
WHERE daten->>'slug' = :slug
ORDER BY geaendert_am DESC, history_id DESC
LIMIT 1;
read_version_snapshot:
sql: |
SELECT daten
FROM public.history_dokumentation
WHERE history_id = :history_id
AND daten->>'slug' = :slug;
verify_ui_after_change:
steps:
- "GET /doku/{slug} -> neuer Inhalt in HTML"
- "GET /doku/{slug}/{latest_history_id} -> unveränderter Snapshot"
prozesse_templates:
description: "Prozesse werden als Lesedaten gepflegt; Änderungen gehen über SQL/Migrations."
read:
list_sql: |
SELECT p.slug, p.titel, p.tags, p.updated_at
FROM public.prozesse p
ORDER BY p.updated_at DESC;
detail_sql: |
SELECT p.slug, p.titel, p.beschreibung, ps.name, ps.beschreibung
FROM public.prozesse p
JOIN public.prozess_schritt ps ON ps.prozess_id = p.id
WHERE p.slug = :slug
ORDER BY ps.id;
seed_pattern:
note: >
Neue Prozesse immer als neue Migration anlegen (z. B. 000X_*.sql),
bestehende Seeds über 0008_system_seed.sql/0006_prozesse_seed.sql ersetzen,
danach Reihenfolge/Constraints prüfen.
migration_snippet: |
INSERT INTO public.prozesse (slug, titel, beschreibung, tags)
VALUES (:slug, :titel, :beschreibung, :tags);
code_templates:
post_change_verify:
path_sql: |
SELECT id, relativer_pfad, code_hash, datei_mtime, updated_at
FROM public.code_datei
WHERE relativer_pfad = :relativer_pfad;
history_sql: |
SELECT history_id, operation, geaendert_am, daten->>'code_hash' AS code_hash
FROM public.history_code_datei
WHERE (daten->>'relativer_pfad') = :relativer_pfad
ORDER BY geaendert_am DESC, history_id DESC
LIMIT 5;
sync_effect:
- "Route aufrufen: GET /code/{id} (oder GET /code) nach Dateiaenderung"
- "Repository-Sync: Insert/Update/Delete via PostgresCodeDateiRepository::synchronisiere()"
when_user_says_create_doku:
- check if slug exists in public.dokumentation
- if exists: edit through POST /doku/{slug}
- if not exists: add row in public.dokumentation via migration/SQL before using UI
when_user_says_update_doku:
- open route GET /doku/{slug}
- edit content in editor
- POST /doku/{slug}
- verify with GET /doku/{slug} and GET /doku/{slug}/{latest_version}
when_user_says_create_or_update_prozess:
- do migration change in src/migrations first (0005/0006/0008 family)
- verify in DB and UI via /prozesse and /prozesse/{slug}
when_user_says_update_code_after_file_change:
- ensure changed files are in scanner scope (extensions/ignores)
- call /code route once (or /code/{id}) to trigger sync
- verify row changed in public.code_datei and snapshots in history_code_datei