291 lines
18 KiB
Markdown
291 lines
18 KiB
Markdown
---
|
|
phase: 18-desktop-client-fertigstellen
|
|
plan: 02
|
|
type: execute
|
|
wave: 2
|
|
depends_on: ["18-01"]
|
|
files_modified:
|
|
- .gitea/workflows/ci.yml
|
|
- .gitea/scripts/publish-images.sh
|
|
- .gitea/scripts/publish-release.sh
|
|
autonomous: true
|
|
requirements: [DESK-01, DESK-04]
|
|
user_setup: []
|
|
|
|
estimate:
|
|
tokens: 45000
|
|
raw_tokens: 45000
|
|
tasks: 2
|
|
confidence: low
|
|
|
|
must_haves:
|
|
truths:
|
|
- "Der CI-Job desktop laeuft nach test auf main und bei Tags v*, baut das Linux-AppImage mit der Tag-Version und uebergibt desktop-dist/ per actions/cache an publish (D-06, D-07)."
|
|
- "publish bricht hart ab, wenn das Manifest aus dem Zwischenspeicher fehlt — nie ein Abbild ohne Pakete (D-08, Pitfall 1)."
|
|
- "publish-release.sh haengt bei Tags jede Datei aus dem Manifest idempotent als Release-Datei an den Gitea-Release; das Token verlaesst nie die Header-Datei (D-01, D-08)."
|
|
artifacts:
|
|
- path: ".gitea/workflows/ci.yml"
|
|
provides: "Job desktop (Linux-AppImage) und Uebergabe an publish per actions/cache"
|
|
contains: "desktop-dist-${{ gitea.sha }}"
|
|
- path: ".gitea/scripts/publish-images.sh"
|
|
provides: "Harte Pruefung auf desktop-dist/manifest.json vor dem Docker-Bau"
|
|
contains: "manifest.json"
|
|
- path: ".gitea/scripts/publish-release.sh"
|
|
provides: "Idempotenter Upload der Release-Dateien (GET assets, DELETE, POST multipart)"
|
|
contains: "upload_asset"
|
|
key_links:
|
|
- from: ".gitea/workflows/ci.yml (desktop)"
|
|
to: ".gitea/workflows/ci.yml (publish)"
|
|
via: "actions/cache/save + actions/cache/restore mit Schluessel desktop-dist-${{ gitea.sha }}, fail-on-cache-miss: true"
|
|
pattern: "fail-on-cache-miss"
|
|
- from: ".gitea/workflows/ci.yml (desktop)"
|
|
to: ".gitea/scripts/desktop-version.sh + desktop-collect.sh"
|
|
via: "Schritte 'Version setzen' und 'Pakete einsammeln'"
|
|
pattern: "desktop-collect.sh --require linux"
|
|
- from: ".gitea/scripts/publish-release.sh"
|
|
to: "desktop-dist/manifest.json"
|
|
via: "jq -r '.files[].name' — nur Dateien aus dem Manifest werden hochgeladen"
|
|
pattern: "files\\[\\]"
|
|
---
|
|
|
|
<objective>
|
|
Die in 18-01 lokal bewiesene Strecke wird in die Pipeline gehoben: ein neuer
|
|
Job `desktop` baut auf `main` und bei Tags `v*` das Linux-AppImage mit der
|
|
Tag-Version, sammelt es mit Manifest ein und uebergibt `desktop-dist/` per
|
|
`actions/cache` an `publish`, das ohne Manifest hart abbricht und die Pakete
|
|
ins API-Abbild kopiert. Bei Tags haengt `publish-release.sh` jede Datei aus
|
|
dem Manifest an den Gitea-Release. Der Windows-Cross-Bau kommt in 18-05 in
|
|
denselben Job; der echte Pipeline-Lauf wird dort mit beiden Dateien bewiesen.
|
|
|
|
Purpose: D-06, D-08 (Pipeline-Seite) und D-01 (Release-Dateien) aus
|
|
18-CONTEXT.md; Erfolgskriterium 1 (Linux-Haelfte und Release-Anhang).
|
|
Output: Job `desktop`, angepasster Job `publish`, Manifest-Pruefung in
|
|
`publish-images.sh`, Funktion `upload_asset` in `publish-release.sh`.
|
|
|
|
**Externe Schnittstellen (Gitea REST):** siehe `18-COVERAGE.md` — neu sind
|
|
`GET …/releases/{id}/assets`, `DELETE …/releases/{id}/assets/{asset_id}` und
|
|
`POST …/releases/{id}/assets?name=` (multipart-Feld `attachment`); Gitea
|
|
1.26.2 laesst Release-Anhaenge standardmaessig ohne Typ-Beschraenkung bis
|
|
2048 MB zu.
|
|
</objective>
|
|
|
|
## Artifacts this phase produces
|
|
|
|
Dieser Plan: `.gitea/workflows/ci.yml` (Job `desktop`: Schritte
|
|
"Systemabhaengigkeiten", "Rust-Toolchain", "Cargo-Zwischenspeicher", "Version
|
|
setzen", "Rust pruefen", "Alte Bundles entfernen", "Linux-AppImage bauen",
|
|
"Pakete einsammeln", "Uebergabe an publish"; Job `publish`: "Desktop-Pakete
|
|
aus dem Zwischenspeicher holen", "Pakete pruefen"),
|
|
`.gitea/scripts/publish-images.sh` (Manifest-Pruefung),
|
|
`.gitea/scripts/publish-release.sh` (`HDR_AUTH`, `upload_asset`,
|
|
`DESKTOP_DIST`). Gesamtliste der Phase: siehe 18-01-PLAN.md.
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
|
@$HOME/.claude/gsd-core/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/18-desktop-client-fertigstellen/18-CONTEXT.md
|
|
@.planning/phases/18-desktop-client-fertigstellen/18-RESEARCH.md
|
|
@.planning/phases/18-desktop-client-fertigstellen/18-COVERAGE.md
|
|
@.planning/phases/18-desktop-client-fertigstellen/18-01-SUMMARY.md
|
|
|
|
@.gitea/workflows/ci.yml
|
|
@.gitea/scripts/publish-images.sh
|
|
@.gitea/scripts/publish-release.sh
|
|
@.gitea/scripts/desktop-collect.sh
|
|
@.gitea/scripts/desktop-version.sh
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Job desktop (Linux-AppImage) und Uebergabe an publish per actions/cache</name>
|
|
<reversibility rating="reversible">Job-Aufbau und Cache-Schluessel lassen sich jederzeit aendern; kein Zustand ausserhalb des Runners.</reversibility>
|
|
<files>
|
|
.gitea/workflows/ci.yml,
|
|
.gitea/scripts/publish-images.sh
|
|
</files>
|
|
<read_first>
|
|
.gitea/workflows/ci.yml,
|
|
.gitea/scripts/publish-images.sh,
|
|
.gitea/scripts/desktop-collect.sh (Optionen und Ausgabe, aus 18-01),
|
|
.planning/phases/18-desktop-client-fertigstellen/18-RESEARCH.md (Abschnitte "Code Examples 2", "Common Pitfalls 1 und 5", "Standard Stack: Installation"),
|
|
docs/ci-cd-setup.md (Abschnitt 4 "Pipeline-Ueberblick")
|
|
</read_first>
|
|
<action>
|
|
**Job `desktop` in `.gitea/workflows/ci.yml`** zwischen `test` und `publish`
|
|
einfuegen, Kopfkommentar der Datei um einen Satz zu Phase 18 ergaenzen.
|
|
`name: Desktop-Pakete bauen`, `runs-on: ubuntu-latest`, `needs: test`,
|
|
`if: gitea.ref == 'refs/heads/main' || startsWith(gitea.ref, 'refs/tags/v')`
|
|
(D-06). Schritte in dieser Reihenfolge, deutsche Schrittnamen wie im Rest der
|
|
Datei: `actions/checkout@v4` mit `fetch-depth: 0` (Tags fuer `git describe`);
|
|
`actions/setup-node@v4` (Node 24); corepack/pnpm wie in `test`;
|
|
`pnpm install --frozen-lockfile`; "Systemabhaengigkeiten":
|
|
`sudo apt-get update` und `sudo apt-get install -y --no-install-recommends`
|
|
mit **vollstaendiger** Liste `libwebkit2gtk-4.1-dev libjavascriptcoregtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev libgtk-3-dev libssl-dev patchelf file xdg-utils`
|
|
(Pitfall 5 — das Runner-Abbild hat davon nur `librsvg2-dev` und `file`;
|
|
alle Paketnamen wurden am 2026-09-16 per `apt-cache policy` im Abbild
|
|
`gitea/runner-images:ubuntu-latest` bestaetigt, ebenso `sudo`, `jq`, `curl`
|
|
und `git`); "Rust-Toolchain": `curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable`
|
|
und danach `echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"` (kein Rust im
|
|
Runner-Abbild; bewusst kein Fremd-Action, gleiche Zurueckhaltung wie beim
|
|
Verzicht auf die Artefakt-Aktionen); "Cargo-Zwischenspeicher": `actions/cache@v4`
|
|
mit `path` `~/.cargo/registry`, `~/.cargo/git`, `~/.cache/tauri`,
|
|
`apps/desktop/src-tauri/target`, `key: desktop-cargo-${{ hashFiles('apps/desktop/src-tauri/Cargo.lock') }}`,
|
|
`restore-keys: desktop-cargo-` (der Cache-Server des Runners ist laut
|
|
RESEARCH aktiv: `172.18.0.1:42641`); "Version setzen":
|
|
`sh .gitea/scripts/desktop-version.sh`; "Rust pruefen":
|
|
`cargo check` und `cargo clippy` mit `working-directory: apps/desktop/src-tauri`
|
|
(D-16; Clippy ohne `-D warnings`, Fehler brechen ab, Warnungen nicht);
|
|
"Alte Bundles entfernen": `rm -rf apps/desktop/src-tauri/target/release/bundle`
|
|
(ein aus dem Cache wiederhergestelltes altes AppImage wuerde sonst neben dem
|
|
neuen liegen und das Sammel-Skript zu Recht abbrechen); "Linux-AppImage
|
|
bauen": `pnpm --filter @tessera/desktop exec tauri build --bundles appimage`;
|
|
"Pakete einsammeln": `sh .gitea/scripts/desktop-collect.sh --require linux`
|
|
(18-05 erweitert auf `linux,windows`); "Uebergabe an publish":
|
|
`actions/cache/save@v4` mit `path: desktop-dist` und
|
|
`key: desktop-dist-${{ gitea.sha }}` (Pitfall 1: bewusst **nicht** die
|
|
Artefakt-Aktionen von GitHub — auf dieser Gitea-Instanz dokumentiert
|
|
unzuverlaessig; im Workflow-Kommentar ebenfalls nur so umschreiben, damit
|
|
das Negativ-Tor in `<verify>` nicht am Kommentartext scheitert).
|
|
|
|
**Job `publish` anpassen:** `needs: desktop` statt `needs: test`. Nach dem
|
|
Checkout und vor dem Registry-Login zwei Schritte: "Desktop-Pakete aus dem
|
|
Zwischenspeicher holen" mit `actions/cache/restore@v4`, `path: desktop-dist`,
|
|
`key: desktop-dist-${{ gitea.sha }}`, `fail-on-cache-miss: true`; "Pakete
|
|
pruefen": `test -f desktop-dist/manifest.json` und `jq . desktop-dist/manifest.json`
|
|
(harter Abbruch, nie stillschweigend ein Abbild ohne Pakete). Der Schritt mit
|
|
`publish-release.sh` bleibt; die Pakete liegen fuer ihn unter `desktop-dist/`.
|
|
|
|
**`publish-images.sh`:** Im echten Bau-Pfad (nicht bei `--print-plan`) vor
|
|
der Schleife pruefen, dass `desktop-dist/manifest.json` existiert, sonst
|
|
Exit 1 mit Meldung — zweites Netz gegen Pitfall 1. Kopfkommentar um einen
|
|
Absatz ergaenzen (Phase 18: die Pakete kommen aus dem Job `desktop`, das
|
|
Dockerfile der API kopiert `desktop-dist/`). Weiterhin kein Secret.
|
|
</action>
|
|
<acceptance_criteria>
|
|
- `grep -c '^ desktop:$' .gitea/workflows/ci.yml` ergibt 1; `grep -c 'needs: desktop' .gitea/workflows/ci.yml` ergibt 1; `grep -c 'fail-on-cache-miss: true' .gitea/workflows/ci.yml` ergibt 1; `grep -c 'desktop-dist-${{ gitea.sha }}' .gitea/workflows/ci.yml` ergibt 2 (save und restore).
|
|
- `grep -c 'upload-artifact' .gitea/workflows/ci.yml` ergibt 0.
|
|
- `grep -c 'libwebkit2gtk-4.1-dev' .gitea/workflows/ci.yml` ergibt mindestens 1; `grep -c 'desktop-collect.sh --require linux' .gitea/workflows/ci.yml` ergibt 1; `grep -c 'desktop-version.sh' .gitea/workflows/ci.yml` ergibt 1.
|
|
- `sh -n .gitea/scripts/publish-images.sh` endet mit 0; `GITHUB_REF=refs/tags/v1.1.0 sh .gitea/scripts/publish-images.sh --print-plan` gibt weiterhin die vier `push`-Zeilen aus (Probelauf braucht kein Manifest).
|
|
- `grep -c 'manifest.json' .gitea/scripts/publish-images.sh` ergibt mindestens 1.
|
|
</acceptance_criteria>
|
|
<verify>
|
|
<automated>cd /home/vicolab/projects/tessera-ctl && test "$(grep -c 'desktop-dist-${{ gitea.sha }}' .gitea/workflows/ci.yml)" = "2" && grep -q 'fail-on-cache-miss: true' .gitea/workflows/ci.yml && grep -q 'needs: desktop' .gitea/workflows/ci.yml && test "$(grep -c 'upload-artifact' .gitea/workflows/ci.yml)" = "0" && grep -q 'desktop-collect.sh --require linux' .gitea/workflows/ci.yml && grep -q 'desktop-version.sh' .gitea/workflows/ci.yml && node -e "const y=require('fs').readFileSync('.gitea/workflows/ci.yml','utf8');if(!/^ desktop:\n/m.test(y)||!/^ publish:\n/m.test(y))process.exit(1)" && echo CI-OK</automated>
|
|
<fails_when>Cache-Schluessel nicht genau zweimal, Restore ohne harten Abbruch, publish haengt nicht an desktop, ein upload-artifact-Schritt ist vorhanden, Skript-Schritte fehlen, oder die Job-Schluessel fehlen — `CI-OK` fehlt.</fails_when>
|
|
<automated>cd /home/vicolab/projects/tessera-ctl && sh -n .gitea/scripts/publish-images.sh && GITHUB_REF=refs/tags/v1.1.0 sh .gitea/scripts/publish-images.sh --print-plan | grep -c '^push ' | grep -qx 4 && grep -q 'manifest.json' .gitea/scripts/publish-images.sh && echo IMAGES-OK</automated>
|
|
<fails_when>Syntaxfehler, weniger als vier push-Zeilen im Probelauf, oder die Manifest-Pruefung fehlt im Skript — `IMAGES-OK` fehlt.</fails_when>
|
|
</verify>
|
|
<done>
|
|
Der Workflow enthaelt den Job `desktop` (Linux-AppImage mit Tag-Version,
|
|
Cache, Uebergabe per `actions/cache`), `publish` haengt daran und bricht
|
|
ohne Manifest ab; `publish-images.sh` prueft das Manifest ebenfalls.
|
|
</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Release-Dateien idempotent an den Gitea-Release haengen</name>
|
|
<precondition>Das Gitea-Secret `REGISTRY_TOKEN` traegt `repository: write` (damit wurde am 2026-09-16 der Release v1.1.0 aus der Pipeline angelegt); es wird unveraendert weiterverwendet. Lokal liegt `desktop-dist/manifest.json` aus 18-01 vor (fuer den Probelauf).</precondition>
|
|
<files>
|
|
.gitea/scripts/publish-release.sh
|
|
</files>
|
|
<read_first>
|
|
.gitea/scripts/publish-release.sh (gesamt — Idempotenz-Muster GET -> case -> PATCH/POST, Header-Datei-Mechanik ab Zeile 117),
|
|
.planning/phases/18-desktop-client-fertigstellen/18-RESEARCH.md (Abschnitte "Code Examples 6", "Don't Hand-Roll", "Security Domain"),
|
|
.planning/phases/18-desktop-client-fertigstellen/18-COVERAGE.md,
|
|
desktop-dist/manifest.json (Form der `files`-Eintraege)
|
|
</read_first>
|
|
<action>
|
|
Kopfkommentar um Umgebung `DESKTOP_DIST` (Vorgabe `desktop-dist`) und die
|
|
drei neuen Endpunkte ergaenzen. Neben `$HDR` (mit JSON-Content-Type) eine
|
|
zweite Header-Datei `$HDR_AUTH` anlegen, die **nur** die
|
|
`Authorization`-Zeile traegt — beim multipart-Upload darf kein
|
|
`Content-Type: application/json` mitgehen; gleiche `umask 077`/`mktemp`/
|
|
`trap`-Mechanik, Token nie als Argument (T-18-03). Funktion
|
|
`upload_asset FILE NAME RELEASE_ID` nach dem Muster GET -> Entscheidung per
|
|
HTTP-Code -> Aktion: `GET $RELEASES_URL/$ID/assets` (200 erwartet), per
|
|
`jq -r --arg n "$NAME" '.[] | select(.name == $n) | .id'` vorhandene Datei
|
|
gleichen Namens ermitteln und mit `DELETE $RELEASES_URL/$ID/assets/$ASSET_ID`
|
|
entfernen (204 erwartet), dann
|
|
`curl -sS --header @"$HDR_AUTH" -X POST -F "attachment=@$FILE;filename=$NAME" -o "$RESP" -w '%{http_code}' "$RELEASES_URL/$ID/assets?name=$NAME"`
|
|
(201 erwartet; jeder andere Code: Meldung mit Code und Antwort nach stderr,
|
|
Exit 1). Aufruf nach dem bestehenden `case`-Block (Release angelegt oder
|
|
aktualisiert; `ID` aus beiden Zweigen verfuegbar machen): Manifest
|
|
`$DESKTOP_DIST/manifest.json` muss existieren, sonst Exit 1 (Release-Text ist
|
|
dann schon da, der Job wird sichtbar rot); fuer jeden Namen aus
|
|
`jq -r '.files[].name'` `upload_asset "$DESKTOP_DIST/$NAME" "$NAME" "$ID"`,
|
|
danach je Datei `Release-Datei $NAME hochgeladen`. `--dry-run` listet
|
|
zusaetzlich die geplanten Uploads (`POST $RELEASES_URL/{id}/assets?name=…`)
|
|
aus dem Manifest, falls es vorhanden ist.
|
|
|
|
Bekannter Fallstrick fuer 18-05: Der Job-Container erreicht Gitea ueber
|
|
`https://git.vicolab.de` hinter dem Nginx Proxy Manager; das AppImage ist
|
|
rund 106 MB — falls der Proxy den Upload abweist (413), kann `GITEA_API` im
|
|
Workflow-Schritt auf die Host-Adresse `http://172.18.0.1:3002/api/v1` gesetzt
|
|
werden (gleiche Route, ueber die der Runner seinen Cache-Server erreicht).
|
|
Das wird erst im CI-Lauf entschieden, nicht hier. Der Upload-Pfad selbst
|
|
laeuft erst beim naechsten Freigabe-Tag (ein Test-Tag wuerde den Live-Kanal
|
|
ausloesen) — deshalb ist der Probelauf mit `--dry-run` hier das Tor.
|
|
</action>
|
|
<acceptance_criteria>
|
|
- `sh -n .gitea/scripts/publish-release.sh` endet mit 0.
|
|
- `sh .gitea/scripts/publish-release.sh --dry-run --tag v1.1.0` gibt eine Zeile mit `assets?name=Tessera-1.1.0.AppImage` aus (Manifest aus 18-01 vorhanden) und endet mit 0; ohne Token, ohne Netzaufruf.
|
|
- `grep -c 'HDR_AUTH' .gitea/scripts/publish-release.sh` ergibt mindestens 3 (Anlegen, Schreiben, Verwendung); `grep -c '^upload_asset()' .gitea/scripts/publish-release.sh` ergibt 1.
|
|
- `grep -c "files\[\].name" .gitea/scripts/publish-release.sh` ergibt mindestens 1.
|
|
- Das Token wird nirgends als Argument uebergeben: `grep -c 'token %s' .gitea/scripts/publish-release.sh` ergibt genau 1 (die bestehende printf-Zeile in die Header-Datei) oder 2 (zweite Header-Datei), nie in einer `curl`-Zeile.
|
|
</acceptance_criteria>
|
|
<verify>
|
|
<automated>cd /home/vicolab/projects/tessera-ctl && sh -n .gitea/scripts/publish-release.sh && sh .gitea/scripts/publish-release.sh --dry-run --tag v1.1.0 | grep -q 'assets?name=Tessera-1.1.0.AppImage' && test "$(grep -c 'HDR_AUTH' .gitea/scripts/publish-release.sh)" -ge 3 && grep -q '^upload_asset()' .gitea/scripts/publish-release.sh && grep -q 'files\[\].name' .gitea/scripts/publish-release.sh && test "$(grep -c 'curl.*GITEA_TOKEN' .gitea/scripts/publish-release.sh)" = "0" && echo RELEASE-OK</automated>
|
|
<fails_when>Syntaxfehler, der Probelauf nennt den AppImage-Upload nicht, die zweite Header-Datei oder die Funktion fehlt, die Dateinamen kommen nicht aus dem Manifest, oder das Token steht in einer curl-Zeile — `RELEASE-OK` fehlt.</fails_when>
|
|
</verify>
|
|
<done>
|
|
Das Release-Skript laedt alle Manifest-Dateien idempotent hoch (vorhandene
|
|
Datei gleichen Namens wird ersetzt), das Token bleibt in Header-Dateien, der
|
|
Probelauf nennt die geplanten Uploads. Der echte Pipeline-Beweis folgt in
|
|
18-05 (gemeinsam mit Windows), der Release-Anhang beim naechsten Freigabe-Tag.
|
|
</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<threat_model>
|
|
## Trust Boundaries
|
|
|
|
| Boundary | Description |
|
|
|----------|-------------|
|
|
| CI-Runner -> Gitea-API (Release-Dateien) | Ausgehender Aufruf mit dem Zugriffstoken `REGISTRY_TOKEN`. |
|
|
| Runner -> Cache-Server (`actions/cache`) | Uebergabe der Pakete zwischen zwei Jobs desselben Laufs. |
|
|
| Runner -> Internet (rustup, crates.io, Tauri-Werkzeuge) | Der Job laedt Werkzeuge aus dem Netz. |
|
|
|
|
## STRIDE Threat Register
|
|
|
|
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
|
|-----------|----------|-----------|----------|-------------|-----------------|
|
|
| T-18-03 | Information Disclosure | `publish-release.sh` (Token) | high | mitigate | Token nur aus der Umgebung, nie als Argument, nur ueber Header-Dateien mit `umask 077`; keine Ausgabe des Tokens; zweite Header-Datei ohne JSON-Content-Type fuer multipart. Gate: keine `curl`-Zeile enthaelt `GITEA_TOKEN`. |
|
|
| T-18-06 | Tampering | `publish` ohne Pakete (Cache-Fehlschlag) | medium | mitigate | `fail-on-cache-miss: true` plus expliziter `test -f desktop-dist/manifest.json` im Workflow und in `publish-images.sh`. |
|
|
| T-18-21 | Tampering | Cache-Uebergabe zwischen Jobs (`desktop-dist-{sha}`) | low | accept | Cache-Server nur lokal fuer diesen Runner (`172.18.0.1`), Schluessel exakt am Commit-SHA, keine `restore-keys`-Fallbacks fuer die Uebergabe. |
|
|
| T-18-SC | Tampering | Paketinstallationen (`actions/cache@v4`, `actions/checkout@v4`, `actions/setup-node@v4`; Rust-Toolchain per rustup) | low | mitigate | Nur GitHub-eigene Actions in der bereits genutzten Major-Version; rustup-Installer von der offiziellen Adresse; keine neuen npm/pip/cargo-Pakete in diesem Plan. |
|
|
</threat_model>
|
|
|
|
<verification>
|
|
1. `ci.yml` enthaelt Job `desktop`, `publish` mit `needs: desktop`, Cache-Restore mit hartem Abbruch, kein upload-artifact.
|
|
2. `publish-images.sh` und `publish-release.sh` bestehen `sh -n`; Probelaeufe zeigen die erwarteten Zeilen (`push` x4, `assets?name=Tessera-1.1.0.AppImage`).
|
|
3. Kein `curl`-Aufruf traegt das Token als Argument.
|
|
4. Der echte Lauf wird in 18-05 bewiesen; der Release-Anhang beim naechsten Tag (18-06, human-check Punkt b).
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Job `desktop` baut das AppImage mit Tag-Version und uebergibt es per Cache.
|
|
- `publish` kann kein Abbild ohne Pakete mehr bauen.
|
|
- Release-Dateien werden bei Tags idempotent aus dem Manifest hochgeladen.
|
|
</success_criteria>
|
|
|
|
<output>
|
|
Create `.planning/phases/18-desktop-client-fertigstellen/18-02-SUMMARY.md` when done.
|
|
</output>
|