Skip to content

Commit cce4b32

Browse files
authored
feat(selfhost): gate priority label on linked-issue label propagation (#2750)
* feat(selfhost): gate priority label on linked-issue label propagation gittensor:priority is a maintainer reward/bonus label and must never be inferred from a PR's title, changed files, AI output, or existing PR labels. Delete the content-glob-based priority inference in resolvePrTypeLabel and replace it with a generic, config-driven linkedIssueLabelPropagation setting: a mapped PR label is only ever applied when a linked/closing issue already carries the configured issue label, fetched fail-open (a fetch failure never invents a priority label). Exclusive mappings replace the bug/feature classification entirely (the gittensor:priority case); additive mappings apply alongside it, for a self-hoster's own unrelated label pair. Also add typeLabels for per-repo label-name overrides. * fix(selfhost): keep the linked-issue-label-propagation fetch out of the UI typecheck Splitting the GitHub-fetch orchestrator into its own file (linked-issue-label-propagation-fetch.ts) keeps linked-issue-label-propagation.ts limited to pure config types and the normalizer. focus-manifest.ts imports that pure module, and focus-manifest.ts is itself pulled into the gittensory-ui workspace's isolated typecheck via registration-workspace.ts, which has no visibility into the Worker's ambient Env type -- the fetch orchestrator's github/app + github/backfill imports broke ui:typecheck by dragging that whole module graph in. Also regenerate the stale env-reference file and close two branch-coverage gaps. * test(selfhost): close the last patch-coverage gap in the type-label block settings.typeLabels is optional only for RepositorySettings-fixture backward compat, same as typeLabelsEnabled above it -- getRepositorySettings always resolves it to a concrete PrTypeLabelSet, so the ?? DEFAULT_TYPE_LABELS fallback is unreachable on this webhook-integration path. * fix(selfhost): cap the linked-issue label propagation fetch fan-out pr.linkedIssues is already hard-capped to MAX_LINKED_ISSUE_NUMBERS (50) at extraction time, so this Promise.all could never actually exceed that in production -- but the function itself trusted every caller to have gone through the capped extractor first. Add a local cap as a second, self-contained line of defense, and document why the bare Promise.all needs no per-item catch (fetchLinkedIssueFacts already fails open internally). * fix(selfhost): preserve DB values for unnamed typeLabels/propagation keys A partial .gittensory.yml override naming only one typeLabels or linkedIssueLabelPropagation field (e.g. typeLabels.priority) previously reset the other fields to the built-in defaults instead of preserving the DB-persisted values, violating the documented yml > DB > defaults precedence. Parse these two settings as sparse partials and merge them field-by-field in resolveEffectiveSettings. * test(selfhost): cover the missing linkedIssueLabelPropagation DB-default branch codecov/patch flagged a partial branch left by the prior commit: the dbSettings.linkedIssueLabelPropagation ?? DEFAULT fallback in resolveEffectiveSettings was never exercised with no DB value present, unlike its already-covered typeLabels counterpart. * fix(selfhost): validate sparse-override fields before merging them A present-but-malformed nested field (e.g. typeLabels.priority: 123, or linkedIssueLabelPropagation.mappings: "oops") was still copied into the sparse manifest override using the normalizer's built-in-default fallback, silently overwriting a DB-persisted value on a config typo instead of being ignored. Gate each field on its own raw shape before copying it. Also reject a non-boolean removeOtherTypeLabels instead of silently coercing it to false, which could flip an intended-exclusive mapping to additive, and validate linkedIssueLabelPropagation.enabled the same way. Fixes a doc-comment pointing at the wrong file.
1 parent 28a6b40 commit cce4b32

19 files changed

Lines changed: 1278 additions & 140 deletions

.gittensory.yml.example

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -293,15 +293,43 @@ settings:
293293
# The label name to apply. String. Default: gittensor.
294294
gittensorLabel: gittensor
295295

296-
# Apply the per-PR TYPE/taxonomy label -- exactly one of gittensor:bug / gittensor:feature /
297-
# gittensor:priority, classified from the PR title + changed paths (#label-decoupling). Independent of
298-
# `autoLabelEnabled` above (which only governs the base `gittensorLabel` context label): a type label is
299-
# internal triage metadata, so it applies to every PR regardless of author type (bot/maintainer/missing
300-
# author) or `publicAudienceMode`/`publicSurface`, UNLESS `publicAudienceMode: gittensor_only` is muting
301-
# this PR's author entirely (that mode's whole point is total silence for a non-confirmed-miner author).
296+
# Apply the per-PR TYPE/taxonomy label -- bug/feature by the PR title, or priority via
297+
# `linkedIssueLabelPropagation` below (#label-decoupling). Independent of `autoLabelEnabled` above
298+
# (which only governs the base `gittensorLabel` context label): a type label is internal triage
299+
# metadata, so it applies to every PR regardless of author type (bot/maintainer/missing author) or
300+
# `publicAudienceMode`/`publicSurface`, UNLESS `publicAudienceMode: gittensor_only` is muting this
301+
# PR's author entirely (that mode's whole point is total silence for a non-confirmed-miner author).
302302
# Bool. Default: true.
303303
typeLabelsEnabled: true
304304

305+
# Per-repo override of the three TYPE label NAMES. Omit any key to keep its default. Map with
306+
# string values bug/feature/priority. Default: gittensor:bug / gittensor:feature / gittensor:priority.
307+
# typeLabels:
308+
# bug: gittensor:bug
309+
# feature: gittensor:feature
310+
# priority: gittensor:priority
311+
312+
# Linked-issue label propagation (#priority-linked-issue-gate): the ONLY mechanism that can select a
313+
# label like `gittensor:priority` -- it is NEVER inferred from a PR's title, changed files, AI
314+
# output, or existing PR labels, only ever copied from a linked/closing issue ("Fixes #123") that
315+
# ALREADY carries the configured issue label. Generic beyond the priority use case: any issue label
316+
# can map to any PR label. `removeOtherTypeLabels: true` REPLACES the type label entirely (bug/
317+
# feature are removed), matching how `gittensor:priority` behaves today; `false` applies the mapped
318+
# label ADDITIVELY alongside the normal title-based bug/feature label, leaving it untouched -- useful
319+
# for a mapping unrelated to the bug/feature/priority triad (e.g. a `customer:vip` issue label
320+
# copied to a `triage:vip` PR label). Disabled by default (no mappings) -- a self-hoster opts in per
321+
# repo. If your labels carry reward or moderation weight, configure this in PRIVATE per-repo/global
322+
# config (see `config/examples/README.md`) rather than the public `.gittensory.yml`, so contributors
323+
# cannot see the exact mapping rules. A per-repo `mappings` override in the private-config layer
324+
# REPLACES the global default list wholesale -- it does not merge with it.
325+
# linkedIssueLabelPropagation:
326+
# enabled: true
327+
# mode: exclusive_type_label
328+
# mappings:
329+
# - issueLabel: gittensor:priority
330+
# prLabel: gittensor:priority
331+
# removeOtherTypeLabels: true
332+
305333
# Create the label if it does not yet exist. Bool. Default: true.
306334
createMissingLabel: true
307335

@@ -336,7 +364,9 @@ settings:
336364
# 1. Context label (`gittensorLabel`, gated by `autoLabelEnabled` above) — the base per-PR marker shown
337365
# to the public surface, subject to `publicAudienceMode`/`publicSurface`/author-qualification.
338366
# 2. Taxonomy/type label (`gittensor:bug`/`gittensor:feature`/`gittensor:priority`, gated by
339-
# `typeLabelsEnabled` above) — internal triage metadata, applied independently of #1.
367+
# `typeLabelsEnabled` above) — internal triage metadata, applied independently of #1. bug/
368+
# feature come from the PR title; priority ONLY from `linkedIssueLabelPropagation` above —
369+
# never inferred from title, files, AI, or existing PR labels (#priority-linked-issue-gate).
340370
# 3. Autonomy-outcome labels (`gittensory:ready-to-merge` etc.) — gated by the `review_state_label`
341371
# autonomy class below, advisory commentary on the bot's own verdict.
342372
# 4. Anti-abuse enforcement labels (blacklist/contributor-cap/review-nag) — gated by the `close`

apps/gittensory-ui/public/openapi.json

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8969,6 +8969,66 @@
89698969
},
89708970
"typeLabelsEnabled": {
89718971
"type": "boolean"
8972+
},
8973+
"typeLabels": {
8974+
"type": "object",
8975+
"properties": {
8976+
"bug": {
8977+
"type": "string"
8978+
},
8979+
"feature": {
8980+
"type": "string"
8981+
},
8982+
"priority": {
8983+
"type": "string"
8984+
}
8985+
},
8986+
"required": [
8987+
"bug",
8988+
"feature",
8989+
"priority"
8990+
]
8991+
},
8992+
"linkedIssueLabelPropagation": {
8993+
"type": "object",
8994+
"properties": {
8995+
"enabled": {
8996+
"type": "boolean"
8997+
},
8998+
"mode": {
8999+
"type": "string",
9000+
"enum": [
9001+
"exclusive_type_label"
9002+
]
9003+
},
9004+
"mappings": {
9005+
"type": "array",
9006+
"items": {
9007+
"type": "object",
9008+
"properties": {
9009+
"issueLabel": {
9010+
"type": "string"
9011+
},
9012+
"prLabel": {
9013+
"type": "string"
9014+
},
9015+
"removeOtherTypeLabels": {
9016+
"type": "boolean"
9017+
}
9018+
},
9019+
"required": [
9020+
"issueLabel",
9021+
"prLabel",
9022+
"removeOtherTypeLabels"
9023+
]
9024+
}
9025+
}
9026+
},
9027+
"required": [
9028+
"enabled",
9029+
"mode",
9030+
"mappings"
9031+
]
89729032
}
89739033
},
89749034
"required": [

0 commit comments

Comments
 (0)