Skip to content

Review gate: PR 820 (CI/CD pipeline + addon-dep detector)#20

Merged
eduralph merged 18 commits into
review-base/upstream-gramps60from
feature/ci-cd-pipeline-upstream
May 24, 2026
Merged

Review gate: PR 820 (CI/CD pipeline + addon-dep detector)#20
eduralph merged 18 commits into
review-base/upstream-gramps60from
feature/ci-cd-pipeline-upstream

Conversation

@eduralph

Copy link
Copy Markdown
Owner

Pre-merge review gate (fork-internal mirror of upstream gramps-project#820).

This PR exists so the full diff that will land upstream as PR 820 can be re-read here with fresh eyes before being marked ready upstream. Nothing here is intended to be merged into the fork's maintenance/gramps60 — the fork tracks upstream by sync, not by self-merge. Close without merging once the upstream PR is reviewed.

What's on this branch

The branch carries the full PR 820 stack (CI image + workflows + shared unittest harness + plugin-registration tests) plus the latest addition:

  • ad2815ceetests: detect addons that import a sibling without depends_on
    • tests/test_addon_dependencies.py: independent detector for the Mantis 13707 bug class (WebConnect packs importing libwebconnect without declaring depends_on).
    • Picked up by the existing integration-test job's unittest discover -s tests; no ci.yml change.
    • On gramps60 the tree is already clean (0 (a) findings); the test passes today.

What to look at first on the new commit

  • tests/test_addon_dependencies.py — the whole file. Particularly:
    • Independence from Gramps' loader (no gramps.gen.plug imports — by design, so the test does not run addons through the very dependency resolver whose leniency lets 13707 ship).
    • The exec-shim for .gpr.py (permissive globals + fake register()).
    • The isolation hardening in the subprocess loader (-I, cwd=/tmp, PYTHONPATH stripped, "" removed from sys.path — without these, PEP 420 implicit namespace packages silently defeat the isolation).
    • Classifier precedence: (a) wins over (b) wins over (c).

Not in this PR / commit

  • No depends_on fixes to any addon — that's a separate later round on maintenance/gramps61.
  • No ci.yml edit for a dedicated job — the test rides the existing discover -s tests invocation; carving it into its own continue-on-error: true job is the follow-up the brief explicitly defers until findings appear.

🤖 Generated with Claude Code

eduralph and others added 18 commits April 19, 2026 14:37
Introduce GitHub Actions CI inside a shared Docker image on ghcr.io,
plus a native Windows runner for cross-platform unit-test coverage,
and a shared unittest harness with Gramps-backed fixtures that verifies
every addon registers, loads, and exposes valid plugin metadata.

CI infrastructure
-----------------
- .github/docker/gramps-ci/Dockerfile — Python 3.12 + Gramps 6.0 (pip)
  + PyGObject + GTK typelibs + xvfb/xauth + ruff, dbf, intltool,
  gettext, git. GTK lives in the base so addon modules that do
  `from gi.repository import Gtk` at load time are importable; xvfb
  and xauth are bundled for tests that actually render.
- .github/workflows/docker-build.yml — rebuilds the image on
  .github/docker/** changes or via workflow_dispatch.
- .github/workflows/ci.yml — seven jobs: lint (ruff E9/F63/F7/F82 +
  trailing whitespace), addon-structure (every addon has
  po/template.pot), compile-check (py_compile on every .py),
  unit-test-linux (container), unit-test-windows (native, conda+pip),
  integration-test (container with --init so xvfb-run does not hang),
  build (make.py gramps60 build all).
- .github/environment.yml — hybrid conda+pip env for Windows. Gramps
  is not on conda-forge, so pygobject/gtk3 come from conda and
  gramps/orjson/dbf come from pip.

Shared test harness
-------------------
- tests/__init__.py — GPL header.
- tests/gramps_test_env.py — sys.path / GRAMPS_RESOURCES bootstrap
  and two unittest base classes: GrampsTestCase (session-cached
  plugin manager + registry via setUpClass) and GrampsDbTestCase
  (same plus a fresh in-memory SQLite DB per test).
- tests/test_plugin_registration.py — four unittest.TestCase classes
  covering plugin registration, subprocess-isolated module loading
  (crash-safe), required metadata (gramps_target_version=6.0, valid
  id/name/version), and import/export entry-function smoke tests.

Gate policy
-----------
All seven jobs run on every push and PR. Four are marked
continue-on-error: true so they surface issues without blocking
merges while the existing tree is cleaned up:

  - lint              (~79 pre-existing ruff E9/F63/F7/F82 errors)
  - addon-structure   (4 addons missing po/template.pot)
  - unit-test-linux   (some addon test modules fail to import today)
  - unit-test-windows (same)

compile-check, integration-test, and build are blocking from day
one. Each non-blocking gate will be flipped to blocking in the same
follow-up PR that fixes its underlying issues, so the tightening is
incremental and visible in history.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Dockerfile bakes in only `dbf`, but addons declare a wider set of
Python deps in their .gpr.py `requires_mod` lists (networkx, psycopg2,
pygraphviz, lxml, svgwrite, boto3, litellm, life_line_chart, psycopg).
Without these installed, per-addon unit tests and the plugin-
registration subprocess load fail with ImportError/NameError.

Add a pre-test step to unit-test-linux, unit-test-windows, and
integration-test that globs every *.gpr.py, extracts the requires_mod
union via ast.literal_eval, and pip-installs each package one at a
time. Per-package install (not batched) keeps a single build failure
(pygraphviz without graphviz-dev, psycopg2 without libpq-dev) from
aborting the rest — the affected addon's tests will skip or fail in
isolation without blocking others.

Mirrors Gramps' Addon Manager install path
(gramps/gui/plug/_windows.py __on_install_clicked → req.install →
gen/utils/requirements.py), keeping .gpr.py files as the single source
of truth for addon deps. New addon deps do not need a parallel update
to the Dockerfile or this workflow.
With ci.yml's auto-derive step in place (previous commit), dbf is
installed at CI runtime from TMGimporter's .gpr.py requires_mod list.
Keeping it baked into the Dockerfile and environment.yml in parallel
would defeat the "single source of truth = .gpr.py" goal and drift
the moment a new addon declares an additional dep.

Remove dbf from both; leave the stable base (PyGObject, pycairo,
Gramps, orjson, ruff) since those are not addon deps. Add a comment
pointing readers at the auto-derive step so future edits do not
re-bake runtime deps back in.
Root cause of "Unit Tests (Linux)" and "Integration Tests (Gramps)"
failures was not broken test modules — the steps never invoked
unittest. The container's default shell is /bin/sh (dash on
python:3.12-slim), and the inline scripts use bash-only parameter
expansions (${f%.py}, ${mod//\//.}) to build the dotted module list.
Dash fails with "Bad substitution" on the first such line; the rest
of the script never runs. continue-on-error: true masked this as a
generic job failure for two CI rounds.

Add "shell: bash" explicitly to:
- unit-test-linux / Run per-addon unit tests (bashisms)
- integration-test / Run per-addon integration tests (bashisms)
- integration-test / Run plugin registration tests (no bashisms today,
  but consistent and future-proof)

Compile Check already sets shell: bash. Windows jobs inherit bash
via defaults.run at the job level. No other steps affected.
The Windows unit-test job hung on TMGimporter's DB-backed tests because
make_database("sqlite").load(":memory:", None) deadlocks under the
conda-forge GTK + pip Gramps combination. Rather than patch the hang,
introduce a filename convention so per-addon authors can declare OS
scope up front:

  test_*.py              general (every OS)
  test_linux_*.py        Linux-only
  test_windows_*.py      Windows-only
  test_integration_*.py  Linux-only, full-pipeline/DB-backed (pre-existing)

unit-test-linux skips test_windows_* and test_integration_*;
unit-test-windows skips test_linux_* and test_integration_*.

Applied to TMGimporter: the 13 DB-backed classes in tests/test_libtmg.py
move to tests/test_linux_libtmg.py (along with the _Rec/_table/_make_db/
_add_person/_MockUser helpers they use). The 7 pure-logic classes
(TestStripTmgCodes, TestTmgDateToGrampsDate, TestNumTo{Month,Date},
TestParseDate, TestRepoTypeFromName, TestUrlFromName) stay in
test_libtmg.py and will run on every OS.

Locally all 175 tests still pass via run-addon-unit.sh TMGimporter.
EditExifMetadata and PhotoTaggingGramplet both do
`from gi.repository import GExiv2` at module load. The CI image
installs several gir1.2-* typelibs (glib, gtk, pango, gdkpixbuf,
atk) but not gexiv2, so plugin-registration smoke tests on those
addons fail with:

    cannot import name GExiv2, introspection typelib not found

Add the missing apt package. GObject Introspection typelibs come
from system apt packages, not pip — they cannot be auto-derived
from requires_gi the way requires_mod is.

Companion to addons-source PRs gramps-project#878 (EditExifMetadata declares
requires_gi=[(GExiv2, 0.10)] alongside requires_mod=[Pillow]) and
gramps-project#880 (PhotoTaggingGramplet declares requires_gi=[(GExiv2, 0.10)]).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`git grep '[ \t]$'` in BRE/ERE mode interprets `[ \t]` as the character
class { space, backslash, 't' } — i.e. the bracket contains a literal
't', not a tab. The check therefore matches every line that ends in
't', '\', '[', ']' or a space, fires on ~3,000 false-positive lines
across the tree, and would block any PR touching such a file even
when no real trailing whitespace exists.

Switch to PCRE via `-P` so `\t` means tab. Also tighten to `[ \t]+$`
to make intent obvious (no behavioural change for the match decision).

Verified on the live tree:
  - BRE:  matches 3182 lines across ~430 files (mostly false positives).
  - PCRE: matches  597 lines across   21 files — real trailing whitespace.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per Gary Griffin's request on PR gramps-project#820: addons whose every register()
call sets include_in_listing=False are not built or released by
make.py, so the CI does not gate on them. Across the current addon
tree this skips 11 directories (CheckPlaceTitles, DetId, FaceDetection,
HtmlView, MongoDB, PhpGedView, Query, RebuildTypes, SourceIndex,
SourceReferences, WordleGramplet) — the ones Gary called out in his
comment, modulo case differences and a couple of names that no longer
exist as directories.

The check is inlined at each iteration site rather than centralised in
a helper script: a small shell function (is_active) per relevant job
step, plus an include_in_listing filter in the four addon-iterating
tests in tests/test_plugin_registration.py. Repeating ~7 lines of
bash across six job steps was preferred to introducing a separate
.github/scripts/ helper file, mirroring the pattern the workflow
already uses for the requires_mod auto-derive step.

To re-enable CI gating for an addon, set include_in_listing=True on
at least one register() call in its descriptor (or remove the field
entirely — Gramps' default is True). TMGimporter, for example, stays
gated because its main register() carries True even though three
conditional ones use False.

Affected steps:
  - lint (ruff): builds --exclude args from the skipped list
  - addon-structure: skips the po/template.pot check
  - compile-check (py_compile): skips files under skipped dirs
  - unit-test-linux / unit-test-windows: skips test modules
  - integration-test (per-addon): skips test modules
The integration-test plugin-registration smoke step inherits the
filter via the test file changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The auto-derive step parses requires_mod from every .gpr.py and pip-
installs each name, which validates the install-side of the contract.
But Gramps' end-user dep gate (gen/utils/requirements.py:check_mod)
calls find_spec() — the importable name, not the PyPI name. For most
addons the two coincide; for Pillow they don't, so requires_mod=
["Pillow"] pip-installs cleanly, CI stays green, and the Addon
Manager still rejects the addon on every end-user install.

Adds a follow-up step after each "Install addon runtime deps" block
(one per job: unit-test-linux, unit-test-windows, integration-test).
For each declared requires_mod name: if pip-show confirms the package
is installed but find_spec returns None, the declaration is wrong.
Pip-install failures are skipped, so missing system deps (graphviz-dev
for pygraphviz, libpq-dev for psycopg2) don't cause false positives.

Verified locally: catches requires_mod=["Pillow"] with exit 1 and a
GitHub annotation; current addons-source tree (all 9 declared mods)
passes with no false positives.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ps60

Drops the lint job's continue-on-error: true (and its TODO comment),
making ruff E9/F63/F7/F82 a blocking gate per the comment's own
instruction.

The ruff backlog the comment guarded against was cleared by 24 PRs
(gramps-project#843 + gramps-project#847-gramps-project#869) merged into maintenance/gramps60 between 2026-05-12
and 2026-05-18. Verified locally:

    pipx run ruff check --select=E9,F63,F7,F82 \
        --no-fix --exclude='*.gpr.py' .

on `gramps-project/addons-source:maintenance/gramps60` reports
"All checks passed!".

Other continue-on-error gates in this workflow (addon-structure,
compile-check, integration) each guard their own backlogs and stay
non-blocking until those clear separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ared

Drops continue-on-error: true (and the TODO comments) from both
unit-test jobs. They were guarding against "currently-broken addon
unit modules (import failures, stale API usage)" that have since been
fixed by the merged plugin-registration / dep-declaration round
(gramps-project#875 WordleGramplet, gramps-project#876 SourceReferences, gramps-project#878 EditExifMetadata,
gramps-project#879 MongoDB, gramps-project#880 PhotoTaggingGramplet, gramps-project#869 SurnameMappingGramplet,
gramps#2299 ClipboardGramplet) plus the lint backlog (gramps-project#843, gramps-project#847-gramps-project#869).

Verified by the most recent fork CI run on
eduralph/addons-source:maintenance/gramps60 (sha 8aabdd4, 2026-05-18):
both Unit Tests (Linux) and Unit Tests (Windows) reported success.

Also clears two stale comments that referenced the removed flag:
- unit-test-linux's "Run per-addon unit tests" step: drop the
  trailing "Falls silently under continue-on-error." sentence.
- integration's requires_mod-validate step: collapse the now-moot
  "this is the blocking copy" rationale to a plain pointer at
  unit-test-linux.

The lone remaining continue-on-error is on addon-structure (line 70),
gated by the po/template.pot backlog. PRs gramps-project#838-gramps-project#841 were closed
without merge; per Eduard's call the po/template.pot check stays
non-blocking for now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the seven hardcoded "gramps60" strings in ci.yml with values
computed by a new setup job that strips "maintenance/" off the ref and
validates the remainder matches grampsNN. Container jobs now pull
gramps-ci:<suffix> and the build step calls make.py with the matching
suffix, so the same workflow runs unchanged on maintenance/gramps60,
maintenance/gramps61, and any future maintenance/grampsNN branch.

A non-matching ref (master, topic branch, gramps100) fails fast in the
setup job rather than racing through with a malformed image tag.

Verified:
- YAML parses (python3 -c "import yaml; yaml.safe_load(...)")
- bash dry-run of the case statement accepts gramps60/61/62/42 and
  rejects master, feature/foo, maintenance/gramps60-test, gramps100
- container jobs (lint, compile-check, unit-test-linux,
  integration-test, build) all carry needs: setup; non-container jobs
  (addon-structure, unit-test-windows) do not

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the three hardcoded "gramps60" strings in docker-build.yml
with values computed from github.ref_name in a new "Compute branch
parameters" step. The image is tagged gramps-ci:<suffix> and built
with GRAMPS_SERIES=<series> (e.g. gramps60→6.0, gramps61→6.1), so the
same workflow produces the correct image on every maintenance branch.

Also drops the paths: filter from the push trigger. With the filter
the workflow only fired when .github/docker/** changed, which means
newly-created maintenance branches (inheriting the Dockerfile from
their parent unchanged) would never produce their gramps-ci:<suffix>
image, and ci.yml jobs would fail pulling a non-existent tag. Every
push to a maintenance branch now runs docker-build; buildx layer
cache turns the steady-state case into a ~20-30 s no-op rebuild.

Verified:
- YAML parses (python3 -c "import yaml; yaml.safe_load(...)")
- bash dry-run of the case/series logic produces 6.0, 6.1, 6.2, 7.0,
  4.2 for the corresponding maintenance/grampsNN refs and rejects
  master

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the hardcoded "gramps>=6.0,<6.1" pip pin with
"gramps==\${GRAMPS_SERIES}.*", and pulls GRAMPS_SERIES from a build
arg with no default. Same Dockerfile now builds gramps-ci:gramps60
when invoked with GRAMPS_SERIES=6.0, gramps-ci:gramps61 with 6.1, and
so on. docker-build.yml derives the value from the branch ref.

No default on GRAMPS_SERIES on purpose: a wrong default would silently
produce an image for the wrong Gramps series that "looks fine" but is
mismatched against the branch's addon code. A guard line fails the
build loudly if the arg is missing.

Verified:
- docker build --check --build-arg GRAMPS_SERIES=6.0 → no warnings
- a minimal repro Dockerfile invoking the guard without
  GRAMPS_SERIES exits 1 with "GRAMPS_SERIES is required (e.g. 6.0)"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…branches

PR 820's "pip install gramps==${SERIES}.*" only works on maintenance
branches whose Gramps release has been published to PyPI. The
maintenance/gramps61 branch carries the in-development 6.1 series and
has no PyPI release yet, so docker-build fails with "No matching
distribution found for gramps==6.1.*" the moment the branch-neutral
pipeline is exercised against it.

This commit adds a hybrid install path to the Dockerfile and the
matching wiring in docker-build.yml:

  - Dockerfile attempts pip install first. On "No matching distribution
    found for gramps==..." it falls back to a SHA-pinned git clone of
    gramps-project/gramps@maintenance/gramps${SERIES_NODOT} and
    "pip install ." from that working tree. Any other pip failure
    (network, 503, etc.) is fatal so a transient PyPI hiccup cannot
    silently flip a normally-released branch into "test against
    moving tip" mode.

  - docker-build.yml's params step captures the upstream branch's
    current HEAD SHA via git ls-remote and passes it in as
    GRAMPS_FALLBACK_SHA. The SHA participates in the buildx cache key
    so a moved upstream tip actually re-runs the install layer
    (without that, gramps61 CI would stay frozen on whichever revision
    was first baked into the image).

Trade-off — and worth flagging to addon contributors: a green CI on an
unreleased branch means "addons work with the current upstream tip,"
not "addons work with X.Y.0." The ::notice:: (PyPI path) vs ::warning::
(fallback path) log distinction in the Dockerfile makes the active
path visible in every docker-build run.

Verified locally:
- docker build --check --build-arg GRAMPS_SERIES=6.0 → clean
- docker build with GRAMPS_SERIES=6.0 + valid fallback SHA →
  ::notice::installed gramps==6.0.* from PyPI; image reports
  "Gramps 6.0.8" (released path unchanged)
- docker build with GRAMPS_SERIES=6.1 + upstream HEAD SHA → fallback
  fires; image reports "Gramps 6.1.0-beta1" (only obtainable from
  git clone, since no 6.1.* exists on PyPI)
- bash unit test of the failure-mode triage: missing SHA when PyPI
  lacks the version exits 1 with ::error::no gramps==... and
  GRAMPS_FALLBACK_SHA is unset; non-version pip error exits 1 with
  ::error::pip install gramps failed (non-version reason) and dumps
  the captured stderr

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two doc-only additions covering practical implications of the
branch-neutral CI from the preceding commits:

  - .github/workflows/ci.yml header: short NOTE for maintainers
    explaining that the first push to a newly-created maintenance
    branch will see container jobs fail at "Initialize containers"
    because the gramps-ci:<suffix> image is still being built by the
    companion docker-build.yml workflow on the same push. Solution
    is a one-time re-run after docker-build finishes. This is
    inherent to running both workflows on the same push event and is
    not a problem on subsequent pushes (the image already exists).

  - CONTRIBUTING.md "Work Towards a Merge": short paragraph for
    contributors explaining what a green CI check means on an
    unreleased maintenance branch. When PyPI lacks a release for the
    matching Gramps series, the CI image is built from a SHA-pinned
    snapshot of gramps-project/gramps@maintenance/grampsNN, so green
    means "addons work with the upstream branch tip at <sha>", not
    "addons work with the released X.Y.0". The exact SHA is logged
    as a ::warning:: line in the Build Docker Images output.

Verified:
- YAML still parses

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Operational document for the gramps-project/addons-source maintainer
covering the new steps introduced by PR 820 + the branch-neutral
follow-up:

  - One-time setup: make the gramps-ci GHCR package public (so fork
    PR contributors can pull the image), and expect the first-push
    race on maintenance/gramps60 immediately after merge.
  - Creating a new maintenance branch: a plain `git branch && git
    push` plus a one-time re-run of the failed CI jobs after the
    image is built.
  - When a Gramps minor release lands on PyPI: nothing to do (the
    hybrid Dockerfile auto-detects); optional workflow_dispatch to
    rebuild immediately.
  - Diagnostic log markers: ::notice:: / ::warning:: / ::error::
    annotations emitted from docker-build.yml and the Dockerfile,
    with their respective root causes.
  - Future-proofing knobs: upstream repo URL and GHCR tag retention,
    only mentioned in case they ever become relevant.

The ci.yml header NOTE about the first-push race now points to this
runbook for the full picture (GHCR visibility, log markers, etc.).

Verified:
- YAML parses
- All TOC anchors in the new file resolve to existing headers
- ../MAINTAINERS.md and ../CONTRIBUTING.md (relative links in the
  new file) both exist
- CONTRIBUTING.md#work-towards-a-merge anchor exists

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds tests/test_addon_dependencies.py, an independent detector for the
class of bug behind Mantis 13707 (WebConnect packs importing libwebconnect
without declaring depends_on=["libwebconnect"], so install fails when
libwebconnect is absent). Picked up automatically by the integration-test
job's `python3 -m unittest discover -s tests -p "test_*.py"` — no ci.yml
change.

Design

  - Independent of Gramps' loader. Does NOT import gramps.gen.plug —
    no PluginRegister, no BasePluginManager, no PluginData. Using
    Gramps' loader would test addons through the very dependency
    resolver whose leniency let 13707 ship, and would tie this test to
    Gramps' internal, unstable API.
  - Reads every *.gpr.py via an exec-shim of its own: a permissive
    globals dict (LOAD_GLOBAL → __missing__ → sentinel, so plugin-type
    constants like GRAMPLET / REPORT / TOOL never NameError) plus a
    fake register() that records each call's kwargs. Builds the
    addon-provided-module set and an id → {modules, depends_on,
    requires_mod, dir} map.
  - Isolated-load each plugin's registered module in a fresh
    subprocess with sys.path = [target_dir] + dep_dirs (declared
    depends_on ids resolved → their directories via the index). Uses
    `python3 -I`, cwd=/tmp, PYTHONPATH stripped, and strips "" from
    sys.path defensively — without those, PEP 420 implicit namespace
    packages let sibling addons import as empty packages and silently
    defeat the isolation. Gramps remains reachable from system
    site-packages, as intended: the isolation is addon-from-addon, not
    addon-from-Gramps.
  - Pins GI namespace versions Gramps itself pins (Gtk 3.0, PangoCairo
    1.0, etc.) before importing the addon. Not Gramps' loader — just
    matching the runtime conditions a plugin is loaded under, so
    addons whose top-level `from gi.repository import X` is
    version-sensitive don't generate false (c) failures from ambiguous
    GI defaults.
  - Classifies failures: (a) missing name is an addon-provided module
    not in this addon's depends_on — FINDING, fails the test;
    (b) missing name is in this addon's requires_mod — environment
    concern (PR 820's auto-derive owns it), ignored;
    (c) anything else — logged separately, not a finding. A given
    stderr may name multiple missing modules; (a) wins over (b) wins
    over (c) so the highest-signal finding is reported.

Limitation, stated in the module docstring: catches undeclared
dependencies that manifest at LOAD time (top-level imports). MISSES
lazily-imported deps — a sibling addon imported inside a function not
called at module load. No false positives, not exhaustive.

Verified

  - Synthetic positive/negative: with depends_on=["libwebconnect"]
    stripped from a USWebConnectPack copy, detector flags bucket (a)
    `libwebconnect`; with the declaration left in, rc=0 clean load.
    (Both checks live under /tmp/, not committed; they validate the
    classifier and isolation end-to-end.)
  - Full tree run in gramps-ci-local:gramps60-test (PR 820's CI image,
    Gramps 6.0.8): 187 plugins indexed, 159 pass, 0 (a), 7 (b)
    ignored, 21 (c) logged. Every WebConnect pack on this branch
    already declares depends_on=["libwebconnect"] — the 13707
    declaration is in effect on gramps60, so the test is green here.
    On gramps61 the situation may differ and the remediation round
    will be informed by re-running this detector there.

This commit is the detector + the regression check. It does NOT apply
any depends_on fixes — the WebConnect remediation is a separate later
round on maintenance/gramps61.

Issue #13707.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@eduralph eduralph changed the base branch from maintenance/gramps60 to review-base/upstream-gramps60 May 24, 2026 10:49
@eduralph eduralph marked this pull request as ready for review May 24, 2026 10:50
@eduralph eduralph merged commit 4b37de5 into review-base/upstream-gramps60 May 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant