Skip to content

6.38.0 — AGPLv3 licence, identity names per number, RF check digit on CSV import - #1710

Merged
rpgmem merged 19 commits into
mainfrom
develop
Oct 12, 2026
Merged

rpgmem merged 19 commits into
mainfrom
develop

Conversation

@rpgmem

@rpgmem rpgmem commented Oct 12, 2026

Copy link
Copy Markdown
Owner

Release 6.38.0

The licence moves to AGPLv3 or later. Identity resolution names each number in "One address, several people" and can consolidate a candidate imported twice. The recruitment CSV import honours the RF check digit, and a manage-only role reaches the Reregistration screen. Tests now cover error paths across the plugin, and the PHP coverage floor rises to 90%. CI jobs run with a read-only token. Issues: #1682, #1683, #1684, #1690, #1695, #1698, #1699, #1702, #1703.

Checklist

After merge (maintainer / agent)

  1. Maintainer pushes the tag with the guarded recipe in CLAUDE.md.
  2. Agent resets develop to main (--force-with-lease).
  3. Agent opens the SHA-backfill PR for the CHANGELOG heading.
  4. Agent lists any branch left behind; maintainer deletes it.

🤖 Generated with Claude Code

https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA


Generated by Claude Code

rpgmem and others added 19 commits October 12, 2026 01:04
…#1685)

* fix(identity): fold a person's duplicate candidacy when consolidating

Consolidating a mistyped CPF/RF refused when both numbers sat on
candidacies of the same account, because ffc_recruitment_candidate holds
each identifier once, and no screen could decide about the second
candidacy. The mistyped candidacy is now folded into the sound one:
classifications move by candidate_id only, staging rows are re-pointed,
the emptied row is deleted and the survivor gains only values it lacks.
A shared notice/adjutancy/list, a different PCD condition, a conflicting
other identifier or different accounts are still refused (#1682).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(identity): drop an identical, never-called duplicate classification when folding candidacies

A preliminary list promoted under the mistyped number and a definitive
list re-imported under the right one leave both candidacies with the same
standing on one notice, adjutancy and list. Where the pair is identical
(rank, score, time points, EMEBS, preview status and reason) and neither
side was ever called, the merged side's copy is dropped before the move;
any difference or any call still refuses. Re-measured at write time (#1682).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(identity): read candidacy-merge rows through ArrayValue for the level-9 row gate

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Co-authored-by: Claude <noreply@anthropic.com>
…1686)

* fix(recruitment): the CSV import honours the RF check digit setting

The import refused a mistyped CPF but never checked an RF, even with
Settings -> General -> RF check digit on, while every other write path
refused it through validate_rf(). The validator now calls validate_rf(),
which reads the setting: off keeps today's behaviour, on refuses the row
with recruitment_csv_rf_invalid and its line number (#1683).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* test(recruitment): use the standard bad-check-digit RF fixture

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Co-authored-by: Claude <noreply@anthropic.com>
Each per-number Move / Split group on the account tiers shows the names
recorded beside that number. IdentityRecordNames now also reads
ffc_submissions, decoding the encrypted answers first and the plaintext
copy second through SubmitterName, bounded to 20 rows per hash -- the
store where the shared-mailbox panel's rows live (#1684).


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

Co-authored-by: Claude <noreply@anthropic.com>
Adds a task issue form and an area dropdown on the bug and feature
forms, rewrites the PR template (stale since 6.7.3) and adds a release
template, and writes the rules around them into CLAUDE.md: an issue
before every PR, titles, labels, one merge at a time, closing issues by
hand and what happens to branches (#1688).


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

Co-authored-by: Claude <noreply@anthropic.com>
The agent's PR tool cannot set labels at creation, so they are applied
as the next step, before the PR is marked ready.


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

Co-authored-by: Claude <noreply@anthropic.com>
* chore(deps)(deps-dev): bump phpstan/phpstan from 2.2.16 to 2.3.1

Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan-phar-composer-source) from 2.2.16 to 2.3.1.
- [Commits](https://github.com/phpstan/phpstan-phar-composer-source/commits)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-version: 2.3.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* refactor(frontend): compute the asset suffix once in frontend_assets()

PHPStan 2.3.1 reports `$s ?? …` in the CSV-download branch as dead
(nullCoalesce.variable): $has_csv_download implies $has_base_assets, so $s
is always set there. Removing only the fallback makes 2.2.16 report $s as
possibly undefined, so the suffix is now computed once before the branches,
which both versions accept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Bumps [eslint](https://github.com/eslint/eslint) from 10.11.0 to 10.12.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](eslint/eslint@v10.11.0...v10.12.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 10.12.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Meusburger <rpgmem@gmail.com>
Bumps [jsdom](https://github.com/jsdom/jsdom) from 30.1.1 to 30.1.2.
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Commits](jsdom/jsdom@v30.1.1...v30.1.2)

---
updated-dependencies:
- dependency-name: jsdom
  dependency-version: 30.1.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Meusburger <rpgmem@gmail.com>
…larations together (#1696)

The repository had no LICENSE file, so GitHub showed no licence while the
plugin header, readme.txt, composer.json and README.md said GPLv3 or later,
and package.json declared none. LICENSE now carries the official AGPLv3 text
and every declaration says AGPLv3 or later. PluginIdentityTest fails when
any of them disagrees, as it already does for the version and the floors.

The vendored bundles in libs/js stay MIT.

Closes #1695


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

Co-authored-by: Claude <noreply@anthropic.com>
…paths (#1697)

* test(audience): cover audience, recruitment and reregistration error paths

Covers the logged-in audience calendar, the audience CSV import/export
handler tiers, the reregistration form inputs and admin views, the
recruitment schema migration chain, booking conflict queries, the
public recruitment listing filters, the "My Calls" section, the
reregistration AJAX, frontend and submission-action flows, and the
self-scheduling post-commit failure paths. Across the 18 target files
the uncovered statements fall from 1,119 to 63; what remains is the
ABSPATH guard, `exit` after redirects and two fopen() failures.

Refs #1690

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* docs(changelog): record the audience, recruitment and reregistration coverage

Refs #1690

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* docs(changelog): drop a doubled blank line

Refs #1690

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Co-authored-by: Claude <noreply@anthropic.com>
…ings error paths (#1700)

* test(certificates): cover certificate templates, form editor and settings error paths

Covers the refusal, not-found and failure branches of the certificate
template screen and receipt selector, the public operator-access metabox
statuses, the form editor save handler, the Date Messages screen and its
failed writes, third-party page-cache purges (isolated processes), QR temp
file failures, rate-limit gates and their log rows, settings maintenance
tools that throw, activity-log cursor and submission trail, the role
editor, the submissions export source and the captcha tab. Measured
against the develop clover, 754 of the 972 uncovered statements in the
18 targeted classes are now covered (CertTemplateAdminScreen 99->26,
PublicCsvDownloadMetabox 97->3, DateMessagesAdminPage 86->5, FormCache
83->2, CertTemplateReceiptSettings 70->3, FormEditorSaveHandler 68->5,
QRCodeGenerator 55->9, AdminUserCustomFields 49->3, RateLimitChecker
47->2, ActivityLogQuery 40->2, RoleCapabilityEditor 39->2,
SubmissionsExportSource 39->2, SettingsActionHandler 41->15, TabCaptcha
26->6).

Refs #1690

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* docs(changelog): record the certificate, form editor and settings coverage

Refs #1690

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Co-authored-by: Claude <noreply@anthropic.com>
…error paths (#1701)

* test(identity): cover identity resolution, migrations and encryption error paths

Drive the identity-resolution screen's nine POST handlers and its render
through their seams (capability refusals, nonce keys, outcomes, redirects),
and cover the refusal, empty-read, write-failure and absent-store paths of
the identity normalisation, key-rotation, email-rehash, CPF/RF split and
html-image migrations, the strategy calculator's lazy builds, the FK
migration, the datetime-to-unix helper, role registration, profile reads and
writes, user creation, legacy v1 decryption and the identity repair, relink,
agreement, search and conflict queries. Measured against the develop clover,
uncovered statements across the 19 target files drop from 1016 to 175
(841 newly covered); the largest gains are the resolution page (185 -> 9),
identity normalisation (116 -> 15) and the status calculator (107 -> 17).

Refs #1690

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* docs(changelog): record the identity, migration and encryption coverage

Refs #1690

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Co-authored-by: Claude <noreply@anthropic.com>
…y token (#1704)

ci.yml, lint.yml, phpcs.yml and assets.yml declared no permissions, so
their jobs ran with the repository's default GITHUB_TOKEN scope although
none writes anything; the only token use is the non-blocking Coveralls
upload, which passes it to identify the repository. All four now declare
contents: read.

Closes #1703


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

Co-authored-by: Claude <noreply@anthropic.com>
CI measured 93.64% line coverage after the error-path test pushes of
#1697, #1700 and #1701. The floor follows the rule now written in
CLAUDE.md and shared by the four plugins: the highest of 90%, the
measured figure minus 5 points, and its current value.

Refs #1690


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

Co-authored-by: Claude <noreply@anthropic.com>
The Reregistration menu and render_page() admitted only
ffc_view_reregistration, so a custom role holding
ffc_manage_reregistration alone got "Permission denied". The 3-state
model says a manage grant does not also need the view one: the page now
opens for view OR manage, and the menu is registered under whichever
tier the user holds, as Date Messages already does.

Closes #1698


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

Co-authored-by: Claude <noreply@anthropic.com>
… magic-link QR (#1707)

MagicLinkHelper::generate_magic_link() returns an empty string only for
an empty token, and generate_magic_link_qr() already returns early when
the submission has none, so the "Link generation failed" branch could
never run. No behaviour change for a submission with or without a token.

Closes #1702


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

Co-authored-by: Claude <noreply@anthropic.com>
Functions\expect( 'x' )->never() is never consulted when setUp() already
stubbed x with Functions\when(): the stub keeps answering, so the test
passed whether or not x ran. A direct call injected after each of the
twelve such sites left every test green; the same injection now fails
every one of them.

The sites count calls through a new Tests\Support\CallLog helper (a
second when() does replace the first) and assert the count after the
act. RestControllerTest had been hiding a call: the constructor registers
a filter through add_filter(), so its count now starts after the
constructor. ExpectAfterSetUpStubTest refuses the shape, and CLAUDE.md
records how to assert a call count when setUp() stubs the function.

Closes #1699


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

Co-authored-by: Claude <noreply@anthropic.com>
Bumps the three version sites to 6.38.0, renames [Unreleased] to
[6.38.0] (2026-10-12) with a condensed section that still cites every
delivering issue, and rewrites the Upgrade Notice for this release,
which carries the AGPLv3 licence change. The batch's @SInCE tags
already name 6.38.0, and the documentation topics for the two
operator-facing changes (#1683, #1684) were updated in their PRs.


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

Co-authored-by: Claude <noreply@anthropic.com>
@rpgmem
rpgmem marked this pull request as ready for review October 12, 2026 23:06
@rpgmem
rpgmem enabled auto-merge (squash) October 12, 2026 23:06
@rpgmem
rpgmem disabled auto-merge October 12, 2026 23:07
@rpgmem
rpgmem merged commit 529e80a into main Oct 12, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant