Skip to content

ci: guard the Flatpak sources against lockfile drift - #490

Merged
InstaZDLL merged 5 commits into
mainfrom
ci/flatpak-sources-guard
Aug 8, 2026
Merged

ci: guard the Flatpak sources against lockfile drift#490
InstaZDLL merged 5 commits into
mainfrom
ci/flatpak-sources-guard

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Follow-up to #489, which uncovered the problem.

The gap

Flathub builds with the network disabled, so every crate and every npm tarball the build pulls must be pre-declared in packaging/flatpak/generated/ by generate-sources.sh. Nothing enforced that. chore: update dependencies (23be643) bumped Cargo.lock without re-running the generator, leaving 53 crates with no source entry — wasmtime 46.0.1 declared against 47.0.3 in the lock — and CI stayed green the whole time. That failure mode only surfaces inside Flathub's sandbox, on a crate nobody can download.

What this adds

packaging/flatpak/check-sources.py — verifies coverage: every registry crate in Cargo.lock has a source entry, every npm package in the Flatpak lockfile has a tarball. Offline, ~1 second, no venv / npm / network. Wired into ci.yml behind a paths filter (src-tauri/Cargo.lock, package.json, packaging/flatpak/**).

Replayed against 52f2b4a (the commit before #489 fixed it), it reports the 53 missing crates and exits 1 — so it catches the exact case that motivated it, not a hypothetical one.

flatpak-sources-refresh.yml — monthly (3rd, 04:00 UTC) + workflow_dispatch. Regenerates, re-runs the check on its own output, and opens/updates a PR when the manifests moved. This is the part that actually bumps the Flatpak dependency set.

Why not "regenerate and diff the tree"

That's the obvious design and it would be wrong here. generate-sources.sh runs npm install --package-lock-only, which re-resolves ^ ranges against the registry as it is at that moment — so a regenerate-and-compare check would go red the day any transitive dependency publishes a patch, with nothing wrong in the repo. Contributors would learn to ignore it.

So the two concerns are split: CI guards the invariant the build needs (nothing undeclared) on every relevant PR; freshness is a periodic chore. The invariant is deterministic and offline, the chore is neither.

Notes

  • The refresh job runs on the default branch only (schedule + dispatch), so no untrusted code reaches the generator — it executes npm install (with --ignore-scripts) and pip install from a commit-pinned upstream.
  • PRs opened with GITHUB_TOKEN don't trigger workflows, so CI won't run on the refresh PR; the job re-runs check-sources.py itself before opening it, and the PR body says to close/reopen to kick CI off.
  • All three action SHAs verified against the GitHub API (I had initially written a setup-python SHA that doesn't exist).

Checks

  • Both workflow files parse as YAML; the PR-creating step passes bash -n
  • check-sources.py exits 0 on main, exits 1 with the 53 crates on 52f2b4a
  • Action SHAs resolve to real commits ✅

Not verified by me: neither workflow has actually run — the CI job only executes once this lands on a PR that touches a lockfile, and the refresh job needs a dispatch. The refresh job is the riskier of the two (it pushes a branch and opens a PR); worth a manual workflow_dispatch after merge to confirm end to end.

https://claude.ai/code/session_01N9WXrurarkoiT2jgimvyj1

Summary by CodeRabbit

  • Nouvelles fonctionnalités

    • Ajout d’une vérification automatisée de la cohérence des sources Flatpak avec les dépendances npm et Rust.
    • Mise en place d’un rafraîchissement mensuel, également déclenchable manuellement.
    • Création ou mise à jour automatique d’une demande de modification lorsque des sources évoluent.
  • Améliorations

    • Les contrôles ciblés s’exécutent uniquement lorsque nécessaire.
    • Les écarts sont signalés avec des diagnostics détaillés.
    • Aucune demande de modification n’est créée si aucune mise à jour n’est requise.

Flathub builds with the network disabled, so every crate and npm tarball
must be pre-declared in packaging/flatpak/generated/. Nothing enforced
that: `chore: update dependencies` (23be643) bumped Cargo.lock without
re-running generate-sources.sh, leaving 53 crates with no source entry,
and CI stayed green -- the failure only surfaces inside Flathub's
sandbox, on a crate nobody can download.

check-sources.py verifies coverage: every registry crate in Cargo.lock
has a source entry, every npm package in the Flatpak lockfile has a
tarball. Offline, ~1s, no venv or npm. Replayed against 52f2b4a it
reports the 53 missing crates and exits 1, so it does catch the case
that motivated it.

It deliberately does NOT regenerate-and-diff the tree. The generator
runs `npm install --package-lock-only`, which re-resolves `^` ranges
against the registry as it is right now, so such a check would go red
whenever any transitive dependency publishes -- with nothing wrong in
the repo. Freshness is a separate monthly job that regenerates and opens
a PR (also workflow_dispatch-able); it runs on the default branch only,
so no untrusted code reaches the generator.

Claude-Session: https://claude.ai/code/session_01N9WXrurarkoiT2jgimvyj1
@InstaZDLL InstaZDLL added scope: ci CI/CD, workflows scope: docs Docs, README, assets type: ci CI/CD changes size: l 200-500 lines labels Aug 7, 2026
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f718d8b8-6a28-4c29-ac43-25c31d777798

📥 Commits

Reviewing files that changed from the base of the PR and between c687cb6 and 02f43a8.

📒 Files selected for processing (1)
  • .github/workflows/flatpak-sources-refresh.yml

📝 Walkthrough

Walkthrough

Le changement ajoute un vérificateur hors ligne des sources Flatpak. La CI l’exécute pour les modifications concernées. Un workflow mensuel ou manuel régénère les sources et gère une pull request dédiée.

Changes

Sources Flatpak

Layer / File(s) Summary
Contrôle de couverture des sources
packaging/flatpak/check-sources.py
Le script compare les dépendances Cargo et npm verrouillées aux sources Flatpak. Il vérifie les dépendances directes, les contraintes semver et les overrides npm.
Validation CI des sources
.github/workflows/ci.yml
La CI détecte les modifications pertinentes. Elle exécute l’auto-test et le contrôle de couverture après un checkout sans identifiants persistants.
Rafraîchissement automatisé
.github/workflows/flatpak-sources-refresh.yml, CLAUDE.md
Le workflow régénère et valide les sources chaque mois ou sur déclenchement manuel. Il crée un commit signé, met à jour une branche dédiée et ouvre ou réutilise une pull request. La procédure est documentée.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Déclencheur
  participant Workflow
  participant Génération
  participant Validation
  participant Git
  participant PullRequest
  Déclencheur->>Workflow: Lancer le rafraîchissement
  Workflow->>Génération: Exécuter generate-sources.sh
  Génération-->>Workflow: Retourner les fichiers générés
  Workflow->>Validation: Exécuter check-sources.py
  Validation-->>Workflow: Retourner le statut
  Workflow->>Git: Créer et vérifier le commit signé
  Git-->>Workflow: Publier ou ignorer la branche
  Workflow->>PullRequest: Mettre à jour ou créer la pull request
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Le titre décrit clairement l’objectif principal : empêcher la dérive entre les lockfiles et les sources Flatpak.
Description check ✅ Passed La description explique le problème, la solution, les tests effectués et la validation manuelle restante, avec un contenu globalement complet.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/flatpak-sources-guard

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 149-162: Disable checkout credential persistence in the checkout
step at .github/workflows/ci.yml:149-162 by adding persist-credentials: false.
Apply the same setting to the checkout step at
.github/workflows/flatpak-sources-refresh.yml:45-45; in that workflow’s push
step, run gh auth setup-git with GH_TOKEN before git push, while preserving
contents: write and pull-requests: write permissions.

In @.github/workflows/flatpak-sources-refresh.yml:
- Around line 75-89: Modifiez l’étape « Commit + open PR » pour obtenir l’OID
distant de la branche via git ls-remote avant le push. Utilisez cet OID comme
valeur explicite de force-with-lease, ou un lease vide si BRANCH n’existe pas
encore, afin d’éviter stale info malgré l’absence de branche amont configurée.

In `@packaging/flatpak/check-sources.py`:
- Around line 96-111: Update check_node to compare the root dependency fields
dependencies, devDependencies, optionalDependencies, and peerDependencies
between package.json and package-lock.json, reporting any mismatch. For
overrides, validate the requested versions against the resolved package entries
in the lockfile tree rather than expecting a root overrides field; preserve the
existing source URL validation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b4b6f4ce-3c46-439e-9e83-1255443559fd

📥 Commits

Reviewing files that changed from the base of the PR and between e0ee50c and 2c7c90d.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • .github/workflows/flatpak-sources-refresh.yml
  • CLAUDE.md
  • packaging/flatpak/check-sources.py

Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/flatpak-sources-refresh.yml Outdated
Comment thread packaging/flatpak/check-sources.py
Three review findings, all confirmed against the code.

The push was broken from the second run on. A bare --force-with-lease
compares against the remote-tracking ref, which actions/checkout never
fetches for this branch, so once the branch exists remotely the push
dies with "stale info" -- reproduced locally against a scratch remote
before fixing. Now the lease carries the OID from git ls-remote, with
the empty-OID "must not exist" form covering the first run. Both paths
verified.

Both checkouts drop persist-credentials, so the job token never lands in
.git/config -- it matters most in the refresh job, which runs npm
install and pip install right after. The push step re-supplies
credentials through gh auth setup-git.

check-sources.py gained the manifest half it was missing: npm ci refuses
to run when package.json and package-lock.json disagree, so adding a
dependency without regenerating breaks the Flathub build exactly like an
undeclared crate does -- and the tarball coverage check can't see it,
since the lockfile stays internally consistent. Overrides are validated
against the resolved tree rather than a root `overrides` field, because
npm doesn't echo one into the lockfile; the pin only means something if
the resolved versions obey it. Verified on four scratch trees: added
dep, changed spec and a violated fast-uri override all exit 1, an
untouched tree still exits 0.

Claude-Session: https://claude.ai/code/session_01N9WXrurarkoiT2jgimvyj1

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/flatpak-sources-refresh.yml:
- Around line 46-51: Configure the actions/checkout step to explicitly use the
main branch by adding ref: main alongside persist-credentials. Keep the existing
credential handling unchanged so the generator reads main’s lockfiles and the
pull request target remains consistent.

In `@packaging/flatpak/check-sources.py`:
- Around line 218-228: Update the resolved-package collection in the validation
flow to preserve every version found for the requested package instead of keying
entries by package name. In the block around resolved and the subsequent
version-validation loop, use a set of all matching pkg.get("version") values so
nested node_modules copies cannot overwrite one another, then validate each
collected version and retain the existing skipped behavior when none are found.
- Around line 190-196: Corrigez la logique de l’opérateur « ^ » dans la fonction
de comparaison des versions afin de respecter les bornes npm pour les versions
0.x : pour ^0.minor.patch, exigez la même version majeure et un minor égal à la
borne avec patch suffisant ; pour ^0.0.patch, exigez également le même patch ou
une version ultérieure compatible. Ajoutez des tests couvrant chaque borne et la
première version juste au-dessus.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0f2495a4-8207-41de-b012-b5dd9edece5c

📥 Commits

Reviewing files that changed from the base of the PR and between 2c7c90d and 6e4a3fb.

📒 Files selected for processing (3)
  • .github/workflows/ci.yml
  • .github/workflows/flatpak-sources-refresh.yml
  • packaging/flatpak/check-sources.py

Comment thread .github/workflows/flatpak-sources-refresh.yml
Comment thread packaging/flatpak/check-sources.py Outdated
Comment thread packaging/flatpak/check-sources.py Outdated
Three review findings, all confirmed.

workflow_dispatch can target any branch, but the PR is always opened
against main -- generating from another branch's lockfiles would produce
sources that don't match the base. The checkout now pins ref: main,
which also makes the header's "default branch only" claim true.

The override check kept one version per package name, so the dict
overwrote entries: npm nests a second copy under
node_modules/<dep>/node_modules/<name> when versions conflict, and an
unpatched nested copy could slip through the very check meant to catch
it (which survived depended on iteration order). It now collects every
resolved version. Verified: a nested fast-uri 3.1.2 alongside a patched
top-level copy is reported, where before it wasn't.

The caret comparison was "same major", which is wrong below 1.0.0 --
npm pins the leftmost non-zero component there, so ^0.1.2 accepted
0.2.0 and ^0.0.3 accepted 0.1.0. It failed permissively, i.e. it would
have blessed a version npm rejects. Now bounded properly, and a
prerelease version returns "unverified" instead of being compared as if
the suffix weren't there. A --self-test mode covers each boundary and
the first version past it (21 cases), wired into CI; all four cases the
old logic got wrong are in the table.

Claude-Session: https://claude.ai/code/session_01N9WXrurarkoiT2jgimvyj1
@InstaZDLL InstaZDLL added size: xl > 500 lines and removed size: l 200-500 lines labels Aug 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/flatpak-sources-refresh.yml (1)

134-135: 🩺 Stability & Availability | 🔵 Trivial

Vérifiez le déclenchement de la CI pour la PR automatisée.

GITHUB_TOKEN ne déclenche pas automatiquement les workflows pour la PR créée. Le workflow valide check-sources.py, mais la CI complète ne s’exécute pas sur cette PR.

Si la protection de main exige ces checks, la PR reste bloquée. Si elle ne les exige pas, elle peut être fusionnée sans validation complète. Utilisez un jeton GitHub App à permissions minimales, ou déclenchez une validation de confiance séparée. Vérifiez ce comportement sur une exécution réelle.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/flatpak-sources-refresh.yml around lines 134 - 135, Update
the automated PR creation flow in flatpak-sources-refresh.yml so it does not
rely on GITHUB_TOKEN, which cannot trigger the required CI workflows. Use a
minimally permissioned GitHub App token, or add a separate trusted validation
trigger that runs the full CI checks including check-sources.py. Verify the
complete CI executes on an actual automated PR and satisfies main branch
protection.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In @.github/workflows/flatpak-sources-refresh.yml:
- Around line 134-135: Update the automated PR creation flow in
flatpak-sources-refresh.yml so it does not rely on GITHUB_TOKEN, which cannot
trigger the required CI workflows. Use a minimally permissioned GitHub App
token, or add a separate trusted validation trigger that runs the full CI checks
including check-sources.py. Verify the complete CI executes on an actual
automated PR and satisfies main branch protection.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5a5e108f-eb36-44bd-90d3-5db92fde8438

📥 Commits

Reviewing files that changed from the base of the PR and between 6e4a3fb and cd9beb5.

📒 Files selected for processing (3)
  • .github/workflows/ci.yml
  • .github/workflows/flatpak-sources-refresh.yml
  • packaging/flatpak/check-sources.py

The Main ruleset turns out to require signatures, code scanning and code
quality. Two consequences the automated PR fell foul of:

A commit made with `git commit` in a runner is unsigned, and an unsigned
commit in the branch blocks the merge -- so the PR was never mergeable in
the first place, before any question of CI. The commit is now built
through the Git Data API, which GitHub signs with its own key; same
approach as release-please-bump-lockfile.yml, which exists for this
reason. That also drops the credential helper and the force-with-lease
dance, since nothing pushes over git any more.

Events raised by GITHUB_TOKEN deliberately don't start workflow runs, so
the required checks never ran. Setting APP_ID + APP_PRIVATE_KEY (a
GitHub App with contents:write + pull-requests:write) now makes the PR be
opened as that App and CI fires normally; without them the workflow still
works and the body says CI has to be kicked off by a close/reopen.

Three bugs in my own first cut of this, caught before pushing: a step's
own `env` isn't in scope for its `if`, so the App-token guard had to move
to job level or it would always skip; `token && '' || 'yes'` always
yields 'yes' because the empty string is falsy; and the PR step now gates
on the commit step's verdict, since regenerated files can be
byte-identical to main even when git diff flagged them.

Claude-Session: https://claude.ai/code/session_01N9WXrurarkoiT2jgimvyj1

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/flatpak-sources-refresh.yml:
- Around line 20-27: Update the commit creation flow using
github.rest.git.createCommit to produce a signed commit, then validate
commit.data.verification.verified before calling updateRef or createRef and fail
if verification is false. Correct the surrounding comments and pull-request body
so they accurately describe the signing behavior.
- Around line 67-72: Restreindre les permissions du jeton créé par l’étape
app-token en ajoutant uniquement permission-contents: write et
permission-pull-requests: write dans sa configuration with, sans accorder
d’autres permissions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ae3db05d-990f-4141-93d9-89f757693ef1

📥 Commits

Reviewing files that changed from the base of the PR and between cd9beb5 and c687cb6.

📒 Files selected for processing (1)
  • .github/workflows/flatpak-sources-refresh.yml

Comment thread .github/workflows/flatpak-sources-refresh.yml
Comment thread .github/workflows/flatpak-sources-refresh.yml
Both findings applied, one of them after checking the premise rather
than the code.

The claim my comments made -- that commits created through the Git Data
API are signed by GitHub -- turns out to be correct: release-please's own
API-created commit dcd7280 reports verified=true, reason=valid, with a
signature present. So the comments stay as they are. But the property is
load-bearing (an unsigned commit is unmergeable under the ruleset) and
invisible if it ever changes, so the workflow now re-reads the commit
after creating it and fails loudly, leaving the branch untouched, when
verification.verified isn't true. Read back via getCommit rather than
trusting the create response, so the check reflects what the API will
report to the merge button.

The App token is narrowed to permission-contents: write and
permission-pull-requests: write -- what the job actually needs for the
blob/tree/commit/ref writes and for `gh pr create`. Without those it
inherits every permission the App holds on the repo. Both input names
verified against the action's own action.yml, since an unknown input is
ignored with a warning and would have looked like hardening while
granting everything.

Claude-Session: https://claude.ai/code/session_01N9WXrurarkoiT2jgimvyj1
@InstaZDLL
InstaZDLL merged commit 3862dc0 into main Aug 8, 2026
15 checks passed
@InstaZDLL
InstaZDLL deleted the ci/flatpak-sources-guard branch August 8, 2026 05:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: ci CI/CD, workflows scope: docs Docs, README, assets size: xl > 500 lines type: ci CI/CD changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant