From e81c8ba37e4b5dd2492a894e607d08ac4bb9a7e6 Mon Sep 17 00:00:00 2001 From: James Maes Date: Thu, 15 Oct 2026 16:20:12 -0500 Subject: [PATCH 01/19] ci(release-rc): pass secrets to the shared workflow so it can sign the RC tag QRun-IO/.github#2 adds a tag-and-release job that signs vX with the QRUN_TAG_SIGNING_KEY secret after Maven Central confirms the artifact. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release-rc.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-rc.yml b/.github/workflows/release-rc.yml index dd332a0..a342ba8 100644 --- a/.github/workflows/release-rc.yml +++ b/.github/workflows/release-rc.yml @@ -21,5 +21,6 @@ jobs: release-rc: 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 }} -- 2.54.0 From 05fcbdd8b8bcad3e734b3cf3a92707191f497326 Mon Sep 17 00:00:00 2001 From: James Maes Date: Fri, 16 Oct 2026 15:33:59 -0500 Subject: [PATCH 02/19] fix(alerts): stable chain break alert title The wormaudit.chain.break title was the first break reason, so the bell text changed on every run. The title is now "Audit chain verification found N problems"; the first reasons move to the summary. Dedupe key and severity are unchanged. Co-Authored-By: Claude Opus 5.5 --- .../wormaudit/processes/VerificationRuns.java | 15 ++++++++++++++- .../qbits/wormaudit/ProcessesAndAlertsTest.java | 3 ++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/kingsrook/qbits/wormaudit/processes/VerificationRuns.java b/src/main/java/com/kingsrook/qbits/wormaudit/processes/VerificationRuns.java index 30e6bbd..ce097df 100644 --- a/src/main/java/com/kingsrook/qbits/wormaudit/processes/VerificationRuns.java +++ b/src/main/java/com/kingsrook/qbits/wormaudit/processes/VerificationRuns.java @@ -24,6 +24,7 @@ package com.kingsrook.qbits.wormaudit.processes; import java.time.Instant; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import com.kingsrook.qbits.wormaudit.WormAuditQBitConfig; import com.kingsrook.qbits.wormaudit.chain.AuditStore; import com.kingsrook.qbits.wormaudit.model.WormAuditTables; @@ -43,6 +44,11 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord; *******************************************************************************/ public final class VerificationRuns { + /** How many break reasons the alert summary spells out; the title carries the total. */ + private static final int ALERT_SUMMARY_REASONS = 3; + + + /******************************************************************************* ** Utility class. *******************************************************************************/ @@ -79,8 +85,15 @@ public final class VerificationRuns } else { + long problems = result.getBreakCount(); + String summary = result.getBreaks().stream().limit(ALERT_SUMMARY_REASONS).map(b -> b.reason()).collect(Collectors.joining("; ")); + if(problems > ALERT_SUMMARY_REASONS) + { + summary += "; and " + (problems - ALERT_SUMMARY_REASONS) + " more"; + } QAlerts.raise(new AlertSignal("wormaudit.chain.break").withDedupKey("chain") - .withTitle(result.getBreaks().getFirst().reason()).withRecordRef(new AlertRecordRef(WormAuditTables.VERIFICATION, row.getValue("id")))); + .withTitle("Audit chain verification found " + problems + (problems == 1 ? " problem" : " problems")).withSummary(summary) + .withRecordRef(new AlertRecordRef(WormAuditTables.VERIFICATION, row.getValue("id")))); } } return (result); diff --git a/src/test/java/com/kingsrook/qbits/wormaudit/ProcessesAndAlertsTest.java b/src/test/java/com/kingsrook/qbits/wormaudit/ProcessesAndAlertsTest.java index 2c06ffd..bb1842c 100644 --- a/src/test/java/com/kingsrook/qbits/wormaudit/ProcessesAndAlertsTest.java +++ b/src/test/java/com/kingsrook/qbits/wormaudit/ProcessesAndAlertsTest.java @@ -142,7 +142,8 @@ class ProcessesAndAlertsTest assertThat(Sink.raised.getFirst().getDedupKey()).isEqualTo("chain"); assertThat(Sink.raised.getFirst().getRecordRefs().getFirst().getTableName()) .isEqualTo(WormAuditTables.VERIFICATION); - assertThat(Sink.raised.getFirst().getTitle()).contains("Break at link 1"); + assertThat(Sink.raised.getFirst().getTitle()).isEqualTo("Audit chain verification found 1 problem"); + assertThat(Sink.raised.getFirst().getSummary()).contains("Break at link 1"); var first = STORE.all(WormAuditTables.VERIFICATION).getFirst(); assertThat(first.getValueLong("first_break_link_id")).isEqualTo(1); assertThat(first.getValueLong("first_break_audit_id")).isEqualTo(1); -- 2.54.0 From a5d7192b95b0d51df5667dc53261379ff2aa4f29 Mon Sep 17 00:00:00 2001 From: James Maes Date: Mon, 19 Oct 2026 10:01:20 -0500 Subject: [PATCH 03/19] feat: allow host-owned Audit app composition Keep standalone defaults while permitting embedded WORM metadata and authorized widgets without replacing host navigation or help. Co-Authored-By: Codex Signed-off-by: James Maes --- README.md | 6 +++ .../qbits/wormaudit/WormAuditQBitConfig.java | 22 ++++++++++- .../wormaudit/WormAuditQBitProducer.java | 5 ++- .../qbits/wormaudit/model/WormAuditHelp.java | 2 +- .../qbits/wormaudit/WormAuditHomeTest.java | 23 +++++++++++ .../wormaudit/WormAuditQBitProducerTest.java | 38 +++++++++++++++++++ 6 files changed, 93 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7db37ac..566395e 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,12 @@ Open `/app/worm_audit` (or `/app/admin/worm_audit` with `parentAppName="admin"`) The full-width `wormAuditHome` HTML widget uses the standard `/qqq/v1/widget` API. Hosts can add `wormAuditHealthCard` to their own dashboard. Both widgets and the app require read permission on at least one WORM table, using the host's table permission rules. +To embed WORM in an existing host app, set `withIncludeApp(false)` and configure +`appName`, `appLabel` and optional `parentAppName` for that host app. The host owns +its navigation, home widget placement and app help; the qbit does not create or replace +the app or fill its help. Both widgets and all table help, permissions, guards, +processes and schedules remain registered. The health card links to the configured +host app. Standalone app creation remains enabled by default. The aggregate home shows only identifiers, counts, times, shortened hashes and reasons; it never renders audit messages or field values. Record links retain the target table's permissions. diff --git a/src/main/java/com/kingsrook/qbits/wormaudit/WormAuditQBitConfig.java b/src/main/java/com/kingsrook/qbits/wormaudit/WormAuditQBitConfig.java index 2dc4230..15bd42f 100644 --- a/src/main/java/com/kingsrook/qbits/wormaudit/WormAuditQBitConfig.java +++ b/src/main/java/com/kingsrook/qbits/wormaudit/WormAuditQBitConfig.java @@ -48,6 +48,7 @@ public class WormAuditQBitConfig implements QBitConfig private String exportLockMode = "GOVERNANCE"; private int exportRetentionDays = 30; private final WormAuditSettings settings = new WormAuditSettings(this); + private boolean includeApp = true; private String appName = "worm_audit"; private String appLabel = "WORM Audit"; private String parentAppName; @@ -131,7 +132,26 @@ public class WormAuditQBitConfig implements QBitConfig /******************************************************************************* - ** Read the app name. + ** Whether to create the standalone app. Widgets remain available when false. + *******************************************************************************/ + public boolean getIncludeApp() + { + return (includeApp); + } + + + /******************************************************************************* + ** Disable standalone app creation when the host owns the Audit app and help. + *******************************************************************************/ + public WormAuditQBitConfig withIncludeApp(boolean includeApp) + { + this.includeApp = includeApp; + return (this); + } + + + /******************************************************************************* + ** Read the app name (the host-owned destination when includeApp is false). *******************************************************************************/ public String getAppName() { diff --git a/src/main/java/com/kingsrook/qbits/wormaudit/WormAuditQBitProducer.java b/src/main/java/com/kingsrook/qbits/wormaudit/WormAuditQBitProducer.java index 9415674..f8bf8f0 100644 --- a/src/main/java/com/kingsrook/qbits/wormaudit/WormAuditQBitProducer.java +++ b/src/main/java/com/kingsrook/qbits/wormaudit/WormAuditQBitProducer.java @@ -214,7 +214,10 @@ public class WormAuditQBitProducer implements QBitMetaDataProducer new WormAuditApp.HealthCardRenderer().render(input)).isInstanceOf(QPermissionDeniedException.class); + assertThatThrownBy(() -> new WormAuditApp.HomeRenderer().render(input)).isInstanceOf(QPermissionDeniedException.class); + QContext.getQSession().withPermissions(WormAuditTables.CHAIN + ".read"); + String card = ((RawHTML) new WormAuditApp.HealthCardRenderer().render(input).getWidgetData()).getHtml(); + assertThat(card).contains("href=\"/app/audit\""); + assertThat(new WormAuditApp.HomeRenderer().render(input).getWidgetData()).isInstanceOf(RawHTML.class); + QContext.setQSession(new QSession()); + assertThatThrownBy(() -> new WormAuditApp.HealthCardRenderer().render(input)).isInstanceOf(QPermissionDeniedException.class); + } + + /******************************************************************************* ** Exercise the real SQL anti-join without Docker, including its exact boundary. *******************************************************************************/ diff --git a/src/test/java/com/kingsrook/qbits/wormaudit/WormAuditQBitProducerTest.java b/src/test/java/com/kingsrook/qbits/wormaudit/WormAuditQBitProducerTest.java index 575242f..c91296b 100644 --- a/src/test/java/com/kingsrook/qbits/wormaudit/WormAuditQBitProducerTest.java +++ b/src/test/java/com/kingsrook/qbits/wormaudit/WormAuditQBitProducerTest.java @@ -21,10 +21,15 @@ package com.kingsrook.qbits.wormaudit; import java.util.List; +import com.kingsrook.qbits.wormaudit.app.WormAuditApp; +import com.kingsrook.qbits.wormaudit.model.WormAuditHelp; import com.kingsrook.qbits.wormaudit.ddl.WormAuditDdl; import com.kingsrook.qbits.wormaudit.model.WormAuditTables; import com.kingsrook.qbits.wormaudit.processes.WormAuditProcesses; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; +import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule; import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -43,6 +48,7 @@ class WormAuditQBitProducerTest { var config = new WormAuditQBitConfig(); QInstance instance = WormAuditTestSupport.init(config); + assertThat(config.getIncludeApp()).isTrue(); assertThat(config.getRetentionYears()).isEqualTo(6); assertThat(config.getTableMetaDataCustomizer()).isNull(); assertThat(config.getSigningKey()).isNull(); @@ -160,4 +166,36 @@ class WormAuditQBitProducerTest () -> WormAuditDdl.resolveNames(new WormAuditQBitConfig(), new QInstance())) .isInstanceOf(IllegalArgumentException.class); } + + + /******************************************************************************* + ** Embedded production keeps the host app intact and registers all evidence metadata. + *******************************************************************************/ + @Test + void embeddedMetadataPreservesHostApp() throws Exception + { + var config = new WormAuditQBitConfig().withBackendName("memory").withIncludeApp(false).withAppName("audit"); + var instance = WormAuditTestSupport.core(new QBackendMetaData().withName("memory").withBackendType(MemoryBackendModule.class)); + var host = new QAppMetaData().withName("audit").withLabel("Combined Audit").withWidgets(List.of("hostSummary")); + instance.addApp(host); + new WormAuditQBitProducer().withQBitConfig(config).produce(instance); + assertThat(config.getIncludeApp()).isFalse(); + assertThat(instance.getApp("worm_audit")).isNull(); + assertThat(instance.getApp("audit")).isSameAs(host); + assertThat(host.getLabel()).isEqualTo("Combined Audit"); + assertThat(host.getWidgets()).containsExactly("hostSummary"); + assertThat(host.getHelpContent()).isNullOrEmpty(); + assertThat(instance.getWidget(WormAuditApp.HEALTH_CARD_WIDGET)).isNotNull(); + assertThat(instance.getWidget(WormAuditApp.HOME_WIDGET)).isNotNull(); + for(String name : WormAuditTables.ALL) + { + assertThat(instance.getTable(name).getHelpContent()).containsKey(WormAuditHelp.SLOT); + assertThat(instance.getTable(name).getPermissionRules().getPermissionBaseName()).isEqualTo(name); + } + assertThat(instance.getTable(WormAuditTables.CHAIN).getDisabledCapabilities()).contains(Capability.TABLE_UPDATE, Capability.TABLE_DELETE, Capability.TABLE_INSERT); + assertThat(instance.getTable("audit").getDisabledCapabilities()).contains(Capability.TABLE_UPDATE, Capability.TABLE_DELETE); + assertThat(instance.getProcess(WormAuditProcesses.VERIFY)).isNotNull(); + assertThat(instance.getProcess(WormAuditProcesses.CHAIN).getSchedule().getRepeatSeconds()).isEqualTo(config.getChainIntervalSeconds()); + assertThat(instance.getProcess(WormAuditProcesses.DAILY).getSchedule().getCronExpression()).isEqualTo(config.getVerifyCron()); + } } -- 2.54.0 From 558effe85fd21df5c4924f33d3e6cd1b00f09200 Mon Sep 17 00:00:00 2001 From: James Maes Date: Tue, 20 Oct 2026 19:07:42 -0500 Subject: [PATCH 04/19] ci: adopt verified central QQQ orb 0.7.3 Co-Authored-By: Codex --- .circleci/config.yml | 71 +++++++------------------------------------- .qqq-ci.yml | 16 ++++++++++ 2 files changed, 26 insertions(+), 61 deletions(-) create mode 100644 .qqq-ci.yml diff --git a/.circleci/config.yml b/.circleci/config.yml index 8f47e24..113ce37 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,65 +1,14 @@ version: 2.1 - +setup: true orbs: - 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 - + qqq-orb: kingsrook/qqq-orb@0.7.3 workflows: - - test_only: + setup: jobs: - - qqq-orb/mvn_test_only: - pre-steps: - - configure-package-install - context: [ qqq-maven-registry-credentials ] - filters: - branches: - ignore: /(develop|release/.*|integration.*)/ - tags: - only: [] - - 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+$/ ] + - qqq-orb/generate_pipeline: + orb_version: 0.7.3 + filters: + branches: + ignore: automation/qbit-rc-pending + tags: + only: /^v\d+\.\d+\.\d+$/ diff --git a/.qqq-ci.yml b/.qqq-ci.yml new file mode 100644 index 0000000..2f714fd --- /dev/null +++ b/.qqq-ci.yml @@ -0,0 +1,16 @@ +profile: qbit +orb_version: 0.7.3 +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-no-integration +publish: + snapshot: true + release_candidate: true + release: true + asciidoc: false +release_completion: false -- 2.54.0 From 082f3b95f09b92eb60b0ecfdfb11c49f89c8447c Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 21 Oct 2026 00:56:21 -0500 Subject: [PATCH 05/19] ci: adopt shared orb 0.7.11 (#22) Co-authored-by: Codex --- .circleci/config.yml | 4 ++-- .qqq-ci.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 113ce37..def7f95 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,12 +1,12 @@ version: 2.1 setup: true orbs: - qqq-orb: kingsrook/qqq-orb@0.7.3 + qqq-orb: kingsrook/qqq-orb@0.7.11 workflows: setup: jobs: - qqq-orb/generate_pipeline: - orb_version: 0.7.3 + orb_version: 0.7.11 filters: branches: ignore: automation/qbit-rc-pending diff --git a/.qqq-ci.yml b/.qqq-ci.yml index 2f714fd..34d6648 100644 --- a/.qqq-ci.yml +++ b/.qqq-ci.yml @@ -1,5 +1,5 @@ profile: qbit -orb_version: 0.7.3 +orb_version: 0.7.11 default_branch: develop java_version: '21' browsers: true -- 2.54.0 From a8980eec0fe110a7af45f297d5cd69372c771110 Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 21 Oct 2026 02:24:23 -0500 Subject: [PATCH 06/19] ci: centralize rc release completion in qqq orb (#23) Co-authored-by: Codex --- .github/workflows/release-rc.yml | 76 ++++++++++++++++++++++++++------ .qqq-ci.yml | 2 +- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release-rc.yml b/.github/workflows/release-rc.yml index a342ba8..0158aa2 100644 --- a/.github/workflows/release-rc.yml +++ b/.github/workflows/release-rc.yml @@ -1,26 +1,74 @@ -# 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: Print the plan without merging or creating branches + description: Plan a cut without changing release state 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] - + types: + - closed + branches: + - develop + repository_dispatch: + types: + - qbit-rc-published permissions: contents: write - + actions: read + checks: read jobs: release-rc: - 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 + 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@7d73ac90b6b5a6acb61b4d3fa56625c5b6dcc652 with: - dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }} + 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 || '' }} + secrets: inherit diff --git a/.qqq-ci.yml b/.qqq-ci.yml index 34d6648..d983335 100644 --- a/.qqq-ci.yml +++ b/.qqq-ci.yml @@ -13,4 +13,4 @@ publish: release_candidate: true release: true asciidoc: false -release_completion: false +release_completion: true -- 2.54.0 From 0ce28313f9a6334e8ac2ed9917b3e1014743c823 Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 21 Oct 2026 08:52:48 -0500 Subject: [PATCH 07/19] ci: adopt central orb 0.7.12 and stable gates (#24) Co-authored-by: Codex --- .circleci/config.yml | 4 ++-- .github/workflows/release-rc.yml | 2 +- .qqq-ci.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index def7f95..c9cb461 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,12 +1,12 @@ version: 2.1 setup: true orbs: - qqq-orb: kingsrook/qqq-orb@0.7.11 + qqq-orb: kingsrook/qqq-orb@0.7.12 workflows: setup: jobs: - qqq-orb/generate_pipeline: - orb_version: 0.7.11 + orb_version: 0.7.12 filters: branches: ignore: automation/qbit-rc-pending diff --git a/.github/workflows/release-rc.yml b/.github/workflows/release-rc.yml index 0158aa2..b48a32a 100644 --- a/.github/workflows/release-rc.yml +++ b/.github/workflows/release-rc.yml @@ -53,7 +53,7 @@ jobs: || (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@7d73ac90b6b5a6acb61b4d3fa56625c5b6dcc652 + uses: QRun-IO/qqq-orb/.github/workflows/qbit-release-rc.yml@cc30f599e63c8e41629c99d23395bac1adb8bc44 with: dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false }} diff --git a/.qqq-ci.yml b/.qqq-ci.yml index d983335..3a681a5 100644 --- a/.qqq-ci.yml +++ b/.qqq-ci.yml @@ -1,5 +1,5 @@ profile: qbit -orb_version: 0.7.11 +orb_version: 0.7.12 default_branch: develop java_version: '21' browsers: true -- 2.54.0 From c8ea4b627bb9f0c614e31635d0c8c92bd297f0ae Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 21 Oct 2026 10:47:40 -0500 Subject: [PATCH 08/19] ci: use the shared branch-aware CodeQL worker (#26) Co-authored-by: Codex --- .github/workflows/codeql.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..bd3ceaf --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,23 @@ +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 -- 2.54.0 From 2c470560a30171341cbb68f30355ba61990cda88 Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 21 Oct 2026 14:16:40 -0500 Subject: [PATCH 09/19] ci: adopt shared orb 0.7.17 (#28) Co-authored-by: Codex --- .circleci/config.yml | 4 ++-- .qqq-ci.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c9cb461..1d4fa0d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,12 +1,12 @@ version: 2.1 setup: true orbs: - qqq-orb: kingsrook/qqq-orb@0.7.12 + qqq-orb: kingsrook/qqq-orb@0.7.17 workflows: setup: jobs: - qqq-orb/generate_pipeline: - orb_version: 0.7.12 + orb_version: 0.7.17 filters: branches: ignore: automation/qbit-rc-pending diff --git a/.qqq-ci.yml b/.qqq-ci.yml index 3a681a5..b634788 100644 --- a/.qqq-ci.yml +++ b/.qqq-ci.yml @@ -1,5 +1,5 @@ profile: qbit -orb_version: 0.7.12 +orb_version: 0.7.17 default_branch: develop java_version: '21' browsers: true -- 2.54.0 From d93b2796291e02297b497762b8e63d2313f3b27e Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 21 Oct 2026 14:46:57 -0500 Subject: [PATCH 10/19] ci: adopt shared orb 0.7.18 (#29) Co-authored-by: Codex --- .circleci/config.yml | 4 ++-- .qqq-ci.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1d4fa0d..42b73df 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,12 +1,12 @@ version: 2.1 setup: true orbs: - qqq-orb: kingsrook/qqq-orb@0.7.17 + qqq-orb: kingsrook/qqq-orb@0.7.18 workflows: setup: jobs: - qqq-orb/generate_pipeline: - orb_version: 0.7.17 + orb_version: 0.7.18 filters: branches: ignore: automation/qbit-rc-pending diff --git a/.qqq-ci.yml b/.qqq-ci.yml index b634788..7976133 100644 --- a/.qqq-ci.yml +++ b/.qqq-ci.yml @@ -1,5 +1,5 @@ profile: qbit -orb_version: 0.7.17 +orb_version: 0.7.18 default_branch: develop java_version: '21' browsers: true -- 2.54.0 From be0d49d9db5a94d5d648026f61541a607355cbc2 Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 21 Oct 2026 15:29:03 -0500 Subject: [PATCH 11/19] ci: adopt managed orb 0.8 patch track (#30) Co-authored-by: Codex --- .circleci/config.yml | 4 ++-- .qqq-ci.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 42b73df..f890c28 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,12 +1,12 @@ version: 2.1 setup: true orbs: - qqq-orb: kingsrook/qqq-orb@0.7.18 + qqq-orb: kingsrook/qqq-orb@0.8 workflows: setup: jobs: - qqq-orb/generate_pipeline: - orb_version: 0.7.18 + orb_version: '0.8' filters: branches: ignore: automation/qbit-rc-pending diff --git a/.qqq-ci.yml b/.qqq-ci.yml index 7976133..fa97e65 100644 --- a/.qqq-ci.yml +++ b/.qqq-ci.yml @@ -1,5 +1,5 @@ profile: qbit -orb_version: 0.7.18 +orb_version: '0.8' default_branch: develop java_version: '21' browsers: true -- 2.54.0 From e1d3986acc5461996d52b7c52fde4433f50e4ee5 Mon Sep 17 00:00:00 2001 From: Forge Recovery Date: Mon, 2 Nov 2026 04:18:20 +0000 Subject: [PATCH 12/19] ci: add the identical QRun release caller Co-Authored-By: Codex --- .gitea/workflows/qrun-release.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .gitea/workflows/qrun-release.yaml diff --git a/.gitea/workflows/qrun-release.yaml b/.gitea/workflows/qrun-release.yaml new file mode 100644 index 0000000..122f7e9 --- /dev/null +++ b/.gitea/workflows/qrun-release.yaml @@ -0,0 +1,12 @@ +# Identical in every QRun Maven application: do not edit. It only delegates to the protected qrun-release-v1 channel. +name: qrun-release +on: + push: + branches: [main, develop, 'release/**', 'hotfix/**'] +permissions: + contents: read +jobs: + release: + uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/java-maven.yaml@qrun-release-v1 + with: + runner: linux-small -- 2.54.0 From 2eaf49eccbe94736068cf184d9aa200d76af9466 Mon Sep 17 00:00:00 2001 From: Forge Recovery Date: Mon, 2 Nov 2026 06:22:56 +0000 Subject: [PATCH 13/19] ci: move the QRun release caller to the thin v2 contract Co-Authored-By: Claude Sonnet 5.5 Co-Authored-By: Codex --- .gitea/workflows/qrun-release.yaml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/qrun-release.yaml b/.gitea/workflows/qrun-release.yaml index 122f7e9..77794cf 100644 --- a/.gitea/workflows/qrun-release.yaml +++ b/.gitea/workflows/qrun-release.yaml @@ -1,12 +1,16 @@ -# Identical in every QRun Maven application: do not edit. It only delegates to the protected qrun-release-v1 channel. +# Standard QRun release caller (v2, thin contract): every push and manual dispatch delegates to the protected qrun-release-v1 channel. Do not edit per repository. name: qrun-release on: - push: - branches: [main, develop, 'release/**', 'hotfix/**'] + push: {} + workflow_dispatch: {} permissions: contents: read + actions: read jobs: release: - uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/java-maven.yaml@qrun-release-v1 + uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/java-docker.yaml@qrun-release-v1 with: runner: linux-small + docker-runner: container-build + browsers: true + browser-runner: browser-build -- 2.54.0 From dc6361b4c2a1023e4c18dad6b4fb73bcaddb4ae5 Mon Sep 17 00:00:00 2001 From: Forge Recovery Date: Mon, 2 Nov 2026 15:57:08 +0000 Subject: [PATCH 14/19] ci: add the isolated postpublish job to the QRun release caller Co-Authored-By: Claude Sonnet 5.5 Co-Authored-By: Codex --- .gitea/workflows/qrun-release.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitea/workflows/qrun-release.yaml b/.gitea/workflows/qrun-release.yaml index 77794cf..5ebc530 100644 --- a/.gitea/workflows/qrun-release.yaml +++ b/.gitea/workflows/qrun-release.yaml @@ -14,3 +14,12 @@ jobs: docker-runner: container-build browsers: true browser-runner: browser-build + postpublish: + if: ${{ needs.release.outputs.capture == 'true' }} + needs: release + permissions: + contents: write + actions: write + uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/qrun-source-version.yaml@qrun-release-v1 + with: + runner: linux-small -- 2.54.0 From 29fc15a10ae47edfdd00985feb68f30f6cac9bd4 Mon Sep 17 00:00:00 2001 From: Forge Recovery Date: Mon, 2 Nov 2026 20:23:20 +0000 Subject: [PATCH 15/19] ci: enroll shared Gitea release-candidate lifecycle Co-Authored-By: Codex --- .gitea/workflows/release-rc.yaml | 72 ++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 .gitea/workflows/release-rc.yaml diff --git a/.gitea/workflows/release-rc.yaml b/.gitea/workflows/release-rc.yaml new file mode 100644 index 0000000..12ec9fc --- /dev/null +++ b/.gitea/workflows/release-rc.yaml @@ -0,0 +1,72 @@ +name: Release candidate +'on': + workflow_dispatch: + inputs: + dry_run: + description: Plan a cut without changing release state + 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 release run id (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 +permissions: + contents: read +jobs: + release-rc: + # The old qbit-rc-published repository_dispatch is replaced by the already-proven workflow_dispatch + # completion: the publisher's own ActionsWRITE job dispatches this workflow with finalize=true plus + # merge_sha/bump_sha/branch/version (same inputs as the manual finalize retry). + permissions: + contents: write + actions: read + pull-requests: write + if: github.event_name == 'workflow_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: self:KofTwentyTwo/platform-ci/.gitea/workflows/qbit-release-rc.yaml@qrun-release-v1 + with: + dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false + }} + finalize: ${{ 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: ${{ inputs.merge_sha || '' }} + bump_sha: ${{ inputs.bump_sha || '' }} + branch: ${{ inputs.branch || '' }} + version: ${{ inputs.version || '' }} + secrets: + QRUN_TAG_SIGNING_KEY: ${{ secrets.QRUN_TAG_SIGNING_KEY }} -- 2.54.0 From db2c2b675507e31477eee1bba48b35b0f79d9844 Mon Sep 17 00:00:00 2001 From: James Maes Date: Thu, 5 Nov 2026 22:10:41 +0000 Subject: [PATCH 16/19] ci: use shared ci-workflows Maven workflow Replace the platform-ci release callers with one caller of KofTwentyTwo/ci-workflows maven.yaml (qqq-orb port). Co-Authored-By: Claude Opus 5.5 --- .gitea/workflows/build.yaml | 7 +++ .gitea/workflows/qrun-release.yaml | 25 ----------- .gitea/workflows/release-rc.yaml | 72 ------------------------------ 3 files changed, 7 insertions(+), 97 deletions(-) create mode 100644 .gitea/workflows/build.yaml delete mode 100644 .gitea/workflows/qrun-release.yaml delete mode 100644 .gitea/workflows/release-rc.yaml diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..ed42608 --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,7 @@ +name: build +on: + push: +jobs: + maven: + uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@main + secrets: inherit diff --git a/.gitea/workflows/qrun-release.yaml b/.gitea/workflows/qrun-release.yaml deleted file mode 100644 index 5ebc530..0000000 --- a/.gitea/workflows/qrun-release.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Standard QRun release caller (v2, thin contract): every push and manual dispatch delegates to the protected qrun-release-v1 channel. Do not edit per repository. -name: qrun-release -on: - push: {} - workflow_dispatch: {} -permissions: - contents: read - actions: read -jobs: - release: - uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/java-docker.yaml@qrun-release-v1 - with: - runner: linux-small - docker-runner: container-build - browsers: true - browser-runner: browser-build - postpublish: - if: ${{ needs.release.outputs.capture == 'true' }} - needs: release - permissions: - contents: write - actions: write - uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/qrun-source-version.yaml@qrun-release-v1 - with: - runner: linux-small diff --git a/.gitea/workflows/release-rc.yaml b/.gitea/workflows/release-rc.yaml deleted file mode 100644 index 12ec9fc..0000000 --- a/.gitea/workflows/release-rc.yaml +++ /dev/null @@ -1,72 +0,0 @@ -name: Release candidate -'on': - workflow_dispatch: - inputs: - dry_run: - description: Plan a cut without changing release state - 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 release run id (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 -permissions: - contents: read -jobs: - release-rc: - # The old qbit-rc-published repository_dispatch is replaced by the already-proven workflow_dispatch - # completion: the publisher's own ActionsWRITE job dispatches this workflow with finalize=true plus - # merge_sha/bump_sha/branch/version (same inputs as the manual finalize retry). - permissions: - contents: write - actions: read - pull-requests: write - if: github.event_name == 'workflow_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: self:KofTwentyTwo/platform-ci/.gitea/workflows/qbit-release-rc.yaml@qrun-release-v1 - with: - dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false - }} - finalize: ${{ 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: ${{ inputs.merge_sha || '' }} - bump_sha: ${{ inputs.bump_sha || '' }} - branch: ${{ inputs.branch || '' }} - version: ${{ inputs.version || '' }} - secrets: - QRUN_TAG_SIGNING_KEY: ${{ secrets.QRUN_TAG_SIGNING_KEY }} -- 2.54.0 From 54bef4a36b68fa1bdc294d3db97db7767642c0a0 Mon Sep 17 00:00:00 2001 From: CI Date: Thu, 5 Nov 2026 16:28:51 -0600 Subject: [PATCH 17/19] ci: rerun shared workflow -- 2.54.0 From b904359beac869819e7226b0cdb7d4a6880069e3 Mon Sep 17 00:00:00 2001 From: James Maes Date: Fri, 6 Nov 2026 14:17:16 +0000 Subject: [PATCH 18/19] ci: pin shared workflow to v1 Co-Authored-By: Claude Opus 5.5 --- .gitea/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index ed42608..ce00351 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -3,5 +3,5 @@ on: push: jobs: maven: - uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@main + uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1 secrets: inherit -- 2.54.0 From fc4375adb5ce4c86364ce9282c1ec5379cfc059a Mon Sep 17 00:00:00 2001 From: James Maes Date: Sat, 7 Nov 2026 17:56:03 +0000 Subject: [PATCH 19/19] build(deps): align with Gitea registry (parent 2.2.1, QQQ 4.1.0-RC.26) pom.xml: parent -> 2.2.1 pom.xml: qqq.snapshot.version: 4.1.0-RC.5 -> 4.1.0-RC.26 pom.xml: qbit-object-storage: 0.1.0-RC.2 -> 0.1.0-RC.10 pom.xml: qqq-frontend-next: 1.0.0-RC.14 -> 1.0.0-RC.38 pom.xml: added qqq.version=4.1.0-RC.26 (parent BOM import) Co-Authored-By: Claude Opus 5.5 --- pom.xml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index d8d5bc8..11e6c30 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,7 @@ + 4.1.0-RC.26 0.1.0-SNAPSHOT 21 21 @@ -33,7 +34,7 @@ com.kingsrook qbit-build-parent - 2.0.0 + 2.2.1 com.kingsrook.qbits @@ -86,7 +87,7 @@ com.kingsrook.qbits qbit-object-storage - 0.1.0-RC.2 + 0.1.0-RC.10 com.kingsrook.qqq @@ -95,7 +96,7 @@ com.kingsrook.qqq qqq-frontend-next - 1.0.0-RC.14 + 1.0.0-RC.38 test @@ -231,7 +232,7 @@ - 4.1.0-RC.5 + 4.1.0-RC.26 -- 2.54.0