Skip to content

WS-ARCH-001-02A: expose TASK submission context API - #314

Merged
abiorh-claw merged 3 commits into
mainfrom
codex/ws-arch-001-02a-task-api
Aug 11, 2026
Merged

abiorh-claw merged 3 commits into
mainfrom
codex/ws-arch-001-02a-task-api

Conversation

@Abiorh001

@Abiorh001 Abiorh001 commented Aug 10, 2026 •

Copy link
Copy Markdown
Collaborator

Chunk

WS-ARCH-001-02A — TASK Submission Context Public API

Goal

Expose immutable, dependency-safe TASK facts and a transaction-bound owner-local capability for task, assignment, predecessor, and Submission lifecycle context without changing live submission behavior.

Human-approved intent

Continue the modular-monolith correction incrementally. TASKS owns Submission creation and lifecycle truth; cross-module consumers must use typed public contracts rather than TASK ORM models, repositories, or sessions.

What changed

  • Added the public app.modules.tasks.api submission-context contracts.
  • Added TaskRepository.lock_submission_context with TASK-first row locking and exact assignment/predecessor validation.
  • Reused the existing latest-submission query with optional lock/reload semantics.
  • Added boundary, immutable-contract, lifecycle matrix, and PostgreSQL lock-race tests.
  • Added the exact capability manifest and architecture-lockdown documentation.

Why it changed

ART preparation cannot safely stop importing private TASK persistence until TASKS exposes its own authoritative transaction-bound facts.

Design chosen

Frozen, slotted UUID/value facts and a Protocol define the public surface. The implementation remains owner-local in TaskRepository and returns only TASK-owned facts plus locked PROJECT/CHECKER reference selectors. No ORM row, session, mutable policy body, or concrete service crosses the boundary.

Alternatives rejected

  • Exporting TASK ORM rows or repositories: leaks persistence ownership.
  • Moving Submission lifecycle into ART: violates the approved module boundary.
  • Rewriting live submission behavior in this chunk: exceeds 02A scope and sequence.

Scope control

No AUTH/ART activation, route/schema change, Submission persistence change, PROJECT/CHECKER canonical fact claim, dependency addition, or CI workflow change.

Product behavior

Unchanged. This installs a hidden structural capability only.

Acceptance criteria proof

  • Exact initial context requires in_progress with no predecessor.
  • Exact revision context requires needs_revision with the latest same-contributor predecessor.
  • Missing, stale, crossed-state, and cross-contributor contexts fail closed with a bounded failure set.
  • Lock order is task, assignment, latest Submission.
  • Public API imports no private module or mutable persistence surface.

Tests/checks run

  • Ruff: pass.
  • Focused TASK API tests: 6 passed, 100% coverage.
  • PostgreSQL state-matrix and race tests: collect successfully; hosted Backend/Agent Gates execute them because no local WORKSTREAM_TEST_DATABASE_URL is configured.
  • Protected-base module-boundary validation: pass.
  • Markdown links, stale wording, and git diff integrity: pass.

Test delta

Additive tests only. No skips, xfails, removals, weakened assertions, or lowered thresholds.

CI integrity

No CI files or package scripts changed. The contract preserves the 90% subsystem threshold and now fails fast unless the required PostgreSQL test URL is supplied.

Reviewer results

Architecture PASS; security PASS; product/ops PASS after contributor/state corrections; QA PASS WITH LOW RISKS; senior engineering PASS; CI integrity PASS; docs PASS; reuse PASS; test-delta PASS WITH LOW RISKS.

External review

GitHub hosted checks and CodeRabbit are pending on this PR.

Remaining risks

The two new PostgreSQL tests were not executed locally because this worktree has no test database URL. Hosted CI is the required proof before merge.

Follow-up work

On human merge, WS-ARCH-001-02B becomes eligible. It adds the PROJECT locked-policy capability; this PR does not start it.

Human review focus

Review the public fact boundary, the task/assignment/predecessor lock order, crossed-state failures, and the PostgreSQL race proof.

Human merge ownership

A human owns the merge decision. This agent will not merge the PR without explicit approval for this PR.

Summary by CodeRabbit

  • New Features

    • Added task submission-context contracts for validating task, assignment, predecessor, and project-policy information.
    • Added transactional context locking to ensure consistent submission state and prevent conflicting updates.
    • Added clear, stable errors for unavailable or invalid submission contexts.
  • Documentation

    • Documented the tasks module’s public boundary and ownership rules.
  • Tests

    • Added coverage for validation, concurrency, predecessor consistency, and module-boundary enforcement.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Abiorh001, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 19 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b399f8a6-e4b5-4e45-b151-0e7271f9c577

📥 Commits

Reviewing files that changed from the base of the PR and between 370c4aa and e643bf8.

📒 Files selected for processing (9)
  • .agent-loop/initiatives/WS-ARCH-001-modular-monolith-boundaries/chunks/WS-ARCH-001-02A-task-submission-context-api.md
  • .agent-loop/initiatives/WS-ARCH-001-modular-monolith-boundaries/evidence/WS-ARCH-001-02A-task-manifest.md
  • .agent-loop/initiatives/WS-ARCH-001-modular-monolith-boundaries/reviews/WS-ARCH-001-02A-external-review-response.md
  • .ci/behavior-ownership/partition.v1.json
  • backend/app/modules/tasks/api/submission_context.py
  • backend/app/modules/tasks/repository.py
  • backend/scripts/behavior_ownership.py
  • backend/tests/architecture/test_module_boundaries.py
  • backend/tests/test_tasks.py
📝 Walkthrough

Walkthrough

The PR adds a public TASK submission-context API, repository-owned locking and validation for initial and revision submissions, architecture-boundary checks, PostgreSQL state and concurrency tests, and updated implementation documentation.

Changes

TASK submission-context API

Layer / File(s) Summary
Public submission-context contracts
backend/app/modules/tasks/api/*, docs/architecture_lockdown.md, backend/tests/architecture/test_module_boundaries.py
The TASK API exposes immutable request, reference, predecessor, and facts dataclasses, constrained lifecycle types, stable failure types, and a transaction-bound locking protocol. Architecture tests and documentation define the public boundary.
Repository locking and lineage validation
backend/app/modules/tasks/repository.py
TaskRepository.lock_submission_context locks task-related records, validates ownership, lifecycle, assignment, predecessor, contributor, and project-policy references, and returns typed facts or TaskSubmissionContextUnavailable. Latest-submission lookup now supports optional row locking and ORM refresh.
State, concurrency, and verification coverage
backend/tests/test_tasks.py, .agent-loop/initiatives/.../WS-ARCH-001-02A-task-manifest.md, .agent-loop/initiatives/.../WS-ARCH-001-02A-task-submission-context-api.md
Tests cover immutable contracts, initial and revision contexts, stale and cross-contributor predecessors, PostgreSQL state transitions, concurrent locking, and the required database-backed test command. The manifest records the boundary and verification evidence.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant TaskRepository
  participant PostgreSQL
  Caller->>TaskRepository: lock_submission_context(request)
  TaskRepository->>PostgreSQL: Lock task, assignment, and latest submission
  PostgreSQL-->>TaskRepository: Return locked records
  TaskRepository->>TaskRepository: Validate lifecycle, ownership, and predecessor lineage
  TaskRepository-->>Caller: Return context facts or unavailable error
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: abiorh-claw

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.29% 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
Title check ✅ Passed The title clearly and concisely identifies the main change: exposing the TASK submission context API.
Description check ✅ Passed The description covers the goal, design, scope, tests, risks, follow-up work, and review ownership, with sufficient evidence for this PR.
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.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/ws-arch-001-02a-task-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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.

🧹 Nitpick comments (4)
backend/app/modules/tasks/api/submission_context.py (1)

20-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive the accepted failure codes from the TaskSubmissionContextFailure alias.

Lines 21-24 duplicate the literal members declared at lines 11-14. If a future code is added to the alias only, the constructor rejects it at runtime while the type checker accepts it. Derive the set from the alias to keep one source of truth.

♻️ Proposed refactor
-from typing import Literal, Protocol
+from typing import Literal, Protocol, get_args
@@
 class TaskSubmissionContextUnavailable(RuntimeError):
     """Report one stable failure without exposing TASK persistence."""
 
     def __init__(self, code: TaskSubmissionContextFailure) -> None:
-        if code not in {
-            "task_submission_context_invalid",
-            "task_submission_predecessor_changed",
-        }:
+        if code not in get_args(TaskSubmissionContextFailure):
             raise ValueError("task submission context failure code is invalid")
         super().__init__(code)
         self.code = code
🤖 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 `@backend/app/modules/tasks/api/submission_context.py` around lines 20 - 27,
Update the validation in TaskSubmissionContextFailure.__init__ to derive the
accepted failure-code set from the TaskSubmissionContextFailure alias instead of
duplicating its string literals. Preserve the existing ValueError for
unsupported codes and assignment of the validated code.
backend/tests/architecture/test_module_boundaries.py (1)

196-208: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that the import scan found imports.

If rglob matches no file, or exact_source_imports returns an empty set, imports stays empty and the all(...) assertion passes without proving anything. Add a non-empty check so the boundary proof cannot pass vacuously.

♻️ Proposed refactor
-    imports = set()
+    api_files = list((ROOT / "backend/app/modules/tasks/api").rglob("*.py"))
+    assert api_files
+    imports: set[str] = set()
-    for path in (ROOT / "backend/app/modules/tasks/api").rglob("*.py"):
+    for path in api_files:
         imports.update(boundary.exact_source_imports(path, ROOT))
+    assert imports
     assert all(
🤖 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 `@backend/tests/architecture/test_module_boundaries.py` around lines 196 - 208,
Update test_tasks_public_api_has_no_private_or_mutable_dependency to assert that
imports is non-empty after scanning the tasks API files, before the existing
all(...) boundary assertion, so the architecture proof cannot pass vacuously.
backend/app/modules/tasks/repository.py (1)

118-122: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Docstrings for the new and changed TaskRepository methods do not follow the file convention. Every other method in TaskRepository documents its parameters and return value with Google-style Args and Returns sections. The two methods touched here omit the new parameters and the raised failure type.

  • backend/app/modules/tasks/repository.py#L118-L122: add Args, Returns, and Raises sections to lock_submission_context, and name TaskSubmissionContextUnavailable with its two failure paths.
  • backend/app/modules/tasks/repository.py#L254-L268: add for_update and populate_existing to the Args section of get_latest_submission_for_task.
🤖 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 `@backend/app/modules/tasks/repository.py` around lines 118 - 122, Update
TaskRepository.lock_submission_context in
backend/app/modules/tasks/repository.py:118-122 with Google-style Args, Returns,
and Raises sections, documenting both TaskSubmissionContextRequest and
TaskSubmissionContextFacts and naming TaskSubmissionContextUnavailable for its
two failure paths. Update get_latest_submission_for_task in
backend/app/modules/tasks/repository.py:254-268 by documenting the for_update
and populate_existing parameters in its Args section.
backend/tests/test_tasks.py (1)

1747-1766: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Cancel contender_call in the cleanup block.

If _wait_for_task_database_lock raises the AssertionError at line 1566, or if line 1760 fails, contender_call is still pending. The finally block then calls await contender.close() while that task holds the session and waits on a database lock. The close can hang or raise, and asyncio reports a destroyed pending task. Cancel the task before closing the sessions.

♻️ Proposed refactor
     holder = db_session.get_session_factory()()
     contender = db_session.get_session_factory()()
+    contender_call: asyncio.Task[Any] | None = None
     try:
         held_facts = await TaskRepository(holder).lock_submission_context(request)
@@
         contender_facts = await contender_call
         assert contender_facts == held_facts
+        contender_call = None
     finally:
+        if contender_call is not None:
+            contender_call.cancel()
+            with suppress(asyncio.CancelledError):
+                await contender_call
         await holder.close()
         await contender.close()

This uses contextlib.suppress, so add from contextlib import suppress to the imports.

🤖 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 `@backend/tests/test_tasks.py` around lines 1747 - 1766, Cancel the pending
contender_call in the finally cleanup before closing holder and contender
sessions, and suppress the resulting cancellation exception using
contextlib.suppress. Add the suppress import if needed, ensuring cleanup cannot
hang when _wait_for_task_database_lock or an assertion fails.
🤖 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.

Nitpick comments:
In `@backend/app/modules/tasks/api/submission_context.py`:
- Around line 20-27: Update the validation in
TaskSubmissionContextFailure.__init__ to derive the accepted failure-code set
from the TaskSubmissionContextFailure alias instead of duplicating its string
literals. Preserve the existing ValueError for unsupported codes and assignment
of the validated code.

In `@backend/app/modules/tasks/repository.py`:
- Around line 118-122: Update TaskRepository.lock_submission_context in
backend/app/modules/tasks/repository.py:118-122 with Google-style Args, Returns,
and Raises sections, documenting both TaskSubmissionContextRequest and
TaskSubmissionContextFacts and naming TaskSubmissionContextUnavailable for its
two failure paths. Update get_latest_submission_for_task in
backend/app/modules/tasks/repository.py:254-268 by documenting the for_update
and populate_existing parameters in its Args section.

In `@backend/tests/architecture/test_module_boundaries.py`:
- Around line 196-208: Update
test_tasks_public_api_has_no_private_or_mutable_dependency to assert that
imports is non-empty after scanning the tasks API files, before the existing
all(...) boundary assertion, so the architecture proof cannot pass vacuously.

In `@backend/tests/test_tasks.py`:
- Around line 1747-1766: Cancel the pending contender_call in the finally
cleanup before closing holder and contender sessions, and suppress the resulting
cancellation exception using contextlib.suppress. Add the suppress import if
needed, ensuring cleanup cannot hang when _wait_for_task_database_lock or an
assertion fails.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c2a4695-7e62-490e-8da1-c952ca2f20dd

📥 Commits

Reviewing files that changed from the base of the PR and between 0d0e6a2 and 370c4aa.

📒 Files selected for processing (8)
  • .agent-loop/initiatives/WS-ARCH-001-modular-monolith-boundaries/chunks/WS-ARCH-001-02A-task-submission-context-api.md
  • .agent-loop/initiatives/WS-ARCH-001-modular-monolith-boundaries/evidence/WS-ARCH-001-02A-task-manifest.md
  • backend/app/modules/tasks/api/__init__.py
  • backend/app/modules/tasks/api/submission_context.py
  • backend/app/modules/tasks/repository.py
  • backend/tests/architecture/test_module_boundaries.py
  • backend/tests/test_tasks.py
  • docs/architecture_lockdown.md

@abiorh-claw
abiorh-claw self-requested a review August 10, 2026 23:52
@abiorh-claw
abiorh-claw merged commit 3e9785e into main Aug 11, 2026
9 checks passed
@abiorh-claw
abiorh-claw deleted the codex/ws-arch-001-02a-task-api branch August 11, 2026 00:18
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