Skip to content

refactor(user-management): move byte-identical team_service to autobot_shared (#12647) - #13164

Merged
mrveiss merged 13 commits into
Dev_new_guifrom
issue-12647-team
Aug 1, 2026
Merged

mrveiss merged 13 commits into
Dev_new_guifrom
issue-12647-team

Conversation

@mrveiss

@mrveiss mrveiss commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Closes #12647

Partial slice — the issue must stay open after merge. The close keyword
above is required by the Validate PR issue link gate; merging to
Dev_new_gui does not auto-close, and #12647 still has rbac_middleware.py,
user_service.py and organization_service.py outstanding. Same handling as
the earlier slices #13126 / #13130 / #13163.

Thinking Path

services/team_service.py was byte-identical across autobot-backend and
autobot-slm-backend:

$ diff autobot-backend/user_management/services/team_service.py \
       autobot-slm-backend/user_management/services/team_service.py
$ echo $?
0

701 lines duplicated verbatim, which is the exact failure mode #12645 exists to
end: a fix lands in one service and silently never reaches the other.

It is the first user_management service that can actually move, and that
is worth being precise about, because its byte-identical twin cannot. The whole
question is what a service needs at runtime:

dependency where it lives since
Team, TeamMembership autobot_shared #13130
AuditAction, AuditLog, AuditResourceType autobot_shared #13130
BaseService, TenantContext autobot_shared #12972

Every runtime dependency is already shared. User and Organization appear in
this file only in docstrings and error strings, never as classes — verified
by grep — so it does not need the concrete per-backend models.

organization_service.py is also byte-identical (545 lines) and is
deliberately not in this PR: it does from user_management.models import Organization, Team, User at runtime, and those concrete classes stay
backend-local by design (#13163). Moving it would force autobot_shared to
import a backend-local package — an inverted dependency. It needs its own
resolution, not a copy of this one.

What Changed

  • New autobot_shared/user_management/team_service.py — the implementation,
    with its three imports repointed at the shared modules.
  • Both backends keep a re-export shim exporting the full public surface
    (TeamService, TeamServiceError, TeamNotFoundError, DuplicateTeamError,
    MembershipError), so every importer — including each
    user_management/services/__init__.py — is untouched.
  • New autobot_shared/user_management/team_service_test.py (8 tests).

Placement: flat, as autobot_shared/user_management/team_service.py,
matching the existing base_service.py rather than inventing a second
services/ location for the same category of file. The package's lazy
_LAZY_IMPORTS already spells base_service as .base_service.

Verification

Identity, not just equivalence. The tests assert both shims resolve to the
same objects — two same-named classes are not interchangeable, the trap
#12913 fixed for CircuitState.

$ PYTHONPATH=$PWD python3 -m pytest autobot_shared/user_management -q
40 passed

(8 of these are the new team_service_test.py; see the note on the 5 failures
below.)

Both backends import through the shim and configure cleanly:

$ PYTHONPATH=$PWD/autobot-backend:$PWD python3 -c "..."
BACKEND: services import + configure_mappers OK, identity OK
TeamService.VALID_ROLES: ['admin', 'member', 'owner']

$ PYTHONPATH=$PWD/autobot-slm-backend:$PWD python3 -c "..."
SLM: services import + configure_mappers OK, identity OK

Consumer suites:

autobot-slm-backend/user_management     26 passed
autobot-backend/user_management         34 passed, 3 failed (pre-existing)

The 3 backend failures are rbac_denial_audit_test.py::TestAuditVocabulary,
red on Dev_new_gui itself since #13130 relocated models/audit.py. They are
unrelated to this change and are fixed in PR #13163. For the same reason,
models/base_test.py's 5 failures in the shared run are pre-existing (_ROOT
off-by-one from #13126) and also fixed in #13163. Merge order between the two
PRs does not matter; neither touches the other's files.

isort, black --line-length=120 and flake8 --config=.flake8 clean on all
four touched files.

Note

Importing the SLM package during verification auto-generated a .slm_keys
file into the worktree (it is gitignored, so it was never committable). It was
removed, and the remaining runs used throwaway SLM_SECRET_KEY /
SLM_ENCRYPTION_KEY values per #13090's guidance. Both worktrees confirmed
clean of key material.

Model Used

Claude Opus 5 (1M context)

mrveiss added 2 commits July 31, 2026 23:55
…t_shared (#12647)

team_service.py was byte-identical across autobot-backend and
autobot-slm-backend — 701 duplicated lines, so a fix landed in one service
or the other but never both by accident.

It is the first user_management *service* that could move: its entire
runtime dependency set already lives in autobot_shared — Team/TeamMembership
and the audit vocabulary (#13130), BaseService/TenantContext (#12972). Its
only mentions of User and Organization are in docstrings and error strings,
never as classes, so it does not need the concrete per-backend models.

Placed flat as autobot_shared/user_management/team_service.py, matching the
existing base_service.py rather than inventing a second services/ location.

Both backends keep a re-export shim so all existing importers, including
each services/__init__.py, are untouched.

organization_service.py is also byte-identical but deliberately not moved:
it imports the concrete User and Organization at runtime, and those stay
backend-local by design.
@github-actions

Copy link
Copy Markdown
Contributor

✅ SSOT Configuration Compliance: Passing

🎉 No hardcoded values detected that have SSOT config equivalents!

github-actions Bot and others added 11 commits July 31, 2026 21:27
…t mypy (#12647)

team_service.py moved into autobot_shared/, which CI type-checks strictly; the
backend copy never was. Two latent errors surfaced:

- _apply_team_name_update declared name: str but update_team passes
  str | None. The body already guards with 'if name and ...', so this was a
  wrong annotation rather than a live crash — widened to str | None.
- changes needed an explicit annotation; typed as dict[str, dict[str, Any]]
  to match the {'old': ..., 'new': ...} audit payload it carries.
@mrveiss
mrveiss merged commit 3493e6b into Dev_new_gui Aug 1, 2026
43 of 44 checks passed
@mrveiss
mrveiss deleted the issue-12647-team branch August 1, 2026 03:49
mrveiss added a commit that referenced this pull request Aug 1, 2026
… models (#12647) (#13201)

* refactor(user-management): converge organization_service via injected models (#12647)

organization_service.py was byte-identical across both backends (545 lines)
but could not be relocated the way team_service.py was (#13164): it queries
the concrete Organization and User models, and those stay backend-local
under the abstract-core decision (#13163) because backend carries LLC/PM-sync
columns and activity relationships SLM must not gain. Importing them from
autobot_shared would invert the dependency this umbrella exists to remove.

Per the owner's decision the canonical service takes them by injection:

- autobot_shared/user_management/organization_service.py names neither class.
  Queries go through self.organization_model / self.user_model; annotations
  use the shared OrganizationCore. Team needs no injection — it is already
  one shared class (#13130).
- Each backend subclasses it and binds its own pair, and re-exports the four
  error classes so every existing importer is untouched.
- Construction raises TypeError if a subclass forgot to bind, so the failure
  names its cause instead of surfacing as an AttributeError inside a query.

No behaviour change: the query construction is identical, only the way the
model class is reached differs. The public API is unchanged — all 11 methods
plus the inherited apply_tenant_filter, verified against the pre-refactor
surface.

* docs(session): handoff for issue-12647 organization_service slice (#12647)

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant