First version of qbit-cluster (spec: QRun-IO/qqq docs/specs/2026-10-15-cluster-ha-design.md, section E).
Tables cluster_node, cluster_lease, cluster_setting: standard QQQ tables secured by cluster.read / cluster.write, with help for every table, field, process, widget and value source.
Liquibase in the jar: db/liquibase/qbit-cluster/cluster.changelog.sql (idempotent schema, no FKs, runnable by the app owner role) and cluster-defaults.changelog.sql (insert-only seeded settings: heartbeat 15 s, lost after 60 s, keep 7 days, alert on lost).
Runtime (ClusterRuntimeService): registers the node, heartbeats on the database clock, picks up drain requests (QNodeLifecycle.startDraining("requested by ...")), tracks DRAINING / STOPPED, and on the cluster-sweeper lease holder marks stale nodes LOST (alert cluster.node.lost via core QAlerts) and purges old rows idempotently.
ClusterLeases: tryAcquire, renew, release, isLeader; every change is one atomic compare-and-set UPDATE through core ConditionalUpdateAction.
Record actions Drain node / Cancel drain (standard ETL Review / Result, cluster.drain), the Cluster app whose home is the full-width Nodes table widget grouped by role, and the cluster.node.heartbeat health check.
Built against QQQ 4.1.0-SNAPSHOT (feature/cluster-ha-p1) through the qqq.snapshot.version property; the release switches it to the QQQ RC that ships the cluster APIs. CircleCI cannot build this until then (the published SNAPSHOT lacks those classes), so the merge uses the owner's admin authorization.
Test plan
./mvnw -o verify: checkstyle clean, 35 tests pass (memory backend; real Liquibase on H2 twice; runtime, sweep and lease compare-and-set as SQL through the RDBMS module; a 12-thread lease race with exactly one winner), JaCoCo 93.8% instructions, all classes covered.
## Summary
First version of **qbit-cluster** (spec: QRun-IO/qqq `docs/specs/2026-10-15-cluster-ha-design.md`, section E).
- Tables `cluster_node`, `cluster_lease`, `cluster_setting`: standard QQQ tables secured by `cluster.read` / `cluster.write`, with help for every table, field, process, widget and value source.
- Liquibase in the jar: `db/liquibase/qbit-cluster/cluster.changelog.sql` (idempotent schema, no FKs, runnable by the app owner role) and `cluster-defaults.changelog.sql` (insert-only seeded settings: heartbeat 15 s, lost after 60 s, keep 7 days, alert on lost).
- Runtime (`ClusterRuntimeService`): registers the node, heartbeats on the database clock, picks up drain requests (`QNodeLifecycle.startDraining("requested by ...")`), tracks DRAINING / STOPPED, and on the `cluster-sweeper` lease holder marks stale nodes LOST (alert `cluster.node.lost` via core `QAlerts`) and purges old rows idempotently.
- `ClusterLeases`: `tryAcquire`, `renew`, `release`, `isLeader`; every change is one atomic compare-and-set UPDATE through core `ConditionalUpdateAction`.
- Record actions **Drain node** / **Cancel drain** (standard ETL Review / Result, `cluster.drain`), the Cluster app whose home is the full-width **Nodes** table widget grouped by role, and the `cluster.node.heartbeat` health check.
Built against QQQ `4.1.0-SNAPSHOT` (feature/cluster-ha-p1) through the `qqq.snapshot.version` property; the release switches it to the QQQ RC that ships the cluster APIs. CircleCI cannot build this until then (the published SNAPSHOT lacks those classes), so the merge uses the owner's admin authorization.
## Test plan
- [x] `./mvnw -o verify`: checkstyle clean, 35 tests pass (memory backend; real Liquibase on H2 twice; runtime, sweep and lease compare-and-set as SQL through the RDBMS module; a 12-thread lease race with exactly one winner), JaCoCo 93.8% instructions, all classes covered.
- [x] Help coverage gate (`HelpBundleAssertions`) passes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
First version of qbit-cluster (spec: QRun-IO/qqq
docs/specs/2026-10-15-cluster-ha-design.md, section E).cluster_node,cluster_lease,cluster_setting: standard QQQ tables secured bycluster.read/cluster.write, with help for every table, field, process, widget and value source.db/liquibase/qbit-cluster/cluster.changelog.sql(idempotent schema, no FKs, runnable by the app owner role) andcluster-defaults.changelog.sql(insert-only seeded settings: heartbeat 15 s, lost after 60 s, keep 7 days, alert on lost).ClusterRuntimeService): registers the node, heartbeats on the database clock, picks up drain requests (QNodeLifecycle.startDraining("requested by ...")), tracks DRAINING / STOPPED, and on thecluster-sweeperlease holder marks stale nodes LOST (alertcluster.node.lostvia coreQAlerts) and purges old rows idempotently.ClusterLeases:tryAcquire,renew,release,isLeader; every change is one atomic compare-and-set UPDATE through coreConditionalUpdateAction.cluster.drain), the Cluster app whose home is the full-width Nodes table widget grouped by role, and thecluster.node.heartbeathealth check.Built against QQQ
4.1.0-SNAPSHOT(feature/cluster-ha-p1) through theqqq.snapshot.versionproperty; the release switches it to the QQQ RC that ships the cluster APIs. CircleCI cannot build this until then (the published SNAPSHOT lacks those classes), so the merge uses the owner's admin authorization.Test plan
./mvnw -o verify: checkstyle clean, 35 tests pass (memory backend; real Liquibase on H2 twice; runtime, sweep and lease compare-and-set as SQL through the RDBMS module; a 12-thread lease race with exactly one winner), JaCoCo 93.8% instructions, all classes covered.HelpBundleAssertions) passes.🤖 Generated with Claude Code