Files
tessera-ctl/.planning/quick/260917-jdh-ci-job-desktop-ueberspringen-wenn-apps-d/260917-jdh-PLAN.md
T
schalli 5a444ec8f2
Tessera CI/CD / Lint & Type Check (push) Successful in 47s
Tessera CI/CD / Tests (push) Successful in 1m5s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 5m21s
Tessera CI/CD / Build & Publish Images (push) Successful in 4m20s
docs(quick-260917-jdf/jdh/jdd/jn2): Aktenstand — Bildmarke in Akzentfarbe, CI-Desktop-Skip, Favoriten-Symbol/-Sortierung, Desktop-Server-Adresse
Vier Quick-Tasks mit Plan, Bericht und Verifikation; Browser-Nachweis der
Web-Teile lokal erbracht (Kacheln #284a7b bei #0057b8, Proxy-Symbol trotz
Zertifikatsfehler, Direktbild bei interner Adresse, Sortierung ueber Reload).
Offen: CI-Beweis des Desktop-Skips nach diesem Push, Windows-VM-Probe der
Client-Aenderungen mit dem CI-Paket.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 15:10:20 +02:00

179 lines
39 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: quick-260917-jdh
plan: 01
type: execute
wave: 1
depends_on: []
autonomous: true
requirements: [QUICK-260917-JDH]
files_modified:
- .gitea/workflows/ci.yml
- .gitea/scripts/desktop-stamp.sh
- docs/anleitung-betrieb.md
- docs/ci-cd-setup.md
- docs/anleitung-entwicklung.md
- CHANGELOG.md
estimate:
tokens: 40000
raw_tokens: 40000
tasks: 2
confidence: low
must_haves:
truths:
- "Neues POSIX-Skript `.gitea/scripts/desktop-stamp.sh` (Kopfkommentar im Stil von desktop-collect.sh, `set -eu`, kennt kein Secret) mit zwei Unterbefehlen. `stamp`: Version aus `desktop-version.sh --print` (gleiches Verzeichnis, `$(dirname \"$0\")`), letzter Commit an den Desktop-Pfaden per `git log -1 --format=%H -- apps/desktop .gitea/scripts/desktop-version.sh .gitea/scripts/desktop-collect.sh .gitea/scripts/desktop-stamp.sh .gitea/workflows/ci.yml` (Konstante `DESKTOP_PATHS`; `pnpm-lock.yaml` bewusst nicht enthalten, Begruendung im Kopfkommentar), Ausgaben `stamp=<Version>-<voller SHA>`, `version=`, `sha7=`, `skip_allowed=true|false` (true NUR bei `GITHUB_REF` = `refs/heads/main`) — jede Ausgabe nach stdout UND, falls gesetzt, an `$GITHUB_OUTPUT`. Leerer SHA (keine Historie) → Exit 1 mit Hinweis auf `fetch-depth: 0`."
- "`desktop-stamp.sh check` (Umgebung `CACHE_HIT`, `STAMP_VERSION`, `STAMP_SHA7`, `DESKTOP_DIST` Vorgabe `desktop-dist`) gibt `reuse=true` und `files=<Name1>,<Name2>` nur aus, wenn ALLES gilt: `CACHE_HIT` = `true`; `manifest.json` vorhanden und per `jq -e .` gueltig; `.channel` = `beta`; `.version` = `STAMP_VERSION`; `.files.linux.name` und `.files.windows.name` nicht leer; jede der beiden Dateien existiert und stimmt in Groesse (`stat -c %s`) und sha256 (`sha256sum`) mit dem Manifest ueberein. Sonst `reuse=false`, die Reste (`*.AppImage`, `*.exe`, `manifest.json` in `DESKTOP_DIST`) werden entfernt, Exit 0 (kein Job-Fehler), Grund im Log. Bei Treffer Log-Zeile `Desktop unveraendert seit <sha7>: Pakete <Namen> aus dem Zwischenspeicher (gebaut aus <manifest.commit> am <buildTime>)`."
- "ci.yml, Job `desktop`: direkt nach `actions/checkout@v4` (Index 0) stehen drei neue Schritte — Index 1 `id: stamp` (`run: sh .gitea/scripts/desktop-stamp.sh stamp`), Index 2 `id: stamp-cache` (`uses: actions/cache/restore@v4`, `path: desktop-dist`, `key: desktop-dist-stamp-${{ steps.stamp.outputs.stamp }}`, KEIN `restore-keys`, `if: steps.stamp.outputs.skip_allowed == 'true'`), Index 3 `id: reuse` (`run: sh .gitea/scripts/desktop-stamp.sh check` mit `env` `CACHE_HIT: ${{ steps.stamp-cache.outputs.cache-hit }}`, `STAMP_VERSION: ${{ steps.stamp.outputs.version }}`, `STAMP_SHA7: ${{ steps.stamp.outputs.sha7 }}`; ohne `if`, laeuft immer)."
- "ci.yml: alle 13 bisherigen Bau-Schritte zwischen `reuse` und `Uebergabe an publish` (setup-node, corepack, pnpm install, Systemabhaengigkeiten, Rust-Toolchain, Cargo-Zwischenspeicher, Windows-Werkzeuge, Version setzen, Rust pruefen, Alte Bundles entfernen, Linux-AppImage bauen, Windows-Installer bauen, Pakete einsammeln) tragen `if: steps.reuse.outputs.reuse != 'true'`; ihr Inhalt bleibt sonst byteidentisch. Neuer Schritt `Pakete unter dem Stempel ablegen` (`actions/cache/save@v4`, `path: desktop-dist`, gleicher Schluessel wie der Restore, `if: steps.reuse.outputs.reuse != 'true' && steps.stamp.outputs.skip_allowed == 'true'`) steht UNMITTELBAR vor `Uebergabe an publish`; `Uebergabe an publish` bleibt ohne `if` und mit Schluessel `desktop-dist-${{ gitea.sha }}`."
- "ci.yml: Jobs `quality`, `test`, `publish`, der `on:`-Block sowie `env`/`needs`/`if` des Jobs `desktop` sind gegenueber Commit 38c1400 unveraendert (js-yaml-Tiefenvergleich). Kopfkommentar (Z. 1-11) um einen Absatz `quick-260917-jdh` ergaenzt. `node` + js-yaml parst die Datei fehlerfrei."
- "Lokale Probe des Skripts: `DESKTOP_TAG=v1.2.0 GITHUB_REF=refs/heads/main sh .gitea/scripts/desktop-stamp.sh stamp` liefert `stamp=1.2.0-<SHA aus git log>` und `skip_allowed=true`; mit `GITHUB_REF=refs/tags/v1.2.0` `skip_allowed=false`; mit gesetztem `GITHUB_OUTPUT` landen alle vier Schluessel in der Datei. `check` gegen einen im Scratchpad gebauten Mini-`desktop-dist` (zwei 1-Byte-Dateien, Manifest via `jq -n` mit echten Groessen/Pruefsummen, Kanal beta, Version 1.2.0) liefert `reuse=true`; nach Veraendern einer Datei `reuse=false` und das Manifest ist entfernt; mit `CACHE_HIT=false` `reuse=false`."
- "docs/anleitung-betrieb.md Kap. 10: neuer Unterabschnitt `### Wann gebaut wird und wann Pakete übernommen werden` zwischen „Woher die Pakete kommen” und „Wo die Pakete im Abbild liegen” (Stempel, Tags bauen immer, nach Freigabe einmal neu, uebernommene Pakete tragen den Stand ihres Baus — gewollt fuer die Client-Updatepruefung, Cache-Ablauf → Neubau, Pruefung vor Uebernahme); Satz zur Job-Dauer verweist darauf; eine neue Zeile in der Fehlerbilder-Tabelle (aelterer Commit im Paketnamen ist kein Fehler). docs/ci-cd-setup.md Abschnitt 4 (Job `desktop`): Absatz `Ueberspringen bei unveraendertem Desktop` mit Schrittfolge, Schluessel, Pfadliste, Begruendung `pnpm-lock.yaml`, Verweis auf `desktop-stamp.sh`; Abschnitt 6: neuer Eintrag `### desktop baut, obwohl nichts geaendert wurde — oder uebernimmt trotz Aenderung`. docs/anleitung-entwicklung.md, Abschnitt „Desktop-App lokal bauen”: ein Absatz zum Ueberspringen im CI, zur Pfadliste und zur lokalen Stempel-Probe."
- "CHANGELOG.md `## Unveröffentlicht` `### Geändert`: genau ein neuer Stichpunkt `Desktop-App: Beta-Pakete werden nur noch neu gebaut, wenn sich an der Desktop-App etwas geändert hat; sonst bleiben die zuletzt gebauten Pakete gültig, und die App meldet keinen neuen Beta-Stand` (Stil wie Bestand: Praefix `Desktop-App:`, echte Umlaute, kein Punkt am Ende). Nur zusaetzliche Zeilen; Zeilen anderer paralleler Auftraege bleiben stehen."
- "Zwei Commits ohne Push: `ci: …` (Task 1) und `docs: …` (Task 2); keine `.planning/`-Dateien in den Commits; keine Datei ausserhalb von files_modified."
artifacts:
- ".gitea/scripts/desktop-stamp.sh — neu (Unterbefehle `stamp` und `check`, Konstante `DESKTOP_PATHS`)"
- ".gitea/workflows/ci.yml — Job `desktop`: Schritte `stamp`, `stamp-cache`, `reuse`, `Pakete unter dem Stempel ablegen`; `if:` an 13 Bau-Schritten; Kopfkommentar"
- "docs/anleitung-betrieb.md — Kap. 10 Unterabschnitt + Fehlerbilder-Zeile"
- "docs/ci-cd-setup.md — Abschnitt 4 Absatz, Abschnitt 6 Eintrag"
- "docs/anleitung-entwicklung.md — Absatz in „Desktop-App lokal bauen”"
- "CHANGELOG.md — ein Stichpunkt unter Unveröffentlicht/Geändert"
key_links:
- "Die Ueberspringen-Kette ist: `stamp` (Stempel) → `stamp-cache` (Restore, exakter Schluessel) → `reuse` (Manifest/Pruefsummen-Gate, Ausgabe `reuse`) → `if:` an JEDEM Bau-Schritt → `Uebergabe an publish` unveraendert. Fehlt das `if:` an nur einem Bau-Schritt, laeuft er im Skip-Fall ins Leere (z. B. `Pakete einsammeln` ohne Bundles → Job rot); `Uebergabe an publish` darf umgekehrt NIE ein `if:` bekommen, sonst bricht `publish` mit `fail-on-cache-miss` ab."
- "act_runner (v0.6.1 auf dem Dev-Host, Cache-Server aktiv) sucht zu JEDEM Schluessel erst exakt, dann als Praefix. Deshalb steht der volle 40-stellige SHA am Ende des Stempel-Schluessels (nichts kann laenger und gleich-praefixig sein) und der Restore hat KEIN `restore-keys` — ein Praefix-Treffer waere ein fremder Stand. `cache-hit` ist ohnehin nur bei exaktem Treffer `true`; das `check`-Gate verlangt genau das."
- "`skip_allowed` haengt an `GITHUB_REF == refs/heads/main`: Nur so kommen ausschliesslich Beta-Pakete (Suffix `-beta.<sha7>`, `channel: beta`) unter einen Stempel-Schluessel. Bei Tags wird weder gesucht noch abgelegt — Release-Dateien entstehen frisch mit reiner Version; `check` prueft zusaetzlich `channel == beta`."
- "Die Version ist Teil des Stempels, weil `desktop-version.sh --print` nach einem Freigabe-Tag (Kap. 9: `git merge --ff-only main` + Tag, also auf der main-Historie) eine neue Basisversion liefert — die Beta-Pakete muessen dann einmal neu entstehen, auch wenn `apps/desktop` unveraendert ist."
- "Die drei Stempel-Schritte stehen VOR setup-node/apt/rustup: `actions/cache/restore` ist eine JS-Action und laeuft (wie checkout) unter dem Node des Runners; das Skript braucht nur `git`, `jq`, `sha256sum`, `stat` — alle im Runner-Abbild (`publish` nutzt `jq` heute vor jedem Installationsschritt). Der Cargo-Cache-Schritt (`actions/cache@v4`) hat einen Post-Schritt; ist er per `if:` uebersprungen, entfaellt auch der Post-Schritt."
- "`publish` bleibt unangetastet: Es holt weiter `desktop-dist-${{ gitea.sha }}`. Im Skip-Fall sichert `Uebergabe an publish` den restaurierten `desktop-dist/` (alter Beta-Suffix, alter `manifest.commit`) unter dem neuen SHA — der Client vergleicht `manifest.commit` mit seinem `TESSERA_COMMIT`, also bekommt ein Client dieses Standes keinen Update-Hinweis mehr, ein aelterer weiterhin (gewollt, siehe Betriebshandbuch)."
---
<objective>
Der CI-Job `desktop` baut die Rust/Tauri-Pakete (Linux-AppImage, Windows-Installer per Cross-Bau) heute bei jedem Push auf `main` — rund fuenf Minuten auf dem einzigen Runner, obwohl die meisten Pushes nur Web/API aendern. Kuenftig ueberspringt der Job den Bau, wenn sich am Desktop-Stand nichts geaendert hat, und uebernimmt die zuletzt gebauten Pakete aus dem Zwischenspeicher des Runners:
1. **Stempel** = `<Version aus desktop-version.sh --print>-<voller SHA des letzten Commits an den Desktop-Pfaden>` (Pfadliste: `apps/desktop`, `desktop-version.sh`, `desktop-collect.sh`, `desktop-stamp.sh`, `ci.yml`). Berechnet von einem neuen, lokal testbaren Skript `.gitea/scripts/desktop-stamp.sh`.
2. **Ablauf im Job:** checkout → Stempel → `actions/cache/restore@v4` (Schluessel `desktop-dist-stamp-<Stempel>`, nur auf `main`) → Pruefung des gefundenen `desktop-dist/` (Manifest, Kanal, Version, Dateien, Groessen, Pruefsummen) → bei Treffer entfallen alle Bau-Schritte per `if:`; sonst Bau wie bisher plus Ablage unter dem Stempel-Schluessel. `Uebergabe an publish` (`desktop-dist-<sha>`) laeuft immer, `publish` bleibt unveraendert.
3. **Tags `v*` bauen immer** (Release-Dateien frisch, reine Version). Fehlender/beschaedigter Cache → normaler Bau (fail-safe).
4. **Doku** in Betriebshandbuch Kap. 10, CI-Runbook Abschnitt 4/6, Entwicklungsanleitung; ein CHANGELOG-Stichpunkt.
Task 1 ist der Tracer: Skript + Workflow bilden die komplette Kette, lokal bewiesen durch Skript-Proben (Stempel, Tag-Fall, `GITHUB_OUTPUT`, `check` mit gutem/veraendertem/fehlendem Cache) und eine js-yaml-Strukturpruefung des Workflows (Reihenfolge, `if:`-Bedingungen, Schluessel, unveraenderte Nachbarjobs). Der echte CI-Beweis (ein Push ohne Desktop-Aenderung ueberspringt, einer mit Desktop-Aenderung baut) ist Nachweis durch den Orchestrator NACH dem Push — kein Executor-Task.
Purpose: Pushes auf `main`, die nur Web/API betreffen, sollen den Runner nicht fuenf Minuten mit einem identischen Rust-Bau belegen; die Beta-Pakete bleiben dabei exakt die des letzten Desktop-Standes, sodass installierte Clients keinen unnoetigen Update-Hinweis bekommen.
Output: `desktop-stamp.sh` (neu), angepasste `ci.yml`, vier Doku-Stellen, ein CHANGELOG-Stichpunkt, zwei Commits (`ci:`, `docs:`), kein Push.
</objective>
<execution_context>
@~/.claude/gsd-core/workflows/execute-plan.md
@~/.claude/gsd-core/templates/summary.md
</execution_context>
<context>
@/home/vicolab/projects/tessera-ctl/CLAUDE.md
@/home/vicolab/projects/tessera-ctl/.gitea/workflows/ci.yml
@/home/vicolab/projects/tessera-ctl/.gitea/scripts/desktop-version.sh
@/home/vicolab/projects/tessera-ctl/.gitea/scripts/desktop-collect.sh
@/home/vicolab/projects/tessera-ctl/.planning/phases/18-desktop-client-fertigstellen/18-02-SUMMARY.md
</context>
<tasks>
<task type="tracer">
<name>Task 1: `desktop-stamp.sh` (Stempel + Cache-Pruefung) und Job `desktop` ueberspringt den Bau bei Treffer</name>
<files>.gitea/scripts/desktop-stamp.sh, .gitea/workflows/ci.yml</files>
<read_first>
- .gitea/scripts/desktop-collect.sh Z. 1-23 (Kopfkommentar-Stil, `set -eu`, Umgebungsvariablen-Block, „Dieses Skript kennt kein Secret”), Z. 79-84 (Aufraeumen von `DESKTOP_DIST`), Z. 131-154 (Manifest-Form: `version`, `channel`, `commit`, `buildTime`, `files.linux/windows.{name,size,sha256}`)
- .gitea/scripts/desktop-version.sh Z. 17-48 (`--print`, `DESKTOP_TAG` fuer die lokale Probe, Exit 1 ohne Tag)
- .gitea/workflows/ci.yml Z. 1-11 (Kopfkommentar), Z. 62-152 (Job `desktop`, 15 Schritte), Z. 154-186 (`publish` — NICHT anfassen)
- .planning/phases/18-desktop-client-fertigstellen/18-02-SUMMARY.md Z. 27 und Z. 122 (Cache-Uebergabe per save/restore mit exaktem SHA-Schluessel ist auf dieser Gitea-Instanz bewiesen; `grep` mit `${{`-Mustern in dieser Umgebung nur ueber `command grep`)
</read_first>
<action>
**A. Neues Skript `.gitea/scripts/desktop-stamp.sh`** (POSIX sh, `#!/bin/sh`, `set -eu`, Kopfkommentar wie desktop-collect.sh mit Kennung `quick-260917-jdh`, Zeile „Dieses Skript kennt kein Secret”). Kopfkommentar erklaert: Zweck (Bau ueberspringen, wenn der Desktop-Stand unveraendert ist), Aufbau des Stempels, Aufrufformen, Umgebungsvariablen, und warum `pnpm-lock.yaml` NICHT in der Pfadliste steht (die Tauri-CLI-Version haengt an `apps/desktop/package.json`, das enthalten ist; der Desktop-Bau liest ausserhalb von `apps/desktop` keine Werkstatt-Datei — `frontendDist` ist `../src` innerhalb von `apps/desktop`, keine Abhaengigkeit auf `packages/*`). Aufbau:
1. Konstante `DESKTOP_PATHS` = `apps/desktop .gitea/scripts/desktop-version.sh .gitea/scripts/desktop-collect.sh .gitea/scripts/desktop-stamp.sh .gitea/workflows/ci.yml` (eine Zeile, Leerzeichen-getrennt; das Skript selbst gehoert dazu, damit eine Aenderung der Stempelregel einen Neubau ausloest).
2. Helfer `out NAME WERT`: schreibt `NAME=WERT` nach stdout und, wenn `GITHUB_OUTPUT` gesetzt ist, zusaetzlich per `>>` in diese Datei.
3. Unterbefehl `stamp`: `VERSION="$("$(dirname "$0")/desktop-version.sh" --print)"` (Fehler des Aufrufs durchreichen — ohne Tag endet es mit Exit 1 wie bisher); `LAST="$(git log -1 --format=%H -- $DESKTOP_PATHS)"` (bewusst ungequotet, Wortaufteilung der Pfadliste); leeres `LAST` → Fehlermeldung nach stderr („keine Historie zu den Desktop-Pfaden — im CI ist fetch-depth: 0 Pflicht”) und Exit 1; `SHA7="$(git rev-parse --short=7 "$LAST")"`; `SKIP=false`, bei `${GITHUB_REF:-}` = `refs/heads/main` → `true`; Log-Zeile mit Version, `SHA7`, Betreff des Commits (`git log -1 --format=%s "$LAST"`) und der Aussage, ob Ueberspringen erlaubt ist (bei Tag/anderem Ref: „Tag oder fremder Zweig — es wird immer gebaut”); dann `out stamp "$VERSION-$LAST"`, `out version "$VERSION"`, `out sha7 "$SHA7"`, `out skip_allowed "$SKIP"`.
4. Unterbefehl `check`: liest `CACHE_HIT` (Vorgabe leer), `STAMP_VERSION`, `STAMP_SHA7`, `DESKTOP_DIST` (Vorgabe `desktop-dist`), `MANIFEST="$DESKTOP_DIST/manifest.json"`. Lokale Funktion `no_reuse GRUND`: Meldung `Kein uebernehmbarer Stand (GRUND) -- Desktop wird gebaut.`, entfernt `"$DESKTOP_DIST"/*.AppImage "$DESKTOP_DIST"/*.exe "$MANIFEST"` (`rm -f`, wie desktop-collect.sh; `.gitkeep` bleibt), `out reuse false`, `exit 0`. Pruefreihenfolge, jeweils `no_reuse` mit sprechendem Grund: `CACHE_HIT` ungleich `true` („kein Zwischenspeicher zum Stempel”); Manifest fehlt oder `jq -e . "$MANIFEST" >/dev/null` scheitert; `jq -r .channel` ungleich `beta`; `jq -r .version` ungleich `STAMP_VERSION`; `jq -r '.files.linux.name // empty'` oder `.files.windows.name` leer; je Datei: nicht vorhanden, `stat -c %s` ungleich `.files.<p>.size`, `sha256sum | cut -d' ' -f1` ungleich `.files.<p>.sha256`. Sind alle Pruefungen bestanden: Log-Zeile `Desktop unveraendert seit $STAMP_SHA7: Pakete <linux>, <windows> aus dem Zwischenspeicher (gebaut aus <.commit> am <.buildTime>)`, `out reuse true`, `out files "<linux>,<windows>"`.
5. Unbekannter/fehlender Unterbefehl → Aufrufhinweis nach stderr, Exit 1. Ausfuehrbit setzen (`chmod +x`, wie die Nachbarn; Aufruf im Workflow trotzdem per `sh`).
**B. `.gitea/workflows/ci.yml`, nur Job `desktop`** (Jobs `quality`, `test`, `publish`, `on:`, Job-`env`/`needs`/`if` bleiben byteidentisch):
1. Kopfkommentar: nach Z. 11 einen Absatz `quick-260917-jdh` anfuegen: `desktop` ueberspringt den Bau auf `main`, wenn zum Stempel (Version + letzter Commit an den Desktop-Pfaden, `.gitea/scripts/desktop-stamp.sh`) fertige Pakete im Zwischenspeicher liegen; Tags `v*` bauen immer; `publish` unveraendert.
2. Nach dem Checkout-Schritt drei Schritte einfuegen (Kommentar davor: warum sie VOR setup-node/apt/Rust stehen — die teuren Schritte sollen beim Ueberspringen gar nicht laufen): (a) `name: Desktop-Stempel berechnen`, `id: stamp`, `run: sh .gitea/scripts/desktop-stamp.sh stamp`. (b) `name: Fertige Pakete zum Stempel suchen`, `id: stamp-cache`, `if: steps.stamp.outputs.skip_allowed == 'true'`, `uses: actions/cache/restore@v4`, `with:` `path: desktop-dist`, `key: desktop-dist-stamp-${{ steps.stamp.outputs.stamp }}` — bewusst OHNE `restore-keys` (Kommentar: act_runner sucht auch zum Hauptschluessel per Praefix; ein aelterer Stand darf nie als Treffer gelten). (c) `name: Gefundene Pakete pruefen`, `id: reuse`, `env:` `CACHE_HIT: ${{ steps.stamp-cache.outputs.cache-hit }}`, `STAMP_VERSION: ${{ steps.stamp.outputs.version }}`, `STAMP_SHA7: ${{ steps.stamp.outputs.sha7 }}`, `run: sh .gitea/scripts/desktop-stamp.sh check` — kein `if`, damit `steps.reuse.outputs.reuse` immer definiert ist (bei Tags: `CACHE_HIT` leer → `reuse=false`).
3. An jeden der 13 Bau-Schritte (setup-node, corepack, pnpm install, Systemabhaengigkeiten, Rust-Toolchain, Cargo-Zwischenspeicher, Windows-Werkzeuge, Version setzen, Rust pruefen, Alte Bundles entfernen, Linux-AppImage bauen, Windows-Installer bauen, Pakete einsammeln) die Zeile `if: steps.reuse.outputs.reuse != 'true'` anfuegen (bei `uses:`-Schritten direkt nach `uses:`/`name:`; sonst nichts aendern — Reihenfolge, Inhalte, Kommentare bleiben).
4. Vor `Uebergabe an publish` einen Schritt `name: Pakete unter dem Stempel ablegen`, `if: steps.reuse.outputs.reuse != 'true' && steps.stamp.outputs.skip_allowed == 'true'`, `uses: actions/cache/save@v4`, `with:` `path: desktop-dist`, `key: desktop-dist-stamp-${{ steps.stamp.outputs.stamp }}` (Kommentar: nur nach echtem Bau und nur auf main — Tag-Pakete tragen keinen Beta-Suffix und duerfen nie unter einem Stempel liegen; ein bereits vorhandener Schluessel loest bei actions/cache/save nur eine Info aus, keinen Fehler).
5. `Uebergabe an publish` bleibt exakt wie bisher (kein `if`, Schluessel `desktop-dist-${{ gitea.sha }}`); Kommentar davor ergaenzen: laeuft in beiden Faellen — im Skip-Fall sichert er den restaurierten Stand unter dem neuen SHA, deshalb muss `publish` nichts wissen.
**C. Lokale Proben** (Teil von `<verify>`; nichts davon braucht den Runner): `sh -n`, Stempel auf `main`/Tag, `GITHUB_OUTPUT`, `check` mit gutem, veraendertem und fehlendem Cache (Mini-`desktop-dist` im Scratchpad, nie das echte `desktop-dist/` des Arbeitsbaums), js-yaml-Strukturpruefung des Workflows samt Tiefenvergleich der unveraenderten Jobs gegen `git show 38c1400:.gitea/workflows/ci.yml`. Zaehlpruefungen auf `${{`-Muster nur mit `command grep` (Shim-Artefakt laut 18-02).
Kein `git push`, kein Docker, kein `tauri build`, keine Aenderung an `publish` oder an den Skripten `desktop-version.sh`/`desktop-collect.sh`/`publish-*.sh`.
</action>
<verify>
<automated>cd /home/vicolab/projects/tessera-ctl && sh -n .gitea/scripts/desktop-stamp.sh && test -x .gitea/scripts/desktop-stamp.sh && LAST="$(git log -1 --format=%H -- apps/desktop .gitea/scripts/desktop-version.sh .gitea/scripts/desktop-collect.sh .gitea/scripts/desktop-stamp.sh .gitea/workflows/ci.yml)" && test -n "$LAST" && EXP="1.2.0-$LAST" && OUT="$(DESKTOP_TAG=v1.2.0 GITHUB_REF=refs/heads/main sh .gitea/scripts/desktop-stamp.sh stamp)" && printf '%s\n' "$OUT" | command grep -qx "stamp=$EXP" && printf '%s\n' "$OUT" | command grep -qx 'skip_allowed=true' && printf '%s\n' "$OUT" | command grep -qx 'version=1.2.0' && DESKTOP_TAG=v1.2.0 GITHUB_REF=refs/tags/v1.2.0 sh .gitea/scripts/desktop-stamp.sh stamp | command grep -qx 'skip_allowed=false' && O=$(mktemp) && GITHUB_OUTPUT=$O DESKTOP_TAG=v1.2.0 GITHUB_REF=refs/heads/main sh .gitea/scripts/desktop-stamp.sh stamp >/dev/null && command grep -q '^stamp=1.2.0-' "$O" && command grep -q '^version=1.2.0$' "$O" && command grep -q -E '^sha7=[0-9a-f]{7}$' "$O" && command grep -q '^skip_allowed=true$' "$O" && rm -f "$O" && D=$(mktemp -d) && printf 'a' > "$D/Tessera-1.2.0-beta.2cd4adc.AppImage" && printf 'b' > "$D/Tessera-Setup-1.2.0-beta.2cd4adc.exe" && jq -n --arg l "$(sha256sum "$D/Tessera-1.2.0-beta.2cd4adc.AppImage" | cut -d' ' -f1)" --arg w "$(sha256sum "$D/Tessera-Setup-1.2.0-beta.2cd4adc.exe" | cut -d' ' -f1)" '{version:"1.2.0",channel:"beta",commit:"2cd4adc",buildTime:"2026-09-17T00:00:00Z",files:{linux:{name:"Tessera-1.2.0-beta.2cd4adc.AppImage",size:1,sha256:$l},windows:{name:"Tessera-Setup-1.2.0-beta.2cd4adc.exe",size:1,sha256:$w}}}' > "$D/manifest.json" && CACHE_HIT=true STAMP_VERSION=1.2.0 STAMP_SHA7=2cd4adc DESKTOP_DIST="$D" sh .gitea/scripts/desktop-stamp.sh check | tee /dev/stderr | command grep -qx 'reuse=true' && printf 'x' >> "$D/Tessera-Setup-1.2.0-beta.2cd4adc.exe" && CACHE_HIT=true STAMP_VERSION=1.2.0 STAMP_SHA7=2cd4adc DESKTOP_DIST="$D" sh .gitea/scripts/desktop-stamp.sh check | command grep -qx 'reuse=false' && test ! -f "$D/manifest.json" && CACHE_HIT=false DESKTOP_DIST="$D" sh .gitea/scripts/desktop-stamp.sh check | command grep -qx 'reuse=false' && rm -rf "$D" && node -e 'const y=require("./node_modules/.pnpm/js-yaml@4.2.0/node_modules/js-yaml");const fs=require("fs");const cp=require("child_process");const d=y.load(fs.readFileSync(".gitea/workflows/ci.yml","utf8"));const o=y.load(cp.execSync("git show 38c1400:.gitea/workflows/ci.yml").toString());for(const j of["quality","test","publish"]){if(JSON.stringify(d.jobs[j])!==JSON.stringify(o.jobs[j]))throw new Error("Job veraendert: "+j)}if(JSON.stringify(d.on)!==JSON.stringify(o.on))throw new Error("on veraendert");const n=d.jobs.desktop,p=o.jobs.desktop;for(const k of["env","needs","if","runs-on"]){if(JSON.stringify(n[k])!==JSON.stringify(p[k]))throw new Error("desktop."+k+" veraendert")}const s=n.steps;const ix=i=>s.findIndex(x=>x.id===i);const iS=ix("stamp"),iC=ix("stamp-cache"),iR=ix("reuse");const iU=s.findIndex(x=>x.name==="Uebergabe an publish");const iV=s.findIndex(x=>x.name==="Pakete unter dem Stempel ablegen");if(!(iS===1&&iC===2&&iR===3))throw new Error("Stempel-Schritte nicht an Index 1-3");if(s[iS].run.trim()!=="sh .gitea/scripts/desktop-stamp.sh stamp")throw new Error("stamp run");const key="desktop-dist-stamp-${{ steps.stamp.outputs.stamp }}";if(!(s[iC].uses==="actions/cache/restore@v4"&&s[iC].with.path==="desktop-dist"&&s[iC].with.key===key&&!("restore-keys" in s[iC].with)&&String(s[iC].if).includes("steps.stamp.outputs.skip_allowed")))throw new Error("Restore-Schritt");if(!(s[iR].run.trim()==="sh .gitea/scripts/desktop-stamp.sh check"&&s[iR].if===undefined&&String(s[iR].env.CACHE_HIT).includes("steps.stamp-cache.outputs.cache-hit")&&String(s[iR].env.STAMP_VERSION).includes("steps.stamp.outputs.version")&&String(s[iR].env.STAMP_SHA7).includes("steps.stamp.outputs.sha7")))throw new Error("reuse-Schritt");if(iU-iR-1!==14)throw new Error("erwartet 13 Bau-Schritte + Stempel-Save zwischen reuse und Uebergabe, gefunden "+(iU-iR-1));for(let i=iR+1;i<iU;i++){if(!(s[i].if&&String(s[i].if).includes("steps.reuse.outputs.reuse")))throw new Error("if fehlt: "+(s[i].name||s[i].uses))}const old=p.steps.slice(1,14),neu=s.slice(iR+1,iV).map(x=>{const c={...x};delete c.if;return c});if(JSON.stringify(old)!==JSON.stringify(neu))throw new Error("Bau-Schritte inhaltlich veraendert");if(!(iV===iU-1&&s[iV].uses==="actions/cache/save@v4"&&s[iV].with.path==="desktop-dist"&&s[iV].with.key===key&&String(s[iV].if).includes("steps.stamp.outputs.skip_allowed")))throw new Error("Stempel-Save");if(!(s[iU].if===undefined&&JSON.stringify(s[iU])===JSON.stringify(p.steps[14])))throw new Error("Uebergabe an publish veraendert");if(s.length!==iU+1)throw new Error("Schritte nach Uebergabe");console.log("CI-OK:",s.length,"Schritte im Job desktop")' && command grep -q "steps.reuse.outputs.reuse != 'true'" .gitea/workflows/ci.yml && command grep -q 'quick-260917-jdh' .gitea/workflows/ci.yml</automated>
</verify>
<done>Skript liefert lokal Stempel `1.2.0-<SHA>` mit `skip_allowed` je Ref und schreibt nach `GITHUB_OUTPUT`; `check` akzeptiert nur ein vollstaendiges, pruefsummen-korrektes Beta-`desktop-dist` und raeumt sonst auf; Job `desktop` hat die Kette stamp → restore → check → 13 bedingte Bau-Schritte → Stempel-Save → unveraenderte Uebergabe; `quality`/`test`/`publish` byteidentisch; Commit `ci: Job desktop ueberspringt den Bau, wenn der Desktop-Stand unveraendert ist — Pakete aus dem Zwischenspeicher`.</done>
</task>
<task type="auto">
<name>Task 2: Doku (Betriebshandbuch Kap. 10, CI-Runbook Abschnitt 4/6, Entwicklungsanleitung) und CHANGELOG</name>
<files>docs/anleitung-betrieb.md, docs/ci-cd-setup.md, docs/anleitung-entwicklung.md, CHANGELOG.md</files>
<read_first>
- docs/anleitung-betrieb.md Z. 552-623 (Kap. 10: Unterabschnitte, Satz zur Job-Dauer Z. 569-573, Fehlerbilder-Tabelle Z. 616-623; echte Umlaute, Sie-Form, typografische Anfuehrungszeichen)
- docs/ci-cd-setup.md Z. 108-176 (Abschnitt 4, Job `desktop` als nummerierte Liste; Schreibweise ae/oe/ue OHNE Umlaute) und Z. 295-340 (Abschnitt 6, Stil der Fehlerbehebungs-Eintraege)
- docs/anleitung-entwicklung.md Z. 128-171 („Desktop-App lokal bauen”, letzter Absatz „Der Windows-Installer wird nur im CI gebaut”; echte Umlaute)
- CHANGELOG.md Z. 1-12 — FRISCH lesen: `## Unveröffentlicht` ist derzeit leer; parallele Auftraege (260917-jdd, 260917-jdf) koennen dort inzwischen Unterabschnitte angelegt haben. Reihenfolge der Unterabschnitte wie im Block 1.2.0: Neu, Geändert, Entfernt, Behoben.
</read_first>
<action>
1. **docs/anleitung-betrieb.md, Kap. 10.** (a) Den Satz zur Job-Dauer (Z. 572-573, „Der Job dauert damit etwa fünf bis sieben Minuten …”) um einen Halbsatz ergaenzen: „– sofern überhaupt gebaut wird, siehe nächster Abschnitt.” (b) Zwischen „Woher die Pakete kommen” und „Wo die Pakete im Abbild liegen” einen neuen Unterabschnitt `### Wann gebaut wird und wann Pakete übernommen werden` einfuegen, Inhalt in Alltagssprache: Seit September 2026 baut die Pipeline die Desktop-Pakete auf dem Beta-Kanal nur noch, wenn sich an der Desktop-App etwas geändert hat. Massgeblich ist ein Stempel aus Versionsnummer des letzten Freigabe-Tags und dem letzten Commit an den Desktop-Pfaden (`apps/desktop/`, die Skripte `desktop-version.sh`, `desktop-collect.sh`, `desktop-stamp.sh`, die Workflow-Datei `ci.yml`). Liegen zu diesem Stempel fertige Pakete im Zwischenspeicher des Runners, übernimmt der Job sie unverändert; die Bau-Schritte entfallen, der Job braucht dann unter einer Minute. Im Protokoll steht dann „Desktop unveraendert seit <Commit>: Pakete … aus dem Zwischenspeicher”. Drei Regeln als Aufzaehlung: Freigabe-Tags bauen immer (Release-Dateien frisch mit reiner Versionsnummer); nach einer Freigabe wird einmal neu gebaut, auch ohne Änderung, weil die Versionsnummer zum Stempel gehört (Beta-Pakete tragen danach die neue Basisversion); übernommene Pakete tragen den Stand ihres Baus — Dateiname `-beta.<Commit>` und Manifest nennen den Commit des Baus, nicht den des aktuellen Abbilds; das ist gewollt: ein Client dieses Standes bekommt keinen unnötigen Hinweis auf einen neuen Beta-Stand, ein älterer Client weiterhin. Abschliessender Absatz: fehlt der Eintrag im Zwischenspeicher (der Runner räumt Einträge nach einigen Tagen ohne Nutzung bzw. nach etwa einem Monat weg) oder ist er unvollständig, wird ganz normal gebaut — die Pipeline prüft vor der Übernahme Manifest, Kanal, Version, Dateinamen, Größen und Prüfsummen. (c) Fehlerbilder-Tabelle: neue Zeile — Symptom „Beta-Paket nennt einen älteren Commit als das laufende Abbild (Dateiname `-beta.<Commit>`, Einstellungen → Desktop-App)”, Ursache „Erwartet: Desktop-App seit diesem Commit unverändert, Pakete aus dem Zwischenspeicher übernommen (Abschnitt „Wann gebaut wird …”)”, Beheben „Kein Fehler. Soll dennoch neu gebaut werden, genügt eine Änderung unter `apps/desktop/` im nächsten Push.”
2. **docs/ci-cd-setup.md.** (a) Abschnitt 4, direkt nach der Einleitung von „Job `desktop`: Windows- und Linux-Pakete auf dem Linux-Runner” (vor der nummerierten Liste) einen Absatz **Ueberspringen bei unveraendertem Desktop (quick-260917-jdh)** — Schreibweise ae/oe/ue wie die Datei: Direkt nach dem Checkout berechnet `desktop-stamp.sh stamp` den Stempel `<Version>-<SHA>` (Version aus `desktop-version.sh --print`; SHA = letzter Commit an `apps/desktop`, `desktop-version.sh`, `desktop-collect.sh`, `desktop-stamp.sh`, `ci.yml`; `pnpm-lock.yaml` bewusst nicht, weil die Tauri-CLI-Version an `apps/desktop/package.json` haengt und der Bau keine Datei ausserhalb von `apps/desktop` liest) und gibt `skip_allowed=true` nur fuer `refs/heads/main` aus. Dann `actions/cache/restore@v4` mit Schluessel `desktop-dist-stamp-<Stempel>` — ohne `restore-keys`, weil act_runner auch den Hauptschluessel per Praefix sucht und ein aelterer Stand nie als Treffer gelten darf. Danach `desktop-stamp.sh check`: `cache-hit`, Manifest, Kanal `beta`, Version, beide Dateien mit Groesse und sha256 laut Manifest → `reuse=true`; sonst raeumt es `desktop-dist/` und gibt `reuse=false`. Alle Bau-Schritte (setup-node bis Pakete einsammeln) tragen `if: steps.reuse.outputs.reuse != 'true'`. Nach einem echten Bau legt `actions/cache/save@v4` die Pakete zusaetzlich unter dem Stempel-Schluessel ab (nur main). Die Uebergabe an `publish` (`desktop-dist-<sha>`) laeuft in beiden Faellen; `publish` ist unveraendert. Bei Tags `v*` wird weder gesucht noch abgelegt. Lokale Probe: `DESKTOP_TAG=v1.2.0 GITHUB_REF=refs/heads/main sh .gitea/scripts/desktop-stamp.sh stamp`. (b) In der nummerierten Liste Punkt 7 („Uebergabe an `publish`”) um den Satz ergaenzen, dass er auch im Skip-Fall laeuft und dann den restaurierten Stand unter dem neuen SHA sichert. (c) Abschnitt 6, nach „Job `desktop` schlaegt fehl”, neuer Eintrag `### \`desktop\` baut, obwohl nichts geaendert wurde -- oder uebernimmt trotz Aenderung` mit zwei nummerierten Listen: Baut trotzdem — erster Lauf nach der Aenderung (Stempel neu), neuer Freigabe-Tag (Version im Stempel), Eintrag vom Runner-Cache weggeraeumt (act_runner raeumt ungenutzte Eintraege nach einigen Tagen, alte nach etwa einem Monat weg), `ci.yml`/Skripte geaendert, `check` hat den Eintrag verworfen (Grund steht im Schritt „Gefundene Pakete pruefen”). Uebernimmt trotz Aenderung — die Aenderung liegt ausserhalb der Pfadliste (z. B. nur `pnpm-lock.yaml`): entweder unter `apps/desktop/` etwas aendern oder `DESKTOP_PATHS` in `desktop-stamp.sh` erweitern (das loest selbst einen Neubau aus).
3. **docs/anleitung-entwicklung.md**, nach dem Absatz „Der Windows-Installer wird nur im CI gebaut” (Z. 162-171) einen Absatz anfuegen, beginnend mit **Im CI wird die Desktop-App nur gebaut, wenn sich etwas an ihr geändert hat.** Der Job `desktop` vergleicht einen Stempel aus Versionsnummer und letztem Commit an `apps/desktop/`, `desktop-version.sh`, `desktop-collect.sh`, `desktop-stamp.sh` und `ci.yml` mit dem Zwischenspeicher des Runners und übernimmt bei Treffer die zuletzt gebauten Pakete (Details: `docs/ci-cd-setup.md`, Abschnitt 4). Eine Änderung außerhalb dieser Pfade – etwa nur in `pnpm-lock.yaml` – löst keinen Desktop-Bau aus; soll trotzdem neu gebaut werden, genügt eine Änderung unter `apps/desktop/`. Stempel lokal ansehen: `DESKTOP_TAG=v1.2.0 GITHUB_REF=refs/heads/main sh .gitea/scripts/desktop-stamp.sh stamp`.
4. **CHANGELOG.md**, `## Unveröffentlicht`: Unterabschnitt `### Geändert` verwenden — existiert er (durch parallele Auftraege) bereits, die Zeile am Ende seiner Liste anfuegen; sonst ihn an der richtigen Stelle (nach `### Neu`, vor `### Entfernt`/`### Behoben`, falls vorhanden) anlegen. Genau eine Zeile: `- Desktop-App: Beta-Pakete werden nur noch neu gebaut, wenn sich an der Desktop-App etwas geändert hat; sonst bleiben die zuletzt gebauten Pakete gültig, und die App meldet keinen neuen Beta-Stand`. Keine bestehende Zeile aendern oder verschieben.
5. Alle Aenderungen sind reine Einfuegungen; Umlaut-Konvention je Datei beachten (Betrieb/Entwicklung/CHANGELOG echte Umlaute, ci-cd-setup.md ae/oe/ue). Commit `docs: CI-Desktop-Bau nur bei geaendertem Desktop-Stand — Betriebshandbuch, CI-Runbook, Entwicklungsanleitung, CHANGELOG`.
</action>
<verify>
<automated>cd /home/vicolab/projects/tessera-ctl && command grep -q '^### Wann gebaut wird und wann Pakete übernommen werden' docs/anleitung-betrieb.md && command grep -q 'desktop-stamp.sh' docs/anleitung-betrieb.md && command grep -q 'sofern überhaupt gebaut wird' docs/anleitung-betrieb.md && test "$(command grep -c 'beta\.<Commit>' docs/anleitung-betrieb.md)" -ge 2 && command grep -q 'Ueberspringen bei unveraendertem Desktop' docs/ci-cd-setup.md && command grep -q 'desktop-dist-stamp-' docs/ci-cd-setup.md && command grep -q 'pnpm-lock.yaml' docs/ci-cd-setup.md && command grep -q '^### `desktop` baut, obwohl nichts geaendert wurde' docs/ci-cd-setup.md && command grep -q 'DESKTOP_PATHS' docs/ci-cd-setup.md && command grep -q 'Im CI wird die Desktop-App nur gebaut, wenn sich etwas an ihr geändert hat' docs/anleitung-entwicklung.md && command grep -q 'desktop-stamp.sh stamp' docs/anleitung-entwicklung.md && awk '/^## Unveröffentlicht/{u=1;next} /^## /{u=0} u' CHANGELOG.md | command grep -q '^- Desktop-App: Beta-Pakete werden nur noch neu gebaut' && ! awk '/^## 1\.2\.0/{u=1} u' CHANGELOG.md | command grep -q '^- Desktop-App: Beta-Pakete werden nur noch neu gebaut' && awk '/^## Unveröffentlicht/{u=1;next} /^## /{u=0} u' CHANGELOG.md | command grep -q '^### Geändert' && ! command grep -q -E '[äöüÄÖÜß]' docs/ci-cd-setup.md && echo DOCS-OK</automated>
</verify>
<done>Betriebshandbuch Kap. 10 erklaert Stempel, Tag-Regel, Neubau nach Freigabe, aelteren Commit-Stempel als gewollt und den Cache-Ablauf; CI-Runbook beschreibt Schrittfolge, Schluessel, Pfadliste samt `pnpm-lock.yaml`-Begruendung und zwei Fehlerbilder; Entwicklungsanleitung nennt Pfadliste, Erzwingen eines Neubaus und die lokale Probe; CHANGELOG traegt genau einen neuen Stichpunkt unter Unveröffentlicht/Geändert; Commit `docs: …`. Der CI-Nachweis (Skip ohne Desktop-Aenderung, Bau mit Desktop-Aenderung) bleibt Sache des Orchestrators nach dem Push — im SUMMARY als offen fuehren.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| Git-Push → Workflow-Ausloesung | Nur Pushes auf `main`/`live` und Tags `v*` starten den Lauf; Schreibrecht hat allein das Konto `schalli` (ci-cd-setup.md Abschnitt 5) |
| Job `desktop` → Cache-Server des act_runner | Stempel-Schluessel und Cache-Inhalte werden von eigenen Laeufen geschrieben und gelesen; kein externer Zugang |
| Cache-Inhalt → API-Abbild / Release | Uebernommene Pakete landen ungeprueft durch Menschen im API-Abbild unter `/app/desktop-dist/` |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-JDH-01 | Tampering | Cache-Schluessel `desktop-dist-stamp-<Version>-<SHA>` | low | mitigate | Schluessel entsteht ausschliesslich aus dem ausgecheckten Commit (`git log`, `git describe`), nicht aus Ereignis-Eingaben; nur `refs/heads/main` liest/schreibt ihn (`skip_allowed`); Restore ohne `restore-keys`, voller SHA am Schluesselende (act_runner-Praefixsuche); `cache-hit` muss exakt `true` sein. |
| T-JDH-02 | Tampering | Restaurierter `desktop-dist/` (unvollstaendiger/beschaedigter Eintrag) | low | mitigate | `desktop-stamp.sh check` prueft Manifest (jq), Kanal `beta`, Version, Existenz, Groesse und sha256 jeder Datei; bei Abweichung Aufraeumen + normaler Bau (fail-safe), nie Job-Abbruch durch das Gate selbst. |
| T-JDH-03 | Spoofing | Beta-Pakete unter einem Stempel aus einem Tag-Lauf (reine Version, Kanal live) | low | mitigate | Stempel-Save nur bei `skip_allowed == 'true'` (main); Tags suchen weder noch legen sie ab; `check` verwirft `channel != beta`. |
| T-JDH-04 | Information Disclosure | Schluessel, `GITHUB_OUTPUT`, Log-Zeilen | low | accept | Enthalten nur Version, Commit-SHA, Dateinamen, Bauzeit — alles bereits oeffentlich im Repository bzw. Manifest; das Skript kennt kein Secret, `REGISTRY_TOKEN` bleibt allein in `publish`. |
| T-JDH-05 | Denial of Service | Cache-Server nicht erreichbar / Eintrag weggeraeumt | low | accept | `actions/cache/restore` ohne `fail-on-cache-miss` protokolliert nur; `reuse=false` → Bau wie bisher. Kosten: eine Bauzeit, kein Ausfall. |
| T-JDH-06 | Repudiation | Veraltete Pakete trotz Desktop-Aenderung (Pfadliste unvollstaendig) | low | mitigate | Pfadliste deckt alle Bau-Eingaben (`apps/desktop` inkl. `frontendDist ../src`, Cargo.lock, package.json; Skripte; ci.yml; das Stempel-Skript selbst); Ausnahme `pnpm-lock.yaml` begruendet und dokumentiert; Manifest nennt Bau-Commit und Bauzeit nachvollziehbar. |
| T-JDH-SC | Tampering | npm/pip/cargo installs | low | accept | Keine neue Abhaengigkeit (kein `pnpm add`, kein `cargo add`, keine neue Action ausser den bereits genutzten `actions/cache/restore@v4`/`save@v4`); package-legitimacy gate entfaellt. |
</threat_model>
<verification>
- Skript: `sh -n` gruen; `stamp` liefert `1.2.0-<SHA>` (identisch mit `git log -1 --format=%H -- <Pfadliste>`), `skip_allowed=true` auf main, `false` bei Tag; `GITHUB_OUTPUT` erhaelt vier Schluessel; `check` akzeptiert nur ein vollstaendiges, pruefsummen-korrektes Beta-Manifest und raeumt sonst auf.
- Workflow: js-yaml parst; Jobs `quality`/`test`/`publish` und `on:` identisch zu 38c1400; Job `desktop`: Schritte 1-3 = stamp/stamp-cache/reuse, 13 Bau-Schritte mit `if: steps.reuse.outputs.reuse != 'true'` und sonst unveraendertem Inhalt, Stempel-Save vor unveraenderter `Uebergabe an publish`; `command grep -c` der Bedingung = 14.
- Doku: vier Stellen vorhanden (Greps in Task 2), CHANGELOG genau ein neuer Stichpunkt unter Unveröffentlicht/Geändert.
- Zwei Commits (`ci:`, `docs:`), kein Push, keine `.planning/`-Dateien darin.
- **Nachweis durch den Orchestrator nach dem Push (nicht Teil der Executor-Tasks):** (1) Der Push dieser Aenderung selbst baut (ci.yml/Skript sind in der Pfadliste) und legt `desktop-dist-stamp-1.2.0-<SHA>` ab. (2) Ein folgender Push ohne Desktop-Aenderung: Schritt „Gefundene Pakete pruefen” meldet „Desktop unveraendert seit <sha7>: Pakete … aus dem Zwischenspeicher”, die 13 Bau-Schritte sind uebersprungen, Job `desktop` unter einer Minute, `publish` gruen, `GET /api-proxy/desktop/latest` auf alpha nennt den aelteren Beta-Suffix. (3) Ein Push mit Desktop-Aenderung baut vollstaendig und legt einen neuen Stempel ab. Ergebnis in den SUMMARY-Nachtrag.
</verification>
<success_criteria>
- Alle `must_haves.truths` erfuellt; beide `<verify>`-Ketten gruen.
- Keine Datei ausserhalb von `files_modified` + `.planning/` veraendert (`git status` vor jedem Commit gegenpruefen); `publish`, `desktop-version.sh`, `desktop-collect.sh`, `publish-*.sh` unberuehrt.
- SUMMARY nennt den offenen CI-Nachweis (Skip/Bau) ausdruecklich als Aufgabe des Orchestrators.
</success_criteria>
<output>
Create `/home/vicolab/projects/tessera-ctl/.planning/quick/260917-jdh-ci-job-desktop-ueberspringen-wenn-apps-d/260917-jdh-SUMMARY.md` when done
</output>