Skip to content

feat(BA-6554): add app_config_fragment service layer#12358

Merged
fregataa merged 21 commits into
mainfrom
feat/BA-6554-app-config-fragments-service
Jun 25, 2026
Merged

feat(BA-6554): add app_config_fragment service layer#12358
fregataa merged 21 commits into
mainfrom
feat/BA-6554-app-config-fragments-service

Conversation

@jopemachine

@jopemachine jopemachine commented Jun 23, 2026

Copy link
Copy Markdown
Member

Summary

Adds the AppConfigFragment service layer (BA-6554) — the admin write paths with the allow-list write-gate. Mirrors the layered AppConfigDefinition / AppConfigAllowList stacks under BEP-1052 (epic BA-5781).

What's included

  • Actions / processors for the standard operations: create, get, search, update, purge.
  • Admin path: create / update / purge at any scope — the only path that creates user-scope rows.
  • Write-gate: every write requires an app_config_allow_list row for the target (config_name, scope_type). Since an allow-list row itself requires a registered config_name (FK to app_config_definitions), this single check also enforces registration. Violations raise AppConfigFragmentWriteNotAllowed. This is the first point that depends on the AppConfigAllowList repository.
  • RBAC: adds APP_CONFIG_FRAGMENT to the EntityType / RBACElementType (+ DTO) enums and the GraphQL permission union, plus the schema reference docs.
  • Errors: AppConfigFragmentWriteNotAllowed.
  • Tests: test_service.py with 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:

  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 ← you are here
  6. feat(BA-6618): app_config_fragment bulk CRUD service layer #12401feat(BA-6618): AppConfigFragment bulk CRUD service layer
  7. feat(BA-6555): add app_config service layer #12359feat(BA-6555): app_config service layer
  8. feat(BA-6556): AppConfig REST v2 API (raw fragments and merged read/update) #12377feat(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/

@github-actions github-actions Bot added the size:XL 500~ LoC label Jun 23, 2026
@github-actions github-actions Bot added area:docs Documentations comp:manager Related to Manager component comp:common Related to Common component labels Jun 23, 2026
@jopemachine jopemachine force-pushed the feat/BA-6554-app-config-fragments-service branch 5 times, most recently from 66ebd0a to b40117b Compare June 23, 2026 02:41
@jopemachine jopemachine force-pushed the feat/BA-6554-app-config-fragments-service branch 6 times, most recently from 59a0a70 to 7694f19 Compare June 23, 2026 03:48
Base automatically changed from feat/BA-6553-app-config-fragments-repository to main June 23, 2026 03:58
@jopemachine jopemachine force-pushed the feat/BA-6554-app-config-fragments-service branch from 7694f19 to 02106ca Compare June 23, 2026 04:04
Comment thread src/ai/backend/manager/errors/app_config.py Outdated
@jopemachine jopemachine marked this pull request as ready for review June 23, 2026 06:38
@jopemachine jopemachine requested a review from a team as a code owner June 23, 2026 06:38
Copilot AI review requested due to automatic review settings June 23, 2026 06:38

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

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 expose AppConfigAllowListRepository.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.

Comment thread src/ai/backend/manager/services/app_config_fragment/service.py
Comment thread changes/12358.feature.md Outdated
Comment thread src/ai/backend/manager/repositories/base/querier.py Outdated
Comment thread tests/unit/manager/services/app_config_fragment/test_service.py Outdated
jopemachine and others added 15 commits June 25, 2026 16:19
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>
@jopemachine jopemachine force-pushed the feat/BA-6554-app-config-fragments-service branch from 008639e to 89c2848 Compare June 25, 2026 07:24
Comment thread src/ai/backend/manager/repositories/app_config_fragment/db_source/db_source.py Outdated
jopemachine and others added 4 commits June 25, 2026 17:51
…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>
@jopemachine jopemachine requested review from a team and fregataa June 25, 2026 09:34
Comment thread src/ai/backend/manager/repositories/app_config_fragment/db_source/db_source.py Outdated
Comment thread src/ai/backend/manager/repositories/app_config_fragment/db_source/db_source.py Outdated
… (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 fregataa merged commit d4c9971 into main Jun 25, 2026
34 of 36 checks passed
@fregataa fregataa deleted the feat/BA-6554-app-config-fragments-service branch June 25, 2026 11:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:docs Documentations comp:cli Related to CLI component comp:client Related to Client component comp:common Related to Common component comp:manager Related to Manager component size:XL 500~ LoC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants