Compare commits

...
Author SHA1 Message Date
james.maes 4624ae6461 Merge pull request 'build(deps): align with Gitea registry (parent 2.2.1, QQQ 4.1.0-RC.26)' (#38) from deps/align-gitea into develop
build / maven (push) Successful in 2m56s
maven / build (push) Successful in 2m56s
2026-11-07 18:22:07 +00:00
james.maesandClaude Opus 5.5 d5288c55a6 build(deps): align with Gitea registry (parent 2.2.1, QQQ 4.1.0-RC.26)
build / maven (push) Successful in 4m14s
maven / build (push) Successful in 4m14s
pom.xml: parent -> 2.2.1
pom.xml: qqq.snapshot.version: 4.1.0-RC.7 -> 4.1.0-RC.26
pom.xml: added qqq.version=4.1.0-RC.26 (parent BOM import)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 18:12:05 +00:00
james.maes 8784d913e6 Merge pull request 'ci: pin shared workflow to v1' (#37) from ci/pin-v1-develop into develop
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1e25bb5a243dfdcd95d4b9a4f780200ff8c8778a] (push) Successful in 13s
QRun-IO/ci-policy: native / native-result (push) Successful in 11s
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Successful in 11s
build / maven (push) Successful in 3m26s
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
maven / build (push) Successful in 3m26s
2026-11-06 16:11:55 +00:00
james.maesandClaude Opus 5.5 25a24f1bc8 ci: pin shared workflow to v1
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1e25bb5a243dfdcd95d4b9a4f780200ff8c8778a] (pull_request) Successful in 12s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 6s
build / maven (push) Successful in 2m31s
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 5s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
maven / build (push) Successful in 2m31s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:15:30 +00:00
james.maes f68d370e92 Merge pull request 'ci: use shared ci-workflows Maven workflow' (#36) from ci/ci-workflows into develop
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 6s
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Successful in 5s
build / maven (push) Successful in 2m20s
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow d20a578c7d818bc7ca32a2ac2c675da4e4f27385] (push) Successful in 6s
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
maven / build (push) Successful in 2m20s
2026-11-05 23:18:32 +00:00
CI afe9b54831 ci: rerun shared workflow
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1b3648d9421543350067e95e54959dfb471b26d5] (pull_request) Successful in 8s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
build / maven (push) Successful in 1m56s
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 5s
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 6s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (pull_request) Failing after 1s
maven / build (push) Successful in 1m56s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (pull_request) Successful in 1m48s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (pull_request) Failing after 1s
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Failing after 19m14s
2026-11-05 16:28:06 -06:00
james.maesandClaude Opus 5.5 3741e90059 ci: use shared ci-workflows Maven workflow
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
build / maven (push) Failing after 2s
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1b3648d9421543350067e95e54959dfb471b26d5] (pull_request) Failing after 2s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 2s
maven / build (push) Failing after 2s
QRun-IO/ci-policy: native / native-result (pull_request) Failing after 2s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
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 <noreply@anthropic.com>
2026-11-05 22:09:42 +00:00
forge-recovery 257788ec60 ci: enroll shared Gitea lifecycle
QRun-IO/ci-policy: ci / ci-result (push) Successful in 4s
QRun-IO/ci-policy: ci / java-docker-3 (push) Successful in 32m44s
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
qrun-release / postpublish (push) Failing after 10m16s
Shared Java Maven CI with Docker / verify (push) Successful in 2m10s
Shared Java Maven CI with Docker / secrets (push) Successful in 19s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (push) Successful in 20s
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 9s
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (push) Successful in 1m33s
Shared Java Maven CI with Docker / gate (push) Successful in 16s
qrun-release / release (push) Successful in 32m40s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (push) Successful in 15s
Shared QRun source version / version (push) Failing after 10m16s
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
2026-11-02 22:24:45 +00:00
forge-recovery 40bc96ba7c ci: enroll shared Gitea lifecycle
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (push) Successful in 34s
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 11s
QRun-IO/ci-policy: ci / java-docker-3 (push) Successful in 21m43s
QRun-IO/ci-policy: ci / ci-result (push) Successful in 6s
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (push) Successful in 1m39s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (push) Successful in 16s
2026-11-02 21:36:42 +00:00
forge-recoveryandCodex a80c090ad3 ci: enroll shared Gitea release-candidate lifecycle
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 8s
Release candidate / release-rc (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (pull_request) Successful in 13s
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 5s
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (pull_request) Successful in 2m11s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (pull_request) Successful in 21s
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Successful in 1h6m17s
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 20:22:20 +00:00
311de2c433 ci: add the isolated postpublish job to the QRun release caller
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
Shared Java Maven CI with Docker / secrets (push) Successful in 14s
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
Shared Java Maven CI with Docker / verify (push) Canceled after 0s
qrun-release / postpublish (push) Canceled after 0s
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 5s
Release candidate / release-rc (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (pull_request) Successful in 14s
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 4s
Shared Java Maven CI with Docker / gate (push) Canceled after 0s
qrun-release / release (push) Canceled after 2h25m15s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (pull_request) Successful in 1m52s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (pull_request) Successful in 11s
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Successful in 3h8m5s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 15:55:41 +00:00
d79b413626 ci: move the QRun release caller to the thin v2 contract
QRun-IO/ci-policy: ci / java-docker-2 (pull_request) Canceled after 7m59s
Shared Java Maven CI with Docker / verify (push) Canceled after 0s
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-9 (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 6s
Shared Java Maven CI with Docker / secrets (push) Successful in 10s
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 5s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (pull_request) Canceled after 0s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (pull_request) Canceled after 0s
Shared Java Maven CI with Docker / gate (push) Canceled after 0s
qrun-release / release (push) Canceled after 8m10s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (pull_request) Successful in 15s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 06:22:10 +00:00
forge-recoveryandCodex 10fe5215b6 ci: add the identical QRun release caller
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-2 (pull_request) Successful in 59m33s
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 3s
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-8 (pull_request) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (pull_request) Successful in 1m45s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (pull_request) Successful in 8s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (pull_request) Successful in 13s
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 04:17:09 +00:00
james.maesandCodex ebb2ba18fb ci: adopt managed orb 0.8 patch track (#30)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 15:28:13 -05:00
james.maesandCodex 7803c5577b ci: adopt shared orb 0.7.18 (#29)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 14:43:40 -05:00
james.maesandCodex f1d8916d9f ci: adopt shared orb 0.7.17 (#28)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 14:15:56 -05:00
james.maesandCodex d79ec6a105 ci: centralize QBit source scanning (#25)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 10:42:15 -05:00
james.maesandCodex fb64a3ecb0 ci: adopt central orb 0.7.12 and stable gates (#24)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 08:56:13 -05:00
james.maesandCodex a0aef77ae4 ci: centralize rc release completion in qqq orb (#23)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 02:25:29 -05:00
james.maesandCodex 909652f1f2 ci: adopt shared orb 0.7.11 (#22)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 00:59:25 -05:00
james.maes 2da172a50e Merge pull request #21 from QRun-IO/feature/central-ci
ci: adopt shared QQQ orb standard
2026-10-20 19:06:55 -05:00
james.maesandCodex 6a05a82221 ci: adopt verified central QQQ orb 0.7.3
Co-Authored-By: Codex <noreply@openai.com>
2026-10-20 19:02:54 -05:00
james.maes ec2e40ba41 Merge pull request #18 from QRun-IO/fix/audit-quota-rotation
fix(scheduling): rotate audit requests across the daily quota
2026-10-19 06:44:10 -05:00
james.maesandOpenAI GPT-6 9ca5f13874 fix(scheduling): rotate audit requests across daily quota
Prioritize never-queued and least-recently-queued page/device pairs using persisted non-quota-skipped history. Scheduled key-page and full audits continue sharing the existing daily allowance with manual requests. Cover odd caps, repeated scheduling, shared allowance and recreated services.

Co-Authored-By: OpenAI GPT-6 <noreply@openai.com>
Signed-off-by: James Maes <james@kof22.com>
2026-10-19 06:41:31 -05:00
james.maes b00fb256b6 Merge pull request #17 from QRun-IO/feature/page-speed-business-home
feat(home): business-only Page Speed home; setup and run queue become admin widgets
2026-10-16 10:19:44 -05:00
james.maesandClaude Opus 5.5 a6c6c99e11 feat(home): business-only Page Speed home; provider setup and run queue become admin widgets
Replace the provider Setup card on the home with a business "not set up"
empty state (no config key names, empty once configured). Setup and Recent
Runs leave the home and become WebPerfWidgets.ADMIN, gated to
<base>.admin.hasAccess with DenyBehavior.HIDDEN, for a host Admin area.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 09:12:51 -05:00
james.maes 0b8b9f8214 Merge pull request #16 from QRun-IO/ci/release-rc-secrets-inherit
ci(release-rc): pass secrets so the shared workflow can sign the RC tag
2026-10-16 08:05:25 -05:00
james.maesandClaude Opus 5.5 483c3d48b3 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 <noreply@anthropic.com>
2026-10-15 16:20:28 -05:00
james.maes 6e6deca794 Merge pull request #15 from QRun-IO/fix/producer-version-from-build
fix: qbit metadata version comes from the build
2026-10-15 01:42:58 -05:00
james.maesandClaude Opus 5.5 9003d1dabe fix: the qbit metadata version comes from the build, not a hardcoded constant
The producer read a literal version, so released artifacts reported a stale
or -SNAPSHOT version. qbit-web-performance.properties (version=${project.version}) is
Maven-filtered and the producer loads it; tests assert producer == bundle ==
artifact version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 01:33:04 -05:00
james.maes c937d753df Merge pull request #14 from QRun-IO/fix/help-bundle-version
fix(help): help bundle version comes from the build, not a hardcoded SNAPSHOT
2026-10-14 22:37:20 -05:00
james.maesandClaude Opus 5.5 ffa50f6964 fix(help): the help bundle carries the artifact version (Maven-filtered) instead of a hardcoded SNAPSHOT
On release branches the bundle's version and the producer's differed, so the
bundle test that asserts they match failed, and released bundles recorded
SNAPSHOT as their provenance. help/*.yaml is now filtered:
version: ${project.version}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:10:27 -05:00
james.maes 8e2ecb3b3f Merge pull request #13 from QRun-IO/feature/help-bundle
feat(help): qbit-web-performance help bundle via QQQ help bundles, with coverage gate
2026-10-14 20:45:30 -05:00
19 changed files with 427 additions and 82 deletions

No files matched your search

+11 -57
View File
@@ -1,60 +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.8
workflows:
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+$/]
setup:
jobs:
- qqq-orb/generate_pipeline:
orb_version: '0.8'
filters:
branches:
ignore: automation/qbit-rc-pending
tags:
only: /^v\d+\.\d+\.\d+$/
+7
View File
@@ -0,0 +1,7 @@
name: build
on:
push:
jobs:
maven:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1
secrets: inherit
+23
View File
@@ -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
+62 -13
View File
@@ -1,25 +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
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 }}
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
+16
View File
@@ -0,0 +1,16 @@
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
View File
@@ -4,7 +4,11 @@ All notable changes to this project are documented here. The format follows Keep
## [0.1.0] - Unreleased
### Changed
- The Performance home is business-only. The provider Setup readout and Recent Runs left the home's widget list (`WebPerfWidgets.HOME`); they are `WebPerfWidgets.ADMIN` (`Setup`, `RecentRuns`), gated to `<base>.admin.hasAccess` and hidden when denied, for a host to place on its Admin area. A new `NotConfigured` widget leads the home: while no Google API key is set it says "Page speed testing is not set up. Ask an admin to add a Google PageSpeed API key." (no config key names), and renders nothing once configured.
### Fixed
- Read the QBit metadata version from the build so release artifacts report their actual version, matching the help bundle.
- Use dropdowns for tier, scope, source, strategy, metric, comparator and severity, preserving existing stored values.
- Allow manual Tested Pages inserts with a URL. Validate absolute HTTPS URLs and fill in the site, path and Manual source automatically; validate URL edits too.
+4
View File
@@ -72,6 +72,10 @@ Tier, Scope, Source, Strategy, Metric, Comparator and Severity use dropdowns wit
The changelog's changeset 10 seeds 13 reference budgets (Google's "good" thresholds and the Lighthouse category bands); nothing else is seeded. The target table is filled by Sync pages from sitemap, and the lab, finding, report, field, regression, change and comparison tables are filled by the qbit as it runs (empty is a normal start). Every table's help (`help/qbit-web-performance.yaml`, shown on the table screen) says how its rows get created, and the Setup card on the home shows what still needs doing. Budgets with source RUM are not evaluated yet (no real-user source is wired); LAB and CRUX budgets are.
## Scheduled audit quota
Set `withMaxRunsPerDay(300)` in the host configuration to reserve headroom below the default 500-run cap. Key-page schedules, full audits and manually queued runs share that daily allowance in the configured time zone. Scheduled requests prioritize page/device pairs never queued before, then those queued least recently. Quota-skipped rows do not advance that rotation, so large sitemaps continue through the remaining pairs on later days instead of repeatedly testing the first pages. Existing queued/running pairs still merge; manual selection keeps its requested order.
## In-app help
The QBit registers [`help/qbit-web-performance.yaml`](src/main/resources/help/qbit-web-performance.yaml) with QQQ help bundles (RC.7 or later). Registration is metadata-only; core skips startup synchronization when the host has no `helpContent` table. App and table empty-state help remains available without Help installed.
+4
View File
@@ -1,5 +1,9 @@
# Upgrading
## Business-only home; admin widgets (unreleased)
No DDL change. The Setup and Recent Runs widgets are no longer on the Performance home. They are still registered (`webPerformanceSetup`, `webPerformanceRecentRuns`), gated to `web_performance.admin.hasAccess` and hidden without it; add `WebPerfWidgets.ADMIN` (qualified with `config.qualify`) to your Admin app's widgets to keep them. The home gains a `webPerformanceNotConfigured` widget that shows only while no Google API key is set.
## Table help, Setup card and `reportRetentionDays` (unreleased)
No DDL change, no new changeset and no host include change. The qbit now fills empty `help` slots on its app and tables (help a host set is kept) and adds a Setup widget to the Performance home. `reportRetentionDays` (default 0, keep forever) is now enforced: set it above 0 only if you want Lighthouse Reports older than that many days deleted.
+31 -2
View File
@@ -18,6 +18,7 @@
<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 -->
<revision>0.1.0-SNAPSHOT</revision>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
@@ -29,7 +30,7 @@
<parent>
<groupId>com.kingsrook</groupId>
<artifactId>qbit-build-parent</artifactId>
<version>2.0.0</version>
<version>2.2.1</version>
</parent>
<groupId>com.kingsrook.qbits</groupId>
@@ -130,7 +131,35 @@
</dependencies>
<build>
<resources>
<!-- help bundles carry the artifact version (QQQ help bundle provenance) -->
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>help/*.yaml</include>
<include>qbit-web-performance.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>help/*.yaml</exclude>
<exclude>qbit-web-performance.properties</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables combine.children="append">
<qbit.artifactVersion>${project.version}</qbit.artifactVersion>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
@@ -184,7 +213,7 @@
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<qqq.snapshot.version>4.1.0-RC.7</qqq.snapshot.version>
<qqq.snapshot.version>4.1.0-RC.26</qqq.snapshot.version>
</properties>
<dependencyManagement>
<dependencies>
@@ -21,8 +21,11 @@
package com.kingsrook.qbits.webperf;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import com.kingsrook.qbits.webperf.metadata.WebPerfMetaData;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
@@ -62,13 +65,41 @@ public class WebPerfQBitProducer implements QBitMetaDataProducer<WebPerfQBitConf
public static final String GROUP_ID = "com.kingsrook.qbits";
public static final String ARTIFACT_ID = "web-performance";
public static final String VERSION = "0.1.0";
public static final String VERSION = loadVersion();
private WebPerfQBitConfig qBitConfig;
private String namespace;
/*******************************************************************************
** Read the artifact version from the Maven-filtered properties resource.
*******************************************************************************/
private static String loadVersion()
{
try(InputStream inputStream = WebPerfQBitProducer.class.getClassLoader().getResourceAsStream("qbit-web-performance.properties"))
{
if(inputStream == null)
{
throw (new IllegalStateException("Missing qbit-web-performance.properties; build with Maven to generate the artifact version"));
}
Properties properties = new Properties();
properties.load(inputStream);
String version = properties.getProperty("version");
if(version == null || version.isBlank() || version.contains("${"))
{
throw (new IllegalStateException("Missing or unfiltered version in qbit-web-performance.properties; build with Maven"));
}
return (version);
}
catch(IOException e)
{
throw (new IllegalStateException("Could not read artifact version from qbit-web-performance.properties", e));
}
}
/*******************************************************************************
** Fluent setter for the config.
**
@@ -50,6 +50,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.DenyBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
@@ -129,13 +130,15 @@ public final class WebPerfMetaData
{
qInstance.addProcess(process);
}
addWidget(qInstance, WebPerfWidgets.SETUP, "Setup", WidgetType.HTML, WebPerfWidgets.Setup.class);
addWidget(qInstance, WebPerfWidgets.NOT_CONFIGURED, "Page Speed Setup", WidgetType.HTML, WebPerfWidgets.NotConfigured.class);
qInstance.getWidget(config.qualify(WebPerfWidgets.NOT_CONFIGURED)).setIsCard(false);
addAdminWidget(qInstance, WebPerfWidgets.SETUP, "Page Speed Setup (Admin)", WebPerfWidgets.Setup.class);
addWidget(qInstance, WebPerfWidgets.SITE_SCORES, "Site Scores", WidgetType.HTML, WebPerfWidgets.SiteScores.class);
addWidget(qInstance, WebPerfWidgets.TREND, "Performance Trend", WidgetType.LINE_CHART, WebPerfWidgets.Trend.class);
addWidget(qInstance, WebPerfWidgets.SLOWEST_PAGES, "Slowest Pages", WidgetType.HTML, WebPerfWidgets.SlowestPages.class);
addWidget(qInstance, WebPerfWidgets.OPPORTUNITIES, "Top Opportunities", WidgetType.HTML, WebPerfWidgets.TopOpportunities.class);
addWidget(qInstance, WebPerfWidgets.FIELD_STATUS, "Google Field Data", WidgetType.HTML, WebPerfWidgets.FieldDataStatus.class);
addWidget(qInstance, WebPerfWidgets.RECENT_RUNS, "Recent Runs", WidgetType.HTML, WebPerfWidgets.RecentRuns.class);
addAdminWidget(qInstance, WebPerfWidgets.RECENT_RUNS, "Recent Runs", WebPerfWidgets.RecentRuns.class);
addWidget(qInstance, WebPerfWidgets.RECORD_PANEL, "Performance", WidgetType.HTML, WebPerfWidgets.RecordPanel.class);
List<String> panelTables = new ArrayList<>(config.getContentTables());
@@ -258,6 +261,20 @@ public final class WebPerfMetaData
/*******************************************************************************
** A system widget for a host's Admin area (WebPerfWidgets.ADMIN): gated to
** "<base>.admin.hasAccess" and HIDDEN, never a locked shell, when denied.
*******************************************************************************/
private void addAdminWidget(QInstance qInstance, String name, String label, Class<? extends AbstractWidgetRenderer> renderer)
{
addWidget(qInstance, name, label, WidgetType.HTML, renderer);
qInstance.getWidget(config.qualify(name)).setPermissionRules(new QPermissionRules().withLevel(PermissionLevel.HAS_ACCESS_PERMISSION)
.withPermissionBaseName(config.getPermissionBaseName() + ".admin")
.withDenyBehavior(DenyBehavior.HIDDEN));
}
/*******************************************************************************
** The Performance app: its one home (the widgets) and the data tables.
** No process is in its nav.
@@ -209,7 +209,8 @@ public final class ScheduledSteps
try
{
WebPerfService service = WebPerfRuntime.forInput(input).service();
WebPerfService.QueuePlan plan = service.enqueue(service.requestsFor(service.enabledTargets(keyOnly), "SCHEDULE", reason, Instant.now()), Instant.now());
Instant now = Instant.now();
WebPerfService.QueuePlan plan = service.enqueue(service.scheduledRequests(keyOnly, reason, now), now);
LOG.info("Web performance tests queued", logPair("reason", reason), logPair("queued", plan.queue().size()), logPair("merged", plan.merged().size()),
logPair("overCap", plan.overQuota().size()));
}
@@ -31,6 +31,8 @@ import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.HexFormat;
import java.util.LinkedHashMap;
@@ -448,6 +450,32 @@ public class WebPerfService
/*******************************************************************************
** Scheduled requests, least recently queued page/strategy first. Quota skips
** do not advance the rotation; never-tested pairs precede historical runs.
** Manual requests keep the caller's ordering.
**
** @param keyOnly only KEY pages
** @param reason schedule description
** @param now earliest start
** @return requests ordered fairly across daily quota boundaries
** @throws SQLException on a history read failure
*******************************************************************************/
public List<RunRequest> scheduledRequests(boolean keyOnly, String reason, Instant now) throws SQLException
{
List<RunRequest> requests = requestsFor(enabledTargets(keyOnly), "SCHEDULE", reason, now);
Map<String, Instant> lastQueued = new HashMap<>();
for(Map<String, Object> row : store.query("SELECT r.target_id, r.strategy, MAX(r.queued_at) AS last_queued FROM {lab_run} r JOIN {target} t ON t.id = r.target_id WHERE t.site_key = ? AND r.status <> 'SKIPPED_QUOTA' GROUP BY r.target_id, r.strategy", siteKey()))
{
lastQueued.put(WebPerfStore.asLong(row.get("target_id")) + "|" + row.get("strategy"), (Instant) row.get("last_queued"));
}
requests.sort(Comparator.comparing(request -> lastQueued.get(request.targetId() + "|" + request.strategy()),
Comparator.nullsFirst(Comparator.naturalOrder())));
return (requests);
}
/*******************************************************************************
** Enabled pages, optionally only the KEY tier.
**
@@ -59,6 +59,7 @@ import com.kingsrook.qqq.backend.core.model.dashboard.widgets.RawHTML;
public final class WebPerfWidgets
{
public static final String SETUP = "Setup";
public static final String NOT_CONFIGURED = "NotConfigured";
public static final String SITE_SCORES = "SiteScores";
public static final String TREND = "Trend";
public static final String SLOWEST_PAGES = "SlowestPages";
@@ -67,8 +68,18 @@ public final class WebPerfWidgets
public static final String FIELD_STATUS = "FieldDataStatus";
public static final String RECORD_PANEL = "RecordPanel";
/** The home widgets, in order. */
public static final List<String> HOME = List.of(SETUP, SITE_SCORES, TREND, SLOWEST_PAGES, OPPORTUNITIES, FIELD_STATUS, RECENT_RUNS);
/** The business home's widgets, in order. No provider or run-queue detail (standard A-0). */
public static final List<String> HOME = List.of(NOT_CONFIGURED, SITE_SCORES, TREND, SLOWEST_PAGES, OPPORTUNITIES, FIELD_STATUS);
/**
* The system widgets (provider setup readout, run queue). Not on the home: a host places them on its
* Admin area. Each is gated to "&lt;base&gt;.admin.hasAccess" and hidden (not shown locked) without it.
*/
public static final List<String> ADMIN = List.of(SETUP, RECENT_RUNS);
/** The business empty state shown on the home while no Google API key is set. */
public static final String NOT_CONFIGURED_TITLE = "Page speed testing is not set up";
public static final String NOT_CONFIGURED_BODY = "Ask an admin to add a Google PageSpeed API key.";
static final int TREND_WEEKS = 26;
@@ -416,6 +427,29 @@ public final class WebPerfWidgets
/*******************************************************************************
** Not Configured: the home's business empty state. Shown only while lab tests
** cannot run because no Google API key is set; says who can fix it and names
** no configuration key. Renders nothing once the provider is configured.
*******************************************************************************/
public static class NotConfigured extends AbstractWidgetRenderer
{
/*******************************************************************************
** Render.
*******************************************************************************/
@Override
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
{
if(runtime(input).labRunner() != null)
{
return (html(""));
}
return (html("<div style=\"font-weight:600\">" + Html.escape(NOT_CONFIGURED_TITLE) + "</div>" + Html.muted(NOT_CONFIGURED_BODY)));
}
}
/*******************************************************************************
** Setup: what is configured (pages, lab runner, budgets, field data) and
** the next step for each (WebPerfSetupStatus).
@@ -1,11 +1,11 @@
bundle: qbit-web-performance
version: 0.1.0-SNAPSHOT
version: ${project.version}
layer: QBIT
entries:
- key: app:webPerformance;slot:help
summary: Page speed tests, Google field data, budgets and regressions for your site.
content: |
Use the Setup card to see what is configured and what to do next. Sync your pages, configure a lab runner and review your budgets. The dashboard shows site scores, trends, slow pages, opportunities, field data and recent runs.
Sync your pages and review your budgets, then run tests. The dashboard shows site scores, trends, slow pages, opportunities and field data. If page speed testing is not set up, it says so and tells you to ask an admin.
Run **{{label:webPerformanceTestPageSpeed}}** for selected pages or **{{label:webPerformanceRunSiteAudit}}** for all enabled pages. Mobile and desktop are separate runs by default. Your host controls the enabled features, device strategies, daily run limit and scheduler. Without a configured runner, lab tests wait in the queue.
@@ -623,7 +623,9 @@ entries:
content: |
The scheduled check records observations in **{{label:web_performance_change_event}}**. Repeated observations update the existing event instead of creating duplicates.
- key: widget:webPerformanceSetup;slot:label
summary: Check your tested pages, lab runner, budgets and field-data setup, with the next step for each.
summary: Admin only. Check your tested pages, lab runner, budgets and field-data setup, with the next step for each.
- key: widget:webPerformanceNotConfigured;slot:label
summary: Says when page speed testing is not set up and who can fix it.
- key: possibleValueSource:web_performance_target;slot:help
summary: Choose a tested page.
- key: possibleValueSource:web_performance_lab_run;slot:help
@@ -0,0 +1 @@
version=${project.version}
@@ -45,7 +45,13 @@ class WebPerfHelpBundleTest
assertThat(qInstance.getTable("helpContent")).isNull();
assertThat(HelpBundles.of(qInstance).getBundles()).singleElement()
.satisfies(bundle -> assertThat(bundle.getId()).isEqualTo("qbit-web-performance"));
.satisfies(bundle ->
{
assertThat(bundle.getId()).isEqualTo("qbit-web-performance");
assertThat(System.getProperty("qbit.artifactVersion")).isNotBlank();
assertThat(bundle.getVersion()).isEqualTo(System.getProperty("qbit.artifactVersion"))
.isEqualTo(WebPerfQBitProducer.VERSION);
});
HelpBundleAssertions.forInstance(qInstance)
.bundles("help/qbit-web-performance.yaml")
.owning(target -> target.name().startsWith("web_performance_") || target.name().startsWith("webPerformance"))
@@ -86,6 +86,71 @@ class WebPerfP1Test
/*******************************************************************************
** The business home has no provider setup readout and no run-queue widget;
** those are the admin-placeable list.
*******************************************************************************/
@Test
void testHomeIsBusinessOnly()
{
List<String> home = qInstance.getApp(config.getAppName()).getWidgets();
assertThat(home).doesNotContain(config.qualify(WebPerfWidgets.SETUP), config.qualify(WebPerfWidgets.RECENT_RUNS));
assertThat(WebPerfWidgets.HOME).doesNotContain(WebPerfWidgets.SETUP, WebPerfWidgets.RECENT_RUNS);
assertThat(WebPerfWidgets.ADMIN).containsExactly(WebPerfWidgets.SETUP, WebPerfWidgets.RECENT_RUNS);
assertThat(home).contains(config.qualify(WebPerfWidgets.SITE_SCORES), config.qualify(WebPerfWidgets.TREND), config.qualify(WebPerfWidgets.SLOWEST_PAGES),
config.qualify(WebPerfWidgets.OPPORTUNITIES), config.qualify(WebPerfWidgets.FIELD_STATUS));
for(String name : WebPerfWidgets.ADMIN)
{
assertThat(qInstance.getWidget(config.qualify(name))).as(name + " is still defined for a host to place").isNotNull();
}
}
/*******************************************************************************
** Unconfigured: a business empty state naming no config key. Configured:
** nothing at all.
*******************************************************************************/
@Test
void testNotConfiguredEmptyState() throws Exception
{
assertThat(html(WebPerfWidgets.NOT_CONFIGURED, Map.of())).as("configured shows nothing").isEmpty();
config.withLabRunner(null).withCruxClient(null);
String card = html(WebPerfWidgets.NOT_CONFIGURED, Map.of());
assertThat(card).contains("Page speed testing is not set up").contains("Ask an admin to add a Google PageSpeed API key.");
assertThat(card).doesNotContain("googleApiKey").doesNotContain("qbit config").doesNotContain("environment variable");
}
/*******************************************************************************
** The admin widgets render for an admin, and are hidden (permission denied
** under enforcement) for a session without the admin permission.
*******************************************************************************/
@Test
void testAdminWidgetsAdminOnly() throws Exception
{
for(String name : WebPerfWidgets.ADMIN)
{
assertThat(html(name, Map.of())).as(name + " for an admin").isNotEmpty();
}
WebPerfTestSupport.login(qInstance, "web_performance.read", "web_performance.write", "web_performance.view.hasAccess");
for(String name : WebPerfWidgets.ADMIN)
{
assertThat(qInstance.getWidget(config.qualify(name)).getPermissionRules().getDenyBehavior()).isEqualTo(com.kingsrook.qqq.backend.core.model.metadata.permissions.DenyBehavior.HIDDEN);
RenderWidgetInput input = new RenderWidgetInput().withWidgetMetaData(qInstance.getWidget(config.qualify(name)));
input.setPermissionEnforcement(com.kingsrook.qqq.backend.core.actions.permissions.PermissionEnforcement.ENFORCE);
org.junit.jupiter.api.Assertions.assertThrows(com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException.class,
() -> new RenderWidgetAction().execute(input), name + " must not render without admin");
assertThat(com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper.hasWidgetPermission(input, config.qualify(name))).isFalse();
}
assertThat(html(WebPerfWidgets.SITE_SCORES, Map.of())).as("business widgets still render").isNotEmpty();
}
/*******************************************************************************
** An instance with a content table "page", the QBit wired to it, and the
** local Google stand-in.
@@ -206,6 +271,73 @@ class WebPerfP1Test
/*******************************************************************************
** An odd daily cap rotates individual device strategies, not just pages.
** Skipped rows and repeated scheduling must not starve the remaining pairs.
*******************************************************************************/
@Test
void scheduledQuotaRotatesPagesAndStrategies() throws Exception
{
config.withMaxRunsPerDay(3);
Instant dayOne = Instant.parse("2026-10-19T12:00:00Z");
WebPerfService service = service();
service.applySync(service.planSync(sitemap), dayOne);
WebPerfService.QueuePlan first = service.enqueue(service.scheduledRequests(false, "audit", dayOne), dayOne);
assertEquals(3, first.queue().size());
assertEquals(3, first.overQuota().size());
assertEquals(0, service.enqueue(service.scheduledRequests(false, "audit", dayOne), dayOne).queue().size());
store().update("UPDATE {lab_run} SET status = 'SUCCEEDED' WHERE status = 'QUEUED'");
Instant dayTwo = dayOne.plusSeconds(86400);
WebPerfService.QueuePlan second = service.enqueue(service.scheduledRequests(false, "audit", dayTwo), dayTwo);
assertEquals(3, second.queue().size());
List<String> firstPairs = first.queue().stream().map(run -> run.targetId() + "|" + run.strategy()).toList();
assertThat(second.queue().stream().map(run -> run.targetId() + "|" + run.strategy()).toList())
.doesNotContainAnyElementsOf(firstPairs);
assertEquals(6, store().query("SELECT DISTINCT target_id, strategy FROM {lab_run} WHERE status <> 'SKIPPED_QUOTA'").size());
assertThat(service.scheduledRequests(true, "key pages", dayTwo)).hasSize(2)
.allSatisfy(run -> assertEquals(SITE + "/", run.url()));
}
/*******************************************************************************
** Manual and key-page runs consume the same allowance as the full audit.
** Persisted history rotates the remaining pairs after a service restart.
*******************************************************************************/
@Test
void scheduledQuotaSharesAllowanceAndSurvivesRestart() throws Exception
{
config.withMaxRunsPerDay(3);
Instant day = Instant.parse("2026-10-19T12:00:00Z");
WebPerfService service = service();
service.applySync(service.planSync(sitemap), day);
List<WebPerfService.RunRequest> manual = service.requestsFor(service.enabledTargets(false), "MANUAL", "selected page", day);
assertEquals(1, service.enqueue(List.of(manual.get(3)), day).queue().size());
assertEquals(2, service.enqueue(service.scheduledRequests(true, "key pages", day), day).queue().size());
assertEquals(0, service.enqueue(service.scheduledRequests(false, "audit", day), day).queue().size());
store().update("UPDATE {lab_run} SET status = 'SUCCEEDED' WHERE status = 'QUEUED'");
List<String> rotated = new ArrayList<>();
for(int nextDay = 1; nextDay <= 3; nextDay++)
{
Instant now = day.plusSeconds(86400L * nextDay);
WebPerfService restarted = new WebPerfService(new WebPerfRuntime(config), store());
assertEquals(2, restarted.enqueue(restarted.scheduledRequests(true, "key pages", now), now).queue().size());
WebPerfService.QueuePlan audit = restarted.enqueue(restarted.scheduledRequests(false, "audit", now), now);
assertEquals(1, audit.queue().size());
assertEquals(2, audit.merged().size());
WebPerfService.RunRequest queued = audit.queue().get(0);
assertThat(queued.url()).isNotEqualTo(SITE + "/");
rotated.add(queued.targetId() + "|" + queued.strategy());
store().update("UPDATE {lab_run} SET status = 'SUCCEEDED' WHERE status = 'QUEUED'");
}
assertThat(rotated).doesNotHaveDuplicates();
assertEquals(6, store().query("SELECT DISTINCT target_id, strategy FROM {lab_run} WHERE status <> 'SKIPPED_QUOTA'").size());
}
/*******************************************************************************
** The Setup card reports pages, budgets, the lab runner and field data from
** the runtime's own resolution, with the next step for each.
@@ -74,6 +74,9 @@ class WebPerfQBitProducerTest
@Test
void testRegistersIntoAFreshInstance() throws Exception
{
assertThat(System.getProperty("qbit.artifactVersion")).isNotBlank();
assertThat(WebPerfQBitProducer.VERSION).isEqualTo(System.getProperty("qbit.artifactVersion"));
assertThat(new WebPerfQBitProducer().getQBitMetaData().getVersion()).isEqualTo(WebPerfQBitProducer.VERSION);
QInstance qInstance = WebPerfTestSupport.newInstance();
new WebPerfQBitProducer().withQBitConfig(WebPerfTestSupport.config()
.withUrlSource(new SitemapUrlSource("https://www.example.com/sitemap.xml"))