Skip to content

feat(BA-6810): AppConfigFragment visible-fragments query (repository layer)#12706

Merged
HyeockJinKim merged 2 commits into
mainfrom
feat/BA-6555-app-config-visible-fragments
Jul 10, 2026
Merged

feat(BA-6810): AppConfigFragment visible-fragments query (repository layer)#12706
HyeockJinKim merged 2 commits into
mainfrom
feat/BA-6555-app-config-visible-fragments

Conversation

@jopemachine

@jopemachine jopemachine commented Jul 9, 2026

Copy link
Copy Markdown
Member

📚 Stacked PRs

Part of the AppConfigFragment / AppConfig stack under BEP-1052 (epic BA-5781). Merge in order:

  1. feat(BA-6552): add app_config_fragments DB model and Alembic migration #12306feat(BA-6552): app_config_fragments DB model and Alembic migration
  2. feat(BA-6553): add app_config_fragments repository layer #12307feat(BA-6553): repository layer
  3. refactor(BA-6619): consolidate AppConfigScopeType into common.data (single definition) #12403refactor(BA-6619): consolidate AppConfigScopeType into common.data
  4. refactor(BA-6620): ExistsQuerier ops primitive + AppConfigAllowList.exists #12405refactor(BA-6620): ExistsQuerier + AppConfigAllowList.exists
  5. feat(BA-6554): add app_config_fragment service layer #12358feat(BA-6554): AppConfigFragment service layer
  6. feat(BA-6702): move fragment rank to the allow list with scope defaults #12516feat(BA-6702): move fragment rank to the allow list with scope defaults
  7. feat(BA-6701): expose allow-list rank on the v2 API surface #12517feat(BA-6701): expose allow-list rank on the v2 API surface
  8. feat(BA-6704): cascade app config subtree deletion from the definition #12518feat(BA-6704): cascade app config subtree deletion from the definition
  9. feat(BA-6626): app_config_fragment bulk repository layer #12426feat(BA-6626): app_config_fragment bulk repository layer
  10. feat(BA-6618): app_config_fragment bulk CRUD service layer #12401feat(BA-6618): app_config_fragment bulk CRUD service layer
  11. 👉 feat(BA-6810): AppConfigFragment visible-fragments query (repository layer) #12706feat(BA-6810): AppConfigFragment visible-fragments query (repository layer) ← you are here
  12. feat(BA-6555): AppConfig merge engine + resolve service (service layer) #12359feat(BA-6555): AppConfig merge engine + resolve service (service layer)
  13. feat(BA-6556): AppConfig REST v2 API (raw fragments and merged read/update) #12377feat(BA-6556): AppConfig REST v2 API

Summary

Split out of #12359 (BA-6555). This is the repository read layer the merged-AppConfig service (#12359) builds on; the merge engine + resolve service follows in #12359 (stacked on this).

What's included

  • Visibility conditions (models/app_config_fragment/conditions.py): by_config_names (name membership) plus scope-only by_public_visibility / by_domain_visibility / by_user_visibility — kept separate so the merged read AND-combines config_name IN (...) with (public OR domain OR user).
  • AppConfigScopeArguments (repositories/app_config_fragment/types.py): the resolving (domain, user) principal, bundled so it travels together.
  • list_visible_fragments / list_visible_fragments_bulk (db_source.py + repository.py): per name, the public OR the scope's domain OR its user fragment, joined to the allow-list for the merge rank, fetched in a single query (rank-ordered, ready to deep-merge).
  • Real-DB repository tests: the visibility conditions and the merged read.

No service/merge logic here — that is #12359.

@github-actions github-actions Bot added size:L 100~500 LoC comp:manager Related to Manager component labels Jul 9, 2026
@jopemachine jopemachine force-pushed the feat/BA-6555-app-config-visible-fragments branch from a676c3f to 5816ebe Compare July 9, 2026 08:47
@jopemachine jopemachine changed the title feat(BA-6555): AppConfigFragment visible-fragments query (repository layer) feat(BA-6810): AppConfigFragment visible-fragments query (repository layer) Jul 9, 2026
@jopemachine jopemachine marked this pull request as ready for review July 9, 2026 09:11
@jopemachine jopemachine requested a review from a team as a code owner July 9, 2026 09:11
Copilot AI review requested due to automatic review settings July 9, 2026 09:11

Copilot AI 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.

Pull request overview

This PR adds the repository read layer for resolving a merged AppConfig — the query foundation the merge/resolve service (#12359) builds on. Given a (domain, user) principal, it fetches the fragments visible to that principal (the public scope, plus the principal's own domain and user fragments) for one or many config names, joined to the allow-list to carry the merge rank, in a single rank-ordered query. It is part of the AppConfigFragment/AppConfig stack under BEP-1052.

Changes:

  • Adds visibility query conditions (by_config_names, by_public_visibility, by_domain_visibility, by_user_visibility) kept separate so the read AND-combines config_name IN (...) with the (public OR domain OR user) scope group.
  • Adds AppConfigScopeArguments (bundled (domain_id, user_id) principal) and the list_visible_fragments / list_visible_fragments_bulk methods on the db_source + repository, using an inner join to the allow-list for rank, rank-ordered.
  • Adds real-DB repository tests for the visibility conditions and the merged read, plus a changelog fragment.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/ai/backend/manager/models/app_config_fragment/conditions.py New name-membership + per-scope visibility condition builders.
src/ai/backend/manager/repositories/app_config_fragment/types.py New AppConfigScopeArguments principal value object.
src/ai/backend/manager/repositories/app_config_fragment/db_source/db_source.py _list_visible_fragments + single/bulk public methods (allow-list join, rank ordering).
src/ai/backend/manager/repositories/app_config_fragment/repository.py Resilience-wrapped repository passthroughs for the two new reads.
tests/unit/manager/repositories/app_config_fragment/test_repository.py Real-DB tests for visibility conditions and merged read.
changes/12706.feature.md Changelog fragment.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…layer)

Add the rank-ordered read that the merged-AppConfig service builds on:
- by_config_names + scope-only by_public/domain/user_visibility conditions
- AppConfigScopeArguments (the resolving (domain, user) principal)
- list_visible_fragments / list_visible_fragments_bulk: per name, the public OR the
  scope's domain OR its user fragment, joined to the allow-list for the merge rank, in a
  single query
- real-DB repository tests for the visibility conditions and the merged read

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jopemachine jopemachine force-pushed the feat/BA-6555-app-config-visible-fragments branch from 5816ebe to 81ef72e Compare July 9, 2026 09:29
Comment thread src/ai/backend/manager/repositories/app_config_fragment/db_source/db_source.py Outdated
Comment on lines +218 to +221
scope_visibility = [
AppConfigFragmentConditions.by_public_visibility(),
AppConfigFragmentConditions.by_domain_visibility(str(scope.domain_id)),
AppConfigFragmentConditions.by_user_visibility(str(scope.user_id)),

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.

It doesn't seem necessary to manage this as a separate variable.

Comment on lines +138 to +160
@staticmethod
def by_domain_visibility(domain: str) -> QueryCondition:
"""The ``domain`` scope for ``domain``."""

def inner() -> sa.sql.expression.ColumnElement[bool]:
return sa.and_(
AppConfigFragmentRow.scope_type == AppConfigScopeType.DOMAIN,
AppConfigFragmentRow.scope_id == domain,
)

return inner

@staticmethod
def by_user_visibility(user_id: str) -> QueryCondition:
"""The ``user`` scope for ``user_id``."""

def inner() -> sa.sql.expression.ColumnElement[bool]:
return sa.and_(
AppConfigFragmentRow.scope_type == AppConfigScopeType.USER,
AppConfigFragmentRow.scope_id == user_id,
)

return inner

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.

Did you decide that managing it this way is better than generalizing it into a single condition? (I’m asking just to confirm, since this seems like a design choice.)

Comment on lines +110 to +120
@app_config_fragment_repository_resilience.apply()
async def list_visible_fragments(
self, config_name: str, scope: AppConfigScopeArguments
) -> list[AppConfigFragmentData]:
return await self._db_source.list_visible_fragments(config_name, scope)

@app_config_fragment_repository_resilience.apply()
async def list_visible_fragments_bulk(
self, config_names: list[str], scope: AppConfigScopeArguments
) -> list[AppConfigFragmentData]:
return await self._db_source.list_visible_fragments_bulk(config_names, scope)

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.

It seems like just using bulk would be fine- is there really a need to manage list_visible_fragments separately?

Drop the single-name list_visible_fragments and the shared private
_list_visible_fragments helper, inlining the query body into
list_visible_fragments_bulk. Callers use the bulk form with a
single-element list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment on lines +231 to +239
# Join each fragment to its allow-list entry (indexed ``(config_name, scope_type)`` FK
# pair), which carries the merge ``rank`` the result is ordered by.
selector = sa.select(AppConfigFragmentRow).join(
AppConfigAllowListRow,
sa.and_(
AppConfigAllowListRow.config_name == AppConfigFragmentRow.config_name,
AppConfigAllowListRow.scope_type == AppConfigFragmentRow.scope_type,
),
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Isn't the "join" unnecessary?

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

Labels

comp:manager Related to Manager component size:L 100~500 LoC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants