Compare commits

..
2 Commits
Author SHA1 Message Date
james.maesandClaude Opus 5.5 186bfabc34 revert(alerts): drop the runtime alert-only mechanism
Setting alerts_type.notify=false from the adapter overrides an admin's
choice on every restart. Making STORAGE_DEBRIS and RESTORE_COMPLETED
alert-only waits on a proper notify flag in the alert type metadata (core
and qbit-alerts). The plan-named titles stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:39:00 -05:00
james.maesandClaude Opus 5.5 5ea7a99d5c fix(alerts): name the plan in alert titles, keep info alerts out of the bell
Per-plan alert titles were constants, so two plans looked identical in the
notification bell. Titles now carry the plan's display name; dedupe keys are
unchanged.

STORAGE_DEBRIS and RESTORE_COMPLETED are informational, so they are declared
alert-only: after the first raise the adapter switches notify off on the
Alerts qbit's alert type row (core alert type metadata has no such setting).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:30:40 -05:00
9 changed files with 83 additions and 131 deletions

No files matched your search

+57 -11
View File
@@ -1,14 +1,60 @@
version: 2.1
setup: true
orbs:
qqq-orb: kingsrook/qqq-orb@0.8
qqq-orb: kingsrook/qqq-orb@0.6.9
commands:
configure-package-install:
steps:
- run:
name: Keep package installation unattended
command: |
# These build machines are discarded after the job.
sudo install -d /etc/needrestart/conf.d
printf '%s\n' '$nrconf{restart} = "l";' | sudo tee /etc/needrestart/conf.d/99-qqq-ci.conf >/dev/null
workflows:
setup:
jobs:
- qqq-orb/generate_pipeline:
orb_version: '0.8'
filters:
branches:
ignore: automation/qbit-rc-pending
tags:
only: /^v\d+\.\d+\.\d+$/
test_only:
jobs:
- qqq-orb/mvn_test_only:
pre-steps:
- configure-package-install
context: [qqq-maven-registry-credentials]
filters:
branches:
ignore: /(develop|release\/.*)/
publish_snapshot:
jobs:
- qqq-orb/mvn_publish:
pre-steps:
- configure-package-install
context: [qqq-maven-registry-credentials]
branch_type: snapshot
filters:
branches:
only: [develop]
publish_release_candidate:
jobs:
- qqq-orb/mvn_publish:
pre-steps:
- configure-package-install
context: [qqq-maven-registry-credentials]
branch_type: release_candidate
filters:
branches:
only: [/release\/.*/]
publish_release:
jobs:
- qqq-orb/mvn_publish:
pre-steps:
- configure-package-install
context: [qqq-maven-registry-credentials]
branch_type: release
filters:
branches:
ignore: /.*/
tags:
only: [/^v\d+\.\d+\.\d+$/]
-7
View File
@@ -1,7 +0,0 @@
name: build
on:
push:
jobs:
maven:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1
secrets: inherit
-23
View File
@@ -1,23 +0,0 @@
name: CodeQL
'on':
push:
branches:
- main
- develop
- release/**
- hotfix/**
pull_request: {}
schedule:
- cron: 41 16 * * 0
workflow_dispatch: {}
permissions:
contents: read
actions: read
security-events: write
packages: read
concurrency:
group: codeql-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
analyze:
uses: QRun-IO/qqq-orb/.github/workflows/qbit-codeql.yml@c288a8864fe9054f68410e1d78f5863aea4c4d22
+14 -62
View File
@@ -1,74 +1,26 @@
# Cut the next release candidate (QRun-IO/qqq docs/QRUN-QBIT-REPO-STANDARD.md, C6):
# run this workflow by hand, or merge a develop PR labelled `release:rc`. It merges develop into
# the release line and the release/* push runs CircleCI publish_release_candidate.
name: Release candidate
'on':
on:
workflow_dispatch:
inputs:
dry_run:
description: Plan a cut without changing release state
description: Print the plan without merging or creating branches
type: boolean
default: false
finalize:
description: Retry finalization of an existing cut; do not cut another RC
type: boolean
default: false
recover:
description: Recover the same published RC after a deployment timeout; never
reupload
type: boolean
default: false
circle_build:
description: Original Circle publisher build number (recovery only)
type: string
default: ''
merge_sha:
description: Existing cut merge_sha (finalize retry only)
type: string
default: ''
bump_sha:
description: Existing cut bump_sha (finalize retry only)
type: string
default: ''
branch:
description: Existing cut branch (finalize retry only)
type: string
default: ''
version:
description: Existing cut version (finalize retry only)
type: string
default: ''
pull_request:
types:
- closed
branches:
- develop
repository_dispatch:
types:
- qbit-rc-published
types: [closed]
branches: [develop]
permissions:
contents: write
actions: read
checks: read
jobs:
release-rc:
if: github.event_name == 'workflow_dispatch' || github.event_name == 'repository_dispatch'
|| (github.event_name == 'pull_request' && github.event.pull_request.merged
== true && github.event.pull_request.base.ref == 'develop' && contains(github.event.pull_request.labels.*.name,
'release:rc'))
uses: QRun-IO/qqq-orb/.github/workflows/qbit-release-rc.yml@cc30f599e63c8e41629c99d23395bac1adb8bc44
with:
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false
}}
finalize: ${{ github.event_name == 'repository_dispatch' || (github.event_name
== 'workflow_dispatch' && inputs.finalize) }}
recover: ${{ github.event_name == 'workflow_dispatch' && inputs.recover || false
}}
circle_build: ${{ github.event_name == 'workflow_dispatch' && inputs.circle_build
|| '' }}
merge_sha: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.merge_sha
|| inputs.merge_sha || '' }}
bump_sha: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.bump_sha
|| inputs.bump_sha || '' }}
branch: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch
|| inputs.branch || '' }}
version: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.version
|| inputs.version || '' }}
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release:rc'))
uses: QRun-IO/.github/.github/workflows/qbit-release-rc.yml@main
secrets: inherit
with:
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
-16
View File
@@ -1,16 +0,0 @@
profile: qbit
orb_version: '0.8'
default_branch: develop
java_version: '21'
browsers: true
api_checks: false
docs_only: false
release_on_main: false
release_tag_policy: stable
test_branch_policy: non-release
publish:
snapshot: true
release_candidate: true
release: true
asciidoc: false
release_completion: true
+4 -5
View File
@@ -18,7 +18,6 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<properties>
<qqq.version>4.1.0-RC.26</qqq.version> <!-- feeds qbit-build-parent's qqq-bom-pom import -->
<qbit.guardrails.enforce>true</qbit.guardrails.enforce>
<qbit.guardrails.strictUiApiRoutes>true</qbit.guardrails.strictUiApiRoutes>
<revision>0.1.0-SNAPSHOT</revision>
@@ -30,7 +29,7 @@
<parent>
<groupId>com.kingsrook</groupId>
<artifactId>qbit-build-parent</artifactId>
<version>2.2.1</version>
<version>2.1.0</version>
</parent>
<groupId>com.kingsrook.qbits</groupId>
@@ -87,7 +86,7 @@
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-object-storage</artifactId>
<version>0.1.0-RC.10</version>
<version>0.1.0-RC.2</version>
<optional>true</optional>
</dependency>
@@ -107,7 +106,7 @@
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-frontend-next</artifactId>
<version>1.0.0-RC.38</version>
<version>1.0.0-RC.14</version>
<scope>test</scope>
</dependency>
@@ -217,7 +216,7 @@
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<qqq.snapshot.version>4.1.0-RC.26</qqq.snapshot.version>
<qqq.snapshot.version>4.1.0-RC.14</qqq.snapshot.version>
</properties>
<dependencyManagement>
<dependencies>
@@ -262,7 +262,7 @@ public class SlaChecker
if(missing)
{
BackupAlerts.raise(BackupAlerts.RUN_MISSING, planKey, BackupAlertSink.Severity.CRITICAL, "The latest backup is missing from storage",
BackupAlerts.raise(BackupAlerts.RUN_MISSING, planKey, BackupAlertSink.Severity.CRITICAL, "The latest backup of \"" + name + "\" is missing from storage",
"plan", planKey, "planName", name);
}
else
@@ -273,7 +273,7 @@ public class SlaChecker
if(late)
{
BackupAlerts.raise(BackupAlerts.PLAN_MISSED, planKey, critical ? BackupAlertSink.Severity.CRITICAL : BackupAlertSink.Severity.ERROR,
"A backup did not run in time", "plan", planKey, "planName", name, "slaHours", String.valueOf(slaHours(plan)));
"Backup \"" + name + "\" did not run in time", "plan", planKey, "planName", name, "slaHours", String.valueOf(slaHours(plan)));
}
else
{
@@ -283,7 +283,7 @@ public class SlaChecker
if(latestFailed || abandoned)
{
BackupAlerts.raise(BackupAlerts.RUN_FAILED, planKey, failing ? BackupAlertSink.Severity.ERROR : BackupAlertSink.Severity.WARNING,
failing ? "Backups have failed twice in a row" : "A backup run failed", "plan", planKey, "planName", name);
failing ? "Backup \"" + name + "\" has failed twice in a row" : "Backup \"" + name + "\" run failed", "plan", planKey, "planName", name);
}
else if(latestFinished != null)
{
@@ -292,7 +292,7 @@ public class SlaChecker
if(ownerBad)
{
BackupAlerts.raise(BackupAlerts.PLAN_OWNER_INVALID, planKey, BackupAlertSink.Severity.WARNING, "The backup plan owner is not valid",
BackupAlerts.raise(BackupAlerts.PLAN_OWNER_INVALID, planKey, BackupAlertSink.Severity.WARNING, "The owner of backup plan \"" + name + "\" is not valid",
"plan", planKey, "planName", name);
}
else
@@ -302,7 +302,7 @@ public class SlaChecker
if(unverified)
{
BackupAlerts.raise(BackupAlerts.VERIFY_OVERDUE, planKey, BackupAlertSink.Severity.WARNING, "No backup has been verified recently",
BackupAlerts.raise(BackupAlerts.VERIFY_OVERDUE, planKey, BackupAlertSink.Severity.WARNING, "No backup of \"" + name + "\" has been verified recently",
"plan", planKey, "planName", name, "verifySlaDays", String.valueOf(plan.getValueInteger("verifySlaDays")));
}
else
@@ -507,7 +507,7 @@ public class SlaChecker
LAST_AUDIT_AT.put(auditKey, now);
if(orphans + debris > 0)
{
BackupAlerts.raise(BackupAlerts.STORAGE_DEBRIS, planKey, BackupAlertSink.Severity.INFO, "Backup storage has orphans or debris",
BackupAlerts.raise(BackupAlerts.STORAGE_DEBRIS, planKey, BackupAlertSink.Severity.INFO, "Backup storage of \"" + planKey + "\" has orphans or debris",
"plan", planKey, "orphans", String.valueOf(orphans), "debris", String.valueOf(debris));
}
else
@@ -639,7 +639,7 @@ public class BackupVerifier
detail.put("level", outcome.level());
detail.put("mismatches", String.valueOf(outcome.mismatches()));
BackupAlerts.raiseWithKey(BackupAlerts.VERIFY_FAILED, BackupAlerts.verifyKey(planKey, outcome.level()), BackupAlertSink.Severity.ERROR,
"Backup verification failed (" + outcome.level() + ")", detail);
"Verification of backup \"" + (plan.getValueString("name") == null ? planKey : plan.getValueString("name")) + "\" failed (" + outcome.level() + ")", detail);
}
}
}
@@ -87,6 +87,7 @@ class SlaCheckerTest
assertThat(late.previousStatus()).isEqualTo(SlaChecker.UNKNOWN);
assertThat(reload(plan).getValueString("slaStatus")).isEqualTo("LATE");
BackupAlertsAssert.assertOpen("backups.plan.missed:nightly", BackupAlertSink.Severity.CRITICAL);
assertThat(sink.open.get("backups.plan.missed:nightly").title()).isEqualTo("Backup \"Nightly\" did not run in time");
}