Skip to content

chore(engine): one maintainer-association predicate, with a guard against the ninth copy - #9932

Merged
JSONbored merged 2 commits into
mainfrom
chore/consolidate-maintainer-associations-9860
Jul 30, 2026
Merged

chore(engine): one maintainer-association predicate, with a guard against the ninth copy#9932
JSONbored merged 2 commits into
mainfrom
chore/consolidate-maintainer-associations-9860

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Toward #9860 (candidate class: hand-maintained lists). Follow-up to #9877.

What verifying #9877's merge turned up

#9877 consolidated five spellings of ["OWNER", "MEMBER", "COLLABORATOR"]. Checking that the merge landed intact, I grepped for the literal again and found three more — so the real count was eight:

Site Note
packages/loopover-engine/src/scoring/pending-pr-scenarios.ts Its own comment: "Duplicated here (rather than imported) because this package cannot reach into src/; keep the two in sync by hand." That reason expired the moment #9877 moved the definition into the engine.
src/services/contributor-evidence-graph.ts Spelled with .toUpperCase()
src/services/outcome-calibration.ts Found by the new guard, not by me — on its first run

Two of the eight had already drifted: one compared case-sensitively, one case-insensitively. That is how a copy stops being a copy, and it is the argument for a checker rather than another sweep.

The one that isn't membership

packages/loopover-engine/src/signals/engine.ts's strongestAssociation depends on the list's order (OWNER > MEMBER > COLLABORATOR), not on membership. It iterates the shared constant now and says so in a comment, so the ordering dependency is explicit rather than implied by a re-typed literal.

The guard

scripts/check-maintainer-association-copies.ts, wired into test:ci. Per #9860's own bar — compute the fact, don't remember it — it greps for the three-value maintainer subset rather than listing known offenders, and fails with the import to use:

Found 1 re-typed maintainer-association literal(s) (#9860):
  src/services/outcome-calibration.ts:79 — const MAINTAINER_AUTHOR_ASSOCIATIONS = new Set([...]);

Import from packages/loopover-engine/src/settings/author-association.ts instead:
  isMaintainerAuthorAssociation(association)  — membership, case-insensitive
  MAINTAINER_AUTHOR_ASSOCIATIONS             — the list, in precedence order
  classifyAuthorAssociation(association)     — maintainer | contributor | unknown

It deliberately does not flag z.enum declarations of GitHub's full eight-value vocabulary (CONTRIBUTOR, FIRST_TIMER, MANNEQUIN, NONE, …). Those are a wire schema, not this predicate, and the contract legitimately declares that enum four times — conflating the two would be its own mistake. That exclusion is tested.

Behaviour note

pending-pr-scenarios.ts and outcome-calibration.ts were case-sensitive; the shared predicate is case-insensitive and trims. GitHub always sends upper-case, so this is a widening with no practical change — and it removes a latent mismatch where a lower-cased stored value would have been missed by two of the eight and caught by the other six.

Validation

  • 6 new unit tests on the guard itself, against a synthetic tree so both halves are pinned: what it must catch (any quoting/spacing), and what it must not (the full-vocabulary enum, the definition module). One asserts it passes on the real repository, which is the point of wiring it into test:ci.
  • 280 unit tests across the touched modules pass; the engine's own suite is 911 pass / 0 fail.
  • maintainer-associations:check, engine-parity:drift-check, dead-exports:check, dead-source-files:check, import-specifiers:check, docs:drift-check, db:migrations:check, tsc --noEmit, git diff --check all clean.

…inst the ninth copy

#9877 consolidated five spellings of ["OWNER", "MEMBER", "COLLABORATOR"] into
packages/loopover-engine/src/settings/author-association.ts. Verifying that merge
turned up three more, so the real count was eight:

- engine/scoring/pending-pr-scenarios.ts, whose own comment said "duplicated here
  (rather than imported) because this package cannot reach into src/; keep the two in
  sync by hand". That reason expired the moment the definition moved INTO the engine.
- services/contributor-evidence-graph.ts, spelled with .toUpperCase().
- services/outcome-calibration.ts -- which I did not find by reading, but which the
  new guard caught on its first run.

Two had already drifted: one compared case-sensitively, one case-insensitively. That
is how a copy stops being a copy, and why this is worth a checker rather than a sweep.

engine/signals/engine.ts's strongestAssociation is the one consumer that depends on
the list's ORDER (OWNER > MEMBER > COLLABORATOR) rather than membership, so it now
iterates the shared constant and says so.

The guard computes the fact instead of remembering it, per #9860's own bar: it greps
for the three-value maintainer subset and fails with the import to use. It deliberately
does NOT flag z.enum declarations of GitHub's full eight-value vocabulary -- those are
a wire schema, not this predicate.
@loopover-orb

loopover-orb Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Important

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏳ LoopOver is waiting…

LoopOver has seen this pull request and is waiting on CI checks to finish before reviewing it. This comment will update once the review runs.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed · 🟨 Waiting

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@JSONbored JSONbored self-assigned this Jul 30, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 30, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
loopover-ui 7f9b7f0 Commit Preview URL

Branch Preview URL
Jul 30 2026, 05:39 PM

@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Bundle Report

Bundle size has no change ✅

@JSONbored
JSONbored force-pushed the chore/consolidate-maintainer-associations-9860 branch from 105da06 to badf9fb Compare July 30, 2026 17:03
@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 91.88%. Comparing base (8248b88) to head (7f9b7f0).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/loopover-engine/src/signals/engine.ts 92.30% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #9932   +/-   ##
=======================================
  Coverage   91.88%   91.88%           
=======================================
  Files         923      923           
  Lines      113407   113391   -16     
  Branches    27332    27325    -7     
=======================================
- Hits       104205   104191   -14     
+ Misses       7915     7913    -2     
  Partials     1287     1287           
Flag Coverage Δ
backend 95.69% <95.45%> (-0.01%) ⬇️
engine 72.36% <87.50%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...oopover-engine/src/scoring/pending-pr-scenarios.ts 57.91% <100.00%> (-0.42%) ⬇️
...oopover-engine/src/signals/issue-quality-report.ts 100.00% <100.00%> (ø)
...opover-engine/src/signals/predicted-gate-engine.ts 86.68% <100.00%> (-0.04%) ⬇️
src/services/contributor-evidence-graph.ts 99.56% <100.00%> (ø)
src/services/control-panel-roles.ts 100.00% <100.00%> (ø)
src/services/outcome-calibration.ts 98.52% <100.00%> (-0.03%) ⬇️
src/services/recommendation-outcomes.ts 99.17% <100.00%> (ø)
packages/loopover-engine/src/signals/engine.ts 97.67% <92.30%> (-0.01%) ⬇️

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 30, 2026
…al exception

Review was right, and the miss was in the guard itself. MAINTAINER_TRIPLE only
matched comma-separated array literals, so it could never have found
`value === "OWNER" || value === "MEMBER" || value === "COLLABORATOR"` -- which was
sitting in signals/engine.ts with fourteen call sites, in the same file whose
strongestAssociation I did migrate. A guard that recognises one spelling of the thing
it guards is a hand-maintained list with extra steps.

Detection is now "all three named within a 4-line window", which is form-agnostic and
survives a formatter wrapping a chain. Widening it immediately surfaced four more
genuine copies nothing had flagged: signals/issue-quality-report.ts,
signals/predicted-gate-engine.ts, services/control-panel-roles.ts (identical chains)
and services/recommendation-outcomes.ts (the same predicate inside isMaintainerLane).

It also surfaced one site that must NOT be migrated: settings/command-authorization.ts
maps OWNER/MEMBER to `maintainer` and COLLABORATOR to a SEPARATE `collaborator` role.
Collapsing those would grant collaborators maintainer-only commands. That is an
exception with a stated reason, not a known-copy list -- the distinction #9860 draws --
and it follows check-regate-sort-key.ts's ALLOWED_OMISSIONS idiom.

Also fixes the guard's own reporting: it printed the window's first line, which is
often a brace or blank, instead of the line that names an association.
@JSONbored

Copy link
Copy Markdown
Owner Author

The blocker is correct, and the miss was in the guard rather than only in the migration. Fixed in the latest push — thank you, this was the right catch.

What was wrong

MAINTAINER_TRIPLE only matched comma-separated array literals, so it could never have found

function isMaintainerAssociation(value: string | null | undefined): boolean {
  return value === "OWNER" || value === "MEMBER" || value === "COLLABORATOR";
}

which was sitting in signals/engine.ts with 14 call sites — in the same file whose strongestAssociation I did migrate. A guard that recognises one spelling of the thing it guards is a hand-maintained list with extra steps, which is exactly what #9860 says to reject.

What changed

Detection is now "all three named within a 4-line window" — form-agnostic, and it survives a formatter wrapping a chain across lines.

Widening it immediately surfaced four more genuine copies that nothing had flagged, none of which I had found by reading:

File Form
signals/issue-quality-report.ts identical ===/|| chain
signals/predicted-gate-engine.ts identical chain
services/control-panel-roles.ts identical chain
services/recommendation-outcomes.ts same predicate inside isMaintainerLane

So the real count was thirteen, not eight. All are migrated.

One site deliberately not migrated

settings/command-authorization.ts names all three but is not this predicate:

if (args.commenterAssociation === "OWNER" || args.commenterAssociation === "MEMBER") roles.push("maintainer");
if (args.commenterAssociation === "COLLABORATOR") roles.push("collaborator");

COLLABORATOR is a separate role there. Collapsing it into the maintainer predicate would grant collaborators maintainer-only commands — a privilege escalation, not a cleanup. It is allowlisted in ALLOWED_DISTINCT_SEMANTICS with that reason, following check-regate-sort-key.ts's ALLOWED_OMISSIONS idiom. That is an exception list with stated reasons, not a list of known duplicates awaiting migration — the distinction #9860 draws.

Also fixed

The guard printed the window's first line, which is often a closing brace or blank, instead of the line that actually names an association. Its output was unreadable in exactly the case it was built for.

Validation

  • 11 guard tests (up from 6), including the ===/|| chain, a formatter-wrapped chain, the allowlisted different-semantics case, and one asserting the reported line is the one naming an association. The windowing trade-off — two hits inside one window collapse to one finding — is now stated in a test rather than left implicit.
  • Engine suite 911 pass / 0 fail; 124 tests across the touched modules pass.
  • maintainer-associations:check, engine-parity:drift-check, dead-exports:check, import-specifiers:check, docs:drift-check, tsc --noEmit, git diff --check all clean.

@JSONbored
JSONbored merged commit b21216b into main Jul 30, 2026
7 checks passed
@JSONbored
JSONbored deleted the chore/consolidate-maintainer-associations-9860 branch July 30, 2026 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant