Skip to content

feat(review): wire linked-issue satisfaction into the deterministic gate - #4069

Merged
JSONbored merged 1 commit into
mainfrom
feat/linked-issue-satisfaction-gate-orchestration
Jul 7, 2026
Merged

feat(review): wire linked-issue satisfaction into the deterministic gate#4069
JSONbored merged 1 commit into
mainfrom
feat/linked-issue-satisfaction-gate-orchestration

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

  • Wires the pure linked-issue satisfaction analysis core (feat(review): pure linked-issue satisfaction assessment builder (advisory finding) #2172, src/services/linked-issue-satisfaction.ts) into a full model-calling orchestration + cache + gate pipeline -- the two pieces PR feat(review): add linked-issue satisfaction advisory + unified-comment section #3833 explicitly deferred ("the AI-call orchestration and any future gate.linkedIssueSatisfaction mode wiring... is a separate, maintainer-only slice").
  • New src/services/linked-issue-satisfaction-run.ts mirrors ai-slop.ts's runGittensoryAiSlopAdvisory shape exactly: env/budget/BYOK gating, a bounded 2-model x 3-attempt retry/fallback loop, and confidence-floor-aware retry (an early below-floor "unaddressed" call retries; the floor is re-checked fresh on every independent attempt, never lowered).
  • Extends fetchLinkedIssueFacts (src/github/backfill.ts) to also return the linked issue's title/body -- same endpoint/call, purely additive optional fields; verified its 3 existing callers (linked-issue-hard-rules.ts, processors.ts, linked-issue-label-propagation-fetch.ts) are unaffected.
  • New DB-backed gate.linkedIssueSatisfaction mode (off/advisory/block, default off), wired through the full aiReviewMode-style template: migration, schema.ts, types.ts, repositories.ts resolver (default/parse/insert/update), openapi/schemas.ts (both schemas), src/api/routes.ts maintainer-settings write schema, settings-preview.ts, .gittensory.yml gate.linkedIssueSatisfaction (packages/gittensory-engine's FocusManifestGateConfig + the app-side applyGateConfigOverrides resolver), and the 3 docs pages the check-docs-drift script requires (docs.how-reviews-work.tsx, docs.tuning.tsx, docs.github-app.tsx).
  • New linked_issue_scope_mismatch finding + isConfiguredGateBlocker branch (src/rules/advisory.ts): closes fix(review): deterministic linked-issue gate never checks issue-PR scope match, only existence/openness #3906's gap. In block mode, an above-confidence-floor "unaddressed" verdict now actually fails the gate, instead of the structured "Linked issue" signal staying green while only the AI reviewer's free-text prose flagged the scope mismatch (the JSONbored/metagraphed PR fix(review): preserve gate verdict on auto-review skip #3910 repro).
  • New linked_issue_satisfaction_cache table (mirrors ai_slop_cache, migration 0119): keyed on (repo, pull, head SHA, linked issue number). The extra linked_issue_number column (absent from ai_slop_cache) exists because a PR's primary linked issue can change between passes (an edited body re-links a different issue) -- reusing a stored verdict for a different issue would silently answer the wrong question.
  • Threads the resolved {status, rationale} through unified-comment-bridge.ts into the already-built linkedIssueSatisfactionBlock() renderer (feat(review): render linked-issue satisfaction as an advisory section in the unified comment #2174) -- no changes to the renderer itself, purely an additive passthrough field.

Multi-linked-issue decision

v1 assesses only the PR's primary (first) linked issue, not every linked issue a PR might cite. Documented in runLinkedIssueSatisfactionForAdvisory's doc comment (src/queue/processors.ts) and the cache migration's comment. Rationale:

  • Each additional issue needs its own bounded (up to 6-call) retry/fallback budget -- assessing N issues multiplies AI spend by N per review pass.
  • The concrete repro this closes (metagraphed PR fix(review): preserve gate verdict on auto-review skip #3910) cited exactly one issue.
  • The cache/fingerprint shape this PR adds is naturally single-issue (a linkedIssueNumber cache-key column, not an array), keeping the schema and orchestration simple.

A future slice could widen this to assess every linked issue independently if multi-issue PRs turn out to be common enough to matter; documenting the choice here rather than building it speculatively.

Design note: gate.linkedIssueSatisfaction vs. the pre-existing review.linkedIssueSatisfaction

PR #3665 already parses a review.linkedIssueSatisfaction config-as-code field (#2173) -- but it's YAML-only (no DB column), lives under the content-oriented review: manifest block (alongside footer.text, fields:{...} show/hide toggles), and per its own PR description is unconsumed by any decision path yet. This PR adds a new, distinct gate.linkedIssueSatisfaction field (mirroring aiReviewMode's exact DB + dashboard + yml wiring) rather than repurposing that one:

  1. The pure analysis module's own header comment explicitly forward-references "a future gate.linkedIssueSatisfaction mode wiring" as the intended name for this slice.
  2. Every other gate-blocking mode in this codebase lives under gate.* + a DB column (aiReviewMode, selfAuthoredLinkedIssueGateMode, manifestPolicyGateMode, ...); review.* is reserved for presentation-only toggles. Mixing a blocking decision into the content-config block would be a real architectural inconsistency, not just a naming quibble.
  3. The parent task/issue thread explicitly asked for the full DB-backed aiReviewMode-style template, not the YAML-only pattern.

review.linkedIssueSatisfaction is left untouched and unconsumed by this PR -- flagging it here for a maintainer to decide whether to deprecate it in a follow-up, since having two same-named-leaf config surfaces (review.linkedIssueSatisfaction vs gate.linkedIssueSatisfaction) is a real footgun if a maintainer sets the wrong one.

Closes #1961, Closes #3906.

Scope

  • The PR title follows type(scope): short summary Conventional Commit format.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked an issue.

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck
  • npm run test:coverage locally -- every changed/added src/** line and branch in this diff is covered (verified by cross-referencing coverage/lcov.info against the exact diff hunks)
  • npm run test:workers
  • npm run build:mcp
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests. (N/A here -- no auth/session/CORS surface touched.)
  • API/OpenAPI/MCP behavior is updated and tested where needed.
  • UI changes use live API data or real empty/error/loading states, not production mock/demo fallbacks. (N/A -- no UI code touched, only docs-page text.)
  • Visible UI changes include a UI Evidence section. Not applicable: the only apps/gittensory-ui/** changes are additional <li> bullet text in 3 existing docs pages (docs.how-reviews-work.tsx, docs.tuning.tsx, docs.github-app.tsx) -- same list structure, no new component/layout/visual state to screenshot.
  • Public docs/changelogs are updated where needed (the 3 docs pages above); CHANGELOG.md itself is untouched.

Notes

  • packages/gittensory-engine requires npm run build --workspace=@jsonbored/gittensory-engine before npm run test:coverage will resolve on a genuinely fresh checkout (no prior dist/) -- pre-existing gap in test:ci's own script ordering (test:coverage runs before build:miner, which is what builds the engine package), unrelated to this PR's code. Filed separately for a maintainer follow-up rather than folded into this PR.

Wires the pure linked-issue satisfaction analysis core (src/services/linked-issue-satisfaction.ts,
#2172) into a full model-calling orchestration + cache + gate pipeline: a new
runGittensoryLinkedIssueSatisfaction service mirroring ai-slop.ts's multi-model retry/BYOK shape, a
linked_issue_satisfaction_cache table keyed on (repo, pull, head SHA, linked issue number), and a new
DB-backed gate.linkedIssueSatisfaction mode (off/advisory/block, default off) wired through the full
aiReviewMode-style template (migration, schema, repositories resolver, OpenAPI, settings-preview,
.gittensory.yml, docs).

Extends fetchLinkedIssueFacts to also return the linked issue's title/body (same call, additive).

Adds a linked_issue_scope_mismatch finding + isConfiguredGateBlocker branch: in block mode, an
above-confidence-floor "unaddressed" verdict now fails the gate instead of only the AI reviewer's
free-text prose flagging a scope mismatch while the structured Linked issue signal stays green.

Closes #1961, Closes #3906
@superagent-security

Copy link
Copy Markdown
Contributor

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

@cloudflare-workers-and-pages

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
gittensory-ui 073dfa8 Commit Preview URL

Branch Preview URL
Jul 07 2026, 10:47 PM

@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.10714% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 93.62%. Comparing base (b679697) to head (073dfa8).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/gittensory-engine/src/focus-manifest.ts 0.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4069      +/-   ##
==========================================
+ Coverage   93.60%   93.62%   +0.01%     
==========================================
  Files         381      383       +2     
  Lines       35627    35738     +111     
  Branches    13070    13118      +48     
==========================================
+ Hits        33347    33458     +111     
  Misses       1618     1618              
  Partials      662      662              
Files with missing lines Coverage Δ
src/api/routes.ts 93.98% <ø> (ø)
src/db/repositories.ts 96.64% <100.00%> (+0.01%) ⬆️
src/db/schema.ts 72.13% <100.00%> (+0.46%) ⬆️
src/github/backfill.ts 97.04% <100.00%> (+0.08%) ⬆️
src/openapi/schemas.ts 100.00% <ø> (ø)
src/queue/processors.ts 95.02% <100.00%> (+0.06%) ⬆️
...rc/review/linked-issue-satisfaction-cache-input.ts 100.00% <100.00%> (ø)
src/review/unified-comment-bridge.ts 99.54% <100.00%> (+<0.01%) ⬆️
src/rules/advisory.ts 97.28% <100.00%> (+0.01%) ⬆️
src/services/linked-issue-satisfaction-run.ts 100.00% <100.00%> (ø)
... and 5 more
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@loopover-orb loopover-orb Bot added the gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. label Jul 7, 2026
@loopover-orb

loopover-orb Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Warning

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

⏸️ Gittensory review result - manual review recommended

Review updated: 2026-07-07 23:01:48 UTC

44 files · 1 AI reviewer · 1 blocker · readiness 100/100 · CI green · clean

⏸️ Suggested Action - Manual Review

  • Possible leaked secret in the diff (generic_secret_assignment) — Remove the secret from the diff, rotate the exposed credential, then re-run the gate.

Review summary
This PR wires the previously-deferred model-calling orchestration and gate mode for linked-issue satisfaction (#1961/#3906) on top of the existing pure analysis core (#2172), following the established `aiReviewMode`/`slopGateMode` template end-to-end: schema+migration (0123 gate mode column, 0124 cache table), repositories resolver, types, openapi schemas (both), routes write-schema, settings-preview, config-as-code (`.gittensory.yml` + `FocusManifestGateConfig` + `applyGateConfigOverrides`), the 3 docs pages plus `check-docs-drift` manifest entry, and the `isConfiguredGateBlocker` branch in advisory.ts for the new `linked_issue_scope_mismatch` finding. Migrations are D1-remote-safe (no CREATE TEMP/ATTACH/PRAGMA/transaction control), schema and migration match column-for-column, and the cache table's extra `linked_issue_number` primary-key column is correctly reasoned (a PR's primary linked issue can change between passes). The confidence-floor retry logic, fail-safe error handling, and BYOK/shared-budget gating all mirror `ai-slop.ts`'s proven pattern rather than reimplementing it, and the PR closes a real linked issue (#3906) with a concrete repro cited in the migration comment.

Nits — 6 non-blocking
  • src/queue/processors.ts and src/services/linked-issue-satisfaction-run.ts both add a control-flow depth of 5 (over the repo's threshold of 4) in the try/catch + nested retry loops — consider extracting the per-attempt validation into a small helper for readability.
  • runLinkedIssueSatisfactionForAdvisory does its own dedicated fetchLinkedIssueFacts call independent of resolveLinkedIssueAdvisoryContext's narrower fetch — when both linkedIssueGateMode and linkedIssueSatisfactionGateMode are enabled on the same repo this doubles the GitHub API call for the same issue; the tradeoff is explicitly documented in the code but sharing the fetch would be cheaper.
  • I could not verify from the truncated diff that `linkedIssueSatisfaction`'s conditional push into `args.advisory.findings` (processors.ts ~8845) executes before `gateEvaluation = evaluateGateCheck(...)` reads `advisory.findings` later in the same function — please confirm the ordering holds so `block` mode actually blocks (mirrors the existing manifest-policy/self-authored-linked-issue pattern, which suggests it does).
  • migrations/0124_linked_issue_satisfaction_cache.sql's `created_at ... DEFAULT CURRENT_TIMESTAMP` is dead weight since `putCachedLinkedIssueSatisfaction` always supplies `nowIso()` explicitly on insert — harmless (mirrors `ai_slop_cache`'s existing migration) but worth flagging for consistency with the schema.ts `$defaultFn` convention noted elsewhere in this repo's review guide.
  • Consider sharing the linked-issue text fetch between `linked-issue-hard-rules.ts`'s existing lookup and this new satisfaction assessment when both gates are enabled, to cut the duplicate GitHub call noted above.
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.

Concerns raised — review before merging

  • Possible leaked secret in the diff (generic_secret_assignment) — Remove the secret from the diff, rotate the exposed credential, then re-run the gate.
Signal Result Evidence
Code review ❌ 1 blocker 1 reviewer
Linked issue ✅ Linked #3906, #1961
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (2 linked issues).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 51 registered-repo PR(s), 43 merged, 503 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 51 PR(s), 503 issue(s).
Gate result ❌ Blocking Repo-configured hard blocker found.
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: Python, TypeScript, JavaScript, Ruby, Go, Kotlin, MDX, Shell
  • Official Gittensor activity: 51 PR(s), 503 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.

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


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.

  • Re-run Gittensory review

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 7, 2026
@JSONbored
JSONbored merged commit 3356fc6 into main Jul 7, 2026
13 checks passed
@JSONbored
JSONbored deleted the feat/linked-issue-satisfaction-gate-orchestration branch July 7, 2026 23:07
loopover-orb Bot pushed a commit that referenced this pull request Jul 12, 2026
…on is unconsumed (#5308)

True when written (#4069, 2026-07-07), invalidated three days later
by #4149 (src/signals/focus-manifest.ts's resolveEffectiveSettings),
which added a fallback-alias: when gate.linkedIssueSatisfaction is
unset, review.linkedIssueSatisfaction IS folded in and feeds the
real merge/close decision. None of the 7 subsequent commits touching
this file corrected the comment. src/types.ts already carries the
correct, current description.

Comment-only, no behavior change.

Refs #5284
JSONbored added a commit that referenced this pull request Jul 21, 2026
… from the engine twin

While mirroring contentLaneDeliverableGateMode into gate-advisory.ts, found that
linkedIssueSatisfactionGateMode (#4069, merged 2026-07-07) was never mirrored either
-- CI's engine-parity check only diffs against each PR's own changed files, so a
prior PR that touched only the host twin without updating the engine twin (and
without bumping packages/loopover-engine/package.json) went undetected. Any
self-hoster consuming @loopover/engine's predicted-gate logic directly would
silently never predict a linked_issue_scope_mismatch blocker for a repo with
linkedIssueSatisfaction: block configured. Backfilled now while in the area.
JSONbored added a commit that referenced this pull request Jul 21, 2026
* feat(review): add a config-as-code content-lane deliverable gate

Registries with a "one entry file per contribution" content lane (e.g.
an issue naming a specific path under a configured entryFileGlob) could
have their linked issue auto-satisfied by a PR that never touches that
path -- a test-only or unrelated-file PR closes the issue without
delivering the content it asked for.

Add checkContentLaneDeliverable(), a pure, spec-driven check: it scans
the linked issue's text for a path token matching the resolved repo's
RegistryLaneSpec and verifies the PR's changed files actually touch it.
Wire it behind a new contentLaneDeliverableGateMode setting (off by
default, advisory/block like every other gate dimension), fully wired
through the DB, engine focus-manifest parser, OpenAPI schema, and
.loopover.yml -- no repo-specific logic anywhere in the check itself,
so any repo with a contentLane: config gets the same protection.

* fix(engine): mirror the content-lane deliverable gate mode into the engine twin

packages/loopover-engine/src/advisory/gate-advisory.ts is a hand-duplicated
twin of src/rules/advisory.ts (#4518's gate-decision twin pair), enforced by
scripts/check-engine-parity.ts's version-skew check. The new
contentLaneDeliverableGateMode field and its isConfiguredGateBlocker branch
were added to the host copy but never mirrored to the engine copy, so
self-hosters consuming @loopover/engine directly would silently lack the
new gate dimension, and CI's engine-parity check correctly failed the PR.

* fix(engine): backfill the linked-issue satisfaction gate mode missing from the engine twin

While mirroring contentLaneDeliverableGateMode into gate-advisory.ts, found that
linkedIssueSatisfactionGateMode (#4069, merged 2026-07-07) was never mirrored either
-- CI's engine-parity check only diffs against each PR's own changed files, so a
prior PR that touched only the host twin without updating the engine twin (and
without bumping packages/loopover-engine/package.json) went undetected. Any
self-hoster consuming @loopover/engine's predicted-gate logic directly would
silently never predict a linked_issue_scope_mismatch blocker for a repo with
linkedIssueSatisfaction: block configured. Backfilled now while in the area.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. manual-review Gittensor contributor context

Development

Successfully merging this pull request may close these issues.

fix(review): deterministic linked-issue gate never checks issue-PR scope match, only existence/openness AI linked-issue satisfaction assessment

1 participant