Skip to content

feat(release): keep a line's backport record and reports across batch pull requests - #5264

Merged
myasnikovdaniil merged 7 commits into
mainfrom
feat/backport-batch-line-record
Nov 11, 2026
Merged

myasnikovdaniil merged 7 commits into
mainfrom
feat/backport-batch-line-record

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

This PR keeps backport batch record of a line across batch pull requests. Before it lived only in body of open batch PR, so once that PR merged or closed it was gone, and a conflict deferred in merged batch showed up as missing in check and on cut. Three check --cut results also did not match the docs, all three are fixed here.

  • check --cut fails when workflow original is the only thing left waiting. Before it passed as soon as pick next to it got backport/skip or landed by hand.
  • run, check, fold and notify read line record from newest trusted batch PR in any state (opened by bot or by someone with write or admin access). If newest record is in doubt (author no longer trusted, bot batch without block, unreadable block, closed record newer than open batch) command stops and doesn't fall back to older one.
  • Record is written only while its PR is open. Merged and closed batch PRs are never edited or commented, so run that picks nothing with no open batch writes nothing and says so.
  • Every run makes report: each original merged to main since line branched in exactly one category, what the run read (heads, policy, tool commit, Actions run, flags) and each drop with its reason and merge commit. Latest report sits in body next to record and each one also goes to immutable comment. Report ID is hash of classification without PR number, so identical runs post one comment. backport-batch report --pr N [--id ID] reads it back, merged batches too, it needs main in local clone to check drops.
  • run --defer --push of last pick used to refuse with "close batch pull request instead". Now it writes report and record with reason while PR is open, then closes PR and deletes branch, both only while head is still the same. If close stops halfway next run --push finishes it, and it never deletes branch that moved or that new batch uses.
  • run --drop N --reason R keeps original off one line, --undrop withdraws it. Drop is stored in line record bound to merge commit and carried to next batches. Original the line already carries can't be dropped, and backport/include doesn't lift a drop.
  • Output names policy rules instead of internal numbers (no more "rule 101"), --push prints PR URL.

Reasons and file names from record are accepted only in the clean one-line form a run writes them, and everything printed is escaped.

Record version 2 reads unversioned blocks that open batches carry today. I ran new tool next to current one without --push on today's history for release-1.7 and release-1.6, every original got same category and check output is the same.

Downstream repositories

Release note

feat(release): backport batch keeps line record and report of every run across batch pull requests, check --cut fails on workflow change left waiting alone, deferred conflict stays deferred after its batch merges, --defer of last pick closes the batch, and new run --drop keeps a fix off one line.

Summary by CodeRabbit

  • New Features

    • Maintainers can permanently exclude a change from a release line with --drop, or undo that decision with --undrop.
    • Added a report command to view validated batch reports in JSON or a readable summary.
    • Deferring every remaining change can close an emptied batch. A later run --push can complete an interrupted close.
    • Batch reports now show change classifications, including deferred and dropped items.
  • Bug Fixes

    • Checks more clearly distinguish missing, deferred, skipped, and carried changes, with cut checks enforcing outstanding workflow requirements.
    • Batch operations refuse edited or untrusted records and avoid writing to closed pull requests.
  • Documentation

    • Updated command and release-process guidance for drops, reports, cut checks, and batch closure.

check --cut returned 0 whenever nothing was missing and nothing was
only on the batch, so a workflow original waiting for a maintainer
passed the cut once the pick beside it was labelled backport/skip or
reached the line by hand. The waiting original was not even printed:
the case that held it swallowed the warning the plain check gives.

check now sorts every original into one disposition and exits once,
so a wait fails the cut on its own. A backport/skip original the open
batch still carries is named with the run --defer that takes it out.

Assisted-By: LLM
Signed-off-by: Myasnikov Daniil <[email protected]>
fold, run --defer and the run's own stderr printed "rule 101" for an
original merged before its line started under the policy, and "rule
100" for backport/skip. Those numbers exist only inside the tool; the
policy has six rules, so a reader looking for rule 101 finds nothing.
Output now names such a rule in words, and the policy's own rules by
their number.

Assisted-By: LLM
Signed-off-by: Myasnikov Daniil <[email protected]>
…s it

run --push opened or updated the batch pull request and said nothing
about which one, so the scheduled job's log and a maintainer's
terminal had no link to follow. gh pr create prints the URL; the run
now passes it on, on stderr, so stdout stays the body alone, which the
scheduled script saves as it is. An update prints the open pull
request's URL.

Assisted-By: LLM
Signed-off-by: Myasnikov Daniil <[email protected]>
…t in any state

The deferrals a batch recorded were read only from the open batch pull
request. Once it merged, a conflict it had deferred showed as missing
and failed check and the cut, until some later run picked something
else and opened a new batch; a run that picked nothing opened none.

run, check, fold and notify now find the line's record in the newest
batch pull request whatever its state: one opened from a batch branch
of this repository by an author the batch trusts. A record that is in
doubt stops the command instead of letting it fall back to an older
one: a block by an author no longer trusted, a bot's batch whose block
was removed, a record that does not read whole, a closed record newer
than the open batch, or no record within the pages read.

The record is still written only to the open batch, and a merged or
closed batch is never written again, so a run that picks nothing with
no batch open keeps nothing and says so. The reader takes the version
2 record the next change writes; the writer stays at version 1.

Assisted-By: LLM
Signed-off-by: Myasnikov Daniil <[email protected]>
…rite record version 2

Each run rewrote the batch body whole, so the only account of what a
run found was gone at the next run, and a merged batch kept only its
last one. Nothing said what a classification was made of either: not
the heads it read, the policy, the tool or the flags.

A run now makes a report: every original merged into main since the
line branched, in exactly one category, with what the run read. Its
ID hashes the classification and names no pull request, so a new
batch's report is whole before GitHub numbers the pull request, and a
run that finds the same as the last one makes the same report, which
is posted once and keeps its first bytes. The body carries the latest
report under the record; a comment the tool never edits keeps each
one, so report reads any of them back, from merged batches too.

The comment goes up before the push and the body edit on an open
batch, and after the pull request a new batch opens with the report in
its body; a later run posts a report the body kept but no comment did,
byte for byte, before its own. Every write is preceded by a read of
the pull request, and nothing is written once it is merged or closed.
Both the comment and the body are rendered and measured before the
first write, so an oversized one stops the run with nothing written.

The record becomes version 2: what it was built from, with the run's
--no-workflow-files, which --defer keeps rather than resets, and the
report it goes with.

Assisted-By: LLM
Signed-off-by: Myasnikov Daniil <[email protected]>
…le it is open

docs/release/backport-tasks/failed-ci.md says to defer the culprit with
run --defer --push. When the culprit was all the batch carried, the
push stopped with "close batch pull request #N instead", and closing
it by hand left nothing that said why: once closed, the deferred
original was simply missing.

The run now closes the batch itself, and does every write while the
pull request is open: the report, then the body, whose record keeps
the deferrals and says the batch is closing, at the head the rebuild
started from. The close and the deletion of the branch go only while
GitHub and the remote still show that head, and the deletion is
leased on it.

A close that stops part way is finished by the next run --push, before
anything else, and by no other command; a head the close did not
freeze is refused, never adopted. The run that finishes it stops
there, and fails when it was asked for --defer, since no open batch
is left to defer on. Recovery never writes the closed
pull request. A same-day batch that reuses the branch name and fails
to open its pull request leaves a branch the next run names for a
person to delete.

Assisted-By: LLM
Signed-off-by: Myasnikov Daniil <[email protected]>
A fix the rules take for every line can still be wrong for one of
them, typically because it builds on code that line does not have.
The only ways to keep it out were backport/skip, which keeps it off
every line, and --defer, which holds it out of one batch: the next
batch tried it again, and a conflict that would never resolve came
back every run.

run --drop N --reason R records a drop in the line's record, bound to
the original's merge commit on main, and every later record carries
it, a new batch's included. What the open batch carries of a dropped
original comes off it the way --defer takes one off; any other drop
changes only the record, so a batch on a line that moved since stays
where it is. --undrop withdraws one. Both need an open batch, as every
write to the record does.

A drop comes after the rules and the carries: backport/skip, rules 2
to 4 or the line itself decide first and the drop stays on record,
and backport/include does not lift it. check notes a dropped original,
the body lists it, fold refuses it, and a record whose drop names
another original's merge stops every command. Each report keeps the
record's drops with the merge commit each is bound to and its reason,
held on read to the same rules, so an earlier report still says why
after an undrop.

A reason now travels from a body to terminals, bodies and comments, so
a reason or a file name in a record is believed only in the form a run
writes it, one clean line, and so is a report's diagnostic. Output
that repeats a reason, file names, a report ID or a state GitHub
reports escapes it.

Assisted-By: LLM
Signed-off-by: Myasnikov Daniil <[email protected]>
@github-actions github-actions Bot added area/release Issues or PRs related to release tooling (changelog, backport, release pipeline) kind/feature Categorizes issue or PR as related to a new feature size/XXL This PR changes 1000+ lines, ignoring generated files labels Nov 10, 2026
@coderabbitai

coderabbitai Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

Walkthrough

The backport-batch tool now stores versioned line records and run reports, supports persistent drop and undrop operations, and can close and resume batches safely. Checks and notifications use recorded state, and the documentation describes the updated commands and behavior.

Changes: Backport batch records and operations

Layer / File(s) Summary
Versioned line records and discovery
cmd/backport-batch/record.go, cmd/backport-batch/github.go, cmd/backport-batch/main.go, cmd/backport-batch/lines.go, cmd/backport-batch/record_test.go, cmd/backport-batch/fixture_test.go
The tool parses version 1 and version 2 line records, validates their contents, and discovers trusted records across recent pull requests. Batch preparation retains the policy and history inputs used to classify originals.
Drop, undrop, and reassembly
cmd/backport-batch/assemble.go, cmd/backport-batch/drop.go, cmd/backport-batch/defer.go, cmd/backport-batch/body.go, cmd/backport-batch/rules.go, cmd/backport-batch/drop_test.go, cmd/backport-batch/fold_test.go, docs/release/backports.md, docs/release/backport-tasks/resolve-conflict.md
run accepts --drop and --undrop, records drop reasons, and validates drop entries against original merges. Drop verdicts affect batch contents and are reflected in reports and fold results.
Report construction and publication
cmd/backport-batch/report.go, cmd/backport-batch/assemble.go, cmd/backport-batch/body.go, cmd/backport-batch/github.go, cmd/backport-batch/main.go, cmd/backport-batch/report_test.go, cmd/backport-batch/fixture_test.go, cmd/backport-batch/README.md, docs/release/backport-tasks/README.md
The tool builds canonically identified reports, validates report fields and drop bindings, retains reports in batch bodies, and publishes trusted comments. The report command prints a validated report as text or JSON. Body and report size limits are checked before writes.
Batch closure and recovery
cmd/backport-batch/assemble.go, cmd/backport-batch/github.go, cmd/backport-batch/defer_test.go, cmd/backport-batch/drop_test.go, cmd/backport-batch/fixture_test.go, docs/release/backport-tasks/failed-ci.md
When deferrals remove all picks, --push records a closing state and closes the pull request. It deletes the branch only at the recorded head. A later run --push can finish an interrupted close.
Check and notification dispositions
cmd/backport-batch/check.go, cmd/backport-batch/notify.go, cmd/backport-batch/check_test.go, cmd/backport-batch/cut_test.go, cmd/backport-batch/notify_test.go, cmd/backport-batch/workflows_test.go, docs/release/backport-mechanics.md, docs/release/backports.md
check reports missing, deferred, waiting, dropped, and stale originals with distinct outcomes. notify reads deferred entries from the line record and verifies the pull request remains open before writing.

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

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant run
  participant finish
  participant publish
  participant GitHub
  participant postReport
  run->>finish: build and validate report, body, and line record
  finish->>publish: pass publication
  publish->>GitHub: push branch and create or update pull request
  publish->>postReport: publish run report
  postReport->>GitHub: verify open state and create report comment
Loading

Possibly related PRs

  • cozystack/cozystack#5106: Introduces the backport-batch tool that this change extends with records, reports, drop operations, and batch closure.
  • cozystack/cozystack#5251: Changes rule 4 classification, whose outcomes are included in the reports added here.

Suggested labels: kind/feature, area/release, size/XXL

Merge Risk: Low · up to 2c1cb

The batch tool refuses to proceed when the newest batch record belongs to an author who is no longer trusted. This is documented, intentional behavior. In the rare case of a revoked maintainer or a changed bot login, the line stays blocked until an operator restores trust or edits the record. Owners should be aware of this before merging; no other concrete merge-blocking defect was established.

Pre-merge checks: Passed 5

Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly summarizes the main change: preserving backport records and reports across batch pull requests.
Docstring Coverage Passed Docstring coverage is 92.31% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentati…
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 92.31% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 91 functions across 27 files. (6 skipped: 6 unsupported.)

Finishing Touches

Generate docstrings

  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Additional comments: 1

cmd/backport-batch/record.go-411-417: 1

411-417: Major · Stability & Availability · Heavy lift

Unverified finding
Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.

Add a recovery path for an untrusted author on a merged or closed batch. Without one, the line can stay blocked with no way out.

findLineRecord returns an error when the newest record-bearing batch PR fails trusted, whatever its state. The trust check is made now, not when the PR was written. A merged or closed batch PR is never written again. So a PR that was trusted when it merged becomes a permanent refusal if its author loses trust later. Realistic triggers:

  • The maintainer who opened a batch by hand leaves the org or drops to read access. permission then returns read or none.
  • The author account is deleted. GitHub reports it as ghost.
  • BACKPORT_BATCH_BOT is changed to a new App login. Every earlier batch opened by cozystack-ci[bot] then fails trustedAuthor, because only the exact current login is trusted.

prepare calls findLineRecord for run, check and fold, and notify calls it too. So every command on the line exits 2. That includes the run --continue --push that would open a newer batch and replace the stale record. The line cannot recover by itself. A closed PR cannot be deleted. Editing the block out only moves the failure to the "bot's batch without a block" branch, or the search skips back to an older record. The only workarounds are to restore the former author's permission or to revert the bot setting.

Add an explicit escape for this case. One option keeps the strict refusal only for the open batch. For a merged or closed carrier, it trusts the PR if the author was trusted when the record was written, for example by checking the author against a configured list of former bot logins. Another option is an operator flag or environment variable that names merged or closed PR numbers to pass over, with a warning. Add a test for each case: a revoked author on a merged batch, and a renamed bot.

Review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 5e9c392 and 2c1cb2f.

Files selected for processing: 33

  • cmd/backport-batch/README.md
  • cmd/backport-batch/assemble.go
  • cmd/backport-batch/assemble_test.go
  • cmd/backport-batch/batchbot_test.go
  • cmd/backport-batch/body.go
  • cmd/backport-batch/body_test.go
  • cmd/backport-batch/check.go
  • cmd/backport-batch/check_test.go
  • cmd/backport-batch/cut_test.go
  • cmd/backport-batch/defer.go
  • cmd/backport-batch/defer_test.go
  • cmd/backport-batch/drop.go
  • cmd/backport-batch/drop_test.go
  • cmd/backport-batch/fixture_test.go
  • cmd/backport-batch/fold_test.go
  • cmd/backport-batch/github.go
  • cmd/backport-batch/history_test.go
  • cmd/backport-batch/lines.go
  • cmd/backport-batch/main.go
  • cmd/backport-batch/notify.go
  • cmd/backport-batch/notify_test.go
  • cmd/backport-batch/overrides_test.go
  • cmd/backport-batch/record.go
  • cmd/backport-batch/record_test.go
  • cmd/backport-batch/report.go
  • cmd/backport-batch/report_test.go
  • cmd/backport-batch/rules.go
  • cmd/backport-batch/workflows_test.go
  • docs/release/backport-mechanics.md
  • docs/release/backport-tasks/README.md
  • docs/release/backport-tasks/failed-ci.md
  • docs/release/backport-tasks/resolve-conflict.md
  • docs/release/backports.md

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict: LGTM

Reviewed the record-persistence core, the report subsystem, --drop/--undrop and the --defer-closes-batch flow, with an independent adversarial pass on top. No blocking findings: I could not ground a silent-drop, wrong-inclusion, forged-record-believed, duplicate/suppressed-report or wrong-branch-deletion. The package's own tests pass; note it exceeds go-test's default 600s timeout on a loaded machine, so it needs an explicit -timeout (a timeout, not a failure).

Checked and cleared: trust is the exact bot login or a live write/admin repo permission, author_association deliberately unused, and forks, other bots and permission-API errors all fail closed; v1/unversioned records are read while a malformed one exits 2 rather than parsing as empty; report IDs hash the classification minus id/time so identical runs post one comment; drops are bound to the original's merge commit and are inert for an already-carried original; the close-and-delete flow is leased on the frozen head at every step; untrusted titles/notes/reasons/filenames are escaped and the record/report are single-line JSON.

No API change, no new Helm lookups, no new GitHub App (the cozystack-ci App and its scheduled workflow are untouched).

Non-blocking:

  • One-time duplicate report comment at the new->continue boundary, since Mode is part of the hashed report ID. Steady-state scheduled runs stay idempotent.
  • The test suite is real-git-fixture heavy and slow enough to exceed the default go-test timeout; worth an explicit -timeout or lighter shared fixtures.
  • Scope: this bundles the record-persistence fix with three separable features (reports, --drop/--undrop, --defer-closes-batch). Splitting them would make each easier to review. Not blocking this one.

@myasnikovdaniil
myasnikovdaniil merged commit c6cba19 into main Nov 11, 2026
68 checks passed
@myasnikovdaniil
myasnikovdaniil deleted the feat/backport-batch-line-record branch November 11, 2026 07:12
@myasnikovdaniil
myasnikovdaniil restored the feat/backport-batch-line-record branch November 11, 2026 07:14
@myasnikovdaniil
myasnikovdaniil deleted the feat/backport-batch-line-record branch November 11, 2026 07:15
myasnikovdaniil added a commit that referenced this pull request Nov 12, 2026
## What this PR does

Backports to `release-1.7` from `main`, picked by the rules of the
[backport
policy](https://github.com/cozystack/cozystack/blob/main/docs/release/backports.md)
in merge order. A merged pull request comes as its own commits, picked
with `git cherry-pick -x` so each keeps its author, message and
sign-offs, and a merge commit that carries the original's merge message
with a `Backported-from:` trailer naming it; a squash-merged one comes
as one pick. `backport-batch verify` checks that shape. A pick listed as
changed differs from the commit it came from; `git range-diff
<merge>^1..<merge>^2 <batch merge>^1..<batch merge>^2` shows how. A
conflict or a failing check here is a [backport
task](https://github.com/cozystack/cozystack/blob/main/docs/release/backport-tasks/README.md).

- [#5284](#5284)
`fix(seaweedfs): bump the COSI driver to v0.4.0 for the lost-grant fix`,
1 commit

### Maintainer overrides

- [#5083](#5083)
`fix(workloadmonitor): stop doubling the bucket- prefix in bucket
Workload names`: `backport/skip`, left out
- [#4973](#4973)
`fix(kubernetes-nodes): refuse a partial resources spec on every pool`:
`backport/skip`, left out
- [#5180](#5180) `fix(tests):
keep two unit tests green after the release image restamp`:
`backport/skip`, left out

### Skipped

- Rule 1, already on `release-1.7`: 46
- Rule 2, the title type is not `fix` or `security`, or the scope is
`deps`: 31
- Rule 3, a breaking change: 5
- Rule 4, touches platform migrations, `targetVersion` or a CRD, or
makes a sizeable API change: 2

<details><summary>Rule 1, already on `release-1.7`</summary>

- [#4815](#4815)
`fix(vm-disk): name the importer error in the disk's WorkloadsReady
message`, `its 2 commits picked, the last in 4323363`
- [#4756](#4756)
`fix(backupstrategy-controller): run backup clients on a package-owned
image`, `its 2 commits picked, the last in 4643366`
- [#4293](#4293)
`fix(fluxcd): keep Helm uninstall working during flux-plunger recovery`,
`its 1 commit picked, the last in 0210407`
- [#4791](#4791)
`fix(kubernetes): keep NFS publish from hanging on a dead server`, `its
1 commit picked, the last in fa88bd1`
- [#4830](#4830)
`fix(multus): drop the CPU limit and make resources configurable`, `its
2 commits picked, the last in 363c842`
- [#4865](#4865) `fix(kafka):
pass the container limits to the Kafka JVMs`, `its 3 commits picked, the
last in fc38a54`
- [#4866](#4866)
`fix(linstor): mark volumes restored from a snapshot DRBD-initialized`,
`its 1 commit picked, the last in f9550b7`
- [#4867](#4867) `fix(e2e):
keep chainsaw asserts polling through transient API errors`, `its 1
commit picked, the last in df75adc`
- [#4819](#4819)
`fix(seaweedfs): build SeaweedFS in-tree with a bounded S3 read-ahead`,
`its 5 commits picked, the last in 7f4c346`
- [#3427](#3427)
`fix(backups): prepare standalone VMDisk restore targets and add VM
backup/restore e2e round-trips`, `its 3 commits picked, the last in
4ac0cd5`
- [#4889](#4889)
`fix(monitoring): send alerts to every Alertmanager replica`, `its 1
commit picked, the last in 5033d53`
- [#4929](#4929)
`fix(backups): gate every default Strategy CR on its CRD`, `its 2
commits picked, the last in dee1eef`
- [#4914](#4914)
`fix(fluxcd): close the flux-plunger review follow-ups for the hook,
drain and Warnings`, `its 7 commits picked, the last in a8e4916`
- [#4888](#4888) `fix(ci):
dispatch frozen-line builds with GITHUB_TOKEN`, `its 1 commit picked,
the last in bf81e2d`
- [#4939](#4939)
`fix(cluster-api): resync capi-operator every minute`, `its 1 commit
picked, the last in cc8d6a4`
- [#5027](#5027) `fix(hack):
keep the backup stack in the vminstance selective install`, `its 1
commit picked, the last in 8ba8386`
- [#4955](#4955)
`fix(vm-disk): let CDI adopt the PVC a Velero restore recreates`, `its 2
commits picked, the last in 691d7bc`
- [#4985](#4985) `fix(etcd):
stop counting canceled watch streams as failed gRPC requests`, `its 1
commit picked, the last in 38e4089`
- [#5005](#5005)
`fix(tcp-balancer): cap HAProxy fds and threads by the pod's own
limits`, `its 1 commit picked, the last in 7a3d0b0`
- [#5071](#5071)
`fix(marketplace): do not install a connected repository's components on
connect`, `its 2 commits picked, the last in 25f8f11`
- [#5086](#5086)
`fix(tenant): stop the gateway release waiting on TenantGateway
listeners and certificates`, `its 1 commit picked, the last in
18b2249`
- [#5087](#5087)
`fix(controller): carry healthCheckExprs and waitStrategy to existing
releases`, `its 1 commit picked, the last in d2a452e`
- [#5088](#5088)
`fix(ingress-nginx): drop the admission-webhook policy a rebase brought
back`, `its 1 commit picked, the last in cf00b41`
- [#5142](#5142)
`fix(release): keep release bodies within GitHub's size limit`, `its 1
commit picked, the last in c740515`
- [#4917](#4917)
`fix(kubernetes): run Proxmox CCM v0.16.1 and pin the Proxmox images by
digest`, `its 2 commits picked, the last in 8ff6dca`
- [#4974](#4974)
`fix(kubernetes-nodes): plan autoscaler capacity on the cores the VM
actually gets`, `its 1 commit picked, the last in d68a163`
- [#4971](#4971) `fix(e2e):
keep the Proxmox tenant kubeconfig per run and remove it`, `its 2
commits picked, the last in 77227c5`
- [#4970](#4970) `fix(e2e):
read the KubernetesNodes pool release in the HelmRelease guard`, `its 3
commits picked, the last in 6b17a76`
- [#4972](#4972) `fix(e2e):
validate the leak allowlist against every suite layout`, `its 3 commits
picked, the last in 7ec550b`
- [#5082](#5082)
`fix(marketplace): pin a tapped repository to a digest instead of a
mutable tag`, `its 15 commits picked, the last in 9cff86f`
- [#4004](#4004)
`fix(dashboard): link a tenant row to its detail page`, `its 2 commits
picked, the last in bd5e0b5`
- [#5212](#5212)
`fix(cilium): pass the CiliumNode list to jq on stdin in the IPAM hook`,
`its 1 commit picked, the last in b28a2e2`
- [#5213](#5213)
`fix(kubernetes): move the OIDC users out of the bootstrap Job's
script`, `its 1 commit picked, the last in 02dd8e5`
- [#5208](#5208)
`fix(clickhouse): restore the archive a Backup was taken as`, `its 3
commits picked, the last in 9ded5c5`
- [#5230](#5230)
`fix(backups): re-render the default backup objects when the S3 endpoint
changes`, `its 8 commits picked, the last in 490eab5`
- [#5133](#5133)
`fix(securitygroups): project a membership-only group to a policy Cilium
accepts`, `its 1 commit picked, the last in b7c50a5`
- [#5091](#5091)
`fix(postgres): render the declared databases in a stable order`, `its 1
commit picked, the last in b658dff`
- [#5123](#5123)
`fix(mariadb-operator): derive the cluster name from the platform
domain`, `its 2 commits picked, the last in 17702dd`
- [#5151](#5151) `fix(redis):
run one Sentinel when Redis runs a single pod`, `its 5 commits picked,
the last in 5524a41`
- [#5124](#5124)
`fix(lineage, kubernetes): resolve CAPK node VMs to their cluster`, `its
2 commits picked, the last in c908560`
- [#5184](#5184) `fix(api):
read pool usage under the quota key the controller counts`, `its 1
commit picked, the last in 18c17af`
- [#5190](#5190)
`fix(platform): remove the dead registries value and patch-containerd
Secret`, `its 2 commits picked, the last in 617f889`
- [#5211](#5211)
`fix(postgres): fail a backup job as soon as its cnpg Backup fails`,
`its 1 commit picked, the last in 6645e21`
- [#5155](#5155)
`fix(postgres): refuse system databases and never drop them`, `its 1
commit picked, the last in 6f1bb80`
- [#5207](#5207)
`fix(mariadb): fail backup and restore jobs whose operator Job failed`,
`its 5 commits picked, the last in ef3de55`
- [#5024](#5024)
`fix(kubernetes, monitoring): send tenant cluster metrics to the
tenant's vminsert`, `its 1 commit picked, the last in 423a75b`

</details>

<details><summary>Rule 2, the title type is not `fix` or `security`, or
the scope is `deps`</summary>

- [#4845](#4845)
`docs(governance): welcome new contributors and link LF sanctions
guidance`, `type docs`
- [#4881](#4881)
`feat(bucket): add a ui flag to skip the s3manager web UI`, `type feat`
- [#4899](#4899) `chore: link
the FOSSA license scan for CLOMonitor`, `type chore`
- [#4898](#4898)
`feat(backups): back up Valkey with the Redis strategy`, `type feat`
- [#4940](#4940)
`docs(release): add changelog for v1.6.5`, `type docs`
- [#4861](#4861)
`feat(release): sign the release assets and attach a source SBOM`, `type
feat`
- [#3780](#3780) `docs: move
the operator guides out of docs/ to where readers find them`, `type
docs`
- [#4936](#4936)
`docs(release): check changelog claims against the code`, `type docs`
- [#4959](#4959)
`docs(release): point changelog check B at the Talos installer profile`,
`type docs`
- [#3777](#3777)
`docs(agents): define where a new document belongs`, `type docs`
- [#5025](#5025)
`test(harbor): pin the resources of the nginx proxy and the portal`,
`type test`
- [#5039](#5039)
`feat(keycloak): load additional CA files from Secret or ConfigMap
keys`, `type feat`
- [#4860](#4860)
`feat(kubernetes-nodes): trust a factory CA in the Proxmox template
script`, `type feat`
- [#5090](#5090) `ci(e2e):
end the Proxmox lane's SSH key with a newline`, `type ci`
- [#4969](#4969)
`test(kubernetes): pin RetryOnFailure on the Proxmox csi release`, `type
test`
- [#5000](#5000)
`test(kubernetes): name the Proxmox fixture's node pool
<cluster>-<pool>`, `type test`
- [#5168](#5168)
`docs(agents): steer agents away from Helm lookup`, `type docs`
- [#4997](#4997)
`feat(release): attach an SBOM to every release image`, `type feat`
- [#3440](#3440)
`ci(api-review-gate): publish the gate as a commit status`, `type ci`
- [#5034](#5034) `docs: move
the repo-only guides onto the website`, `type docs`
- [#5215](#5215)
`feat(monitoring-agents): export PVC labels from kube-state-metrics`,
`type feat`
- [#5217](#5217)
`feat(monitoring-agents): keep application identity in container log
streams`, `type feat`
- [#5171](#5171) `feat(api):
expose HelmRelease failure state on Application status`, `type feat`
- [#5237](#5237)
`feat(operator): let the platform source trust a private registry CA`,
`type feat`
- [#5106](#5106)
`feat(release): make backports automatic with backport-batch`, `type
feat`
- [#4983](#4983) `feat(api):
add the reservation evaluator for tenant quotas`, `type feat`
- [#5264](#5264)
`feat(release): keep a line's backport record and reports across batch
pull requests`, `type feat`
- [#5260](#5260) `ci(e2e):
skip final cleanup on disposable PR runners`, `type ci`
- [#5046](#5046)
`feat(backups): record the RDB snapshot location and size for Redis
backups`, `type feat`
- [#5214](#5214)
`feat(observability-proxy): serve tenant namespace logs through an
authorizing proxy`, `type feat`
- [#5282](#5282)
`test(release): isolate report deduplication from workflow attempts`,
`type test`

</details>

<details><summary>Rule 3, a breaking change</summary>

- [#4790](#4790)
`fix(platform)!: refuse a networking variant change on a live cluster`,
`! in the title`
- [#4796](#4796)
`fix(vm-disk)!: refuse a golden-image clone CDI would leave unbound`, `!
in the title`
- [#5085](#5085)
`fix(kubernetes, opensearch, foundationdb)!: restrict managed
application images`, `! in the title`
- [#4818](#4818)
`fix(keycloak)!: make the KMS proxy keep its keys across restarts`, `!
in the title`
- [#5167](#5167)
`fix(monitoring-agents)!: isolate merged log payload under
log_processed`, `! in the title`

</details>

<details><summary>Rule 4, touches platform migrations, `targetVersion`
or a CRD, or makes a sizeable API change</summary>

- [#4941](#4941)
`fix(backups): keep the backupstrategy-controller Helm release under the
Secret size limit`, `a sizeable API change (breaking-change: [v1alpha1]
spec.spec.template: field "metadata" was removed, and 5 more), a CRD`
- [#5223](#5223)
`fix(backups): report sizeBytes for MariaDB and CNPG backup artifacts`,
`a CRD`

</details>

```backport-batch
{"version":2,"line":"release-1.7","built":{"main":"76a34d3e53a14c39d8df63eec6271c2eaae5ffea","line":"3cd9d4c220b519972677c7ff756ae6c4db07ef4a","newer":"","policy":"76a34d3e53a14c39d8df63eec6271c2eaae5ffea","batch":"e27d6630a0008381f0bc105a87d117dfcc29680f","tool":"76a34d3e53a14c39d8df63eec6271c2eaae5ffea","run":"https://github.com/cozystack/cozystack/actions/runs/43209950053","at":"2026-11-12T11:33:16Z","options":{"noWorkflowFiles":true,"mode":"new"}},"report":"9ca0285a0bb30a09736f6a430878b3ce0d32a2811639861e80daa549f208d7b3","closing":null,"deferred":[],"dropped":[],"stale":[]}
```

```backport-batch-report
{"format":1,"id":"9ca0285a0bb30a09736f6a430878b3ce0d32a2811639861e80daa549f208d7b3","line":"release-1.7","built":{"main":"76a34d3e53a14c39d8df63eec6271c2eaae5ffea","line":"3cd9d4c220b519972677c7ff756ae6c4db07ef4a","newer":"","policy":"76a34d3e53a14c39d8df63eec6271c2eaae5ffea","batch":"e27d6630a0008381f0bc105a87d117dfcc29680f","tool":"76a34d3e53a14c39d8df63eec6271c2eaae5ffea","run":"https://github.com/cozystack/cozystack/actions/runs/43209950053","at":"2026-11-12T11:33:16Z","options":{"noWorkflowFiles":true,"mode":"new"}},"overrides":{"backport/include":[],"backport/skip":[4973,5083,5180]},"primary":{"picked":[5284],"missing":[],"deferred":[],"parked":[],"skipped":{"on-line":[3427,4004,4293,4756,4791,4815,4819,4830,4865,4866,4867,4888,4889,4914,4917,4929,4939,4955,4970,4971,4972,4974,4985,5005,5024,5027,5071,5082,5086,5087,5088,5091,5123,5124,5133,5142,5151,5155,5184,5190,5207,5208,5211,5212,5213,5230],"before-start":[],"type":[3440,3777,3780,4845,4860,4861,4881,4898,4899,4936,4940,4959,4969,4983,4997,5000,5025,5034,5039,5046,5090,5106,5168,5171,5214,5215,5217,5237,5260,5264,5282],"breaking":[4790,4796,4818,5085,5167],"touches":[4941,5223],"newer":[],"skip-label":[4973,5083,5180],"dropped":[]}},"overlays":{"dropped":[],"stale":[],"changed":[]},"diag":{},"other":[]}
```

### Screenshots

See the original pull requests.

### Downstream repositories

- [ ] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
fix(seaweedfs): bucket users granted together could get S3 keys that never work (403 InvalidAccessKeyId). COSI driver is bumped to v0.4.0, which writes each grant to per-identity IAM file.
```
myasnikovdaniil added a commit that referenced this pull request Nov 13, 2026
…n code their line lacks (#5269)

## What this PR does

Stacked on #5264, that one merges first.

Clean cherry-pick doesn't mean the original landed. On release-1.6 the
batch carried a fix whose two deletions hit files the line never had, so
only its test arrived. Other originals conflicted on every run because
they change code that never reaches the line. Batch now tells these
cases apart instead of picking half a fix or retrying a conflict nobody
can resolve.

- Every file an original modifies or deletes is checked at its
counterpart on the line, renames followed on both sides. If part of the
original has no effect there (change to a file the line lacks, deletion
of a file it never had, deleted file the unit keeps), the original is
parked as partial and its release note stays out. A file that already
matches what the original makes counts as there (same blob, binary too,
or clean `git merge-file`). This runs in the picker, `fold`, `verify`
and over units already on the open batch, so such a unit fails `check
--cut` whatever its label until `run --defer` takes it out.
- Pick that differs from its original keeps its release note under
"Check these notes" with a `git range-diff` command to compare, record
and report mark it `changed`.
- Run names what an original builds on that the line lacks: the commit
on `main` that added a file the fix modifies or deletes, and blame of
removed lines whose text is still missing on the line. Generated files
and generated keys of ApplicationDefinitions are skipped. Lines a fix
only adds are not checked, a dependency that lives only there still
needs `--drop`.
- If a prerequisite never reaches the line (`feat`, direct push,
excluded or itself parked) the original is parked and not tried. If it
is still to come, the original waits for it and is retried later.
Prerequisites the line or open batch already carry, or this run picked,
count as there. `run`, `check` and record-only commands decide in the
same order. On scheduled run a workflow original whose only missing
dependency is in tests or docs waits for a maintainer instead of
parking. `backport/include` lifts neither park, wait nor partial.
- `notify` announces each park once on the open batch and marks it in
the record, nothing is announced while no batch is open.

Record moves to version 3 and report to format 2, format 1 reports still
read. I ran the new tool next to the previous one without `--push` on
today's history. On release-1.7 nothing changes category and `check`
prints the same. On release-1.6 #5071 is parked on the two features it
builds on, #5088 already on the open batch now fails the cut as a
partial unit, and #5142 on scheduled run still waits for a maintainer
with a note that only its tests depend on what 1.6 lacks. Rule 6 holds
twelve originals on 1.6 right now, run over them directly flags the
seven that change code 1.6 never got and none of the other five.
Detection adds under 2 s per line.

After this merges, CI on the open 1.6 batch goes red at its next push
since `verify` refuses #5088 there. `run --line release-1.6 --defer 5088
--reason ... --push` takes it out.

### Downstream repositories

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
feat(release): backport batch holds back a pick that applies only part of its original and fails the cut on such unit already on a batch, sets apart the note of a changed pick, parks an original that builds on code its line never gets, waits for one whose prerequisite is still to come, and announces each park once.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **New Features**
* Backport batches now identify partial picks and missing prerequisites,
and can park affected originals or leave them waiting.
* Batch reports and notifications provide details about parked, partial,
changed, and waiting picks.
* Release notes for picks that differ from their originals are set apart
for review.
* **Bug Fixes**
* Verification, folding, and batch checks reject picks that omit effects
from the original; renamed paths are supported.
* **Documentation**
* Updated backport guidance to explain parking, prerequisites, partial
picks, and release-note checks.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/release Issues or PRs related to release tooling (changelog, backport, release pipeline) kind/feature Categorizes issue or PR as related to a new feature size/XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants