fix: scope repo-filtered health/lifecycle pipes to calling project (IN-1253) - #4528
Conversation
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
…N-1253) Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
PR SummaryMedium Risk Overview
Deploy these pipes before the paired insights server PR that calls them by name. Reviewed by Cursor Bugbot for commit 58fcdcc. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Pull request overview
Adds project-scoped Tinybird endpoints for live repository-filtered lifecycle and Health Score breakdown calculations.
Changes:
- Adds repository-filtered lifecycle aggregation.
- Adds repository-filtered Health Score signal aggregation.
- Restricts results using the calling project slug.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
repo_lifecycle_v2.pipe |
Adds scoped lifecycle rollup. |
repo_health_score_v2_breakdown.pipe |
Adds scoped health breakdown rollup. |
Suppressed comments (1)
services/libs/tinybird/pipes/repo_lifecycle_v2.pipe:48
- This does not actually mirror the project lifecycle population:
project_insights_copy_health_v2_projectfiltersrep.enabled = true AND rep.excluded = false, whilehealth_score_v2_repo_copy_dsstill contains disabled repositories. A caller-supplied disabled URL can therefore contribute (and potentially win) the lifecycle rollup. Filtering these flags still preserves archived labels becausearchivedis a separate field.
WHERE
hs.repoUrl
IN {{ Array(repos, 'String', description="Selected repo URLs", required=True) }}
AND ip.slug = {{ String(slug, description="Calling project's slug", required=True) }}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| AND rep.enabled = true | ||
| AND rep.excluded = false | ||
| AND ip.slug = {{ String(slug, description="Calling project's slug", required=True) }} |
| - Deliberately does NOT filter on `enabled`/`excluded` — `archived` is itself a valid | ||
| `lifecycleLabelV2` value (per the spec's decision tree: archived flag > abandoned > inert > | ||
| declining > stable > active, first match wins), so an all-archived/excluded selection must still | ||
| produce a real lifecycle label rather than being dropped from the rollup. |
…(IN-1253) Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (3)
services/libs/tinybird/pipes/repo_lifecycle_v2.pipe:47
- The PR contract says an all-archived/excluded selection must collapse to
NULL, and the project-level counterpart restricts its population. This query only scopes by URL/project, so an archived repository remains ingroupArrayand returnsarchived. Filter to enabled, non-archived, non-excluded repositories so the empty aggregate produces the documentedNULL.
WHERE
hs.repoUrl IN {{ Array(repos, 'String', description="Selected repo URLs", required=True) }}
AND ip.slug = {{ String(slug, description="Calling project's slug", required=True) }}
services/libs/tinybird/pipes/repo_health_score_v2_breakdown.pipe:120
enabledandarchivedare independent flags (repository upserts can setarchivedwhile forcingenabled = true), so this does not actually drop every archived repo.health_score_v2_signal_detail_dsretains a base row for archived repos with NULL category signals; thereforecount() > 0passes and returns a NULL-filled row instead of the promised zero rows. Explicitly exclude archived repositories.
AND rep.enabled = true
AND rep.excluded = false
AND ip.slug = {{ String(slug, description="Calling project's slug", required=True) }}
HAVING count() > 0
services/libs/tinybird/pipes/repo_lifecycle_v2.pipe:10
- This description contradicts both the PR contract and the SQL: it says archived/excluded selections produce a real label and that there is no
repositoriesjoin, while the stated endpoint behavior requiresNULLfor that population and lines 43–44 perform both joins. Update the metadata together with the population-filter fix so operators are not given the opposite contract.
- Deliberately does NOT filter on `enabled`/`excluded` — `archived` is itself a valid
`lifecycleLabelV2` value (per the spec's decision tree: archived flag > abandoned > inert >
declining > stable > active, first match wins), so an all-archived/excluded selection must still
produce a real lifecycle label rather than being dropped from the rollup.
- Reads `health_score_v2_repo_copy_ds` directly, no join with `repositories`.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
Suppressed comments (5)
services/libs/tinybird/pipes/repo_health_score_v2_breakdown.pipe:118
- Archived repositories are not excluded here.
archivedis independent ofenabled/excluded, andhealth_score_v2_signal_detail.pipematerializes every non-deleted, non-excluded repo, so an enabled archived repo survives the inner join and makesHAVING count() > 0return a mostly empty aggregate row. This contradicts the documented zero-row behavior for an all-archived selection; filterrep.archived = falseexplicitly.
AND rep.enabled = true
AND rep.excluded = false
services/libs/tinybird/pipes/repo_health_score_v2_breakdown.pipe:106
- This median also needs
quantileExactOrNull; otherwise an all-NULL security category becomes0rather than remaining unavailable.
quantileExact(0.5)
(hv2.securitySupplyChainScoreV2) AS securitySupplyChainScoreV2,
services/libs/tinybird/pipes/repo_health_score_v2_breakdown.pipe:108
- This median also needs
quantileExactOrNull; otherwise an all-NULL development category becomes0rather than remaining unavailable.
quantileExact(0.5)
(hv2.developmentActivityScoreV2) AS developmentActivityScoreV2,
services/libs/tinybird/pipes/repo_health_score_v2_breakdown.pipe:110
- The raw total must preserve an empty/all-NULL input as
NULL. WithquantileExact, that case becomes0, so the endpoint'sIS NULLbranch is bypassed and it returnshealthScoreV2: 0with acriticallabel for repos whose health score is unavailable.
quantileExact(0.5)
(hv2.healthScoreV2) AS healthScoreV2Raw
services/libs/tinybird/pipes/repo_lifecycle_v2.pipe:9
- This contract conflicts with both the PR description and the upstream data: the PR says an all-archived selection returns
NULL, while these lines say it returnsarchived; additionally,health_score_v2.pipeexcludesexcluded = truerows before materializinghealth_score_v2_repo_copy_ds, so an all-excluded selection cannot produce a real label here. Please decide the intended archived behavior and align the SQL and descriptions.
- Deliberately does NOT filter on `enabled`/`excluded` — `archived` is itself a valid
`lifecycleLabelV2` value (per the spec's decision tree: archived flag > abandoned > inert >
declining > stable > active, first match wins), so an all-archived/excluded selection must still
produce a real lifecycle label rather than being dropped from the rollup.
| quantileExact(0.5) | ||
| (hv2.maintainerHealthScoreV2) AS maintainerHealthScoreV2, |
| `lifecycleLabelV2` value (per the spec's decision tree: archived flag > abandoned > inert > | ||
| declining > stable > active, first match wins), so an all-archived/excluded selection must still | ||
| produce a real lifecycle label rather than being dropped from the rollup. | ||
| - Reads `health_score_v2_repo_copy_ds` directly, no join with `repositories`. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Reviewed by Cursor Bugbot for commit 58fcdcc. Configure here.
| quantileExact(0.5) | ||
| (hv2.developmentActivityScoreV2) AS developmentActivityScoreV2, | ||
| quantileExact(0.5) | ||
| (hv2.healthScoreV2) AS healthScoreV2Raw |
There was a problem hiding this comment.
Null health totals become critical
Medium Severity
quantileExact on healthScoreV2 returns 0 when every selected repo is NULL (IN-1248: fewer than two categories), so the later IS NULL check never runs and the endpoint emits healthScoreV2: 0 with healthLabel: critical. A single-repo selection that should reduce to that repo's own NULL score is shown as a failing zero instead of unavailable.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 58fcdcc. Configure here.


Summary
Adds two new Tinybird pipes,
repo_lifecycle_v2andrepo_health_score_v2_breakdown, that recompute Lifecycle and the Health Score breakdown live for a caller-supplied set of repo URLs — powering the Overview page's per-repo filter (IN-1253). Both pipes joininsightsProjectsand require the caller'sslugto match, so areposvalue from a different project can never leak that project's data.repo_health_score_v2_breakdown.pipe: mirrorsproject_insights_health_breakdown_copy.pipe's per-column aggregation, scoped torepos;HAVING count() > 0returns zero rows when every selected repo is archived/excluded.repo_lifecycle_v2.pipe: mirrors the project-level lifecycle logic, scoped torepos; deliberately has noHAVINGguard, so an all-archived/excluded selection collapses to one row withlifecycleLabel: null.Already deployed and validated in staging + production (cross-project leakage test against a foreign repo URL confirmed zero data returned; happy-path validated against OpenStack and project-jupyter).
Deploy order: this PR must be live in production before the paired insights PR (linuxfoundation/insights#TBD) is merged, since the insights server routes call these pipes by name.
Test plan
tb checkpasses on both pipes