Skip to content

feat: align health score v2 with community feedback commitments IN-1226 - #4460

Open
gaspergrom wants to merge 6 commits into
mainfrom
feat/CM-IN-1226-health-score-v2-compliance
Open

feat: align health score v2 with community feedback commitments IN-1226#4460
gaspergrom wants to merge 6 commits into
mainfrom
feat/CM-IN-1226-health-score-v2-compliance

Conversation

@gaspergrom

@gaspergrom gaspergrom commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Implements 8 compliance-gap items from IN-1226, all Tinybird-only changes, already deployed and validated against production data.
  • Key changes: lifecycle-stage split (abandoned vs inert), bus factor observed-activity fallback, vulnerability severity filter correction (MODERATE→MEDIUM), methodology versioning + monthly raw-input snapshot datasource, bot-filtering join fix, non-author responsiveness fix, and documentation of experimental-repo exclusion + weighted-mean bus-factor prototype.
  • No deploy step needed — all pipes/datasources are already live in production Tinybird.

JIRA

IN-1226

…226)

Phase 1 - Issue responsiveness fix:
- issue_analysis_copy_pipe: Filter comments to exclude author self-responses
- This ensures 'responded' signal reflects genuine external feedback, not self-commentary

Phase 2 - v2 compliance & documentation:
- health_score_v2_lifecycle: Split 'abandoned' (unanswered 90-180d + no maintainer 12m)
  from 'inert' (no commits 18m + zero activity)
- health_score_v2_maintainer: Add observed review/merge actor count (parallel to
  curated roster) via greatest(curated, observed) for bus-factor
- health_score_v2_maintainer/lifecycle: Repoint org-diversity & bus-factor joins to
  use cleaned deduplicated streams (activityRelations_deduplicated_cleaned_bucket_union)
  instead of raw activityRelations
- All v2 datasources/pipes: Add methodologyVersion='2.0.0' constant
- Create health_score_v2_raw_inputs_snapshot pipe+datasource (monthly append-mode
  capture of raw signals for validation)
- README: Document repositories.excluded as official experimental-repo mechanism
- project_insights_health_breakdown_copy: Add busFactorScoreActivityWeightedMean
  for validation/comparison alongside existing coverage-filtered average
- health_score_v2_repo_copy_ds: Fix description (graceful-degradation IS implemented,
  document security_contact_email spec gap as temporary)

Item 7 verification: All 9 per-signal *Available flags confirmed present and emitted
(busFactorAvailable, orgDiversityAvailable, responsivenessAvailable,
scorecardAvailable, securityPracticesAvailable, dependencyHealthAvailable,
releaseCadenceAvailable, issueResolutionAvailable, prMergeAvailable).

Note: Day thresholds (90/180/18mo) are provisional pending validation analysis.
No changes to IN-1212 branch. Postgres writeback deferred (Tinybird-only per decision).

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
…N-1226)

- issue_analysis_copy_pipe: fix node name mismatch (issues_comment ->
  issues_comment_non_author) and column alias (commentedAt -> respondedAt)
  that would have failed at Tinybird push time
- project_insights_health_breakdown_ds: add missing busFactorScoreActivityWeightedMean
  schema column to match the pipe output that already emitted it
- health_score_v2_raw_inputs_snapshot: fix vulnerable-deps join to use the
  real advisory schema (packageDependencies/advisoryPackages/advisories),
  matching the pattern already proven in health_score_v2_security.pipe --
  the pipe previously referenced a nonexistent 'dependencies' table

Found while dispatching the Tinybird deploy for the IN-1226 commit; 8 of 9
pipes and all datasources deployed clean on the first pass, this fixes the
one that didn't.

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
…1226)

health_score_v2_raw_inputs_snapshot.pipe previously split its base data
into three NODEs (maintainer/security/development) joined together in a
final calc node. Tinybird cannot resolve column references once it inlines
multiple independently-defined subqueries together at that depth
("Identifier 's.repoUrl' cannot be resolved from subquery with name s").

The three proven, already-deployed category pipes (health_score_v2_maintainer/
_security/_development) never join across NODEs this way - each does its
base + joins in a single node. Collapsed this pipe to match that pattern.

Also fixes 13 SELECT aliases that didn't match the target datasource's
schema column names (e.g. curatedBusFactorCount vs busFactorCuratedCount) -
same class of reference bug as 9c0e94d, only surfaces at actual deploy.

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
quantile(0.5)(dateDiff(..., max(...) OVER (PARTITION BY ...))) is rejected
by ClickHouse - window functions can't nest inside an aggregate. Replaced
with the same groupArray/arraySort/arraySlice top-2-dates approach already
proven in health_score_v2_development.pipe's release-cadence subquery.

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
… (IN-1226)

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
…N-1226)

The vulnerabilities datasource has always stored medium-severity CVEs
as severity='MEDIUM' (see extractSeverity.ts), but health_score_v2_security
and health_score_v2_raw_inputs_snapshot filtered on severity='MODERATE',
a value that never existed in the data. openModerateVulns/openModerates
were silently 0 for every repo, so the openVulnScore penalty term for
medium-severity vulnerabilities never applied, inflating securitySupplyChainScoreV2
for any repo with real medium-severity CVEs. Found via the Part 3 CVE
data sanity validation analysis (0/31,710 repos ever showed a moderate
CVE, which is not a plausible population characteristic).

Also corrects the same stale MODERATE reference in the vulnerabilities
datasource's severity enum docstring.

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI balanced review requested due to automatic review settings August 10, 2026 23:01
@cursor

cursor Bot commented Aug 10, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes alter published health scores, lifecycle labels, and security vuln penalties across all repos; issue responsiveness and activity source switches can shift maintainer metrics materially.

Overview
Tinybird-only updates for Health Score v2 (IN-1226): methodology 2.0.0 is stamped on category and aggregate outputs, and a monthly append pipe/datasource captures raw scoring inputs (not computed scores) for audit and validation.

Scoring and upstream fixes: Maintainer bus factor uses max(curated maintainers, observed review/merge actors) and reads activity from activityRelations_deduplicated_cleaned_bucket_union. Lifecycle splits the old long-idle case into abandoned (unanswered issues + no maintainer activity) vs inert (18mo idle with no issues/PRs). Open vuln counts use severity MEDIUM instead of the non-matching MODERATE. Issue responsiveness ignores the opener’s own comments. Project breakdown adds busFactorScoreActivityWeightedMean.

Docs: README documents repositories.excluded for out-of-scope repos; repo copy datasource text now describes Layer 1+2 graceful degradation.

Reviewed by Cursor Bugbot for commit 310860e. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 4 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 310860e. Configure here.

(r.lastCommitAt < now() - INTERVAL 18 MONTH)
AND coalesce(w.issuesInWindow18m, 0) + coalesce(p.prsInWindow18m, 0) = 0,
'abandoned',
'inert',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inert label breaks project rollup

High Severity

The new inert lifecycle label is emitted by health_score_v2_lifecycle.pipe, but project_insights_copy.pipe's best-state-wins indexOf list still only has active/stable/declining/abandoned/archived. ClickHouse returns 0 for a missing value, so inert sorts ahead of every other state and can override active at project level.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 310860e. Configure here.

coalesce(w.issuesInWindow18m, 0) > 0
AND coalesce(w.unansweredCount, 0) > 0
AND (r.lastCommitAt < now() - INTERVAL 12 MONTH),
'abandoned',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Abandoned misses NULL lastCommitAt

Medium Severity

The new abandoned branch requires lastCommitAt &lt; now() - INTERVAL 12 MONTH, but this pipe already documents that repos.lastCommitAt is often NULL as an upstream data gap. In ClickHouse that comparison is false for NULL, so repos with unanswered issues and no recent commits can still fall through to active.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 310860e. Configure here.

sd.busFactorAvailable
) / nullIf(
sumIf(coalesce(sd.commitsLast6m, 1), sd.busFactorAvailable), 0
) AS busFactorScoreActivityWeightedMean

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weighted mean coalesce asymmetry

Medium Severity

busFactorScoreActivityWeightedMean uses coalesce(commitsLast6m, 0) in the numerator and coalesce(commitsLast6m, 1) in the denominator. Repos with busFactorAvailable but NULL commitsLast6m add weight without contributing score, which dilutes the activity-weighted mean.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 310860e. Configure here.

p.merged12m AS prsMergedLast12m,
p.closedUnmerged12m AS prsClosedUnmergedLast12m,
p.medianMergeS AS medianPrMergeSeconds
FROM (SELECT DISTINCT url AS repoUrl FROM repositories WHERE deletedAt IS NULL) AS allRepos

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New pipe omits FINAL keyword

Low Severity

The new snapshot pipe reads repositories with SELECT DISTINCT ... WHERE deletedAt IS NULL and no FINAL. repositories is a ReplacingMergeTree, so unmerged soft-delete versions can still surface without FINAL.

Fix in Cursor Fix in Web

Triggered by learned rule: Tinybird pipes querying ReplacingMergeTree datasources must use FINAL keyword

Reviewed by Cursor Bugbot for commit 310860e. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Aligns Health Score v2 Tinybird pipelines with IN-1226 requirements.

Changes:

  • Adds lifecycle, bus-factor, responsiveness, and vulnerability corrections.
  • Introduces methodology versioning and monthly raw-input snapshots.
  • Documents repository exclusion and adds weighted bus-factor aggregation.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
services/libs/tinybird/README.md Documents repository exclusion.
services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe Adds activity-weighted bus-factor aggregation.
services/libs/tinybird/pipes/issue_analysis_copy_pipe.pipe Measures non-author issue responses.
services/libs/tinybird/pipes/health_score_v2.pipe Adds methodology version.
services/libs/tinybird/pipes/health_score_v2_signal_detail.pipe Exposes methodology version.
services/libs/tinybird/pipes/health_score_v2_security.pipe Corrects medium severity and versions output.
services/libs/tinybird/pipes/health_score_v2_raw_inputs_snapshot.pipe Adds monthly raw-input snapshots.
services/libs/tinybird/pipes/health_score_v2_maintainer.pipe Adds observed-actor bus-factor fallback.
services/libs/tinybird/pipes/health_score_v2_lifecycle.pipe Splits abandoned and inert states.
services/libs/tinybird/pipes/health_score_v2_development.pipe Adds methodology version.
services/libs/tinybird/datasources/vulnerabilities.datasource Corrects severity documentation.
services/libs/tinybird/datasources/project_insights_health_breakdown_ds.datasource Stores weighted bus-factor metric.
services/libs/tinybird/datasources/health_score_v2_signal_detail_ds.datasource Stores methodology version.
services/libs/tinybird/datasources/health_score_v2_security_ds.datasource Stores methodology version.
services/libs/tinybird/datasources/health_score_v2_repo_copy_ds.datasource Updates scoring documentation and schema.
services/libs/tinybird/datasources/health_score_v2_raw_inputs_snapshot_ds.datasource Defines snapshot storage.
services/libs/tinybird/datasources/health_score_v2_maintainer_ds.datasource Stores methodology version.
services/libs/tinybird/datasources/health_score_v2_lifecycle_ds.datasource Stores lifecycle methodology version.
services/libs/tinybird/datasources/health_score_v2_development_ds.datasource Stores methodology version.
Suppressed comments (2)

services/libs/tinybird/pipes/health_score_v2_lifecycle.pipe:62

  • Introducing inert here leaves the project rollup unaware of the new value. project_insights_copy.pipe:130 uses indexOf(['active', 'stable', 'declining', 'abandoned', 'archived'], x); indexOf returns 0 for inert, so a mixed active/inert project incorrectly rolls up to inert instead of the best state. Add inert between declining and abandoned in that ordering and update the associated lifecycle descriptions.
                    'inert',

services/libs/tinybird/pipes/health_score_v2_raw_inputs_snapshot.pipe:220

  • These PR inputs also diverge from health_score_v2_development.pipe:216-225: old PRs merged/closed recently are included and the median spans all history, whereas scoring starts with PRs opened in the trailing 12 months. Use the same window as the category pipe so snapshots remain auditable.
                countIf(mergedAt IS NOT NULL AND mergedAt > now() - INTERVAL 12 MONTH) AS merged12m,
                countIf(
                    closedAt IS NOT NULL AND mergedAt IS NULL AND closedAt > now() - INTERVAL 12 MONTH
                ) AS closedUnmerged12m,
                quantile(0.5)
                (mergedInSeconds) AS medianMergeS

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 60 to +62
(r.lastCommitAt < now() - INTERVAL 18 MONTH)
AND coalesce(w.issuesInWindow18m, 0) + coalesce(p.prsInWindow18m, 0) = 0,
'abandoned',
'inert',
Comment on lines +115 to +117
countIf(
openedAt > now() - INTERVAL 18 MONTH AND respondedInSeconds IS NULL
) AS unansweredCount
Comment on lines +229 to +236
WHERE
timestamp > now() - INTERVAL 12 MONTH
AND (
type = 'pull_request-reviewed'
OR type = 'pull_request-merged'
OR type = 'merge_request-review-approved'
OR type = 'merge_request-merged'
)
Comment on lines +75 to +82
WHERE
timestamp > now() - INTERVAL 12 MONTH
AND (
type = 'pull_request-reviewed'
OR type = 'pull_request-merged'
OR type = 'merge_request-review-approved'
OR type = 'merge_request-merged'
)
Comment on lines +31 to +34
c.type = 'issue-comment'
AND c.sourceParentId != ''
AND c.memberId != opened.memberId
AND toYear(c.timestamp) >= 1971
repoMeta.branchProtectionRequiredReviews,
repoMeta.branchProtectionRequiresStatusChecks,
repoMeta.branchProtectionAllowsForcePush,
deps.vulnerableDeps,
p.merged12m AS prsMergedLast12m,
p.closedUnmerged12m AS prsClosedUnmergedLast12m,
p.medianMergeS AS medianPrMergeSeconds
FROM (SELECT DISTINCT url AS repoUrl FROM repositories WHERE deletedAt IS NULL) AS allRepos
Comment on lines +50 to +52
ENGINE MergeTree
ENGINE_SORTING_KEY (snapshotDate, repoUrl)
ENGINE_TTL snapshotDate + INTERVAL 24 MONTH
UPDATE repositories SET excluded = 1 WHERE url = 'https://github.com/org/repo-meta';
```

The flag is read by `health_score_v2_maintainer.pipe` and `health_score_v2_lifecycle.pipe` as part of their graceful-degradation logic.
coalesce(sd.commitsLast6m, 0) * coalesce(sd.busFactorScore, 0),
sd.busFactorAvailable
) / nullIf(
sumIf(coalesce(sd.commitsLast6m, 1), sd.busFactorAvailable), 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants