Raw JSON report machine-readable
{
"data": {
"repo": {
"topics": [],
"is_fork": false,
"size_kb": 4653,
"has_wiki": true,
"homepage": "https://hasteward.prplanit.com",
"languages": {
"Go": 655694,
"Dockerfile": 1744
},
"pushed_at": "2026-07-19T20:20:44Z",
"created_at": "2026-02-11T12:46:40Z",
"owner_type": "Organization",
"updated_at": "2026-07-19T20:20:48Z",
"description": "High Availability Steward is a (WIP) Go CLI and Kubernetes operator for database cluster triage, repair, backup, and restore. Pronounced like Haste·Ward, H.A. or Ha! Steward — flexible pronunciation. Backups use restic for block-level dedup, encryption, and compression.",
"is_archived": false,
"is_disabled": false,
"license_spdx": "AGPL-3.0",
"default_branch": "main",
"license_spdx_raw": "AGPL-3.0",
"primary_language": "Go",
"significant_languages": [
"Go"
]
},
"owner": {
"blog": null,
"name": "Precision Plan IT",
"type": "Organization",
"login": "PrPlanIT",
"company": null,
"location": "United States of America",
"followers": 1,
"avatar_url": "https://avatars.githubusercontent.com/u/223043941?v=4",
"created_at": "2025-07-27T00:42:15Z",
"is_verified": null,
"public_repos": 12,
"account_age_days": 361
},
"license": {
"state": "standard",
"spdx_id": "AGPL-3.0",
"raw_spdx": "AGPL-3.0",
"file_present": true,
"scorecard_found": true,
"profile_has_license": true
},
"activity": {
"releases": [
{
"tag": "dev-ad9b2d1",
"kind": "other",
"published_at": "2026-07-19T20:20:18Z"
},
{
"tag": "latest-dev",
"kind": "other",
"published_at": "2026-06-11T11:51:05Z"
},
{
"tag": "v0.2.1",
"kind": "patch",
"published_at": "2026-07-17T00:31:58Z"
},
{
"tag": "dev-113cfc1",
"kind": "other",
"published_at": "2026-07-17T00:19:30Z"
},
{
"tag": "v0.2.0",
"kind": "minor",
"published_at": "2026-07-17T00:02:09Z"
},
{
"tag": "dev-a33dd17",
"kind": "other",
"published_at": "2026-07-16T23:47:15Z"
},
{
"tag": "dev-8c9cfda",
"kind": "other",
"published_at": "2026-07-16T23:30:15Z"
},
{
"tag": "dev-ada6a6b",
"kind": "other",
"published_at": "2026-07-16T20:52:34Z"
},
{
"tag": "dev-36fddae",
"kind": "other",
"published_at": "2026-07-16T20:33:22Z"
},
{
"tag": "v0.1.0",
"kind": "minor",
"published_at": "2026-06-11T11:51:06Z"
},
{
"tag": "v0.0.1",
"kind": "patch",
"published_at": "2026-06-11T11:51:07Z"
}
],
"recent_commits": [
{
"oid": "0ce6d0775fdbe681663cb6f8790c3275be4ca599",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-19T20:20:37Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ad9b2d11171b68994e27f4c0178f16b644f06e41",
"body": "…cistate fix)\n\nPicks up the StageFreight cross-job cistate fix: review now forwards its\nper-subsystem fragment (.stagefreight/subsystems/) instead of the monolithic\npipeline.json, so publish's authorization gate unions build+security regardless\nof artifact download order (was intermittently blocking publish with\n\"security did not run\").",
"is_bot": false,
"headline": "ci: regenerate — review forwards subsystems/ fragments (StageFreight …",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-19T20:12:30Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "53307a00bc26dd9b4a48a4fd258c242952a235c1",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-19T07:04:49Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "85784a39fca1f5cdf6861105c358041e976061c2",
"body": "…rsion\n\nCNPG cross-instance comparison chose authority by highest Postgres timeline\nnumber. A restore/PITR mints a HIGHER timeline number that forks from an\nOLDER LSN, so a stale-restore primary reported SafeToHeal=true and `repair`\nwould re-clone a fresher replica FROM the stale primary — silent,\nu\n[…]\n- Port Galera's fail-closed-on-unread guard to CNPG: a Bound-PVC instance\n with unreadable pg_controldata makes authority undeterminable, not \"safe\".\n\nTests: src/engine/triage/cnpg_comparison_test.go",
"is_bot": false,
"headline": "fix(triage): guard CNPG authority against stale-restore timeline inve…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-19T06:56:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "078159ac24e93261bd87bf651573f40852d000a1",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-17T00:19:45Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "113cfc1400aa95c70f26a9cd9d1eecbf033d541a",
"body": "…mits)\n\nAdds a GHCR registry (ghcr.io/prplanit/hasteward, credentials: GHCR — GHCR_USER + GHCR_TOKEN, mirroring stagefreight) plus ghcr-stable / ghcr-prerelease / ghcr-dev targets alongside the existing Docker Hub + Harbor ones, so images publish to GHCR on the same triggers (tag → v{version}/latest\n[…]\nk; (2) confirm GHCR_USER/GHCR_TOKEN are available to the project (org-level, like stagefreight). Config validated through the stagefreight loader (ghcr provider recognized, 3 registries / 16 targets).",
"is_bot": false,
"headline": "feat(ci): publish to GHCR and default 'update' to it (no pull rate li…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-17T00:12:16Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "bca9f6797671439865c1462d06352cfd2243fa8c",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-16T23:47:30Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a33dd175c65aacc6b72400ca7d48cd70c5091022",
"body": "…p shape\n\ndocs generate builds the CLI reference from the live command tree, so the new #31 shape (backup create/list/restore/export/prune/policies/repositories, status, prune-wal, reset-authority, version, update) rendered correctly. But two reparented commands carried hardcoded example paths in th\n[…]\nd export ...' and prune-backups' said 'hasteward prune backups ...'. Updated to 'hasteward backup export' / 'hasteward backup prune'. Regenerated CLI reference verified clean of stale paths. Refs #31.",
"is_bot": false,
"headline": "docs(cli): update reparented commands' example paths to the new backu…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-16T23:40:03Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "947503f2e761b054203500220c2c299b2dbb8adb",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-16T23:30:29Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8c9cfdacecf9dcdeaaa499ec1dd99d45a819f7dc",
"body": "Completes the #31 TOOL domain. 'hasteward update' pulls the published image and atomically replaces the running binary with the one inside it — modeled on StageFreight's proven update: resolve ref (--dev -> latest-dev, --image <ref>) -> locate self (follow symlinks) -> stage in the same dir (writabi\n[…]\npull, extract /hasteward (the scratch image's binary path), and the 'version' verify step all succeed; graceful error when docker/image is unavailable. Full suite green. Closes the TOOL domain of #31.",
"is_bot": false,
"headline": "feat(cli): add an update command to self-update the CLI binary",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-16T23:22:38Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "bfd36606b0f255a72e8ce0a172a9bb9386b0d8e6",
"body": "…me the invariant)\n\n'reconfigure' named the mechanism ('stops all pods, fixes metadata, restarts') and told an operator nothing about WHEN to reach for it. Renamed to 'reset-authority', which names the failed invariant: the cluster's authority metadata is inconsistent and must be forcibly reset. Rea\n[…]\nde is nowhere branched on, so the mode label change is safe.\n\nVerified: 'reset-authority' present with --fix-bootstrap/--instance/--heal-timeout intact, 'reconfigure' gone, full suite green. Refs #31.",
"is_bot": false,
"headline": "refactor(cli): #31 Phase 3: rename reconfigure -> reset-authority (na…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-16T23:18:15Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "bc8c1b6a36466352a7d75700f0acdc871d087508",
"body": "… drop the old sprawl\n\nEverything backup-related now lives under the backup noun, and the old command sprawl is REMOVED outright (no compat aliases — the previous shape was the problem, not something to preserve): backup create/list/restore/export/prune/policies/repositories. Bare 'backup' is now gr\n[…]\ns to 'backup create'. Behavior-verified at runtime: every old shape gone, every new shape parses its flags, bare backup shows group help, each HASTEWARD_* var appears once, full suite green. Refs #31.",
"is_bot": false,
"headline": "refactor(cli): #31 Phase 2: reparent backup lifecycle under one noun;…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-16T23:14:55Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "eeb6f626d5f9506639bfedbbbd907f94b67c8c46",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-16T20:52:49Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ada6a6b9c64ad384707bffca39b45028847e9eb8",
"body": "…(#31)\n\nContinues #31 flag-scope cleanup for the multi-consumer behavior knobs: --method -> backup + restore; --snapshot -> restore + export; --heal-timeout -> repair + reconfigure. --instance (addressing: 'which node') and --delete-timeout (used by 5 commands incl. read-only triage — effectively a \n[…]\nch flag (incl -m shorthand), non-owners reject it, kept-global flags still work everywhere, and every HASTEWARD_* var appears exactly once in the regenerated env reference. Full suite green. Refs #31.",
"is_bot": false,
"headline": "refactor(cli): demote dual-consumer behavior flags to their commands …",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-16T20:40:25Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "794d4c0c6845f63cbe71b77180bde3480fe0faa0",
"body": "…#31)\n\nContinues #31 Phase 1 flag-scope cleanup. Persistent (global) flags should answer 'which cluster am I talking to?', not 'how does this recovery algorithm behave?'. Moves the four cleanly single-consumer flags off RootCmd.PersistentFlags() onto their owning command: --unwedge, --wipe-datadir, \n[…]\nscope without degrading any real invocation. Multi-consumer flags (--instance, --heal-timeout, --method, --snapshot) and the legitimately-global --delete-timeout are handled/left separately. Refs #31.",
"is_bot": false,
"headline": "refactor(cli): demote single-consumer flags to their owning command (…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-16T20:36:06Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "01dce7a0deaf95678113ef4227dab1cf21888cb3",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-16T20:33:36Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "36fddae55ff88547881b846739785e38f1da3106",
"body": "…, additive)\n\nFirst additive slice of the #31 CLI curation — behavior-neutral, no removals, compat aliases retained.\n\nstatus: 'get status' is a factual state query ('what is?'), distinct from triage ('what's wrong + what next'), and doesn't belong under a 'get' resource-lister. Promoted to a top-lev\n[…]\nrune backups' overloaded the word 'prune'. Promoted to top-level 'prune-wal' (shared runPruneWAL); 'prune wal' retained as a compat alias, and 'prune' now documents itself as retention-only. Refs #31.",
"is_bot": false,
"headline": "refactor(cli): promote status and prune-wal to top-level (#31 Phase 1…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-16T20:26:14Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d8e0200bc6b2248709de2e17dfc192acbc90ba1b",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-16T20:25:08Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0bf49728d4c0d11c5763494750d9ee630490e0d6",
"body": "HASteward injects Version/Commit/BuildDate into src/version via ldflags at build time but had no command to print them — 'hasteward version' did nothing. Adds it: human output ('hasteward <version>' + Commit/Built) and structured output via the standard printer (model.VersionInfo) so --output json/jsonl works for automation. First entry of #31's TOOL domain (operate on the binary itself); 'update' (self-update from the published image, --dev) is the follow-on. Refs #31.",
"is_bot": false,
"headline": "feat(cli): add a version command",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-16T20:17:28Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "430d58863946f53f7430de66d11ce1bccdd00359",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-16T19:59:39Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "caa367af93d5172233d942f6bfdf41c4ca52d448",
"body": "…an't strand the operator\n\nreconfigure suspends the operator CR and scales the StatefulSet down, restoring both in a deferred rescue closure that used the run's context. The #29 signal trap already makes the rescue RUN on interrupt, but its resumeCR/scaleStatefulSet calls used the run ctx — which an\n[…]\nnline closure and the fake client does not enforce ctx cancellation (same limitation noted on #29); the fresh-context resume pattern is proven at the provider level by the #29 Cleanup test. Fixes #30.",
"is_bot": false,
"headline": "fix(reconfigure): run the rescue on a fresh context so an interrupt c…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-16T19:51:56Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "2b47606024884b902e89822a3870b573dd96e04e",
"body": "…ine bootstrap path\n\nThe remedy for the galera-operator-recovery-deadlock diagnosis (triage catalog entry #1). Bootstrap gains a second path to its one goal — force the operator to bootstrap the authoritative node — selected from cluster state: ALL DOWN uses the existing offline flow; operator recov\n[…]\n, no wedge flag; honors --dry-run. Exactly the manual fix validated live on kimai. Provider gains ForceBootstrapLive + ClearForceBootstrap. Command help documents both paths. 4 unit tests. Closes #27.",
"is_bot": false,
"headline": "feat(bootstrap): resolve galera-operator-recovery-deadlock via an onl…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-16T19:48:00Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f3392a7094ec07bfe03075f572ab8203ea072cb7",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-16T19:47:51Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ac6ea39353ff9ceb5e3e2892469b71e9e2c56e9c",
"body": "…ator-recovery-deadlock is entry #1\n\nPart-1's OperatorWedge was too vague a label. Rework it into a general, extensible pattern: triage accumulates a catalog of precise, named (condition -> safe remedy) entries on TriageResult.Diagnoses, printed generically (DIAGNOSIS: <id>, cause, remediation targe\n[…]\n 'hasteward bootstrap ... --dry-run' and names the bootstrap source as Target. Validated against the live kimai signature; 7 unit tests. Remediation (bootstrap online path) is the follow-on. Refs #27.",
"is_bot": false,
"headline": "refactor(triage): rework triage into a diagnosis catalog; galera-oper…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-16T19:39:36Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "31fbc94c79a1ffb03b7d243f338cf4545b511863",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-15T20:09:09Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "5689aa0abd412586b769f12bc28a2f34312a0ab3",
"body": "…ntext\n\nSafetyGate suspends the operator CR up front; the resume is a deferred Cleanup. But the CLI ran cobra's plain Execute() (no signal handling), so SIGINT/SIGTERM hard-killed the process and deferred cleanup never ran — stranding the operator spec.suspend=true (which Flux later reconciles away,\n[…]\nhe resume reaches the API even when the run context is dead. Guarded by tests: Cleanup resumes (patches suspend:false) with an already-cancelled context, and no-ops when it never suspended. Fixes #29.",
"is_bot": false,
"headline": "fix(repair): run cleanup on interrupt and resume the CR on a fresh co…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T20:01:33Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a741570780d3f4ce4c238826d5a851c3c9d34d82",
"body": "…on a preview)\n\nrepair --dry-run performed a full live repair for every engine: there was no cfg.DryRun check in the shared flow, so galera reached SafetyGate (which patches spec.suspend=true on the operator CR) and both engines reached Heal (datadir clear/rebuild). Only CNPG's --unwedge deadlock br\n[…]\nly phase — before SafetyGate, Escrow, or Heal touch the cluster. Guarded by a service-level test asserting a dry-run reaches none of the mutating phases while a real run still reaches Heal. Fixes #28.",
"is_bot": false,
"headline": "fix(repair): honor --dry-run in the shared repair flow (never mutate …",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T20:01:33Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "48ad83023ef6c6c26f9434ad200917dd97bd13c6",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-15T19:52:57Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "85c3bcdf323ee9f8c86524cd2f448eaee9d889e1",
"body": "…overy)\n\nTriage was data-plane only: on a wedged mariadb-operator it reported 'No action needed' while the cluster flapped, because the wedge lives in the control plane — which triage read (GaleraReady condition, status.galeraRecovery) but only for display, never as a verdict.\n\nAdds detectOperatorWe\n[…]\ne cases (operator agrees, no snapshot, a node has a valid seqno, real heal work, ambiguous authority, all-down). Part 1 of #27; remediation (reachable force-bootstrap + clear-status) is the follow-on.",
"is_bot": false,
"headline": "feat(galera): detect a wedged operator (data healthy but stuck in rec…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T19:43:23Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1e6e704c43a066ccf59041940a8d1b976d1773f2",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-15T17:03:50Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ca554596f19e20d450fce9c5164ed5f2a31d3d8d",
"body": "Declare an explicit go test suite so StageFreight runs 'go test ./...' with -cover (populating the per-package cov column that was blank under the auto-synthesized suite) and gates on the statement-weighted whole-module total. Floor set to 15% — below the current ~17.4% with headroom so it catches a\n[…]\ns; raise as coverage grows. Gate defaults to perform, so a drop below the floor fails the suite. Validated through StageFreight's config loader (enabled=true, 1 suite, coverage=true, coverage_min=15).",
"is_bot": false,
"headline": "chore(test): enable go test coverage reporting and a regression gate",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T16:56:02Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a077a011031b38f70d9c145c2a85644bd59c048f",
"body": "…f using the stale captured one\n\nhcfg.primaryIP is captured during Assess and consumed much later as pg_basebackup -h <ip> in Heal. If CNPG failed the primary over between Assess and Heal, basebackup would clone from a stale node (fails clean, but an unverified-value-drives-action smell). healInstan\n[…]\nrrors the discoverHealConfigLive pattern the deadlock breaker already uses. Guarded by a flow test asserting the helper's basebackup command targets the live IP, not the stale captured one. Fixes #24.",
"is_bot": false,
"headline": "fix(cnpg): re-resolve the primary IP live before basebackup instead o…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T16:50:09Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f24ea8fe26fc80368aa90facd71417f07c9e3e24",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-15T16:39:08Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "303f82550f31d8e53cf7247e9918c65f5a609111",
"body": "…rce the LSN safety gate\n\nThe WAL prune shipped one monolithic bash script (sh -c walScript) that computed the checkpoint REDO boundary AND deleted segments, run-once inside a helper pod. Because all logic lived in bash at delete-time, it was untestable and the LSN precondition in its own docstring \n[…]\nhan-REDO delete list in Go and rm it. Fully unit-tested via the exec hook: abort-on-lag, delete-only-older-segments, no-replica-without-force, force-override, and LSN parsing.\n\nCloses #23. Closes #25.",
"is_bot": false,
"headline": "refactor(cnpg): Go-owns-prune: move WAL-prune logic out of bash, enfo…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T16:31:47Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "43acafa3ea9d807dbc1fb8585b4fbaf35cd649d0",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-15T16:12:49Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "39d31bf0254c21fc30549531824f0dbdb9930df1",
"body": "… success\n\ncnpgjob.Run's final unfence (STEP 7) only WARN-logged on failure, set fenceApplied=false, and returned nil — so an instance left fenced (which CNPG will not manage, meaning it has NOT rejoined) was reported as a successful heal, and the orchestrator recorded a dead, unmanaged instance as \n[…]\nommand) and returns an error on unfence failure. Guarded by a flow test that drives Run's full happy path through fakes and injects an unfence-patch failure, asserting Run returns an error. Fixes #21.",
"is_bot": false,
"headline": "fix(cnpg): fail when the post-heal unfence fails instead of reporting…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T16:05:18Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "4d358ea0f08a5c9829a6fa1d37c556321fe9baf0",
"body": "An untargeted repair heals every NeedsHeal instance, and healInstance fences the target then rm -rf's its pgdata before a pg_basebackup rebuild. Nothing stopped that destructive path from running against the CURRENT PRIMARY if it was ever flagged NeedsHeal — a self-inflicted outage and data loss. he\n[…]\ntPrimary; if the primary cannot be verified it refuses rather than gamble on a destructive rebuild. Guarded by a flow test asserting no helper pod is created when the target is the primary. Fixes #22.",
"is_bot": false,
"headline": "fix(cnpg): refuse to heal the current primary (fail-closed guard)",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T16:02:44Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "97707b725afe195a76106bebcc7138ac48f1929c",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-15T15:53:55Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6c0d64951f877ff7f822a37ce51959f68640ec7d",
"body": "…r of #7)\n\ncnpgRepair.healInstance returned nil when the target replica never became Ready after the offline-PVC heal, so the orchestrator recorded it in HealedInstances despite it never rejoining — the CNPG twin of galera #7 (and worse: galera already returned an error here). Now returns an error o\n[…]\ncnpgjob.Run offline-job path through fakes (auto-succeeded helper) then asserts the never-Ready wait returns an error. Seams repair/cnpg.go sleeps through common.Sleep so it runs instantly. Fixes #19.",
"is_bot": false,
"headline": "fix(cnpg): return an error when a healed replica never rejoins (mirro…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T15:44:05Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c6bff0c11ce8c15f1949a291762888f830f553ce",
"body": "…t unreconciliation)\n\nOfflinePVCJob's deferred restoreReconciliation — its headline safety net — was armed only AFTER the disable PATCH returned success. A PATCH can commit server-side while the client sees an error (apiserver rollout, timeout mid-response), which took the error branch and returned \n[…]\nng early is safe. Adds a flow test injecting the disable failure and asserting the re-enable patch still fires; routes cnpgjob's poll/retry sleeps through common.Sleep so it runs instantly. Fixes #20.",
"is_bot": false,
"headline": "fix(cnpg): arm reconciliation-restore before the disable (no permanen…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T15:39:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "cd9f098c8f5dc02c58b1a8a05b0a4d9a9520aeb2",
"body": "…rsion CAS)\n\nHardens #16's serialization to close the residual race. Replaces deepRecover's separate suspend-check + annotation-set (a Get-then-Patch with a millisecond window) with provider.AcquireFenceLock: one Get that checks spec.suspend and a fresh (<1h) lock, then an Update carrying the CR's r\n[…]\nnotation (no bootstrap change, no new RBAC). Removes the now-superseded IsCRSuspended/SetFenceLock. Adds TestAcquireFenceLock (acquired / suspended / fresh-lock / stale-lock / CAS-conflict). Refs #16.",
"is_bot": false,
"headline": "fix(triage): make deepRecover's fence-lock acquire atomic (resourceVe…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T15:03:41Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ac9c2a2091c53c59bf66bdc1719c1fe49f56d0af",
"body": "…p rescue\n\nexecuteBootstrap's rollback relied on remembering to call rescue() at each of ~9 error returns — the fragile pattern that harbored bug #5. Adds a committed flag (set only after STEP 8 scale-up, the point of no return) and a single deferred backstop 'if !committed { rescue() }', so ANY exi\n[…]\nruns at most once), composing with the existing explicit calls. Post-commit failures (STEP 9-11 cleanup) correctly do NOT roll back. Structurally mirrors triage.deepRecover's defer-restore. Fixes #17.",
"is_bot": false,
"headline": "refactor(bootstrap): defer-backstop rollback so no error path can ski…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T13:06:46Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "96a0f5c18ba0feb1eae4167dd45a9380e2053281",
"body": "…hint fallback\n\nOn a failed recover, executeBootstrap fabricated a Valid:false result carrying the node's triage EffectiveSeqno (a hint). Since #2 those entries cannot nominate authority, but the STEP 4b gcache IST-gap loop still consumed the fabricated seqno — a hint could skew maxSeqno/gap and tri\n[…]\nsary galera.cache wipe (forced SST). Now a failed recover is simply excluded (mirrors triage.deepRecover); the belly-up authority gate already handles a node with no authoritative position. Fixes #18.",
"is_bot": false,
"headline": "fix(bootstrap): skip a failed wsrep_recover instead of fabricating a …",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T13:03:13Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "dde3684dc36ab47377a4f89aa0bf38651d4ed2a4",
"body": "deepRecover mutated cluster state (suspend/scale-0/wsrep_recover/restore) inside triage's lock-free path, with no protection against a concurrent bootstrap or another triage — a triage running during a bootstrap could have its deferred ResumeCR/scale-up undo the bootstrap's fence mid-flight. Now dee\n[…]\nng mid-recover aborts. Adds provider.IsCRSuspended/SetFenceLock/ClearFenceLock; points bootstrap's lock const at the shared annotation. Guarded by TestDeepRecover_SkipsWhenAlreadySuspended. Fixes #16.",
"is_bot": false,
"headline": "fix(triage): serialize deepRecover's fence against concurrent operations",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T13:01:09Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9fb82004a614f4f9ba41fae067e66180b06e77e1",
"body": "triage.runPVCProbes hand-rolled the same create -> poll -> logs -> delete helper-pod lifecycle that RunHelperPod already provides. Adds RunHelperPodSpec(HelperPodSpec) — options-based (read-only mount, node pinning, custom command, custom label) returning (logs, succeeded, error). RunHelperPod is no\n[…]\ns of duplicated lifecycle. Removes the now-dead logHelperPodOutput (RunHelperPodSpec returns logs directly). Flow tests (executeBootstrap/healNode use RunHelperPod) still green. Completes #8 (item 7).",
"is_bot": false,
"headline": "refactor(provider): triage PVC probe reuses the shared helper-pod runner",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T11:18:37Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f9890ebdca556dfb3157a72461c704f4f7bb302e",
"body": "The wsrep GLOBAL_STATUS query + tab-parse was implemented three times (triage collect, repair.probeWsrep, reconfigure). Adds provider.QueryWsrep(ctx, pod) returning every wsrep_* status var as a lowercased name->value map (LIKE 'wsrep_%%', a superset of all three sites' needs); each caller reads the fields it needs. parseWsrepStatus is now map-based (+ a unit test); repair.probeWsrep keeps its retry and ExecOK/parser-mismatch semantics. One GLOBAL_STATUS query site remains. Advances #8 (item 5).",
"is_bot": false,
"headline": "refactor(provider): dedup wsrep queries into provider.QueryWsrep",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T11:13:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "79acb2afd84d0d58cf228c6c9f50173d8d32ff51",
"body": "Completes the low-risk remainder of the intra-Galera consolidation (#8), most of which was already absorbed by #3/#9/#10/#11/#12. healNode's SA capture now uses k8s.ServiceAccountFromPods; its inline target-pod-gone loop now delegates to k8s.WaitForPodGone; the two raw zero-UUID literals in triage n\n[…]\nlera-config PVC name is now provider.GaleraPVCName (mirrors DataPVCName), replacing the galera-%s literals in repair/reconfigure/triage. Drops the now-unused apierrors import from repair. Advances #8.",
"is_bot": false,
"headline": "refactor(galera): absorb remaining low-risk #8 consolidation items",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T10:29:14Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "909e89dc0a3827a97ac1d261418cc0e381e741b2",
"body": "…Gone\n\nLifts two engine-agnostic ops off the Galera provider. FindCondition (a pure status.conditions[] parser) moves from provider/galera.go to k8s.FindCondition, next to the GetNested* status helpers — its natural home, reusable by any engine; the provider + repair callers now use k8s.FindConditio\n[…]\n CNPG caller (moving it is churn without dedup), and CNPG's helper-pod equivalent is the distinct cnpgjob.OfflinePVCJob (fencing-based), not the same primitive. Completes #10's genuinely-shared scope.",
"is_bot": false,
"headline": "refactor(k8s): move FindCondition to src/k8s + consolidate waitForPod…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T09:28:39Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "23cb276e308a9decad694ac26964b43d3a245523",
"body": "The retention Prune body was near-verbatim between galera and cnpg — restic client, policy build, log line, the backup Forget + diverged ForgetGrouped blocks, and the keep/remove tally were all identical. Only the engine tag differed (== r.Name()), and galera used a copyTags helper while cnpg inlined the same copy loop. Extracts runPrune(ctx, cfg, engine, opts) + copyTags into prune.go; both Prune methods delegate. No behavior change. Closes #14.",
"is_bot": false,
"headline": "refactor(retention): collapse Prune into shared runPrune",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T08:39:50Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a7a488f70e4fa09674545b4aca51c06734e30738",
"body": "The untargeted (all-members) heal plan was identical between galera and cnpg except the member noun (nodes/replicas). Extracts buildUntargetedPlan(result, noun); both planUntargeted methods delegate. planTargeted is intentionally left per-engine: its primary protection genuinely diverges (galera war\n[…]\ns on the current primary, at different positions) and unifying that load-bearing safety gate is not worth the risk for the small remaining overlap. Adds a unit test for the shared plan. Completes #13.",
"is_bot": false,
"headline": "refactor(repair): dedup planUntargeted into buildUntargetedPlan",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T08:34:21Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "6641be88289357f1cafc27e86bea4ee0d2e22904",
"body": "…air escrow)\n\nThe repair Escrow phase was copy-pasted between galeraRepair and cnpgRepair — the pre-repair-backup guard and the entire diverged per-instance backup loop were identical, differing only in the donor source (galera: resolved donor; cnpg: currentPrimary) and the dump filename. Extracts r\n[…]\ndonor case). Adds a unit test via a fake Backer covering safe/split-brain/no-escrow/missing-creds/empty-donor/backup-failure/best-effort-diverged. Advances #13 (Escrow done; plan scaffolding remains).",
"is_bot": false,
"headline": "refactor(repair): extract shared runEscrow (dedup galera/cnpg pre-rep…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T08:31:24Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9344f460b64232100662b4f61bae0cd3eee46004",
"body": "Third full-sequence flow test, guarding the #7 fix. healNode drives the full suspend -> scale-down -> wipe-helper -> scale-up -> resume sequence against a fake API server, then the fake never recreates the target pod so the Ready-wait exhausts. Asserts healNode returns an ERROR (not nil) — so the orchestrator never records an un-rejoined node as healed — plus the scale sequence [2,3], that the wipe helper ran, and that the CR was resumed. Runs instantly thanks to the sleep seam. Advances #15.",
"is_bot": false,
"headline": "test(repair): add healNode never-ready flow test (#7 guard)",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T08:02:40Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "fa1e40d79ab928e0f4e750d983909003f5916dcc",
"body": "Adds common.Sleep + SetSleepForTest/DisableSleepForTest and routes the hot-path poll/retry sleeps (galera_ops RunWsrepRecover/RunHelperPod/WaitPodsTerminated, k8s.WaitAllReady, repair.healNode, bootstrap.executeBootstrap) through it. Tests call common.DisableSleepForTest() to run those loops with no real delay. Retrofits the deepRecover and executeBootstrap flow tests: 7s -> 0.02s and 26s -> 0.02s. No production behavior change (Sleep defaults to time.Sleep). Advances #15.",
"is_bot": false,
"headline": "test(common): add injectable sleep seam so flow tests run instantly",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T08:00:17Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "740846131ebd45ed6c8b38070e20fe0ae4c5c191",
"body": "…uard)\n\nSecond full-sequence Tier-2 flow test, guarding the #5 fix. Drives executeBootstrap through fence -> wsrep_recover -> authority mutations against a fake API server, then injects a STEP 8 scale-up failure so rescue() runs, and asserts the rescue is symmetric: forceClusterBootstrapInPod (STEP \n[…]\n/SetPodLogsHookForTest) + reactor patterns from the deepRecover test. Runs ~26s due to RunWsrepRecover/RunHelperPod real poll sleeps; a future injectable poll-interval would speed it up. Advances #15.",
"is_bot": false,
"headline": "test(bootstrap): add executeBootstrap rescue-symmetry flow test (#5 g…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T07:10:08Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c84d62d5fb777233490b481cabf7a25b1f7c8a4e",
"body": "The first full-sequence Tier-2 flow test. Drives triage's belly-up escalation against a fake API server (fake dynamic CR + fake clientset with scale/create reactors) plus canned wsrep_recover output, asserting the safety invariant end to end: deepRecover fences (suspend CR, scale to 0), reads each n\n[…]\neclares authority (no forceClusterBootstrapInPod). Adds the enabling seams: k8s.PodLogs + SetPodLogsHookForTest (helperPodOutput routes through it) and provider.NewGaleraProviderForTest. Advances #15.",
"is_bot": false,
"headline": "test(triage): add the deepRecover fence->recover->restore flow test",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T06:52:53Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ea5a659fb9dbc21cc71263b248a279c664547b37",
"body": "…xec hook)\n\nEstablishes the test seam Tier-2 needs: SetClientsForTest injects a kubernetes/fake + dynamic/fake -backed Clients, and SetExecHookForTest replaces the SPDY exec backend with canned results (the SPDY executor cannot be faked). Adds the first fake-client tests on top: readiness-by-name at\n[…]\nhe fake dynamic client (#1/#5), DeleteRecoveryPods (#6), and WaitPodsTerminated's all-gone success path. This is the net that de-risks the remaining consolidation refactors (#8/#10/#13). Advances #15.",
"is_bot": false,
"headline": "test(k8s): add the Tier-2 fake-client harness (injectable clients + e…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T06:44:18Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "fe0e22ef3d4fc98125d58ecc4e58912417e1704f",
"body": "…oded selector/PVC literals\n\nAdds PodSelector() and DataPVCName(pod) to the EngineProvider interface and both providers (Galera: app.kubernetes.io/instance=<cluster> and storage-<pod>; CNPG: cnpg.io/cluster=<cluster> and PVC==pod). Replaces the label-selector and PVC-name literals scattered across t\n[…]\nreconfigure/provider (galera) and repair/triage (cnpg) with the provider methods — one source of truth, no drift. Behavior-identical: the methods return the exact strings that were inline. Closes #12.",
"is_bot": false,
"headline": "refactor(provider): add PodSelector()/DataPVCName() and replace hardc…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T06:35:59Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ecc9a51605e34133ec0d99df1c8f7708e50999de",
"body": "Removes the ptr[T] generic that was copy-defined in 8 packages (provider, repair, bootstrap, triage, reconfigure, restore, escrow, cnpgjob) in favor of a single common.Ptr, and replaces the inlined delete/heal timeout defaults (300/600) with common.DefaultDeleteTimeout / common.DefaultHealTimeout. Pure dedup, no behavior change. Closes #11.",
"is_bot": false,
"headline": "refactor(common): centralize Ptr[T] and default operation timeouts",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T06:29:14Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "59c4d9367892d066e9c3d0e0e7290d8db3ca17a4",
"body": "…migrate CNPG off index 0\n\nCompletes the shared readiness work. Adds WaitAllReady and FindReadyPod to src/k8s and folds the three near-duplicate waitForAllReady loops (repair-galera, bootstrap-galera, repair-cnpg) and the two identical findHealthyPod functions (backup-galera, restore-galera) into th\n[…]\n). No ContainerStatuses[0] remains anywhere in src/engine; readiness is judged by container name everywhere behind one implementation, which also unblocks the fake-client test seam for #15. Closes #9.",
"is_bot": false,
"headline": "refactor(k8s): finish shared readiness — WaitAllReady/FindReadyPod + …",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T05:37:34Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d237ea775ac651df9b6e3ef66322368f41427461",
"body": "healNode returned nil when the target never became Ready within the heal timeout, so the orchestrator recorded it in HealedInstances despite it never rejoining. It now returns an error in that case, so the result is honest and the failure surfaces. The Ready-but-not-yet-Synced path is left as success: that is a legitimate transient in-cluster state (Donor/Joined catching up) which the post-repair reassess confirms, and hard-failing it would false-alarm on a slow SST. Fixes #7.",
"is_bot": false,
"headline": "fix(repair): do not report a never-Ready node as healed",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T05:00:08Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2cc29ed8ca00a01e1cc0ef8967d49115d0bd6755",
"body": "…e the fence\n\nTwo fence/rollback safety fixes in executeBootstrap. (1) rescue() cleared the generation lock, restored scale, and resumed the CR but left safe_to_bootstrap=1 (STEP 6) and forceClusterBootstrapInPod (STEP 7) in place — so a STEP 8 scale-up failure resumed the operator still configured \n[…]\neletes it while the CR is suspended) could stall WaitPodsTerminated and spuriously fail a valid bootstrap. Now deletes recovery pods before the wait, matching triage's deepRecover. Fixes #5, fixes #6.",
"is_bot": false,
"headline": "fix(bootstrap): symmetric rescue rollback + clear recovery pods befor…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T04:57:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1d22587d38b00dceb3d9b96a5c7058b7fa521281",
"body": "…dex 0\n\nKubernetes sorts pod.Status.ContainerStatuses alphabetically, so on a Galera pod (spec [mariadb, agent]) index 0 is the agent sidecar. Every ContainerStatuses[0].Ready check was reading the agent, not mariadb: triage missed a crashlooping mariadb behind a Ready agent, waitForAllReady/heal-re\n[…]\nsite (triage, repair, bootstrap, backup, restore, reconfigure). CNPG pods are single-container (postgres) so their [0] sites are correct-by-accident and left for the dedup pass. Fixes #3; advances #9.",
"is_bot": false,
"headline": "fix(k8s): judge mariadb readiness by container name, not container in…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T04:53:18Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ce64f2af93122523fbcf3a6ad5d0f93816d7c999",
"body": "…rride the bootstrap target\n\nA failed wsrep_recover was fabricated as a Valid:false result carrying the node's triage EffectiveSeqno (a hint), and neither buildLineageGroups nor selectCandidate checked rr.Valid — so a stale hint could form a phantom lineage or override the authority node (data-loss \n[…]\ner win, keeping the original candidate only when no authoritative recover exists. Excludes UUID=unknown (triage's no-data sentinel). Un-skips the #2 guard test and adds a #4 guard. Fixes #2, fixes #4.",
"is_bot": false,
"headline": "fix(bootstrap): never let a non-authoritative recover nominate or ove…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T04:49:13Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "51a58b2fe0e4e47c3c141465395d57d143ad323b",
"body": "…t path\n\nSafetyGate suspends the MariaDB CR before the donor probe, but only healNode resumed it — so a run that exited before healing (escrow failure, a healthy target, nothing to heal) left the operator suspended indefinitely with no reconciliation. Adds Cleanup to the Repairer contract, deferred \n[…]\nresumes if still suspended, and crSuspended is now kept truthful at every suspend/resume so Cleanup is idempotent and never double-resumes. cnpgRepair.Cleanup is a no-op (it never suspends). Fixes #1.",
"is_bot": false,
"headline": "fix(repair): guarantee the operator CR is resumed on every repair exi…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T04:45:34Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e1c4d1af62fe32db041bf3b3cf5a0171ada87a0f",
"body": "triage.Classify folds a completed TriageResult into the pure taxonomy {healthy, auto-heal, bootstrap-required, operator-required} — the auto-repairable-vs-operator boundary — mirroring the repair/bootstrap gates (advisory for now). TestClassify_Scenarios drives it with a 7-case table of real cluster states. Adds a skipped bootstrap guard (TestBuildLineageGroups_ExcludesInvalidRecover) that pins the failed-recover hint-override (issue #2); un-skip when the !Valid exclusion lands. See issue #15.",
"is_bot": false,
"headline": "test(triage): add Tier-1 classification scenario suite + bug #2 guard",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T04:40:40Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "af991b3fd6a8af16fa1a9efd23a7810d6a11efac",
"body": "…-brain\n\ncrossInstanceComparison now takes each node's most-authoritative UUID (fenced recover > live wsrep_cluster_state_uuid > grastate.dat) and consults the operator galeraRecovery snapshot / mariadbd-log scrapes only when NO node yields an authoritative UUID. Verified live on osticket: the clust\n[…]\nr to state its actual trigger (UUID divergence / unread / seqno advance) instead of hardcoding a seqno claim. Adds grastateUUIDFor + a regression test pinning the live scenario and the blind-fallback.",
"is_bot": false,
"headline": "fix(triage): stop stale hint UUIDs from manufacturing a phantom split…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T04:40:40Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "413ca0f2ab41f1e92051398e156882bd5f29e2d9",
"body": "…elly-up cluster\n\nWhen nothing is serving, triage now fences the cluster (suspend CR, scale to 0, confirm terminated), reads each node's authoritative uuid:seqno via wsrep_recover, then restores on all exit paths. Pure evaluation: it never declares authority or sets safe_to_bootstrap, so it cannot h\n[…]\nates data.wsrepRecovered (authoritative) for the seqno derivation. Paranoid, fail-closed gate: isAnythingAlive AND a fresh authenticated SELECT 1, failing closed when the root password is unavailable.",
"is_bot": false,
"headline": "feat(triage): auto-escalate to a fenced wsrep_recover on a provably b…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T04:40:40Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "b5ce7b00900323feded9112fb446a635b53e809c",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-15T01:57:22Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "31d9391274b3686f607e357e937eae54618b30d6",
"body": "…ared by bootstrap + repair)\n\nThe Galera fence/recover primitives (SuspendCR, ResumeCR, ScaleStatefulSet, DeleteRecoveryPods, RunWsrepRecover, RunHelperPod, ParseWsrepRecoverOutput) were duplicated in the bootstrap and repair engines — the code even flagged them 'duplicated from galera engine'. They\n[…]\nils closed.\n\nPure refactor + safety hardening — triage untouched, full engine build/vet/tests green. Sets up the provider so triage can escalate to a fenced wsrep_recover without duplicating anything.",
"is_bot": false,
"headline": "refactor(galera): consolidate cluster k8s ops onto GaleraProvider (sh…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-15T01:49:50Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5de336bc9fb160256f3648700442109a16bf8235",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-14T15:40:22Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6d54f29846f8f2d69756b19433faca07f3baba87",
"body": "…live)\n\nHASteward's Cloudflare + GitHub Pages sites are both live (github.io self-enabled via ensurePages), so move both docs targets from every-main-push to git_tags: [stable] — ship docs once per release, matching StageFreight.",
"is_bot": false,
"headline": "chore(pages): gate docs deploys to release tags (both sites verified …",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-14T15:33:02Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d3ad70aad7f6d7bb7004b106a81bbcda05529d4a",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-14T15:18:29Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8462ab67f0f707099dc4ae33164110246e1126e1",
"body": "…surePages)\n\nThe docs-github comment's 'must enable Pages manually' caveat is obsolete: the StageFreight github pages provider now calls the Pages API to enable source=gh-pages after the verified push. This commit also triggers a deploy on the ensurePages-enabled image to verify it end-to-end.",
"is_bot": false,
"headline": "docs(pages): note that the provider now auto-enables GitHub Pages (en…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-14T15:09:59Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1e3acf156dea7670a1e7c1d2e0fbd0d5b544f2af",
"body": "…teward-docs'\n\nSelf-named project matching StageFreight. The old hasteward-docs Pages project is now orphaned — delete it in the Cloudflare dashboard; the hasteward.prplanit.com domain re-attaches to the new project on next deploy.",
"is_bot": false,
"headline": "chore(pages): name the Cloudflare Pages project 'hasteward', not 'has…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-14T08:28:54Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "55c4093794202133ae3991ce3e96000f55c7d324",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-14T07:55:31Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "18fbf659d5cb9922d14b4f5ad0691a776dfb9660",
"body": "The docs-site build passed --site-dir {output} (= 'site'), which mkdocs resolves relative to the config file at docs/assets/mkdocs/ — landing site_dir inside docs_dir and aborting ('site_dir should not be within docs_dir'). Since the config lives under docs/ (user preference), point --site-dir at an absolute $(pwd)/site (the container workspace root, /workspace/site) — outside docs_dir and exactly where SF's 'source: site' output captures it. Also gitignore site/.",
"is_bot": false,
"headline": "fix(ci): docs-site: write mkdocs output outside docs_dir",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-14T07:46:31Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "21dca2c11622cb680231ef062a44a55bbe775061",
"body": "…GitHub Pages\n\nAdd a MkDocs Material documentation site — config under docs/assets/mkdocs (kept out of docs_dir via exclude_docs), a teal landing hero, left-sidebar nav, and content reorganized into usage/design/config/about. Reference pages assemble the binary-generated fragments (assets/modules) v\n[…]\nanit.com) and GitHub Pages (gh-pages branch of the mirror). Both gated on main push for now to exercise the deploy before gating to release tags. Release targets adopt type: latest / type: prerelease.",
"is_bot": false,
"headline": "docs: MkDocs Material site (hero + autogen reference) → Cloudflare + …",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-14T07:18:38Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "cb669d81925682a2596663305bac62b77e871582",
"body": "Add an env-binding registry (src/env) that declares each persistent flag's HASTEWARD_ variable exactly once, and a docsgen generator framework (internal/docsgen) with CLI + Environment generators. 'docs generate --output-dir' now emits one markdown fragment per registered generator (cli-reference, e\n[…]\nnv registry so no metadata is duplicated. root.go binds every persistent flag via env.* — a single declaration point that creates the flag, seeds its env default, and registers the flag-variable link.",
"is_bot": false,
"headline": "feat(docs): generate CLI + environment reference from the binary",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-14T07:18:37Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f51de7a108afcb8195ce21096dcef8c313fa1bd9",
"body": "Generated-By: StageFreight",
"is_bot": false,
"headline": "docs: refresh generated docs and badges",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-14T05:58:42Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ded13a71fa6d0019438b0910b51cfbb00ac6fbf7",
"body": "…low rules\n\nThe committed pipeline predated StageFreight's provenance-based loop prevention, so the audition ci-render --check gate failed as stale. Regenerated via stagefreight ci render gitlab against latest-dev.",
"is_bot": false,
"headline": "ci: re-render .gitlab-ci.yml — add StageFreight loop-prevention workf…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-14T05:50:56Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b1982c6a432ee71eb149b8d9fb2ca4a3730b998f",
"body": "…ad of aborting\n\nA fully belly-up cluster (every mysqld crashed holding its datadir, so grastate is -1 and no node has a Known seqno) aborted at Gate 2 (\"could not determine best seqno node\") BEFORE the fenced wsrep_recover — the one operation that establishes authoritative positions. Chicken-and-eg\n[…]\nith tests (single lineage; split-brain majority-first; excludes zero-UUID/phantom seqnos). This is the 'stabilize when provably safe' path: fence + recover is low-risk on a cluster with nothing alive.",
"is_bot": false,
"headline": "fix(bootstrap): fence + wsrep-recover a belly-up Galera cluster inste…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-14T00:06:58Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "306eb83f7b1c1f162f905a0134ba19902a46e8d8",
"body": "…he hasteward binary\n\nThe Go CLI/operator (triage/repair/bootstrap) supersedes this Ansible logic, which predates the fixed recovery engine (trustworthy seqno derivation, fail-closed authority) and can give a wrong assessment on a production DB. Banner added so nobody runs the stale path.",
"is_bot": false,
"headline": "docs(recovery): deprecate the Ansible recovery playbook in favor of t…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-14T00:06:58Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "910908bf89d18c2eb3526a3df2867706922592b6",
"body": "Narrator: StageFreight\nCue: narrate\n\nX-StageFreight-Generated: true",
"is_bot": false,
"headline": "docs: refresh generated docs and badges [skip ci]",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-12T00:43:28Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c3fb62800cb314c6aefedecc0a08256f6d5e698e",
"body": "X-StageFreight-Generated: true",
"is_bot": false,
"headline": "chore(deps): update managed dependencies",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-12T00:34:12Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9e771b85ad93c300028b7966e225f771c60fb091",
"body": "X-StageFreight-Generated: true",
"is_bot": false,
"headline": "chore(config): dissolve docs/badges/narrator into the narrate schema",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-12T00:26:46Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "5fc3729b2bde3bedc51a0c4bed4a11ac40004a57",
"body": "X-StageFreight-Generated: true",
"is_bot": false,
"headline": "chore(deps): update managed dependencies",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-07-08T17:42:40Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "fbbf18793bfb138227f8cd1965328db268917ffb",
"body": "fmt.Fprintln already appends a newline; the trailing \\n in the arg is a vet 'redundant newline' error, which go test treats as a build failure. Switched to fmt.Fprint with an explicit \\n\\n — identical output (heading + blank line), vet-clean. Surfaced once the CI re-render let the pipeline reach the test phase.\n\nX-StageFreight-Generated: true",
"is_bot": false,
"headline": "fix(docsgen): drop redundant newline in Fprintln (go vet build failure)",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-08T17:35:22Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "fb3885c67792a372eaf5ac1434d5ddf6aa466872",
"body": "The audition contract now renders when: always on the audition job; regenerating .gitlab-ci.yml to match and clear the stale-CI audition failure.\n\nX-StageFreight-Generated: true",
"is_bot": false,
"headline": "ci: regenerate pipeline to match current StageFreight render",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-08T17:18:51Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9f97f8eee8c56ba15a0ca90be19d7271b3db7144",
"body": "…l-closed authority\n\nX-StageFreight-Generated: true",
"is_bot": false,
"headline": "fix(galera): trustworthy seqno derivation, working wsrep-recover, fai…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-07-03T22:35:42Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4e5cac459a0729783de079a02df9c1cf5b0e9f6e",
"body": "…e + needs heal\n\nA same-timeline replica that is behind and not streaming cannot catch up by replication — the WAL it needs has been recycled (shows as crash-looping or idle-not-streaming). classifyInstance treated EVERY same-timeline replica as Recoverable, and the assess switch left the crash-loop\n[…]\nck, which stranded never sets); just-missing pods keep their optimistic wait-for-CNPG handling. Adds classifier cases for stranded->disposable and authority-precedence.\n\nX-StageFreight-Generated: true",
"is_bot": false,
"headline": "fix(triage): classify same-timeline WAL-stranded replica as disposabl…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-06-29T05:08:58Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "b896e40c6ff6e42ddb3a5c363ad942fe68f73c15",
"body": "Narrator: StageFreight\nCue: docs/narrator\n\nX-StageFreight-Generated: true",
"is_bot": false,
"headline": "docs: refresh generated docs and badges [skip ci]",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-06-13T19:16:23Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "43f28693f0c9a9338e2e71d48c8c7eedfdefbb3a",
"body": "Repair, unwedge and prune-WAL all toggle the same cluster-scoped\ncnpg.io/reconciliationLoop switch and perform read-modify-write updates to the\nshared cnpg.io/fencedInstances annotation. Two concurrent operations on one\ncluster corrupt each other: operation A re-enabling reconciliation (its restore)\n[…]\n(cnpg\nimplements it; galera is a no-op), and acquired directly in PruneWAL. The lock spans\nthe whole operation, not a single offline-job, because the RMW races span it.\n\nX-StageFreight-Generated: true",
"is_bot": false,
"headline": "feat(engine): serialize cluster operations with a coordination Lease",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-06-13T19:08:54Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "6ae4ed784af74e6dbdbbc63b30c488a24d80685e",
"body": "Narrator: StageFreight\nCue: docs/narrator\n\nX-StageFreight-Generated: true",
"is_bot": false,
"headline": "docs: refresh generated docs and badges [skip ci]",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-06-13T19:04:29Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "38c0624f39f45be7213fac475a3410b4e9fa7d44",
"body": "…le before unfence\n\nTwo gaps found auditing the reconcile bracket as a hard invariant:\n\n1. Context cancellation left the cluster unreconciled. The deferred re-enable\n reused the caller ctx, so on cancel/timeout/SIGINT the re-enable Patch ran on\n the cancelled context, failed, and only logged - l\n[…]\nn success.\n\nDoes not address concurrent repairs on one cluster (reconciliationLoop is\ncluster-scoped + fence is read-modify-write) - serialization is a separate change.\n\nX-StageFreight-Generated: true",
"is_bot": false,
"headline": "fix(cnpgjob): harden reconcile restore — detached-ctx retry + re-enab…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-06-13T18:57:19Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1ddb5c0f11679cda9f7c505d49ae49deba346784",
"body": "Narrator: StageFreight\nCue: docs/narrator\n\nX-StageFreight-Generated: true",
"is_bot": false,
"headline": "docs: refresh generated docs and badges [skip ci]",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-06-13T17:44:05Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f10da157c8f658651419f65d0d5288228bd9b721",
"body": "…rits the reconcile fix\n\nThe fence -> disable-reconcile -> acquire-PVC -> re-enable -> unfence dance was\ncopy-pasted in repair (healInstance, clearDatadirOffline) and prunewal, each with\nits own fenceInstance/unfenceInstance/logHealPodOutput. Lift it into a single\nsrc/engine/cnpgjob package (Run + F\n[…]\n json/io/types imports\n and the duplicated helpers; cnpg_offlinejob.go is removed.\n\nNo behavior change for repair (already validated live on nextcloud-2 and gitlab-1).\n\nX-StageFreight-Generated: true",
"is_bot": false,
"headline": "refactor(engine): extract shared cnpgjob.Run primitive; prunewal inhe…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-06-13T17:36:43Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "285cbd7cce6ecfcfbc484c9fd6706ec44094d8b8",
"body": "Narrator: StageFreight\nCue: docs/narrator\n\nX-StageFreight-Generated: true",
"is_bot": false,
"headline": "docs: refresh generated docs and badges [skip ci]",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-06-13T16:08:09Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6b4092829314b895e861d3a3e41e750774a111a1",
"body": "…s unreconciled\n\nrunOfflinePVCJob passed the reconciliation-loop toggle inverted: it ENABLED\nthe loop during the PVC handoff (re-introducing the race) and the deferred\ncleanup DISABLED it, leaving the cluster unreconciled on exit - the exact\nhazard the always-re-enable invariant exists to prevent. C\n[…]\ne) and false to re-enable (defer), with inline\n/* disabled */ and /* re-enabled */ labels so the bool cannot be misread again.\nThe heal data path itself was unaffected.\n\nX-StageFreight-Generated: true",
"is_bot": false,
"headline": "fix(repair): correct inverted reconcile-loop toggle that left cluster…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-06-13T16:00:58Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "25ca7ea7fa5faa9439e6b7b9cfb6e6c7dc5c5738",
"body": "Narrator: StageFreight\nCue: docs/narrator\n\nX-StageFreight-Generated: true",
"is_bot": false,
"headline": "docs: refresh generated docs and badges [skip ci]",
"author_name": "stagefreight",
"author_login": null,
"committed_at": "2026-06-13T15:49:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4b4b3e9a09955990738dc05419074a17cd75e8fa",
"body": "… race\n\nThe heal aggressively deleted the fenced instance pod for 300s hoping to\nwin its RWO PVC. On a healthy cluster that race is unwinnable: CNPG fencing\nstops Postgres but the operator keeps recreating the instance pod, which\nre-grabs the volume before the helper can mount it (proven live: a fen\n[…]\nw build only\ntheir helper pod spec and delegate, so the always-re-enable safety invariant\nlives in one place. prunewal shares the same race and is a separate follow-up.\n\nX-StageFreight-Generated: true",
"is_bot": false,
"headline": "fix(repair): acquire the heal PVC via reconcile-disable, not a delete…",
"author_name": "SoFMeRight",
"author_login": "SoFMeRight",
"committed_at": "2026-06-13T15:41:53Z",
"body_truncated": true,
"is_coding_agent": false
}
],
"releases_count": 11,
"commits_last_year": 242,
"latest_release_at": "2026-07-19T20:20:18Z",
"latest_release_tag": "dev-ad9b2d1",
"releases_from_tags": false,
"days_since_last_push": 3,
"active_weeks_last_year": 16,
"days_since_latest_release": 3,
"mean_days_between_releases": 4.3
},
"community": {
"has_readme": true,
"has_license": true,
"has_description": true,
"has_contributing": false,
"health_percentage": 37,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"ecosystem": {
"packages": [
{
"name": "github.com/PrPlanIT/HASteward",
"exists": true,
"license": null,
"keywords": [],
"ecosystem": "go",
"matches_repo": true,
"registry_url": "https://pkg.go.dev/github.com/PrPlanIT/HASteward",
"is_deprecated": false,
"latest_version": "v0.2.1",
"repository_url": "https://github.com/PrPlanIT/HASteward",
"versions_count": 4,
"total_downloads": null,
"dependents_count": null,
"deprecation_note": null,
"maintainers_count": null,
"monthly_downloads": null,
"first_published_at": null,
"latest_published_at": "2026-07-17T00:12:16Z",
"latest_version_yanked": null,
"days_since_latest_publish": 6
}
]
},
"popularity": {
"forks": 0,
"stars": 0,
"watchers": 0,
"fork_history": {
"days": [],
"complete": true,
"collected": 0,
"total_forks": 0
},
"star_history": {
"days": [],
"complete": true,
"collected": 0,
"total_stars": 0,
"collected_at": null
},
"open_issues_and_prs": 0
},
"ai_readiness": {
"has_nix": false,
"example_dirs": [
"examples"
],
"has_llms_txt": false,
"has_dockerfile": true,
"has_mcp_signal": false,
"bootstrap_files": [],
"api_schema_files": [],
"has_devcontainer": false,
"typecheck_configs": [],
"toolchain_manifests": [
"go.mod"
],
"largest_source_bytes": 53045,
"source_files_sampled": 141,
"oversized_source_files": 0,
"agent_instruction_files": [
"CLAUDE.md"
],
"agent_instruction_max_bytes": 756
},
"dependencies": {
"manifests": [
"go.mod"
],
"advisories": {
"error": null,
"scope": null,
"source": null,
"findings": [],
"collected": false,
"malicious": [],
"truncated": false,
"by_severity": {},
"advisory_count": 0,
"affected_count": 0,
"assessed_count": 0,
"malicious_count": 0,
"assessed_package": null,
"unassessed_count": 0,
"direct_affected_count": 0
},
"ecosystems": [
"go"
],
"dependencies": [
{
"name": "github.com/prometheus/client_golang",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.23.2"
},
{
"name": "github.com/robfig/cron/v3",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v3.0.1"
},
{
"name": "github.com/spf13/cobra",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.10.2"
},
{
"name": "github.com/spf13/pflag",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.0.10"
},
{
"name": "k8s.io/api",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v0.36.2"
},
{
"name": "k8s.io/apimachinery",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v0.36.2"
},
{
"name": "k8s.io/client-go",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v0.36.2"
},
{
"name": "sigs.k8s.io/controller-runtime",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v0.24.1"
}
],
"all_dependencies": {
"error": "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
"source": null,
"packages": [],
"collected": false,
"truncated": false,
"total_count": null,
"direct_count": null,
"indirect_count": null
}
},
"maintainership": {
"issues": {
"open_prs": 0,
"merged_prs": 0,
"open_issues": 0,
"closed_ratio": null,
"closed_issues": 0,
"closed_unmerged_prs": 0
},
"bus_factor": 1,
"bot_contributors": 0,
"top_contributors": [
{
"type": "User",
"login": "SoFMeRight",
"commits": 185,
"avatar_url": "https://avatars.githubusercontent.com/u/32850661?v=4"
}
],
"contributors_sampled": 1,
"top_contributor_share": 1
},
"quality_signals": {
"has_ci": false,
"has_tests": true,
"ci_workflows": [],
"has_docs_dir": true,
"linter_configs": [],
"has_editorconfig": false,
"has_linter_config": false,
"has_precommit_config": false
},
"security_signals": {
"lockfiles": [
"go.sum"
],
"scorecard": {
"checks": [
{
"name": "Binary-Artifacts",
"score": 10,
"reason": "no binaries found in the repo",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
},
{
"name": "Branch-Protection",
"score": 0,
"reason": "branch protection not enabled on development/release branches",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
},
{
"name": "CI-Tests",
"score": null,
"reason": "no pull request found",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
},
{
"name": "CII-Best-Practices",
"score": 0,
"reason": "no effort to earn an OpenSSF best practices badge detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
},
{
"name": "Code-Review",
"score": 0,
"reason": "Found 0/30 approved changesets -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
},
{
"name": "Contributors",
"score": 3,
"reason": "project has 1 contributing companies or organizations -- score normalized to 3",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
},
{
"name": "Dangerous-Workflow",
"score": null,
"reason": "no workflows found",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
},
{
"name": "Dependency-Update-Tool",
"score": 0,
"reason": "no update tool detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
},
{
"name": "Fuzzing",
"score": 0,
"reason": "project is not fuzzed",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
},
{
"name": "License",
"score": 10,
"reason": "license file detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
},
{
"name": "Maintained",
"score": 10,
"reason": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
},
{
"name": "Packaging",
"score": null,
"reason": "packaging workflow not detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
},
{
"name": "Pinned-Dependencies",
"score": 0,
"reason": "dependency not pinned by hash detected -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
},
{
"name": "SAST",
"score": 0,
"reason": "no SAST tool detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
},
{
"name": "Security-Policy",
"score": 0,
"reason": "security policy file not detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
},
{
"name": "Signed-Releases",
"score": null,
"reason": "no releases found",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
},
{
"name": "Token-Permissions",
"score": null,
"reason": "No tokens found",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
},
{
"name": "Vulnerabilities",
"score": 8,
"reason": "2 existing vulnerabilities detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
}
],
"commit": "0ce6d0775fdbe681663cb6f8790c3275be4ca599",
"ran_at": "2026-07-23T10:15:45Z",
"aggregate_score": 3.3,
"scorecard_version": "v5.5.0"
},
"has_codeql_workflow": false,
"has_security_policy": false,
"has_dependabot_config": false
},
"contribution_flow": {
"collected": true,
"ci_last_run_at": "2026-07-19T20:20:45Z",
"oldest_open_prs": [],
"last_merged_pr_at": null,
"ci_last_conclusion": null,
"oldest_open_issues": []
}
},
"config": {
"disabled_metrics": [],
"disabled_categories": [],
"disabled_components": {}
},
"source": {
"url": "https://github.com/PrPlanIT/HASteward",
"host": "github.com",
"name": "HASteward",
"owner": "PrPlanIT"
},
"metrics": {
"overall": {
"key": "overall",
"band": "at_risk",
"name": "Overall health",
"note": null,
"notes": [],
"value": 47,
"inputs": {
"security": 34,
"vitality": 85,
"community": 24,
"governance": 33,
"engineering": 54
},
"components": []
},
"categories": [
{
"key": "vitality",
"band": "excellent",
"name": "Vitality",
"value": 85,
"weight": 0.22,
"metrics": [
{
"key": "development_activity",
"band": "good",
"name": "Development activity",
"note": null,
"notes": [],
"value": 75,
"inputs": {
"commits_last_year": 242,
"human_commit_share": 1,
"days_since_last_push": 3,
"active_weeks_last_year": 16
},
"components": [
{
"key": "push_recency",
"name": "Push recency",
"detail": "last push 3 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "push_recency",
"params": {
"days": 3
}
}
],
"max_points": 36
},
{
"key": "commit_cadence",
"name": "Commit cadence",
"detail": "16/52 weeks with commits",
"points": 11.1,
"status": "partial",
"details": [
{
"code": "commit_cadence_weeks",
"params": {
"weeks": 16
}
}
],
"max_points": 36
},
{
"key": "commit_volume",
"name": "Commit volume",
"detail": "242 commits in the last year",
"points": 18,
"status": "met",
"details": [
{
"code": "commits_last_year",
"params": {
"count": 242
}
}
],
"max_points": 18
},
{
"key": "openssf_scorecard_maintained",
"name": "OpenSSF Scorecard: Maintained",
"detail": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
},
{
"key": "release_discipline",
"band": "excellent",
"name": "Release discipline",
"note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Signed-Releases. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"openssf_scorecard_signed_releases"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 100,
"inputs": {
"releases_count": 11,
"latest_release_tag": "dev-ad9b2d1",
"releases_from_tags": false,
"days_since_latest_release": 3,
"mean_days_between_releases": 4.3
},
"components": [
{
"key": "ships_releases",
"name": "Ships releases",
"detail": "11 releases published",
"points": 27,
"status": "met",
"details": [
{
"code": "releases_published",
"params": {
"count": 11
}
}
],
"max_points": 27
},
{
"key": "release_recency",
"name": "Release recency",
"detail": "latest release 3 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "release_recency",
"params": {
"days": 3
}
}
],
"max_points": 36
},
{
"key": "release_cadence",
"name": "Release cadence",
"detail": "a release every ~4.3 days",
"points": 27,
"status": "met",
"details": [
{
"code": "release_cadence",
"params": {
"gap": 4.3
}
}
],
"max_points": 27
},
{
"key": "openssf_scorecard_signed_releases",
"name": "OpenSSF Scorecard: Signed-Releases",
"detail": "no releases found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 10
}
]
},
{
"key": "abandonment",
"band": "excellent",
"name": "Abandonment",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"cap": null,
"state": "unverified",
"guards": [],
"signals": [],
"red_flag": false,
"multiplier_pct": 100,
"declared_reason": null,
"unverified_reason": "repository_too_young",
"unanswered_open_prs": null,
"unanswered_open_issues": null,
"days_since_last_merged_pr": null,
"days_since_last_human_commit": null,
"days_since_last_human_commit_is_floor": false
},
"components": [
{
"key": "project_is_still_maintained",
"name": "Project is still maintained",
"detail": "maintenance record not established from the collected data",
"points": 100,
"status": "met",
"details": [
{
"code": "abandonment_unverified",
"params": {}
}
],
"max_points": 100
}
]
}
],
"description": "Is the project alive — is code being written and are releases shipping?"
},
{
"key": "community",
"band": "critical",
"name": "Community & Adoption",
"value": 24,
"weight": 0.18,
"metrics": [
{
"key": "popularity",
"band": "critical",
"name": "Popularity & adoption",
"note": null,
"notes": [],
"value": 1,
"inputs": {
"forks": 0,
"stars": 0,
"watchers": 0,
"growth_state": "unverified",
"growth_factor_pct": 100,
"growth_unverified_reason": "no_history"
},
"components": [
{
"key": "stars",
"name": "Stars",
"detail": "0 stars",
"points": 0,
"status": "missed",
"details": [
{
"code": "stars",
"params": {
"count": 0
}
}
],
"max_points": 60
},
{
"key": "forks",
"name": "Forks",
"detail": "0 forks",
"points": 0,
"status": "missed",
"details": [
{
"code": "forks",
"params": {
"count": 0
}
}
],
"max_points": 25
},
{
"key": "watchers",
"name": "Watchers",
"detail": "0 watchers",
"points": 0,
"status": "missed",
"details": [
{
"code": "watchers",
"params": {
"count": 0
}
}
],
"max_points": 15
}
]
},
{
"key": "community_health",
"band": "moderate",
"name": "Community health",
"note": null,
"notes": [],
"value": 50,
"inputs": {
"has_readme": true,
"has_license": true,
"has_contributing": false,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 22.5,
"status": "met",
"details": [],
"max_points": 22.5
},
{
"key": "license",
"name": "License",
"detail": "recognized license (AGPL-3.0)",
"points": 22.5,
"status": "met",
"details": [
{
"code": "license_standard",
"params": {}
},
{
"code": "license_spdx",
"params": {
"spdx": "AGPL-3.0"
}
}
],
"max_points": 22.5
},
{
"key": "contributing_guide",
"name": "CONTRIBUTING guide",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 18
},
{
"key": "code_of_conduct",
"name": "Code of conduct",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 13.5
},
{
"key": "issue_template",
"name": "Issue template",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.2
},
{
"key": "pr_template",
"name": "PR template",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 6.3
}
]
}
],
"description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
},
{
"key": "governance",
"band": "at_risk",
"name": "Sustainability & Governance",
"value": 33,
"weight": 0.24,
"metrics": [
{
"key": "maintainer_resilience",
"band": "critical",
"name": "Maintainer resilience (bus factor)",
"note": null,
"notes": [],
"value": 13,
"inputs": {
"bus_factor": 1,
"contributors_sampled": 1,
"top_contributor_share": 1
},
"components": [
{
"key": "bus_factor",
"name": "Bus factor",
"detail": "1 contributor(s) cover half of all commits",
"points": 9,
"status": "partial",
"details": [
{
"code": "bus_factor",
"params": {
"count": 1
}
}
],
"max_points": 54
},
{
"key": "commit_distribution",
"name": "Commit distribution",
"detail": "top contributor authored 100% of commits",
"points": 0,
"status": "missed",
"details": [
{
"code": "top_contributor_share",
"params": {
"share": 100
}
}
],
"max_points": 22.5
},
{
"key": "contributor_breadth",
"name": "Contributor breadth",
"detail": "1 contributors",
"points": 1.4,
"status": "partial",
"details": [
{
"code": "contributors_sampled",
"params": {
"count": 1
}
}
],
"max_points": 13.5
},
{
"key": "openssf_scorecard_contributors",
"name": "OpenSSF Scorecard: Contributors",
"detail": "project has 1 contributing companies or organizations -- score normalized to 3",
"points": 3,
"status": "partial",
"details": [],
"max_points": 10
}
]
},
{
"key": "responsiveness",
"band": "critical",
"name": "Issue & PR responsiveness",
"note": "Excluded from scoring (no data or not applicable): Issue resolution, PR acceptance. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"issue_resolution",
"pr_acceptance"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 1,
"inputs": {
"merged_prs": 0,
"open_issues": 0,
"closed_issues": 0,
"issue_closed_ratio": null,
"closed_unmerged_prs": 0
},
"components": [
{
"key": "issue_resolution",
"name": "Issue resolution",
"detail": "no issues or no data",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_issues_or_data",
"params": {}
}
],
"max_points": 46.75
},
{
"key": "pr_acceptance",
"name": "PR acceptance",
"detail": "no decided pull requests or no data",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_decided_prs_or_data",
"params": {}
}
],
"max_points": 38.25
},
{
"key": "openssf_scorecard_code_review",
"name": "OpenSSF Scorecard: Code-Review",
"detail": "Found 0/30 approved changesets -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
}
]
},
{
"key": "stewardship",
"band": "at_risk",
"name": "Ownership & stewardship",
"note": null,
"notes": [],
"value": 42,
"inputs": {
"followers": 1,
"owner_type": "Organization",
"is_verified": null,
"owner_login": "PrPlanIT",
"public_repos": 12,
"account_age_days": 361
},
"components": [
{
"key": "ownership_backing",
"name": "Ownership backing",
"detail": "organization-owned",
"points": 30,
"status": "met",
"details": [
{
"code": "owner_organization",
"params": {}
}
],
"max_points": 30
},
{
"key": "verified_domain",
"name": "Verified domain",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 20
},
{
"key": "owner_reach",
"name": "Owner reach",
"detail": "1 followers of PrPlanIT",
"points": 2.2,
"status": "partial",
"details": [
{
"code": "owner_followers",
"params": {
"count": 1,
"login": "PrPlanIT"
}
}
],
"max_points": 25
},
{
"key": "track_record",
"name": "Track record",
"detail": "12 public repos, account ~0 yr old",
"points": 10.1,
"status": "partial",
"details": [
{
"code": "public_repos",
"params": {
"count": 12
}
},
{
"code": "account_age_years",
"params": {
"years": 0
}
}
],
"max_points": 25
}
]
},
{
"key": "package_maintenance",
"band": "excellent",
"name": "Package maintenance",
"note": null,
"notes": [],
"value": 92,
"inputs": {
"packages": [
"github.com/PrPlanIT/HASteward"
],
"ecosystems": "go",
"any_deprecated": false,
"min_days_since_publish": 6
},
"components": [
{
"key": "published_resolvable",
"name": "Published & resolvable",
"detail": "1 package(s) on go",
"points": 25,
"status": "met",
"details": [
{
"code": "packages_published",
"params": {
"count": 1,
"ecosystems": "go"
}
}
],
"max_points": 25
},
{
"key": "publish_recency",
"name": "Publish recency",
"detail": "latest publish 6 days ago",
"points": 35,
"status": "met",
"details": [
{
"code": "publish_recency",
"params": {
"days": 6
}
}
],
"max_points": 35
},
{
"key": "version_history",
"name": "Version history",
"detail": "4 published versions",
"points": 12,
"status": "partial",
"details": [
{
"code": "published_versions",
"params": {
"count": 4
}
}
],
"max_points": 20
},
{
"key": "not_deprecated",
"name": "Not deprecated",
"detail": "active, not deprecated or yanked",
"points": 20,
"status": "met",
"details": [
{
"code": "package_not_deprecated",
"params": {}
}
],
"max_points": 20
}
]
}
],
"description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
},
{
"key": "engineering",
"band": "moderate",
"name": "Engineering Quality",
"value": 54,
"weight": 0.2,
"metrics": [
{
"key": "engineering_practices",
"band": "at_risk",
"name": "Engineering practices",
"note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: CI-Tests. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"openssf_scorecard_ci_tests"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 30,
"inputs": {
"has_ci": false,
"has_tests": true,
"has_editorconfig": false,
"has_linter_config": false,
"has_precommit_config": false
},
"components": [
{
"key": "ci_workflows",
"name": "CI workflows",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 24
},
{
"key": "tests_present",
"name": "Tests present",
"detail": null,
"points": 24,
"status": "met",
"details": [],
"max_points": 24
},
{
"key": "linter_config",
"name": "Linter config",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 16
},
{
"key": "pre_commit_hooks",
"name": "Pre-commit hooks",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 9.6
},
{
"key": "editorconfig",
"name": ".editorconfig",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 6.4
},
{
"key": "openssf_scorecard_ci_tests",
"name": "OpenSSF Scorecard: CI-Tests",
"detail": "no pull request found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 20
}
]
},
{
"key": "documentation",
"band": "excellent",
"name": "Documentation",
"note": null,
"notes": [],
"value": 90,
"inputs": {
"topics": [],
"has_wiki": true,
"homepage": "https://hasteward.prplanit.com",
"has_readme": true,
"has_docs_dir": true,
"has_description": true
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 30,
"status": "met",
"details": [],
"max_points": 30
},
{
"key": "documentation_directory",
"name": "Documentation directory",
"detail": null,
"points": 25,
"status": "met",
"details": [],
"max_points": 25
},
{
"key": "documentation_homepage_site",
"name": "Documentation / homepage site",
"detail": "https://hasteward.prplanit.com",
"points": 15,
"status": "met",
"details": [],
"max_points": 15
},
{
"key": "repository_description",
"name": "Repository description",
"detail": null,
"points": 10,
"status": "met",
"details": [],
"max_points": 10
},
{
"key": "topics",
"name": "Topics",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
},
{
"key": "wiki",
"name": "Wiki",
"detail": null,
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
}
],
"description": "Are baseline engineering and documentation practices in place?"
},
{
"key": "security",
"band": "at_risk",
"name": "Security",
"value": 34,
"weight": 0.16,
"metrics": [
{
"key": "security_posture",
"band": "at_risk",
"name": "Security posture",
"note": "Excluded from scoring (no data or not applicable): CI-Tests, Dangerous-Workflow, Packaging, Signed-Releases, Token-Permissions. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"ci_tests",
"dangerous_workflow",
"packaging",
"signed_releases",
"token_permissions"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 34,
"inputs": {
"source": "openssf_scorecard",
"checks_evaluated": 13,
"scorecard_version": "v5.5.0",
"checks_inconclusive": 5,
"scorecard_aggregate": 3.3
},
"components": [
{
"key": "binary_artifacts",
"name": "Binary-Artifacts",
"detail": "no binaries found in the repo",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "branch_protection",
"name": "Branch-Protection",
"detail": "branch protection not enabled on development/release branches",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "ci_tests",
"name": "CI-Tests",
"detail": "no pull request found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 2.5
},
{
"key": "cii_best_practices",
"name": "CII-Best-Practices",
"detail": "no effort to earn an OpenSSF best practices badge detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 2.5
},
{
"key": "code_review",
"name": "Code-Review",
"detail": "Found 0/30 approved changesets -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "contributors",
"name": "Contributors",
"detail": "project has 1 contributing companies or organizations -- score normalized to 3",
"points": 0.8,
"status": "partial",
"details": [],
"max_points": 2.5
},
{
"key": "dangerous_workflow",
"name": "Dangerous-Workflow",
"detail": "no workflows found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 10
},
{
"key": "dependency_update_tool",
"name": "Dependency-Update-Tool",
"detail": "no update tool detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "fuzzing",
"name": "Fuzzing",
"detail": "project is not fuzzed",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "license",
"name": "License",
"detail": "license file detected",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "maintained",
"name": "Maintained",
"detail": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "packaging",
"name": "Packaging",
"detail": "packaging workflow not detected",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 5
},
{
"key": "pinned_dependencies",
"name": "Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "sast",
"name": "SAST",
"detail": "no SAST tool detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "security_policy",
"name": "Security-Policy",
"detail": "security policy file not detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "signed_releases",
"name": "Signed-Releases",
"detail": "no releases found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 7.5
},
{
"key": "token_permissions",
"name": "Token-Permissions",
"detail": "No tokens found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 7.5
},
{
"key": "vulnerabilities",
"name": "Vulnerabilities",
"detail": "2 existing vulnerabilities detected",
"points": 6,
"status": "partial",
"details": [],
"max_points": 7.5
}
]
},
{
"key": "high_risk_jurisdiction_exposure",
"band": "excellent",
"name": "High-Risk Jurisdiction Exposure",
"note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.",
"notes": [
{
"code": "jurisdiction_evidence_limits",
"params": {}
}
],
"value": 100,
"inputs": {
"meaning": "self-published location evidence; not nationality or citizenship",
"red_flag": false,
"exposures": [],
"policy_countries": [
"Russia",
"Iran",
"North Korea"
],
"review_only_matches": 0,
"assessed_self_published_locations": 2
},
"components": [
{
"key": "policy_exposure_multiplier",
"name": "Policy exposure multiplier",
"detail": "no confirmed policy-scope location match",
"points": 100,
"status": "met",
"details": [
{
"code": "jurisdiction_no_match",
"params": {}
}
],
"max_points": 100
}
]
}
],
"description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
},
{
"key": "ai_readiness",
"band": "moderate",
"name": "AI Readiness",
"value": 69,
"weight": 0,
"metrics": [
{
"key": "ai_agent_context",
"band": "excellent",
"name": "Agent context & guidance",
"note": null,
"notes": [],
"value": 85,
"inputs": {
"has_llms_txt": false,
"legible_history_share": 1,
"agent_instruction_files": [
"CLAUDE.md"
],
"agent_instruction_max_bytes": 756
},
"components": [
{
"key": "agent_instructions",
"name": "Agent instructions",
"detail": "CLAUDE.md",
"points": 45,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "CLAUDE.md"
}
}
],
"max_points": 45
},
{
"key": "machine_readable_docs_llms_txt",
"name": "Machine-readable docs (llms.txt)",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
},
{
"key": "legible_commit_history",
"name": "Legible commit history",
"detail": "100 of 100 human commits state their intent (structured subject or explanatory body)",
"points": 40,
"status": "met",
"details": [
{
"code": "legible_history",
"params": {
"legible": 100,
"sampled": 100
}
}
],
"max_points": 40
}
]
},
{
"key": "ai_verify_loop",
"band": "moderate",
"name": "Verify loop (build / test / typecheck)",
"note": null,
"notes": [],
"value": 56,
"inputs": {
"has_nix": false,
"has_tests": true,
"lockfiles": [
"go.sum"
],
"has_dockerfile": true,
"typed_language": true,
"bootstrap_files": [],
"has_devcontainer": false,
"has_linter_config": false,
"typecheck_configs": [],
"agent_commit_share": 0,
"toolchain_manifests": [
"go.mod"
],
"dependency_bot_commit_share": 0
},
"components": [
{
"key": "one_command_bootstrap",
"name": "One-command bootstrap",
"detail": "go.mod (toolchain convention, no task runner)",
"points": 12.6,
"status": "partial",
"details": [
{
"code": "toolchain_convention",
"params": {
"files": "go.mod"
}
}
],
"max_points": 18
},
{
"key": "automated_tests",
"name": "Automated tests",
"detail": null,
"points": 22,
"status": "met",
"details": [],
"max_points": 22
},
{
"key": "lint_format_config",
"name": "Lint / format config",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 11
},
{
"key": "static_type_checking",
"name": "Static type checking",
"detail": "Go (statically typed)",
"points": 11,
"status": "met",
"details": [
{
"code": "statically_typed_language",
"params": {
"language": "Go"
}
}
],
"max_points": 11
},
{
"key": "reproducible_environment",
"name": "Reproducible environment",
"detail": "Dockerfile, lockfile",
"points": 10,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "Dockerfile, lockfile"
}
}
],
"max_points": 10
},
{
"key": "demonstrated_agent_practice",
"name": "Demonstrated agent practice",
"detail": "no agent-authored commits among the last 100",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_agent_authored_commits",
"params": {
"sampled": 100
}
}
],
"max_points": 10
},
{
"key": "automated_maintenance",
"name": "Automated maintenance",
"detail": "no automated dependency updates observed",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_dependency_automation",
"params": {}
}
],
"max_points": 8
},
{
"key": "openssf_scorecard_pinned_dependencies",
"name": "OpenSSF Scorecard: Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "ai_code_legibility",
"band": "excellent",
"name": "Code legibility for models",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"primary_language": "Go",
"largest_source_bytes": 53045,
"source_files_sampled": 141,
"oversized_source_files": 0
},
"components": [
{
"key": "type_checkable_code",
"name": "Type-checkable code",
"detail": "Go (statically typed)",
"points": 45,
"status": "met",
"details": [
{
"code": "statically_typed_language",
"params": {
"language": "Go"
}
}
],
"max_points": 45
},
{
"key": "manageable_file_sizes",
"name": "Manageable file sizes",
"detail": "0/141 source files over 60KB",
"points": 55,
"status": "met",
"details": [
{
"code": "oversized_source_files",
"params": {
"kb": 60,
"sampled": 141,
"oversized": 0
}
}
],
"max_points": 55
}
]
},
{
"key": "ai_interfaces",
"band": "at_risk",
"name": "Machine-readable interfaces",
"note": null,
"notes": [],
"value": 40,
"inputs": {
"example_dirs": [
"examples"
],
"has_mcp_signal": false,
"api_schema_files": []
},
"components": [
{
"key": "api_schema_openapi_graphql_proto",
"name": "API schema (OpenAPI/GraphQL/proto)",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 40
},
{
"key": "mcp_server",
"name": "MCP server",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 20
},
{
"key": "runnable_examples",
"name": "Runnable examples",
"detail": "examples",
"points": 40,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "examples"
}
}
],
"max_points": 40
}
]
}
],
"description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
}
],
"metrics_version": "1.13.0"
},
"warnings": [
"GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository"
],
"report_type": "repository",
"generated_at": "2026-07-23T10:15:51.697905Z",
"schema_version": "0.27.0",
"badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/p/PrPlanIT/HASteward.svg",
"full_name": "PrPlanIT/HASteward",
"license_state": "standard",
"license_spdx": "AGPL-3.0"
}