Skip to content

[qa-lead S1] Provision <id>:hands-off labels across the fleet — the opt-out is inert where the persona actually runs #1644

Closed

Description

@don-petry

Story

As a maintainer who is told by every qa-lead advisory that I can opt out with qa-lead:hands-off,
I want that label to actually exist in the repos where personas act,
so that the escape hatch mandated by persona-standards §4 rule 4 is real rather than inert.

The problem, verified 2026-09-01

combsmith/.github#756 made scripts/apply-repo-settings.sh derive <id>:hands-off from the persona manifests, and the labels exist in petry-projects/.github. They exist nowhere else. Via the labels API:

Repo persona opt-out labels present
.github all 8 (business-analyst, dev-lead, devops-lead, pr-review, qa-lead, scrum-master, security-lead, sre-lead)
.github-private only dev-lead:hands-off
TalkTerm, markets, ContentTwin, bmad-bgreat-suite, repo-template only dev-lead:hands-off
broodly, incubator none

.github-private is the only repo where a persona mention is routable — it is the sole adopter of .github/workflows/persona-mention.yml. So the one repo where qa-lead can act is a repo where its opt-out label does not exist.

The router matches the opt-out by exact label name. In persona-mention-reusable.yml:

if [ -n "$opt_out" ] && printf '%s\n' "$LABELS" | grep -qxF "$opt_out"; then
  echo "Item carries '${opt_out}' — persona '${slug}' is opted out here. Skipping."

A label that does not exist can never match, so the check silently reads as "not opted out." This is fail-open by omission — note the contrast with the adjacent guard, which explicitly refuses to route when the label set is unreadable precisely so that "unreadable" is never mistaken for "no labels." The missing-label case defeats that care.

Likely root cause — check this first

#1209 (apply-repo-settings 100% fail: check-suites API needs classic-PAT/App admin (GH_PAT_WORKFLOWS rejected, 403)) is open. If the settings-apply path is 100% failing org-wide, that is very likely why #756's derived labels never reached any repo but the one they were authored in. Diagnose that before hand-creating anything: a label fanout that works once by hand and then rots is not a fix.

Acceptance Criteria

  1. Every org repo that has adopted — or may adopt — a persona surface carries the full derived <id>:hands-off family, one per persona directory in personas/. Verified by an API check recorded in the PR (before/after table).
  2. .github-private specifically carries qa-lead:hands-off, because it is the only repo where the mention router is live today. This is the acceptance bar that actually matters.
  3. The family stays derived, not enumerated — adding a new persona requires no edit to any label list. This is fix(pr-review-sweep): dispatch with GH_PAT_WORKFLOWS, not the bot comment PAT #756's explicit constraint and must not be regressed.
  4. The reason the fanout never happened is identified and fixed at the cause, not worked around. If apply-repo-settings 100% fail: check-suites API needs classic-PAT/App admin (GH_PAT_WORKFLOWS rejected, 403) #1209 is the blocker, either fix it or scope label application so it does not depend on the failing check-suites call — and say which, in the PR description.
  5. Re-running is idempotent; --dry-run reports what it would create and mutates nothing.
  6. Drift is surfaced, not discovered by hand: a repo missing a persona opt-out label appears in an existing report. Extend scripts/fleet_report.sh / the Fleet Monitor rather than adding a new scheduled workflow (AGENTS.md, "Scheduled workflows").
  7. The advisory footer's opt-out sentence is true everywhere it can be printed. If AC test issue from agent #1 cannot be met for some repo, then prompts/qa-lead/advisory.md must stop printing an instruction that does not work there. Prefer making it true.

Tasks / Subtasks

Dev Notes

Project Structure Notes

Changes land in petry-projects/.github (scripts/apply-repo-settings.sh, standards/github-settings.md) and in this repo's fleet reporting (scripts/fleet_report.sh + its bats suite, registered in lint.yml's bats list per the AGENTS.md exception).

References


Update — 2026-09-01, cross-session reconciliation

This is already a tracked item — link, do not duplicate. #1318 (Epic: Persona framework — post-rollout hardening & follow-ups, open since 2026-07-19) carries:

That confirms the root cause hypothesised in "Likely root cause" above, and narrows it: the derivation is merged (combsmith/.github#765/#756) and simply has never been run against the fleet. So AC #4's diagnosis is largely pre-answered — the fix is running/scheduling it, plus whatever #1209 blocks. Tick this item on #1318 when the fanout lands.

Live-state correction. .github-private now carries all nine <id>:hands-off labels (including qa-lead:hands-off and solution-architect:hands-off), where at the start of the review it carried only dev-lead:hands-off. PR #1655's diff does not provision labels, and no label mutation appears in its run log, so the provenance is unestablished (the org audit-log API is not readable with the available token). Treat "AC #2 is already met" as observed, not delivered — the remaining fleet repos (TalkTerm, markets, ContentTwin, bmad-bgreat-suite, repo-template, broodly, incubator) are still short, and .github itself is missing solution-architect:hands-off.

Activity

  1. don-petry commented on Sep 1, 2026

    @don-petry
    CollaboratorAuthor

    Dev-Lead Implementation Plan

    Issue: #1644 — [qa-lead S1] Provision <id>:hands-off labels across the fleet — the opt-out is inert where the persona actually runs

    Diagnosis (AC #4) — recorded before touching code

    Live label state re-checked via the labels API today (2026-09-01):

    Repo persona :hands-off labels present
    .github 8 (business-analyst … sre-lead; missing solution-architect)
    .github-private all 9 incl. qa-lead:hands-off and solution-architect:hands-off
    TalkTerm, markets, ContentTwin, bmad-bgreat-suite, repo-template only dev-lead:hands-off
    broodly, incubator unreadable from this token

    So AC #2 is already met in live state: .github-private — the only repo where the mention
    router is live — now carries qa-lead:hands-off, so the router's grep -qxF has something to
    match. The remaining gap is (a) the derived family has not fanned out to the other consumer
    repos, and (b) that drift is invisible — nothing surfaces a repo missing a persona opt-out label.

    Scope boundary: the fanout mechanism (scripts/apply-repo-settings.sh, standards/github-settings.md)
    lives in petry-projects/.github, a different repo. It cannot be changed from a .github-private
    branch, and this automation only pushes here. The cross-repo fanout fix + one-time backfill is tracked
    as a follow-up PR against petry-projects/.github. This PR delivers AC #6 — the durable, in-repo
    piece: drift is surfaced, not discovered by hand.

    Scope

    Extend the Actions Fleet Monitor so any org repo missing a derived persona <id>:hands-off
    label is surfaced in the fleet report — the family is derived from personas/*/persona.yml
    (opt_out_label:), never enumerated (AC #3).

    Implementation Checklist

    • scripts/fleet_report.sh — add pure functions: derive the opt-out family from
      personas/*/persona.yml; classify a repo's labels (COMPLETE / INCOMPLETE / ABSENT);
      render a "Persona opt-out label coverage" section; emit an alert JSON of INCOMPLETE repos.
    • scripts/fleet_monitor.sh — fetch each discovered repo's labels, build the drift TSV,
      append the new section, export the drift JSON. Best-effort (a label-read failure is a
      per-repo ::warning::, never fatal), mirroring the stub-drift path.

    Tests to Write

    • tests/fleet_report.bats — derivation returns the full family from a fixture personas dir;
      classify COMPLETE / INCOMPLETE / ABSENT; report lists only INCOMPLETE repos with missing
      labels named; alert JSON contains exactly the INCOMPLETE repos; empty/malformed inputs are
      tolerated (no crash).

    Test Command

    scripts/run-bats.sh tests/fleet_report.bats and bash .dev-lead/scripts/dev-lead-lint.sh

  2. don-petry commented on Sep 1, 2026

    @don-petry
    CollaboratorAuthor

    Dev-Lead: Progress (in progress — not yet durable)

    Provisional. Nothing is pushed yet; the automation posts the authoritative
    completion record (with PR number + head SHA) after the push. Treat the notes
    below as intent, not delivered results.

    What this branch does (AC #6 — the in-repo, durable piece)

    Extends the Actions Fleet Monitor so a repo missing a persona <id>:hands-off
    opt-out label is surfaced in the report, not discovered by hand:

    • scripts/fleet_report.sh — new pure functions: derive_persona_optout_labels
      (family derived from personas/*/persona.yml, never enumerated — AC Add @claude delegation, auto-merge, and rebase handling #3),
      persona_optout_row (COMPLETE / INCOMPLETE / ABSENT), generate_persona_optout_report
      (a "Persona opt-out label coverage" section), persona_optout_alert_json.
    • scripts/fleet_monitor.sh — fetches each discovered repo's labels (best-effort;
      a per-repo read failure is a ::warning::, never fatal), builds the drift TSV,
      renders the new section, and writes fleet_persona_optout.json.
    • tests/fleet_report.bats — 12 new tests (already registered in lint.yml).

    Smoke-tested against live fleet labels: .github-private → COMPLETE (all 9,
    incl. qa-lead:hands-off); .github → INCOMPLETE (missing solution-architect:hands-off);
    TalkTerm/markets/ContentTwin → INCOMPLETE (only dev-lead:hands-off);
    broodly → ABSENT. solution-architect:hands-off shows up with zero list edits,
    demonstrating the derived-not-enumerated constraint.

    Scope boundary — read before reviewing (AC #1/#3/#4/#5 fanout)

    The fanout mechanism (scripts/apply-repo-settings.sh, standards/github-settings.md)
    lives in petry-projects/.github, a different repo. It cannot be changed from a
    .github-private branch and this automation only pushes here, so the cross-repo
    fanout fix + one-time backfill is a follow-up PR against petry-projects/.github,
    not this one. This PR delivers the drift visibility so the gap stops being silent.

    Diagnosis (AC #4)

    Re-verified live label state 2026-09-01: AC #2 is already met — .github-private
    (the only repo where the mention router is live) now carries qa-lead:hands-off, so the
    router's grep -qxF has a real label to match. The remaining gap is fanout to the other
    consumer repos + the absence of any drift surfacing (now closed here).

    Local check output (provisional — not the pushed run)

    shellcheck -S warning scripts/fleet_report.sh scripts/fleet_monitor.sh → clean
    bash .dev-lead/scripts/dev-lead-lint.sh → all checks passed
    bats tests/fleet_report.bats → 93 ok / 0 not ok
    

    Notes

    • No change to prompts/qa-lead/advisory.md (AC Workflow should skip no-op PRs in MAX_PRS count #7): its opt-out sentence is true
      in .github-private (the only repo where qa-lead runs), so the footer stays — the
      right move is making the label real everywhere, which the fanout follow-up does.
    • The durable record (PR + head SHA) follows the push.
  3. don-petry commented on Sep 1, 2026

    @don-petry
    CollaboratorAuthor

    Dev-Lead: Implementation Complete — PR #1655

    The implementation for issue #1644 is durable: commits are pushed and a pull request is open. This record is backed by verifiable artifacts you can confirm in one click.

    Review happens on the PR. Acceptance criteria and test results are evidenced by the PR's diff and CI checks — not asserted here. If a later stage fails, this claim is retracted in place.

  4. added a commit that references this issue on Sep 7, 2026
  5. don-petry commented on Sep 8, 2026

    @don-petry
    CollaboratorAuthor

    Provisioning half split to #1740 (blocked on petry-projects/.github#1045). PR #1655 merges on the observability scope; the <id>:hands-off labels are not yet provisioned outside .github, so this story's opt-out guarantee is not yet real — tracked on #1740, not closed with this PR.

  6. added a commit that references this issue on Sep 8, 2026
  7. don-petry commented on Sep 20, 2026

    @don-petry
    CollaboratorAuthor

    Closing — delivered and verified 2026-09-20

    AC #1 / #2 — met. All nine <id>:hands-off labels are present in every fleet repo, verified via the labels API: .github, .github-private, TalkTerm, markets, ContentTwin, bmad-bgreat-suite, repo-template, broodly, incubator — 9/9 each. At the start of this epic only .github carried the full family and six repos carried dev-lead:hands-off alone. qa-lead:hands-off exists in .github-private, so the router's grep -qxF opt-out now has something to match.

    AC #3 — met. The family remains derived from personas/<id>/persona.yml via apply_labels() → persona_opt_out_label_configs(); no enumeration was introduced.

    AC #4 — cause identified and fixed. #1209 (the suspected blocker) closed 2026-09-01, but the labels still did not spread, so it was not the cause. The real gap was that nothing checked or remediated the derived family: check_labels() in compliance-audit.sh iterated a hardcoded seven-label list, and compliance-remediate.sh held a second independent copy. Filed as combsmith/.github#1139 and fixed by combsmith/.github#1140 (merged 2026-09-16).

    AC #6 — met. scripts/fleet_report.sh carries the missing-opt-out-label drift check, so a short repo is surfaced rather than found by hand.

    AC #5 / #7 — unchanged from #756's derivation and the advisory footer is now true everywhere it can be printed.

    Remaining dependency, tracked elsewhere — not a reason to hold this open

    combsmith/.github#1128 (#1045 Step D — add apply-repo-settings.yml to DEPLOYABLE_WORKFLOWS) is still open. Four repos (ContentTwin, repo-template, broodly, incubator) still have no caller stub, and markets' workflow is disabled_manually with a missing GH_TOKEN_ADMIN. Their labels are present today, but the self-heal path that keeps them present is not yet deployed everywhere. If a repo drifts, the fleet-report check added here is what will surface it.

    Closing as delivered. Epic #1643's S1 is complete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    dev-leadFor dev-lead agent pickupenhancementFeature requestsinitiativeEpic / initiative tracking issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions