Skip to content

[US-11.6 / OBT-231] Scope console lists to the caller's managed projects - #90

Open
levigtri wants to merge 5 commits into
levigft/obt-200-us-21-deactivate-a-language-instead-of-permanently-deletingfrom
levigft/obt-231-us-116-scope-the-organizations-list-for-non-admin-users
Open

[US-11.6 / OBT-231] Scope console lists to the caller's managed projects#90
levigtri wants to merge 5 commits into
levigft/obt-200-us-21-deactivate-a-language-instead-of-permanently-deletingfrom
levigft/obt-231-us-116-scope-the-organizations-list-for-non-admin-users

Conversation

@levigtri

@levigtri levigtri commented Jul 4, 2026

Copy link
Copy Markdown
Member

[US-11.6 / OBT-231] Scope console lists to the caller's managed projects

Stacked on #97 (US-2.1). Base is levigft/obt-200-us-21-..., not main. Merge #97 first; GitHub retargets this PR to main automatically once that happens. The dependency is real: this PR filters on Language.is_active, a column that #97 introduces — the filter cannot be expressed on main.

Summary

Non-admin users previously received the full dataset from the console list endpoints. This scopes GET /api/organizations, /api/languages, /api/phases and /api/phases/with-dependencies to the projects the caller manages (ProjectUserAccess.role == "manager"), aligning the scoping with the 2026-07 per-project manager model. Platform admins are unaffected and continue to see everything.

Changes

1. Add the managed-projects helper

app/core/org_scope.py — new get_managed_project_ids(db, user_id) returning the project IDs where the user's ProjectUserAccess.role == "manager". (Reused by US-11.8's console guard and project scoping.)

2. Add per-domain "by projects" services

app/services/org/list_organizations_by_projects.py, app/services/language/list_languages_by_projects.py, app/services/phase/list_phases_by_projects.py (new) — resolve, from a set of project IDs, the organizations linked via project_organization_access, the distinct languages (Project.language_id), and the phases attached via project_phases (plus list_phases_with_deps_by_projects). Re-exported from each domain __init__.py. Replaces the earlier organization-based services.

3. Scope the list endpoints by managed projects

app/api/organizations.py, app/api/languages.py, app/api/phases.py — each list route branches on user.is_platform_admin; otherwise resolves get_managed_project_ids and delegates to the matching "by projects" service.

4. Keep inactive languages out of the manager list (rule change 2026-07-16)

app/services/language/list_languages_by_projects.py filters on Language.is_active. Without it, the manager path bypassed the active-only filter that list_languages already applies, so a manager kept seeing a language after a platform admin deactivated it — the owner's rule is that a manager must never see an inactive language. app/api/languages.py correspondingly honours include_inactive only on the platform-admin branch, so the parameter is unreachable for a manager.

This is the change that forced the stack: Language.is_active arrives with #97, and list_languages_by_projects is owned by this PR, so neither branch could carry the fix alone.

Type of Change

  • Feature
  • Behavior change (non-admins scoped by managed projects; managers no longer receive inactive languages)

Testing

  • tests/test_console_scoping.py::test_list_languages_by_projects_excludes_inactive (new) — a manager managing a project whose language was deactivated receives only the active one. Verified to fail without the filter (assert ['lca', 'lci'] == ['lca']) and pass with it.
  • tests/test_console_scoping.py and the org/language/phase service tests pass.
  • Admin → full lists; manager → only data of projects they manage, active languages only.
  • ruff check + ruff format --check clean.

Summary by CodeRabbit

  • New Features
    • Languages can now be marked inactive and deactivated by platform administrators.
    • Language listings hide inactive languages by default, with an option to include them.
    • New languages record their creator.
    • Organizations, languages, and phases are scoped to projects managed by the signed-in user.
  • Bug Fixes
    • Projects can no longer be created or updated with inactive languages.
  • Tests
    • Added coverage for permissions, project scoping, inactive-language handling, and creator tracking.

Merge reconciliation with Journeys (#132/#135)

Once journeys land, list_phases_by_projects / list_phases_with_deps_by_projects must derive the manager's phases from the journeys of their managed projects (full phase set per journey, sort_order ordering; link-based fallback only for projects without a journey) instead of project_phases rows — otherwise managers only see phases that already had a status change. See the "Merge reconciliation" section on #135 and the stacked reconciliation PR #136 (merge it last); the fix is already validated on the local integration branch.

@levigtri levigtri self-assigned this Jul 4, 2026
Non-admin users receive only the data tied to the projects they manage (ProjectUserAccess.role
== "manager"), instead of the full dataset: organizations linked to those projects,
the languages of those projects, and the phases attached to them. Platform admins are
unaffected and continue to see everything.

Adds get_managed_project_ids and per-domain "by projects" services
(list_organizations_by_projects, list_languages_by_projects, list_phases_by_projects and
list_phases_with_deps_by_projects), replacing the earlier organization-based scoping to align
with the 2026-07 per-project manager model. Wires them into the organizations, languages and
phases list endpoints.
@levigtri
levigtri force-pushed the levigft/obt-231-us-116-scope-the-organizations-list-for-non-admin-users branch from 454360f to 0f9fc13 Compare July 4, 2026 20:34
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: QUIET

Plan: Pro Plus

Run ID: 2d81999d-05ca-4e22-87ff-e1b675026949

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Adds active-state and creator metadata to languages, an admin-only deactivation flow, project-scoped organization/language/phase listings, role-aware API routing, and validation preventing projects from using inactive languages.

Language lifecycle and persistence

Layer / File(s) Summary
Language lifecycle and persistence
alembic/versions/*language*, app/db/models/language.py, app/models/language.py, app/services/language/*, tests/test_language_service.py
Languages gain is_active and nullable created_by fields; creation persists the creator, listing filters inactive records by default, and platform admins can deactivate languages.
Project language validation
app/services/project/create_project.py, app/services/project/update_project.py, tests/test_project_service.py
Project creation and language changes reject missing or inactive languages.

Managed-project listing services

Layer / File(s) Summary
Managed-project listing services
app/core/org_scope.py, app/services/org/*, app/services/language/*, app/services/phase/*, tests/test_console_scoping.py
Manager project IDs are resolved from access roles, and organization, language, phase, and dependency queries support project-scoped results.

Role-aware API endpoints

Layer / File(s) Summary
Role-aware API endpoints
app/api/languages.py, app/api/organizations.py, app/api/phases.py
Platform admins receive broad listings, non-admins receive managed-project results, language creation records the authenticated user, and language deactivation returns HTTP 204 for authorized admins.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant API
  participant get_managed_project_ids
  participant ListingService
  participant AsyncSession
  User->>API: Request organization, language, or phase listing
  API->>get_managed_project_ids: Resolve managed projects for non-admin
  get_managed_project_ids->>AsyncSession: Query manager access
  AsyncSession-->>get_managed_project_ids: Managed project IDs
  API->>ListingService: Query scoped records
  ListingService->>AsyncSession: Execute project-scoped query
  AsyncSession-->>ListingService: Matching records
  ListingService-->>API: Return records
  API-->>User: Return serialized response
Loading

Suggested reviewers: joaocarvoli, henokteixeira, caliridaniel

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: scoping console lists to the caller's managed projects.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch levigft/obt-231-us-116-scope-the-organizations-list-for-non-admin-users

Comment @coderabbitai help to get the list of available commands.

@levigtri
levigtri requested review from joaocarvoli and removed request for joaocarvoli July 16, 2026 00:31
levigtri and others added 2 commits July 16, 2026 17:43
…-a-language-instead-of-permanently-deleting' into fix/obt-231-90

# Conflicts:
#	app/api/languages.py
list_languages_by_projects is the manager's list path and returned languages
regardless of is_active, so a manager kept seeing a language after a platform
admin deactivated it. Filter on is_active, and honour include_inactive only on
the admin branch of the list route.

This is why the branch now sits on top of US-2.1 (#97): is_active is introduced
there, so the filter cannot be expressed on main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@levigtri
levigtri changed the base branch from main to levigft/obt-200-us-21-deactivate-a-language-instead-of-permanently-deleting July 16, 2026 20:43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/api/languages.py`:
- Line 7: Move the platform-admin branching and managed-project lookup from the
language router into a language service operation that performs role-aware
language listing. Update the router to only parse validated request data, inject
dependencies, call that service operation, and map its result into the response;
remove its direct use of get_managed_project_ids and related business
orchestration.

In `@app/core/org_scope.py`:
- Around line 12-19: Move get_managed_project_ids from app/core/org_scope.py
into the appropriate app/services module, preserving its database query
behavior. Update app/api/organizations.py at lines 7 and 32-33 and
app/api/phases.py at lines 6, 40-44, and 54-55 to import and invoke the
service-layer helper; remove the core-layer definition and direct callers.

In `@app/services/language/deactivate_language.py`:
- Around line 9-17: Add concise docstrings to the public functions
deactivate_language, create_language, list_languages, and
list_languages_by_projects. In app/services/language/deactivate_language.py
lines 9-17, document platform-admin-only deactivation; in
app/services/language/create_language.py lines 8-14, document creation and
creator attribution; in app/services/language/list_languages.py lines 7-10,
document default inactive filtering and include_inactive; and in
app/services/language/list_languages_by_projects.py lines 8-18, document
managed-project and active-language filtering.

In `@app/services/org/list_organizations_by_projects.py`:
- Around line 8-10: Add concise docstrings to the public functions
list_organizations_by_projects in
app/services/org/list_organizations_by_projects.py (lines 8-10),
list_phases_by_projects in app/services/phase/list_phases_by_projects.py (lines
8-12) describing project and optional single-project filtering, and the
project-scoped phase/dependency function at lines 30-33 describing its results.

In `@app/services/project/create_project.py`:
- Around line 19-23: Protect language validation and the subsequent project
write in both create_project.py (lines 19-33) and update_project.py (lines
19-30) with the same transaction/row lock, or enforce the active-language
invariant atomically at write time. Ensure create_project cannot insert and
update_project cannot assign a language_id after another transaction deactivates
the language.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: QUIET

Plan: Pro Plus

Run ID: 3a04dc45-3d81-4171-bb55-4d99459d6cc7

📥 Commits

Reviewing files that changed from the base of the PR and between d669a56 and 083b8fc.

📒 Files selected for processing (23)
  • alembic/versions/20260704_0003_add_language_is_active.py
  • alembic/versions/20260704_0004_add_language_created_by.py
  • app/api/languages.py
  • app/api/organizations.py
  • app/api/phases.py
  • app/core/org_scope.py
  • app/db/models/language.py
  • app/models/language.py
  • app/services/language/__init__.py
  • app/services/language/create_language.py
  • app/services/language/deactivate_language.py
  • app/services/language/list_languages.py
  • app/services/language/list_languages_by_projects.py
  • app/services/org/__init__.py
  • app/services/org/list_organizations_by_projects.py
  • app/services/phase/__init__.py
  • app/services/phase/list_phases_by_projects.py
  • app/services/project/create_project.py
  • app/services/project/update_project.py
  • tests/baker.py
  • tests/test_console_scoping.py
  • tests/test_language_service.py
  • tests/test_project_service.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/api/languages.py`:
- Line 7: Move the platform-admin branching and managed-project lookup from the
language router into a language service operation that performs role-aware
language listing. Update the router to only parse validated request data, inject
dependencies, call that service operation, and map its result into the response;
remove its direct use of get_managed_project_ids and related business
orchestration.

In `@app/core/org_scope.py`:
- Around line 12-19: Move get_managed_project_ids from app/core/org_scope.py
into the appropriate app/services module, preserving its database query
behavior. Update app/api/organizations.py at lines 7 and 32-33 and
app/api/phases.py at lines 6, 40-44, and 54-55 to import and invoke the
service-layer helper; remove the core-layer definition and direct callers.

In `@app/services/language/deactivate_language.py`:
- Around line 9-17: Add concise docstrings to the public functions
deactivate_language, create_language, list_languages, and
list_languages_by_projects. In app/services/language/deactivate_language.py
lines 9-17, document platform-admin-only deactivation; in
app/services/language/create_language.py lines 8-14, document creation and
creator attribution; in app/services/language/list_languages.py lines 7-10,
document default inactive filtering and include_inactive; and in
app/services/language/list_languages_by_projects.py lines 8-18, document
managed-project and active-language filtering.

In `@app/services/org/list_organizations_by_projects.py`:
- Around line 8-10: Add concise docstrings to the public functions
list_organizations_by_projects in
app/services/org/list_organizations_by_projects.py (lines 8-10),
list_phases_by_projects in app/services/phase/list_phases_by_projects.py (lines
8-12) describing project and optional single-project filtering, and the
project-scoped phase/dependency function at lines 30-33 describing its results.

In `@app/services/project/create_project.py`:
- Around line 19-23: Protect language validation and the subsequent project
write in both create_project.py (lines 19-33) and update_project.py (lines
19-30) with the same transaction/row lock, or enforce the active-language
invariant atomically at write time. Ensure create_project cannot insert and
update_project cannot assign a language_id after another transaction deactivates
the language.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: QUIET

Plan: Pro Plus

Run ID: 3a04dc45-3d81-4171-bb55-4d99459d6cc7

📥 Commits

Reviewing files that changed from the base of the PR and between d669a56 and 083b8fc.

📒 Files selected for processing (23)
  • alembic/versions/20260704_0003_add_language_is_active.py
  • alembic/versions/20260704_0004_add_language_created_by.py
  • app/api/languages.py
  • app/api/organizations.py
  • app/api/phases.py
  • app/core/org_scope.py
  • app/db/models/language.py
  • app/models/language.py
  • app/services/language/__init__.py
  • app/services/language/create_language.py
  • app/services/language/deactivate_language.py
  • app/services/language/list_languages.py
  • app/services/language/list_languages_by_projects.py
  • app/services/org/__init__.py
  • app/services/org/list_organizations_by_projects.py
  • app/services/phase/__init__.py
  • app/services/phase/list_phases_by_projects.py
  • app/services/project/create_project.py
  • app/services/project/update_project.py
  • tests/baker.py
  • tests/test_console_scoping.py
  • tests/test_language_service.py
  • tests/test_project_service.py
🛑 Comments failed to post (5)
app/api/languages.py (1)

7-7: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Move listing-scope orchestration into a language service.

This router now branches on platform-admin status and calls the database-backed get_managed_project_ids; expose one service operation for role-aware language listing instead, leaving the router to dependency injection, request parsing, and response mapping.

As per coding guidelines, app/api must “parse and validate input, call service functions,” and must not contain “business rules, orchestration, model creation, or direct database access in routers.”

Also applies to: 21-25

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/languages.py` at line 7, Move the platform-admin branching and
managed-project lookup from the language router into a language service
operation that performs role-aware language listing. Update the router to only
parse validated request data, inject dependencies, call that service operation,
and map its result into the response; remove its direct use of
get_managed_project_ids and related business orchestration.

Source: Coding guidelines

app/core/org_scope.py (1)

12-19: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Keep project-scope lookup in the service layer.

The new database-backed authorization helper is defined in app/core and consumed directly by API routers. Move it into app/services and update each caller.

  • app/core/org_scope.py#L12-L19: relocate get_managed_project_ids.
  • app/api/organizations.py#L7-L7: import the helper from its service module.
  • app/api/organizations.py#L32-L33: call the service-layer helper.
  • app/api/phases.py#L6-L6: import the helper from its service module.
  • app/api/phases.py#L40-L44: call the service-layer helper for phase listing.
  • app/api/phases.py#L54-L55: call the service-layer helper for dependency listing.

As per coding guidelines, app/core is reserved for core infrastructure, app/api is an HTTP access layer, and all database access must live in app/services/.

📍 Affects 3 files
  • app/core/org_scope.py#L12-L19 (this comment)
  • app/api/organizations.py#L7-L7
  • app/api/organizations.py#L32-L33
  • app/api/phases.py#L6-L6
  • app/api/phases.py#L40-L44
  • app/api/phases.py#L54-L55
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/core/org_scope.py` around lines 12 - 19, Move get_managed_project_ids
from app/core/org_scope.py into the appropriate app/services module, preserving
its database query behavior. Update app/api/organizations.py at lines 7 and
32-33 and app/api/phases.py at lines 6, 40-44, and 54-55 to import and invoke
the service-layer helper; remove the core-layer definition and direct callers.

Source: Coding guidelines

app/services/language/deactivate_language.py (1)

9-17: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add concise docstrings to the public language services.

  • app/services/language/deactivate_language.py#L9-L17: document the platform-admin-only deactivation behavior.
  • app/services/language/create_language.py#L8-L14: document creation and creator attribution semantics.
  • app/services/language/list_languages.py#L7-L10: document default inactive filtering and include_inactive.
  • app/services/language/list_languages_by_projects.py#L8-L18: document managed-project and active-language filtering.

As per coding guidelines, app/services/**/*.py requires “concise docstrings on public service functions.”

📍 Affects 4 files
  • app/services/language/deactivate_language.py#L9-L17 (this comment)
  • app/services/language/create_language.py#L8-L14
  • app/services/language/list_languages.py#L7-L10
  • app/services/language/list_languages_by_projects.py#L8-L18
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/services/language/deactivate_language.py` around lines 9 - 17, Add
concise docstrings to the public functions deactivate_language, create_language,
list_languages, and list_languages_by_projects. In
app/services/language/deactivate_language.py lines 9-17, document
platform-admin-only deactivation; in app/services/language/create_language.py
lines 8-14, document creation and creator attribution; in
app/services/language/list_languages.py lines 7-10, document default inactive
filtering and include_inactive; and in
app/services/language/list_languages_by_projects.py lines 8-18, document
managed-project and active-language filtering.

Source: Coding guidelines

app/services/org/list_organizations_by_projects.py (1)

8-10: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add concise docstrings to the new public service functions.

  • app/services/org/list_organizations_by_projects.py#L8-L10: document the project-scoped organization query.
  • app/services/phase/list_phases_by_projects.py#L8-L12: document project and optional single-project filtering.
  • app/services/phase/list_phases_by_projects.py#L30-L33: document project-scoped phase and dependency results.

As per coding guidelines, public functions in app/services/**/*.py require concise docstrings.

📍 Affects 2 files
  • app/services/org/list_organizations_by_projects.py#L8-L10 (this comment)
  • app/services/phase/list_phases_by_projects.py#L8-L12
  • app/services/phase/list_phases_by_projects.py#L30-L33
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/services/org/list_organizations_by_projects.py` around lines 8 - 10, Add
concise docstrings to the public functions list_organizations_by_projects in
app/services/org/list_organizations_by_projects.py (lines 8-10),
list_phases_by_projects in app/services/phase/list_phases_by_projects.py (lines
8-12) describing project and optional single-project filtering, and the
project-scoped phase/dependency function at lines 30-33 describing its results.

Source: Coding guidelines

app/services/project/create_project.py (1)

19-23: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Files ==\n'
git ls-files 'app/services/project/*' 'app/services/language/*' 'app/models/*' 'app/core/*' | sort

printf '\n== Outlines ==\n'
for f in app/services/project/create_project.py app/services/project/update_project.py app/services/language/*.py app/models/*.py app/core/exceptions.py; do
  if [ -f "$f" ]; then
    echo "--- $f"
    ast-grep outline "$f" --view expanded || true
  fi
done

printf '\n== Relevant content ==\n'
for f in app/services/project/create_project.py app/services/project/update_project.py app/services/language/*.py app/models/*.py app/core/exceptions.py; do
  if [ -f "$f" ]; then
    echo "--- $f"
    nl -ba "$f" | sed -n '1,240p'
  fi
done

Repository: shemaobt/tripod-api

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in \
  app/services/project/create_project.py \
  app/services/project/update_project.py \
  app/services/language/get_language_by_id.py \
  app/services/language/deactivate_language.py \
  app/core/database.py \
  app/models/language.py \
  app/models/project.py
do
  echo "--- $f"
  nl -ba "$f" | sed -n '1,220p'
  echo
done

printf '\n== Search for row locking / isolation / select_for_update ==\n'
rg -n "select_for_update|FOR UPDATE|with_for_update|isolation_level|begin\(|commit\(|flush\(" app/services app/core app/models -g '!**/__pycache__/**'

Repository: shemaobt/tripod-api

Length of output: 241


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

paths = [
    "app/services/project/create_project.py",
    "app/services/project/update_project.py",
    "app/services/language/get_language_by_id.py",
    "app/services/language/deactivate_language.py",
    "app/core/database.py",
    "app/models/language.py",
    "app/models/project.py",
]
for p in paths:
    path = Path(p)
    print(f"--- {p}")
    if not path.exists():
        print("MISSING")
        print()
        continue
    for i, line in enumerate(path.read_text().splitlines(), 1):
        if i > 220:
            print("... (truncated)")
            break
        print(f"{i:4d}: {line}")
    print()
PY

printf '\n== Search for row locking / transaction boundaries ==\n'
rg -n "select_for_update|with_for_update|FOR UPDATE|begin\(|commit\(|flush\(" app/services app/core app/models -g '!**/__pycache__/**'

Repository: shemaobt/tripod-api

Length of output: 19707


Lock language validation to the project write

create_project and update_project read Language.is_active without any row lock, then write the project later. Another transaction can deactivate the language in between, letting an inactive language_id slip through.

  • app/services/project/create_project.py#L19-L33: validate and insert under the same lock/transaction, or enforce the active-language invariant at write time.
  • app/services/project/update_project.py#L19-L30: apply the same protection before assigning project.language_id.
📍 Affects 2 files
  • app/services/project/create_project.py#L19-L23 (this comment)
  • app/services/project/update_project.py#L19-L24
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/services/project/create_project.py` around lines 19 - 23, Protect
language validation and the subsequent project write in both create_project.py
(lines 19-33) and update_project.py (lines 19-30) with the same transaction/row
lock, or enforce the active-language invariant atomically at write time. Ensure
create_project cannot insert and update_project cannot assign a language_id
after another transaction deactivates the language.

@joaocarvoli joaocarvoli left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

some comments

deps_result = await db.execute(
select(PhaseDependency).where(
PhaseDependency.phase_id.in_(phase_ids),
PhaseDependency.depends_on_id.in_(phase_ids),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just make sure here if dropping the edges that point outside the managed projects is really what we want. If a manager has "Review" attached to his project but "Draft" is not, the admin gets Review: [Draft] and the manager gets Review: [], so on the Console the phase appears with no prerequisite at all. The PR is for scope the visibility, not for change the dependencies graph — could you verify that against the owner's rule and if it is wrong drop the depends_on_id filter?

db_session, [managed.id], project_id=other.id
)

assert result == []

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

list_phases_with_deps_by_projects is the only one of the four scoped endpoints with no test here, and it is the one with more logic (the deps map + the filter of the other comment). Could you add one? I think it would already answer the question there.

return list(result.scalars().unique().all())


async def list_phases_with_deps_by_projects(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Two public functions on the same file here — the rest of app/services/phase/ is one function per file named after it, and list_all_phases_with_deps.py is exactly the sibling of this one. If the one function per file is really the majority on the codebase, please move list_phases_with_deps_by_projects to its own file.

# Conflicts:
#	app/api/languages.py
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.

2 participants