Direct annotation allocation enforcement¶
Boundary¶
Implement item 1 of STATUS.md, continuing the MVP merged in #2991. Do not enable any environment flag, introduce reviewer UI, or implement rebalancing. An allocation percentage remains a share of review slots, not a percentage of studies.
Implementation and acceptance map¶
| Step | Change | Acceptance evidence |
|---|---|---|
| 1 | Extract the existing fail-closed bucket computation into a shared core policy | Existing proportional next-study tests remain green; direct eligibility agrees with the same bucket plan |
| 2 | Guard GET .../studies/{studyId}/review before usage writes and reservation/presence setup |
Out-of-share or unconfigured reviewers receive 404 with no side effects; allocated reviewers retain access |
| 3 | Guard PUT .../studies/{studyId}/session/{sessionId} before mapping and calling the submission orchestrator |
Out-of-share new annotation submissions return 404 without calling the writer; approved submissions still save |
| 4 | Bind the flagged ordinary submission's body stage/session IDs to its route | A caller cannot check eligibility against one stage or saved session and write another; mismatches return 400 |
| 5 | Preserve saved work and existing feature boundaries | Saved ordinary sessions must match the reviewer and stage, plus the submitted session ID on writes; another stage/reviewer or a reconciliation session cannot grant an exemption |
| 5a | Recheck eligibility against each freshly loaded submission attempt | Concurrent deletion of an exempt saved session cannot recreate out-of-share work on a retry; denial precedes session mutation and guarded save |
| 6 | Validate and document the slice | Focused core and API tests, broader relevant .NET tests/build, documentation validation, and a reviewable PR with results |
Compatibility¶
- Allocation is checked only when the runtime flag is on; disabled/missing stage allocation retains existing eligibility behaviour. Flagged ordinary submissions must still have matching route/body stage and session IDs, even on stages without allocation.
- Existing saved ordinary sessions (incomplete or completed) remain readable and editable despite stale allocation; this preserves resumption and correction, not creation of new sessions.
- A slot reservation alone is not evidence of a saved session and cannot bypass direct endpoint eligibility. Random assignment's existing-reservation behaviour is unchanged.
- Invalid reviewer membership or configuration fails closed for new work, without a reservation, usage write, submission, or server error. Existing authorization and capacity checks still apply.
- Reconciliation reads and reconciliation submissions keep their existing behaviour. Submission mode is taken from the body used by the existing save path, not the unused query parameter.
- No changes to screening, session deletion, SignalR protocol, or transaction/statistics persistence. The submission orchestrator repeats eligibility before mutating each loaded study, including retries. Membership lifecycle policy and cross-request configuration consistency remain later roadmap work.
Validation cases¶
- Two reviewers with a one-review-per-study 50/50 split: direct read and new submission agree with each reviewer's bucket membership.
- Flag off, stage allocation disabled, or no allocation: legacy behaviour.
- Reviewer omitted; another configured member removed; invalid reviews-per-study: deny new work.
- Saved own same-stage session: preserve access, including completing/correcting that exact session.
- Other reviewer's session, other-stage session, reconciliation session, or forged body IDs: no saved-session exemption.
- Reconciliation GET/PUT remain unfiltered;
?reconciliation=truecannot bypass an ordinary body. - Test active-reviewer tracking both on and off so it cannot accidentally gate allocation checks.
- Delete or replace the exempt saved session between optimistic-concurrency attempts: return 404 without a second mutation/save. Retained exact sessions, allocated work, reconciliation and flag-off submissions retain their existing retry behaviour.
Follow-on order¶
Reviewer assignment UI and remaining-share count, then administrator allocation progress, membership lifecycle, third-party stage authorization, editor/save coordination, measured performance work, history-aware rebalancing, and only then optional onboarding/co-allocation work. The feature is not fully usable or ready for rollout merely because this first slice is implemented.
Implementation evidence (2026-09-05)¶
Steps 1–5 are implemented; step 6 has completed local validation and awaits PR review/merge. There are 51 new controller cases, ten submission-orchestrator cases and five new core policy cases, using the existing xUnit, Moq and mapper collection patterns. No new packages or database migrations are required.
- Full
SyRF.ProjectManagement.Core.Tests: 2,116 passed, one existing skipped test. - Full
SyRF.API.Endpoint.Tests: 1,227 passed, none skipped (including the saved-session retry fix). - API and Project Management endpoint projects build successfully with .NET SDK 10.0.100.
- Documentation validation completed with no errors and 62 warnings;
git diff --checkpassed. - The broader suites include existing next-study, capacity, reconciliation, submission-retry, statistics-owner and allocation-configuration regression tests.
Reproduce from the worktree root:
dotnet test src/libs/project-management/SyRF.ProjectManagement.Core.Tests/SyRF.ProjectManagement.Core.Tests.csproj -m:2
dotnet test src/services/api/SyRF.API.Endpoint.Tests/SyRF.API.Endpoint.Tests.csproj -m:2
dotnet build src/services/project-management/SyRF.ProjectManagement.Endpoint/SyRF.ProjectManagement.Endpoint.csproj -m:2
./docs/scripts/validate-docs.sh --verbose
git diff --check
This is local code/test evidence, not preview or production evidence. No flags, environment configuration, or live data were changed. No browser UI is added in this slice.