refactor(BA-7297): migrate the manager's pass-through actions to ops-generic wiring - #13708
Open
HyeockJinKim wants to merge 53 commits into
Open
refactor(BA-7297): migrate the manager's pass-through actions to ops-generic wiring#13708HyeockJinKim wants to merge 53 commits into
HyeockJinKim wants to merge 53 commits into
Conversation
fregataa
reviewed
Aug 12, 2026
Comment on lines
+50
to
+58
| with pytest.raises(PermissionDeniedError): | ||
| await user_registry.operations.append_error_log( | ||
| AppendErrorLogRequest( | ||
| severity=ErrorLogSeverity.WARNING, | ||
| source="test-user", | ||
| message="Test warning from user", | ||
| context_lang="python", | ||
| context_env="{}", | ||
| ) |
Member
There was a problem hiding this comment.
Is the change of results of the same action okay?
…ps-generic wiring Every retention_policy operation only forwarded a repository spec, so the domain now wires the generic ops services directly: the actions carry v2 specs (`RetentionPolicyCreator` / `Updater` / `Purger` / `Searcher`) and the service, repository and db_source are gone. `delete` becomes purge-shaped — a retention policy carries no lifecycle column, so removing one has always been the row leaving the table; it keeps its own `action_name` so the audit history does not merge with `purge`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ops-generic wiring Both services only forwarded a repository spec, so the catalog now wires the generic ops services directly — reads through the public gate (`public_get_ops` / `public_search_ops`), writes through the SUPERADMIN one. The service, admin service, repository, admin repository and db_source are gone. `delete` becomes purge-shaped: the table carries no lifecycle column, so removing a row has always been a hard delete. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The search executed SQL inside the service — a layer violation with no repository behind it. The select and the row-to-data assembly move into a `Searcher` spec and the action wires straight to `global_search_ops`, so the service disappears without a repository being introduced in its place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The keypair / user / project resource policies key on `name` — what `keypairs`, `users` and `groups` reference — so the primary key stays where it is. What they lacked is an `EntityID`: the v2 action layer identifies every entity by a UUID, and `ProcessorGroup` is bounded on `EntityData`, so a name-keyed row could not be wired to the generic ops services at all. `uuid` is added as a unique alternate key alongside the name, the same shape `resource_slot_types` already carries, and the `data/` types report it as their `entity_id()`. Existing rows are backfilled by the column default, so the column is NOT NULL from the start. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ns to ops-generic wiring Every operation only forwarded a repository spec, so the domain wires the generic ops services directly and the service, repository and their legacy specs are gone. The name stays the primary key, so the querier, updater and purger address the row by it while the new `uuid` answers `entity_id()`. Adds the `global_get_ops` factory: `GetGlobalOpsAction` already existed for a SUPERADMIN-gated read of a name-keyed global row, but only the `public_*` half of the get axis had a factory — the search axis already carried both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…to ops-generic wiring Five of the six operations only forwarded a repository spec and now wire to the generic ops services. The service keeps one method: resolving the caller's own policy joins through `users`, which a `DataLookup` cannot express — a lookup spec stays on one table by design — so that read keeps its repository method, and the auth service's `get_by_name` keeps the other. The repository test now covers the join that justifies the repository, and the service test covers the one method left on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ns to ops-generic wiring Five of the six operations only forwarded a repository spec and now wire to the generic ops services. The service keeps one method: resolving the caller's own policy joins through `keypairs` and filters on the keypair being active, which a `DataLookup` cannot express — a lookup spec stays on one table by design. The creator drops `max_quota_scope_size` / `max_vfolder_count` / `max_vfolder_size`: they are not columns of this table and `build_row` never wrote them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`FieldEntityCreator.build_row(owner_id)` already had the shape a just-created parent's id calls for, and `V2WriteOps` already carried both writes on one session — what was missing was the composition above it, so a parent-plus-children create had no ops-direct path and stayed on the legacy `DependentCreatorSpec`. Adds that composition at each layer it was missing: `EntityWithFieldsResult` (the repository answer, a dataclass rather than a pair so the two halves cannot be read the wrong way round), `OpsRepository.create_global_entity_with_fields`, `GlobalCreateWithFieldsService`, `CreateGlobalWithFieldsOpsAction` and the `global_create_with_fields_ops` factory. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`bulk_` covered two contracts at once. `bulk_create_*` flushed every row in one go
and raised on the first failure; `bulk_purge_*` and `bulk_update_data` isolated each
item in a savepoint and answered per entity. Only the argument shape (`Sequence` vs
`Mapping[EntityID, …]`) and the return type (`list` vs `BulkResultWithFailures`) said
which was which.
`Bulk` was also the wrong word on the create side: none of the four bulk creates is
bulk-shaped — their target is one scope, one owner, or the system, so they are
scope-, single-entity- and global-shaped. Only the purge and update paths carry
`BaseBulkAction`, where the caller names the entities and each is answered for.
So the failure mode is now written out, with no unmarked default:
atomic_create_* raises; the run records one failure
partial_bulk_purge_* per-entity verdicts; the run itself succeeds
partial_bulk_update_*
The rename runs through the ops methods, `OpsRepository`, the ops-backend action
axis, the combined bases, the generic services and the factories. Shapes, result
types, the condition-selected `batch_*` family and the legacy providers are
untouched; the factories have no callers yet, so no wiring site moves.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soft delete keeps turning up — role preset today, vfolder, project and user next — and each of them meets the same three-layer split with nothing written down: ops executes an UPDATE, the action reclassifies the run as a DELETE, and the spec is the only place the transition can be fenced off. The rules go in `models/specs/AGENTS.md` (the lifecycle column stays off the general updater; delete and restore get their own updaters with constant `build_values()`) and `actions/AGENTS.md` (a soft delete inherits a `Delete*` base and never reaches the transition through an update-shaped action). The background goes in the matching `KNOWLEDGE.md` files: why the guard has to sit in the spec rather than in ops, why the delete services are identical to their update siblings on purpose, and — from the rename that preceded this — why the atomic/partial failure mode is named rather than passed as an argument. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`ActionOperationType.RESTORE` already existed, mapped to the soft-delete permission, and was written up as a deliberate split — the audit label is restore, the permission checked is soft-delete. What was missing was anywhere to declare it: the v2 ops bases stopped at `Delete*`, so a restore had no base to inherit and fell back to declaring `UPDATE`, which loses the distinction exactly where it was supposed to be visible. Adds `RestoreSingleEntityOpsAction` and `RestorePartialBulkOpsAction` with the services and factories behind them. Both execute as an update, like their delete siblings — the DB operation is an UPDATE either way and the direction is the updater's business. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every one of the eleven operations only forwarded a repository spec, so the domain wires the generic ops services directly and the service, repository and db_source are gone — the RBAC-specific ops provider goes with them. The create was the reason this domain had stayed on the legacy path: it writes a preset and its permission rows together, which the v2 specs could not express until the with-fields composition landed. `RolePermissionPresetCreator` is a field creator of its preset, exactly as the entity judgment already said, and the create carries both specs so the two writes share a transaction. Soft delete gets the split the conventions now call for: `RolePresetUpdater` carries no `deleted` field, and `RolePresetSoftDeleteUpdater` / `RolePresetRestoreUpdater` write the constant. Restore declares `RESTORE` rather than the `UPDATE` it used to. `bulk_add_permissions` becomes atomic — a preset granting a subset of what the caller asked for is worse than one that refused — while removals stay per-entity, where the caller named the rows and each one's fate belongs in the answer. The `role_name_template` check moves out of the write and into an action validator, so a broken template is refused before a transaction opens. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s-generic wiring All five operations only forwarded a repository spec, so the domain wires the generic ops services directly and the service is gone. `resolve_by_name` becomes the first `lookup_ops` consumer: the variant name is a unique key on one table, which is exactly what `DataLookup` is for, and the lookup shape reports the resolved id on the audit trail where the search it used to ride on reported nothing. The repository keeps the two reads that live outside the action layer — sokovan's deployment executor reads a variant by id, the model-serving service by name — and loses everything the ops path now covers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-generic wiring Six of the eight operations only forwarded a repository spec and now wire to the generic ops services. The service keeps the two presigned-URL paths, which reach S3, and moves to the v2 global base behind the service-backed factory. `delete` becomes `purge` in name as well as shape. The table carries no lifecycle column — the repository issued a plain `sa.delete` — so declaring `DELETE` had it checked against the soft-delete permission instead of the hard-delete one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…neric wiring Six of the ten operations only forwarded a repository spec and now wire to the generic ops services. The service keeps the four quota-scope paths, which reach the storage proxy, and moves to the v2 global base behind the service-backed factory. `delete` becomes `purge` in name and shape, as object_storage's did — the table has no lifecycle column, so `DELETE` had it checked against the soft-delete permission. `get` branched on which key the caller supplied, reading by id or by name. The name half becomes its own lookup action, which is the shape that branch exists to absorb; the adapter and the download handler each call the one they mean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`NotificationRuleData` carried the whole channel, so the row conversion read a relationship, so `NotificationRuleRow` needed one, so every create, update, get and search re-selected the rule with the channel eagerly loaded. One choice in the data type reached all the way down into four queries. A row projection mirrors one table. The rule now carries `channel_id`, the relationship and its join-condition helper are gone, and the four eager loads with them. Both id fields become typed (`NotificationRuleID` / `NotificationChannelID`) and the two data types become frozen, as the data layer requires. Two reads genuinely need both tables and say so in the statement: dispatch joins the channel to filter on its `enabled` flag and returns the pair as `MatchingNotificationRuleData` — a composite the repository assembles, not a row projection — and `validate_rule` reads the channel by the id the rule names. The GraphQL rule keeps a `channel` field, now a resolver over the channel dataloader that already existed: a client that does not ask for it does not pay for the read, and one that asks across a page gets them batched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`notification_rules.channel_id` is NOT NULL, so a rule without a channel cannot exist and the resolver has no reason to answer null — which also keeps the field from becoming a breaking schema change. A miss now raises `NotificationChannelNotFound` rather than being reported as absence. `channelId` is a field added after its parent type, so it declares its own `added_version` instead of inheriting the type's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every operation forwarded a spec through the service and the repository without touching it, so both layers go and the actions carry the v2 specs. The create was scope-shaped, but the scope it named was always the global one with an empty id — a global action wearing a scope. It says so now, which moves its gate from a scope-chain resolution that only the global scope could satisfy to the SUPERADMIN check the rest of the catalog's writes already use. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-generic wiring Four of the seven operations forwarded a spec without touching it; those move to ops and the delete becomes a purge, the table carrying no lifecycle column. Three stay. Preview and execute call Prometheus. The modify stays for a subtler reason: `filter_labels` and `group_labels` live together in one JSONB column, so a partial update has to read the stored value and merge before it writes. That is a read-then-write, not a forward — the earlier survey had it marked pass-through. The metric repository resolves preset ids while evaluating utilization queries and has no action to run, so the db_source keeps a search for it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…share it `myKeypairResourcePolicyV2` hands any authenticated caller the same node type the admin queries return, and that node carries a `keypairs` connection searching every keypair assigned to the policy. Since most users sit on `default`, one query walked from one's own policy to every other user's access key, admin flag and email. The adapter's docstring already claimed a superadmin gate on that field; it was never there. It is now. The legacy schema had the same shape by a different route: `keypairResourcePolicy` and `userResourcePolicy` read one's own policy when called without a name, but a name — anyone's — was loaded with no role check at all, while the sibling list resolvers next to them branch on role carefully. Naming a policy now needs the roles that may already list them. Neither changes what a user sees about their own policy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sixteen actions said `Modify` while the operation they declare is `UPDATE`, and the three resource policies had already drifted the other way — class `Modify*`, audit name `admin_update_*`. One verb for one operation, so `Modify` follows the operation it performs, through the action classes, their files, the service methods and the processor attributes. The names clients see do not move: the legacy `modify_domain` / `modify_group` / `modify_user` mutations, `admin_modify_prometheus_query_preset`, and the REST routes all keep their spelling. Only the handler and adapter methods behind them follow the attribute they call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t a scope of its own The previous fix put `check_admin_only()` on the GraphQL field. That gate only covers the one resolver, and the adapter method behind it stayed reachable — so the hole would reopen the next time something else called it. The connection now runs the admin keypair search narrowed by a resource-policy filter. `AdminSearchKeypairsAction` moves onto the global base, whose processor prepends the SUPERADMIN check itself: the gate travels with the action, no caller can skip it, and no configuration can turn it off. That last part is what made the scope path unsafe rather than merely indirect. `SearchKeypairsByResourcePolicyAction` did carry an RBAC scope validator, but that validator returns early when RBAC enforcement is disabled — so with enforcement off, every authenticated caller passed. Its scope was also ungrantable: keypair resource policies appear in no scope-entity combination, so no role could have authorized it even with enforcement on. The action, its service method, its repository read and its operation scope all go. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two of the ninety-odd names this branch declared did not follow the rest. The service catalog search recorded `admin_search_service_catalogs`, but the qualifier marks a path that has a counterpart, and it has none — every other super-admin-only global catalog (login client types, retention policies, runtime variants, resource slot types) records its search unqualified. So does this one now. Error logs had it the other way round. `list_error_logs` was the read that narrows rows by the caller's role, while `search_error_logs` was the one that returns every row; but `list_` elsewhere in the codebase means "unpaged", not "scoped", and both of these page. They become `search_error_logs` and `admin_search_error_logs`, which is the pairing the app-config domains already use, and `list_` keeps its one meaning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…on to ops-generic wiring Registering a namespace and the two reads over the table forwarded specs unchanged; they move to ops. Reading one storage's namespaces was shaped as a get, but what comes back is a list and the storage id selects it rather than identifying a row — it is a search with a condition, and says so now. Two stay. Unregistering addresses its row by `(storage_id, namespace)` while the purge specs key on a single primary value, so it keeps its service; it does declare `PURGE` rather than `DELETE`, the table having no lifecycle column. The grouped read assembles a dict of namespaces per storage, which is an aggregation rather than a page of rows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…thout carrying them `ModelCardRow.to_data()` read `resource_requirement_rows`, so producing a `ModelCardData` meant loading a child table. That forced the relationship, forced an eager load on every read that returned a card, and put the whole domain out of reach of the single-entity read/write specs — not just the writes, but the get and the searches too. The requirements move out of the projection. The row carries its own columns, the relationship goes, the creator names the child rows separately for the write path to insert, and the update's re-read after syncing them is no longer needed because nothing implicit reads them any more. What renders them asks for them, and asks for every card at once. REST fills the field with one extra batched read before answering, so its response is unchanged. GraphQL resolves it as a field, so a query that does not select `minResource` never reads that table — which is most of them, since the only code that consumes the requirements is `available_presets`, and that has always joined the table directly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Now that the card's projection no longer reaches into its requirements table, the create is expressible as an entity spec. `ModelCardCreator` says in the spec what the call site used to say in an RBAC element reference: a card is its own scope, and it joins the model-store project it is registered in. Not yet wired — the actions still run the legacy creator. It lands here so the follow-up that moves create, search and the project-scoped search onto ops starts from a spec that already typechecks against the entity family. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The migration re-chains onto main's new head; the two import edits are what the conflict resolution left behind. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: octodog <mu001@lablup.com>
Moving these actions onto the global processor attached a SUPERADMIN gate that the legacy processor never had, so routes their own registry declares as auth_required started answering 403 to every regular user. The route registries are the authority: a domain can serve two surfaces from one processor -- storage answers an auth_required v1 surface and a superadmin_required v2 surface -- so the processor carries the weaker gate and the route middleware keeps the stronger one. Reads move to the public processor, which demands a caller but not a role; writes stay behind the gate. Three domains had no test covering a non-admin caller at all, since every existing case authenticates as an admin. A read/write access pair now pins the contract for the query-preset catalog. Also repair the global purge path: it rebuilt the deleted row by calling the row class, which fails on any row whose __init__ narrows to the caller-supplied columns -- a server-generated one arrives as an unexpected keyword. It now maps the RETURNING columns the way insert and update already did. error_log keeps the gate by decision: its recording and per-user reads become superadmin-only, and the tests state that. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An error log belongs to whoever hit it, but the sweep had filed the domain as global state, so recording one demanded super-admin. The row carries a user FK, an is_cleared flag the owner dismisses it with, and a read that hides cleared rows from that owner and shows them to an admin -- none of which a global entity has. Recording and reading now target the owning user's scope, so the permission question is whether the caller may act on that user's error logs rather than whether they administer the installation. Clearing targets the log itself: by then the row exists to name, and it is a soft delete, so it carries an updater whose values are a constant and an operation_type that records the run as a delete rather than an edit. The actions no longer carry is_admin, is_superadmin or user_domain. Who the caller is comes from the request context; an action that takes its own caller's role as an argument is taking a claim it cannot check. The REST handler branches between the scoped read and the whole-table read instead, which is where the two gates were always distinguishable. That leaves the domain with no service and no hand-written repository methods. The row also loses its constructor: created_at moves to the shared mixin, so no writer can set it. Ownership was enforced by a WHERE clause in the repository, which is domain judgment living below the layer that owns it. It now rests on the permission layer, whose rows for this entity type are seeded separately -- until then a non-super-admin caller is refused. The component suite cannot see either state because it replaces the virtual-scope validators with mocks; the ownership test that depended on the old WHERE clause is dropped rather than left asserting a guard that no longer runs there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Removal addressed a namespace by the (storage, namespace) pair it was registered under, which no purge spec can express -- they key on a single primary value -- so the operation kept a service and a hand-written repository method to work around the shape. Translating the pair into an id is a read, so it becomes one: a lookup resolves the pair, and the purge takes the id like every other purge. The unique constraint on the pair is what makes that resolution single-valued, and the lookup family already rejects a second match rather than answering with an arbitrary row. The grouped read goes the same way. Grouping namespaces by storage was a repository method returning a dict, which is the caller's shape rather than the table's; the deprecated bucket route now searches and groups what it got. That leaves the domain with no service. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The v2 spec families all live under models/ beside their row -- creators, purgers, upserters -- but the update specs had been landing under repositories/, which put one half of a domain's write declarations in a different layer from the other half. The twelve specs built on the v2 DataUpdater move to models/; the resource policy trio and the retention policy join the packages their rows already share, the way their creators do. Their imports carried nothing from repositories/, so nothing follows them across the layer boundary. The remaining twenty-three stay where they are: they subclass UpdaterSpec from repositories/base/, so moving them would make models/ import repositories/. They belong under models/ once they are migrated to the v2 family, not before, and both AGENTS.md files now say which of the two a file in each place is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Which family an action belongs to is a judgment that reads as arbitrary once it is made -- the code shows the base class, not why that base was the right one. Four packages had that judgment settled during review, so each now carries a table of its processor fields against the entity type, shape and operation they run as, and the reasoning behind the ones that surprise. The four are the ones actually examined: error log, storage namespace, role preset, notification. The rest keep no document rather than a guessed one. Two of the tables record a gap instead of a decision -- error logs authorize through permission rows that are not seeded yet, and a storage namespace names its storage through a column that cannot say which table it points at. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ribe A domain's KNOWLEDGE.md states the entity type, shape and operation each processor field runs as. Nothing makes that table wrong when a field moves to a different factory -- it just quietly stops being true, and the next reader trusts it because it reads like a fact rather than a snapshot. The guardrail names the trigger: a composition change updates the table in the same change, and a domain that gains one without a document writes it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The object and VFS storage packages answer two REST surfaces, and the v1 one declared auth_required while v2 declared superadmin_required. Reading that as two audiences, the earlier commit opened the catalog reads to any authenticated caller so the v1 surface would keep working. That was the wrong reading. Registering storages, listing them, and handing out presigned URLs against them are all operations only a super admin performs; the v1 declaration was the stale half, not the authoritative one. The reads go back behind the gate and the nine v1 routes now say the same thing the processors do. Six tests asserted a regular user could read the registry. They assert the refusal instead -- the behaviour they were written against was never decided, it was inherited from a processor that gated nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A preset is a PromQL template and executing one runs that query against the metrics backend, which reads across the cluster rather than anything the caller owns. The routes had declared the reads and the execute as auth_required while the processors gated execute on the super-admin role, so the surface promised something the action layer refused. The reads join the writes behind the gate and the five routes now say the same thing. The access test asserts the refusal it should have asserted from the start; nothing else in the suite would notice, since the filter and preview cases all authenticate as an admin. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three domains wire a lookup and two called the field resolve_by_name, which says how the key happens to be shaped rather than what the field is. Storage namespaces resolve on a pair, not a name, so the older name would not have survived being copied there anyway. Also fold the login client type admin processors back into the domain's own class. Which gate an operation carries is a property of that operation, and a second class states it twice -- once in its name and again in the factory that wires it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… reads Both operations carried an action, a service method, a processor field, a repository method and a db_source query, and nothing called any of them. The search that sits beside each already answers the same question -- these took an owner id where the search takes conditions -- so removing them costs no coverage. Their tests go with them. What they verified was that the service copies row fields into a data type, which is the conversion the repository should have done before returning; the search path in the same domain already does it that way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Record what each entity type constant stands for: the ownership tree, the per-entity fields and references, the public and global groupings, and the foreign-key rule for assignments. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The marker is backfilled only from the former main_access_key, so a user who deleted a keypair can carry none. Ordering by the newest let a new keypair move which one answers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two GetMy actions ran on the gateless legacy processor and never reached the registry, so the catalog uniqueness sweep could not see them. They now take the scopes as input and run behind the scope gate; the my-policy route is the adapter naming the caller's own scope. The join each repository method carried moves into an OperationScope, which leaves both domains with no service and the keypair domain with no repository. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An action is global or public; that only super-admins pass the global gate is a separate fact the gate decides. Seventeen action names introduced on this branch said admin, and none of them exist on main, so no audit history splits here. Processor fields wired from the global_* and public_* factories now carry the same prefix, which is what the new Naming section in services/AGENTS.md states. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
HyeockJinKim
force-pushed
the
BA-7297-passthrough-sweep
branch
from
August 16, 2026 03:15
9d1e292 to
3d819c1
Compare
Co-authored-by: octodog <mu001@lablup.com>
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
Moves the manager's pass-through actions onto ops-generic wiring: an operation whose
service only forwarded a spec to a repository that only forwarded it to db_source now
carries the spec on the action and runs against the generic ops services, and the two
forwarding layers go. Fifteen domains, ninety-six actions.
The sweep kept turning up things that had to be settled before the next domain could
move, so the branch carries those too:
owns, RESTORE wired through the v2 bases, and the many-row writes renamed for their
failure mode (
atomic_*raises,partial_bulk_*answers per item).sets a lifecycle flag is a delete; one that removes the row is a purge. Fourteen
actions were declaring the wrong one, including four that removed rows while claiming
a soft delete.
ModifybecomesUpdateeverywhere, matching the operation theaction already declared.
data/type that mirrors one row may not nest another entity.Notification rules name their channel by id; model cards no longer carry their
resource requirements. Both compose one layer up instead, which is what freed those
domains for the v2 specs at all.
any authenticated user could enumerate other users; and the legacy schema loaded any
named policy with no role check. Neither was introduced here.
pants check --changed-sincenever reached files this branch had not edited. Repo-widepants check ::finds them; that is the gate this branch used from then on.Test plan
pants check ::andpants lint ::pass repo-widetest_registry_catalog.py— every v2 action is wired, names are unique and snake_casenotification, resource policy, app config, prometheus preset, error log)
that
minResourceresolves on demand in GraphQLResolves BA-7297
📚 Documentation preview 📚: https://sorna--13708.org.readthedocs.build/en/13708/
📚 Documentation preview 📚: https://sorna-ko--13708.org.readthedocs.build/ko/13708/