feat(api): tenant self-service for installation health/repair - #8006
feat(api): tenant self-service for installation health/repair#8006rsnetworkinginc wants to merge 1 commit into
Conversation
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
|
Caution 🛑 LoopOver review result - reject/close recommendedReview updated: 2026-07-22 10:39:26 UTC
Review summary Nits — 5 non-blocking
Why this is blocked
📋 Copy for AI agents — paste into your coding agentDecision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. 🟩 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 LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
|
LoopOver is closing this pull request on the maintainer's behalf (Possible leaked secret in the diff (private_key_block)). This is an automated maintenance action — to pursue this change, please open a new pull request with the issues resolved. Closed PRs may be analyzed later to improve review accuracy, but they are not automatically reopened or re-reviewed. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8006 +/- ##
==========================================
- Coverage 91.92% 89.80% -2.13%
==========================================
Files 741 95 -646
Lines 75957 24371 -51586
Branches 23048 4587 -18461
==========================================
- Hits 69827 21887 -47940
+ Misses 5037 2214 -2823
+ Partials 1093 270 -823
Flags with carried forward coverage won't be shown. Click here to find out more.
|
Summary
The installation health/repair routes —
GET /v1/installations,GET /v1/installations/:id/health,GET /v1/installations/:id/repair, andPOST /v1/installations/:id/repair/refresh(src/api/routes.ts) — carry no inline role/ownership check. They are reachable only by the global fleet operator, becausecanSessionAccessPathfalls through to the operator gate for any path not on its explicit allowlist. A hosted tenant therefore has no self-service way to see or repair even their own installation's health.This adds a tenant-scoped equivalent under
/v1/app/installations*, reusing/v1/app/maintainer-dashboard's exact scoping mechanism (loadControlPanelAccessScopeinsrc/services/control-panel-roles.ts) rather than inventing a new auth path. An operator (or static service identity) still sees the whole fleet; a non-operator session is limited to installations under their own account or maintained repos, so tenant A can never read or trigger repair on tenant B's installation. No bulk/cross-repo controls are added (that is #7676, out of scope).Closes #7661
What changed
src/api/routes.ts(+78): adds four tenant-scoped sibling routes next to the operator-only installation routes:GET /v1/app/installations— lists only the caller's in-scope installations and health.GET /v1/app/installations/:id/health— the caller's own installation health (403forbidden_installationotherwise).GET /v1/app/installations/:id/repair— repair diagnostics for the caller's own installation.POST /v1/app/installations/:id/repair/refresh— refreshes the caller's own installation; ownership is enforced before the refresh side effect so a tenant can never trigger repair on an installation they don't own.resolveAppInstallationScope(mirrors the maintainer-dashboard role gate +loadControlPanelAccessScopestep, returningscope === nullfor operators/service identities) andinstallationRecordInScope(the exact installation-id / account-login match maintainer-dashboard applies). The unauthenticated guard reuses the file's established/* v8 ignore next */pattern, since the global protected-route middleware already rejects unauthenticated private requests before the handler.test/integration/app-installations-selfservice.test.ts(+249, new): a dedicated in-process integration test driving every route and every added branch viaapp.request.Tests
New dedicated test file exercises all four routes end-to-end in-process, covering every added branch and error path: operator/api-token whole-fleet access, tenant-scoped list, own-installation reads (both the installation-id and account-login scope arms), the ownership-denied
403 forbidden_installationpath (tenant A → tenant B), theinsufficient_rolegate on every route, invalid-id400, not-found404, the refreshinstallation_not_foundpath, and a successful own-installation refresh. Measured coverage the codecov way (vitest --coverage --coverage.reporter=json) and cross-checked every added line in the diff againstcoverage-final.json: 0 uncovered statements and 0 uncovered branches on the changed source.Verified locally on current main: typecheck/engine build/
git diff --checkclean, 7 new tests pass (77 pass across the touched integration + openapi suites), 100% patch coverage —src/api/routes.ts: 0 uncovered statement lines, 0 uncovered branch lines on the added diff (src/services/control-panel-roles.tsunchanged).