This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/sbp by this push:
     new 7815a1c  Add a form for admins to run checks again without using the 
cache
7815a1c is described below

commit 7815a1c9cb08c2620a6d4caeb473204523659a17
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Jan 29 20:32:25 2026 +0000

    Add a form for admins to run checks again without using the cache
---
 atr/models/sql.py                               |  2 ++
 atr/post/draft.py                               | 28 +++++++++++++++++++++
 atr/shared/web.py                               | 12 +++++++--
 atr/storage/writers/revision.py                 |  2 ++
 atr/tasks/checks/__init__.py                    | 19 ++++++++++++++
 atr/templates/check-selected.html               |  6 ++++-
 atr/templates/report-selected-path.html         | 30 +++++++++++++++++-----
 migrations/versions/0044_2026.01.29_24d82084.py | 33 +++++++++++++++++++++++++
 8 files changed, 123 insertions(+), 9 deletions(-)

diff --git a/atr/models/sql.py b/atr/models/sql.py
index 0b8d8a4..e178e2f 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -936,6 +936,7 @@ class CheckResult(sqlmodel.SQLModel, table=True):
         sa_column=sqlalchemy.Column(sqlalchemy.JSON), **example({"expected": 
"...", "found": "..."})
     )
     input_hash: str | None = sqlmodel.Field(default=None, index=True, 
**example("blake3:7f83b1657ff1fc..."))
+    cached: bool = sqlmodel.Field(default=False, **example(False))
 
 
 class CheckResultIgnore(sqlmodel.SQLModel, table=True):
@@ -1180,6 +1181,7 @@ class Revision(sqlmodel.SQLModel, table=True):
 
     description: str | None = sqlmodel.Field(default=None, **example("This is 
a description"))
     tag: str | None = sqlmodel.Field(default=None, **example("rc1"))
+    use_check_cache: bool = sqlmodel.Field(default=True, **example(True))
 
     def model_post_init(self, _context):
         if isinstance(self.created, str):
diff --git a/atr/post/draft.py b/atr/post/draft.py
index c7fd259..3ca61bc 100644
--- a/atr/post/draft.py
+++ b/atr/post/draft.py
@@ -146,6 +146,34 @@ async def hashgen(session: web.Committer, project_name: 
str, version_name: str,
     )
 
 
[email protected]("/draft/recheck/<project_name>/<version_name>")
[email protected]()
+async def recheck(session: web.Committer, project_name: str, version_name: 
str) -> web.WerkzeugResponse:
+    """Start a new draft revision to rerun all checks without using caches."""
+    await session.check_access(project_name)
+    if not session.is_admin:
+        raise base.ASFQuartException("Admin access required", errorcode=403)
+
+    description = "Empty revision to restart all checks without cache for the 
whole release candidate draft"
+    async with storage.write(session) as write:
+        wacp = await write.as_project_committee_participant(project_name)
+        async with wacp.revision.create_and_manage(
+            project_name,
+            version_name,
+            session.uid,
+            description=description,
+            use_check_cache=False,
+        ) as _creating:
+            pass
+
+    return await session.redirect(
+        get.compose.selected,
+        project_name=project_name,
+        version_name=version_name,
+        success="All checks restarted without cache",
+    )
+
+
 
@post.committer("/draft/sbomgen/<project_name>/<version_name>/<path:file_path>")
 @post.empty()
 async def sbomgen(session: web.Committer, project_name: str, version_name: 
str, file_path: str) -> web.WerkzeugResponse:
diff --git a/atr/shared/web.py b/atr/shared/web.py
index 66c4bc0..a63190f 100644
--- a/atr/shared/web.py
+++ b/atr/shared/web.py
@@ -112,12 +112,19 @@ async def check(
             ),
         )
 
-    empty_form = form.render(
+    fresh_form = form.render(
         model_cls=form.Empty,
         action=util.as_url(post.draft.fresh, 
project_name=release.project.name, version_name=release.version),
         submit_label="Restart all checks",
         submit_classes="btn btn-primary",
     )
+    recheck_form = form.render(
+        model_cls=form.Empty,
+        action=util.as_url(post.draft.recheck, 
project_name=release.project.name, version_name=release.version),
+        submit_label="Recheck all without cache",
+        submit_classes="btn btn-outline-secondary",
+        # confirm="Restart all checks without using cached results? This 
creates a new revision.",
+    )
 
     vote_task_warnings = _warnings_from_vote_result(vote_task)
     has_files = await util.has_files(release)
@@ -152,7 +159,8 @@ async def check(
         vote_task=vote_task,
         archive_url=archive_url,
         vote_task_warnings=vote_task_warnings,
-        empty_form=empty_form,
+        fresh_form=fresh_form,
+        recheck_form=recheck_form,
         csrf_input=str(form.csrf_input()),
         resolve_form=resolve_form,
         has_files=has_files,
diff --git a/atr/storage/writers/revision.py b/atr/storage/writers/revision.py
index dc08d43..0bc1f11 100644
--- a/atr/storage/writers/revision.py
+++ b/atr/storage/writers/revision.py
@@ -111,6 +111,7 @@ class CommitteeParticipant(FoundationCommitter):
         version_name: str,
         asf_uid: str,
         description: str | None = None,
+        use_check_cache: bool = True,
     ) -> AsyncGenerator[types.Creating]:
         """Manage the creation and symlinking of a mutable release revision."""
         # Get the release
@@ -177,6 +178,7 @@ class CommitteeParticipant(FoundationCommitter):
                     created=datetime.datetime.now(datetime.UTC),
                     phase=release.phase,
                     description=description,
+                    use_check_cache=use_check_cache,
                 )
 
                 # Acquire the write lock and add the row
diff --git a/atr/tasks/checks/__init__.py b/atr/tasks/checks/__init__.py
index 9f3d5c5..a83e8f9 100644
--- a/atr/tasks/checks/__init__.py
+++ b/atr/tasks/checks/__init__.py
@@ -65,6 +65,7 @@ class Recorder:
     afresh: bool
     __cached: bool
     __input_hash: str | None
+    __use_check_cache: bool | None
 
     def __init__(
         self,
@@ -86,6 +87,7 @@ class Recorder:
         self.member_problems: dict[sql.CheckResultStatus, int] = {}
         self.__cached = False
         self.__input_hash = None
+        self.__use_check_cache = None
 
         self.project_name = project_name
         self.version_name = version_name
@@ -139,6 +141,7 @@ class Recorder:
             status=status,
             message=message,
             data=data,
+            cached=False,
             input_hash=self.__input_hash,
         )
 
@@ -208,6 +211,9 @@ class Recorder:
         if config.get().DISABLE_CHECK_CACHE:
             return False
 
+        if not await self.use_check_cache():
+            return False
+
         no_cache_file = self.abs_path_base() / ".atr-no-cache"
         if await aiofiles.os.path.exists(no_cache_file):
             return False
@@ -245,6 +251,7 @@ class Recorder:
                     status=cached.status,
                     message=cached.message,
                     data=cached.data,
+                    cached=True,
                     input_hash=self.__input_hash,
                 )
                 data.add(new_result)
@@ -302,6 +309,18 @@ class Recorder:
             member_rel_path=member_rel_path,
         )
 
+    async def use_check_cache(self) -> bool:
+        if self.__use_check_cache is not None:
+            return self.__use_check_cache
+
+        async with db.session() as data:
+            revision = await data.revision(release_name=self.release_name, 
number=self.revision_number).get()
+        if revision is None:
+            self.__use_check_cache = True
+            return True
+        self.__use_check_cache = revision.use_check_cache
+        return self.__use_check_cache
+
     async def warning(
         self, message: str, data: Any, primary_rel_path: str | None = None, 
member_rel_path: str | None = None
     ) -> sql.CheckResult:
diff --git a/atr/templates/check-selected.html 
b/atr/templates/check-selected.html
index 77b793d..7a77cc4 100644
--- a/atr/templates/check-selected.html
+++ b/atr/templates/check-selected.html
@@ -198,7 +198,11 @@
     <div class="mb-2">
       <p>The following form is for debugging purposes only. It will create a 
new revision.</p>
     </div>
-    <div class="mb-3">{{ empty_form|safe }}</div>
+    <div class="mb-2">{{ fresh_form|safe }}</div>
+    {% if is_viewing_as_admin_fn(current_user.uid) %}
+      <div class="mb-2">{{ recheck_form|safe }}</div>
+      <p class="text-muted small mb-3">Rechecks without using cached 
results.</p>
+    {% endif %}
 
     <h3 id="delete-draft" class="mt-4">Delete this draft</h3>
     <p>Permanently delete this release candidate draft and all associated 
files. This action cannot be undone.</p>
diff --git a/atr/templates/report-selected-path.html 
b/atr/templates/report-selected-path.html
index 9040529..c6ddc96 100644
--- a/atr/templates/report-selected-path.html
+++ b/atr/templates/report-selected-path.html
@@ -212,9 +212,18 @@
                 {% endif %}
               </td>
               <td class="align-middle text-center atr-sans">
-                <span class="badge rounded-pill {% if 
primary_result.status.value == "success" %}bg-success {% elif 
primary_result.status.value == "failure" %}bg-danger {% elif 
primary_result.status.value == "warning" %}bg-warning {% elif 
primary_result.status.value == "exception" %}bg-danger {% else %}bg-secondary{% 
endif %}">
-                  {{ primary_result.status.value|title }}
-                </span>
+                <div class="d-inline-flex flex-column align-items-center 
gap-1">
+                  <span class="badge rounded-pill {% if 
primary_result.status.value == "success" %}bg-success {% elif 
primary_result.status.value == "failure" %}bg-danger {% elif 
primary_result.status.value == "warning" %}bg-warning {% elif 
primary_result.status.value == "exception" %}bg-danger {% else %}bg-secondary{% 
endif %}">
+                    {{ primary_result.status.value|title }}
+                  </span>
+                  {% if primary_result.cached %}
+                    <span class="badge rounded-pill bg-secondary-subtle 
text-secondary-emphasis border border-secondary-subtle small"
+                          title="Cached result"
+                          aria-label="Cached result">
+                      <i class="bi bi-database me-1"></i>Cached
+                    </span>
+                  {% endif %}
+                </div>
               </td>
             </tr>
           {% endfor %}
@@ -286,9 +295,18 @@
                   {% if member_result.message %}{{ member_result.message }}{% 
endif %}
                 </td>
                 <td class="align-middle text-center atr-sans">
-                  <span class="badge rounded-pill {% if 
member_result.status.value == "success" %}bg-success {% elif 
member_result.status.value == "failure" %}bg-danger {% elif 
member_result.status.value == "warning" %}bg-warning {% elif 
member_result.status.value == "exception" %}bg-danger {% else %}bg-secondary{% 
endif %}">
-                    {{ member_result.status.value|title }}
-                  </span>
+                  <div class="d-inline-flex flex-column align-items-center 
gap-1">
+                    <span class="badge rounded-pill {% if 
member_result.status.value == "success" %}bg-success {% elif 
member_result.status.value == "failure" %}bg-danger {% elif 
member_result.status.value == "warning" %}bg-warning {% elif 
member_result.status.value == "exception" %}bg-danger {% else %}bg-secondary{% 
endif %}">
+                      {{ member_result.status.value|title }}
+                    </span>
+                    {% if member_result.cached %}
+                      <span class="badge rounded-pill bg-secondary-subtle 
text-secondary-emphasis border border-secondary-subtle small"
+                            title="Cached result"
+                            aria-label="Cached result">
+                        <i class="bi bi-database me-1"></i>Cached
+                      </span>
+                    {% endif %}
+                  </div>
                 </td>
               </tr>
             {% endfor %}
diff --git a/migrations/versions/0044_2026.01.29_24d82084.py 
b/migrations/versions/0044_2026.01.29_24d82084.py
new file mode 100644
index 0000000..7903c70
--- /dev/null
+++ b/migrations/versions/0044_2026.01.29_24d82084.py
@@ -0,0 +1,33 @@
+"""Record check cache status and allow status to be set
+
+Revision ID: 0044_2026.01.29_24d82084
+Revises: 0043_2026.01.29_d7d89670
+Create Date: 2026-01-29 20:16:47.043872+00:00
+"""
+
+from collections.abc import Sequence
+
+import sqlalchemy as sa
+from alembic import op
+
+# Revision identifiers, used by Alembic
+revision: str = "0044_2026.01.29_24d82084"
+down_revision: str | None = "0043_2026.01.29_d7d89670"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+    with op.batch_alter_table("checkresult", schema=None) as batch_op:
+        batch_op.add_column(sa.Column("cached", sa.Boolean(), nullable=False, 
server_default=sa.false()))
+
+    with op.batch_alter_table("revision", schema=None) as batch_op:
+        batch_op.add_column(sa.Column("use_check_cache", sa.Boolean(), 
nullable=False, server_default=sa.true()))
+
+
+def downgrade() -> None:
+    with op.batch_alter_table("revision", schema=None) as batch_op:
+        batch_op.drop_column("use_check_cache")
+
+    with op.batch_alter_table("checkresult", schema=None) as batch_op:
+        batch_op.drop_column("cached")


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to