feat(BA-6554): add app_config_fragment service layer#12358
Merged
Conversation
This was referenced Jun 23, 2026
66ebd0a to
b40117b
Compare
59a0a70 to
7694f19
Compare
Base automatically changed from
feat/BA-6553-app-config-fragments-repository
to
main
June 23, 2026 03:58
7694f19 to
02106ca
Compare
jopemachine
commented
Jun 23, 2026
Contributor
There was a problem hiding this comment.
Pull request overview
Adds the AppConfigFragment service layer in the manager, including action/processors for standard operations and an allow-list write-gate backed by a new repository-level exists() query helper. Also extends RBAC enums/unions and updates GraphQL schema reference docs, with unit tests for the service and allow-list existence checks.
Changes:
- Implement
AppConfigFragmentService+ action classes and processor package for create/get/search/update/purge. - Add
ExistsQuerier+ execution path in repository base + ops provider, and exposeAppConfigAllowListRepository.exists()/ db_source plumbing. - Extend RBAC enums/DTOs/GQL unions and update GraphQL reference docs; add unit tests and Pants BUILD target.
Reviewed changes
Copilot reviewed 24 out of 27 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/manager/services/app_config_fragment/test_service.py | Service tests with mocked repos |
| tests/unit/manager/services/app_config_fragment/BUILD | Pants python_tests() target |
| tests/unit/manager/services/app_config_fragment/init.py | Test package marker |
| tests/unit/manager/repositories/app_config_allow_list/test_repository.py | Add unit tests for exists() |
| src/ai/backend/manager/services/app_config_fragment/service.py | Service layer + write-gate logic |
| src/ai/backend/manager/services/app_config_fragment/processors.py | Processor package wiring |
| src/ai/backend/manager/services/app_config_fragment/actions/update.py | Update action/result definitions |
| src/ai/backend/manager/services/app_config_fragment/actions/scoped_search.py | Scoped search action + target |
| src/ai/backend/manager/services/app_config_fragment/actions/purge.py | Purge action/result definitions |
| src/ai/backend/manager/services/app_config_fragment/actions/get.py | Get action/result definitions |
| src/ai/backend/manager/services/app_config_fragment/actions/create.py | Create action/result definitions |
| src/ai/backend/manager/services/app_config_fragment/actions/base.py | Shared action base classes |
| src/ai/backend/manager/services/app_config_fragment/actions/admin_search.py | Admin search action/result definitions |
| src/ai/backend/manager/services/app_config_fragment/actions/init.py | Actions package marker |
| src/ai/backend/manager/services/app_config_fragment/init.py | Service package marker |
| src/ai/backend/manager/repositories/ops/base/provider.py | Add ReadOps.exists() |
| src/ai/backend/manager/repositories/base/querier.py | Add ExistsQuerier + executor |
| src/ai/backend/manager/repositories/base/init.py | Export exists querier/executor |
| src/ai/backend/manager/repositories/app_config_allow_list/repository.py | Add repository exists() method |
| src/ai/backend/manager/repositories/app_config_allow_list/db_source/db_source.py | Add db_source exists() method |
| src/ai/backend/manager/errors/app_config.py | Add AppConfigFragmentWriteNotAllowed |
| src/ai/backend/manager/api/gql/rbac/types/permission.py | Include fragment in permission union |
| src/ai/backend/common/dto/manager/v2/rbac/types.py | Add DTO enum value |
| src/ai/backend/common/data/permission/types.py | Add EntityType/RBACElementType values |
| docs/manager/graphql-reference/v2-schema.graphql | Update schema reference enum |
| docs/manager/graphql-reference/supergraph.graphql | Update supergraph enum |
| changes/12358.feature.md | Towncrier feature entry |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
47053f0 to
c721b89
Compare
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the self-service update path and its action/processor/tests. The admin path remains the only write path; the now-unused AppConfigFragmentForbidden error is removed as well. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the action convention (allow_list, model_card, object_storage): the update action carries an Updater[Row] and the purge action a Purger[Row], passed straight to the repository — instead of loose (fragment_id, spec) params. Create keeps its CreatorSpec since the repository uses the next-value (rank) path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arch Follow the repository rename: rename the search action/method to admin_search and add a scoped_search action/method that builds a ConfigNameSearchScope and calls repository.scoped_search, so fragments can be searched within a single config_name. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ableActionTarget] Mirror the AuditLog scoped-search pattern: ScopedSearchAppConfigFragmentAction now extends BaseBulkAction[SearchableActionTarget] over a list of ConfigNameTarget items (each carrying its RBAC element ref + ConfigNameSearchScope), and the result extends BaseBulkActionResult exposing element_refs(). Wire the processor via BulkActionProcessor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The allow-list write-gate refuses a well-formed request on policy grounds, so it is a 403 Forbidden (GenericForbidden), not a 400 Bad Request. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ite-gate Rename the write-gate helper to _ensure_write_allowed and back it with a dedicated AppConfigAllowListRepository.exists(config_name, scope_type) instead of building a search querier inside the service. Add the exists method to the allow_list repository/db_source (+ real-DB tests) and switch the fragment service tests to mock exists. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce ExistsQuerier + execute_exists (SELECT EXISTS(...), no count/fetch) and ReadOps.exists, parallel to Querier/BatchQuerier. AppConfigAllowListRepository.exists now takes an ExistsQuerier (spec-object convention, like create/purge/query) instead of a batch_query_in_global count; the fragment service builds the querier in its write-gate. query() stays pk-only, so this fills the by-conditions existence gap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Clarify the service docstring: create/update are write-gated; purge is deliberately not (removal must stay possible after de-allow-listing). - Fix the news fragment to drop the removed self-service path. - execute_exists selects a literal (SELECT true) instead of all columns for the EXISTS probe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per the BA-6614 decision, a scoped fragment search is keyed by the principal scope type (domain / user), not by config_name. Replace ConfigNameTarget/ConfigNameSearchScope with Domain/User targets + scopes (mirroring the audit_log scoped-search pattern): DomainAppConfigFragmentTarget(DomainID) / UserAppConfigFragmentTarget(UserID) -> Domain/UserAppConfigFragmentSearchScope filtering on scope_type + scope_id, OR-combined. No RBAC validator and no existence check (unconditional; unknown scope -> empty). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review: an allow-listed user may update their own user-scope fragment, except the rank field. Drop rank from AppConfigFragmentUpdaterSpec (config-only; rank is assigned at create and never updatable), and correct the update action docstring — it is gated by the allow-list write-gate, not admin-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review: purge must pass the same allow-list write-gate as create/update and is not admin-only. service.purge now fetches the existing fragment and calls _ensure_write_allowed((config_name, scope_type)) before removing, so an allow-listed user may purge their own user-scope fragment. Update the service/action docstrings (drop the 'purge deliberately not gated' rationale) and add a purge-rejected-when-not-allow-listed test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…te (review) The app_config scope enum was defined twice in the data layer (app_config_fragment and app_config_allow_list), forcing the fragment service to import the allow_list copy as AllowListScopeType and convert via .value. Consolidate on the canonical data.app_config_fragment.types.AppConfigScopeType: allow_list data/model/repository/adapter now import it, and the fragment write-gate uses it directly (no conversion). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mmon.data directly (review) Per review: no re-export of AppConfigScopeType anywhere. Every consumer (manager data/models/repositories/services/adapters, allow_list v2 DTO request/response, GQL types, client CLI, tests) now imports it directly from ai.backend.common.data.app_config.types. The DTO/data types modules keep the import only for their own filter/dataclass use and no longer list it in __all__. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ory (review) Move the allow-list write-gate out of the service and into the fragment db_source so the gate check and the write share one transaction (no check-then-write race). WriteOps is read-capable, so create/update/purge run `w.exists(AppConfigAllowListRow ...)` inside their own write tx; update/purge also fetch the target row in that same tx, removing the service's separate get_by_id (3 txs -> 1). The service now purely delegates and no longer depends on AppConfigAllowListRepository. Gate pass/reject is now covered by real-DB repository tests; the service tests verify delegation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
008639e to
89c2848
Compare
fregataa
reviewed
Jun 25, 2026
…nly_if (review) Build the allow-list ExistsQuerier at the caller and pass it through the create/update/purge actions' new only_if field instead of constructing it inside the repository. The repository enforces whatever gate it is handed, atomically within the write transaction, and no longer encodes the allow-list policy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gate check (review) The gate is now just a caller-supplied ExistsQuerier; the one-line w.exists check reads clearly inline at each write site, so the helper (and its AppConfigScopeType / WriteOps imports) no longer earns its keep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…helper (review) The repository tests pass the only_if gate inline as ExistsQuerier(row_class=...) instead of a _allow_list_gate helper — the repository just enforces whatever gate it is handed, so the tests hand it a bare existence check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fregataa
reviewed
Jun 25, 2026
… (review) The pre-fetch SELECT only existed to build the WriteNotAllowed message from the row; the not-found case is already covered by the post-write None check. Build the gate-rejection message from the fragment id instead and check the allow-list gate first, so a missing fragment under a closed gate returns WriteNotAllowed rather than NotFound (consistent with create). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fregataa
approved these changes
Jun 25, 2026
This was referenced Jun 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the AppConfigFragment service layer (BA-6554) — the admin write paths with the allow-list write-gate. Mirrors the layered
AppConfigDefinition/AppConfigAllowListstacks under BEP-1052 (epic BA-5781).What's included
create,get,search,update,purge.app_config_allow_listrow for the target(config_name, scope_type). Since an allow-list row itself requires a registeredconfig_name(FK toapp_config_definitions), this single check also enforces registration. Violations raiseAppConfigFragmentWriteNotAllowed. This is the first point that depends on theAppConfigAllowListrepository.APP_CONFIG_FRAGMENTto theEntityType/RBACElementType(+ DTO) enums and the GraphQL permission union, plus the schema reference docs.AppConfigFragmentWriteNotAllowed.test_service.pywith mocked repositories covering the write-gate (pass/reject) and admin create/update/get/search/purge.Self-service write paths are intentionally not included here. Permission-matrix enforcement is carried declaratively by the action framework; the API wiring lands in the later REST/GQL PRs.
📚 Stacked PRs
Part of the AppConfigFragment / AppConfig stack under BEP-1052 (epic BA-5781). Merge in order:
feat(BA-6552): app_config_fragments DB model and Alembic migrationfeat(BA-6553): repository layerrefactor(BA-6619): consolidate AppConfigScopeType into common.datarefactor(BA-6620): ExistsQuerier + AppConfigAllowList.existsfeat(BA-6554): AppConfigFragment service layer← you are herefeat(BA-6618): AppConfigFragment bulk CRUD service layerfeat(BA-6555): app_config service layerfeat(BA-6556): AppConfig REST v2 API📚 Documentation preview 📚: https://sorna--12358.org.readthedocs.build/en/12358/
📚 Documentation preview 📚: https://sorna-ko--12358.org.readthedocs.build/ko/12358/