Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Chunk Contract: WS-ARCH-001-02B — PROJECT Locked Policy Public API

Implementation state: in review; on merge, this chunk is complete and
WS-ARCH-001-02C becomes the next eligible implementation boundary.

## Parent initiative

WS-ARCH-001 — Modular Monolith Boundaries
Expand All @@ -8,14 +11,20 @@ WS-ARCH-001 — Modular Monolith Boundaries

Expose dependency-free immutable PROJECT facts and ports for the locked guide,
source snapshot, effective submission-artifact policy, and pre-submit policy
lineage referenced by one TASK context.
lineage referenced by one TASK context. This chunk installs the PROJECT-owned
capability; live caller composition and cutover remain in the later chunk that
owns those caller paths.

## Why this chunk exists

TASK's private pre-submit loader currently reads PROJECT ORM rows. Preparation
must obtain server-owned locked-policy facts from PROJECTS without transferring
PROJECT persistence or policy ownership to TASKS or ART.

Locked-context resolution is distinct from current-context selection. Exact
immutable rows that were validly locked by a task remain usable after they
become `superseded`; the port must never substitute their current successors.

## Approved plan reference

- INTENT: `.agent-loop/initiatives/WS-ARCH-001-modular-monolith-boundaries/INTENT.md`
Expand All @@ -38,42 +47,56 @@ WS-ARCH-001-02A is merged and its exact TASK fact manifest is recorded.

```text
backend/app/modules/projects/api/**
backend/app/modules/projects/locked_policy_repository.py
backend/app/modules/projects/repository.py
backend/app/modules/projects/service.py
backend/app/modules/tasks/pre_submit_context.py
backend/tests/architecture/test_module_boundaries.py
backend/tests/test_ci_test_lanes.py
backend/tests/test_projects.py
backend/tests/test_tasks.py
backend/tests/projects/test_locked_policy_context.py
backend/scripts/behavior_ownership.py
backend/scripts/run_test_lanes.py
.ci/module-boundaries/private-edge-debt.v1.json
.ci/behavior-ownership/**
.agent-loop/initiatives/WS-ARCH-001-modular-monolith-boundaries/chunks/WS-ARCH-001-02B-project-locked-policy-api.md
.agent-loop/initiatives/WS-ARCH-001-modular-monolith-boundaries/evidence/WS-ARCH-001-02B-project-manifest.md
.agent-loop/initiatives/WS-ARCH-001-modular-monolith-boundaries/reviews/WS-ARCH-001-02B-external-review-response.md
docs/architecture_lockdown.md
```

## Not allowed

Policy recompilation or approval changes; AUTH/ART activation; CHECKER plan
ownership; ORM/session/repository or mutable policy bodies in `projects.api`;
new private edges.
new private edges; live TASK/ART/CHECKER caller cutover or composition wiring.

## Acceptance criteria

- [ ] PROJECTS validates exact project/guide/snapshot/policy lineage under its
- [x] PROJECTS validates exact project/guide/snapshot/policy lineage under its
own locks and returns immutable canonical facts/digests.
- [ ] Cross-project, replaced, draft, and hash-mismatched context fails closed.
- [ ] TASK consumes `projects.api` only; no PROJECT model/repository import
remains in the touched pre-submit path.
- [ ] Public API dependency/leak/cycle and protected-ledger checks pass.
- [ ] Record the exact PROJECT fact/port manifest in
- [x] Cross-project, successor-substituted, draft/pending/incomplete,
non-canonical, missing, and hash-mismatched context fails closed.
- [x] Exact task-locked guide, effective-policy, and compiled pre-submit-policy
rows remain valid after later `superseded` transitions; active/current
selection semantics are not used by this port.
- [x] The public port exposes canonical JSON policy bodies only as deeply
immutable values. PROJECT owns their validation; later CHECKER consumers
may interpret them, while TASK and ART may only pass them through.
- [x] No live consumer is cut over in this chunk. The exact PROJECT capability
is composed into callers only in a later contract that permits the
application/composition paths; no session or concrete PROJECT factory is
exported to work around that boundary.
- [x] Public API dependency/leak/cycle and protected-ledger checks pass.
- [x] Record the exact PROJECT fact/port manifest in
`.agent-loop/initiatives/WS-ARCH-001-modular-monolith-boundaries/evidence/WS-ARCH-001-02B-project-manifest.md`.

## Verification commands

```bash
(cd backend && .venv/bin/python -m ruff check app/modules/projects app/modules/tasks/pre_submit_context.py tests/architecture/test_module_boundaries.py)
(cd backend && PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 .venv/bin/python -m pytest -q -p pytest_asyncio.plugin -p pytest_cov.plugin tests/architecture/test_module_boundaries.py tests/test_projects.py tests/test_tasks.py tests/test_submission_bundle_admission.py --cov=app.modules.projects.api --cov-report=term-missing --cov-fail-under=90)
(cd backend && .venv/bin/python -m ruff check app/modules/projects tests/architecture/test_module_boundaries.py tests/projects/test_locked_policy_context.py)
(cd backend && export WORKSTREAM_TEST_DATABASE_URL="${WORKSTREAM_TEST_DATABASE_URL:?set WORKSTREAM_TEST_DATABASE_URL}" && PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 .venv/bin/python -m pytest -q -p pytest_asyncio.plugin -p pytest_cov.plugin tests/architecture/test_module_boundaries.py tests/projects/test_locked_policy_context.py --cov=app.modules.projects.api --cov=app.modules.projects.locked_policy_repository --cov-report=term-missing --cov-fail-under=90)
(cd backend && .venv/bin/python -m scripts.module_boundaries validate --protected-base origin/main)
(cd backend && .venv/bin/python -m scripts.behavior_ownership validate)
python3 scripts/check_markdown_links.py
git diff --check
```
Expand All @@ -85,8 +108,8 @@ docs, reuse/dedup, and test delta.

## Human review focus

Exact locked-policy lineage, immutable output, and absence of PROJECT behavior
inside TASK or ART.
Exact historical locked-policy lineage, deep immutable output, successor
non-substitution, and absence of PROJECT behavior inside TASK or ART.

## Stop conditions

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# WS-ARCH-001-02B PROJECT Public Capability Manifest

## Public package

`backend/app/modules/projects/api` exposes only dependency-safe immutable
contracts:

- `ProjectLockedPolicyContextRequest` carries exact TASK-stamped project,
guide-version, source-snapshot, effective-policy, and pre-submit-policy
selectors and hashes;
- `CanonicalJsonObject` copies a JSON mapping into canonical immutable text and
exposes no parsed mutable projection;
- `ProjectLockedPolicyContextFacts` carries the exact guide ID/status,
snapshot identity, effective-policy identity/status/body, pre-submit-policy
identity/status/compiler, and compiled bundle;
- `ProjectLockedPolicyContextPort` defines the transaction-bound owner
capability;
- the three lifecycle aliases and `ProjectLockedPolicyContextUnavailable`
expose closed historical states and one stable failure code.

The public package imports no ORM model, repository, SQLAlchemy session, TASK,
ART, CHECKER, AUTH, mutable mapping field, guide content, source manifest,
source-item metadata, actor provenance, or audit evidence.

## Owner-local implementation

`ProjectLockedPolicyRepository.lock_locked_policy_context(...)` locks rows in
this order:

1. exact project;
2. exact project guide by project and version;
3. exact guide-source snapshot ID;
4. exact effective-policy ID;
5. exact pre-submit-policy ID.

It verifies every project, guide, version, snapshot, effective-policy, and
pre-submit-policy cross-link; recomputes the snapshot, effective-policy, and
compiled-bundle hashes; requires complete compiled fields; and returns only
canonical immutable facts.

Locked-context resolution never uses current/latest selectors. Exact rows that
were validly locked remain valid in their canonical `superseded` state.
Current successor substitution, draft/pending/incomplete rows, missing rows,
cross-project lineage, or stored-body/hash drift fail closed.

## Boundary disposition

This chunk changes no route, authorization availability, project lifecycle,
policy compilation, task preparation, ART behavior, or CHECKER behavior. Live
caller composition is deliberately deferred to a later contract with the
necessary application/composition paths. No session, repository, or concrete
PROJECT factory crosses the public boundary.

The eligible PROJECT implementation is registered as lifecycle behavior in the
existing ownership partition and exact additive-transition allowlist.

## Deterministic proof

- public canonical JSON copies nested caller data and exposes immutable text
only;
- current and exact superseded locked rows resolve to identical canonical
lineage identities;
- draft, pending, cross-project, successor-substituted, and hash-drifted
contexts fail closed;
- PostgreSQL state-matrix tests exercise exact historical resolution, a
two-session test proves pre-submit-row contention, and unit SQL assertions
prove all five selected queries use `FOR UPDATE`;
- focused public API and locked-policy repository coverage is 100 percent;
- protected-base module-boundary and behavior-ownership validation pass;
- the focused capability tests live in a bounded PROJECT test module, and the
frozen test-structure ledger records no new or grown structural debt;
- that bounded module is registered in the existing `project_lifecycle` CI
lane;
- Ruff, Markdown-link, stale-wording, and diff checks pass.

The PostgreSQL cases run in hosted Backend/Agent Gates because this worktree
has no `WORKSTREAM_TEST_DATABASE_URL`; the chunk command fails fast when that
required database URL is absent.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# WS-ARCH-001-02B External Review Response

## Comments addressed

- Replaced the stale AUTH-only transition-validator wording with the exact
module public-API foundation terminology.
- Extracted exact historical-lineage resolution from the legacy 1,300-line
PROJECT repository into `ProjectLockedPolicyRepository` and added that
bounded module to the chunk's 90 percent coverage command.
- Replaced iteration-count PostgreSQL lock polling with a wall-clock deadline
and a bounded positive polling interval.

## Comments deferred

None.

## Human decisions needed

None beyond the existing PR review and merge decision.

## Commands rerun

- Ruff formatting and lint for the affected PROJECT and CI files.
- Focused PROJECT boundary and locked-policy tests with coverage for
`app.modules.projects.api` and
`app.modules.projects.locked_policy_repository`.
- Module-boundary, behavior-ownership, and test-structure validators.
- Markdown-link and whitespace checks.

## Remaining risks

PostgreSQL-backed cases remain delegated to the hosted `project_lifecycle`
lane because no local test database URL is configured.
10 changes: 9 additions & 1 deletion .ci/behavior-ownership/partition.v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,10 @@
"group": "shared",
"target": "backend/app/modules/outbox/service.py"
},
{
"group": "lifecycle",
"target": "backend/app/modules/projects/api/locked_policy.py"
},
{
"group": "lifecycle",
"target": "backend/app/modules/projects/authorization_reads.py"
Expand Down Expand Up @@ -576,6 +580,10 @@
"group": "lifecycle",
"target": "backend/app/modules/projects/guide_setup_continuation.py"
},
{
"group": "lifecycle",
"target": "backend/app/modules/projects/locked_policy_repository.py"
},
{
"group": "lifecycle",
"target": "backend/app/modules/projects/models.py"
Expand Down Expand Up @@ -789,7 +797,7 @@
"target": "backend/scripts/week2_api_e2e.py"
}
],
"authority_digest": "a26548020ee449fb68a72c86e17bd1f98a6d47804e55d09a5f1a9f3b4c5fb095",
"authority_digest": "6025c45a4c93c4fbe7039f2730f1f97176b985ebf0700d15d15ad82a9f095a78",
"protected_base_commit": "7676ce4347db0c9694962a9b587a20765e16eac6",
"schema": "workstream.behavior-ownership-partition.v1"
}
25 changes: 25 additions & 0 deletions backend/app/modules/projects/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Dependency-safe public API for the PROJECTS business module."""

from app.modules.projects.api.locked_policy import (
CanonicalJsonObject,
ProjectLockedPolicyContextFacts,
ProjectLockedPolicyContextPort,
ProjectLockedPolicyContextRequest,
ProjectLockedPolicyContextUnavailable,
ProjectLockedPolicyEffectiveStatus,
ProjectLockedPolicyFailure,
ProjectLockedPolicyGuideStatus,
ProjectLockedPolicyPreSubmitStatus,
)

__all__ = (
"CanonicalJsonObject",
"ProjectLockedPolicyContextFacts",
"ProjectLockedPolicyContextPort",
"ProjectLockedPolicyContextRequest",
"ProjectLockedPolicyContextUnavailable",
"ProjectLockedPolicyEffectiveStatus",
"ProjectLockedPolicyFailure",
"ProjectLockedPolicyGuideStatus",
"ProjectLockedPolicyPreSubmitStatus",
)
Loading
Loading