Anton Vinogradov created IGNITE-28966:
-----------------------------------------

             Summary: Protected Classes CI check: classes in the 
org.apache.ignite.internal package are never detected
                 Key: IGNITE-28966
                 URL: https://issues.apache.org/jira/browse/IGNITE-28966
             Project: Ignite
          Issue Type: Task
            Reporter: Anton Vinogradov


The {{@Order}} annotation sets the number of a field inside a network message. 
Nodes of different versions read messages by these numbers, so a change here 
can break a rolling upgrade. The {{Protected Classes}} workflow 
({{.github/workflows/check-protected-classes.yml}}) exists to warn a reviewer 
about such changes.

The workflow decides that a changed file is protected by searching the whole 
file text for one string:

{code:js}
const ANNOTATION = 'org.apache.ignite.internal.Order';
...
return content.includes(ANNOTATION);
{code}

That string is the full class name of the annotation. A class that lives in the 
{{org.apache.ignite.internal}} package uses {{@Order}} without importing it, so 
the string never appears in the file and the file is never reported.

When nothing is found the script stops at {{if (hits.length === 0) return;}}, 
so the pull request gets no comment, no {{compatibility}} label and no 
{{Rolling upgrade compatibility}} check. Note that the job {{Rolling Upgrade 
check}} is green in both cases: it is green even when the check does find 
something. The real signal for a reviewer is the comment, the label and the 
separate check. When detection fails none of them appear, so "nothing was 
found" looks exactly like "nothing was checked". This is the case the job must 
catch.

h3. What the check does not see

13 message classes, all in 
{{modules/core/src/main/java/org/apache/ignite/internal/}}: {{ExchangeInfo}}, 
{{GridJobCancelRequest}}, {{GridJobExecuteRequest}}, 
{{GridJobExecuteResponse}}, {{GridJobSiblingsRequest}}, 
{{GridJobSiblingsResponse}}, {{GridTaskCancelRequest}}, 
{{GridTaskSessionRequest}}, {{GridTopicMessage}}, {{IgniteDiagnosticRequest}}, 
{{IgniteDiagnosticResponse}}, {{TxEntriesInfo}}, {{TxInfo}}.

h3. What the check reports by mistake

The same search fails the other way too, because it looks at the whole file 
text and not at the import line. 5 files name the class in javadoc, in a string 
constant, or import it to read it by reflection. None of them has a field with 
{{@Order(}}, and all of them are reported today:

* 
{{modules/nio/src/main/java/org/apache/ignite/plugin/extensions/communication/Message.java}}
 (javadoc)
* 
{{modules/codegen/src/main/java/org/apache/ignite/internal/CustomMapper.java}} 
(javadoc)
* 
{{modules/codegen/src/main/java/org/apache/ignite/internal/MessageProcessor.java}}
 ({{@SupportedAnnotationTypes}})
* 
{{modules/codegen/src/main/java/org/apache/ignite/internal/idto/IDTOSerializerGenerator.java}}
 ({{el.getAnnotation(Order.class)}})
* 
{{modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java}}
 ({{jarForClass(Order.class)}})

h3. How to reproduce

The workflow runs on {{pull_request_target}}. GitHub always takes the workflow 
file from the base branch, not from the pull request. So a pull request against 
apache/ignite cannot test its own change to this file: you will still see the 
old behaviour. Test the change in your own fork. Push it to the fork 
{{master}}, then open a pull request inside the fork that changes an {{@Order}} 
number in 
{{modules/core/src/main/java/org/apache/ignite/internal/GridJobExecuteRequest.java}}.

Live example: [#13445|https://github.com/apache/ignite/pull/13445] 
(IGNITE-28270) moves the {{@Order}} numbers of 12 fields in 
{{GridJobExecuteRequest}} from 13..24 to 14..25. The job {{Rolling Upgrade 
check}} passes in 5 seconds, and the pull request has no comment and no label.

h3. Fix

A file is protected when both rules hold:

# the file text contains {{@Order(}};
# the file has the line {{import org.apache.ignite.internal.Order;}} or the 
line {{package org.apache.ignite.internal;}}.

Rule 1 alone is not enough. {{org.apache.ignite.internal.systemview.Order}} is 
a different annotation, imported by 52 system view classes. They are not 
messages and must stay silent. They are silent today as well, because 
{{org.apache.ignite.internal.Order}} is not a substring of 
{{org.apache.ignite.internal.systemview.Order}}.

Rule 2 alone is not enough: that is today's bug.

No file in the {{org.apache.ignite.internal}} package imports the system view 
{{Order}}, so the two rules together are safe. Two details carry weight:

* The package test must be an exact match on {{package 
org.apache.ignite.internal;}}. A prefix match brings back the very classes the 
rule must skip: {{StatisticsColumnConfigurationView}}, 
{{StatisticsColumnGlobalDataView}}, {{StatisticsColumnLocalDataView}} and 
{{StatisticsColumnPartitionDataView}} sit in 
{{org.apache.ignite.internal.processors.query.stat.view}} and use the system 
view annotation.
* The open bracket in {{@Order(}} is required. {{Order.java}}, 
{{MessageMarshallerGenerator.java}} and {{MessageDeploymentGenerator.java}} sit 
in the same package and write {{@Order}} in javadoc only, never {{@Order(}}.

New noise: {{modules/core/src/test/resources/codegen/}} holds 72 {{.java}} test 
data files, and 41 of them match the new rule. The workflow filters on 
{{path.endsWith('.java')}} only, nothing excludes {{src/test/resources}}, so 
the new rule will start reporting them. This breaks nothing, but an explicit 
path exclusion is better.

Keep the safety rule of this job: it runs on {{pull_request_target}} with a 
write token, so the pull request files must stay data read through the API. Do 
not add {{actions/checkout}} of the pull request head, and do not run any pull 
request code.

h3. Second defect: an old warning is never cleared

When nothing is found the script returns before the code that deletes an 
earlier comment, so a pull request that stopped touching protected classes 
keeps a stale warning comment.

The {{compatibility}} label is worse. The script only calls {{addLabels}} and 
never calls {{removeLabel}}, on any path, so the label is never cleared at all. 
That part has to be written from scratch, moving the early return is not enough.

The same block also deletes and re-creates the comment instead of updating it, 
so every push to a pull request that keeps touching protected classes sends a 
new notification to everyone subscribed. {{issues.updateComment}} on the 
existing comment fixes that and the stale comment case at once.

Expected: when nothing is found, delete the old comment and remove the 
{{compatibility}} label.

h3. Third defect: a modified file is read only at the base revision

{code:js}
else if (f.status === 'removed' || f.status === 'modified' || f.status === 
'changed')
  revisions.push([f.filename, baseSha]);
{code}

A pull request that *adds* {{@Order}} to an existing class, that is, moves the 
class onto codegen serialization, is never reported, because the base revision 
has no annotation yet. Unlike the package blindness above, this hole also hides 
classes outside {{org.apache.ignite.internal}}. A modified file should be read 
at both revisions, the way {{renamed}} already is.

h3. Open question

The {{Rolling upgrade compatibility}} check is created only when something is 
found. So "checked and clean" looks the same as "the check saw nothing", and 
the next blind spot will look fine again. Consider creating the check on every 
run: {{success}} when nothing is found, {{neutral}} when something is found. If 
this is out of scope, please say so here.

h3. Acceptance criteria

* On the current master the new rule matches 566 files: 525 files plus 41 test 
data files under {{modules/core/src/test/resources/codegen/}}. The old rule 
matches 518. The difference is +53 (13 message classes and 40 test data files) 
and -5 (the false positives above).
* All 13 classes listed above are matched.
* The 5 files that only name the class in text are not matched.
* The 52 system view classes stay unmatched.
* A pull request that changes an {{@Order}} number in {{GridJobExecuteRequest}} 
gets the comment, the {{compatibility}} label and the {{Rolling upgrade 
compatibility}} check.
* A pull request that adds {{@Order}} to an existing class is reported too.
* A pull request that stopped touching protected classes loses the old comment 
and the {{compatibility}} label.
* The workflow still does not check out and does not run the pull request code.

h3. History and related work

The script was rewritten from shell and {{grep}} to {{actions/github-script}} 
in IGNITE-28913. The {{org.apache.ignite.internal.Order}} literal goes back to 
the first version in IGNITE-28018 and survived IGNITE-28822 and IGNITE-28821 
unchanged.

IGNITE-28716 is Resolved as Implemented, but its pull request 
[#13171|https://github.com/apache/ignite/pull/13171] is still open (created 
2026-05-22, never merged), and none of the files it lists are in master: 
{{scripts/check-protected-classes.sh}}, {{tests/check-protected-classes.bats}}, 
{{.github/workflows/test-workflow-scripts.yml}}, {{run-local-check.sh}}. That 
work moves the same detection into a shell script with BATS tests and a local 
runner, which is the local check this task needs. But it keeps the same 
{{org.apache.ignite.internal.Order}} literal, so it does not fix this defect, 
and it is written against the shape of the workflow before IGNITE-28822 and 
IGNITE-28913. Before you start, ask on the dev list whether #13171 will be 
merged. If yes, add the new rule and its test there. If no, fix the inline 
script in {{check-protected-classes.yml}}.

IGNITE-25489 asks for a wider check over all {{Message}} subclasses. This 
ticket only fixes the detection rule of the existing check.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to