fix(release): refuse an empty or multi-document latest.json, test the Publish step #464

Merged
maximus merged 1 commit from issue-419-latest-json-check-followups into main 2026-10-18 17:37:48 +00:00
Owner

Resolves #419

Follow-ups of the review of PR #418 (comment 22774). Point 1 had to land before the next tag.

Changes

  1. Empty or multi-document latest.json refused. scripts/check-latest-json.jq now wants exactly one document (length == 1 and (.[0] | ...)), and the Publish step of release.yml calls it with -s. Reproduced before the fix with the official jq 1.6 static binary (sha256 af986793...a124c44, the version of ubuntu:22.04): an empty file passed with rc 0, while jq 1.7 refused it with rc 4. Two new tests cover it: an empty file, and two documents whose last one is valid (also two valid documents).
  2. "On every PR" made true (decision of Max): check-frontend.yml no longer ignores .claude/** or .forgejo/workflows/release.yml. The test reads verify-release.sh and now runs the Publish step, so a PR that touches only one of them must run it. This widens the trigger set. REVIEW.md puts any paths-ignore change in the CI zone at the high level, so it is named here. REVIEW.md (the CI-and-guards rule and the commands table), CLAUDE.md and docs/architecture.md follow. Cost: about 2.5 min of runner time on PRs touching project skills or release.yml.
  3. release.yml:104: the stale ".deb fallback" comment is replaced by the three keys. This was also item 3 of #461.
  4. The Publish step is tested. build-latest-json.test.mjs extracts the exact run: text of "Publish latest.json to package registry" and runs it under bash --noprofile --norc -e -o pipefail, in a directory laid out like the checkout, with a fake curl on PATH. A valid manifest gives DELETE then PUT with the expected URLs and file. On the generic key, a wrong version, an empty file or two documents, the step exits 1 with the error line and makes no request. The extracted text was compared to a PyYAML parse of the step: identical (46 lines). A guard asserts it has no workflow expression, and a comment at the top of the step says so. The guard matters because the runner substitutes those expressions, even in comments, and the test would not see that.
  5. docs/qa-update-cycle.md: a npm run tauri dev build on Linux shows TargetsNotFound in the Updates card on every check. This is expected since #418, not a regression. Added to the existing bullet. The stale release.yml:217-219 reference now names the step.

Outside this repo: la-compagnie-maximus/docs/forgejo-ops.md, commit 9a8e3f5 pushed to main.

  • /actions/tasks reads limit only with page. Re-measured on 2026-10-18 over 654 tasks: ?limit=5 returns 654, ?limit=5&page=1 returns 5, ?page=2 returns 30, ?limit=100&page=1 returns 50.
  • The run 513 case: cancel-in-progress did not cancel a running PR job.

Skill /release (step 9 text plus a dated history entry) updated. No CHANGELOG entry: nothing user-facing.

Verification

  • npm run build green; npm test under jq 1.6: 134 files, 2256 tests (2248 before, +8). Probe first: a deliberately red test exits 1.
  • build-latest-json.test.mjs: 36/36 under jq 1.6 and under jq 1.7.
  • python3 scripts/review-levels.py --check: ok.

Mutations: one at a time, on the committed tree, restored by git. The table gives red tests out of 36.

Mutation jq 1.6 jq 1.7
M1 -e dropped from the Publish call 4 4
M2 -s dropped from the Publish call 2 1
M3 exit 1 after the refusal replaced by true 4 4
M4 length == 1 and dropped from the filter 1 1
M5 tree before this PR (old filter, no -s anywhere) 4 2

M5 matches the review's claim: without the fix, the new tests are red under jq 1.7 only for two documents, and under jq 1.6, the CI's, for the empty file too.

Review level

High per REVIEW.md. The diff touches release.yml (the job that holds the signing key and the guard before the registry DELETE), the paths-ignore of check-frontend.yml and .claude/skills/release/.

🤖 Generated with Claude Code

Resolves #419 Follow-ups of the review of PR #418 (comment 22774). Point 1 had to land before the next tag. ## Changes 1. **Empty or multi-document `latest.json` refused.** `scripts/check-latest-json.jq` now wants exactly one document (`length == 1 and (.[0] | ...)`), and the Publish step of `release.yml` calls it with `-s`. Reproduced before the fix with the official jq 1.6 static binary (sha256 `af986793...a124c44`, the version of `ubuntu:22.04`): an empty file passed with rc 0, while jq 1.7 refused it with rc 4. Two new tests cover it: an empty file, and two documents whose last one is valid (also two valid documents). 2. **"On every PR" made true (decision of Max):** `check-frontend.yml` no longer ignores `.claude/**` or `.forgejo/workflows/release.yml`. The test reads `verify-release.sh` and now runs the Publish step, so a PR that touches only one of them must run it. This **widens** the trigger set. REVIEW.md puts any `paths-ignore` change in the CI zone at the high level, so it is named here. `REVIEW.md` (the CI-and-guards rule and the commands table), `CLAUDE.md` and `docs/architecture.md` follow. Cost: about 2.5 min of runner time on PRs touching project skills or `release.yml`. 3. **`release.yml:104`**: the stale ".deb fallback" comment is replaced by the three keys. This was also item 3 of #461. 4. **The Publish step is tested.** `build-latest-json.test.mjs` extracts the exact `run:` text of "Publish latest.json to package registry" and runs it under `bash --noprofile --norc -e -o pipefail`, in a directory laid out like the checkout, with a fake `curl` on PATH. A valid manifest gives `DELETE` then `PUT` with the expected URLs and file. On the generic key, a wrong version, an empty file or two documents, the step exits 1 with the error line and makes no request. The extracted text was compared to a PyYAML parse of the step: identical (46 lines). A guard asserts it has no workflow expression, and a comment at the top of the step says so. The guard matters because the runner substitutes those expressions, even in comments, and the test would not see that. 5. **`docs/qa-update-cycle.md`**: a `npm run tauri dev` build on Linux shows `TargetsNotFound` in the Updates card on every check. This is expected since #418, not a regression. Added to the existing bullet. The stale `release.yml:217-219` reference now names the step. Outside this repo: `la-compagnie-maximus/docs/forgejo-ops.md`, commit `9a8e3f5` pushed to `main`. - `/actions/tasks` reads `limit` only with `page`. Re-measured on 2026-10-18 over 654 tasks: `?limit=5` returns 654, `?limit=5&page=1` returns 5, `?page=2` returns 30, `?limit=100&page=1` returns 50. - The run 513 case: `cancel-in-progress` did not cancel a running PR job. Skill `/release` (step 9 text plus a dated history entry) updated. No CHANGELOG entry: nothing user-facing. ## Verification - `npm run build` green; `npm test` under jq 1.6: 134 files, **2256 tests** (2248 before, +8). Probe first: a deliberately red test exits 1. - `build-latest-json.test.mjs`: 36/36 under jq 1.6 and under jq 1.7. - `python3 scripts/review-levels.py --check`: ok. Mutations: one at a time, on the committed tree, restored by git. The table gives red tests out of 36. | Mutation | jq 1.6 | jq 1.7 | |---|---|---| | M1 `-e` dropped from the Publish call | 4 | 4 | | M2 `-s` dropped from the Publish call | 2 | 1 | | M3 `exit 1` after the refusal replaced by `true` | 4 | 4 | | M4 `length == 1 and` dropped from the filter | 1 | 1 | | M5 tree before this PR (old filter, no `-s` anywhere) | 4 | 2 | M5 matches the review's claim: without the fix, the new tests are red under jq 1.7 only for two documents, and under jq 1.6, the CI's, for the empty file too. ## Review level High per `REVIEW.md`. The diff touches `release.yml` (the job that holds the signing key and the guard before the registry `DELETE`), the `paths-ignore` of `check-frontend.yml` and `.claude/skills/release/`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
maximus added 1 commit 2026-10-18 17:01:43 +00:00
fix(release): refuse an empty or multi-document latest.json, test the Publish step
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m4s
c859405deb
Follow-ups of the review of PR #418.

- check-latest-json.jq wants exactly one document and release.yml calls it
  with -s: under jq 1.6, the release container's, an empty file passed the
  check (no output, status 0) and two documents were judged on the last one,
  so the job would have deleted the published manifest and uploaded the bad
  one. Two tests cover both cases.
- build-latest-json.test.mjs runs the text of release.yml's "Publish
  latest.json to package registry" step against a fake curl: DELETE then PUT
  on a valid manifest, exit 1 and no request on a refused one.
- check-frontend no longer ignores .claude/** and release.yml (decision of
  Max): the test reads verify-release.sh and runs the Publish step, so "on
  every pull request" holds. REVIEW.md, CLAUDE.md and architecture.md follow.
- release.yml: stale ".deb fallback" comment fixed.
- qa-update-cycle.md: a tauri dev build on Linux shows TargetsNotFound,
  expected since #418.

Refs #461

Resolves #419

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Niveau : haut — .forgejo/workflows/release.yml (Release et mise à jour), aussi scripts/check-latest-json.jq, scripts/build-latest-json.test.mjs et .claude/skills/release/SKILL.md
Max doit lire avant le merge.

Verdict : APPROVE

Le point 1 de #419 est fermé : avec -s et length == 1, le filtre refuse un fichier vide et un fichier à deux documents sous jq 1.6 comme sous 1.7. L'étape « Publish » est exécutée telle quelle par le test, et ce test sait échouer sur chaque garde que la PR ajoute.

Ce qui a été rejoué

Tout a tourné sur une copie hors dépôt (git archive c859405, node_modules lié), le dépôt n'a pas été modifié.

  • jq 1.6 : le binaire statique officiel (sha256 af986793…a124c44, identique à celui du corps de la PR). build-latest-json.test.mjs passe à 36/36 sous jq 1.6 et sous jq 1.7.
  • Extraction : le texte que rend publishStepScript() est identique, à l'octet, à run lu par PyYAML (46 lignes).
  • CI de la tête : run 565 vert, journal lu. jq (1.6-2.1ubuntu3.2) est installé, jq-1.6 affiché, build-latest-json.test.mjs (36 tests) passe, 134 fichiers et 2256 tests.
  • Timeline : #419 porte un closes venant de #464, et #461 n'en porte aucun (cité seulement, none).

Mutations du relecteur, une à la fois, restaurées puis comparées à l'original. Le tableau donne les tests rouges sur 36, sous jq 1.6 / sous jq 1.7 :

Mutation 1.6 1.7
R1 filtre length == 1 → length >= 1 1 1
R2 filtre length == 1 and .[0] → .[-1] (dernier document seul) 2 2
R3 contrôle jq déplacé après le DELETE (avant le PUT) 4 4
R4 étape Publish : ${GITHUB_REF_NAME#v} → ${GITHUB_REF_NAME} 1 1
R5 commentaire # tag ${{ github.ref_name }} ajouté dans le run: 1 1
R6 > /dev/null || true sur l'appel du filtre 1 1

Une première version de R4 a muté la ligne 84 (TAG= de l'étape de création de la release) au lieu de l'étape Publish, et la suite est restée verte. C'est attendu : seul le texte de « Publish » est exécuté, et le corps de la PR le dit (« le reste de release.yml ne s'exerce qu'au tag »).

Passe sécurité (niveau haut)

Aucun constat.

  • Secrets : aucun ajouté. fake-token est un littéral de test. FORGEJO_TOKEN: ${{ secrets.PACKAGE_TOKEN }} était déjà là.
  • Motifs dangereux : le test exécute un texte lu dans un fichier du dépôt, dans un mkdtemp, avec un curl factice en tête du PATH. Aucun appel réseau.
  • Configuration : le paths-ignore de check-frontend.yml est élargi, pas restreint : la CI tourne sur plus de PR. check-frontend.yml ne référence aucun secrets, et il n'y a pas de pull_request_target. Aucun manifeste ni lock ne change.
  • Règles de zone : la garde d'avant le DELETE échoue toujours fermée. REVIEW.md (hors de la section « Niveaux »), CLAUDE.md et docs/architecture.md suivent le nouveau déclencheur, comme l'exige « Règles de revue ».

Suggestions (non bloquantes)

  1. Le nouveau déclencheur n'est pas encore prouvé. Cette PR touche aussi scripts/**, donc son run ne montre pas qu'une PR qui ne touche que .claude/** ou release.yml lance bien check-frontend (piège « un filtre paths: n'est pas prouvé par une PR qui touche le chemin exact »). La preuve viendra de la première PR de ce type : le noter sur #419 ou dans STATE.md.
  2. #461 reste ouverte, alors que son point 3 (commentaire périmé de release.yml:104) est réglé ici. Un commentaire sur #461 après le merge évitera de le refaire.
  3. Cycle de mise à jour. Le diff touche release.yml, donc l'étape 10 de /release reste due à la prochaine release. Elle l'est déjà pour #376 et #447 : rien de nouveau.

Actions faites

  • Copie hors dépôt scratchpad/pr464, les sauvegardes orig-* et le script de mutation supprimés en fin de revue.
  • Label de #419 : status:review → status:approved.
Niveau : haut — `.forgejo/workflows/release.yml` (Release et mise à jour), aussi `scripts/check-latest-json.jq`, `scripts/build-latest-json.test.mjs` et `.claude/skills/release/SKILL.md` Max doit lire avant le merge. ## Verdict : APPROVE Le point 1 de #419 est fermé : avec `-s` et `length == 1`, le filtre refuse un fichier vide et un fichier à deux documents sous jq 1.6 comme sous 1.7. L'étape « Publish » est exécutée telle quelle par le test, et ce test sait échouer sur chaque garde que la PR ajoute. ## Ce qui a été rejoué Tout a tourné sur une copie hors dépôt (`git archive c859405`, `node_modules` lié), le dépôt n'a pas été modifié. - **jq 1.6** : le binaire statique officiel (sha256 `af986793…a124c44`, identique à celui du corps de la PR). `build-latest-json.test.mjs` passe à 36/36 sous jq 1.6 et sous jq 1.7. - **Extraction** : le texte que rend `publishStepScript()` est identique, à l'octet, à `run` lu par PyYAML (46 lignes). - **CI de la tête** : run 565 vert, journal lu. `jq (1.6-2.1ubuntu3.2)` est installé, `jq-1.6` affiché, `build-latest-json.test.mjs (36 tests)` passe, 134 fichiers et 2256 tests. - **Timeline** : #419 porte un `closes` venant de #464, et #461 n'en porte aucun (cité seulement, `none`). **Mutations du relecteur**, une à la fois, restaurées puis comparées à l'original. Le tableau donne les tests rouges sur 36, sous jq 1.6 / sous jq 1.7 : | Mutation | 1.6 | 1.7 | |---|---|---| | R1 filtre `length == 1` → `length >= 1` | 1 | 1 | | R2 filtre `length == 1 and .[0]` → `.[-1]` (dernier document seul) | 2 | 2 | | R3 contrôle `jq` déplacé après le `DELETE` (avant le `PUT`) | 4 | 4 | | R4 étape Publish : `${GITHUB_REF_NAME#v}` → `${GITHUB_REF_NAME}` | 1 | 1 | | R5 commentaire `# tag ${{ github.ref_name }}` ajouté dans le `run:` | 1 | 1 | | R6 `> /dev/null \|\| true` sur l'appel du filtre | 1 | 1 | Une première version de R4 a muté la ligne 84 (`TAG=` de l'étape de création de la release) au lieu de l'étape Publish, et la suite est restée verte. C'est attendu : seul le texte de « Publish » est exécuté, et le corps de la PR le dit (« le reste de `release.yml` ne s'exerce qu'au tag »). ## Passe sécurité (niveau haut) Aucun constat. - **Secrets** : aucun ajouté. `fake-token` est un littéral de test. `FORGEJO_TOKEN: ${{ secrets.PACKAGE_TOKEN }}` était déjà là. - **Motifs dangereux** : le test exécute un texte lu dans un fichier du dépôt, dans un `mkdtemp`, avec un `curl` factice en tête du `PATH`. Aucun appel réseau. - **Configuration** : le `paths-ignore` de `check-frontend.yml` est élargi, pas restreint : la CI tourne sur plus de PR. `check-frontend.yml` ne référence aucun `secrets`, et il n'y a pas de `pull_request_target`. Aucun manifeste ni lock ne change. - **Règles de zone** : la garde d'avant le `DELETE` échoue toujours fermée. `REVIEW.md` (hors de la section « Niveaux »), `CLAUDE.md` et `docs/architecture.md` suivent le nouveau déclencheur, comme l'exige « Règles de revue ». ## Suggestions (non bloquantes) 1. **Le nouveau déclencheur n'est pas encore prouvé.** Cette PR touche aussi `scripts/**`, donc son run ne montre pas qu'une PR qui ne touche que `.claude/**` ou `release.yml` lance bien `check-frontend` (piège « un filtre `paths:` n'est pas prouvé par une PR qui touche le chemin exact »). La preuve viendra de la première PR de ce type : le noter sur #419 ou dans `STATE.md`. 2. **#461 reste ouverte**, alors que son point 3 (commentaire périmé de `release.yml:104`) est réglé ici. Un commentaire sur #461 après le merge évitera de le refaire. 3. **Cycle de mise à jour.** Le diff touche `release.yml`, donc l'étape 10 de `/release` reste due à la prochaine release. Elle l'est déjà pour #376 et #447 : rien de nouveau. ## Actions faites - Copie hors dépôt `scratchpad/pr464`, les sauvegardes `orig-*` et le script de mutation supprimés en fin de revue. - Label de #419 : `status:review` → `status:approved`.
maximus merged commit c859405deb into main 2026-10-18 17:37:48 +00:00
maximus deleted branch issue-419-latest-json-check-followups 2026-10-18 17:37:49 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/Simpl-Resultat#464
No description provided.