Skip to content

fix(engine): isolate getTenantConfig reads and normalize tenant config - #9627

Merged
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
kai392:fix/9614-v3-tenant-config
Jul 29, 2026
Merged

fix(engine): isolate getTenantConfig reads and normalize tenant config#9627
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
kai392:fix/9614-v3-tenant-config

Conversation

@kai392

@kai392 kai392 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Closes #9614

What

packages/loopover-engine/src/tenant-config.ts promised full per-tenant isolation but had two gaps:

1. The read path leaked the stored object. getTenantConfig returned store[tenantId] ?? resolveTenantConfig() — only the miss path was fresh. On a hit it handed back the stored object, and setTenantConfig's Object.freeze is shallow, so a caller could mutate a read config and silently rewrite that tenant's stored autonomyLevel/allowedActionClasses. Now getTenantConfig deep-copies on both paths and setTenantConfig deep-freezes the written entry (config, preferences, and the action-class array).

2. maxConcurrentLoops was unnormalized. ?? let -5/0.5/NaN/Infinity through. It now uses the exact finiteNonNegativeInt body from tenant-quota.ts:45-47 (#5828) on an explicitly-supplied override only — an absent override still inherits DEFAULT_TENANT_CONFIG's 1. resolveTenantConfig also drops non-string allowedActionClasses entries, mirroring the autonomyLevel guard.

DEFAULT_TENANT_CONFIG, TENANT_AUTONOMY_LEVELS, EMPTY_TENANT_CONFIG_STORE, and every exported type are unchanged; the JSDoc is corrected.

Tests

Every changed branch is covered in the engine's own node:test suite (packages/loopover-engine/test/tenant-config.test.ts) — the suite the engine Codecov flag grades this source with — at 100% line/branch/function, plus the root vitest suite. Verified: engine suite 6/6, root suite 14/14, tsc --noEmit clean, git diff --check clean.

@kai392
kai392 requested a review from JSONbored as a code owner July 29, 2026 01:21
@loopover-orb

loopover-orb Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Warning

⏸️ LoopOver review result - manual review recommended

Review updated: 2026-07-29 01:42:32 UTC

3 files · 1 AI reviewer · no blockers · CI green · clean

⏸️ Suggested Action - Manual Review

Review summary
This PR closes a real mutation-leak bug: getTenantConfig previously returned the stored object on cache hits, and setTenantConfig's shallow freeze left preferences/allowedActionClasses mutable, so a caller could rewrite a tenant's stored config in place. The fix (deep-copy on both read paths, deep-freeze on write, plus finiteNonNegativeInt normalization for maxConcurrentLoops and a string-only filter for allowedActionClasses) is correct and traceable to the diff, and it's backed by tests in both the engine's own node:test suite and the root vitest suite that actually exercise the hit-path mutation scenario the bug described. The maxConcurrentLoops normalization correctly only applies to explicitly-supplied overrides, preserving the inherited default of 1 when absent.

Nits — 6 non-blocking
  • packages/loopover-engine/src/tenant-config.ts: cloneTenantConfig and the freeze logic in setTenantConfig duplicate the same three-field shape as TenantConfig/TenantExecutionPreferences — if a new preference field is ever added, both this and resolveTenantConfig must be updated in lockstep, which is easy to miss.
  • packages/loopover-engine/src/tenant-config.ts: finiteNonNegativeInt silently coerces negative/NaN/Infinity to 0 rather than distinguishing between 'must be non-negative' and 'must be a number at all' — consider whether callers might want to know an override was rejected vs. clamped to zero.
  • The two near-duplicate test files (packages/loopover-engine/test/tenant-config.test.ts and test/unit/tenant-config.test.ts) reimplement nearly identical test cases against the same source — worth confirming both are intentionally required by separate coverage flags rather than redundant maintenance burden.
  • Consider extracting the preferences deep-freeze/deep-copy shape into a single source of truth (e.g., iterate Object.keys or a shared helper) so adding a new TenantExecutionPreferences field can't silently bypass isolation in one of the two paths.
  • packages/loopover-engine/src/tenant-config.ts:47 — the JSDoc above finiteNonNegativeInt is dense with issue/PR references; a one-line summary plus the existing fix(engine): worktree-pool.ts doesn't normalize maxConcurrency, NaN disables the cap #5828 pointer would be more readable long-term.
  • Readiness score is below the configured threshold — Use the readiness panel as advisory maintainer context; the score does not block this PR.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #9614
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ❌ 8/20 High review scope from cached public metadata (1 linked issue).
Validation posture ❌ 5/25 Preflight is holding this PR: the review lane is unavailable, so it is not ready for automated review.
Contributor workload ✅ 10/10 Author activity: 100 registered-repo PR(s), 49 merged, 7 issue(s).
Contributor context ✅ Confirmed Gittensor contributor kai392; Gittensor profile; 100 PR(s), 7 issue(s).
Improvement ✅ Minor risk: clean · value: minor · LLM: moderate
Linked issue satisfaction

Partially addressed
The PR correctly implements deep-copy-on-read, deep-freeze-on-write, maxConcurrentLoops normalization via the exact finiteNonNegativeInt body, allowedActionClasses filtering, and updated JSDoc, with strong test coverage for the hit path and normalization cases. However, the specific required regression test for the miss/fallback path — asserting that mutating the result of one getTenantConfig call

Review context
  • Author: kai392
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is registered but has no active allocation in the current snapshot.
  • Public profile languages: Python, Cuda, JavaScript, Kotlin, Perl, Ruby, TypeScript, Vue
  • Official Gittensor activity: 100 PR(s), 7 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Add a concise scope and risk note.
  • Then work through the remaining 4 steps in the Signals table above.
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.
🧪 Chat with LoopOver

Ask 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.

  • @loopover ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

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.

  • Re-run LoopOver review

@superagent-security

Copy link
Copy Markdown
Contributor

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

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.03%. Comparing base (bd139a5) to head (da47c4f).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9627      +/-   ##
==========================================
+ Coverage   90.02%   90.03%   +0.01%     
==========================================
  Files         888      888              
  Lines      111983   112021      +38     
  Branches    26570    26575       +5     
==========================================
+ Hits       100810   100857      +47     
+ Misses       9843     9830      -13     
- Partials     1330     1334       +4     
Flag Coverage Δ
backend 95.56% <100.00%> (-0.01%) ⬇️
engine 66.65% <100.00%> (+0.09%) ⬆️

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

Files with missing lines Coverage Δ
packages/loopover-engine/src/tenant-config.ts 100.00% <100.00%> (+15.66%) ⬆️

... and 1 file with indirect coverage changes

JSONbored#9614)

tenant-config.ts promised full per-tenant isolation but leaked the stored object and
skipped normalizing maxConcurrentLoops:

- getTenantConfig now deep-copies on BOTH paths (the hit path previously returned the
  stored object; Object.freeze is shallow), and setTenantConfig deep-freezes the written
  entry (config, preferences, and the action-class array).
- resolveTenantConfig normalizes an explicitly-supplied maxConcurrentLoops override with
  the exact finiteNonNegativeInt body from tenant-quota.ts:45-47 (JSONbored#5828) -- absent override
  still inherits DEFAULT_TENANT_CONFIG's 1 -- and drops non-string allowedActionClasses
  entries, mirroring the existing autonomyLevel guard.
- Corrected getTenantConfig's JSDoc; DEFAULT_TENANT_CONFIG, TENANT_AUTONOMY_LEVELS,
  EMPTY_TENANT_CONFIG_STORE, and every exported type are unchanged.

Tests cover every changed branch in BOTH suites: the engine's own node:test suite
(packages/loopover-engine/test/tenant-config.test.ts -- the source graded by the engine
Codecov flag) at 100% branch, plus the root vitest suite.

Closes JSONbored#9614
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 29, 2026

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb
loopover-orb Bot merged commit 676ba3c into JSONbored:main Jul 29, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

engine(tenant-config): getTenantConfig hands out the store's live config object

2 participants